@sema-agent/core 5.38.0 → 5.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +182 -10
  2. package/dist/agents/send-message-tool.d.ts +8 -0
  3. package/dist/agents/send-message-tool.js +8 -0
  4. package/dist/agents/teacher.js +9 -3
  5. package/dist/agents/verify.js +9 -3
  6. package/dist/core/checkpoint-store.d.ts +12 -0
  7. package/dist/core/governance-codes.js +2 -0
  8. package/dist/core/hooks.d.ts +23 -0
  9. package/dist/core/hooks.js +53 -4
  10. package/dist/core/mailbox-store.d.ts +39 -0
  11. package/dist/core/mailbox-store.js +9 -0
  12. package/dist/core/memory-engine/engine.d.ts +27 -0
  13. package/dist/core/memory-engine/engine.js +103 -1
  14. package/dist/core/memory-engine/export-bundle.d.ts +192 -0
  15. package/dist/core/memory-engine/export-bundle.js +306 -0
  16. package/dist/core/memory-engine/file-backend.d.ts +178 -1
  17. package/dist/core/memory-engine/file-backend.js +637 -6
  18. package/dist/core/memory-engine/index.d.ts +2 -1
  19. package/dist/core/memory-engine/index.js +1 -0
  20. package/dist/core/memory-engine/layout.d.ts +89 -1
  21. package/dist/core/memory-engine/layout.js +131 -1
  22. package/dist/core/memory-engine/memory-backend-contract.d.ts +1 -1
  23. package/dist/core/memory-engine/memory-backend-contract.js +52 -0
  24. package/dist/core/memory-engine/tools.js +8 -1
  25. package/dist/core/permission-rule-consent.d.ts +27 -4
  26. package/dist/core/permission-rule-consent.js +41 -4
  27. package/dist/core/permission-rule-model.d.ts +7 -1
  28. package/dist/core/runner/prepare-task.js +24 -3
  29. package/dist/core/runner/runtask.js +5 -0
  30. package/dist/core/runner/synthetic-tools.js +3 -1
  31. package/dist/core/runner/tool-disclosure.js +2 -1
  32. package/dist/core/sensitive-path-policy.js +3 -3
  33. package/dist/core/store-contracts/mailbox-store-contract.d.ts +29 -1
  34. package/dist/core/store-contracts/mailbox-store-contract.js +78 -0
  35. package/dist/core/types.d.ts +21 -0
  36. package/dist/core/write-protect.d.ts +73 -0
  37. package/dist/core/write-protect.js +195 -0
  38. package/dist/index.d.ts +4 -3
  39. package/dist/index.js +4 -3
  40. package/dist/orchestration/governance-baseline-validity.d.ts +44 -0
  41. package/dist/orchestration/governance-baseline-validity.js +55 -0
  42. package/dist/orchestration/run-workflow-tool.js +33 -8
  43. package/dist/orchestration/workflow-script-runner.js +9 -4
  44. package/dist/tools/fs/read-deny.d.ts +15 -5
  45. package/dist/tools/fs/read-deny.js +33 -12
  46. package/dist/tools/fs/safety.d.ts +5 -2
  47. package/dist/tools/fs/safety.js +5 -3
  48. package/dist/tools/fs/search.d.ts +33 -0
  49. package/dist/tools/fs/search.js +72 -0
  50. package/package.json +1 -1
  51. package/test/export-surface.snapshot.json +21 -1
@@ -1,4 +1,13 @@
1
1
  import { assertRetentionPolicy } from "./retention-policy.js";
