Commit Diff


commit - 9169cf965b11b6344d26b8e48154b7d9c83feb83
commit + 18e7ebf97d7549bb8c88fc683e1b6e36576be347
blob - 6b49757fb26a23f949a702605c73ace91be74886
blob + bf114b2bd34b02e78443d612ca772aff624c3fbe
--- docs/adr/0008-immutable-system-epochs.md
+++ docs/adr/0008-immutable-system-epochs.md
@@ -11,6 +11,14 @@ current `project_context` policy selects a stored vari
 other, while current Provider, Tool, credential, and authority configuration
 remains live.
 
+The complete variants are authoritative opaque bytes, not checksums of the
+stored components. Replay and in-memory copies import them verbatim after
+version, length, field, and raw-summary-tail validation that does not invoke
+composition code. Components remain useful for audit and Subagent
+construction, but current composition code must never normalize a stored Lead
+variant. A Compaction replaces only the raw summary tail while preserving each
+exact stored variant prefix, including its historical wrapper.
+
 `/clear` adopts the invocation's current base, reuses the Session-cached
 personal snapshot without re-reading it, and admits a new in-tree project
 snapshot only when `project_context yes`. Under `project_context no`, the new
@@ -34,6 +42,9 @@ parsing model-written continuation notes.
   model-influenced file edits without the Owner's explicit `/clear` boundary.
 - Rebuilding stored variants on resume: this reintroduces the configuration
   and binary drift the epoch exists to remove.
+- Treating stored components as an integrity hash for the complete variants:
+  that would make a valid old epoch depend on today's composition code and
+  fail closed on intended implementation evolution rather than corruption.
 - Stable Journal offsets for a retained suffix: storage layout, legacy
   records, and partial tails must not become semantic identifiers.
 
blob - a99ea2eb75e7a4bf509218bbe4179107f3db3f1e
blob + 17b42cb2275bf32869e5c535567f0ab05bef670a
--- docs/compaction.md
+++ docs/compaction.md
@@ -19,6 +19,11 @@ file sets, and the exact Retained Projection. On resum
 recomposing either. Current Provider, model, Tool, credential, protection,
 write, and network policy remains live.
 
+The source components are audit and Subagent inputs, not reconstruction
+checksums. If composition code has changed since an epoch was written,
+Compaction replaces only the exact old raw-summary tail and preserves the
+entire stored prefix, including its historical summary wrapper.
+
 External context changes are admitted only at a fresh Session or `/clear`.
 `/clear` adopts the invocation's current base, reuses the Session-cached
 personal snapshot without re-reading it, and re-reads the in-tree project file
blob - 5364cf067c69941889e8ca1f030a52d1179585d6
blob + cffeea1d6d36fc90de905cd182af3d127326fa3d
--- handoff/verification.md
+++ handoff/verification.md
@@ -133,9 +133,10 @@ states the property, not just examples:
   authoritative full/restricted variant, reason, cumulative structured file
   sets, and retained Projection block. Fresh start, clear, successful
   Compaction, and legacy migration synchronize before request/publication.
-  Unsupported,
-  missing, duplicate, inconsistent-derived, over-bound, and malformed fields
-  hard-fail; a partial final epoch is ignored and retried. Cross-process
+  Unsupported, missing, duplicate, over-bound, malformed-encoding, and
+  forbidden-NUL component fields hard-fail; independently authoritative
+  complete variants are never compared with a current-code recomposition. A
+  partial final epoch is ignored and retried. Cross-process
   fixtures change base configuration, context files, composition code,
   Provider/model, Tools, and authority policy, then prove current
   `project_context` selects exact stored bytes for all Provider codecs and
blob - e361bf736cd133685ed8b034cdd4a8229e31a182
blob + 796a50f5e5ae3b70a0e1b0f3ca2f705bc6150b42
--- regress/journal/journal_test.c
+++ regress/journal/journal_test.c
@@ -203,6 +203,14 @@ test_system_epoch_roundtrip(void)
 	static const u_char personal[] = { 'p', 'e', 'r', 0xfe };
 	static const u_char project[] = { 'p', 'r', 'o', 'j', 0xfd };
 	static const u_char summary[] = { 's', 'u', 'm', '\0', 0xfc };
