@sema-agent/core 5.22.0 → 5.23.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 (52) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/agents/subagent.js +3 -2
  3. package/dist/core/governance-codes.js +3 -0
  4. package/dist/core/hooks.d.ts +62 -1
  5. package/dist/core/hooks.js +90 -12
  6. package/dist/core/memory-engine/engine.d.ts +28 -1
  7. package/dist/core/memory-engine/engine.js +62 -3
  8. package/dist/core/memory-engine/index.d.ts +1 -1
  9. package/dist/core/memory-engine/index.js +1 -1
  10. package/dist/core/memory-engine/layout.d.ts +69 -3
  11. package/dist/core/memory-engine/layout.js +75 -6
  12. package/dist/core/permission-rule-consent.js +2 -1
  13. package/dist/core/permission-rule-org.d.ts +36 -2
  14. package/dist/core/permission-rule-org.js +23 -0
  15. package/dist/core/permission-rule-store.js +2 -1
  16. package/dist/core/permission-rule-sync.d.ts +8 -0
  17. package/dist/core/permission-rule-sync.js +35 -6
  18. package/dist/core/runner/prepare-task.d.ts +10 -2
  19. package/dist/core/runner/prepare-task.js +111 -5
  20. package/dist/core/runner/runtask.js +19 -0
  21. package/dist/core/tool-policy.d.ts +37 -4
  22. package/dist/core/tool-policy.js +32 -4
  23. package/dist/core/tool-result-store.d.ts +9 -1
  24. package/dist/core/tool-result-store.js +2 -1
  25. package/dist/core/trace.d.ts +47 -0
  26. package/dist/core/types.d.ts +38 -0
  27. package/dist/core/wiring-manifest.d.ts +16 -1
  28. package/dist/core/wiring-manifest.js +7 -1
  29. package/dist/index.d.ts +5 -3
  30. package/dist/index.js +4 -2
  31. package/dist/orchestration/goal.d.ts +10 -0
  32. package/dist/orchestration/goal.js +6 -5
  33. package/dist/stores/file/adoption/adopt.d.ts +146 -0
  34. package/dist/stores/file/adoption/adopt.js +616 -0
  35. package/dist/stores/file/adoption/marker.d.ts +194 -0
  36. package/dist/stores/file/adoption/marker.js +198 -0
  37. package/dist/stores/file/background-agent-store.js +2 -0
  38. package/dist/stores/file/checkpoint-store.js +2 -0
  39. package/dist/stores/file/file-snapshot-store.js +2 -0
  40. package/dist/stores/file/index.d.ts +2 -0
  41. package/dist/stores/file/index.js +4 -0
  42. package/dist/stores/file/mailbox-store.js +2 -0
  43. package/dist/stores/file/memory-store.js +2 -0
  44. package/dist/stores/file/session-policy-store.js +2 -0
  45. package/dist/stores/file/session-store.js +2 -0
  46. package/dist/stores/file/task-list-store.js +2 -0
  47. package/dist/stores/file/tool-result-store.js +2 -0
  48. package/dist/stores/file/usage-window-store.js +2 -0
  49. package/dist/stores/file/workflow-journal-store.js +2 -0
  50. package/dist/stores/file/workflow-run-store.js +2 -0
  51. package/dist/tools/fs/bash-readonly-classifier.js +59 -10
  52. package/package.json +3 -2
