@sema-agent/core 5.39.0 → 5.41.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 (38) hide show
  1. package/CHANGELOG.md +150 -11
  2. package/dist/core/auto-mode-prompt.js +1 -1
  3. package/dist/core/checkpoint-store.d.ts +12 -0
  4. package/dist/core/governance-codes.js +2 -0
  5. package/dist/core/hooks.js +9 -1
  6. package/dist/core/memory-engine/engine.d.ts +27 -0
  7. package/dist/core/memory-engine/engine.js +103 -1
  8. package/dist/core/memory-engine/export-bundle.d.ts +192 -0
  9. package/dist/core/memory-engine/export-bundle.js +306 -0
  10. package/dist/core/memory-engine/file-backend.d.ts +178 -1
  11. package/dist/core/memory-engine/file-backend.js +648 -6
  12. package/dist/core/memory-engine/index.d.ts +2 -1
  13. package/dist/core/memory-engine/index.js +1 -0
  14. package/dist/core/memory-engine/layout.d.ts +99 -1
  15. package/dist/core/memory-engine/layout.js +143 -7
  16. package/dist/core/memory-engine/memory-backend-contract.d.ts +1 -1
  17. package/dist/core/memory-engine/memory-backend-contract.js +52 -0
  18. package/dist/core/memory-engine/tools.js +8 -1
  19. package/dist/core/permission-rule-consent.js +14 -2
  20. package/dist/core/runner/prepare-config-doors.js +20 -0
  21. package/dist/core/runner/prepare-task.js +13 -0
  22. package/dist/core/runner/runtask.js +14 -1
  23. package/dist/core/runner/synthetic-tools.js +3 -1
  24. package/dist/core/runner/tool-disclosure.js +2 -1
  25. package/dist/core/types.d.ts +7 -0
  26. package/dist/core/write-protect.d.ts +0 -20
  27. package/dist/core/write-protect.js +4 -3
  28. package/dist/engine/harness/agent-harness.d.ts +17 -0
  29. package/dist/engine/harness/agent-harness.js +19 -1
  30. package/dist/engine/harness/types.d.ts +5 -0
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/tools/fs/safety.d.ts +1 -1
  34. package/dist/tools/fs/safety.js +1 -1
  35. package/dist/tools/fs/search.d.ts +33 -0
  36. package/dist/tools/fs/search.js +72 -0
  37. package/package.json +1 -1
  38. package/test/export-surface.snapshot.json +9 -1
@@ -1,4 +1,5 @@
1
- import { scopeDirName, type MemoryPartitionIncidentSink } from "./layout.js";
1
+ import { scopeDirName, type ChallengeEvent, type MemoryPartitionIncidentSink } from "./layout.js";
2
+ import type { MemoryBundleImportPlan, MemoryImportReport } from "./export-bundle.js";
2
3
  import type { HarvestRejection, MemoryBackend, MemoryEntry, MemoryEntryHeader, NotePatch, PatchReport, ScoredMemoryEntry } from "./types.js";
3
4
  /** Files/dirs the entry scan never treats as entries: the derived index, dotfiles (`.hydrate`,
4
5
  * transaction staging files), and anything not `.md`. */
@@ -288,6 +289,55 @@ export interface EntryCustodyReport {
288
289
  events: TransferEvidence[];
289
290
  reason?: string;
290
291
  }