+	static const u_char authoritative_full[] = {
+	    'o', 'l', 'd', '-', 'f', 'u', 'l', 'l', '-', 'w', 'r', 'a', 'p',
+	    's', 'u', 'm', '\0', 0xfc
+	};
+	static const u_char authoritative_restricted[] = {
+	    'o', 'l', 'd', '-', 'r', 'e', 's', 't', 'r', 'i', 'c', 't',
+	    'e', 'd', '-', 'w', 'r', 'a', 'p', 's', 'u', 'm', '\0', 0xfc
+	};
 	struct system_epoch_input input;
 	struct system_epoch epoch;
 	struct compaction_files *files;
@@ -222,6 +230,15 @@ test_system_epoch_roundtrip(void)
 	input.summary.len = sizeof(summary);
 	system_epoch_init(&epoch);
 	CHECK(system_epoch_build(&epoch, &input) == 0);
+	/* Simulate a stored epoch written by an older composition implementation:
+	 * components remain auditable, while these complete Provider inputs are
+	 * independently authoritative. */
+	buf_reset(&epoch.full);
+	buf_add(&epoch.full, authoritative_full,
+	    sizeof(authoritative_full));
+	buf_reset(&epoch.restricted);
+	buf_add(&epoch.restricted, authoritative_restricted,
+	    sizeof(authoritative_restricted));
 	files = compaction_files_new();
 	CHECK(compaction_files_add(files, COMPACTION_FILE_READ,
 	    "README.md", 9) == 0);
@@ -1072,10 +1089,16 @@ test_epoch_malformed_and_bounds(void)
 	make_epoch(&clear, SYSTEM_EPOCH_CLEAR, NULL, 0);
 	make_epoch(&bad_clear, SYSTEM_EPOCH_CLEAR, "not empty", 9);
 
-	/* Stored complete variants must match the rebuilt components exactly. */
+	/* A well-formed complete variant is authoritative even when it differs
+	 * from today's component composition. */
 	epoch_session(&start, id, path);
 	CHECK(path_change_after(path, "\"full\":\"", '0') == 0);
-	restore_fails(id);
+	journal_state_init(&state);
+	CHECK(journal_restore(dir, id, &state) == 0);
+	CHECK(state.have_epoch);
+	CHECK(buf_eq(&state.epoch.base, "pinned base", 11));
+	CHECK(state.epoch.full.len > 0 && state.epoch.full.data[0] == '\0');
+	journal_state_free(&state);
 
 	epoch_session(&start, id, path);
 	CHECK(path_change_after(path, "\"version\":", '9') == 0);
blob - 03f2f68bd17979f0b2124a0f8c79a36b18a06687
blob + dfbc965abf7c9e7d4c5af994e970ec45e31809a6
--- regress/system_epoch/system_epoch_test.c
+++ regress/system_epoch/system_epoch_test.c
@@ -244,7 +244,7 @@ test_all_reasons_and_binary_summary(void)
 }
 
 static void
-test_copy_failure_is_transactional(void)
+test_copy_is_exact_and_transactional(void)
 {
 	struct system_epoch_input input;
 	struct system_epoch dst, src;
@@ -261,17 +261,129 @@ test_copy_failure_is_transactional(void)
 	input.base.len = 6;
 	CHECK(system_epoch_build(&src, &input) == 0);
 	src.full.data[0] = 'X';
-	CHECK(system_epoch_copy(&dst, &src) == -1);
-	CHECK(dst.reason == SYSTEM_EPOCH_START);
-	CHECK(buf_is(&dst.base, "DESTINATION", 11));
-	CHECK(buf_is(&dst.full, "DESTINATION", 11));
+	CHECK(system_epoch_copy(&dst, &src) == 0);
+	CHECK(dst.reason == SYSTEM_EPOCH_COMPACT);
+	CHECK(buf_is(&dst.base, "SOURCE", 6));
+	CHECK(buf_is(&dst.full, "XOURCE", 6));
 	src.version = SYSTEM_EPOCH_VERSION + 1;
 	CHECK(system_epoch_copy(&dst, &src) == -1);
-	CHECK(buf_is(&dst.full, "DESTINATION", 11));
+	CHECK(buf_is(&dst.full, "XOURCE", 6));
 	system_epoch_free(&src);
 	system_epoch_free(&dst);
 }
 
