commit - 323a47d9cab66665f0a5e7b0432a69d1c942642f
commit + 83f2c983cb279c2381c039c3947997ebde1ce2e1
blob - f278d6717aee499f26d1b62c8196bc9d57ed0acd
blob + d583c71ff2f09d11db233da953a5df273a6cee84
--- regress/Makefile
+++ regress/Makefile
# Long integration suites lead so scripts/check can overlap their tails with
# shorter proofs; this order is intentionally not alphabetical.
SUBDIR= journal turn print agent agentcfg anthropic buf check claude_subscription compaction conf deploy diff editor generation http imsgev json \
- local_port log markdown model_window openai output sandbox skills sse term tool_display tools \
+ local_port log markdown model_window openai output sandbox skills sse system_epoch term tool_display tools \
turn_mechanics turn_txn ui_config ui_layout web welcome xmalloc
.include <bsd.subdir.mk>
blob - /dev/null
blob + 9cd0d4146353034b117cb93bcd35bcfd27028f78 (mode 644)
--- /dev/null
+++ regress/system_epoch/Makefile
+PROG= system_epoch_test
+SRCS= system_epoch_test.c system_epoch.c buf.c log.c xmalloc.c
+NOMAN= yes
+
+.PATH: ${.CURDIR}/../../src/fugu
+CFLAGS+= -I${.CURDIR}/../../src/fugu
+
+.include <bsd.regress.mk>
blob - /dev/null
blob + 03f2f68bd17979f0b2124a0f8c79a36b18a06687 (mode 644)
--- /dev/null
+++ regress/system_epoch/system_epoch_test.c
+/*
+ * Copyright (c) 2026 Isaac <isaac@itm.works>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "system_epoch.h"
+
+#include "regress.h"
+
+static int
+buf_is(const struct buf *b, const void *want, size_t len)
+{
+ return (b->len == len && (len == 0 || memcmp(b->data, want, len) == 0));
+}
+
+static void
+test_exact_variants(void)
+{
+ static const char full[] =
+ "BASE\n\nPERSONAL\n\nPROJECT\n\n"
+ "Summary of the conversation so far (your own continuation notes):"
+ "\n\nSUMMARY";
+ static const char restricted[] =
+ "BASE\n\nSummary of the conversation so far "
+ "(your own continuation notes):\n\nSUMMARY";
+ struct system_epoch_input input;
+ struct system_epoch epoch;
+
+ system_epoch_init(&epoch);
+ memset(&input, 0, sizeof(input));
+ input.reason = SYSTEM_EPOCH_START;
+ input.base.data = "BASE";
+ input.base.len = 4;
+ input.personal.data = "PERSONAL";
+ input.personal.len = 8;
+ input.project.data = "PROJECT";
+ input.project.len = 7;
+ input.summary.data = "SUMMARY";
+ input.summary.len = 7;
+ CHECK(system_epoch_build(&epoch, &input) == 0);
+ CHECK(epoch.version == SYSTEM_EPOCH_VERSION);
+ CHECK(epoch.reason == SYSTEM_EPOCH_START);
+ CHECK(buf_is(&epoch.base, "BASE", 4));
+ CHECK(buf_is(&epoch.personal, "PERSONAL", 8));
+ CHECK(buf_is(&epoch.project, "PROJECT", 7));
+ CHECK(buf_is(&epoch.summary, "SUMMARY", 7));
+ CHECK(buf_is(&epoch.full, full, sizeof(full) - 1));
+ CHECK(buf_is(&epoch.restricted, restricted,
+ sizeof(restricted) - 1));
+ CHECK(system_epoch_select(&epoch, 1) == &epoch.full);
+ CHECK(system_epoch_select(&epoch, 0) == &epoch.restricted);
+ system_epoch_free(&epoch);
+}
+
+static void
+test_owned_state(void)
+{
+ char base[] = "owned-base";
+ char personal[] = "owned-personal";
+ char project[] = "owned-project";
+ char summary[] = "owned-summary";
+ struct system_epoch_input input;
+ struct system_epoch epoch, copy;
+
+ system_epoch_init(&epoch);
+ system_epoch_init(©);
+ memset(&input, 0, sizeof(input));
+ input.reason = SYSTEM_EPOCH_CLEAR;
+ input.base.data = base;
+ input.base.len = strlen(base);
+ input.personal.data = personal;
+ input.personal.len = strlen(personal);
+ input.project.data = project;
+ input.project.len = strlen(project);
+ input.summary.data = summary;
+ input.summary.len = strlen(summary);
+ CHECK(system_epoch_build(&epoch, &input) == 0);
+ base[0] = personal[0] = project[0] = summary[0] = 'X';
+ CHECK(buf_is(&epoch.base, "owned-base", 10));
+ CHECK(buf_is(&epoch.personal, "owned-personal", 14));
+ CHECK(buf_is(&epoch.project, "owned-project", 13));
+ CHECK(buf_is(&epoch.summary, "owned-summary", 13));
+ CHECK(system_epoch_select(&epoch, 0) == &epoch.restricted);
+ CHECK(buf_is(&epoch.personal, "owned-personal", 14));
+ CHECK(buf_is(&epoch.project, "owned-project", 13));
+ CHECK(system_epoch_copy(©, &epoch) == 0);
+ epoch.base.data[0] = epoch.personal.data[0] = 'Y';
+ CHECK(buf_is(©.base, "owned-base", 10));
+ CHECK(buf_is(©.personal, "owned-personal", 14));
+ CHECK(copy.reason == SYSTEM_EPOCH_CLEAR);
+ system_epoch_free(©);
+ system_epoch_free(&epoch);
+}
+
+static void
+test_invalid_input_is_transactional(void)
+{
+ static const char base_nul[] = { 'b', '\0', 'x' };
+ static const char personal_nul[] = { 'p', '\0', 'x' };
+ static const char project_nul[] = { 'r', '\0', 'x' };
+ struct system_epoch_input input;
+ struct system_epoch epoch;
+
+ system_epoch_init(&epoch);
+ memset(&input, 0, sizeof(input));
+ input.reason = SYSTEM_EPOCH_MIGRATION;
+ input.base.data = "KEEP";
+ input.base.len = 4;
+ input.personal.data = "P";
+ input.personal.len = 1;
+ CHECK(system_epoch_build(&epoch, &input) == 0);
+
+ input.reason = (enum system_epoch_reason)99;
+ CHECK(system_epoch_build(&epoch, &input) == -1);
+ input.reason = SYSTEM_EPOCH_CLEAR;
+ input.base.data = NULL;
+ CHECK(system_epoch_build(&epoch, &input) == -1);
+ input.base.data = base_nul;
+ input.base.len = sizeof(base_nul);
+ CHECK(system_epoch_build(&epoch, &input) == -1);
+ input.base.data = "B";
+ input.base.len = 1;
+ input.personal.data = personal_nul;
+ input.personal.len = sizeof(personal_nul);
+ CHECK(system_epoch_build(&epoch, &input) == -1);
+ input.personal.data = "P";
+ input.personal.len = 1;
+ input.project.data = project_nul;
+ input.project.len = sizeof(project_nul);
+ CHECK(system_epoch_build(&epoch, &input) == -1);
+
+ input.project.data = NULL;
+ input.project.len = 0;
+ input.base.data = "x";
+ input.base.len = SYSTEM_EPOCH_BASE_MAX + 1;
+ CHECK(system_epoch_build(&epoch, &input) == -1);
+ input.base.len = 1;
+ input.personal.data = "x";
+ input.personal.len = SYSTEM_EPOCH_EXTERNAL_MAX + 1;
+ CHECK(system_epoch_build(&epoch, &input) == -1);
+ input.personal.data = NULL;
+ input.personal.len = 0;
+ input.summary.data = "x";
+ input.summary.len = SYSTEM_EPOCH_SUMMARY_MAX + 1;
+ CHECK(system_epoch_build(&epoch, &input) == -1);
+ input.summary.data = NULL;
+ input.summary.len = 0;
+ input.base.data = "x";
+ input.base.len = SYSTEM_EPOCH_VARIANT_MAX;
+ input.personal.data = "y";
+ input.personal.len = 1;
+ CHECK(system_epoch_build(&epoch, &input) == -1);
+
+ CHECK(epoch.version == SYSTEM_EPOCH_VERSION);
+ CHECK(epoch.reason == SYSTEM_EPOCH_MIGRATION);
+ CHECK(buf_is(&epoch.base, "KEEP", 4));
+ CHECK(buf_is(&epoch.personal, "P", 1));
+ CHECK(buf_is(&epoch.full, "KEEP\n\nP", 7));
+ CHECK(buf_is(&epoch.restricted, "KEEP", 4));
+ system_epoch_free(&epoch);
+}
+
+static void
+test_all_reasons_and_binary_summary(void)
+{
+ static const u_char base[] = { 'B', 0xff };
+ static const u_char personal[] = { 'P', 0xfe };
+ static const u_char project[] = { 'R', 0xfd };
+ static const u_char summary[] = { 'S', '\0', 0xfc };
+ static const char marker[] =
+ "\n\nSummary of the conversation so far "
+ "(your own continuation notes):\n\n";
+ static const enum system_epoch_reason reasons[] = {
+ SYSTEM_EPOCH_START,
+ SYSTEM_EPOCH_CLEAR,
+ SYSTEM_EPOCH_COMPACT,
+ SYSTEM_EPOCH_MIGRATION
+ };
+ struct system_epoch_input input;
+ struct system_epoch epoch, copy;
+ size_t i, off;
+
+ system_epoch_init(&epoch);
+ system_epoch_init(©);
+ memset(&input, 0, sizeof(input));
+ input.base.data = base;
+ input.base.len = sizeof(base);
+ input.personal.data = personal;
+ input.personal.len = sizeof(personal);
+ input.project.data = project;
+ input.project.len = sizeof(project);
+ input.summary.data = summary;
+ input.summary.len = sizeof(summary);
+ for (i = 0; i < sizeof(reasons) / sizeof(reasons[0]); i++) {
+ input.reason = reasons[i];
+ CHECK(system_epoch_build(&epoch, &input) == 0);
+ CHECK(epoch.reason == reasons[i]);
+ CHECK(epoch.version == SYSTEM_EPOCH_VERSION);
+ }
+ CHECK(buf_is(&epoch.base, base, sizeof(base)));
+ CHECK(buf_is(&epoch.personal, personal, sizeof(personal)));
+ CHECK(buf_is(&epoch.project, project, sizeof(project)));
+ CHECK(buf_is(&epoch.summary, summary, sizeof(summary)));
+ off = sizeof(base);
+ CHECK(epoch.full.len == sizeof(base) + 2 + sizeof(personal) + 2 +
+ sizeof(project) + sizeof(marker) - 1 + sizeof(summary));
+ CHECK(memcmp(epoch.full.data, base, sizeof(base)) == 0);
+ CHECK(memcmp(epoch.full.data + off, "\n\n", 2) == 0);
+ off += 2 + sizeof(personal);
+ CHECK(memcmp(epoch.full.data + off, "\n\n", 2) == 0);
+ off += 2 + sizeof(project);
+ CHECK(memcmp(epoch.full.data + off, marker, sizeof(marker) - 1) == 0);
+ CHECK(memcmp(epoch.full.data + epoch.full.len - sizeof(summary),
+ summary, sizeof(summary)) == 0);
+ CHECK(epoch.restricted.len == sizeof(base) + sizeof(marker) - 1 +
+ sizeof(summary));
+ CHECK(memcmp(epoch.restricted.data, base, sizeof(base)) == 0);
+ CHECK(memcmp(epoch.restricted.data + sizeof(base), marker,
+ sizeof(marker) - 1) == 0);
+ CHECK(memcmp(epoch.restricted.data + epoch.restricted.len -
+ sizeof(summary), summary, sizeof(summary)) == 0);
+ CHECK(system_epoch_copy(©, &epoch) == 0);
+ CHECK(buf_is(©.summary, summary, sizeof(summary)));
+ CHECK(buf_is(©.full, epoch.full.data, epoch.full.len));
+ CHECK(buf_is(©.restricted, epoch.restricted.data,
+ epoch.restricted.len));
+ system_epoch_free(©);
+ system_epoch_free(&epoch);
+}
+
+static void
+test_copy_failure_is_transactional(void)
+{
+ struct system_epoch_input input;
+ struct system_epoch dst, src;
+
+ system_epoch_init(&dst);
+ system_epoch_init(&src);
+ memset(&input, 0, sizeof(input));
+ input.reason = SYSTEM_EPOCH_START;
+ input.base.data = "DESTINATION";
+ input.base.len = 11;
+ CHECK(system_epoch_build(&dst, &input) == 0);
+ input.reason = SYSTEM_EPOCH_COMPACT;
+ input.base.data = "SOURCE";
+ 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));
+ src.version = SYSTEM_EPOCH_VERSION + 1;
+ CHECK(system_epoch_copy(&dst, &src) == -1);
+ CHECK(buf_is(&dst.full, "DESTINATION", 11));
+ system_epoch_free(&src);
+ system_epoch_free(&dst);
+}
+
+int
+main(void)
+{
+ test_exact_variants();
+ test_owned_state();
+ test_invalid_input_is_transactional();
+ test_all_reasons_and_binary_summary();
+ test_copy_failure_is_transactional();
+ REGRESS_END();
+}
blob - /dev/null
blob + 14e9c5e26ee7067784bd3d946062195c7e02ef71 (mode 644)
--- /dev/null
+++ src/fugu/system_epoch.c
+/*
+ * Copyright (c) 2026 Isaac <isaac@itm.works>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "buf.h"
+#include "system_epoch.h"
+
+static const char summary_prefix[] =
+ "\n\nSummary of the conversation so far "
+ "(your own continuation notes):\n\n";
+
+static int
+reason_valid(enum system_epoch_reason reason)
+{
+ return (reason == SYSTEM_EPOCH_START || reason == SYSTEM_EPOCH_CLEAR ||
+ reason == SYSTEM_EPOCH_COMPACT ||
+ reason == SYSTEM_EPOCH_MIGRATION);
+}
+
+static int
+span_valid(const struct system_epoch_span *span)
+{
+ return (span->data != NULL || span->len == 0);
+}
+
+static int
+size_add(size_t *total, size_t add)
+{
+ if (SIZE_MAX - *total < add)
+ return (-1);
+ *total += add;
+ return (0);
+}
+
+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)
+ return (0);
+ full = restricted = input->base.len;
+ if (input->personal.len > 0 &&
+ (size_add(&full, 2) == -1 ||
+ size_add(&full, input->personal.len) == -1))
+ return (0);
+ if (input->project.len > 0 &&
+ (size_add(&full, 2) == -1 ||
+ size_add(&full, input->project.len) == -1))
+ return (0);
+ if (input->summary.len > 0) {
+ summary = sizeof(summary_prefix) - 1;
+ if (size_add(&summary, input->summary.len) == -1 ||
+ size_add(&full, summary) == -1 ||
+ size_add(&restricted, summary) == -1)
+ return (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 (0);
+ return (1);
+}
+
+static void
+add_span(struct buf *out, const struct system_epoch_span *span)
+{
+ if (span->len > 0)
+ buf_add(out, span->data, span->len);
+}
+
+static void
+add_external(struct buf *out, const struct system_epoch_span *span)
+{
+ if (span->len == 0)
+ return;
+ buf_addstr(out, "\n\n");
+ add_span(out, span);
+}
+
+static void
+add_summary(struct buf *out, const struct system_epoch_span *summary)
+{
+ if (summary->len == 0)
+ return;
+ buf_add(out, summary_prefix, sizeof(summary_prefix) - 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)
+{
+ memset(epoch, 0, sizeof(*epoch));
+ buf_init(&epoch->base);
+ buf_init(&epoch->personal);
+ buf_init(&epoch->project);
+ buf_init(&epoch->summary);
+ buf_init(&epoch->full);
+ buf_init(&epoch->restricted);
+}
+
+void
+system_epoch_free(struct system_epoch *epoch)
+{
+ buf_free(&epoch->base);
+ buf_free(&epoch->personal);
+ buf_free(&epoch->project);
+ buf_free(&epoch->summary);
+ buf_free(&epoch->full);
+ buf_free(&epoch->restricted);
+ memset(epoch, 0, sizeof(*epoch));
+}
+
+int
+system_epoch_build(struct system_epoch *epoch,
+ const struct system_epoch_input *input)
+{
+ struct system_epoch next;
+
+ if (epoch == NULL || !input_valid(input))
+ return (-1);
+
+ system_epoch_init(&next);
+ next.version = SYSTEM_EPOCH_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->base);
+ add_external(&next.full, &input->personal);
+ add_external(&next.full, &input->project);
+ add_summary(&next.full, &input->summary);
+ add_span(&next.restricted, &input->base);
+ add_summary(&next.restricted, &input->summary);
+
+ system_epoch_free(epoch);
+ *epoch = next;
+ return (0);
+}
+
+int
+system_epoch_copy(struct system_epoch *dst, const struct system_epoch *src)
+{
+ struct system_epoch_input input;
+ struct system_epoch next;
+
+ if (dst == NULL || src == NULL || src->version != SYSTEM_EPOCH_VERSION)
+ return (-1);
+ if (dst == src)
+ 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;
+ return (0);
+}
+
+const struct buf *
+system_epoch_select(const struct system_epoch *epoch, int project_context)
+{
+ if (epoch == NULL)
+ return (NULL);
+ return (project_context ? &epoch->full : &epoch->restricted);
+}
blob - /dev/null
blob + cfe01a1a5d4bb697e413f1e2ed212a8a04a603fa (mode 644)
--- /dev/null
+++ src/fugu/system_epoch.h
+/*
+ * Copyright (c) 2026 Isaac <isaac@itm.works>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef SYSTEM_EPOCH_H
+#define SYSTEM_EPOCH_H
+
+#include <sys/types.h>
+
+#include <stdint.h>
+
+#include "buf.h"
+#include "proto.h"
+
+#define SYSTEM_EPOCH_VERSION 1U
+#define SYSTEM_EPOCH_BASE_MAX FUGU_REQUEST_MAX
+#define SYSTEM_EPOCH_EXTERNAL_MAX FUGU_CTX_MAX
+#define SYSTEM_EPOCH_SUMMARY_MAX FUGU_TURN_DATA_MAX
+#define SYSTEM_EPOCH_VARIANT_MAX FUGU_REQUEST_MAX
+
+enum system_epoch_reason {
+ SYSTEM_EPOCH_START,
+ SYSTEM_EPOCH_CLEAR,
+ SYSTEM_EPOCH_COMPACT,
+ SYSTEM_EPOCH_MIGRATION
+};
+
+struct system_epoch_span {
+ const void *data;
+ size_t len;
+};
+
+struct system_epoch_input {
+ 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;
+};
+
+/*
+ * One owned immutable System baseline epoch. base, personal, and project
+ * are NUL-free; summary and the derived 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.
+ */
+struct system_epoch {
+ uint32_t version;
+ enum system_epoch_reason reason;
+ struct buf base;
+ struct buf personal;
+ struct buf project;
+ struct buf summary;
+ struct buf full;
+ struct buf restricted;
+};
+
+void system_epoch_init(struct system_epoch *);
+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 *);
+int system_epoch_copy(struct system_epoch *, const struct system_epoch *);
+/* Borrowed until the epoch is rebuilt or freed. */
+const struct buf *system_epoch_select(const struct system_epoch *, int);
+
+#endif /* SYSTEM_EPOCH_H */