292
+ /**
293
+ * design/178 v2-c §3 — what the ONE-LOCK export composite ({@link FileMemoryEngineBackend.exportSnapshotOf})
294
+ * answers: every face of the store the bundle needs, read inside a single txn-mutex hold (see the
295
+ * method doc for the fence law). Entries carry committed content only; a bound row whose committed
296
+ * carrier cannot be served is enumerated under `contentUnavailable` instead (never silently
297
+ * dropped, never dressed as content). `unbound`/`pendingLatch` are STORE-LEVEL residual sets (no
298
+ * tenant to belong to); the bundle assembly decides whether their id lists travel (full-store
299
+ * export) or only their counts (subset export). `challenges`/`lineage`/`pollutedSessions` are the
300
+ * RAW governance faces — the (pure) bundle assembly slices them to the exported entry/session
301
+ * sets; `custody` is already sliced (the slicing consumes row-level scope fields this module owns).
302
+ */
303
+ export interface MemoryExportSnapshot {
304
+ storeId: string;
305
+ /** True ⇔ the requested scopes cover every registered scope (one trust domain — the residual id
306
+ * lists and unbound-deletion custody rows may travel). */
307
+ fullStore: boolean;
308
+ entries: MemoryEntry[];
309
+ contentUnavailable: Array<{
310
+ id: string;
311
+ scope: string;
312
+ slug: string;
313
+ rev: string;
314
+ reason: string;
315
+ }>;
316
+ unbound: string[];
317
+ pendingLatch: string[];
318
+ custody: TransferEvidence[];
319
+ unsliceableCustody: number;
320
+ challenges: ChallengeEvent[];
321
+ lineage: Array<{
322
+ entryId: string;
323
+ sessionId: string;
324
+ lastRev: string;
325
+ lastAt: number;
326
+ }>;
327
+ pollutedSessions: Array<{
328
+ sessionId: string;
329
+ at: number;
330
+ reason: string;
331
+ }>;
332
+ quarantined: string[];
333
+ quarantineOpaque: number;
334
+ }
335
+ /** Test hook (证伪式复审 L3): small constants so the crash-window leg does not really wait 15-30s. */
336
+ interface TxnLockTimings {
337
+ staleMs?: number;
338
+ waitMs?: number;
339
+ stealGraceMs?: number;
340
+ }
291
341
  /** Construction options for {@link FileMemoryEngineBackend}. */