+static void
+test_exact_import_and_summary_replacement(void)
+{
+	static const char full_prefix[] = "OLD-COMPOSER-FULL";
+	static const char restricted_prefix[] = "OLD-COMPOSER-RESTRICTED";
+	static const char marker[] =
+	    "\n\nSummary of the conversation so far "
+	    "(your own continuation notes):\n\n";
+	static const u_char binary_summary[] = { 'N', '\0', 'W' };
+	struct system_epoch_exact_input exact;
+	struct system_epoch imported, copy, compacted, repeated, keep, broken;
+	struct system_epoch_input input;
+	struct system_epoch_span summary;
+	size_t off;
+
+	system_epoch_init(&imported);
+	system_epoch_init(&copy);
+	system_epoch_init(&compacted);
+	system_epoch_init(&repeated);
+	system_epoch_init(&keep);
+	system_epoch_init(&broken);
+	memset(&exact, 0, sizeof(exact));
+	exact.version = SYSTEM_EPOCH_VERSION;
+	exact.reason = SYSTEM_EPOCH_START;
+	exact.base.data = "CURRENT-COMPONENT-BASE";
+	exact.base.len = 22;
+	exact.personal.data = "PERSONAL";
+	exact.personal.len = 8;
+	exact.project.data = "PROJECT";
+	exact.project.len = 7;
+	exact.full.data = full_prefix;
+	exact.full.len = sizeof(full_prefix) - 1;
+	exact.restricted.data = restricted_prefix;
+	exact.restricted.len = sizeof(restricted_prefix) - 1;
+	CHECK(system_epoch_import(&imported, &exact) == 0);
+	CHECK(buf_is(&imported.base, "CURRENT-COMPONENT-BASE", 22));
+	CHECK(buf_is(&imported.full, full_prefix, sizeof(full_prefix) - 1));
+	CHECK(buf_is(&imported.restricted, restricted_prefix,
+	    sizeof(restricted_prefix) - 1));
+	CHECK(system_epoch_copy(&copy, &imported) == 0);
+	imported.full.data[0] = 'X';
+	CHECK(buf_is(&copy.full, full_prefix, sizeof(full_prefix) - 1));
+
+	summary.data = binary_summary;
+	summary.len = sizeof(binary_summary);
+	CHECK(system_epoch_replace_summary(&compacted, &copy, &summary) == 0);
+	CHECK(compacted.reason == SYSTEM_EPOCH_COMPACT);
+	CHECK(buf_is(&compacted.base, "CURRENT-COMPONENT-BASE", 22));
+	CHECK(buf_is(&compacted.personal, "PERSONAL", 8));
+	CHECK(buf_is(&compacted.project, "PROJECT", 7));
+	CHECK(buf_is(&compacted.summary, binary_summary,
+	    sizeof(binary_summary)));
+	off = sizeof(full_prefix) - 1;
+	CHECK(compacted.full.len == off + sizeof(marker) - 1 +
+	    sizeof(binary_summary));
+	CHECK(memcmp(compacted.full.data, full_prefix, off) == 0);
+	CHECK(memcmp(compacted.full.data + off, marker,
+	    sizeof(marker) - 1) == 0);
+	CHECK(memcmp(compacted.full.data + compacted.full.len -
+	    sizeof(binary_summary), binary_summary,
+	    sizeof(binary_summary)) == 0);
+	off = sizeof(restricted_prefix) - 1;
+	CHECK(compacted.restricted.len == off + sizeof(marker) - 1 +
+	    sizeof(binary_summary));
+	CHECK(memcmp(compacted.restricted.data, restricted_prefix, off) == 0);
+
+	summary.data = "NEXT";
+	summary.len = 4;
+	CHECK(system_epoch_replace_summary(&repeated, &compacted,
+	    &summary) == 0);
+	CHECK(repeated.full.len == sizeof(full_prefix) - 1 +
+	    sizeof(marker) - 1 + 4);
+	CHECK(memcmp(repeated.full.data, full_prefix,
+	    sizeof(full_prefix) - 1) == 0);
+	CHECK(memcmp(repeated.full.data + repeated.full.len - 4,
+	    "NEXT", 4) == 0);
+
+	memset(&input, 0, sizeof(input));
+	input.reason = SYSTEM_EPOCH_START;
+	input.base.data = "KEEP";
+	input.base.len = 4;
+	CHECK(system_epoch_build(&keep, &input) == 0);
+	exact.reason = SYSTEM_EPOCH_COMPACT;
+	exact.summary.data = "OLD";
+	exact.summary.len = 3;
+	exact.full.data = "LEGACY-FULL-WRAPPER::OLD";
+	exact.full.len = 24;
+	exact.restricted.data = "LEGACY-RESTRICTED-WRAPPER::OLD";
+	exact.restricted.len = 30;
+	CHECK(system_epoch_import(&broken, &exact) == 0);
+	CHECK(system_epoch_replace_summary(&keep, &broken, &summary) == 0);
+	CHECK(buf_is(&keep.full, "LEGACY-FULL-WRAPPER::NEXT", 25));
+	CHECK(buf_is(&keep.restricted,
+	    "LEGACY-RESTRICTED-WRAPPER::NEXT", 31));
+
+	exact.full.data = "NO-SUFFIX";
+	exact.full.len = 9;
+	CHECK(system_epoch_import(&keep, &exact) == -1);
+	CHECK(buf_is(&keep.full, "LEGACY-FULL-WRAPPER::NEXT", 25));
+
+	exact.version = SYSTEM_EPOCH_VERSION + 1;
+	CHECK(system_epoch_import(&keep, &exact) == -1);
+	CHECK(buf_is(&keep.full, "LEGACY-FULL-WRAPPER::NEXT", 25));
+
+	system_epoch_free(&broken);
+	system_epoch_free(&keep);
+	system_epoch_free(&repeated);
+	system_epoch_free(&compacted);
+	system_epoch_free(&copy);
+	system_epoch_free(&imported);
+}
+
 int
 main(void)
 {
@@ -279,6 +391,7 @@ main(void)
 	test_owned_state();
 	test_invalid_input_is_transactional();
 	test_all_reasons_and_binary_summary();
-	test_copy_failure_is_transactional();
+	test_copy_is_exact_and_transactional();
+	test_exact_import_and_summary_replacement();
 	REGRESS_END();
 }
