UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

202 lines (201 loc) • 10.8 kB
import { type TransferEvidence } from "./file-backend.js"; import { type MemoryEntry } from "./types.js"; /** One exported challenge event (unresolved generations only). Generations are DESTINATION-minted: * the bundle deliberately carries no generation number — the importing store re-mints events under * its own idempotent namespace and allocates generations there. */ export interface BundleChallengeRow { eventId: string; entryId: string; reason: string; at: number; challengedRev?: string; } /** One exported durable pollution marker (verbatim source record). */ export interface BundlePollutedSession { sessionId: string; at: number; reason: string; /** design/336 — the record's structured mechanical cause, carried verbatim (absent = the source * record predates the vocabulary or genuinely recorded none; the destination's mints then fall * back to "observed"). */ cause?: import("./types.js").MemoryOriginCause; } /** One exported committed lineage contribution (verbatim `lastRev`/`lastAt`). */ export interface BundleLineageRow { entryId: string; sessionId: string; lastRev: string; lastAt: number; /** design/336 — the contribution's content carries its external-origin marker (see * {@link import("./layout.js").LineageContribution.marked}). MUST travel with the row: dropping * it re-quarantines the source's tag-admissions at the destination (the import pollution sweep * challenges unmarked contributions of imported polluted sessions). */ marked?: true; } /** * design/178 v2 §4.3 + v2-c §6 — the governance-complete export bundle. The governance section is * STRUCTURALLY REQUIRED (a bundle without it is a bad package — there is no "entries-only import" * escape hatch: an optional hatch is a laundering channel, and the fixed section-hash key set makes * a stripped section fail integrity outright). `doc` is human-readable disclosure text OUTSIDE the * hashed sections (documentation, not governance data); unknown root keys are read-tolerated and * never re-export-preserved (a re-export always mints a fresh bundle from store state). */ export interface MemoryExportBundle { /** Envelope version — a reader meeting `bundle > 1` must refuse, never reinterpret. */ bundle: 1; at: number; scopes: string[]; /** The source store's durable identity (control-plane `store-identity.json`, minted at the first * export) — the import side's `origin` stamp for custody rows that carry none. */ storeId: string; /** Full-fidelity entries (provenance/trust/extra verbatim). A duplicate id is a bad package. */ entries: MemoryEntry[]; governance: { challenges: BundleChallengeRow[]; pollutedSessions: BundlePollutedSession[]; lineage: BundleLineageRow[]; /** Evidence-chain rows sliced to the exported scopes (v2-c §2 table): open form — a consumer * tolerates unknown keys; unknown CHANNELS never leave the exporter (they refuse the export). */ custody: TransferEvidence[]; }; /** Honest enumeration of what the bundle does NOT carry. The `unbound`/`pendingLatch` name lists * are populated on a FULL-STORE export only (one trust domain); a subset export ships empty * lists and the counts (an id list would leak another tenant's identifiers). The counts are * always present and always equal the honest total, so a consumer reads ONE seat either way. */ residuals: { quarantined: string[]; unbound: string[]; pendingLatch: string[]; contentUnavailable: Array<{ id: string; scope: string; slug: string; rev: string; reason: string; }>; quarantineOpaque: number; /** Custody rows a SUBSET export could not slice (no scope to slice by — unbound-deletion * history among them): the anti-resurrection guarantee for those ids does not travel. */ unsliceableCustody: number; unboundCount: number; pendingLatchCount: number; }; /** Accident detection, not forgery-proofing (see module note). `sectionHashes` carries EXACTLY * the four fixed keys (meta/entries/governance/residuals); `revs` and `entries` are mutually * pointing (same id set, verbatim revs, `entryCount` ≡ both cardinalities). */ integrity: { entryCount: number; revs: Record<string, string>; sectionHashes: Record<string, string>; }; /** Human-readable disclosure sentences (free-string carriage, subset residuals). Outside the * hashed sections by design — documentation, not governance data. */ doc: string[]; } /** design/178 v2-c §7 — the import answer (the parent-design draft fields plus the v2-c report * seats). Every negative disposition is REPORTED, never silent. */ export interface MemoryImportReport { v: 1; /** The bundle identity (sha256 over the canonical section-hash map) — the latch/remint salt. */ bundleHash: string; sourceStoreId: string; landed: Array<{ id: string; scope: string; slug: string; }>; /** id+rev+scope three-way idempotent skips (slug tolerates the deterministic `-n` suffix). */ alreadyPresent: string[]; /** The destination chain carries a delete row for the id and no live row survives — resurrection * refused (no override; a legitimately re-created id has a live row and takes the ordinary * CAS/conflict path instead). */ refusedErased: string[]; /** Fresh add of a `repo_file`-provenance entry with no `trust` marker — whitewash refused. */ refusedUntrusted: string[]; conflicts: Array<{ id: string; reason: string; }>; /** Entry-attached governance (challenges/lineage) withheld because its entry neither landed nor * matched already-present-at-the-same-rev — misattribution is worse than omission. */ governanceWithheld: string[]; /** Source chain carries a delete row while the destination holds a LIVE entry for the id: named * for deployment adjudication, never deleted on the import's behalf. */ erasedAtSource: string[]; pollutionDivergence: Array<{ sessionId: string; kept: "destination"; }>; /** Governance rows pointing at ids outside entries ∪ the destination account (custody rows are * exempt — an absent-id delete row is exactly the anti-resurrection payload). */ referentialOrphans: string[]; lineageDivergence: Array<{ entryId: string; sessionId: string; kept: "destination"; }>; /** Custody rows withheld unappended: unknown channel, or a known-channel row failing the store's * own validation — reported per row, never a whole-package refusal (a newer exporter must not * brick an older importer) and never a verbatim append (an unvalidatable row would poison the * fail-closed chain). `srcEv` is the row's source identity (its own `srcEv` when it carries one, * else its `ev`). */ custodyWithheld: Array<{ srcEv: string; channel: string; }>; /** Custody rows submitted to the destination chain under this bundle's namespace (idempotent * re-imports answer the same number — the reconciliation handle). */ custodyAppended: number; } /** What the engine hands the File backend's `importBundleCommit` face after the pure validation * pass: the bundle's governed content plus its computed identity. Everything here is inert plain * data (the engine clones the caller's bundle through a JSON round-trip before validating). */ export interface MemoryBundleImportPlan { bundleHash: string; sourceStoreId: string; entries: MemoryEntry[]; challenges: BundleChallengeRow[]; pollutedSessions: BundlePollutedSession[]; lineage: BundleLineageRow[]; /** Open custody rows (byte-equal duplicates already collapsed by validation). */ custody: Array<Record<string, unknown>>; } /** The four fixed integrity section keys (§6.3) — missing OR extra keys are a bad package. */ export declare const BUNDLE_SECTION_KEYS: readonly ["meta", "entries", "governance", "residuals"]; /** The canonical per-section hashes of a bundle's four sections (meta = the bundle/at/scopes/ * storeId scalars). Canonical JSON (recursively sorted keys) + LF, sha256 hex. */ export declare function computeBundleSectionHashes(bundle: Pick<MemoryExportBundle, "bundle" | "at" | "scopes" | "storeId" | "entries" | "governance" | "residuals">): Record<string, string>; /** * The bundle IDENTITY: sha256 over the canonical JSON of `integrity.sectionHashes` (full 64-hex — * the import latch id, the challenge/custody remint salt, and the report echo all share it). The * section hashes already seal the section contents, so their sorted map IS the package identity; * a tampered package fails integrity before its hash ever matters. */ export declare function computeMemoryBundleHash(sectionHashes: Record<string, string>): string; /** * design/178 v2-c §1-2① — the PURE bundle validation (integrity/structure/self-consistency; zero * store reads, zero side effects). Returns the FIRST violation, or undefined (valid). Every * violation is a whole-package refusal at the caller — nothing lands, no latch is set. */ export declare function memoryBundleInvalid(raw: unknown, opts?: { expectedScopes?: readonly string[]; }): string | undefined; /** What the exporter feeds {@link assembleMemoryExportBundle} — the store-consistent snapshot plus * the already-sliced governance pieces (pure data; the snapshot fence lives in the backend). */ export interface MemoryExportBundleInput { at: number; scopes: string[]; storeId: string; entries: MemoryEntry[]; challenges: BundleChallengeRow[]; pollutedSessions: BundlePollutedSession[]; lineage: BundleLineageRow[]; custody: TransferEvidence[]; residuals: MemoryExportBundle["residuals"]; } /** The C-1 disclosure sentence (recommended-arm ruling posture: free strings ride verbatim and the * bundle SAYS so — coding them away would destroy the identity/attribution keys idempotency and * the remint namespaces depend on). */ export declare const BUNDLE_FREE_TEXT_DISCLOSURE = "Free-string fields in this bundle (pollution/challenge reasons, challenge event ids, custody request ids, session ids) are carried verbatim and may contain text the exporting deployment put there; subset-export tenant isolation does not extend to those literals."; /** Assemble the bundle: deterministic ordering, section hashes, disclosure doc. Pure. */ export declare function assembleMemoryExportBundle(input: MemoryExportBundleInput): MemoryExportBundle;