292
342
  export interface FileMemoryEngineBackendOptions {
293
343
  /** ⚠️ 分家坑(service 报告 2026-07-09):不显式传时,控制平面按无参 `resolveMemoryEngineRoot()`
@@ -369,6 +419,10 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
369
419
  private adoptionNoticeKeys;
370
420
  /** Test seam (§7.4 r4-④ arm): force a post-commit-point transfer-append failure. */
371
421
  private transfersAppendFault?;
422
+ /** Test seam (v2-c §3-2 fence pins): runs INSIDE the export composite's mutex hold, after the
423
+ * data reads and before the second fingerprint — a test injects a governance-face write here to
424
+ * exercise the drift-retry and the three-round refusal. Production leaves it unset. */
425
+ private exportFaceProbe?;
372
426
  /** v2-b §3-4 — the resurrection backstop's chain digest, keyed by the evidence log's stat
373
427
  * fingerprint (size + mtimeMs — append-only growth and a torn-tail shrink both move `size`).
374
428
  * NEVER a mount-lifetime cache: the fingerprint is re-verified under the txn mutex on every
@@ -972,6 +1026,129 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
972
1026
  /** The pure open-form evidence read behind {@link custodyOf} (no healing, no writes). `raw` is
973
1027
  * the caller's already-fenced read of the log (undefined = absent). */
974
1028
  private custodyReadPure;
1029
+ /** design/178 v2-c §4 — the export/import refusal code (engine coded-error family, deliberately
1030
+ * outside the governance registry: a store-state refusal, not a per-principal verdict). */
1031
+ private static readonly EXPORT_INCOMPLETE;
1032
+ /** The durable store identity (§6.2): read it, or mint it (`wx`) on first need — the txn mutex is
1033
+ * held by every caller, so the exclusive create cannot race in-process; a cross-process EEXIST
1034
+ * loser adopts the winner's identity. Corrupt/unreadable identity is fail-closed. */
1035
+ private storeIdentityLocked;
1036
+ /** One consistency fingerprint over the three ENGINE governance faces (challenge ledger, lineage
1037
+ * ledger, pollution markers) — raw bytes, journal included. The two strict sidecars' JOURNAL
1038
+ * reads are wrapped FAIL-CLOSED here (§4-6): the shared strict reader folds a journal read
1039
+ * error into absence, and an export that read a stale main file over an unreadable journal
1040
+ * would seal stale governance under an honest hash. Marker files that exist but cannot be read
1041
+ * fingerprint their error code (a stable fault stays stable; a flapping one drifts the fence). */
1042
+ private exportGovernanceFingerprint;
1043
+ /**
1044
+ * The export chain read: PURE INSPECTION (never heals, never quarantines, never truncates — C-8
1045
+ * pins the file byte-identical across a refused export). A torn tail refuses the export
1046
+ * (`memory.export_incomplete`; the deployment heals it by running any locked mutation entry,
1047
+ * then re-exports); an unknown channel refuses too (T-14: a row whose slicing rules this build
1048
+ * does not know can neither be carried — possible cross-tenant leak — nor silently dropped —
1049
+ * lost mandatory governance); one ev under two different payloads refuses (spliced chain);
1050
+ * non-tail corruption stays the fail-closed {@link ControlPlaneCorruptError}. Byte-equal
1051
+ * duplicates collapse (append-idempotency residue).
1052
+ */
1053
+ private readExportChainRowsPure;
1054
+ /**
1055
+ * v2-c §2 — the tenant-slicing table over validated chain rows (T-6..T-11; T-9/T-10 refusals,
1056
+ * T-14 already refused by the reader). `storeId` is a thunk so the identity mints only when a
1057
+ * redaction actually needs the provenance stamp.
1058
+ */
1059
+ private sliceCustodyForScopes;
1060
+ /** T-12 — quarantine residual enumeration for an id set (names for attributable hits, an opaque
1061
+ * count for the rest — same honest bound the erasure attestation uses). */
1062
+ private enumerateQuarantineFor;
1063
+ /**
1064
+ * design/178 v2-c §3 — the ONE-LOCK export composite (capability face; probe with
1065
+ * `typeof backend.exportSnapshotOf === "function"`). All five reads — entries (ledger-driven,
1066
+ * committed content), custody chain, challenge ledger, lineage ledger, pollution markers — happen
1067
+ * inside a single txn-mutex hold: the two backend faces are writer-serialized by the mutex
1068
+ * itself; the three engine faces (written under their own sidecar locks, not the mutex) are
1069
+ * fenced by a byte-level double fingerprint around the reads (≤3 rounds, then a loud refusal —
1070
+ * a store too hot to snapshot answers `memory.export_incomplete`, never a mixed-epoch bundle).
1071
+ * The terminal ownership assertion turns a stolen lock (stale-steal past the 30s line during a
1072
+ * long assembly) into a refusal: an unstolen lock means no writer entered, which IS the
1073
+ * consistency proof — no lease machinery needed. The chain read is pure inspection (a torn tail
1074
+ * refuses with the file byte-untouched); `complete:false`-class scenes all refuse loudly — there
1075
+ * is no degraded bundle shape, by design (§4).
1076
+ */
1077
+ exportSnapshotOf(scopes: readonly string[], timings?: TxnLockTimings): Promise<MemoryExportSnapshot>;
1078
+ /**
1079
+ * design/178 v2 §4.3 — the single-item governance slice face (parent-design form, kept for
1080
+ * NON-export consumers: erasure resolution, per-id inspection tooling). Answers
1081
+ * `complete: false` + reason instead of a bundle-shaped refusal (its callers fail closed on the
1082
+ * flag); definite corruption still throws. The export path deliberately does NOT use this face —
1083
+ * two independently-locked single faces are exactly the cross-face tear the composite closes.
1084
+ */
1085
+ governanceExport(scopes: readonly string[], timings?: TxnLockTimings): Promise<{
1086
+ custody: TransferEvidence[];
1087
+ unbound: string[];
1088
+ complete: boolean;
1089
+ unsliceableCustody: number;
1090
+ reason?: string;
1091
+ }>;
1092
+ /**
1093
+ * design/178 v2-c §5.4 — custody carriage onto THIS chain (capability face). Rows land under the
1094
+ * import namespace — `ev = imp:<bundleHash>:<source ev>` (full-hash salt: deterministic per
1095
+ * bundle, chain-deterministic across hops — a re-exported imported row salts its CURRENT ev
1096
+ * next hop), `origin` kept when present (multi-hop provenance) else stamped with the source
1097
+ * store's identity, `srcEv` kept else stamped with the source ev — so an imported row is ALWAYS
1098
+ * origin-bearing and the local replay/anchor judgments structurally never read it. Unknown
1099
+ * channels and rows failing this store's own validation are WITHHELD and reported (never
1100
+ * appended verbatim — an unvalidatable row would poison the fail-closed chain — and never a
1101
+ * whole-package refusal — a newer exporter must not brick an older importer). Appends are
1102
+ * idempotent by ev with canonical-payload comparison (same-bundle re-import converges; a
1103
+ * different payload under an existing ev is fail-closed corruption). Direct chain append, no
1104
+ * journal: custody carriage is not a ledger mutation, and origin-bearing rows are forbidden in
1105
+ * journals by the recovery leg's own validation.
1106
+ */
1107
+ custodyImport(rows: ReadonlyArray<Record<string, unknown>>, opts: {
1108
+ bundleHash: string;
1109
+ sourceStoreId: string;
1110
+ }, timings?: TxnLockTimings): Promise<{
1111
+ appended: number;
1112
+ withheld: Array<{
1113
+ srcEv: string;
1114
+ channel: string;
1115
+ }>;
1116
+ }>;
1117
+ /** The in-lock custody carriage body ({@link custodyImport}'s and the import composite's shared
1118
+ * leg — the composite already holds the mutex and must not re-enter the acquisition). */
1119
+ private custodyImportLocked;
1120
+ /** Append import-namespace challenge events and VERIFY every key-idempotent replay by reading
1121
+ * the full payload back (§5.2 defense ③): `appendChallengeEvents` is key-idempotent, not
1122
+ * payload-idempotent — a pre-planted same-key event (a resolve, or a same-entry different-
1123
+ * reason challenge) would silently swallow the imported challenge while the return echoes a
1124
+ * plausible assignment. A mismatch ABORTS the import (the latch stays; the account is not
1125
+ * silently short). */
1126
+ private appendImportChallengesVerified;
1127
+ /**
1128
+ * design/178 v2-c §1 — the IMPORT COMPOSITE (capability face; the engine's `importMemoryBundle`
1129
+ * is its validating membrane). One txn-mutex hold end to end:
1130
+ * ①b destination-chain completeness precheck (torn/degraded ⇒ whole package refused with ZERO
1131
+ * side effects — before the latch, "reject before landing" made literal);
1132
+ * ② the synthetic latch (durable pending lineage txn over every bundle entry id — from here
1133
+ * to release, those ids are withheld on every model-visible read face; crash-safe);
1134
+ * ③ authoritative in-lock judgment (the five import rules — a pre-lock classification would
1135
+ * only be advisory, so there is none: the lock-held pass IS the classification);
1136
+ * ④ entry landing through {@link applyPatchesLocked} (same skeleton, same lock, same recovery
1137
+ * leg — never a second transaction protocol; `guard:"absent"` adjudicates the residual
1138
+ * same-id arms);
1139
+ * ⑤ governance legs, custody FIRST unconditionally (chain facts are exempt from the
1140
+ * entry-eligibility rule — an absent-id delete row is exactly the anti-resurrection
1141
+ * payload), then entry-attached lineage/challenges for landed ∪ already-present ids only,
1142
+ * then pollution (retroactive sweep DURABLE BEFORE the marker — the crash between them
1143
+ * leaves contributions challenged and the marker converging on re-import, never the
1144
+ * reverse);
1145
+ * ⑥ the durable completion receipt (`wx`, carrying the report) and the latch release —
1146
+ * receipt-gated, the ONLY release channel.
1147
+ * Any failure past ② leaves the latch standing: re-importing the SAME bundle converges every
1148
+ * crash window (idempotent latch, idempotent judgments, ev/key-idempotent governance legs,
1149
+ * receipt short-circuit).
1150
+ */
1151
+ importBundleCommit(plan: MemoryBundleImportPlan, timings?: TxnLockTimings): Promise<MemoryImportReport>;
975
1152
  getConsolidationCursor(scope: string): Promise<string | undefined>;
976
1153
  setConsolidationCursor(scope: string, cursor: string): Promise<void>;
977
1154
  private readCursors;