@sema-agent/core 5.22.0 → 5.24.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.
- package/CHANGELOG.md +169 -1
- package/dist/agents/subagent.js +3 -2
- package/dist/core/checkpoint-store.d.ts +38 -3
- package/dist/core/checkpoint-store.js +2 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +69 -2
- package/dist/core/hooks.js +100 -15
- package/dist/core/memory-engine/engine.d.ts +28 -1
- package/dist/core/memory-engine/engine.js +62 -3
- package/dist/core/memory-engine/index.d.ts +1 -1
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/memory-engine/layout.d.ts +69 -3
- package/dist/core/memory-engine/layout.js +75 -6
- package/dist/core/permission-rule-consent.js +2 -1
- package/dist/core/permission-rule-org.d.ts +36 -2
- package/dist/core/permission-rule-org.js +23 -0
- package/dist/core/permission-rule-store.d.ts +25 -14
- package/dist/core/permission-rule-store.js +7 -2
- package/dist/core/permission-rule-sync.d.ts +8 -0
- package/dist/core/permission-rule-sync.js +35 -6
- package/dist/core/runner/prepare-task.d.ts +10 -2
- package/dist/core/runner/prepare-task.js +120 -11
- package/dist/core/runner/runtask.js +46 -1
- package/dist/core/runner/session-file-state-replay.js +3 -0
- package/dist/core/tool-policy.d.ts +37 -4
- package/dist/core/tool-policy.js +49 -19
- package/dist/core/tool-result-store.d.ts +17 -1
- package/dist/core/tool-result-store.js +79 -4
- package/dist/core/trace.d.ts +47 -0
- package/dist/core/types.d.ts +45 -5
- package/dist/core/wiring-manifest.d.ts +16 -1
- package/dist/core/wiring-manifest.js +7 -1
- package/dist/index.d.ts +18 -10
- package/dist/index.js +5 -3
- package/dist/orchestration/goal.d.ts +10 -0
- package/dist/orchestration/goal.js +6 -5
- package/dist/stores/file/adoption/adopt.d.ts +146 -0
- package/dist/stores/file/adoption/adopt.js +611 -0
- package/dist/stores/file/adoption/marker.d.ts +202 -0
- package/dist/stores/file/adoption/marker.js +205 -0
- package/dist/stores/file/background-agent-store.js +2 -0
- package/dist/stores/file/checkpoint-store.js +2 -0
- package/dist/stores/file/file-snapshot-store.js +2 -0
- package/dist/stores/file/index.d.ts +2 -0
- package/dist/stores/file/index.js +4 -0
- package/dist/stores/file/mailbox-store.js +2 -0
- package/dist/stores/file/memory-store.js +2 -0
- package/dist/stores/file/session-policy-store.d.ts +11 -1
- package/dist/stores/file/session-policy-store.js +9 -2
- package/dist/stores/file/session-store.js +2 -0
- package/dist/stores/file/task-list-store.js +2 -0
- package/dist/stores/file/tool-result-store.js +2 -0
- package/dist/stores/file/usage-window-store.js +2 -0
- package/dist/stores/file/workflow-journal-store.js +2 -0
- package/dist/stores/file/workflow-run-store.js +2 -0
- package/dist/tools/fs/bash-readonly-classifier.js +59 -10
- package/dist/tools/fs/fs-bash.js +7 -4
- package/dist/tools/monitor.js +3 -3
- package/package.json +3 -2
|
@@ -0,0 +1,202 @@
|
|
|
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. The WRITER emits exactly this list and the drift pin (§10.5 ① — the design-vs-code
|
|
157
|
+
* two-cell guard) lives in the TEST GRID, where a ruled growth of the set is a deliberate edit.
|
|
158
|
+
*
|
|
159
|
+
* #133: the terminal READ validator deliberately does NOT compare a stored report against this
|
|
160
|
+
* constant. A persisted report is a snapshot of the set AS RULED AT ADOPTION TIME; the set is
|
|
161
|
+
* allowed to grow by ruling (§10.5 ① names that road), so an exact-equality read check would mark
|
|
162
|
+
* every already-adopted root corrupt on the release after any growth (and again on rollback) — 13
|
|
163
|
+
* construction sites all refusing to serve. The read validator checks SHAPE and integrity
|
|
164
|
+
* (non-empty, well-formed entries, no duplicate assets — what an empty/mangled account rebuild
|
|
165
|
+
* actually needs), never version-crossed content equality.
|
|
166
|
+
*/
|
|
167
|
+
export declare const NOT_MIGRATED_BY_DESIGN: ReadonlyArray<{
|
|
168
|
+
asset: string;
|
|
169
|
+
ruling: string;
|
|
170
|
+
}>;
|
|
171
|
+
/** The five baseline obligation IDENTITIES (design/183 §6 minimum face). Since #133 the terminal READ
|
|
172
|
+
* validator deliberately does NOT compare a stored report against this table (the stored report is a
|
|
173
|
+
* snapshot of the set as ruled at adoption time; see the validator's own note) — the enforcement that
|
|
174
|
+
* the WRITER emits exactly this set lives in the test grid (test/file-adoption-root.test.ts pins the
|
|
175
|
+
* fresh report's identities against this constant), where a drift is a deliberate, reviewable edit. */
|
|
176
|
+
export declare const BASELINE_CONFIG_IDENTITIES: ReadonlyArray<{
|
|
177
|
+
deployment: string;
|
|
178
|
+
key: string;
|
|
179
|
+
}>;
|
|
180
|
+
/**
|
|
181
|
+
* Read the root adoption marker; `undefined` when none. A corrupt marker THROWS (typed): after
|
|
182
|
+
* phase 4 the marker is the resolution truth, so a guessed read could serve a half-migrated root or
|
|
183
|
+
* invent an adoption that never happened — the same fail-closed stance as the rule-domain marker.
|
|
184
|
+
*/
|
|
185
|
+
export declare function readRootAdoptionFile(root: string): RootAdoptionFile | undefined;
|
|
186
|
+
/** Atomically publish the root marker (write-temp → fsync → rename, same discipline as every store). */
|
|
187
|
+
export declare function writeRootAdoptionFile(root: string, content: RootAdoptionFile): void;
|
|
188
|
+
/**
|
|
189
|
+
* Invariant I6 (design/183 §3.1) — the adoption BOOT GATE, called by every file store constructor:
|
|
190
|
+
* an in-flight root marker means the root is mid-adoption (possibly after a crash), and serving a
|
|
191
|
+
* half-migrated root is refused LOUDLY. The refusal names the in-flight arc and the way forward
|
|
192
|
+
* (resume `adoptLocalDataRoot` to completion). A terminal record passes: a completed adoption is a
|
|
193
|
+
* normal, readable root. A corrupt marker throws (fail-closed, see {@link readRootAdoptionFile}).
|
|
194
|
+
*
|
|
195
|
+
* This gate is what turns "the engine must be stopped during adoption" from an operational assumption
|
|
196
|
+
* into a machine-checked invariant across crashes: the adoption's own locks die with its process, but
|
|
197
|
+
* the marker (and this gate) survive. Boundary, stated honestly: the gate fires at CONSTRUCTION time.
|
|
198
|
+
* A store instance constructed BEFORE the marker landed in another OS process is outside it — cross-
|
|
199
|
+
* process sharing of one data dir is the file family's documented UNSUPPORTED shape (task-list F-14,
|
|
200
|
+
* mailbox RB-249); adoption adds no new promise there.
|
|
201
|
+
*/
|
|
202
|
+
export declare function assertAdoptionBootGate(root: string, storeName: string): void;
|
|
@@ -0,0 +1,205 @@
|
|
|
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 && 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
|
+
const nmd = r.notMigratedByDesign;
|
|
128
|
+
const nmdAssets = new Set();
|
|
129
|
+
const closedSetOk = Array.isArray(nmd) &&
|
|
130
|
+
nmd.length > 0 &&
|
|
131
|
+
nmd.every((e) => {
|
|
132
|
+
if (e === null || typeof e !== "object")
|
|
133
|
+
return false;
|
|
134
|
+
const m = e;
|
|
135
|
+
if (typeof m.asset !== "string" || m.asset === "" || typeof m.ruling !== "string" || m.ruling === "")
|
|
136
|
+
return false;
|
|
137
|
+
if (nmdAssets.has(m.asset))
|
|
138
|
+
return false;
|
|
139
|
+
nmdAssets.add(m.asset);
|
|
140
|
+
return true;
|
|
141
|
+
});
|
|
142
|
+
const legsOk = Array.isArray(r.legs) &&
|
|
143
|
+
r.legs.every((l) => {
|
|
144
|
+
if (l === null || typeof l !== "object")
|
|
145
|
+
return false;
|
|
146
|
+
const e = l;
|
|
147
|
+
return (typeof e.store === "string" &&
|
|
148
|
+
typeof e.action === "string" &&
|
|
149
|
+
["bucket-rebind", "row-rewrite", "carried", "none", "reset"].includes(e.action) &&
|
|
150
|
+
okCount(e.rows) &&
|
|
151
|
+
okCount(e.quarantined));
|
|
152
|
+
});
|
|
153
|
+
const credsOk = r.credentials !== null &&
|
|
154
|
+
typeof r.credentials === "object" &&
|
|
155
|
+
r.credentials.issuedBy === "server" &&
|
|
156
|
+
typeof r.credentials.ref === "string" &&
|
|
157
|
+
r.credentials.ref !== "";
|
|
158
|
+
return (typeof r.adoptionId === "string" &&
|
|
159
|
+
r.toPrincipal === a.toPrincipal &&
|
|
160
|
+
r.atMs === a.atMs &&
|
|
161
|
+
Number.isSafeInteger(a.atMs) &&
|
|
162
|
+
a.atMs >= 0 &&
|
|
163
|
+
sameFrom &&
|
|
164
|
+
closedSetOk &&
|
|
165
|
+
configsOk &&
|
|
166
|
+
legsOk &&
|
|
167
|
+
credsOk);
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
export function readRootAdoptionFile(root) {
|
|
172
|
+
let raw;
|
|
173
|
+
try {
|
|
174
|
+
raw = readFileSync(join(root, ROOT_ADOPTION_FILE), "utf8");
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
if (err.code === "ENOENT")
|
|
178
|
+
return undefined;
|
|
179
|
+
throw new AdoptionError("adoption_marker_corrupt", `could not read the adoption marker in ${root}: ${err.message}`);
|
|
180
|
+
}
|
|
181
|
+
let parsed;
|
|
182
|
+
try {
|
|
183
|
+
parsed = JSON.parse(raw);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
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`);
|
|
187
|
+
}
|
|
188
|
+
if (!isRootAdoptionFileShape(parsed)) {
|
|
189
|
+
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`);
|
|
190
|
+
}
|
|
191
|
+
return parsed;
|
|
192
|
+
}
|
|
193
|
+
export function writeRootAdoptionFile(root, content) {
|
|
194
|
+
ensureDir(root);
|
|
195
|
+
atomicWriteFile(join(root, "tmp"), join(root, ROOT_ADOPTION_FILE), JSON.stringify(content, null, 2));
|
|
196
|
+
}
|
|
197
|
+
export function assertAdoptionBootGate(root, storeName) {
|
|
198
|
+
const f = readRootAdoptionFile(root);
|
|
199
|
+
if (f !== undefined && "marker" in f) {
|
|
200
|
+
const from = f.marker.from.kind === "principal" ? `principal "${f.marker.from.principal}"` : "the local owner";
|
|
201
|
+
throw new AdoptionError("adoption_in_flight", `${storeName}: an adoption of this data root (${from} → "${f.marker.toPrincipal}", phase ${f.marker.phase}) is in flight — ` +
|
|
202
|
+
`resume adoptLocalDataRoot to completion (or repair ${join(root, ROOT_ADOPTION_FILE)}) before constructing stores over it; ` +
|
|
203
|
+
`serving a half-migrated root is refused`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -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);
|
|
@@ -31,6 +31,15 @@ export interface FileSessionPolicyStoreOptions {
|
|
|
31
31
|
export declare class FileSessionPolicyStore implements SessionPolicyStore {
|
|
32
32
|
private readonly dir;
|
|
33
33
|
private readonly onCorruptRead;
|
|
34
|
+
/** #132 — the ADOPTED identity of a local-owner root's anonymous lane, or undefined on an
|
|
35
|
+
* unadopted (or principal-adopted) root. A local-owner adoption rebinds the anonymous
|
|
36
|
+
* `[sid,null]` estate rows to `toPrincipal`; until the deployment's principal wiring lands
|
|
37
|
+
* (`REQUIRE_PRINCIPAL` et al. — recorded `migrated:false` in the config account, an operator
|
|
38
|
+
* action), the runtime still queries anonymously, and ENOENT there would silently drop the very
|
|
39
|
+
* tighten-only deny rules the rebind moved. Adoption is a transfer of the whole anonymous
|
|
40
|
+
* identity, so on such a root the anonymous key IS the adopted principal — reads and writes
|
|
41
|
+
* alike (a window-era anonymous write must not mint a fresh orphan row). */
|
|
42
|
+
private readonly anonymousAlias;
|
|
34
43
|
constructor(root: string, opts?: FileSessionPolicyStoreOptions);
|
|
35
44
|
/** The one delivery point for {@link onCorruptRead}. Swallow-guarded here so no caller has to remember. */
|
|
36
45
|
private disclose;
|
|
@@ -41,7 +50,8 @@ export declare class FileSessionPolicyStore implements SessionPolicyStore {
|
|
|
41
50
|
* (swallow-guarded; never on a plain ENOENT, which really is absence). */
|
|
42
51
|
private discloseCorrupt;
|
|
43
52
|
/** `(sessionId, principal)` → a safe, INJECTIVE filename (sanitizeScope appends the full sha256 of the raw
|
|
44
|
-
* composite key, so distinct keys never collide on disk).
|
|
53
|
+
* composite key, so distinct keys never collide on disk). On a local-owner-adopted root the
|
|
54
|
+
* anonymous lane resolves to the adopted principal's key (#132 — see {@link anonymousAlias}). */
|
|
45
55
|
private pathFor;
|
|
46
56
|
private read;
|
|
47
57
|
getRules(sessionId: string, principal?: string): Promise<StoredSessionRules | null>;
|
|
@@ -2,13 +2,19 @@ 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, readRootAdoptionFile } from "./adoption/marker.js";
|
|
5
6
|
export class FileSessionPolicyStore {
|
|
6
7
|
dir;
|
|
7
8
|
onCorruptRead;
|
|
9
|
+
anonymousAlias;
|
|
8
10
|
constructor(root, opts) {
|
|
11
|
+
assertAdoptionBootGate(root, "FileSessionPolicyStore");
|
|
9
12
|
this.dir = join(root, "session-policy");
|
|
10
13
|
ensureDir(this.dir);
|
|
11
14
|
this.onCorruptRead = opts?.onCorruptRead;
|
|
15
|
+
const adoption = readRootAdoptionFile(root);
|
|
16
|
+
this.anonymousAlias =
|
|
17
|
+
adoption !== undefined && "adopted" in adoption && adoption.adopted.from.kind === "local-owner" ? adoption.adopted.toPrincipal : undefined;
|
|
12
18
|
}
|
|
13
19
|
disclose(info) {
|
|
14
20
|
try {
|
|
@@ -21,7 +27,8 @@ export class FileSessionPolicyStore {
|
|
|
21
27
|
this.disclose({ sessionId, ...(principal !== undefined ? { principal } : {}), path: this.pathFor(sessionId, principal), reason });
|
|
22
28
|
}
|
|
23
29
|
pathFor(sessionId, principal) {
|
|
24
|
-
const
|
|
30
|
+
const effective = principal ?? this.anonymousAlias;
|
|
31
|
+
const composite = JSON.stringify([sessionId, effective ?? null]);
|
|
25
32
|
return join(this.dir, `${sanitizeScope(composite)}.json`);
|
|
26
33
|
}
|
|
27
34
|
read(sessionId, principal) {
|
|
@@ -76,7 +83,7 @@ export class FileSessionPolicyStore {
|
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
85
|
const next = { ...clean, rev: (prior?.rev ?? 0) + 1 };
|
|
79
|
-
atomicWriteFile(join(this.dir, "tmp"), this.pathFor(sessionId, principal), JSON.stringify({ ...next, __sid: sessionId, __principal: principal ?? null }));
|
|
86
|
+
atomicWriteFile(join(this.dir, "tmp"), this.pathFor(sessionId, principal), JSON.stringify({ ...next, __sid: sessionId, __principal: (principal ?? this.anonymousAlias) ?? null }));
|
|
80
87
|
return next;
|
|
81
88
|
}
|
|
82
89
|
async listBySession(sessionId) {
|
|
@@ -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
|
}
|