blob - ce8376919a1290d045d04e1b91fd5519fc35e8c2
blob + 1f11d8cd77d3a5323bc474977068436ff804b1e9
--- src/fugu/coord.c
+++ src/fugu/coord.c
@@ -4256,7 +4256,7 @@ compact_projection(struct coord *c, int active, int fo
 	struct generation_result generation;
 	struct generation_span text;
 	struct system_epoch epoch;
-	struct system_epoch_input input;
+	struct system_epoch_span replacement_summary;
 	struct turn_txn_projection projection;
 	struct msglist compact_messages;
 	struct msg *document_message;
@@ -4370,17 +4370,10 @@ compact_projection(struct coord *c, int active, int fo
 	}
 	generation_result_dispose(&generation);
 
-	memset(&input, 0, sizeof(input));
-	input.reason = SYSTEM_EPOCH_COMPACT;
-	input.base.data = c->epoch.base.data;
-	input.base.len = c->epoch.base.len;
-	input.personal.data = c->epoch.personal.data;
-	input.personal.len = c->epoch.personal.len;
-	input.project.data = c->epoch.project.data;
-	input.project.len = c->epoch.project.len;
-	input.summary.data = summary.data;
-	input.summary.len = summary.len;
-	if (system_epoch_build(&epoch, &input) == -1) {
+	replacement_summary.data = summary.data;
+	replacement_summary.len = summary.len;
+	if (system_epoch_replace_summary(&epoch, &c->epoch,
+	    &replacement_summary) == -1) {
 		why = "replacement System epoch exceeds its bound";
 		goto fail;
 	}
blob - 0c567c4088e25bf7ef84c288e1837ec1e06faaf1
blob + c1869d105447dd1b325f4aa61f1177841eade438
--- src/fugu/journal.c
+++ src/fugu/journal.c
@@ -1285,13 +1285,6 @@ byte_cmp(const void *ap, size_t alen, const void *bp, 
 }
 
 static int
-buf_same(const struct buf *a, const struct buf *b)
-{
-	return (a->len == b->len &&
-	    (a->len == 0 || memcmp(a->data, b->data, a->len) == 0));
-}
-
-static int
 parse_file_array(const struct json *jd, int tok,
     enum compaction_file_kind kind, struct compaction_files *files)
 {
@@ -1512,7 +1505,7 @@ static int
 parse_epoch_record(const struct json *jd, int root, struct parsed_epoch *out)
 {
 	struct system_epoch candidate;
-	struct system_epoch_input input;
+	struct system_epoch_exact_input input;
 	struct buf	 base, personal, project, summary, full, restricted;
 	char		*key;
 	size_t		 keylen;
@@ -1569,13 +1562,10 @@ parse_epoch_record(const struct json *jd, int root, st
 	    decode_hex(jd, field[8], &full,
 	    SYSTEM_EPOCH_VARIANT_MAX) == -1 ||
 	    decode_hex(jd, field[9], &restricted,
-	    SYSTEM_EPOCH_VARIANT_MAX) == -1 ||
-	    (base.len > 0 && memchr(base.data, '\0', base.len) != NULL) ||
-	    (personal.len > 0 && memchr(personal.data, '\0', personal.len)
-	    != NULL) ||
-	    (project.len > 0 && memchr(project.data, '\0', project.len) != NULL))
+	    SYSTEM_EPOCH_VARIANT_MAX) == -1)
 		goto done;
 	memset(&input, 0, sizeof(input));
+	input.version = SYSTEM_EPOCH_VERSION;
 	input.reason = out->epoch.reason;
 	input.base.data = base.data;
 	input.base.len = base.len;
@@ -1585,9 +1575,11 @@ parse_epoch_record(const struct json *jd, int root, st
 	input.project.len = project.len;
 	input.summary.data = summary.data;
 	input.summary.len = summary.len;
-	if (system_epoch_build(&candidate, &input) == -1 ||
-	    !buf_same(&candidate.full, &full) ||
-	    !buf_same(&candidate.restricted, &restricted))
+	input.full.data = full.data;
+	input.full.len = full.len;
+	input.restricted.data = restricted.data;
+	input.restricted.len = restricted.len;
+	if (system_epoch_import(&candidate, &input) == -1)
 		goto done;
 	system_epoch_free(&out->epoch);
 	out->epoch = candidate;
blob - 14e9c5e26ee7067784bd3d946062195c7e02ef71
blob + 0060d2b27f01fe8c8f8ab2d40b2502b13cf3910b
--- src/fugu/system_epoch.c
+++ src/fugu/system_epoch.c
@@ -49,17 +49,35 @@ size_add(size_t *total, size_t add)
 }
 
 static int
+components_valid(enum system_epoch_reason reason,
+    const struct system_epoch_span *base,
+    const struct system_epoch_span *personal,
+    const struct system_epoch_span *project,
+    const struct system_epoch_span *summary)
+{
+	if (!reason_valid(reason) || !span_valid(base) ||
+	    !span_valid(personal) || !span_valid(project) ||
+	    !span_valid(summary) || base->len > SYSTEM_EPOCH_BASE_MAX ||
+	    personal->len > SYSTEM_EPOCH_EXTERNAL_MAX ||
+	    project->len > SYSTEM_EPOCH_EXTERNAL_MAX ||
+	    summary->len > SYSTEM_EPOCH_SUMMARY_MAX)
+		return (0);
+	if ((base->len > 0 && memchr(base->data, '\0', base->len) != NULL) ||
+	    (personal->len > 0 && memchr(personal->data, '\0',
+	    personal->len) != NULL) ||
+	    (project->len > 0 && memchr(project->data, '\0',
+	    project->len) != NULL))
+		return (0);
+	return (1);
+}
+
+static int
 input_valid(const struct system_epoch_input *input)
 {
 	size_t full, restricted, summary = 0;
 
-	if (input == NULL || !reason_valid(input->reason) ||
-	    !span_valid(&input->base) || !span_valid(&input->personal) ||
-	    !span_valid(&input->project) || !span_valid(&input->summary) ||
-	    input->base.len > SYSTEM_EPOCH_BASE_MAX ||
-	    input->personal.len > SYSTEM_EPOCH_EXTERNAL_MAX ||
-	    input->project.len > SYSTEM_EPOCH_EXTERNAL_MAX ||
-	    input->summary.len > SYSTEM_EPOCH_SUMMARY_MAX)
+	if (input == NULL || !components_valid(input->reason, &input->base,
+	    &input->personal, &input->project, &input->summary))
 		return (0);
 	full = restricted = input->base.len;
 	if (input->personal.len > 0 &&
@@ -80,13 +98,29 @@ input_valid(const struct system_epoch_input *input)
 	if (full > SYSTEM_EPOCH_VARIANT_MAX ||
 	    restricted > SYSTEM_EPOCH_VARIANT_MAX)
 		return (0);
-	if ((input->base.len > 0 && memchr(input->base.data, '\0',
-	    input->base.len) != NULL) ||
-	    (input->personal.len > 0 && memchr(input->personal.data, '\0',
-	    input->personal.len) != NULL) ||
-	    (input->project.len > 0 && memchr(input->project.data, '\0',
-	    input->project.len) != NULL))
+	return (1);
+}
+
+static int
+exact_input_valid(const struct system_epoch_exact_input *input)
+{
+	if (input == NULL || input->version != SYSTEM_EPOCH_VERSION ||
+	    !components_valid(input->reason, &input->base, &input->personal,
+	    &input->project, &input->summary) || !span_valid(&input->full) ||
+	    !span_valid(&input->restricted) ||
+	    input->full.len > SYSTEM_EPOCH_VARIANT_MAX ||
+	    input->restricted.len > SYSTEM_EPOCH_VARIANT_MAX)
 		return (0);
+	if (input->summary.len > 0 &&
+	    (input->full.len < input->summary.len ||
+	    input->restricted.len < input->summary.len ||
+	    memcmp((const u_char *)input->full.data + input->full.len -
+	    input->summary.len, input->summary.data,
+	    input->summary.len) != 0 ||
+	    memcmp((const u_char *)input->restricted.data +
+	    input->restricted.len - input->summary.len,
+	    input->summary.data, input->summary.len) != 0))
+		return (0);
 	return (1);
 }
 
@@ -115,13 +149,6 @@ add_summary(struct buf *out, const struct system_epoch
 	add_span(out, summary);
 }
 
-static int
-buf_equal(const struct buf *a, const struct buf *b)
-{
-	return (a->len == b->len &&
-	    (a->len == 0 || memcmp(a->data, b->data, a->len) == 0));
-}
-
 void
 system_epoch_init(struct system_epoch *epoch)
 {
@@ -176,37 +203,132 @@ system_epoch_build(struct system_epoch *epoch,
 }
 
 int
+system_epoch_import(struct system_epoch *epoch,
+    const struct system_epoch_exact_input *input)
+{
+	struct system_epoch next;
+
+	if (epoch == NULL || !exact_input_valid(input))
+		return (-1);
+	system_epoch_init(&next);
+	next.version = input->version;
+	next.reason = input->reason;
+	add_span(&next.base, &input->base);
+	add_span(&next.personal, &input->personal);
+	add_span(&next.project, &input->project);
+	add_span(&next.summary, &input->summary);
+	add_span(&next.full, &input->full);
+	add_span(&next.restricted, &input->restricted);
+	system_epoch_free(epoch);
+	*epoch = next;
+	return (0);
+}
+
+static void
+exact_input_from_epoch(struct system_epoch_exact_input *input,
+    const struct system_epoch *epoch)
+{
+	memset(input, 0, sizeof(*input));
+	input->version = epoch->version;
+	input->reason = epoch->reason;
+	input->base.data = epoch->base.data;
+	input->base.len = epoch->base.len;
+	input->personal.data = epoch->personal.data;
+	input->personal.len = epoch->personal.len;
+	input->project.data = epoch->project.data;
+	input->project.len = epoch->project.len;
+	input->summary.data = epoch->summary.data;
+	input->summary.len = epoch->summary.len;
+	input->full.data = epoch->full.data;
+	input->full.len = epoch->full.len;
+	input->restricted.data = epoch->restricted.data;
+	input->restricted.len = epoch->restricted.len;
+}
+
+int
 system_epoch_copy(struct system_epoch *dst, const struct system_epoch *src)
 {
-	struct system_epoch_input input;
-	struct system_epoch next;
+	struct system_epoch_exact_input input;
 
-	if (dst == NULL || src == NULL || src->version != SYSTEM_EPOCH_VERSION)
+	if (dst == NULL || src == NULL)
 		return (-1);
-	if (dst == src)
+	exact_input_from_epoch(&input, src);
+	return (system_epoch_import(dst, &input));
+}
+
+static int
+variant_prefix_len(const struct system_epoch *epoch,
+    const struct buf *variant, size_t *prefix_len)
+{
+	if (epoch->summary.len == 0) {
+		*prefix_len = variant->len;
 		return (0);
-	memset(&input, 0, sizeof(input));
-	input.reason = src->reason;
-	input.base.data = src->base.data;
-	input.base.len = src->base.len;
-	input.personal.data = src->personal.data;
-	input.personal.len = src->personal.len;
-	input.project.data = src->project.data;
-	input.project.len = src->project.len;
-	input.summary.data = src->summary.data;
-	input.summary.len = src->summary.len;
-	system_epoch_init(&next);
-	if (system_epoch_build(&next, &input) == -1 ||
-	    !buf_equal(&next.full, &src->full) ||
-	    !buf_equal(&next.restricted, &src->restricted)) {
-		system_epoch_free(&next);
-		return (-1);
 	}
-	system_epoch_free(dst);
-	*dst = next;
+	if (variant->len < epoch->summary.len)
+		return (-1);
+	*prefix_len = variant->len - epoch->summary.len;
+	if (memcmp(variant->data + *prefix_len, epoch->summary.data,
+	    epoch->summary.len) != 0)
+		return (-1);
 	return (0);
 }
 
+int
+system_epoch_replace_summary(struct system_epoch *dst,
+    const struct system_epoch *src, const struct system_epoch_span *summary)
+{
+	struct system_epoch_exact_input source, exact;
+	struct buf full, restricted;
+	size_t full_prefix, restricted_prefix, suffix_len = 0;
+	int rc = -1;
+
+	if (dst == NULL || src == NULL || summary == NULL ||
+	    !span_valid(summary) || summary->len == 0 ||
+	    summary->len > SYSTEM_EPOCH_SUMMARY_MAX)
+		return (-1);
+	exact_input_from_epoch(&source, src);
+	if (!exact_input_valid(&source) ||
+	    variant_prefix_len(src, &src->full, &full_prefix) == -1 ||
+	    variant_prefix_len(src, &src->restricted,
+	    &restricted_prefix) == -1)
+		return (-1);
+	if (src->summary.len == 0) {
+		suffix_len = sizeof(summary_prefix) - 1;
+		if (size_add(&suffix_len, summary->len) == -1)
+			return (-1);
+	} else
+		suffix_len = summary->len;
+	if (suffix_len > SYSTEM_EPOCH_VARIANT_MAX ||
+	    full_prefix > SYSTEM_EPOCH_VARIANT_MAX - suffix_len ||
+	    restricted_prefix > SYSTEM_EPOCH_VARIANT_MAX - suffix_len)
+		return (-1);
+
+	buf_init(&full);
+	buf_init(&restricted);
+	if (full_prefix > 0)
+		buf_add(&full, src->full.data, full_prefix);
+	if (restricted_prefix > 0)
+		buf_add(&restricted, src->restricted.data, restricted_prefix);
+	if (src->summary.len == 0) {
+		add_summary(&full, summary);
+		add_summary(&restricted, summary);
+	} else {
+		add_span(&full, summary);
+		add_span(&restricted, summary);
+	}
+	exact = source;
+	exact.reason = SYSTEM_EPOCH_COMPACT;
+	exact.summary = *summary;
+	exact.full.data = full.data;
+	exact.full.len = full.len;
+	exact.restricted.data = restricted.data;
+	exact.restricted.len = restricted.len;
+	rc = system_epoch_import(dst, &exact);
+	buf_free(&restricted);
+	buf_free(&full);
+	return (rc);
+}
+
 const struct buf *
 system_epoch_select(const struct system_epoch *epoch, int project_context)
 {
blob - cfe01a1a5d4bb697e413f1e2ed212a8a04a603fa
blob + be859a0ed0a34709c9d0e954728c4a496838075c
--- src/fugu/system_epoch.h
+++ src/fugu/system_epoch.h
@@ -51,12 +51,30 @@ struct system_epoch_input {
 };
 
 /*
+ * Exact persisted form.  The complete variants are authoritative opaque
+ * Provider inputs; components support audit, Subagents, and future reset
+ * construction, but are not hashes from which replay may rebuild a variant.
+ */
+struct system_epoch_exact_input {
+	uint32_t			 version;
+	enum system_epoch_reason	 reason;
+	struct system_epoch_span	 base;
+	struct system_epoch_span	 personal;
+	struct system_epoch_span	 project;
+	struct system_epoch_span	 summary;
+	struct system_epoch_span	 full;
+	struct system_epoch_span	 restricted;
+};
+
+/*
  * One owned immutable System baseline epoch.  base, personal, and project
- * are NUL-free; summary and the derived variants may contain NUL.  All spans
+ * are NUL-free; summary and the complete variants may contain NUL.  All spans
  * are otherwise opaque bytes: this Module deliberately does not validate or
  * normalize UTF-8.  Each external span is capped at FUGU_CTX_MAX, summary at
  * FUGU_TURN_DATA_MAX, and base and either complete variant at
- * FUGU_REQUEST_MAX.  A Provider codec still checks final JSON expansion.
+ * FUGU_REQUEST_MAX.  Fresh construction derives variants; import and copy
+ * preserve their exact bytes.  A Provider codec still checks final JSON
+ * expansion.
  */
 struct system_epoch {
 	uint32_t		 version;
@@ -74,7 +92,13 @@ void	 system_epoch_free(struct system_epoch *);
 /* On failure, a previously built destination remains byte-identical. */
 int	 system_epoch_build(struct system_epoch *,
 	    const struct system_epoch_input *);
+/* Import all authoritative spans without recomposing either complete variant. */
+int	 system_epoch_import(struct system_epoch *,
+	    const struct system_epoch_exact_input *);
 int	 system_epoch_copy(struct system_epoch *, const struct system_epoch *);
+/* Preserve both exact variant prefixes while replacing their summary suffix. */
+int	 system_epoch_replace_summary(struct system_epoch *,
+	    const struct system_epoch *, const struct system_epoch_span *);
 /* Borrowed until the epoch is rebuilt or freed. */
 const struct buf *system_epoch_select(const struct system_epoch *, int);