@@ -0,0 +1,194 @@
1
+ /**
2
+ * design/183 §3 (F-011) — the ROOT-level adoption marker: the durable phase truth of the data-root
3
+ * adoption state machine (the design/182 rule-bucket marker, generalized to the whole data root).
4
+ *
5
+ * Two shapes live in `<dataRoot>/adoption.json`:
6
+ * - the IN-FLIGHT marker (`marker`): a monotonically-advanced `phase` plus per-leg completion bits
7
+ * for the one leg class without a self-attesting completion predicate (row rewrites). While this
8
+ * shape is on disk the root is FROZEN: every file store constructor refuses to build over it
9
+ * ({@link assertAdoptionBootGate} — invariant I6), so no writer can serve a half-migrated root.
10
+ * - the PERMANENT terminal record (`adopted`): never deleted. It is the idempotence anchor (a re-run
11
+ * with the same (from → toPrincipal) short-circuits to the recorded receipt) and it carries the
12
+ * IMMUTABLE report — the byte-stable half of every receipt.
13
+ *
14
+ * This module is deliberately tiny and dependency-light (node:fs + the atomic-write primitive): every
15
+ * file store imports the gate from here, so it must never grow an import back into a store.
16
+ */
17
+ /** Who the data root belonged to before adoption. Shape-identical to the rule domain's `RuleOwner`
18
+ * (design/182): local-single-user identity is a STRUCTURAL ABSENCE, never a sentinel string. */
19
+ export type AdoptionSource = {
20
+ kind: "local-owner";
21
+ } | {
22
+ kind: "principal";
23
+ principal: string;
24
+ };
25
+ /** Typed refusal codes (design/183 §7.3): every adoption refusal is discriminable — from `not_found`,
26
+ * from a broken deployment, and from each other. */
27
+ export type AdoptionErrorCode =
28
+ /** I6 boot gate: a store construction (or engine boot) over a root whose adoption is mid-flight. */
29
+ "adoption_in_flight"
30
+ /** A DIFFERENT adoption is mid-flight in this root — resume the original or repair the marker. */
31
+ | "adoption_in_flight_mismatch"
32
+ /** The root already completed an adoption toward another principal; a second adoption is a
33
+ * multi-tenant transfer, out of this protocol's scope (design/183 §1 non-goals / D7). */
34
+ | "adoption_retarget"
35
+ /** A live instance (engine or another adoption) owns the data root right now. */
36
+ | "adoption_locked"
37
+ /** A row-rewrite target exists with DIFFERENT bytes: merging two existing rows is not a rewrite. */
38
+ | "adoption_row_collision"
39
+ /** The marker exists but cannot be read as a marker. Fail-closed: after phase 4 the marker IS the
40
+ * owner-resolution truth, so guessing would fork ownership. Repair by hand, then resume. */
41
+ | "adoption_marker_corrupt"
42
+ /** A config-witness receipt does not bind (wrong adoption id / value / unknown entry). */
43
+ | "adoption_witness_mismatch"
44
+ /** A resume supplies LESS than the recorded migration plan (a dropped rules/carriage leg or a
45
+ * changed credentials claim) — completing the arc on the thinner plan would terminalize an
46
+ * incompletely migrated root, so the mismatch is refused instead. */
47
+ | "adoption_plan_mismatch"
48
+ /** The nested permission-rule leg refused (its own collision / lock / marker grammar) — surfaced
49
+ * under the root arc's typed contract, original reason preserved in the message. */
50
+ | "adoption_rule_leg_refused"
51
+ /** Malformed request (empty principal, from == to, …). */
52
+ | "adoption_bad_request";
53
+ export declare class AdoptionError extends Error {
54
+ readonly code: AdoptionErrorCode;
55
+ constructor(code: AdoptionErrorCode, message: string);
56
+ }
57
+ /** One deployment-config the adoption obligates (design/183 §6): configs the state machine cannot
58
+ * reach carry `migrated: false` and stay on the outstanding account until a consumer WITNESSES them. */
59
+ export interface AffectedDeploymentConfig {
60
+ /** e.g. "web-client-bff" | "cli" | "env" | "core-decl" | … */
61
+ deployment: string;
62
+ key: string;
63
+ /** The value the consumer must now carry (the new principal, or "withdrawn" for a retired seat). */
64
+ requiredValue: string;
65
+ /** true ⇔ the adoption itself changed it; false ⇒ an explicit operator action remains. */
66
+ migrated: boolean;
67
+ }
68
+ /** Per-store line of the immutable report (design/183 §6 `legs`). */
69
+ export interface AdoptionLegReport {
70
+ store: string;
71
+ action: "bucket-rebind" | "row-rewrite" | "carried" | "none" | "reset";
72
+ rows?: number;
73
+ quarantined?: number;
74
+ }
75
+ /** The IMMUTABLE half of every receipt (design/183 §6): persisted byte-stable in the terminal record,
76
+ * identical on the first success and on every idempotent re-run. Current state (outstanding configs,
77
+ * live quarantine count) is NOT in here — it is derived fresh per answer ({@link AdoptionReceipt}). */
78
+ export interface AdoptionReport {
79
+ /** Minted at phase ② — the binding anchor config-witness receipts must carry. */
80
+ adoptionId: string;
81
+ from: AdoptionSource;
82
+ toPrincipal: string;
83
+ atMs: number;
84
+ /** The claim REFERENCE for the new identity's credentials — never the credentials themselves (the
85
+ * marker is a plain-text sidecar, not a credential store). The claim must be durable and
86
+ * repeatedly redeemable (server obligation, design/183 §4.3). */
87
+ credentials: {
88
+ issuedBy: "server";
89
+ ref: string;
90
+ };
91
+ affectedDeploymentConfigs: AffectedDeploymentConfig[];
92
+ legs: AdoptionLegReport[];
93
+ /** design/183 §6 (r4) — the machine-readable CLOSED SET of assets not migrated BY DESIGN, each
94
+ * naming its ruling. Black-box discriminable from "forgotten": an asset outside this list that
95
+ * did not arrive is a defect; the list growing beyond the design's written set is DRIFT (§10.5
96
+ * ① — new members enter through a design ruling, never through an implementation). */
97
+ notMigratedByDesign: Array<{
98
+ asset: string;
99
+ ruling: string;
100
+ }>;
101
+ }
102
+ /** The one receipt envelope (design/183 §6): first success and every idempotent re-run answer with
103
+ * this same shape. `immutableReport` is the persisted byte-stable snapshot; `current` is derived. */
104
+ export interface AdoptionReceipt {
105
+ status: "adopted";
106
+ immutableReport: AdoptionReport;
107
+ current: {
108
+ /** `deployment:key` of every config-account entry not yet WITNESSED (an operator ack does not
109
+ * clear the account — only a consumer's bound runtime read-back does; design/183 §3.3). */
110
+ outstandingConfigs: string[];
111
+ /** Live quarantine-area row count (local truth = what is actually on disk; design/183 §7.2). */
112
+ quarantined: number;
113
+ };
114
+ }
115
+ /** The durable root marker file. */
116
+ export type RootAdoptionFile = {
117
+ schemaVersion: 1;
118
+ marker: {
119
+ adoptionId: string;
120
+ from: AdoptionSource;
121
+ toPrincipal: string;
122
+ phase: 2 | 3 | 4 | 5;
123
+ /** The migration plan recorded at phase ② and grown MONOTONICALLY across resumes — what a
124
+ * resume must (at least) re-supply. Without it, an ordinary restart with thinner options
125
+ * would skip a stalled leg and terminalize an incompletely migrated root; the rules leg is
126
+ * bound to its canonical DIRECTORY (a boolean would accept a substituted bucket). REQUIRED:
127
+ * a marker without a readable plan is corrupt (fail-closed), never re-bound to the caller's
128
+ * current options (adversarial-review findings, rounds 1-2). */
129
+ plan: {
130
+ rulesDir?: string;
131
+ carriage: string[];
132
+ credentialsRef: string;
133
+ };
134
+ /** Completion bits for legs without a self-attesting predicate (row rewrites), plus the
135
+ * carriage RESULTS folded in at the phase-⑤ publish: the bits (and their counts, which the
136
+ * terminal report needs) survive a crash between a stage and the next phase publish — a
137
+ * ⑤→⑥ crash must not let the resumed run report a carried store as untouched. */
138
+ legs?: Record<string, {
139
+ done: true;
140
+ rows?: number;
141
+ quarantined?: number;
142
+ }>;
143
+ };
144
+ } | {
145
+ schemaVersion: 1;
146
+ adopted: {
147
+ from: AdoptionSource;
148
+ toPrincipal: string;
149
+ atMs: number;
150
+ immutableReport: AdoptionReport;
151
+ };
152
+ };
153
+ export declare const ROOT_ADOPTION_FILE = "adoption.json";
154
+ /**
155
+ * design/183 §6 (r4) — the CLOSED SET of by-design-not-migrated assets, verbatim from the design's
156
+ * written list. Lives HERE (beside the reader) because the terminal validator enforces EXACT
157
+ * equality: the report is the account's recovery source, and a reduced-or-grown list at read time
158
+ * is either damage or drift — both refused (§10.5 ①).
159
+ */
160
+ export declare const NOT_MIGRATED_BY_DESIGN: ReadonlyArray<{
161
+ asset: string;
162
+ ruling: string;
163
+ }>;
164
+ /** The five baseline obligation IDENTITIES (design/183 §6 minimum face). The terminal validator
165
+ * requires them all: a "reduced but shaped" report would otherwise rebuild a thinner account.
166
+ * The writer's `baselineConfigs` emits exactly these — any drift breaks the writer's own
167
+ * read-back immediately (self-checking pair). */
168
+ export declare const BASELINE_CONFIG_IDENTITIES: ReadonlyArray<{
169
+ deployment: string;
170
+ key: string;
171
+ }>;
172
+ /**
173
+ * Read the root adoption marker; `undefined` when none. A corrupt marker THROWS (typed): after
174
+ * phase 4 the marker is the resolution truth, so a guessed read could serve a half-migrated root or
175
+ * invent an adoption that never happened — the same fail-closed stance as the rule-domain marker.
176
+ */
177
+ export declare function readRootAdoptionFile(root: string): RootAdoptionFile | undefined;
178
+ /** Atomically publish the root marker (write-temp → fsync → rename, same discipline as every store). */
179
+ export declare function writeRootAdoptionFile(root: string, content: RootAdoptionFile): void;
180
+ /**
181
+ * Invariant I6 (design/183 §3.1) — the adoption BOOT GATE, called by every file store constructor:
182
+ * an in-flight root marker means the root is mid-adoption (possibly after a crash), and serving a
183
+ * half-migrated root is refused LOUDLY. The refusal names the in-flight arc and the way forward
184
+ * (resume `adoptLocalDataRoot` to completion). A terminal record passes: a completed adoption is a
185
+ * normal, readable root. A corrupt marker throws (fail-closed, see {@link readRootAdoptionFile}).
186
+ *
187
+ * This gate is what turns "the engine must be stopped during adoption" from an operational assumption
188
+ * into a machine-checked invariant across crashes: the adoption's own locks die with its process, but
189
+ * the marker (and this gate) survive. Boundary, stated honestly: the gate fires at CONSTRUCTION time.
190
+ * A store instance constructed BEFORE the marker landed in another OS process is outside it — cross-
191
+ * process sharing of one data dir is the file family's documented UNSUPPORTED shape (task-list F-14,
192
+ * mailbox RB-249); adoption adds no new promise there.
193
+ */
194
+ export declare function assertAdoptionBootGate(root: string, storeName: string): void;
@@ -0,0 +1,198 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { atomicWriteFile, ensureDir } from "../fs-atomic.js";
4
+ export class AdoptionError extends Error {
5
+ code;
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.code = code;
9
+ this.name = "AdoptionError";
10
+ }
11
+ }
12
+ export const ROOT_ADOPTION_FILE = "adoption.json";
13
+ export const NOT_MIGRATED_BY_DESIGN = [
14
+ { asset: "usage-window", ruling: "183-D1" },
15
+ { asset: "approval-history-fields", ruling: "183-D2" },
16
+ { asset: "sessions/<pid>.json", ruling: "3356-cli-enum" },
17
+ { asset: "session-env/<sessionId>/*.sh", ruling: "3356-cli-enum" },
18
+ { asset: "sema-ca.crt", ruling: "3356-cli-enum" },
19
+ { asset: "backups/", ruling: "3356-cli-enum" },
20
+ { asset: "config-lkg-*", ruling: "3356-cli-enum" },
21
+ ];
22
+ export const BASELINE_CONFIG_IDENTITIES = [
23
+ { deployment: "web-client-bff", key: "injected-principal" },
24
+ { deployment: "cli", key: "cloud-credentials+profiles" },
25
+ { deployment: "cli", key: "SEMA_LIVE_PRINCIPAL" },
26
+ { deployment: "env", key: "REQUIRE_PRINCIPAL" },
27
+ { deployment: "core-decl", key: "localOwnerRules" },
28
+ ];
29
+ function isSource(v) {
30
+ if (v === null || typeof v !== "object")
31
+ return false;
32
+ const s = v;
33
+ if (s.kind === "local-owner")
34
+ return true;
35
+ return s.kind === "principal" && typeof s.principal === "string" && s.principal !== "";
36
+ }
37
+ function isRootAdoptionFileShape(v) {
38
+ if (v === null || typeof v !== "object")
39
+ return false;
40
+ const f = v;
41
+ if (f.schemaVersion !== 1)
42
+ return false;
43
+ if ("marker" in f && !("adopted" in f)) {
44
+ const m = f.marker;
45
+ if (m === null ||
46
+ typeof m !== "object" ||
47
+ typeof m.adoptionId !== "string" ||
48
+ !isSource(m.from) ||
49
+ typeof m.toPrincipal !== "string" ||
50
+ typeof m.phase !== "number" ||
51
+ ![2, 3, 4, 5].includes(m.phase)) {
52
+ return false;
53
+ }
54
+ const p = m.plan;
55
+ if (p === null ||
56
+ p === undefined ||
57
+ typeof p !== "object" ||
58
+ (p.rulesDir !== undefined && typeof p.rulesDir !== "string") ||
59
+ !Array.isArray(p.carriage) ||
60
+ !p.carriage.every((s) => typeof s === "string") ||
61
+ typeof p.credentialsRef !== "string") {
62
+ return false;
63
+ }
64
+ if (m.legs !== undefined) {
65
+ if (m.legs === null || typeof m.legs !== "object" || Array.isArray(m.legs))
66
+ return false;
67
+ const okCount = (n) => n === undefined || (typeof n === "number" && Number.isSafeInteger(n) && n >= 0);
68
+ for (const bit of Object.values(m.legs)) {
69
+ if (bit === null || typeof bit !== "object")
70
+ return false;
71
+ const b = bit;
72
+ if (b.done !== true || !okCount(b.rows) || !okCount(b.quarantined))
73
+ return false;
74
+ }
75
+ }
76
+ const legs = (m.legs ?? {});
77
+ if (m.phase >= 3 && m.from.kind === "principal" && legs["session-policy"]?.done !== true)
78
+ return false;
79
+ if (m.phase === 5) {
80
+ for (const store of p.carriage) {
81
+ if (legs[`carriage:${store}`]?.done !== true)
82
+ return false;
83
+ }
84
+ }
85
+ const planned = new Set(p.carriage);
86
+ for (const key of Object.keys(legs)) {
87
+ if (key.startsWith("carriage:") && !planned.has(key.slice("carriage:".length)))
88
+ return false;
89
+ }
90
+ return true;
91
+ }
92
+ if ("adopted" in f && !("marker" in f)) {
93
+ const a = f.adopted;
94
+ if (a === null ||
95
+ typeof a !== "object" ||
96
+ !isSource(a.from) ||
97
+ typeof a.toPrincipal !== "string" ||
98
+ typeof a.atMs !== "number" ||
99
+ a.immutableReport === null ||
100
+ typeof a.immutableReport !== "object") {
101
+ return false;
102
+ }
103
+ const r = a.immutableReport;
104
+ const sameFrom = isSource(r.from) &&
105
+ (r.from.kind === "local-owner"
106
+ ? a.from.kind === "local-owner"
107
+ : a.from.kind === "principal" &&
108
+ r.from.principal === a.from.principal);
109
+ const okCount = (n) => n === undefined || (typeof n === "number" && Number.isSafeInteger(n) && n >= 0);
110
+ const configIds = new Set();
111
+ const configsOk = Array.isArray(r.affectedDeploymentConfigs) &&
112
+ r.affectedDeploymentConfigs.length > 0 &&
113
+ r.affectedDeploymentConfigs.every((c) => {
114
+ if (c === null || typeof c !== "object")
115
+ return false;
116
+ const e = c;
117
+ if (typeof e.deployment !== "string" || e.deployment === "" || typeof e.key !== "string" || e.key === "")
118
+ return false;
119
+ if (typeof e.requiredValue !== "string" || typeof e.migrated !== "boolean")
120
+ return false;
121
+ const id = JSON.stringify([e.deployment, e.key]);
122
+ if (configIds.has(id))
123
+ return false;
124
+ configIds.add(id);
125
+ return true;
126
+ }) &&
127
+ BASELINE_CONFIG_IDENTITIES.every((b) => configIds.has(JSON.stringify([b.deployment, b.key])));
128
+ const nmd = r.notMigratedByDesign;
129
+ const closedSetOk = Array.isArray(nmd) &&
130
+ nmd.length === NOT_MIGRATED_BY_DESIGN.length &&
131
+ NOT_MIGRATED_BY_DESIGN.every((m, i) => {
132
+ const e = nmd[i];
133
+ return (e !== null && typeof e === "object" && e.asset === m.asset && e.ruling === m.ruling);
134
+ });
135
+ const legsOk = Array.isArray(r.legs) &&
136
+ r.legs.every((l) => {
137
+ if (l === null || typeof l !== "object")
138
+ return false;
139
+ const e = l;
140
+ return (typeof e.store === "string" &&
141
+ typeof e.action === "string" &&
142
+ ["bucket-rebind", "row-rewrite", "carried", "none", "reset"].includes(e.action) &&
143
+ okCount(e.rows) &&
144
+ okCount(e.quarantined));
145
+ });
146
+ const credsOk = r.credentials !== null &&
147
+ typeof r.credentials === "object" &&
148
+ r.credentials.issuedBy === "server" &&
149
+ typeof r.credentials.ref === "string" &&
150
+ r.credentials.ref !== "";
151
+ return (typeof r.adoptionId === "string" &&
152
+ r.toPrincipal === a.toPrincipal &&
153
+ r.atMs === a.atMs &&
154
+ Number.isSafeInteger(a.atMs) &&
155
+ a.atMs >= 0 &&
156
+ sameFrom &&
157
+ closedSetOk &&
158
+ configsOk &&
159
+ legsOk &&
160
+ credsOk);
161
+ }
162
+ return false;
163
+ }
164
+ export function readRootAdoptionFile(root) {
165
+ let raw;
166
+ try {
167
+ raw = readFileSync(join(root, ROOT_ADOPTION_FILE), "utf8");
168
+ }
169
+ catch (err) {
170
+ if (err.code === "ENOENT")
171
+ return undefined;
172
+ throw new AdoptionError("adoption_marker_corrupt", `could not read the adoption marker in ${root}: ${err.message}`);
173
+ }
174
+ let parsed;
175
+ try {
176
+ parsed = JSON.parse(raw);
177
+ }
178
+ catch (err) {
179
+ throw new AdoptionError("adoption_marker_corrupt", `the adoption marker in ${root} is not valid JSON (${err.message}) — refusing to serve this data root until it is repaired`);
180
+ }
181
+ if (!isRootAdoptionFileShape(parsed)) {
182
+ throw new AdoptionError("adoption_marker_corrupt", `the adoption marker in ${root} does not carry a readable shape — refusing to serve this data root until it is repaired`);
183
+ }
184
+ return parsed;
185
+ }
186
+ export function writeRootAdoptionFile(root, content) {
187
+ ensureDir(root);
188
+ atomicWriteFile(join(root, "tmp"), join(root, ROOT_ADOPTION_FILE), JSON.stringify(content, null, 2));
189
+ }
190
+ export function assertAdoptionBootGate(root, storeName) {
191
+ const f = readRootAdoptionFile(root);
192
+ if (f !== undefined && "marker" in f) {
193
+ const from = f.marker.from.kind === "principal" ? `principal "${f.marker.from.principal}"` : "the local owner";
194
+ throw new AdoptionError("adoption_in_flight", `${storeName}: an adoption of this data root (${from} → "${f.marker.toPrincipal}", phase ${f.marker.phase}) is in flight — ` +
195
+ `resume adoptLocalDataRoot to completion (or repair ${join(root, ROOT_ADOPTION_FILE)}) before constructing stores over it; ` +
196
+ `serving a half-migrated root is refused`);
197
+ }
198
+ }
@@ -1,6 +1,7 @@
1
1
  import { join } from "node:path";