2
+ export const MAILBOX_TOMBSTONED_RECIPIENT_CODE = "mailbox.recipient_tombstoned";
3
+ export class MailboxStoreError extends Error {
4
+ code;
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.code = code;
8
+ this.name = "MailboxStoreError";
9
+ }
10
+ }
2
11
  export function newestSentAt(messages) {
3
12
  let newest;
4
13
  for (const m of messages) {
@@ -1,4 +1,5 @@
1
1
  import { type CommittedBinding, type EraseMemoryEntriesInput, type MemoryErasureAttestation, type TransferEvidence } from "./file-backend.js";
2
+ import { type MemoryExportBundle, type MemoryImportReport } from "./export-bundle.js";
2
3
  import { type ChallengeAssignment, type ChallengeEvent, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, type MemoryPartitionIncidentSink, type RetrievedAccountRow, type SessionPollutionRecord } from "./layout.js";
3
4
  import type { HarvestReport, MemoryAnnouncement, MemoryBackend, MemorySessionHandle, ScanFinding } from "./types.js";
4
5
  /**
@@ -438,6 +439,32 @@ export declare class MemoryEngine {
438
439
  * reported honestly, never fabricated and never a throw (#196 absence-reports-not-silent-green).
439
440
  */
440
441
  provenanceOf(entryId: string): Promise<EntryProvenanceAccount>;
442
+ /**
443
+ * Export the requested scopes as a GOVERNANCE-COMPLETE bundle (design/178 v2 §4.3 + v2-c):
444
+ * committed entries (audit-snapshot semantics — shadow-backed rows a disk scan would miss are
445
+ * in; unavailable content is enumerated, never dressed), the scope-sliced custody chain, the
446
+ * exported entries' unresolved challenge events and lineage rows, and the named sessions'
447
+ * pollution markers — all read inside the backend's single-lock export composite, so a bundle is
448
+ * never a mixed-epoch scene. Refusals are loud and total (`memory.export_incomplete`): there is
449
+ * no degraded/partial bundle shape — "a truncated package that looks complete" is the one
450
+ * deliverable this API is forbidden to produce.
451
+ */
452
+ exportMemoryScopes(scopes: readonly string[]): Promise<MemoryExportBundle>;
453
+ /**
454
+ * Import a bundle (design/178 v2-c §1/§7). Validation is TOTAL and lands nothing on failure
455
+ * (`memory.import_rejected`): integrity (fixed section-hash key set, per-entry content-rev
456
+ * recomputation, batch-unique ids/event ids), structure (the governance section is REQUIRED —
457
+ * there is no entries-only escape hatch), and — when `opts.expectedScopes` is present (a service
458
+ * endpoint pins its principal's grant set here) — the scope authorization envelope. Everything
459
+ * after validation runs inside the backend's import composite: the synthetic latch (imported ids
460
+ * stay withheld from every model-visible read face until governance fully lands), the five
461
+ * import judgments IN-LOCK, entry landing through the one transaction skeleton, the governance
462
+ * legs, and the receipt-gated latch release. A crash anywhere converges by re-importing the SAME
463
+ * bundle; a completed import answers its recorded report idempotently.
464
+ */
465
+ importMemoryBundle(bundle: MemoryExportBundle, opts?: {
466
+ expectedScopes?: readonly string[];
467
+ }): Promise<MemoryImportReport>;
441
468
  /** Host API: challenge every entry the lineage ledger attributes to `sessionId` (post-hoc source
442
469
  * falsification — trustedTools misconfigured, a tool re-classified, late delegation evidence).
443
470
  * Same requestId contract as {@link challengeEntries}. */
@@ -6,6 +6,7 @@ import { inlineUntrusted } from "../untrusted-text.js";
6
6
  import { formatMemoryAge } from "../memory-recall.js";
7
7
  import { computeEntryRev, parseEntryFile, serializeEntryFile } from "./frontmatter.js";
8
8
  import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, canonicalJsonStringify, captureErasureInput, erasureRequestInvalid, erasureSelectHash, scanEntryFiles, } from "./file-backend.js";
9
+ import { assembleMemoryExportBundle, computeMemoryBundleHash, memoryBundleInvalid, } from "./export-bundle.js";
9
10
  import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, readIndexRevs, writeIndexRevs, bumpScanFuse, canonicalize, claimRootScope, clearScanFuse, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, isContainedIn, markSessionPolluted, readSessionPollution, recordRetrievedAccount, writeFileNoFollow, readRetrievedAccount, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, appendChallengeEvents, appendLineageAudit, rebuildStrictControlPlaneLedger, isStrictControlPlaneLedgerCorrupt, CHALLENGE_LEDGER_MAX_EVENTS, adjudicateLineagePending, challengedEntryIds, clearLineageForEntries, discardLineagePending, lineageAccountOfEntry, lineageContributionsOfSession, lineageLatchedIds, promoteLineagePending, readChallengeEvents, readChallengedHistory, readLineageRecord, recordChallengedHistory, recordLineageCredential, reconcileLineage, resolveChallengeEvent, stageLineagePending, } from "./layout.js";
10
11
  import { scanMemoryFileName, scanMemoryWrite, scanRemediation } from "./scan.js";
