@sema-agent/core 7.4.0 → 7.5.1
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 +69 -0
- package/dist/agents/cascade.d.ts +2 -1
- package/dist/agents/peer-notices.d.ts +11 -1
- package/dist/agents/peer-session-drain.js +2 -0
- package/dist/agents/verify.d.ts +2 -1
- package/dist/core/ask-origin.d.ts +130 -0
- package/dist/core/ask-origin.js +35 -0
- package/dist/core/auto-mode-arming.d.ts +40 -1
- package/dist/core/auto-mode-arming.js +51 -3
- package/dist/core/auto-mode.d.ts +87 -10
- package/dist/core/auto-mode.js +34 -6
- package/dist/core/checkpoint-store.d.ts +41 -6
- package/dist/core/checkpoint-store.js +8 -0
- package/dist/core/hooks.d.ts +63 -19
- package/dist/core/hooks.js +37 -16
- package/dist/core/mcp.d.ts +47 -55
- package/dist/core/mcp.js +89 -31
- package/dist/core/park-selfcheck.js +3 -0
- package/dist/core/permission-rule-consent.d.ts +2 -11
- package/dist/core/permission-rule-consent.js +13 -62
- package/dist/core/permission-rule-org.d.ts +70 -54
- package/dist/core/permission-rule-org.js +47 -61
- package/dist/core/permission-rule-provider.d.ts +162 -0
- package/dist/core/permission-rule-provider.js +148 -0
- package/dist/core/permission-rule-session.d.ts +23 -19
- package/dist/core/permission-rule-session.js +5 -5
- package/dist/core/permission-rule-store.d.ts +46 -21
- package/dist/core/permission-rule-store.js +13 -6
- package/dist/core/permission-rule-sync.d.ts +2 -1
- package/dist/core/permission-rule-sync.js +11 -0
- package/dist/core/runner/assemble-result.d.ts +3 -2
- package/dist/core/runner/checkpoint-scope.d.ts +32 -0
- package/dist/core/runner/checkpoint-scope.js +4 -0
- package/dist/core/runner/contracts.d.ts +1878 -0
- package/dist/core/runner/contracts.js +1 -0
- package/dist/core/runner/denial-limit-arms.d.ts +57 -31
- package/dist/core/runner/denial-limit-arms.js +42 -17
- package/dist/core/runner/derived-route-fallback.d.ts +34 -0
- package/dist/core/runner/derived-route-fallback.js +16 -0
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +1 -1
- package/dist/core/runner/prepare-announce-once.d.ts +83 -0
- package/dist/core/runner/prepare-announce-once.js +105 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
- package/dist/core/runner/prepare-config-doors.d.ts +2 -10
- package/dist/core/runner/prepare-defer-classify.d.ts +86 -0
- package/dist/core/runner/prepare-defer-classify.js +107 -0
- package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
- package/dist/core/runner/prepare-delegation-surface.js +144 -0
- package/dist/core/runner/prepare-execution-env.d.ts +54 -0
- package/dist/core/runner/prepare-execution-env.js +86 -0
- package/dist/core/runner/prepare-file-history.d.ts +95 -0
- package/dist/core/runner/prepare-file-history.js +383 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +6 -8
- package/dist/core/runner/prepare-hands-readface.js +3 -3
- package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
- package/dist/core/runner/prepare-inherited-gate.js +266 -0
- package/dist/core/runner/prepare-listings.d.ts +77 -0
- package/dist/core/runner/prepare-listings.js +76 -0
- package/dist/core/runner/prepare-lsp.d.ts +55 -0
- package/dist/core/runner/prepare-lsp.js +27 -0
- package/dist/core/runner/prepare-memory.d.ts +1 -1
- package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
- package/dist/core/runner/prepare-offload-wrappers.js +45 -0
- package/dist/core/runner/prepare-permission-rules.d.ts +132 -0
- package/dist/core/runner/prepare-permission-rules.js +140 -0
- package/dist/core/runner/prepare-project-context.d.ts +131 -0
- package/dist/core/runner/prepare-project-context.js +150 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +138 -0
- package/dist/core/runner/prepare-prompt-inputs.js +141 -0
- package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
- package/dist/core/runner/prepare-protocol-tools.js +182 -0
- package/dist/core/runner/prepare-question-face.d.ts +119 -0
- package/dist/core/runner/prepare-question-face.js +83 -0
- package/dist/core/runner/prepare-run-refs.d.ts +89 -0
- package/dist/core/runner/prepare-run-refs.js +39 -0
- package/dist/core/runner/prepare-safety-scan.d.ts +3 -2
- package/dist/core/runner/prepare-task.d.ts +11 -1815
- package/dist/core/runner/prepare-task.js +138 -2542
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
- package/dist/core/runner/prepare-wiring-manifest.d.ts +184 -0
- package/dist/core/runner/prepare-wiring-manifest.js +240 -0
- package/dist/core/runner/prepare-workspace-restore.d.ts +1 -27
- package/dist/core/runner/prepare-workspace-restore.js +1 -22
- package/dist/core/runner/rollback-stack.d.ts +32 -0
- package/dist/core/runner/rollback-stack.js +30 -0
- package/dist/core/runner/runtask.d.ts +11 -2
- package/dist/core/runner/runtask.js +27 -9
- package/dist/core/runner/workspace-path.d.ts +33 -0
- package/dist/core/runner/workspace-path.js +22 -0
- package/dist/core/sensitive-path-policy.d.ts +16 -0
- package/dist/core/sensitive-path-policy.js +1 -1
- package/dist/core/tool-policy.d.ts +57 -9
- package/dist/core/tool-policy.js +11 -0
- package/dist/core/types.d.ts +63 -51
- package/dist/core/wiring-manifest.d.ts +40 -3
- package/dist/core/wiring-manifest.js +4 -3
- package/dist/core/write-protect.d.ts +13 -2
- package/dist/core/write-protect.js +58 -29
- package/dist/engine/harness/types.d.ts +38 -16
- package/dist/engine/harness/types.js +25 -1
- package/dist/engine/session/session.d.ts +3 -11
- package/dist/index.d.ts +10 -6
- package/dist/index.js +9 -5
- package/dist/internal/harness.d.ts +1 -0
- package/dist/stores/file/adoption/adopt.d.ts +1 -1
- package/dist/stores/file/adoption/marker.d.ts +1 -1
- package/dist/stores/file/permission-rule-adopt.js +4 -3
- package/dist/stores/file/permission-rule-store.d.ts +65 -25
- package/dist/stores/file/permission-rule-store.js +215 -37
- package/dist/stores/file/task-list-store.d.ts +1 -1
- package/dist/tools/fs/read-face.d.ts +1 -1
- package/package.json +8 -2
- package/test/export-surface.snapshot.json +76 -28
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
import { closeSync, constants as FS, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { closeSync, constants as FS, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { createHash, randomBytes } from "node:crypto";
|
|
4
4
|
import { PERMISSION_RULE_WRITER, applySyncJoin, applyTombstones, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, foldDelta, } from "../../core/permission-rule-store.js";
|
|
5
5
|
import { canonicalize } from "../../core/canonical-json.js";
|
|
6
6
|
import { BootLock } from "./fs-atomic.js";
|
|
7
7
|
import { assertAdoptionBootGate } from "./adoption/marker.js";
|
|
8
|
+
function isRuleFile(v) {
|
|
9
|
+
const p = v;
|
|
10
|
+
return typeof p === "object" && p !== null && p.schemaVersion === 3 && Array.isArray(p.rules) && Array.isArray(p.tombstones) && typeof p.rev === "number" && (p.quarantined === undefined || Array.isArray(p.quarantined));
|
|
11
|
+
}
|
|
12
|
+
function isLegacyRuleFile(v) {
|
|
13
|
+
const p = v;
|
|
14
|
+
return typeof p === "object" && p !== null && (p.schemaVersion === 1 || p.schemaVersion === 2) && Array.isArray(p.rules) && Array.isArray(p.tombstones) && typeof p.rev === "number";
|
|
15
|
+
}
|
|
16
|
+
function isOrgStateFile(v) {
|
|
17
|
+
const p = v;
|
|
18
|
+
return typeof p === "object" && p !== null && p.schemaVersion === 1 && typeof p.state === "object" && p.state !== null;
|
|
19
|
+
}
|
|
20
|
+
function schemaVersionWord(v) {
|
|
21
|
+
return typeof v === "object" && v !== null ? v.schemaVersion : undefined;
|
|
22
|
+
}
|
|
23
|
+
function jsonForm(v) {
|
|
24
|
+
return JSON.parse(JSON.stringify(v));
|
|
25
|
+
}
|
|
26
|
+
export const ORG_STATE_FILE = "org.json";
|
|
27
|
+
export const SCHEMA3_MARKER_FILE = ".rule-schema-3";
|
|
28
|
+
export class OrgRevisionRefusal extends Error {
|
|
29
|
+
}
|
|
8
30
|
const LOCAL_OWNER_FILE = "local-owner.json";
|
|
9
31
|
const ADOPTION_FILE = "PERMISSION-RULES-ADOPTION.json";
|
|
10
32
|
export function readRuleAdoptionFile(dir) {
|
|
@@ -92,7 +114,7 @@ function assertSafeDir(dir) {
|
|
|
92
114
|
`refusing to use it — anyone who can write here can grant themselves permissions`);
|
|
93
115
|
}
|
|
94
116
|
}
|
|
95
|
-
class
|
|
117
|
+
class FileDurableRulePartition {
|
|
96
118
|
dir;
|
|
97
119
|
file;
|
|
98
120
|
acquireWriteLock;
|
|
@@ -134,12 +156,11 @@ class FilePermissionRuleStore {
|
|
|
134
156
|
this.disclose(why);
|
|
135
157
|
return { unreadable: why };
|
|
136
158
|
}
|
|
137
|
-
if ((parsed
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const why = `${this.file} does not carry a readable rule-file shape; refusing the whole file and loading zero rules`;
|
|
159
|
+
if (!isRuleFile(parsed)) {
|
|
160
|
+
const sv = schemaVersionWord(parsed);
|
|
161
|
+
const why = sv === 1 || sv === 2
|
|
162
|
+
? `${this.file} is a schema ${String(sv)} bucket that was not migrated to schema 3 (written by an older engine after this directory was migrated?); refusing the whole file and loading zero rules`
|
|
163
|
+
: `${this.file} does not carry a readable rule-file shape; refusing the whole file and loading zero rules`;
|
|
143
164
|
this.disclose(why);
|
|
144
165
|
return { unreadable: why };
|
|
145
166
|
}
|
|
@@ -175,6 +196,8 @@ class FilePermissionRuleStore {
|
|
|
175
196
|
}
|
|
176
197
|
async list() {
|
|
177
198
|
const r = this.read();
|
|
199
|
+
if ("unreadable" in r)
|
|
200
|
+
throw new Error(r.unreadable);
|
|
178
201
|
if (!("file" in r))
|
|
179
202
|
return { ...EMPTY_READ };
|
|
180
203
|
const screened = this.screenDurableRows(r.file);
|
|
@@ -189,34 +212,13 @@ class FilePermissionRuleStore {
|
|
|
189
212
|
const r = this.read();
|
|
190
213
|
return "file" in r ? this.screenDurableRows(r.file).quarantined : [];
|
|
191
214
|
}
|
|
192
|
-
async readOrgState() {
|
|
193
|
-
const r = this.read();
|
|
194
|
-
return "file" in r ? r.file.org : undefined;
|
|
195
|
-
}
|
|
196
|
-
async installOrgState(state) {
|
|
197
|
-
this.acquireWriteLock();
|
|
198
|
-
await this.serialize(() => {
|
|
199
|
-
const cur = this.current();
|
|
200
|
-
if (cur.org !== undefined && state.revisionHighWater < cur.org.revisionHighWater) {
|
|
201
|
-
throw new Error(`refusing to lower the org revision high-water mark (${cur.org.revisionHighWater} → ${state.revisionHighWater}) — an interleaved install must re-read and re-compare`);
|
|
202
|
-
}
|
|
203
|
-
if (cur.org !== undefined &&
|
|
204
|
-
state.snapshot.revision === cur.org.snapshot.revision &&
|
|
205
|
-
canonicalize(state.snapshot.rules) !== canonicalize(cur.org.snapshot.rules)) {
|
|
206
|
-
throw new Error(`refusing to replace org revision ${state.snapshot.revision} with DIFFERENT policy content under the same revision — content changes must bump the revision`);
|
|
207
|
-
}
|
|
208
|
-
if (!this.writeAndVerify({ ...cur, schemaVersion: 2, org: state })) {
|
|
209
|
-
throw new Error("the permission-rule store did not survive its own write; the org snapshot was not installed");
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
215
|
current() {
|
|
214
216
|
const r = this.read();
|
|
215
217
|
if ("unreadable" in r) {
|
|
216
218
|
throw new Error(`refusing to write the permission-rule store while it cannot be read: ${r.unreadable}`);
|
|
217
219
|
}
|
|
218
220
|
if ("absent" in r) {
|
|
219
|
-
return { schemaVersion:
|
|
221
|
+
return { schemaVersion: 3, actor: `file-${randomBytes(6).toString("hex")}`, counter: 0, rev: 0, rules: [], tombstones: [] };
|
|
220
222
|
}
|
|
221
223
|
return {
|
|
222
224
|
schemaVersion: r.file.schemaVersion,
|
|
@@ -226,7 +228,6 @@ class FilePermissionRuleStore {
|
|
|
226
228
|
rules: r.file.rules,
|
|
227
229
|
tombstones: r.file.tombstones,
|
|
228
230
|
...(r.file.sync !== undefined ? { sync: r.file.sync } : {}),
|
|
229
|
-
...(r.file.org !== undefined ? { org: r.file.org } : {}),
|
|
230
231
|
...(r.file.quarantined !== undefined ? { quarantined: r.file.quarantined } : {}),
|
|
231
232
|
};
|
|
232
233
|
}
|
|
@@ -284,7 +285,6 @@ class FilePermissionRuleStore {
|
|
|
284
285
|
}, delta, Date.now());
|
|
285
286
|
const landed = {
|
|
286
287
|
...cur,
|
|
287
|
-
schemaVersion: 2,
|
|
288
288
|
rev: cur.rev + 1,
|
|
289
289
|
rules: next.rules,
|
|
290
290
|
tombstones: next.tombstones,
|
|
@@ -309,15 +309,105 @@ class FilePermissionRuleStore {
|
|
|
309
309
|
}),
|
|
310
310
|
};
|
|
311
311
|
}
|
|
312
|
-
export class
|
|
312
|
+
export class FileDurableRulePartitionProvider {
|
|
313
313
|
dir;
|
|
314
314
|
onError;
|
|
315
315
|
lock;
|
|
316
|
+
orgPersistence;
|
|
316
317
|
constructor(dir, onError, opts) {
|
|
317
318
|
this.dir = dir;
|
|
318
319
|
this.onError = onError;
|
|
319
320
|
if (opts?.dataRoot !== undefined)
|
|
320
|
-
assertAdoptionBootGate(opts.dataRoot, "
|
|
321
|
+
assertAdoptionBootGate(opts.dataRoot, "FileDurableRulePartitionProvider");
|
|
322
|
+
this.migrateLegacyBuckets();
|
|
323
|
+
}
|
|
324
|
+
migrateLegacyBuckets() {
|
|
325
|
+
if (!existsSync(this.dir))
|
|
326
|
+
return;
|
|
327
|
+
if (existsSync(join(this.dir, SCHEMA3_MARKER_FILE)))
|
|
328
|
+
return;
|
|
329
|
+
try {
|
|
330
|
+
assertSafeDir(this.dir);
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
this.onError?.(`legacy scan skipped: ${err.message}`);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const held = this.lock !== undefined;
|
|
337
|
+
try {
|
|
338
|
+
this.acquireWriteLock();
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
this.onError?.(`rule-file schema migration deferred (the directory will be scanned again by the next construction that can take its writer lock): ${err.message}`);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
let complete = true;
|
|
345
|
+
try {
|
|
346
|
+
let names;
|
|
347
|
+
try {
|
|
348
|
+
names = readdirSync(this.dir);
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
this.onError?.(`rule-file schema migration deferred (the directory could not be listed): ${err.message}`);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const candidates = names.filter((name) => name === LOCAL_OWNER_FILE || /^[0-9a-f]{64}\.json$/.test(name)).sort();
|
|
355
|
+
const legacy = [];
|
|
356
|
+
for (const name of candidates) {
|
|
357
|
+
const read = readLegacyBucket(join(this.dir, name));
|
|
358
|
+
if (read !== undefined)
|
|
359
|
+
legacy.push({ name, file: read });
|
|
360
|
+
}
|
|
361
|
+
if (legacy.length === 0) {
|
|
362
|
+
this.writeSchema3Marker();
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const orgHome = this.fileOrgPersistence();
|
|
366
|
+
for (const { name, file } of legacy) {
|
|
367
|
+
const { org, schemaVersion: _legacyVersion, checksum: _legacyChecksum, ...rest } = file;
|
|
368
|
+
if (org !== undefined) {
|
|
369
|
+
try {
|
|
370
|
+
orgHome.installOrgStateSync(org);
|
|
371
|
+
}
|
|
372
|
+
catch (err) {
|
|
373
|
+
if (err instanceof OrgRevisionRefusal) {
|
|
374
|
+
this.onError?.(`the org block of ${name} was not folded into ${ORG_STATE_FILE}: ${err.message}`);
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
complete = false;
|
|
378
|
+
this.onError?.(`legacy bucket ${name} left untouched: its org block could not be installed into ${ORG_STATE_FILE} (${err.message}); the migration will retry on the next construction`);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const next = { ...rest, schemaVersion: 3 };
|
|
384
|
+
atomicPublish(this.dir, join(this.dir, name), JSON.stringify({ ...next, checksum: checksumOf(next) }, null, 2));
|
|
385
|
+
this.onError?.(`migrated ${name} to rule-file schema 3${org !== undefined ? ` (its org block moved to ${ORG_STATE_FILE})` : ""}`);
|
|
386
|
+
}
|
|
387
|
+
if (complete)
|
|
388
|
+
this.writeSchema3Marker();
|
|
389
|
+
}
|
|
390
|
+
finally {
|
|
391
|
+
if (!held) {
|
|
392
|
+
this.lock?.release();
|
|
393
|
+
this.lock = undefined;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
writeSchema3Marker() {
|
|
398
|
+
try {
|
|
399
|
+
atomicPublish(this.dir, join(this.dir, SCHEMA3_MARKER_FILE), JSON.stringify({ schemaVersion: 3 }));
|
|
400
|
+
}
|
|
401
|
+
catch (err) {
|
|
402
|
+
this.onError?.(`could not write ${SCHEMA3_MARKER_FILE}: ${err.message}; the legacy scan will run again next construction`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
orgStatePersistence() {
|
|
406
|
+
return this.fileOrgPersistence();
|
|
407
|
+
}
|
|
408
|
+
fileOrgPersistence() {
|
|
409
|
+
this.orgPersistence ??= new FileOrgRuleStatePersistence(this.dir, this.acquireWriteLock, this.onError);
|
|
410
|
+
return this.orgPersistence;
|
|
321
411
|
}
|
|
322
412
|
acquireWriteLock = () => {
|
|
323
413
|
if (this.lock !== undefined)
|
|
@@ -338,11 +428,11 @@ export class FilePermissionRuleStoreProvider {
|
|
|
338
428
|
return this.readOnlySourceView(principalRuleFileName(principal), principalRuleFileName(rec.toPrincipal), "adopted" in adoption || adoption.marker.phase >= 4);
|
|
339
429
|
}
|
|
340
430
|
}
|
|
341
|
-
return new
|
|
431
|
+
return new FileDurableRulePartition(this.dir, join(this.dir, principalRuleFileName(principal)), this.acquireWriteLock, this.onError);
|
|
342
432
|
}
|
|
343
433
|
readOnlySourceView(sourceName, targetName, flipped) {
|
|
344
434
|
const pick = flipped ? targetName : existsSync(join(this.dir, sourceName)) ? sourceName : targetName;
|
|
345
|
-
return readOnlyRuleStoreView(new
|
|
435
|
+
return readOnlyRuleStoreView(new FileDurableRulePartition(this.dir, join(this.dir, pick), this.acquireWriteLock, this.onError));
|
|
346
436
|
}
|
|
347
437
|
forLocalOwner() {
|
|
348
438
|
const adoption = readRuleAdoptionFile(this.dir);
|
|
@@ -352,13 +442,101 @@ export class FilePermissionRuleStoreProvider {
|
|
|
352
442
|
return this.readOnlySourceView(LOCAL_OWNER_FILE, principalRuleFileName(rec.toPrincipal), "adopted" in adoption || adoption.marker.phase >= 4);
|
|
353
443
|
}
|
|
354
444
|
}
|
|
355
|
-
return new
|
|
445
|
+
return new FileDurableRulePartition(this.dir, join(this.dir, LOCAL_OWNER_FILE), this.acquireWriteLock, this.onError);
|
|
356
446
|
}
|
|
357
447
|
dispose() {
|
|
358
448
|
this.lock?.release();
|
|
359
449
|
this.lock = undefined;
|
|
360
450
|
}
|
|
361
451
|
}
|
|
452
|
+
function readLegacyBucket(file) {
|
|
453
|
+
let raw;
|
|
454
|
+
try {
|
|
455
|
+
const st = lstatSync(file);
|
|
456
|
+
if (st.isSymbolicLink() || !st.isFile())
|
|
457
|
+
return undefined;
|
|
458
|
+
raw = readFileSync(file, "utf8");
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
let parsed;
|
|
464
|
+
try {
|
|
465
|
+
parsed = JSON.parse(raw);
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
if (!isLegacyRuleFile(parsed))
|
|
471
|
+
return undefined;
|
|
472
|
+
const { checksum, ...body } = parsed;
|
|
473
|
+
return checksum === checksumOf(body) ? parsed : undefined;
|
|
474
|
+
}
|
|
475
|
+
class FileOrgRuleStatePersistence {
|
|
476
|
+
dir;
|
|
477
|
+
acquireWriteLock;
|
|
478
|
+
onError;
|
|
479
|
+
file;
|
|
480
|
+
writeChain = Promise.resolve();
|
|
481
|
+
constructor(dir, acquireWriteLock, onError) {
|
|
482
|
+
this.dir = dir;
|
|
483
|
+
this.acquireWriteLock = acquireWriteLock;
|
|
484
|
+
this.onError = onError;
|
|
485
|
+
this.file = join(dir, ORG_STATE_FILE);
|
|
486
|
+
}
|
|
487
|
+
readSync() {
|
|
488
|
+
let raw;
|
|
489
|
+
try {
|
|
490
|
+
assertSafeDir(this.dir);
|
|
491
|
+
const st = lstatSync(this.file);
|
|
492
|
+
if (st.isSymbolicLink() || !st.isFile())
|
|
493
|
+
throw new Error(`${this.file} is a symlink or not a regular file; refusing to load the org state from it`);
|
|
494
|
+
raw = readFileSync(this.file, "utf8");
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
if (err?.code === "ENOENT")
|
|
498
|
+
return undefined;
|
|
499
|
+
throw err;
|
|
500
|
+
}
|
|
501
|
+
const parsed = JSON.parse(raw);
|
|
502
|
+
if (!isOrgStateFile(parsed))
|
|
503
|
+
throw new Error(`${this.file} does not carry a readable org-state shape; refusing it whole`);
|
|
504
|
+
const { checksum, ...body } = parsed;
|
|
505
|
+
if (checksum !== checksumOf(body))
|
|
506
|
+
throw new Error(`${this.file} failed its integrity check; refusing it whole`);
|
|
507
|
+
return parsed.state;
|
|
508
|
+
}
|
|
509
|
+
async readOrgState() {
|
|
510
|
+
return this.readSync();
|
|
511
|
+
}
|
|
512
|
+
installOrgStateSync(state) {
|
|
513
|
+
this.acquireWriteLock();
|
|
514
|
+
assertSafeDir(this.dir);
|
|
515
|
+
state = jsonForm(state);
|
|
516
|
+
const cur = this.readSync();
|
|
517
|
+
if (cur !== undefined && state.revisionHighWater < cur.revisionHighWater) {
|
|
518
|
+
throw new OrgRevisionRefusal(`refusing to lower the org revision high-water mark (${cur.revisionHighWater} → ${state.revisionHighWater}) — an interleaved install must re-read and re-compare`);
|
|
519
|
+
}
|
|
520
|
+
if (cur !== undefined && state.snapshot.revision === cur.snapshot.revision && canonicalize(state.snapshot.rules) !== canonicalize(cur.snapshot.rules)) {
|
|
521
|
+
throw new OrgRevisionRefusal(`refusing to replace org revision ${state.snapshot.revision} with DIFFERENT policy content under the same revision — content changes must bump the revision`);
|
|
522
|
+
}
|
|
523
|
+
if (cur !== undefined && state.snapshot.revision === cur.snapshot.revision && state.snapshot.fetchedAtMs <= cur.snapshot.fetchedAtMs)
|
|
524
|
+
return;
|
|
525
|
+
const body = { schemaVersion: 1, state };
|
|
526
|
+
atomicPublish(this.dir, this.file, JSON.stringify({ ...body, checksum: checksumOf(body) }, null, 2));
|
|
527
|
+
const after = this.readSync();
|
|
528
|
+
if (after === undefined || canonicalize(after) !== canonicalize(state)) {
|
|
529
|
+
const why = "the org state file did not survive its own write; the org snapshot was not installed";
|
|
530
|
+
this.onError?.(why);
|
|
531
|
+
throw new Error(why);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async installOrgState(state) {
|
|
535
|
+
const run = this.writeChain.then(() => this.installOrgStateSync(state), () => this.installOrgStateSync(state));
|
|
536
|
+
this.writeChain = run.then(() => undefined, () => undefined);
|
|
537
|
+
await run;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
362
540
|
function readOnlyRuleStoreView(store) {
|
|
363
541
|
return {
|
|
364
542
|
list: () => store.list(),
|
|
@@ -4,7 +4,7 @@ export declare function createFileTaskListStore(root: string, opts?: {
|
|
|
4
4
|
* The DATA ROOT whose adoption marker gates construction (design/183 I6). Most of the file family
|
|
5
5
|
* takes the data root and derives `join(root, "<family>")` itself; this factory is one of the TWO
|
|
6
6
|
* faces that instead take a caller-chosen directory (the other being
|
|
7
|
-
* `
|
|
7
|
+
* `FileDurableRulePartitionProvider`, which takes a rule bucket) — here, the document's own
|
|
8
8
|
* directory. So when a deployment hands in a SUBDIRECTORY the I6 check would look for the marker
|
|
9
9
|
* inside that subdirectory, find nothing, and pass unconditionally: a gate that is always open
|
|
10
10
|
* (backlog #136⑦). Supply the data root here and the gate reads the anchor the root-taking faces
|
|
@@ -19,7 +19,7 @@ export interface ReadFaceInputs {
|
|
|
19
19
|
/** The verifier read-only mount (`handsReadOnly` / toolkit `readOnly`): its containment is a
|
|
20
20
|
* LOAD-BEARING wall (bash_readonly is genuinely confined by it) — never openable. */
|
|
21
21
|
readOnlyMount: boolean;
|
|
22
|
-
/** Org governance declared (RunnerDeps.
|
|
22
|
+
/** Org governance declared (`RunnerDeps.permissionRuleStore.partitions.org === true`): default roots; the task layer
|
|
23
23
|
* may not open (D-2 — tighten-only under governance; the deps layer, being the deployment's own
|
|
24
24
|
* declaration, still may). */
|
|
25
25
|
orgGoverned: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/core",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.1",
|
|
4
4
|
"description": "Stateless, task-oriented AI agent core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"prepare": "npm run build",
|
|
59
59
|
"prepublishOnly": "npm run build && npm run test",
|
|
60
60
|
"gate:blackbox": "node scripts/run-blackbox-gate.mjs",
|
|
61
|
+
"gate:repro": "node scripts/run-repro-gate.mjs",
|
|
61
62
|
"gate:ledger": "node scripts/verify-hardening-ledger.mjs",
|
|
62
63
|
"gate:compensation": "node scripts/verify-compensation-registry.mjs",
|
|
63
64
|
"gate:message-branching": "node scripts/verify-no-message-branching.mjs",
|
|
@@ -71,7 +72,12 @@
|
|
|
71
72
|
"gate:debts:rc": "node scripts/verify-debts.mjs --rc-gate",
|
|
72
73
|
"gate:error-surface": "node scripts/verify-error-surface.mjs",
|
|
73
74
|
"gate:criteria": "node scripts/criteria-lint.mjs",
|
|
74
|
-
"gate:reminder-literal": "node scripts/verify-reminder-literal.mjs"
|
|
75
|
+
"gate:reminder-literal": "node scripts/verify-reminder-literal.mjs",
|
|
76
|
+
"gate:wire": "node scripts/wire-changes.mjs",
|
|
77
|
+
"gate:layering": "node scripts/verify-layering.mjs",
|
|
78
|
+
"gate:phase-api": "node scripts/verify-phase-api.mjs",
|
|
79
|
+
"gate:agent-returns": "node scripts/verify-agent-returns.mjs",
|
|
80
|
+
"gate:all": "node scripts/gate-all.mjs"
|
|
75
81
|
},
|
|
76
82
|
"dependencies": {
|
|
77
83
|
"@modelcontextprotocol/sdk": "1.30.0",
|