commit - 9169cf965b11b6344d26b8e48154b7d9c83feb83
commit + 18e7ebf97d7549bb8c88fc683e1b6e36576be347
blob - 6b49757fb26a23f949a702605c73ace91be74886
blob + bf114b2bd34b02e78443d612ca772aff624c3fbe
--- docs/adr/0008-immutable-system-epochs.md
+++ docs/adr/0008-immutable-system-epochs.md
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
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
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
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
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;
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);
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
}
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;
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(©);
+ 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(©, &imported) == 0);
+ imported.full.data[0] = 'X';
+ CHECK(buf_is(©.full, full_prefix, sizeof(full_prefix) - 1));
+
+ summary.data = binary_summary;
+ summary.len = sizeof(binary_summary);
+ CHECK(system_epoch_replace_summary(&compacted, ©, &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(©);
+ system_epoch_free(&imported);
+}
+
int
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
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;
}
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
}
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)
{
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;
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;
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
}
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 &&
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);
}
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)
{
}
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
};
/*
+ * 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;
/* 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);