2
2
  import { BackgroundAgentStoreError, STALE_RUNNING_REAP_ATTRIBUTION, assertBackgroundAgentReapOptions, queryBackgroundAgents, } from "../../core/background-agent-store.js";
3
3
  import { SharedLedgerTable } from "./shared-ledger.js";
4
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
4
5
  const agentLedgers = new SharedLedgerTable({
5
6
  keyOf: (r) => FileBackgroundAgentStore.key(r.handle, r.scope),
6
7
  apply: (rows, ev) => {
@@ -20,6 +21,7 @@ export class FileBackgroundAgentStore {
20
21
  return this.ledger.rows;
21
22
  }
22
23
  constructor(root, opts = {}) {
24
+ assertAdoptionBootGate(root, "FileBackgroundAgentStore");
23
25
  this.fsyncEnabled = opts.fsync !== false;
24
26
  this.compactEvery = opts.compactEvery ?? 1000;
25
27
  const dir = join(root, "background-agents");
@@ -1,4 +1,5 @@
1
1
  import { join } from "node:path";
2
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
2
3
  import { appendPendingSteer, CheckpointError, checkpointOccMatches, checkpointRowMatches, summarizeCheckpoint, validatePendingSteer, winnerFromOutcome, } from "../../core/checkpoint-store.js";
3
4
  import { SharedLedgerTable } from "./shared-ledger.js";
4
5
  const CHECKPOINT_LEDGER_EVENT_REGISTRY = {
@@ -71,6 +72,7 @@ export class FileCheckpointStore {
71
72
  }
72
73
  fault = null;
73
74
  constructor(root, opts = {}) {
75
+ assertAdoptionBootGate(root, "FileCheckpointStore");
74
76
  this.fsyncEnabled = opts.fsync !== false;
75
77
  this.compactEvery = opts.compactEvery ?? 1000;
76
78
  const dir = join(root, "checkpoints");
@@ -3,6 +3,7 @@ import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { applyManifest, captureManifest, DEFAULT_SNAPSHOT_BOUNDS, } from "../../core/file-snapshot-store.js";
5
5
  import { canonicalStoreKey, ensureDir, sanitizePathComponent, sanitizeScope, writeThenLink } from "./fs-atomic.js";
6
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
6
7
  const sharedInFlight = new Map();
7
8
  export class FileFileSnapshotStore {
8
9
  base;
@@ -20,6 +21,7 @@ export class FileFileSnapshotStore {
20
21
  bounds;
21
22
  onCorruptRead;
22
23
  constructor(root, bounds, opts) {
24
+ assertAdoptionBootGate(root, "FileFileSnapshotStore");
23
25
  this.base = join(root, "file-snapshots");
24
26
  this.blobsDir = join(this.base, "blobs");
25
27
  this.manifestsDir = join(this.base, "manifests");
@@ -18,6 +18,8 @@ export { FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResu
18
18
  export { FileUsageWindowStore } from "./usage-window-store.js";
19
19
  export { resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock } from "./fs-atomic.js";
20
20
  export { atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog } from "./fs-atomic.js";
21
+ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootAdoptionFile, ROOT_ADOPTION_FILE, type AdoptionErrorCode, type AdoptionSource, type AdoptionReport, type AdoptionReceipt, type AdoptionLegReport, type AffectedDeploymentConfig, type RootAdoptionFile, } from "./adoption/marker.js";
22
+ export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, type AdoptionStatus, type AdoptLocalDataRootOptions, type AdoptLocalDataRootResult, type AdoptionCarriageLeg, type AdoptionCarriageLegContext, type AdoptionConfigWitnessReceipt, } from "./adoption/adopt.js";
21
23
  /**
22
24
  * design/80 — the file-backed `StorageBackend` for the local (TOC) single-user binary: the 3rd store impl
23
25
  * alongside `InMemory*` and `Pg*`, selected purely by what the embedder injects into `RunnerDeps` (no engine
@@ -2,6 +2,7 @@ import { join } from "node:path";
2
2
  import { guardedMemoryStore } from "../../core/memory.js";
3
3
  import { TtlSessionStore } from "../../core/session-store.js";
4
4
  import { BootLock, createFileConsolidationLock, resolveDataRoot } from "./fs-atomic.js";
5
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
5
6
  import { FileCheckpointStore } from "./checkpoint-store.js";
6
7
  import { FileMemoryStore } from "./memory-store.js";
7
8
  import { FileSessionRepo } from "./session-store.js";
@@ -20,6 +21,8 @@ export { FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResu
20
21
  export { FileUsageWindowStore } from "./usage-window-store.js";
21
22
  export { resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock } from "./fs-atomic.js";
22
23
  export { atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog } from "./fs-atomic.js";
24
+ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootAdoptionFile, ROOT_ADOPTION_FILE, } from "./adoption/marker.js";
25
+ export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, } from "./adoption/adopt.js";
23
26
  export class FileStorageBackend {
24
27
  root;
25
28
  sessionStore;
@@ -39,6 +42,7 @@ export class FileStorageBackend {
39
42
  fileWorkflowJournal;
40
43
  constructor(opts = {}) {
41
44
  this.root = resolveDataRoot(opts.root);
45
+ assertAdoptionBootGate(this.root, "FileStorageBackend");
42
46
  this.lock = new BootLock(join(this.root, "LOCK"));
43
47
  this.lock.acquire();
44
48
  try {
@@ -3,6 +3,7 @@ import { assertRetentionPolicy } from "../../core/retention-policy.js";
3
3
  import { existsSync, readdirSync, realpathSync } from "node:fs";
4
4
  import { newestSentAt, } from "../../core/mailbox-store.js";
5
5
  import { AppendLog, atomicWriteFile, canonicalStoreKey, ensureDir, readJsonlRecords, sanitizeScope, sanitizePathComponent } from "./fs-atomic.js";
6
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
6
7
  function realpathSyncSafe(p) {
7
8
  try {
8
9
  return realpathSync(p);
@@ -42,6 +43,7 @@ export class FileMailboxStore {
42
43
  touched = new Map();
43
44
  discloseCorrupt;
44
45
  constructor(root, opts = {}) {
46
+ assertAdoptionBootGate(root, "FileMailboxStore");
45
47
  this.fsyncEnabled = opts.fsync !== false;
46
48
  this.compactEvery = opts.compactEvery ?? 500;
47
49
  const sink = opts.onCorruptRead;
@@ -3,6 +3,7 @@ import { uuidv7 } from "../../internal/harness.js";
3
3
  import { firstSentence, lexicalSearchMatch, parseNoteTimestamp, } from "../../core/memory.js";
4
4
  import { cosineDistance, jaccardDistance, termSet } from "../../core/memory-vector.js";
5
5
  import { canonicalStoreKey, AppendLog, atomicWriteFile, ensureDir, readJsonlRecords, sanitizeScope, } from "./fs-atomic.js";
6
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
6
7
  const MAX_OPEN_MEMORY_SCOPES = 64;
7
8
  const sharedMemoryDirs = new Map();
8
9
  export class FileMemoryStore {
@@ -18,6 +19,7 @@ export class FileMemoryStore {
18
19
  scopeKey;
19
20
  closed = false;
20
21
  constructor(root, opts = {}) {
22
+ assertAdoptionBootGate(root, "FileMemoryStore");
21
23
  this.root = join(root, "memory");
22
24
  this.scopeKey = canonicalStoreKey(this.root);
23
25
  const live = sharedMemoryDirs.get(this.scopeKey);
@@ -2,10 +2,12 @@ import { readFileSync, readdirSync, unlinkSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { loosenReasons, normalizeRules, stripRev, SessionPolicyError, } from "../../core/session-policy-store.js";
4
4
  import { atomicWriteFile, ensureDir, sanitizeScope } from "./fs-atomic.js";
5
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
5
6
  export class FileSessionPolicyStore {
6
7
  dir;
7
8
  onCorruptRead;
8
9
  constructor(root, opts) {
10
+ assertAdoptionBootGate(root, "FileSessionPolicyStore");
9
11
  this.dir = join(root, "session-policy");
10
12
  ensureDir(this.dir);
11
13
  this.onCorruptRead = opts?.onCorruptRead;
@@ -2,6 +2,7 @@ import { existsSync, readdirSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { BaseSessionStorage, StoredSession, SessionError, getEntriesToFork, uuidv7, validateEntriesForImport, } from "../../internal/harness.js";
4
4
  import { canonicalStoreKey, AppendLog, atomicWriteFile, ensureDir, readJsonlRecords, sanitizePathComponent } from "./fs-atomic.js";
5
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
5
6
  const SUFFIX = ".jsonl";
6
7
  const sharedSessionStorages = new Map();
7
8
  const sessionStorageFinalizer = new FinalizationRegistry(({ canonical, log }) => {
@@ -57,6 +58,7 @@ export class FileSessionRepo {
57
58
  onCorruptRead;
58
59
  joined = new Map();
59
60
  constructor(root, opts) {
61
+ assertAdoptionBootGate(root, "FileSessionRepo");
60
62
  this.dir = join(root, "sessions");
61
63
  this.tmpDir = join(root, "tmp");
62
64
  ensureDir(this.dir);
@@ -2,9 +2,11 @@ import { join, resolve } from "node:path";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { assertJsonMetadata, normalizeTaskShape } from "../../tools/task-list.js";
4
4
  import { canonicalStoreKey, atomicWriteFile, ensureDir } from "./fs-atomic.js";
5
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
5
6
  const dirLocks = new Map();
6
7
  export function createFileTaskListStore(root) {
7
8
  const dir = resolve(root);
9
+ assertAdoptionBootGate(dir, "createFileTaskListStore");
8
10
  const tmpDir = join(dir, "tmp");
9
11
  const path = join(dir, "task-list.json");
10
12
  ensureDir(dir);
@@ -3,9 +3,11 @@ import { readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { assertSafeToolResultRef } from "../../core/tool-result-store.js";
5
5
  import { ensureDir, sanitizePathComponent, writeThenLink } from "./fs-atomic.js";
6
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
6
7
  export class FileToolResultStore {
7
8
  dir;
8
9
  constructor(root) {
10
+ assertAdoptionBootGate(root, "FileToolResultStore");
9
11
  this.dir = join(root, "tool-results");
10
12
  ensureDir(this.dir);
11
13
  }
@@ -2,9 +2,11 @@ import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { chargeUsageRecord, readUsageRecord, EMPTY_USAGE_WINDOW_RECORD, } from "../../core/usage-window-store.js";
4
4
  import { atomicWriteFile, ensureDir, sanitizeScope } from "./fs-atomic.js";
5
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
5
6
  export class FileUsageWindowStore {
6
7
  dir;
7
8
  constructor(root) {
9
+ assertAdoptionBootGate(root, "FileUsageWindowStore");
8
10
  this.dir = join(root, "usage-windows");
9
11
  ensureDir(this.dir);
10
12
  }
@@ -4,6 +4,7 @@ import { join } from "node:path";
4
4
  import { callKeyOrdinal } from "../../core/workflow-journal-store.js";
5
5
  import { AppendLog } from "./fs-atomic.js";
6
6
  import { canonicalStoreKey, sanitizePathComponent, writeThenLink } from "./fs-atomic.js";
7
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
7
8
  import { oversizeJournalResult } from "../../core/workflow-journal-store.js";
8
9
  export { MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult } from "../../core/workflow-journal-store.js";
9
10
  export const RESUME_CLAIM_TTL_MS = 60 * 60 * 1000;
@@ -28,6 +29,7 @@ export class FileWorkflowJournalStore {
28
29
  }
29
30
  constructor(root, fsyncEnabled = true) {
30
31
  this.fsyncEnabled = fsyncEnabled;
32
+ assertAdoptionBootGate(root, "FileWorkflowJournalStore");
31
33
  this.dir = join(root, "workflow-journal");
32
34
  mkdirSync(this.dir, { recursive: true, mode: 0o700 });
33
35
  this.claimsDir = join(this.dir, "claims");
@@ -2,6 +2,7 @@ import { join } from "node:path";
2
2
  import { assertRetentionPolicy } from "../../core/retention-policy.js";
3
3
  import { WorkflowRunStoreError, isTerminalWorkflowStatus, nextWorkflowRunOnUpdate, queryWorkflowRuns, } from "../../core/workflow-run-store.js";
4
4
  import { SharedLedgerTable } from "./shared-ledger.js";
5
+ import { assertAdoptionBootGate } from "./adoption/marker.js";
5
6
  const runLedgers = new SharedLedgerTable({
6
7
  keyOf: (run) => run.id,
7
8
  apply: (runs, ev) => {
@@ -21,6 +22,7 @@ export class FileWorkflowRunStore {
21
22
  return this.ledger.rows;
22
23
  }
23
24
  constructor(root, opts = {}) {
25
+ assertAdoptionBootGate(root, "FileWorkflowRunStore");
24
26
  this.fsyncEnabled = opts.fsync !== false;
25
27
  this.compactEvery = opts.compactEvery ?? 1000;
26
28
  const dir = join(root, "workflow-runs");