11
12
  export const MEMORY_INSTRUCTION_TEMPLATE = `# Memory
@@ -521,6 +522,105 @@ export class MemoryEngine {
521
522
  return { v: 1, id: entryId, binding, contributors: plane.contributors, ...(plane.exclusion !== undefined ? { exclusion: plane.exclusion } : {}), contentState, ...(ingest !== undefined ? { ingest } : {}), custody };
522
523
  }
523
524
  }
525
+ async exportMemoryScopes(scopes) {
526
+ if (!Array.isArray(scopes) || scopes.length === 0 || scopes.some((s) => typeof s !== "string" || s.length === 0) || new Set(scopes).size !== scopes.length) {
527
+ const e = new Error("exportMemoryScopes: scopes must be a non-empty array of unique non-empty scope names");
528
+ e.code = "config.memory_export_request";
529
+ throw e;
530
+ }
531
+ const requested = [...scopes];
532
+ const face = this.backend.exportSnapshotOf;
533
+ if (typeof face !== "function") {
534
+ const e = new Error("exportMemoryScopes: this backend has no export-snapshot capability (exportSnapshotOf) — a governance-complete bundle cannot be assembled from the generic read faces (they cannot fence the five store faces into one epoch), and a governance-less export is the laundering shape this API refuses by design.");
535
+ e.code = "memory.export_incomplete";
536
+ throw e;
537
+ }
538
+ const snap = await face.call(this.backend, requested);
539
+ const exportedIds = new Set([...snap.entries.map((e) => e.id), ...snap.contentUnavailable.map((r) => r.id)]);
540
+ const resolvedGenerations = new Set();
541
+ for (const e of snap.challenges)
542
+ if (e.kind === "resolve")
543
+ resolvedGenerations.add(`${e.entryId}#${e.generation}`);
544
+ const challenges = [];
545
+ for (const e of snap.challenges) {
546
+ if (e.kind !== "challenge" || !exportedIds.has(e.entryId) || resolvedGenerations.has(`${e.entryId}#${e.generation}`))
547
+ continue;
548
+ challenges.push({ eventId: e.eventId, entryId: e.entryId, reason: e.reason, at: e.at, ...(e.challengedRev !== undefined ? { challengedRev: e.challengedRev } : {}) });
549
+ }
550
+ const lineage = snap.lineage.filter((r) => exportedIds.has(r.entryId));
551
+ const namedSessions = new Set(lineage.map((r) => r.sessionId));
552
+ for (const row of snap.custody) {
553
+ const sessions = row.sessions;
554
+ if (Array.isArray(sessions))
555
+ for (const s of sessions)
556
+ if (typeof s === "string")
557
+ namedSessions.add(s);
558
+ }
559
+ const pollutedSessions = snap.pollutedSessions.filter((p) => namedSessions.has(p.sessionId));
560
+ return assembleMemoryExportBundle({
561
+ at: this.now(),
562
+ scopes: requested,
563
+ storeId: snap.storeId,
564
+ entries: snap.entries,
565
+ challenges,
566
+ pollutedSessions,
567
+ lineage,
568
+ custody: snap.custody,
569
+ residuals: {
570
+ quarantined: snap.quarantined,
571
+ unbound: snap.fullStore ? snap.unbound : [],
572
+ pendingLatch: snap.fullStore ? snap.pendingLatch : [],
573
+ contentUnavailable: snap.contentUnavailable,
574
+ quarantineOpaque: snap.quarantineOpaque,
575
+ unsliceableCustody: snap.unsliceableCustody,
576
+ unboundCount: snap.unbound.length,
577
+ pendingLatchCount: snap.pendingLatch.length,
578
+ },
579
+ });
580
+ }
581
+ async importMemoryBundle(bundle, opts) {
582
+ let inert;
583
+ try {
584
+ inert = JSON.parse(JSON.stringify(bundle));
585
+ }
586
+ catch (err) {
587
+ const e = new Error(`importMemoryBundle: the bundle is not JSON-serializable (${err instanceof Error ? err.message : String(err)})`);
588
+ e.code = "config.memory_import_request";
589
+ throw e;
590
+ }
591
+ const bad = memoryBundleInvalid(inert, { ...(opts?.expectedScopes !== undefined ? { expectedScopes: opts.expectedScopes } : {}) });
592
+ if (bad !== undefined) {
593
+ const e = new Error(`importMemoryBundle: ${bad} — the whole package is refused, nothing landed`);
594
+ e.code = "memory.import_rejected";
595
+ throw e;
596
+ }
597
+ const b = inert;
598
+ const face = this.backend.importBundleCommit;
599
+ if (typeof face !== "function") {
600
+ const e = new Error("importMemoryBundle: this backend has no bundle-import capability (importBundleCommit) — the import latch/receipt transaction requires it, and an unlatched import would serve entries before their governance lands; nothing was imported.");
601
+ e.code = "memory.import_rejected";
602
+ throw e;
603
+ }
604
+ const bundleHash = computeMemoryBundleHash(b.integrity.sectionHashes);
605
+ const custody = [];
606
+ const seenEv = new Set();
607
+ for (const row of b.governance.custody) {
608
+ if (seenEv.has(row.ev))
609
+ continue;
610
+ seenEv.add(row.ev);
611
+ custody.push(row);
612
+ }
613
+ const plan = {
614
+ bundleHash,
615
+ sourceStoreId: b.storeId,
616
+ entries: b.entries,
617
+ challenges: b.governance.challenges,
618
+ pollutedSessions: b.governance.pollutedSessions,
619
+ lineage: b.governance.lineage,
620
+ custody,
621
+ };
622
+ return face.call(this.backend, plan);
623
+ }
524
624
  challengeSession(sessionId, reason, requestId) {
525
625
  if (typeof requestId !== "string" || requestId === "") {
526
626
  const e = new Error("challengeSession: requestId is required (idempotency identity — retries must reuse it; the engine does not mint one)");
@@ -822,7 +922,9 @@ export class MemoryEngine {
822
922
  const rec = reconcileLineage(this.controlDir, this.now);
823
923
  this.settlePromotions(rec.promoted);
824
924
  for (const u of rec.undecidable) {
825
- report.warnings.push(`memory lineage transaction ${u.txnId} is unsettled (a crash landed between commit and its durable credential) — ${u.entryIds.length} entr${u.entryIds.length === 1 ? "y is" : "ies are"} latched (memory reads refuse them) until the host adjudicates it (adjudicatePendingLineage)`);
925
+ report.warnings.push(u.kind === "latch-only"
926
+ ? `memory bundle-import latch ${u.txnId} is unsettled (an import did not run to completion) — ${u.entryIds.length} entr${u.entryIds.length === 1 ? "y is" : "ies are"} latched (memory reads refuse them) until the SAME bundle is imported again to completion (importMemoryBundle; adjudication is refused on import latches)`
927
+ : `memory lineage transaction ${u.txnId} is unsettled (a crash landed between commit and its durable credential) — ${u.entryIds.length} entr${u.entryIds.length === 1 ? "y is" : "ies are"} latched (memory reads refuse them) until the host adjudicates it (adjudicatePendingLineage)`);
826
928
  }
827
929
  if (pollutedReason !== undefined && lineageSessionId !== undefined) {
828
930
  const rec2 = this.sessionPollution(lineageSessionId);
@@ -0,0 +1,192 @@
1
+ import { type TransferEvidence } from "./file-backend.js";
2
+ import type { MemoryEntry } from "./types.js";
3
+ /** One exported challenge event (unresolved generations only). Generations are DESTINATION-minted:
4
+ * the bundle deliberately carries no generation number — the importing store re-mints events under
5
+ * its own idempotent namespace and allocates generations there. */
6
+ export interface BundleChallengeRow {
7
+ eventId: string;
8
+ entryId: string;
9
+ reason: string;
10
+ at: number;
11
+ challengedRev?: string;
12
+ }
13
+ /** One exported durable pollution marker (verbatim source record). */
14
+ export interface BundlePollutedSession {
15
+ sessionId: string;
16
+ at: number;
17
+ reason: string;
18
+ }
19
+ /** One exported committed lineage contribution (verbatim `lastRev`/`lastAt`). */
20
+ export interface BundleLineageRow {
21
+ entryId: string;
22
+ sessionId: string;
23
+ lastRev: string;
24
+ lastAt: number;
25
+ }
26
+ /**
27
+ * design/178 v2 §4.3 + v2-c §6 — the governance-complete export bundle. The governance section is
28
+ * STRUCTURALLY REQUIRED (a bundle without it is a bad package — there is no "entries-only import"
29
+ * escape hatch: an optional hatch is a laundering channel, and the fixed section-hash key set makes
30
+ * a stripped section fail integrity outright). `doc` is human-readable disclosure text OUTSIDE the
31
+ * hashed sections (documentation, not governance data); unknown root keys are read-tolerated and
32
+ * never re-export-preserved (a re-export always mints a fresh bundle from store state).
33
+ */
34
+ export interface MemoryExportBundle {
35
+ /** Envelope version — a reader meeting `bundle > 1` must refuse, never reinterpret. */
36
+ bundle: 1;
37
+ at: number;
38
+ scopes: string[];
39
+ /** The source store's durable identity (control-plane `store-identity.json`, minted at the first
40
+ * export) — the import side's `origin` stamp for custody rows that carry none. */
41
+ storeId: string;
42
+ /** Full-fidelity entries (provenance/trust/extra verbatim). A duplicate id is a bad package. */
43
+ entries: MemoryEntry[];
44
+ governance: {
45
+ challenges: BundleChallengeRow[];
46
+ pollutedSessions: BundlePollutedSession[];
47
+ lineage: BundleLineageRow[];
48
+ /** Evidence-chain rows sliced to the exported scopes (v2-c §2 table): open form — a consumer
49
+ * tolerates unknown keys; unknown CHANNELS never leave the exporter (they refuse the export). */
50
+ custody: TransferEvidence[];
51
+ };
52
+ /** Honest enumeration of what the bundle does NOT carry. The `unbound`/`pendingLatch` name lists
53
+ * are populated on a FULL-STORE export only (one trust domain); a subset export ships empty
54
+ * lists and the counts (an id list would leak another tenant's identifiers). The counts are
55
+ * always present and always equal the honest total, so a consumer reads ONE seat either way. */
56
+ residuals: {
57
+ quarantined: string[];
58
+ unbound: string[];
59
+ pendingLatch: string[];
60
+ contentUnavailable: Array<{
61
+ id: string;
62
+ scope: string;
63
+ slug: string;
64
+ rev: string;
65
+ reason: string;
66
+ }>;
67
+ quarantineOpaque: number;
68
+ /** Custody rows a SUBSET export could not slice (no scope to slice by — unbound-deletion
69
+ * history among them): the anti-resurrection guarantee for those ids does not travel. */
70
+ unsliceableCustody: number;
71
+ unboundCount: number;
72
+ pendingLatchCount: number;
73
+ };
74
+ /** Accident detection, not forgery-proofing (see module note). `sectionHashes` carries EXACTLY
75
+ * the four fixed keys (meta/entries/governance/residuals); `revs` and `entries` are mutually
76
+ * pointing (same id set, verbatim revs, `entryCount` ≡ both cardinalities). */
77
+ integrity: {
78
+ entryCount: number;
79
+ revs: Record<string, string>;
80
+ sectionHashes: Record<string, string>;
81
+ };
82
+ /** Human-readable disclosure sentences (free-string carriage, subset residuals). Outside the
83
+ * hashed sections by design — documentation, not governance data. */
84
+ doc: string[];
85
+ }
86
+ /** design/178 v2-c §7 — the import answer (the parent-design draft fields plus the v2-c report
87
+ * seats). Every negative disposition is REPORTED, never silent. */
88
+ export interface MemoryImportReport {
89
+ v: 1;
90
+ /** The bundle identity (sha256 over the canonical section-hash map) — the latch/remint salt. */
91
+ bundleHash: string;
92
+ sourceStoreId: string;
93
+ landed: Array<{
94
+ id: string;
95
+ scope: string;
96
+ slug: string;
97
+ }>;
98
+ /** id+rev+scope three-way idempotent skips (slug tolerates the deterministic `-n` suffix). */
99
+ alreadyPresent: string[];
100
+ /** The destination chain carries a delete row for the id and no live row survives — resurrection
101
+ * refused (no override; a legitimately re-created id has a live row and takes the ordinary
102
+ * CAS/conflict path instead). */
103
+ refusedErased: string[];
104
+ /** Fresh add of a `repo_file`-provenance entry with no `trust` marker — whitewash refused. */
105
+ refusedUntrusted: string[];
106
+ conflicts: Array<{
107
+ id: string;
108
+ reason: string;
109
+ }>;
110
+ /** Entry-attached governance (challenges/lineage) withheld because its entry neither landed nor
111
+ * matched already-present-at-the-same-rev — misattribution is worse than omission. */
112
+ governanceWithheld: string[];
113
+ /** Source chain carries a delete row while the destination holds a LIVE entry for the id: named
114
+ * for deployment adjudication, never deleted on the import's behalf. */
115
+ erasedAtSource: string[];
116
+ pollutionDivergence: Array<{
117
+ sessionId: string;
118
+ kept: "destination";
119
+ }>;
120
+ /** Governance rows pointing at ids outside entries ∪ the destination account (custody rows are
121
+ * exempt — an absent-id delete row is exactly the anti-resurrection payload). */
122
+ referentialOrphans: string[];
123
+ lineageDivergence: Array<{
124
+ entryId: string;
125
+ sessionId: string;
126
+ kept: "destination";
127
+ }>;
128
+ /** Custody rows withheld unappended: unknown channel, or a known-channel row failing the store's
129
+ * own validation — reported per row, never a whole-package refusal (a newer exporter must not
130
+ * brick an older importer) and never a verbatim append (an unvalidatable row would poison the
131
+ * fail-closed chain). `srcEv` is the row's source identity (its own `srcEv` when it carries one,
132
+ * else its `ev`). */
133
+ custodyWithheld: Array<{
134
+ srcEv: string;
135
+ channel: string;
136
+ }>;
137
+ /** Custody rows submitted to the destination chain under this bundle's namespace (idempotent
138
+ * re-imports answer the same number — the reconciliation handle). */
139
+ custodyAppended: number;
140
+ }
141
+ /** What the engine hands the File backend's `importBundleCommit` face after the pure validation
142
+ * pass: the bundle's governed content plus its computed identity. Everything here is inert plain
143
+ * data (the engine clones the caller's bundle through a JSON round-trip before validating). */
144
+ export interface MemoryBundleImportPlan {
145
+ bundleHash: string;
146
+ sourceStoreId: string;
147
+ entries: MemoryEntry[];
148
+ challenges: BundleChallengeRow[];
149
+ pollutedSessions: BundlePollutedSession[];
150
+ lineage: BundleLineageRow[];
151
+ /** Open custody rows (byte-equal duplicates already collapsed by validation). */
152
+ custody: Array<Record<string, unknown>>;
153
+ }
154
+ /** The four fixed integrity section keys (§6.3) — missing OR extra keys are a bad package. */
155
+ export declare const BUNDLE_SECTION_KEYS: readonly ["meta", "entries", "governance", "residuals"];
156
+ /** The canonical per-section hashes of a bundle's four sections (meta = the bundle/at/scopes/
157
+ * storeId scalars). Canonical JSON (recursively sorted keys) + LF, sha256 hex. */
158
+ export declare function computeBundleSectionHashes(bundle: Pick<MemoryExportBundle, "bundle" | "at" | "scopes" | "storeId" | "entries" | "governance" | "residuals">): Record<string, string>;
159
+ /**
160
+ * The bundle IDENTITY: sha256 over the canonical JSON of `integrity.sectionHashes` (full 64-hex —
161
+ * the import latch id, the challenge/custody remint salt, and the report echo all share it). The
162
+ * section hashes already seal the section contents, so their sorted map IS the package identity;
163
+ * a tampered package fails integrity before its hash ever matters.
164
+ */
165
+ export declare function computeMemoryBundleHash(sectionHashes: Record<string, string>): string;
166
+ /**
167
+ * design/178 v2-c §1-2① — the PURE bundle validation (integrity/structure/self-consistency; zero
168
+ * store reads, zero side effects). Returns the FIRST violation, or undefined (valid). Every
169
+ * violation is a whole-package refusal at the caller — nothing lands, no latch is set.
170
+ */
171
+ export declare function memoryBundleInvalid(raw: unknown, opts?: {
172
+ expectedScopes?: readonly string[];
173
+ }): string | undefined;
174
+ /** What the exporter feeds {@link assembleMemoryExportBundle} — the store-consistent snapshot plus
175
+ * the already-sliced governance pieces (pure data; the snapshot fence lives in the backend). */
176
+ export interface MemoryExportBundleInput {
177
+ at: number;
178
+ scopes: string[];
179
+ storeId: string;
180
+ entries: MemoryEntry[];
181
+ challenges: BundleChallengeRow[];
182
+ pollutedSessions: BundlePollutedSession[];
183
+ lineage: BundleLineageRow[];
184
+ custody: TransferEvidence[];
185
+ residuals: MemoryExportBundle["residuals"];
186
+ }
187
+ /** The C-1 disclosure sentence (recommended-arm ruling posture: free strings ride verbatim and the
188
+ * bundle SAYS so — coding them away would destroy the identity/attribution keys idempotency and
189
+ * the remint namespaces depend on). */
190
+ 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.";
191
+ /** Assemble the bundle: deterministic ordering, section hashes, disclosure doc. Pure. */
192
+ export declare function assembleMemoryExportBundle(input: MemoryExportBundleInput): MemoryExportBundle;