@sema-agent/core
Version:
Stateless, task-oriented AI agent core
202 lines (201 loc) • 10.8 kB
TypeScript
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;