@sema-agent/core 7.14.0 → 7.16.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 +59 -0
- package/dist/core/ask-origin.d.ts +2 -2
- package/dist/core/checkpoint-store.d.ts +14 -15
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +4 -3
- package/dist/core/memory-engine/delegation-settlement.d.ts +37 -12
- package/dist/core/memory-engine/delegation-settlement.js +128 -39
- package/dist/core/memory-engine/engine.d.ts +9 -8
- package/dist/core/memory-engine/engine.js +115 -120
- package/dist/core/memory-engine/file-backend.d.ts +17 -5
- package/dist/core/memory-engine/file-backend.js +78 -34
- package/dist/core/memory-engine/index.d.ts +2 -2
- package/dist/core/memory-engine/index.js +2 -2
- package/dist/core/memory-engine/layout.d.ts +152 -54
- package/dist/core/memory-engine/layout.js +441 -104
- package/dist/core/memory-engine/types.d.ts +8 -3
- package/dist/core/memory-engine/types.js +5 -0
- package/dist/core/runner/abort-race.d.ts +3 -3
- package/dist/core/runner/active-skill-scope.d.ts +2 -2
- package/dist/core/runner/assemble-result.d.ts +29 -29
- package/dist/core/runner/checkpoint-scope.d.ts +4 -4
- package/dist/core/runner/clock-and-limits.d.ts +3 -3
- package/dist/core/runner/compaction-call-options.d.ts +11 -11
- package/dist/core/runner/compaction-knobs.d.ts +4 -4
- package/dist/core/runner/compaction-seams.d.ts +5 -5
- package/dist/core/runner/contracts.d.ts +224 -226
- package/dist/core/runner/decide-continuation.d.ts +6 -6
- package/dist/core/runner/denial-limit-arms.d.ts +8 -8
- package/dist/core/runner/derived-route-fallback.d.ts +2 -2
- package/dist/core/runner/gate-exit.d.ts +6 -6
- package/dist/core/runner/git-status-frame.d.ts +7 -7
- package/dist/core/runner/inherited-ask-grants.d.ts +6 -6
- package/dist/core/runner/memory-capture-optout.d.ts +1 -1
- package/dist/core/runner/memory-consolidation-driver.d.ts +4 -4
- package/dist/core/runner/memory-consolidation.d.ts +6 -6
- package/dist/core/runner/park-commit.d.ts +6 -6
- package/dist/core/runner/permission-rule-lanes.d.ts +22 -20
- package/dist/core/runner/permission-rule-lanes.js +1 -1
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +4 -4
- package/dist/core/runner/prepare-ask-lane.d.ts +3 -3
- package/dist/core/runner/prepare-boundary-parks.d.ts +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -5
- package/dist/core/runner/prepare-config-doors.d.ts +21 -21
- package/dist/core/runner/prepare-context-lane.d.ts +6 -6
- package/dist/core/runner/prepare-defer-classify.d.ts +3 -3
- package/dist/core/runner/prepare-defer-classify.js +12 -10
- package/dist/core/runner/prepare-delegation-surface.d.ts +1 -1
- package/dist/core/runner/prepare-file-history.d.ts +2 -2
- package/dist/core/runner/prepare-gate-stations.d.ts +8 -9
- package/dist/core/runner/prepare-gate-stations.js +2 -2
- package/dist/core/runner/prepare-hands-readface.d.ts +41 -41
- package/dist/core/runner/prepare-hands-readface.js +27 -29
- package/dist/core/runner/prepare-inherited-gate.d.ts +6 -6
- package/dist/core/runner/prepare-listings.d.ts +3 -3
- package/dist/core/runner/prepare-lsp.d.ts +3 -3
- package/dist/core/runner/prepare-memory-engine-session.d.ts +5 -5
- package/dist/core/runner/prepare-memory.js +6 -7
- package/dist/core/runner/prepare-offload-wrappers.d.ts +2 -2
- package/dist/core/runner/prepare-park-ask.d.ts +11 -12
- package/dist/core/runner/prepare-park-ask.js +3 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +6 -6
- package/dist/core/runner/prepare-policy-chain.js +2 -2
- package/dist/core/runner/prepare-prompt-inputs.d.ts +5 -5
- package/dist/core/runner/prepare-protocol-tools.d.ts +1 -1
- package/dist/core/runner/prepare-question-face.d.ts +4 -4
- package/dist/core/runner/prepare-run-refs.d.ts +11 -11
- package/dist/core/runner/prepare-safety-scan.d.ts +1 -1
- package/dist/core/runner/prepare-suspend-saga.d.ts +4 -4
- package/dist/core/runner/prepare-task.d.ts +12 -12
- package/dist/core/runner/prepare-task.js +5 -7
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +2 -3
- package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -4
- package/dist/core/runner/prepare-workspace-restore.d.ts +1 -1
- package/dist/core/runner/remote-env-retry.d.ts +3 -3
- package/dist/core/runner/resume-admission.d.ts +1 -1
- package/dist/core/runner/resume-claim.d.ts +1 -1
- package/dist/core/runner/resume-internals-and-config.d.ts +1 -1
- package/dist/core/runner/resume-policy-outcome.d.ts +1 -1
- package/dist/core/runner/resume-preflight.d.ts +1 -1
- package/dist/core/runner/resume-review-outcome.d.ts +1 -1
- package/dist/core/runner/run-brain-sinks.d.ts +1 -1
- package/dist/core/runner/run-clock-and-content.d.ts +4 -4
- package/dist/core/runner/run-compaction-machinery.d.ts +3 -3
- package/dist/core/runner/run-git-lane.d.ts +1 -1
- package/dist/core/runner/run-harness-handlers.d.ts +2 -2
- package/dist/core/runner/run-identity-wiring.d.ts +4 -4
- package/dist/core/runner/run-leg.d.ts +3 -3
- package/dist/core/runner/run-notification-lane.d.ts +2 -2
- package/dist/core/runner/run-reasoning-seat.d.ts +2 -2
- package/dist/core/runner/run-settle-and-teardown.d.ts +4 -4
- package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
- package/dist/core/runner/run-terminal-adoption.d.ts +5 -5
- package/dist/core/runner/runtask.d.ts +14 -14
- package/dist/core/runner/session-file-state-replay.d.ts +3 -3
- package/dist/core/runner/session-rule-policy.d.ts +2 -2
- package/dist/core/runner/steer-admission.d.ts +2 -2
- package/dist/core/runner/stream-halt-verbs.d.ts +1 -1
- package/dist/core/runner/stream-reap.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.d.ts +8 -8
- package/dist/core/runner/teardown-bounded.d.ts +2 -2
- package/dist/core/runner/tool-defer-gate.d.ts +86 -0
- package/dist/core/runner/tool-defer-gate.js +57 -0
- package/dist/core/runner/tool-disclosure.d.ts +33 -69
- package/dist/core/runner/tool-disclosure.js +0 -43
- package/dist/core/runner/tool-end-body.d.ts +3 -3
- package/dist/core/runner/tool-face-overlay.d.ts +2 -2
- package/dist/core/runner/tool-output-projection.d.ts +2 -2
- package/dist/core/runner/turn-attachments.d.ts +53 -53
- package/dist/core/runner/usage-accounting.d.ts +4 -4
- package/dist/core/runner/workspace-path.d.ts +1 -1
- package/dist/core/runner-deps.d.ts +16 -9
- package/dist/core/task-spec.d.ts +8 -2
- package/dist/core/tool-spec.d.ts +7 -4
- package/dist/core/types.d.ts +1 -1
- package/dist/engine/llm/types.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/tools/fs/bash-readonly-classifier.d.ts +40 -40
- package/dist/tools/fs/encoding.d.ts +31 -40
- package/dist/tools/fs/fs-bash.d.ts +76 -35
- package/dist/tools/fs/fs-bash.js +86 -29
- package/dist/tools/fs/fs-pdf.d.ts +8 -8
- package/dist/tools/fs/fs-search-tools.d.ts +4 -4
- package/dist/tools/fs/fs-shared.d.ts +144 -158
- package/dist/tools/fs/fs-write.d.ts +8 -8
- package/dist/tools/fs/gh-rate-limit.d.ts +13 -14
- package/dist/tools/fs/index.d.ts +74 -78
- package/dist/tools/fs/notebook.d.ts +24 -25
- package/dist/tools/fs/pdf.d.ts +3 -3
- package/dist/tools/fs/read-deny.d.ts +22 -22
- package/dist/tools/fs/read-face.d.ts +21 -24
- package/dist/tools/fs/repo-map.d.ts +8 -8
- package/dist/tools/fs/safety.d.ts +116 -129
- package/dist/tools/fs/search.d.ts +63 -71
- package/package.json +5 -2
- package/test/export-surface.snapshot.json +9 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync, linkSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { QUARANTINE_DIR, appendChallengeEvents, enqueueMemoryAnnouncement, ensureDirExists, lineageContributionsOfSession, lockedStrictUpdate, markSessionPolluted, readSessionPollution, readStrictSidecar, ControlPlaneCorruptError } from "./layout.js";
|
|
4
|
+
import { QUARANTINE_DIR, appendChallengeEvents, enqueueMemoryAnnouncement, ensureDirExists, v1MemoryPathKeyResolver, lineageContributionsOfSession, lockedStrictUpdate, markSessionPolluted, readProjectionDebts, readSessionPollution, readStrictSidecar, ControlPlaneCorruptError } from "./layout.js";
|
|
5
5
|
export const DELEGATION_SETTLEMENT_FILE = "delegation-settlement.json";
|
|
6
6
|
function reqStr(v) {
|
|
7
7
|
return typeof v === "string" && v.length > 0;
|
|
@@ -233,36 +233,101 @@ export function replayExternalSettlementEffects(controlDir, input) {
|
|
|
233
233
|
return applied;
|
|
234
234
|
}
|
|
235
235
|
export const SESSION_ACCOUNTS_FILE = "session-accounts.json";
|
|
236
|
+
function malformedAccountRow(row) {
|
|
237
|
+
return (typeof row !== "object" ||
|
|
238
|
+
row === null ||
|
|
239
|
+
!reqStr(row.sessionId) ||
|
|
240
|
+
typeof row.openedAt !== "number" ||
|
|
241
|
+
!Number.isFinite(row.openedAt) ||
|
|
242
|
+
!Array.isArray(row.unattributed) ||
|
|
243
|
+
(row.closedAt !== undefined && (typeof row.closedAt !== "number" || !Number.isFinite(row.closedAt))) ||
|
|
244
|
+
(row.unadjudicated !== undefined && row.unadjudicated !== true));
|
|
245
|
+
}
|
|
246
|
+
function isUnattributedMember(x) {
|
|
247
|
+
return typeof x === "object" && x !== null && reqStr(x.scope) && safeRelPath(x.rel);
|
|
248
|
+
}
|
|
236
249
|
function coerceSessionAccounts(raw) {
|
|
237
250
|
if (raw === undefined)
|
|
238
|
-
return { version:
|
|
251
|
+
return { version: 2, rows: [] };
|
|
239
252
|
const rec = raw;
|
|
240
253
|
if (typeof rec !== "object" || rec === null || !Array.isArray(rec.rows))
|
|
241
254
|
throw new ControlPlaneCorruptError("memory session-account ledger has the wrong shape");
|
|
242
|
-
if (rec.version !== 1)
|
|
255
|
+
if (rec.version !== 1 && rec.version !== 2)
|
|
243
256
|
throw new ControlPlaneCorruptError(`memory session-account ledger has an unrecognized version (${String(rec.version)}) — refusing (fail-closed)`);
|
|
244
|
-
|
|
257
|
+
const bad = () => {
|
|
258
|
+
throw new ControlPlaneCorruptError("memory session-account ledger: a row is malformed — refusing (fail-closed; an unevaluable row must not read as adjudicated)");
|
|
259
|
+
};
|
|
260
|
+
const rows = rec.rows;
|
|
261
|
+
for (const r of rows) {
|
|
245
262
|
const row = r;
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
row.unattributed.some((x) => typeof x !== "string") ||
|
|
253
|
-
(row.closedAt !== undefined && (typeof row.closedAt !== "number" || !Number.isFinite(row.closedAt))) ||
|
|
254
|
-
(row.unadjudicated !== undefined && row.unadjudicated !== true)) {
|
|
255
|
-
throw new ControlPlaneCorruptError("memory session-account ledger: a row is malformed — refusing (fail-closed; an unevaluable row must not read as adjudicated)");
|
|
263
|
+
if (malformedAccountRow(row))
|
|
264
|
+
bad();
|
|
265
|
+
const members = Array.isArray(row.unattributed) ? row.unattributed : bad();
|
|
266
|
+
if (rec.version === 1) {
|
|
267
|
+
if (members.some((x) => typeof x !== "string"))
|
|
268
|
+
bad();
|
|
256
269
|
}
|
|
257
|
-
|
|
258
|
-
|
|
270
|
+
else {
|
|
271
|
+
if (!members.every(isUnattributedMember))
|
|
272
|
+
bad();
|
|
273
|
+
if (row.unattributable !== undefined && (!Array.isArray(row.unattributable) || row.unattributable.some((x) => typeof x !== "string")))
|
|
274
|
+
bad();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return rec.version === 1 ? { version: 1, rows: rows } : { version: 2, rows: rows };
|
|
278
|
+
}
|
|
279
|
+
function convertSessionAccounts(read, controlDir) {
|
|
280
|
+
if (read.version === 2)
|
|
281
|
+
return { rec: read, converted: false };
|
|
282
|
+
const resolve = v1MemoryPathKeyResolver(controlDir);
|
|
283
|
+
const rows = read.rows.map((r) => {
|
|
284
|
+
const unattributed = [];
|
|
285
|
+
const unattributable = [];
|
|
286
|
+
for (const relPath of r.unattributed) {
|
|
287
|
+
const key = resolve(relPath);
|
|
288
|
+
if (key === undefined)
|
|
289
|
+
unattributable.push(relPath);
|
|
290
|
+
else
|
|
291
|
+
unattributed.push(key);
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
sessionId: r.sessionId,
|
|
295
|
+
openedAt: r.openedAt,
|
|
296
|
+
unattributed,
|
|
297
|
+
...(unattributable.length > 0 ? { unattributable } : {}),
|
|
298
|
+
...(r.closedAt !== undefined ? { closedAt: r.closedAt } : {}),
|
|
299
|
+
...(r.unadjudicated !== undefined ? { unadjudicated: r.unadjudicated } : {}),
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
return { rec: { version: 2, rows }, converted: true };
|
|
303
|
+
}
|
|
304
|
+
export function convertV1SidecarKeys(controlDir) {
|
|
305
|
+
readProjectionDebts(controlDir);
|
|
306
|
+
readHolds(controlDir);
|
|
307
|
+
readSessionAccountsConverted(controlDir);
|
|
308
|
+
}
|
|
309
|
+
function readSessionAccountsConverted(controlDir) {
|
|
310
|
+
const read = coerceSessionAccounts(readStrictSidecar(controlDir, SESSION_ACCOUNTS_FILE, "memory session-account ledger"));
|
|
311
|
+
if (read.version === 2)
|
|
312
|
+
return read;
|
|
313
|
+
return lockedStrictUpdate(controlDir, SESSION_ACCOUNTS_FILE, "memory session-account ledger", coerceSessionAccounts, (again) => {
|
|
314
|
+
const { rec, converted } = convertSessionAccounts(again, controlDir);
|
|
315
|
+
return converted ? { next: rec, result: rec } : { result: rec };
|
|
316
|
+
});
|
|
259
317
|
}
|
|
260
318
|
export function foreignDanglingSessionAccounts(controlDir, selfSessionId) {
|
|
261
|
-
const rec =
|
|
319
|
+
const rec = readSessionAccountsConverted(controlDir);
|
|
262
320
|
return rec.rows.filter((r) => r.sessionId !== selfSessionId && (r.closedAt === undefined || r.unadjudicated === true));
|
|
263
321
|
}
|
|
322
|
+
export function emptyUnattributedSet() {
|
|
323
|
+
return { has: () => false, empty: true };
|
|
324
|
+
}
|
|
325
|
+
function memberKey(scope, rel) {
|
|
326
|
+
return JSON.stringify([scope, rel]);
|
|
327
|
+
}
|
|
264
328
|
export function openSessionAccount(controlDir, input) {
|
|
265
|
-
lockedStrictUpdate(controlDir, SESSION_ACCOUNTS_FILE, "memory session-account ledger", coerceSessionAccounts, (
|
|
329
|
+
lockedStrictUpdate(controlDir, SESSION_ACCOUNTS_FILE, "memory session-account ledger", coerceSessionAccounts, (read) => {
|
|
330
|
+
const { rec } = convertSessionAccounts(read, controlDir);
|
|
266
331
|
const row = rec.rows.find((r) => r.sessionId === input.sessionId);
|
|
267
332
|
if (row === undefined) {
|
|
268
333
|
rec.rows.push({ sessionId: input.sessionId, openedAt: input.now(), unattributed: [...input.unattributed] });
|
|
@@ -276,17 +341,25 @@ export function openSessionAccount(controlDir, input) {
|
|
|
276
341
|
row.openedAt = input.now();
|
|
277
342
|
delete row.closedAt;
|
|
278
343
|
delete row.unadjudicated;
|
|
279
|
-
|
|
344
|
+
const merged = new Map(row.unattributed.map((m) => [memberKey(m.scope, m.rel), m]));
|
|
345
|
+
for (const m of input.unattributed)
|
|
346
|
+
merged.set(memberKey(m.scope, m.rel), { scope: m.scope, rel: m.rel });
|
|
347
|
+
row.unattributed = [...merged.values()];
|
|
280
348
|
}
|
|
281
349
|
return { next: rec, result: undefined };
|
|
282
350
|
});
|
|
283
351
|
}
|
|
284
352
|
export function sessionUnattributedSet(controlDir, sessionId) {
|
|
285
|
-
const
|
|
286
|
-
|
|
353
|
+
const row = readSessionAccountsConverted(controlDir).rows.find((r) => r.sessionId === sessionId);
|
|
354
|
+
if (row === undefined)
|
|
355
|
+
return emptyUnattributedSet();
|
|
356
|
+
const all = (row.unattributable?.length ?? 0) > 0;
|
|
357
|
+
const keys = new Set(row.unattributed.map((m) => memberKey(m.scope, m.rel)));
|
|
358
|
+
return { has: (scope, rel) => all || keys.has(memberKey(scope, rel.split("\\").join("/"))), empty: !all && keys.size === 0 };
|
|
287
359
|
}
|
|
288
360
|
export function closeSessionAccount(controlDir, input) {
|
|
289
|
-
return lockedStrictUpdate(controlDir, SESSION_ACCOUNTS_FILE, "memory session-account ledger", coerceSessionAccounts, (
|
|
361
|
+
return lockedStrictUpdate(controlDir, SESSION_ACCOUNTS_FILE, "memory session-account ledger", coerceSessionAccounts, (read) => {
|
|
362
|
+
const { rec } = convertSessionAccounts(read, controlDir);
|
|
290
363
|
const row = rec.rows.find((r) => r.sessionId === input.sessionId);
|
|
291
364
|
if (row === undefined)
|
|
292
365
|
return { result: false };
|
|
@@ -303,7 +376,8 @@ export function resolveSessionAccountRecord(controlDir, input) {
|
|
|
303
376
|
e.code = "memory.session_account_resolve_unattributed";
|
|
304
377
|
throw e;
|
|
305
378
|
}
|
|
306
|
-
lockedStrictUpdate(controlDir, SESSION_ACCOUNTS_FILE, "memory session-account ledger", coerceSessionAccounts, (
|
|
379
|
+
lockedStrictUpdate(controlDir, SESSION_ACCOUNTS_FILE, "memory session-account ledger", coerceSessionAccounts, (read) => {
|
|
380
|
+
const { rec } = convertSessionAccounts(read, controlDir);
|
|
307
381
|
const row = rec.rows.find((r) => r.sessionId === input.sessionId);
|
|
308
382
|
if (row === undefined) {
|
|
309
383
|
const e = new Error(`resolveSessionAccount: unknown sessionId ${JSON.stringify(input.sessionId)}`);
|
|
@@ -321,21 +395,23 @@ const HOLD_STATUSES = new Set(["capturing", "held", "released", "disposed"]);
|
|
|
321
395
|
const HOLD_TERMINALS = new Set(["dirty", "expired", "conflict", "discarded", "capture_lost"]);
|
|
322
396
|
function coerceHolds(raw) {
|
|
323
397
|
if (raw === undefined)
|
|
324
|
-
return { version:
|
|
398
|
+
return { version: 2, rows: [] };
|
|
325
399
|
const rec = raw;
|
|
326
400
|
if (typeof rec !== "object" || rec === null || !Array.isArray(rec.rows))
|
|
327
401
|
throw new ControlPlaneCorruptError("memory instruction-hold ledger has the wrong shape");
|
|
328
|
-
if (rec.version !== 1)
|
|
402
|
+
if (rec.version !== 1 && rec.version !== 2)
|
|
329
403
|
throw new ControlPlaneCorruptError(`memory instruction-hold ledger has an unrecognized version (${String(rec.version)}) — refusing (fail-closed)`);
|
|
330
|
-
|
|
404
|
+
const v1 = rec.version === 1;
|
|
405
|
+
const rows = [];
|
|
406
|
+
const raws = rec.rows;
|
|
407
|
+
for (const r of raws) {
|
|
331
408
|
const row = r;
|
|
332
409
|
if (typeof row !== "object" ||
|
|
333
410
|
row === null ||
|
|
334
411
|
!reqStr(row.holdId) ||
|
|
335
412
|
!reqStr(row.sessionId) ||
|
|
336
413
|
(row.op !== "add" && row.op !== "update") ||
|
|
337
|
-
!
|
|
338
|
-
!reqStr(row.slug) ||
|
|
414
|
+
!safeRelPath(row.slug) ||
|
|
339
415
|
!reqStr(row.scope) ||
|
|
340
416
|
!reqStr(row.captureName) ||
|
|
341
417
|
!reqStr(row.contentDigest) ||
|
|
@@ -350,15 +426,28 @@ function coerceHolds(raw) {
|
|
|
350
426
|
(row.entryId !== undefined && typeof row.entryId !== "string") ||
|
|
351
427
|
(row.baseRev !== undefined && typeof row.baseRev !== "string") ||
|
|
352
428
|
(row.op === "update" && (!reqStr(row.entryId) || !reqStr(row.baseRev))) ||
|
|
353
|
-
!safeRelPath(row.relPath) ||
|
|
429
|
+
(v1 ? !safeRelPath(row.relPath) : !safeRelPath(row.rel)) ||
|
|
354
430
|
!safeBasename(row.captureName)) {
|
|
355
431
|
throw new ControlPlaneCorruptError("memory instruction-hold ledger: a row is malformed — refusing (fail-closed; an unevaluable hold must not read as settled)");
|
|
356
432
|
}
|
|
433
|
+
if (v1) {
|
|
434
|
+
const { relPath: _relPath, ...rest } = row;
|
|
435
|
+
rows.push({ ...rest, rel: `${row.slug}.md` });
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
rows.push(row);
|
|
439
|
+
}
|
|
357
440
|
}
|
|
358
|
-
return
|
|
441
|
+
return { version: 2, rows };
|
|
442
|
+
}
|
|
443
|
+
function holdsOnDiskAreV1(raw) {
|
|
444
|
+
return typeof raw === "object" && raw !== null && raw.version === 1;
|
|
359
445
|
}
|
|
360
446
|
export function readHolds(controlDir) {
|
|
361
|
-
|
|
447
|
+
const raw = readStrictSidecar(controlDir, HOLDS_FILE, "memory instruction-hold ledger");
|
|
448
|
+
if (!holdsOnDiskAreV1(raw))
|
|
449
|
+
return coerceHolds(raw).rows;
|
|
450
|
+
return lockedStrictUpdate(controlDir, HOLDS_FILE, "memory instruction-hold ledger", coerceHolds, (again) => ({ next: again, result: again.rows }));
|
|
362
451
|
}
|
|
363
452
|
function sha256(buf) {
|
|
364
453
|
return createHash("sha256").update(buf).digest("hex");
|
|
@@ -397,7 +486,7 @@ export function noReplaceRestore(stagingPath, destPath) {
|
|
|
397
486
|
return "restored";
|
|
398
487
|
}
|
|
399
488
|
export function openInstructionHold(controlDir, input) {
|
|
400
|
-
const holdId = `hold-${input.now()}-${createHash("sha256").update(`${input.sessionId}\n${input.
|
|
489
|
+
const holdId = `hold-${input.now()}-${createHash("sha256").update(`${input.sessionId}\n${input.rel}\n${input.content}`).digest("hex").slice(0, 12)}`;
|
|
401
490
|
const buf = Buffer.from(input.content, "utf8");
|
|
402
491
|
const digest = sha256(buf);
|
|
403
492
|
const captureName = `${holdId}.md`;
|
|
@@ -420,7 +509,7 @@ export function openInstructionHold(controlDir, input) {
|
|
|
420
509
|
...(input.entryId !== undefined ? { entryId: input.entryId } : {}),
|
|
421
510
|
op: input.op,
|
|
422
511
|
...(input.baseRev !== undefined ? { baseRev: input.baseRev } : {}),
|
|
423
|
-
|
|
512
|
+
rel: input.rel,
|
|
424
513
|
slug: input.slug,
|
|
425
514
|
scope: input.scope,
|
|
426
515
|
captureName,
|
|
@@ -490,12 +579,12 @@ export function openInstructionHold(controlDir, input) {
|
|
|
490
579
|
}
|
|
491
580
|
export function reconcileHolds(controlDir, input) {
|
|
492
581
|
const holdDir = join(controlDir, HOLD_DIR);
|
|
493
|
-
const discloseStranded = (holdId,
|
|
582
|
+
const discloseStranded = (holdId, rel, stagingName) => {
|
|
494
583
|
try {
|
|
495
584
|
enqueueMemoryAnnouncement(controlDir, {
|
|
496
585
|
kind: "gate",
|
|
497
586
|
at: input.now(),
|
|
498
|
-
items: [`instruction hold ${holdId}: a concurrent writer's bytes for ${JSON.stringify(
|
|
587
|
+
items: [`instruction hold ${holdId}: a concurrent writer's bytes for ${JSON.stringify(rel)} could not be restored onto the plane — they are preserved at hold/${stagingName} in the memory control plane for host recovery`],
|
|
499
588
|
});
|
|
500
589
|
}
|
|
501
590
|
catch {
|
|
@@ -512,7 +601,7 @@ export function reconcileHolds(controlDir, input) {
|
|
|
512
601
|
terminalLost = true;
|
|
513
602
|
}
|
|
514
603
|
else {
|
|
515
|
-
const plane = join(input.
|
|
604
|
+
const plane = join(input.homeOf(row.scope), row.rel);
|
|
516
605
|
if (existsSync(plane)) {
|
|
517
606
|
try {
|
|
518
607
|
const stagingName = uniqueStagingName(row.holdId, input.now);
|
|
@@ -522,7 +611,7 @@ export function reconcileHolds(controlDir, input) {
|
|
|
522
611
|
unlinkSync(staging);
|
|
523
612
|
}
|
|
524
613
|
else if (noReplaceRestore(staging, plane) === "stranded") {
|
|
525
|
-
discloseStranded(row.holdId, row.
|
|
614
|
+
discloseStranded(row.holdId, row.rel, stagingName);
|
|
526
615
|
}
|
|
527
616
|
}
|
|
528
617
|
catch {
|
|
@@ -531,7 +620,7 @@ export function reconcileHolds(controlDir, input) {
|
|
|
531
620
|
}
|
|
532
621
|
}
|
|
533
622
|
else {
|
|
534
|
-
const plane = join(input.
|
|
623
|
+
const plane = join(input.homeOf(row.scope), row.rel);
|
|
535
624
|
if (existsSync(plane)) {
|
|
536
625
|
const bytes = readFileSync(plane);
|
|
537
626
|
if (sha256(bytes) === row.contentDigest) {
|
|
@@ -549,7 +638,7 @@ export function reconcileHolds(controlDir, input) {
|
|
|
549
638
|
}
|
|
550
639
|
}
|
|
551
640
|
else if (noReplaceRestore(staging, plane) === "stranded") {
|
|
552
|
-
discloseStranded(row.holdId, row.
|
|
641
|
+
discloseStranded(row.holdId, row.rel, stagingName);
|
|
553
642
|
}
|
|
554
643
|
}
|
|
555
644
|
catch {
|
|
@@ -1186,6 +1186,10 @@ export declare class MemoryEngine {
|
|
|
1186
1186
|
* eventId anchors on the pollution record (sessionId + marked-at), so however many times this
|
|
1187
1187
|
* settlement replays the same pollution event, the generation is allocated once. */
|
|
1188
1188
|
private settlePromotions;
|
|
1189
|
+
/** The layout-adoption seat of a mount: a backend pinning its own directory root adopts under its
|
|
1190
|
+
* transaction lock; a copy-out projection tree and a backend without the face are adopted by the
|
|
1191
|
+
* engine directly (registry lock only — nothing else writes those trees during a mount). */
|
|
1192
|
+
private adoptLayoutBeforeMount;
|
|
1189
1193
|
materialize(scopes: readonly string[], writeScope: string | null, opts?: {
|
|
1190
1194
|
adoptionRestricted?: boolean;
|
|
1191
1195
|
sessionId?: string;
|
|
@@ -1242,6 +1246,9 @@ export declare class MemoryEngine {
|
|
|
1242
1246
|
* Throws only on a corrupt hold/settlement ledger — the caller's fail-closed reconciliation arm
|
|
1243
1247
|
* refuses the harvest (integrity unknown must not read as "no holds").
|
|
1244
1248
|
*/
|
|
1249
|
+
/** A hold's plane seat as a memory-dir-relative path (the report/warning spelling every other
|
|
1250
|
+
* harvest path uses), derived from the ledger-form row. */
|
|
1251
|
+
private holdPlaneRel;
|
|
1245
1252
|
private settleDueHolds;
|
|
1246
1253
|
/**
|
|
1247
1254
|
* The release leg: re-walk the captured bytes through the FULL gate set and commit them under
|
|
@@ -1663,8 +1670,6 @@ export declare class MemoryEngine {
|
|
|
1663
1670
|
* @returns the rejection to report, or `undefined` when the index is clean/absent.
|
|
1664
1671
|
*/
|
|
1665
1672
|
private gateDerivedIndex;
|
|
1666
|
-
/** Names of read-only sibling scope dirs directly under the writable root (excluded from scans; public since #691 — the runner refuses to admit a writable root that contains another scope's directory to the file tools' allowlist). */
|
|
1667
|
-
readonlyDirNamesUnderRoot(handle: MemorySessionHandle): Set<string>;
|
|
1668
1673
|
/** Projection write: reads the target to skip a no-op, then writes — the same check-then-use shape
|
|
1669
1674
|
* as the containment restores, so it takes the same no-follow write. A link at a projection path
|
|
1670
1675
|
* is never a supported layout (the harvest scan refuses symlinked entry files outright), and this
|
|
@@ -1699,12 +1704,8 @@ export declare class MemoryEngine {
|
|
|
1699
1704
|
* `getByIds` there would sync-adopt mid-harvest.
|
|
1700
1705
|
*/
|
|
1701
1706
|
private debtCommittedProjection;
|
|
1702
|
-
/**
|
|
1703
|
-
*
|
|
1704
|
-
private siblingScopeDirNames;
|
|
1705
|
-
/** chmod a scope's entry tree (best-effort; skipRoot leaves the top dir writable — used when a
|
|
1706
|
-
* read-only layer's dir IS the shared root / an ancestor of the writable root, which must stay
|
|
1707
|
-
* traversable+writable; excludeTopDirs skips sibling scope homes under a root-owning layer).
|
|
1707
|
+
/** chmod a scope's entry tree (best-effort). The tree is the scope's own home and nothing else —
|
|
1708
|
+
* no other scope's directory can lie under it.
|
|
1708
1709
|
*
|
|
1709
1710
|
* `opts.announced` is the CALLER's per-materialize ledger of paths already disclosed (#495 M-1).
|
|
1710
1711
|
* One materialize sweeps each inherited tree TWICE — restore write perms at the top, re-apply
|