@sema-agent/core 7.49.5 → 7.50.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 +157 -0
- package/dist/agents/observer.js +3 -10
- package/dist/agents/subagent-steps.d.ts +6 -14
- package/dist/agents/subagent-steps.js +3 -17
- package/dist/brain/stream-shared.d.ts +1 -1
- package/dist/core/allow-entry.d.ts +1 -1
- package/dist/core/allow-entry.js +1 -1
- package/dist/core/ask-origin.d.ts +15 -11
- package/dist/core/ask-origin.js +6 -14
- package/dist/core/auto-mode-prompt.js +6 -2
- package/dist/core/checkpoint-store.d.ts +6 -4
- package/dist/core/checkpoint-store.js +1 -1
- package/dist/core/context-edit.d.ts +2 -2
- package/dist/core/decision-receipt.d.ts +2 -2
- package/dist/core/decision-receipt.js +30 -11
- package/dist/core/delegation-frames.d.ts +66 -18
- package/dist/core/delegation-frames.js +22 -0
- package/dist/core/engine-notice.d.ts +7 -5
- package/dist/core/frame-settlement.d.ts +6 -1
- package/dist/core/gate-fold.js +34 -19
- package/dist/core/gate-lanes.js +15 -13
- package/dist/core/gate-outcome.d.ts +2 -2
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +28 -20
- package/dist/core/human-input-projection.js +2 -0
- package/dist/core/lsp-diagnostics.d.ts +3 -0
- package/dist/core/lsp-diagnostics.js +6 -0
- package/dist/core/lsp-session.d.ts +39 -17
- package/dist/core/lsp-session.js +118 -58
- package/dist/core/lsp.d.ts +83 -3
- package/dist/core/lsp.js +146 -16
- package/dist/core/mcp.js +2 -1
- package/dist/core/media-byte-cap.d.ts +14 -0
- package/dist/core/media-byte-cap.js +15 -3
- package/dist/core/memory-engine/consolidation-driver.d.ts +23 -1
- package/dist/core/memory-engine/consolidation-driver.js +54 -6
- package/dist/core/memory-engine/consolidation-write-gate.d.ts +46 -0
- package/dist/core/memory-engine/consolidation-write-gate.js +82 -0
- package/dist/core/memory-engine/consolidation.d.ts +17 -0
- package/dist/core/memory-engine/consolidation.js +1 -0
- package/dist/core/memory-engine/distiller.d.ts +8 -2
- package/dist/core/memory-engine/distiller.js +8 -6
- package/dist/core/memory-engine/engine.d.ts +37 -4
- package/dist/core/memory-engine/engine.js +127 -38
- package/dist/core/memory-engine/file-backend.d.ts +8 -3
- package/dist/core/memory-engine/file-backend.js +44 -16
- package/dist/core/memory-engine/index.d.ts +2 -2
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/memory-engine/landing-admission.d.ts +65 -0
- package/dist/core/memory-engine/landing-admission.js +68 -0
- package/dist/core/memory-engine/types.d.ts +23 -0
- package/dist/core/mode-policy.d.ts +3 -2
- package/dist/core/mode-policy.js +2 -0
- package/dist/core/permission-rule-model.d.ts +19 -13
- package/dist/core/permission-rule-model.js +7 -4
- package/dist/core/permission-rule-provider.d.ts +11 -0
- package/dist/core/permission-rules.d.ts +1 -1
- package/dist/core/permission-rules.js +5 -2
- package/dist/core/persisted-rule-arms.d.ts +15 -7
- package/dist/core/persisted-rule-arms.js +13 -6
- package/dist/core/reminder-mint.d.ts +5 -0
- package/dist/core/reminder-mint.js +1 -0
- package/dist/core/retired-keys.d.ts +8 -0
- package/dist/core/retired-keys.js +4 -0
- package/dist/core/rule-lane-reads.d.ts +4 -4
- package/dist/core/rule-lane-reads.js +10 -1
- package/dist/core/runner/construction-doors.d.ts +10 -4
- package/dist/core/runner/construction-doors.js +25 -2
- package/dist/core/runner/contracts.d.ts +20 -3
- package/dist/core/runner/env-capability-census.js +1 -1
- package/dist/core/runner/gate-exit.js +2 -1
- package/dist/core/runner/memory-consolidation-driver.d.ts +6 -4
- package/dist/core/runner/memory-consolidation-driver.js +5 -2
- package/dist/core/runner/memory-consolidation-write-judge.d.ts +17 -0
- package/dist/core/runner/memory-consolidation-write-judge.js +14 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +16 -22
- package/dist/core/runner/permission-rule-lanes.js +13 -43
- package/dist/core/runner/person-path-rows.d.ts +244 -0
- package/dist/core/runner/person-path-rows.js +387 -0
- package/dist/core/runner/policy-throw.d.ts +34 -0
- package/dist/core/runner/policy-throw.js +25 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +8 -1
- package/dist/core/runner/prepare-artifact.d.ts +5 -3
- package/dist/core/runner/prepare-artifact.js +2 -8
- package/dist/core/runner/prepare-ask-lane.d.ts +2 -1
- package/dist/core/runner/prepare-ask-lane.js +14 -16
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-context-lane.js +2 -2
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -3
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +19 -10
- package/dist/core/runner/prepare-hands-readface.js +57 -43
- package/dist/core/runner/prepare-lsp.d.ts +15 -13
- package/dist/core/runner/prepare-lsp.js +11 -11
- package/dist/core/runner/prepare-policy-chain.d.ts +8 -4
- package/dist/core/runner/prepare-policy-chain.js +11 -10
- package/dist/core/runner/prepare-run-refs.js +1 -1
- package/dist/core/runner/prepare-task.js +24 -16
- package/dist/core/runner/prepare-turn-wiring.d.ts +13 -6
- package/dist/core/runner/prepare-turn-wiring.js +19 -8
- package/dist/core/runner/read-root-grant.d.ts +1 -25
- package/dist/core/runner/read-root-grant.js +1 -17
- package/dist/core/runner/retired-runner-deps.d.ts +2 -2
- package/dist/core/runner/run-brain-sinks.d.ts +1 -5
- package/dist/core/runner/run-brain-sinks.js +2 -4
- package/dist/core/runner/run-harness-handlers.js +5 -4
- package/dist/core/runner/run-identity-wiring.d.ts +4 -4
- package/dist/core/runner/run-identity-wiring.js +10 -1
- package/dist/core/runner/run-leg.d.ts +1 -1
- package/dist/core/runner/run-leg.js +22 -9
- package/dist/core/runner/run-settle-and-teardown.d.ts +0 -4
- package/dist/core/runner/run-settle-and-teardown.js +7 -4
- package/dist/core/runner/run-terminal-adoption.d.ts +1 -1
- package/dist/core/runner/run-terminal-adoption.js +1 -1
- package/dist/core/runner/run-turn-boundary.js +2 -2
- package/dist/core/runner/runtask.js +8 -8
- package/dist/core/runner/session-file-state-replay.d.ts +14 -4
- package/dist/core/runner/session-file-state-replay.js +8 -0
- package/dist/core/runner/stream-steer-verb.js +3 -0
- package/dist/core/runner/tool-end-body.d.ts +5 -4
- package/dist/core/runner/tool-end-body.js +4 -4
- package/dist/core/runner/tool-output-projection.d.ts +6 -4
- package/dist/core/runner/tool-output-projection.js +66 -47
- package/dist/core/runner-deps.d.ts +6 -3
- package/dist/core/shared-memory/types.d.ts +2 -2
- package/dist/core/shell-read-silence.d.ts +23 -0
- package/dist/core/shell-read-silence.js +18 -0
- package/dist/core/synthetic-read-verdict.d.ts +80 -21
- package/dist/core/synthetic-read-verdict.js +89 -18
- package/dist/core/task-event.d.ts +113 -25
- package/dist/core/task-spec.d.ts +44 -0
- package/dist/core/task-tool-shape.d.ts +1 -1
- package/dist/core/tool-catalog-entries.js +16 -2
- package/dist/core/tool-policy.d.ts +25 -15
- package/dist/core/tool-policy.js +9 -13
- package/dist/core/trace.d.ts +3 -2
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +2 -2
- package/dist/core/user-attachment.d.ts +164 -0
- package/dist/core/user-attachment.js +437 -0
- package/dist/engine/compaction/compaction.js +10 -3
- package/dist/engine/compaction/utils.js +6 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -1
- package/dist/engine/execution-env/node-execution-env.js +125 -0
- package/dist/engine/harness/agent-harness.d.ts +12 -2
- package/dist/engine/harness/agent-harness.js +15 -3
- package/dist/engine/harness/types.d.ts +21 -0
- package/dist/engine/llm/content-block-render.d.ts +15 -0
- package/dist/engine/llm/content-block-render.js +10 -0
- package/dist/engine/llm/index.d.ts +1 -0
- package/dist/engine/llm/index.js +1 -0
- package/dist/engine/llm/types.d.ts +80 -4
- package/dist/engine/llm/types.js +43 -1
- package/dist/engine/session/import-validate.js +2 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +2 -1
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/internal/llm.d.ts +2 -2
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/workflow.d.ts +4 -5
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompts/default.d.ts +1 -1
- package/dist/tools/artifact/artifact-tool.js +1 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
- package/dist/tools/fs/dir-entries.d.ts +4 -10
- package/dist/tools/fs/dir-entries.js +4 -11
- package/dist/tools/fs/file-read-check.js +3 -3
- package/dist/tools/fs/fs-pdf.js +2 -4
- package/dist/tools/fs/fs-read.d.ts +47 -4
- package/dist/tools/fs/fs-read.js +63 -19
- package/dist/tools/fs/fs-search-tools.d.ts +3 -3
- package/dist/tools/fs/fs-search-tools.js +7 -2
- package/dist/tools/fs/fs-write.d.ts +1 -1
- package/dist/tools/fs/fs-write.js +7 -6
- package/dist/tools/fs/index.d.ts +7 -1
- package/dist/tools/fs/index.js +56 -34
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +32 -22
- package/dist/tools/fs/read-deny.d.ts +14 -0
- package/dist/tools/fs/read-deny.js +8 -0
- package/dist/tools/fs/repo-map.d.ts +3 -2
- package/dist/tools/fs/repo-map.js +11 -3
- package/dist/tools/fs/safety.d.ts +41 -2
- package/dist/tools/fs/safety.js +12 -3
- package/dist/tools/fs/shell-read-check.d.ts +12 -3
- package/dist/tools/fs/shell-read-check.js +44 -24
- package/dist/tools/fs/shell-read-device.d.ts +1 -1
- package/dist/tools/fs/shell-read-face.d.ts +15 -0
- package/dist/tools/fs/shell-read-face.js +5 -0
- package/dist/tools/fs/shell-read-judge.d.ts +9 -4
- package/dist/tools/fs/shell-read-judge.js +1 -1
- package/dist/tools/loop-tick.d.ts +2 -2
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +39 -1
- package/wire-changes/core@7.50.0.json +1699 -0
- package/wire-changes/core@7.50.1.json +65 -0
- package/dist/core/runner/persisted-path-rows.d.ts +0 -88
- package/dist/core/runner/persisted-path-rows.js +0 -108
|
@@ -11,6 +11,7 @@ import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, scanEntryFiles } from "
|
|
|
11
11
|
import { ControlPlaneCorruptError, CURSORS_FILE, QUARANTINE_DIR, CHALLENGES_FILE, IMPORT_RECEIPTS_DIR, LINEAGE_FILE, SESSION_POLLUTION_DIR, appendChallengeEvents, atomicWriteFileSync, quarantineAndTombstone, captureAndClearLineageForEntries, adoptRootScopeLayout, anchorStoreRoot, effectiveControlRootOf, ensureDirExists, LEDGER_FILE, enqueueMemoryAnnouncement, importLatchTxnId, importLineageCommitted, importSessionPollution, isContainedIn, permissionRemedyOf, withPermissionRemedy, lineageContributionsOfSession, listSessionPollution, readChallengeEvents, readLineageRecord, registerScope, registeredScopes, releaseImportLatch, layoutNeedsAdoption, scopeDirFor, scopeDirName, scopeRegistryShape, stageImportLatch, landsOutsideStore, storeActRefusal, storeRootMovedRefusal, writeAllSync, STORE_JOURNAL_FILE, STORE_TXN_LOCK_DIR, TXN_LOCK_STALE_MS, acquireStoreTxnLock, assertStoreTxnLockOwnership, tryStoreTxnLock, } from "./layout.js";
|
|
12
12
|
import { screenInboundEntries } from "./data-plane.js";
|
|
13
13
|
import { scanMemoryFileName, scanMemoryWrite } from "./scan.js";
|
|
14
|
+
import { admittingLandings, cutPerLanding, LandingsNeedAdmission, requireLandingsAdmitted } from "./landing-admission.js";
|
|
14
15
|
import { thrownText } from "../thrown-value.js";
|
|
15
16
|
export { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, scanEntryFiles } from "./entry-scan.js";
|
|
16
17
|
export function canonicalJsonStringify(value) {
|
|
@@ -526,6 +527,7 @@ export class FileMemoryEngineBackend {
|
|
|
526
527
|
unboundRowsKnown = false;
|
|
527
528
|
adoptionNoticeKeys = new Set();
|
|
528
529
|
transfersAppendFault;
|
|
530
|
+
landingFault;
|
|
529
531
|
recoveryCommitProbe;
|
|
530
532
|
chainHealProbe;
|
|
531
533
|
exportFaceProbe;
|
|
@@ -2111,17 +2113,20 @@ export class FileMemoryEngineBackend {
|
|
|
2111
2113
|
return scored.slice(0, limit);
|
|
2112
2114
|
}
|
|
2113
2115
|
async applyPatches(patches, opts) {
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2116
|
+
return admittingLandings(opts?.admit, async (admitted) => {
|
|
2117
|
+
const lock = await this.acquireTxnLock();
|
|
2118
|
+
try {
|
|
2119
|
+
return await this.applyPatchesLocked(patches, lock.token, opts?.anchoredRoot, admitted, opts?.confirm);
|
|
2120
|
+
}
|
|
2121
|
+
catch (err) {
|
|
2122
|
+
throw err instanceof LandingsNeedAdmission ? err : withPermissionRemedy(err, this.directoryRoot);
|
|
2123
|
+
}
|
|
2124
|
+
finally {
|
|
2125
|
+
lock.release();
|
|
2126
|
+
}
|
|
2127
|
+
});
|
|
2124
2128
|
}
|
|
2129
|
+
admitsLandings = true;
|
|
2125
2130
|
txnLockDir() {
|
|
2126
2131
|
return join(this.controlPlaneRoot, STORE_TXN_LOCK_DIR);
|
|
2127
2132
|
}
|
|
@@ -2131,7 +2136,7 @@ export class FileMemoryEngineBackend {
|
|
|
2131
2136
|
assertTxnLockOwnership(token) {
|
|
2132
2137
|
assertStoreTxnLockOwnership(this.controlPlaneRoot, token);
|
|
2133
2138
|
}
|
|
2134
|
-
async applyPatchesLocked(patches, lockToken, anchoredRoot) {
|
|
2139
|
+
async applyPatchesLocked(patches, lockToken, anchoredRoot, admitted, confirm) {
|
|
2135
2140
|
this.assertTxnLockOwnership(lockToken);
|
|
2136
2141
|
if (anchoredRoot !== undefined)
|
|
2137
2142
|
this.assertRootAnchored(anchoredRoot);
|
|
@@ -2146,9 +2151,12 @@ export class FileMemoryEngineBackend {
|
|
|
2146
2151
|
const plannedTargets = new Set();
|
|
2147
2152
|
const nonDeleteIds = new Set();
|
|
2148
2153
|
const plannedDeletes = new Map();
|
|
2154
|
+
const landings = [];
|
|
2155
|
+
const shares = [];
|
|
2149
2156
|
this.batchScan = new Map();
|
|
2150
2157
|
try {
|
|
2151
2158
|
for (const patch of patches) {
|
|
2159
|
+
const landingsBefore = landings.length;
|
|
2152
2160
|
try {
|
|
2153
2161
|
if (patch.op !== "delete") {
|
|
2154
2162
|
if (patch.entry !== undefined && patch.entry.id !== patch.id) {
|
|
@@ -2161,11 +2169,13 @@ export class FileMemoryEngineBackend {
|
|
|
2161
2169
|
}
|
|
2162
2170
|
nonDeleteIds.add(patch.id);
|
|
2163
2171
|
}
|
|
2164
|
-
this.planOne(patch, report, ops, rows, plannedTargets, plannedDeletes, transfers);
|
|
2172
|
+
this.planOne(patch, report, ops, rows, plannedTargets, plannedDeletes, transfers, landings);
|
|
2165
2173
|
}
|
|
2166
2174
|
catch (err) {
|
|
2167
2175
|
report.conflicts.push({ op: patch.op, id: patch.id, reason: `io error: ${thrownText(err)}` });
|
|
2168
2176
|
}
|
|
2177
|
+
if (admitted !== undefined)
|
|
2178
|
+
shares.push({ opsEnd: ops.length, evidenceEnd: transfers.length, ...(landings.length > landingsBefore ? { landed: cloneRows(rows) } : {}) });
|
|
2169
2179
|
}
|
|
2170
2180
|
}
|
|
2171
2181
|
finally {
|
|
@@ -2174,6 +2184,15 @@ export class FileMemoryEngineBackend {
|
|
|
2174
2184
|
if (ops.length === 0) {
|
|
2175
2185
|
return report;
|
|
2176
2186
|
}
|
|
2187
|
+
if (admitted !== undefined)
|
|
2188
|
+
requireLandingsAdmitted(admitted, landings);
|
|
2189
|
+
for (const cut of admitted === undefined ? [{ ops, evidence: transfers, account: rows }] : cutPerLanding(ops, transfers, shares, rows)) {
|
|
2190
|
+
confirm?.();
|
|
2191
|
+
this.commitCut(cut, lockToken, anchoredRoot);
|
|
2192
|
+
}
|
|
2193
|
+
return report;
|
|
2194
|
+
}
|
|
2195
|
+
commitCut({ ops, evidence: transfers, account: rows }, lockToken, anchoredRoot) {
|
|
2177
2196
|
const txn = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2178
2197
|
const stagedFiles = [];
|
|
2179
2198
|
const refuseUnstaging = (batch, at) => {
|
|
@@ -2229,6 +2248,7 @@ export class FileMemoryEngineBackend {
|
|
|
2229
2248
|
this.refuseEscapingTargets([op], "applyPatches (after the commit point; the journal is kept)");
|
|
2230
2249
|
if (op.kind === "write" && op.staged) {
|
|
2231
2250
|
renameSync(op.staged, op.target);
|
|
2251
|
+
this.landingFault?.(op.target);
|
|
2232
2252
|
}
|
|
2233
2253
|
else if (op.kind === "delete") {
|
|
2234
2254
|
rmSync(op.target, { force: true });
|
|
@@ -2244,7 +2264,6 @@ export class FileMemoryEngineBackend {
|
|
|
2244
2264
|
this.saveLedger();
|
|
2245
2265
|
this.appendTransfers(coherentTransfers);
|
|
2246
2266
|
this.closeJournalIfOurs(txn);
|
|
2247
|
-
return report;
|
|
2248
2267
|
}
|
|
2249
2268
|
closeJournalIfOurs(txn) {
|
|
2250
2269
|
const jp = join(this.controlPlaneRoot, STORE_JOURNAL_FILE);
|
|
@@ -2265,7 +2284,7 @@ export class FileMemoryEngineBackend {
|
|
|
2265
2284
|
}
|
|
2266
2285
|
rmSync(jp, { force: true });
|
|
2267
2286
|
}
|
|
2268
|
-
planOne(patch, report, ops, rows, plannedTargets, plannedDeletes, transfers) {
|
|
2287
|
+
planOne(patch, report, ops, rows, plannedTargets, plannedDeletes, transfers, landings) {
|
|
2269
2288
|
if (!isValidEntryId(patch.id)) {
|
|
2270
2289
|
report.conflicts.push({ op: patch.op, id: patch.id, reason: `invalid_id_shape (id ${JSON.stringify(patch.id)} fails the frontmatter id contract — the read side would drop it, leaving an applied-but-invisible entry)` });
|
|
2271
2290
|
return;
|
|
@@ -2336,6 +2355,7 @@ export class FileMemoryEngineBackend {
|
|
|
2336
2355
|
}
|
|
2337
2356
|
if (existingAnywhere !== undefined && existingAnywhere.entry.slug !== entry.slug) {
|
|
2338
2357
|
ops.push({ kind: "delete", target: existingAnywhere.path });
|
|
2358
|
+
landings.push({ id: entry.id, scope: existingAnywhere.entry.scope, slug: existingAnywhere.entry.slug });
|
|
2339
2359
|
}
|
|
2340
2360
|
const dir = this.scopeDir(entry.scope);
|
|
2341
2361
|
let slug = entry.slug;
|
|
@@ -2356,6 +2376,7 @@ export class FileMemoryEngineBackend {
|
|
|
2356
2376
|
plannedTargets.add(target);
|
|
2357
2377
|
ops.push({ kind: "write", target, content });
|
|
2358
2378
|
ops.push({ kind: "shadow-write", target: entry.id, content });
|
|
2379
|
+
landings.push({ id: entry.id, scope: entry.scope, slug });
|
|
2359
2380
|
const now = this.now();
|
|
2360
2381
|
bindRowTracked(rows, entry.id, { rev: computeEntryRev(entry), scope: entry.scope, slug, at: now }, transfers, "applyPatches-move");
|
|
2361
2382
|
const priorBinding = plannedDeletes.get(entry.id);
|
|
@@ -2413,17 +2434,21 @@ export class FileMemoryEngineBackend {
|
|
|
2413
2434
|
const shadowText = this.readCommittedShadow(patch.id);
|
|
2414
2435
|
if (shadowText !== undefined && shadowText !== serializeEntryFile(found.entry)) {
|
|
2415
2436
|
ops.push({ kind: "write", target: found.path, content: shadowText });
|
|
2437
|
+
landings.push({ id: patch.id, scope: found.entry.scope, slug: found.entry.slug });
|
|
2416
2438
|
}
|
|
2417
2439
|
return;
|
|
2418
2440
|
}
|
|
2419
2441
|
if (patch.op === "delete") {
|
|
2420
2442
|
ops.push({ kind: "delete", target: found.path });
|
|
2421
2443
|
ops.push({ kind: "shadow-delete", target: patch.id });
|
|
2444
|
+
landings.push({ id: patch.id, scope: found.entry.scope, slug: found.entry.slug });
|
|
2422
2445
|
const deletedBinding = rowsBinding(rows, patch.id);
|
|
2423
2446
|
if (deletedBinding !== undefined) {
|
|
2424
2447
|
const derived = this.bindingAbsPath(deletedBinding);
|
|
2425
|
-
if (derived !== found.path)
|
|
2448
|
+
if (derived !== found.path) {
|
|
2426
2449
|
ops.push({ kind: "delete", target: derived });
|
|
2450
|
+
landings.push({ id: patch.id, scope: deletedBinding.scope, slug: deletedBinding.slug });
|
|
2451
|
+
}
|
|
2427
2452
|
}
|
|
2428
2453
|
transfers.push({
|
|
2429
2454
|
ev: randomUUID(),
|
|
@@ -2446,11 +2471,14 @@ export class FileMemoryEngineBackend {
|
|
|
2446
2471
|
const dir = this.scopeDir(entry.scope);
|
|
2447
2472
|
const target = join(dir, `${entry.slug}.md`);
|
|
2448
2473
|
const content = serializeEntryFile(entry);
|
|
2449
|
-
if (target !== found.path)
|
|
2474
|
+
if (target !== found.path) {
|
|
2450
2475
|
ops.push({ kind: "delete", target: found.path });
|
|
2476
|
+
landings.push({ id: entry.id, scope: found.entry.scope, slug: found.entry.slug });
|
|
2477
|
+
}
|
|
2451
2478
|
plannedTargets.add(target);
|
|
2452
2479
|
ops.push({ kind: "write", target, content });
|
|
2453
2480
|
ops.push({ kind: "shadow-write", target: entry.id, content });
|
|
2481
|
+
landings.push({ id: entry.id, scope: entry.scope, slug: entry.slug });
|
|
2454
2482
|
bindRowTracked(rows, entry.id, { rev: computeEntryRev(entry), scope: entry.scope, slug: entry.slug, at: this.now() }, transfers, "applyPatches-move");
|
|
2455
2483
|
report.applied.push({ op: "update", id: entry.id, slug: entry.slug });
|
|
2456
2484
|
}
|
|
@@ -10,9 +10,9 @@ export { parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, com
|
|
|
10
10
|
export { consolidationExposedFrontmatter } from "./frontmatter.js";
|
|
11
11
|
export { committedDistilledOf, distilledEquals } from "./frontmatter.js";
|
|
12
12
|
export { deriveShadowedSet } from "./shadowing.js";
|
|
13
|
-
export { CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type MemoryConsolidationOptions, CONSOLIDATION_RUN_STOP_REASONS, type ConsolidationRunStopReason, } from "./consolidation.js";
|
|
13
|
+
export { CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type ConsolidationWriteGate, type MemoryConsolidationOptions, CONSOLIDATION_RUN_STOP_REASONS, type ConsolidationRunStopReason, } from "./consolidation.js";
|
|
14
14
|
export { DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, MEMORY_DISTILLER_PURITY_CONTRACT_V1, detectCleanArmVerbatimLeak, type CleanArmLeakFinding, type CleanArmLeakVerdict, type MemoryDistillerPurityContract, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, type ConsolidationDistillFn, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationDriveResult, type ConsolidationFoldState, type DistillerCandidate, type DistillerChatAnswer, type DistillerChatFn, type DistillerChatRequest, type FuseSchedule, type LlmConsolidationPlan, type LlmConsolidationPlanArm, type LlmConsolidationPlanProduct, type LlmDistillerContract, type MintLlmConsolidationPlanResult, type PlanParseRepairs, type SanitizedLlmGroups, } from "./distiller.js";
|
|
15
|
-
export { CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, type ConsolidationDriverEngine, type ConsolidationDriverRunRow, type ConsolidationRunReceipt, type RunMemoryConsolidationOptions, } from "./consolidation-driver.js";
|
|
15
|
+
export { CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, CONSOLIDATION_WRITE_REFUSED_CODE, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, type ConsolidationDriverEngine, type ConsolidationDriverRunRow, type ConsolidationRunReceipt, type ConsolidationWriteJudge, type ConsolidationWriteTarget, type RunMemoryConsolidationOptions, } from "./consolidation-driver.js";
|
|
16
16
|
export type { OriginClearanceRow, OriginClearanceEvent, OriginClearanceShadow } from "./origin-clearance.js";
|
|
17
17
|
export { MEMORY_ORIGIN_CAUSES, writableRootOf } from "./types.js";
|
|
18
18
|
export type { MemoryBackend, MemoryEntry, MemoryEntryFrontmatter, MemoryEntryOrigin, MemoryOriginCause, MemoryScopeEnumeration, MemoryEntryDistilled, MemoryEntryDistilledInput, MemoryEntryHeader, ScoredMemoryEntry, NotePatch, PatchReport, MaterializedFile, MemorySessionHandle, HarvestReport, HarvestRejection, HarvestRejectionCode, MemoryAnnouncement, ScanFinding, } from "./types.js";
|
|
@@ -12,7 +12,7 @@ export { committedDistilledOf, distilledEquals } from "./frontmatter.js";
|
|
|
12
12
|
export { deriveShadowedSet } from "./shadowing.js";
|
|
13
13
|
export { CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, CONSOLIDATION_RUN_STOP_REASONS, } from "./consolidation.js";
|
|
14
14
|
export { DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, MEMORY_DISTILLER_PURITY_CONTRACT_V1, detectCleanArmVerbatimLeak, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, } from "./distiller.js";
|
|
15
|
-
export { CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, } from "./consolidation-driver.js";
|
|
15
|
+
export { CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, CONSOLIDATION_WRITE_REFUSED_CODE, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, } from "./consolidation-driver.js";
|
|
16
16
|
export { MEMORY_ORIGIN_CAUSES, writableRootOf } from "./types.js";
|
|
17
17
|
export { memoryBackendContract, assertMemoryBackendSearchEquivalence, } from "./memory-backend-contract.js";
|
|
18
18
|
export { SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, isOrgScopeKey, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, } from "./scope-contract.js";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The landing-admission protocol: a caller that holds authority over WHERE notes may land (the consolidation run's write
|
|
3
|
+
* judge) and a backend that names the landings (it alone knows the final spelling — a collision suffix is its decision, taken
|
|
4
|
+
* under its transaction lock against the files as they are at that moment).
|
|
5
|
+
*
|
|
6
|
+
* A LANDING is every entry file a patch writes or removes: an add's final spelling, an update's target, the file a delete
|
|
7
|
+
* removes, and the old file a rename leaves. The backend plans a batch under its lock, finds every landing, and — before it
|
|
8
|
+
* stages anything — leaves the lock to put the ones the caller has not yet been told of to `admit`; it then plans again and
|
|
9
|
+
* writes only when every landing is one `admit` resolved. A spelling that moved in between (a rival writer took the name) is
|
|
10
|
+
* asked about again; it is never renamed after it was judged. The judgment is awaited outside the lock: it may wait on a rule store.
|
|
11
|
+
*
|
|
12
|
+
* An admitted batch is asked whole and committed one landing at a time ({@link cutPerLanding}): a backend's commit record is
|
|
13
|
+
* replayed by its crash recovery without anyone to ask, so it never carries a landing behind another one. A crash between two
|
|
14
|
+
* landings leaves the earlier landed and the later uncommitted, to be asked again by whoever applies it next; recovery only
|
|
15
|
+
* ever completes the one landing whose commit had begun.
|
|
16
|
+
*/
|
|
17
|
+
import type { MemoryBackend, NotePatch, PatchReport } from "./types.js";
|
|
18
|
+
/** One landing: the entry's id and the scope directory and slug of the file it writes or removes. */
|
|
19
|
+
export interface PatchLanding {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly scope: string;
|
|
22
|
+
readonly slug: string;
|
|
23
|
+
}
|
|
24
|
+
/** Resolves ⇒ the landings may be written; rejects ⇒ nothing is written and the rejection propagates unchanged. */
|
|
25
|
+
export type AdmitLandings = (landings: readonly PatchLanding[]) => Promise<void>;
|
|
26
|
+
/** Rounds of "plan, ask, plan again" a batch gets before the backend gives up on a name that keeps moving. */
|
|
27
|
+
export declare const MAX_LANDING_ADMISSION_ROUNDS = 4;
|
|
28
|
+
/** Thrown from inside the lock, before anything is staged: these landings have not been admitted yet. */
|
|
29
|
+
export declare class LandingsNeedAdmission extends Error {
|
|
30
|
+
readonly landings: readonly PatchLanding[];
|
|
31
|
+
constructor(landings: readonly PatchLanding[]);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Inside the lock, once the batch is planned with its `landings`: throw {@link LandingsNeedAdmission} naming the ones not in
|
|
35
|
+
* `admitted`. Nothing is on disk yet, so the throw is a clean exit.
|
|
36
|
+
*/
|
|
37
|
+
export declare function requireLandingsAdmitted(admitted: ReadonlySet<string>, landings: readonly PatchLanding[]): void;
|
|
38
|
+
/** One planned patch's share of an admitted batch: where its operations and evidence end in the batch's lists, and — iff it has
|
|
39
|
+
* a landing — the account as it stands after it. */
|
|
40
|
+
export interface PatchShare<R> {
|
|
41
|
+
readonly opsEnd: number;
|
|
42
|
+
readonly evidenceEnd: number;
|
|
43
|
+
readonly landed?: R;
|
|
44
|
+
}
|
|
45
|
+
/** One transaction of an admitted batch: the operations and evidence it commits and the account it leaves. */
|
|
46
|
+
export interface LandingCut<O, E, R> {
|
|
47
|
+
ops: O[];
|
|
48
|
+
evidence: E[];
|
|
49
|
+
account: R;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The commit cuts of an admitted batch, in order: a cut closes at each patch that has a landing, so no cut carries two patches'
|
|
53
|
+
* landings (a rename's two files are one patch's, committed together; a patch with operations but no landing — a conflict's restore
|
|
54
|
+
* of committed bytes — rides with the next). A cut leaves the account as it stood after its landing; the last cut leaves
|
|
55
|
+
* `finalAccount`, the batch's (whatever the patches after the last landing changed rides with it).
|
|
56
|
+
*/
|
|
57
|
+
export declare function cutPerLanding<O, E, R>(ops: readonly O[], evidence: readonly E[], shares: readonly PatchShare<R>[], finalAccount: R): Array<LandingCut<O, E, R>>;
|
|
58
|
+
/** A backend that cannot admit is handed the landings its caller admitted one call each, so none of its commits carries a landing
|
|
59
|
+
* behind another whatever its own commit unit is; `before` (the caller's holder check) runs ahead of each call. */
|
|
60
|
+
export declare function applyEachAlone(backend: Pick<MemoryBackend, "applyPatches">, patches: readonly NotePatch[], before: () => void): Promise<PatchReport>;
|
|
61
|
+
/**
|
|
62
|
+
* The backend's side of the loop. `run(admitted)` plans and applies one locked attempt (it throws
|
|
63
|
+
* {@link LandingsNeedAdmission} when the plan names a spelling not in `admitted`); with no `admit` it runs once, unconditioned.
|
|
64
|
+
*/
|
|
65
|
+
export declare function admittingLandings<T>(admit: AdmitLandings | undefined, run: (admitted: ReadonlySet<string> | undefined) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export const MAX_LANDING_ADMISSION_ROUNDS = 4;
|
|
2
|
+
export class LandingsNeedAdmission extends Error {
|
|
3
|
+
landings;
|
|
4
|
+
constructor(landings) {
|
|
5
|
+
super("memory landings need admission");
|
|
6
|
+
this.landings = landings;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const landingKey = (l) => `${l.scope}\u0000${l.slug}`;
|
|
10
|
+
export function requireLandingsAdmitted(admitted, landings) {
|
|
11
|
+
const pending = landings.filter((l) => !admitted.has(landingKey(l)));
|
|
12
|
+
if (pending.length > 0)
|
|
13
|
+
throw new LandingsNeedAdmission(pending);
|
|
14
|
+
}
|
|
15
|
+
export function cutPerLanding(ops, evidence, shares, finalAccount) {
|
|
16
|
+
const cuts = [];
|
|
17
|
+
let open;
|
|
18
|
+
let opsAt = 0;
|
|
19
|
+
let evidenceAt = 0;
|
|
20
|
+
for (const s of shares) {
|
|
21
|
+
if (s.opsEnd > opsAt || s.evidenceEnd > evidenceAt) {
|
|
22
|
+
if (open === undefined)
|
|
23
|
+
cuts.push((open = { ops: [], evidence: [], account: finalAccount }));
|
|
24
|
+
open.ops.push(...ops.slice(opsAt, s.opsEnd));
|
|
25
|
+
open.evidence.push(...evidence.slice(evidenceAt, s.evidenceEnd));
|
|
26
|
+
opsAt = s.opsEnd;
|
|
27
|
+
evidenceAt = s.evidenceEnd;
|
|
28
|
+
}
|
|
29
|
+
if (s.landed !== undefined && open !== undefined) {
|
|
30
|
+
open.account = s.landed;
|
|
31
|
+
open = undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const last = cuts[cuts.length - 1];
|
|
35
|
+
if (last !== undefined)
|
|
36
|
+
last.account = finalAccount;
|
|
37
|
+
return cuts;
|
|
38
|
+
}
|
|
39
|
+
export async function applyEachAlone(backend, patches, before) {
|
|
40
|
+
const report = { applied: [], conflicts: [] };
|
|
41
|
+
for (const p of patches) {
|
|
42
|
+
before();
|
|
43
|
+
const r = await backend.applyPatches([p]);
|
|
44
|
+
report.applied.push(...r.applied);
|
|
45
|
+
report.conflicts.push(...r.conflicts);
|
|
46
|
+
}
|
|
47
|
+
return report;
|
|
48
|
+
}
|
|
49
|
+
export async function admittingLandings(admit, run) {
|
|
50
|
+
if (admit === undefined)
|
|
51
|
+
return run(undefined);
|
|
52
|
+
const admitted = new Set();
|
|
53
|
+
for (let round = 0;; round++) {
|
|
54
|
+
try {
|
|
55
|
+
return await run(admitted);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
if (!(err instanceof LandingsNeedAdmission))
|
|
59
|
+
throw err;
|
|
60
|
+
if (round >= MAX_LANDING_ADMISSION_ROUNDS) {
|
|
61
|
+
throw Object.assign(new Error(`memory landings kept moving: ${err.landings.length} spelling(s) were still unadmitted after ${MAX_LANDING_ADMISSION_ROUNDS} rounds, nothing was written`), { code: "memory.landing_unstable" });
|
|
62
|
+
}
|
|
63
|
+
await admit(err.landings);
|
|
64
|
+
for (const l of err.landings)
|
|
65
|
+
admitted.add(landingKey(l));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -379,10 +379,33 @@ export interface MemoryBackend {
|
|
|
379
379
|
* the whole batch with `memory.target_outside_store` when the anchor is another root (`detail.recorded`
|
|
380
380
|
* = the anchor, `detail.now` = `anchoredRoot`): a session mounted before a re-anchor commits nothing
|
|
381
381
|
* into the re-anchored store. A backend without a directory ignores it.
|
|
382
|
+
*
|
|
383
|
+
* `opts.admit` is the caller's authority over WHERE the batch lands, asked at the spelling each ADD will finally take (after
|
|
384
|
+
* the backend's own collision suffix). A backend that declares {@link admitsLandings} resolves every add's final landing
|
|
385
|
+
* `{ id, scope, slug }`, calls `admit` with ALL the ones it has not yet been told of BEFORE it writes any of them (never while
|
|
386
|
+
* it holds its own transaction lock: the caller's judgment may wait on a store), and writes only spellings `admit` has
|
|
387
|
+
* resolved: a spelling that moves after `admit` returned (a rival writer took the name) is asked about again before anything is
|
|
388
|
+
* written, never renamed after the judgment. A rejection from `admit` writes nothing and propagates unchanged. Asked whole, an
|
|
389
|
+
* admitted batch commits one landing at a time: whatever the backend's crash recovery replays without asking (its commit
|
|
390
|
+
* record) never carries a landing behind another one, so a crash between two landings leaves the later one unwritten until
|
|
391
|
+
* its next apply asks again. A backend that does not declare it is handed neither `admit` nor `confirm`: the caller asks about
|
|
392
|
+
* the spellings it froze, all of them, just before the batch, and hands it the writes one call each, its own check before each.
|
|
393
|
+
* `opts.confirm` is the caller's last word, called synchronously under the transaction lock before each commit of the batch
|
|
394
|
+
* (once every spelling is admitted, before anything of that commit is staged), with or without `admit`; a throw aborts what is
|
|
395
|
+
* not yet committed and propagates unchanged.
|
|
382
396
|
*/
|
|
383
397
|
applyPatches(patches: readonly NotePatch[], opts?: {
|
|
384
398
|
readonly anchoredRoot?: string;
|
|
399
|
+
readonly admit?: (landings: readonly {
|
|
400
|
+
readonly id: string;
|
|
401
|
+
readonly scope: string;
|
|
402
|
+
readonly slug: string;
|
|
403
|
+
}[]) => Promise<void>;
|
|
404
|
+
readonly confirm?: () => void;
|
|
385
405
|
}): Promise<PatchReport>;
|
|
406
|
+
/** `true` ⇔ {@link applyPatches} honors `opts.admit` and `opts.confirm` (the File backend does). Absent ⇒ the caller admits the frozen
|
|
407
|
+
* spellings itself and sends the writes one call each. */
|
|
408
|
+
readonly admitsLandings?: true;
|
|
386
409
|
/** design/84 Seam B cursor pair, migrated verbatim onto the entry-id plane (S2 rewires consolidation). */
|
|
387
410
|
getConsolidationCursor(scope: string): Promise<string | undefined>;
|
|
388
411
|
setConsolidationCursor(scope: string, cursor: string): Promise<void>;
|
|
@@ -165,7 +165,8 @@ export declare function refusePermissionModeDoors(spec: Pick<TaskSpec, "permissi
|
|
|
165
165
|
* The doors a RESUMED leg's prepare refuses at, read as one screen for the resume ladder to run BEFORE the claim: the mode
|
|
166
166
|
* doors on the re-supplied config ({@link refusePermissionModeDoors}), its output-style seat (`resolveOutputStyleFrame`), its
|
|
167
167
|
* approval-reach declaration (`screenApprovalReach`), the skills it invokes by name (`screenUserInvokedSkills` — the part no
|
|
168
|
-
* roster changes; a name the leg's roster reads as nothing is prepare's)
|
|
168
|
+
* roster changes; a name the leg's roster reads as nothing is prepare's), its attachments (`screenUserAttachments` — a resume
|
|
169
|
+
* sends no new human message, so any entry is refused) and the mode words the checkpoint row recorded (`recordedModeSeed`) — prepare's own functions, so the refusal carries the very
|
|
169
170
|
* sentence and code prepare would throw. It leaves by the resume's ONE refusal channel, `CheckpointError`
|
|
170
171
|
* `checkpoint.config_refused` (`message` = prepare's sentence, `detail.code` = prepare's code, `detail.field` = the input that
|
|
171
172
|
* carries it: `"taskConfig"` or `"inheritedGate"`), where prepare itself would end the run `failed` after the claim — a
|
|
@@ -174,7 +175,7 @@ export declare function refusePermissionModeDoors(spec: Pick<TaskSpec, "permissi
|
|
|
174
175
|
* corrected resume. A throw that is not one of prepare's five codes (a caller's accessor failing) is not a refusal and leaves
|
|
175
176
|
* as it was thrown.
|
|
176
177
|
*/
|
|
177
|
-
export declare function refuseResumeDoors(config: Pick<TaskSpec, "permissionMode" | "shellGate" | "outputStyle" | "approvalReach" | "skills" | "userInvokedSkills">, recordedGate: Parameters<typeof recordedModeSeed>[0]): void;
|
|
178
|
+
export declare function refuseResumeDoors(config: Pick<TaskSpec, "permissionMode" | "shellGate" | "outputStyle" | "approvalReach" | "skills" | "userInvokedSkills" | "userAttachments">, recordedGate: Parameters<typeof recordedModeSeed>[0]): void;
|
|
178
179
|
/** The three-member answer every mode question gets — the manifest member, the steer verb's return value and the
|
|
179
180
|
* fold notice's detail share it. `requested` is absent when the caller declared nothing (a root inherits `default`,
|
|
180
181
|
* a child inherits its parent). */
|
package/dist/core/mode-policy.js
CHANGED
|
@@ -8,6 +8,7 @@ import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
|
8
8
|
import { createSafeNotifier } from "./safe-notify.js";
|
|
9
9
|
import { assertNoRetiredKeys, RETIRED_TASK_SPEC_KEYS } from "./retired-keys.js";
|
|
10
10
|
import { resolveOutputStyleFrame } from "./output-style.js";
|
|
11
|
+
import { screenUserAttachments } from "./user-attachment.js";
|
|
11
12
|
import { CheckpointError, isCheckpointConfigRefusalCode, recordedModeSeed } from "./checkpoint-store.js";
|
|
12
13
|
import { screenApprovalReach } from "./wiring-manifest.js";
|
|
13
14
|
import { screenUserInvokedSkills } from "./skill-grant.js";
|
|
@@ -116,6 +117,7 @@ export function refuseResumeDoors(config, recordedGate) {
|
|
|
116
117
|
resolveOutputStyleFrame(config.outputStyle);
|
|
117
118
|
screenApprovalReach(config.approvalReach);
|
|
118
119
|
screenUserInvokedSkills(config);
|
|
120
|
+
screenUserAttachments(config.userAttachments, { resumeLeg: true });
|
|
119
121
|
});
|
|
120
122
|
screenAs("inheritedGate", () => void recordedModeSeed(recordedGate));
|
|
121
123
|
}
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
import { type DenyFace } from "../tools/fs/read-deny.js";
|
|
76
76
|
import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
77
77
|
import { type ToolCallLike } from "./tool-registry.js";
|
|
78
|
-
import { type PathFamily, type PathIdentity } from "../tools/fs/safety.js";
|
|
78
|
+
import { type PathDenialEvidence, type PathFamily, type PathIdentity } from "../tools/fs/safety.js";
|
|
79
79
|
import { type TargetViews } from "./path-rule-binding.js";
|
|
80
80
|
/**
|
|
81
81
|
* The closed set of rule BEHAVIORS — what a matching rule says about the call. The upstream vocabulary
|
|
@@ -141,11 +141,12 @@ export declare const READ_RULE_TOOL = "Read";
|
|
|
141
141
|
* roots, the write tools — judged on the path that call reads, searches or writes. A path rule on any other tool keeps
|
|
142
142
|
* its tool's name as its reach (`Write(<path>)` is judged on the Write tool, a stricter reading the reference does not make).
|
|
143
143
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
144
|
+
* Both tightening columns are judged at the gate, on the call's own path target, before the call runs — by the rule
|
|
145
|
+
* policy's lane and by the persisted lane alike, one column read for deny and for ask. A deny row is exported to the
|
|
146
|
+
* engine's deny tables as well (`ToolPolicy.pathRules` bound where the policy is installed, the person's rows at
|
|
147
|
+
* prepare), which do what the gate cannot: refuse inside the tool at execution time, descend into a search's results,
|
|
148
|
+
* reach the shell's file commands' operands, and keep a refused target out of the out-of-root question. An ask does not
|
|
149
|
+
* descend (a search rooted above the asked path is not asked, and its results are not filtered).
|
|
149
150
|
*
|
|
150
151
|
* The ALLOW column is the reference's too: an allow by path is read on these two tools only — a `Read(<path>)` allow speaks
|
|
151
152
|
* for the read face, an `Edit(<path>)` allow for the write face and the read face (an edit allowed is a read allowed) — and
|
|
@@ -156,9 +157,9 @@ export declare const PATH_RULE_FACES: Readonly<Record<string, Readonly<Partial<R
|
|
|
156
157
|
/** The face a call's protective path target is on: `read` for a read slot, `write` for every other access; none without
|
|
157
158
|
* one — the face a {@link PATH_RULE_FACES} rule is judged against, in a rule policy and in the persisted rule lane alike. */
|
|
158
159
|
export declare function callPathFaceOf(call: ToolCallLike): DenyFace | undefined;
|
|
159
|
-
/** Does some {@link PATH_RULE_FACES}
|
|
160
|
-
* the call's tool is called (a caller's tool declaring a read path target is on the read face too)? */
|
|
161
|
-
export declare function
|
|
160
|
+
/** Does some {@link PATH_RULE_FACES} tightening column (deny or ask) hold `face` — may a face-keyed row speak for a call on
|
|
161
|
+
* it, whatever the call's tool is called (a caller's tool declaring a read path target is on the read face too)? */
|
|
162
|
+
export declare function faceRowsSpeakFor(face: DenyFace | undefined): boolean;
|
|
162
163
|
/**
|
|
163
164
|
* The tools whose rule content NAMES THE VALUE of one parameter instead of a command or a path — the reference's `Skill(<name>)`
|
|
164
165
|
* form, one row per tool: `param` is the argument the rule's content is read against, `params` every scalar argument the tool
|
|
@@ -949,6 +950,9 @@ export interface PersistedRuleVerdict {
|
|
|
949
950
|
* the lexer's reason. The lane projects it as an UNREADABLE answer carrying that reason (it fails closed)
|
|
950
951
|
* — never as a matched ask rule. */
|
|
951
952
|
readonly unreadable?: string;
|
|
953
|
+
/** A path DENY's coordinates: the view of the target the reaching row met and the face the call is on. Minted here,
|
|
954
|
+
* with the verdict; the gate's exits mint the refusal's card from it and judge nothing a second time. */
|
|
955
|
+
readonly denial?: PathDenialEvidence;
|
|
952
956
|
}
|
|
953
957
|
/**
|
|
954
958
|
* The ONE adjudication over a MIXED persisted-rule table for a shell call: walk the behaviors in
|
|
@@ -991,10 +995,12 @@ export declare function adjudicatePersistedRules(rules: readonly PersistedRule[]
|
|
|
991
995
|
*
|
|
992
996
|
* A row is anchored by `anchorsOf` (the person's rows: a project row at its scope root, the rest at the chain's top leg).
|
|
993
997
|
*
|
|
994
|
-
* `call.face` is the face the call's protective target is on (`read` / `write`):
|
|
995
|
-
* is the call's tool's row, or when {@link PATH_RULE_FACES} puts that face in the row's
|
|
996
|
-
*
|
|
997
|
-
*
|
|
998
|
+
* `call.face` is the face the call's protective target is on (`read` / `write`): a tightening row (deny or ask) speaks for
|
|
999
|
+
* the call when it is the call's tool's row, or when {@link PATH_RULE_FACES} puts that face in the row's column of THAT
|
|
1000
|
+
* behavior (a `Read(<path>)` deny row refuses, a `Read(<path>)` ask row asks for, a Grep / Glob / RepoMap / caller's reader
|
|
1001
|
+
* searching or reading under the path) — the one sentence the rule policy's lane speaks too. Both directions judge the call's
|
|
1002
|
+
* own target here; the run's deny tables are a different job (a search's results, a shell command's operands, the
|
|
1003
|
+
* execution-time re-judgment).
|
|
998
1004
|
*/
|
|
999
1005
|
export declare function adjudicatePersistedPathRules(rules: readonly PersistedRule[], call: {
|
|
1000
1006
|
tool: string;
|
|
@@ -38,8 +38,8 @@ export function callPathFaceOf(call) {
|
|
|
38
38
|
const target = protectivePathTargetOf(call);
|
|
39
39
|
return target === undefined ? undefined : target.access === "read" ? "read" : "write";
|
|
40
40
|
}
|
|
41
|
-
export function
|
|
42
|
-
return face !== undefined && Object.values(PATH_RULE_FACES).some((columns) => columns.ask?.includes(face) === true);
|
|
41
|
+
export function faceRowsSpeakFor(face) {
|
|
42
|
+
return face !== undefined && Object.values(PATH_RULE_FACES).some((columns) => columns.deny?.includes(face) === true || columns.ask?.includes(face) === true);
|
|
43
43
|
}
|
|
44
44
|
export const NAME_RULE_FACES = Object.freeze({
|
|
45
45
|
Skill: Object.freeze({ param: "skill", params: Object.freeze(["skill", "args"]) }),
|
|
@@ -1000,7 +1000,7 @@ export function adjudicatePersistedRules(rules, call) {
|
|
|
1000
1000
|
}
|
|
1001
1001
|
export function adjudicatePersistedPathRules(rules, call, views, anchorsOf, leads) {
|
|
1002
1002
|
const speaks = (rule, behavior) => eligiblePersisted(rule, call, behavior) ||
|
|
1003
|
-
(
|
|
1003
|
+
(call.face !== undefined && PATH_RULE_FACES[rule.tool]?.[behavior]?.includes(call.face) === true && eligiblePersisted(rule, { ...call, tool: rule.tool }, behavior));
|
|
1004
1004
|
const bind = (rows) => bindPathRules(rows.map((rule) => ({ parsed: rule, ruleText: rule.rule, rule })), (s) => anchorsOf(s.rule));
|
|
1005
1005
|
for (const behavior of RULE_BEHAVIORS_BY_PRECEDENCE) {
|
|
1006
1006
|
if (behavior === "allow") {
|
|
@@ -1021,7 +1021,10 @@ export function adjudicatePersistedPathRules(rules, call, views, anchorsOf, lead
|
|
|
1021
1021
|
const hit = judgeRuleRows(rowsWhereTheyLead(bind(rows), leads), views.tighten, "tighten");
|
|
1022
1022
|
if (hit === undefined)
|
|
1023
1023
|
continue;
|
|
1024
|
-
|
|
1024
|
+
const rule = hit.row.spec.rule;
|
|
1025
|
+
if (hit.unreadable !== undefined)
|
|
1026
|
+
return { behavior: "ask", rules: [rule], unreadable: hit.unreadable };
|
|
1027
|
+
return { behavior, rules: [rule], ...(behavior === "deny" && hit.view !== undefined && call.face !== undefined ? { denial: { rule: rule.rule, view: hit.view, face: call.face } } : {}) };
|
|
1025
1028
|
}
|
|
1026
1029
|
return undefined;
|
|
1027
1030
|
}
|
|
@@ -61,6 +61,17 @@ export interface RemovedPermissionRule {
|
|
|
61
61
|
source: "user" | "project";
|
|
62
62
|
status: "removed";
|
|
63
63
|
}
|
|
64
|
+
/** One read of the person's store as every consumer of it reads it, the failure law applied (a run's one reader of it,
|
|
65
|
+
* `person-path-rows.ts`, takes it; the rule lanes and the read station read it). */
|
|
66
|
+
export interface PersonRowsRead {
|
|
67
|
+
/** The rows this read stands on: each partition it read, as read; each it could not read, the deny and ask rows of the
|
|
68
|
+
* run's last successful read of that partition (none before one). This session's rows first. */
|
|
69
|
+
readonly rules: readonly EffectivePermissionRule[];
|
|
70
|
+
/** The org partition's resolution off this read. Absent when the whole read failed — {@link unreadable} says why. */
|
|
71
|
+
readonly org?: OrgRuleResolution;
|
|
72
|
+
/** Present iff a personal partition (or the whole read) could not be read: the failure's line. */
|
|
73
|
+
readonly unreadable?: string;
|
|
74
|
+
}
|
|
64
75
|
/** THE answer of the store's one read query. */
|
|
65
76
|
export interface EffectivePermissionRules {
|
|
66
77
|
/** Live personal rows, in reporting order: this session's rows first (the narrowest standing yes), then
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
* 匹配,不是 shell AST / 前缀 lane —— `Bash(command:git push*)` 匹配不了 `cd /x && git push`,
|
|
58
58
|
* 「以为挡住了」比没挡更危险。`primaryFieldGeneric:"allow-lexical"` 显式解锁。
|
|
59
59
|
*/
|
|
60
|
-
import type
|
|
60
|
+
import { type NamedToolPolicy } from "./tool-policy.js";
|
|
61
61
|
import { parsePermissionRule, type ParsedPermissionRule } from "./permission-rule-syntax.js";
|
|
62
62
|
import { type PathRuleBases } from "./permission-rule-model.js";
|
|
63
63
|
export { parsePermissionRule, type ParsedPermissionRule };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { attest } from "./tool-policy.js";
|
|
1
2
|
import { MCP_NAMESPACE, protocolOf } from "./protocol-table.js";
|
|
2
3
|
import { catalogRuleFaceOf, isShellFaceToolName, pathTargetOf } from "./tool-registry.js";
|
|
3
4
|
import { indexOfUnescaped, lastIndexOfUnescaped, parsePermissionRule } from "./permission-rule-syntax.js";
|
|
@@ -348,7 +349,7 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
348
349
|
const hit = judgeRuleRows(rows, views, "tighten");
|
|
349
350
|
const rule = hit === undefined ? undefined : pathLane.find((r) => of.get(r).spec === hit.row.spec);
|
|
350
351
|
if (hit !== undefined && hit.unreadable === undefined)
|
|
351
|
-
return { rule: rule };
|
|
352
|
+
return { rule: rule, ...(hit.view !== undefined ? { view: hit.view } : {}) };
|
|
352
353
|
if (hit !== undefined)
|
|
353
354
|
return judgeOthers(lane.filter((r) => !pathRuleOf.has(r)), req) ?? { rule: rule, unreadable: hit.unreadable };
|
|
354
355
|
}
|
|
@@ -416,7 +417,9 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
416
417
|
const denyLane = contentLaneOf("deny", entry, shellCall, callFace);
|
|
417
418
|
const contentDeny = denyLane.length > 0 ? contentHit(denyLane, req) : undefined;
|
|
418
419
|
if (contentDeny !== undefined && contentDeny.unreadable === undefined) {
|
|
419
|
-
|
|
420
|
+
const denied = { action: "deny", message: ruleDenial(req, contentDeny.rule.ruleText, contentDeny.rule.source) };
|
|
421
|
+
const face = contentDeny.view !== undefined ? callPathFaceOf(req) : undefined;
|
|
422
|
+
return face !== undefined ? attest(denied, req, { pathDenial: { rule: contentDeny.rule.ruleText, view: contentDeny.view, face } }) : denied;
|
|
420
423
|
}
|
|
421
424
|
if (entry) {
|
|
422
425
|
const paramDeny = matchParamRules(entry.param.deny, req.args, caps.maxScalarValueChars);
|
|
@@ -11,14 +11,15 @@
|
|
|
11
11
|
* the read closes over the gate input, and the allow arm is a conjunction over the gate's own state
|
|
12
12
|
* (mandates, the org latch, the marked-call seat) rather than a function of the answer alone.
|
|
13
13
|
*/
|
|
14
|
-
import type
|
|
14
|
+
import { type PermissionResult, type ToolCallRequest } from "./tool-policy.js";
|
|
15
15
|
import type { PersistedRuleAnswer, PersistedRuleHit, PersistedRuleHitRule } from "./hooks.js";
|
|
16
16
|
/** The lane's one read, normalized: at most one of `hit` / `unreadable` / `coverage` (the gate's reader
|
|
17
17
|
* produces it; the arms below read it). */
|
|
18
18
|
export interface PersistedRuleRead {
|
|
19
19
|
readonly hit?: PersistedRuleHit;
|
|
20
20
|
/** The lane could not answer: with `reason`, the CALL could not be read against the person's deny/ask rows;
|
|
21
|
-
* without, the store could not be read —
|
|
21
|
+
* without, a partition of the store (or all of it) could not be read — a deny / ask row inside it may forbid this call,
|
|
22
|
+
* so no allow beside it is a yes (the lane has already folded that: an allow, a coverage or nothing reads as this). */
|
|
22
23
|
readonly unreadable?: true;
|
|
23
24
|
/** The lexer's reason when the CALL could not be read against the person's deny/ask rows. */
|
|
24
25
|
readonly reason?: string;
|
|
@@ -42,7 +43,9 @@ export declare function disclosedRuleSet(rules: readonly PersistedRuleHitRule[])
|
|
|
42
43
|
* · DENY — the call is refused outright, with `decisionReason: "persisted_rule"` (the same word the
|
|
43
44
|
* lane's allow carries: one authority, one word) and the rule text disclosed through the display
|
|
44
45
|
* transform; the STATION attributes the refusal (`tightened: "deny"` ⇒ it writes the pass's refusing
|
|
45
|
-
* layer — the attribution is a station's word and lives in the station file).
|
|
46
|
+
* layer — the attribution is a station's word and lives in the station file). A path row's hit reports where
|
|
47
|
+
* it met the call; with `call` given and the face the call's own, the deny carries that as its attested `pathDenial`, so every exit that
|
|
48
|
+
* reads the deny (the gate's exit, the approved-edit re-check, a resumed leg's belt) mints the same card from the same fact.
|
|
46
49
|
* · ASK — on an allow, a fresh ask is minted carrying `matchedAskRule` (the marker an explicit `ask`
|
|
47
50
|
* permission rule stamps, so the ask's origin derives as `ask_rule`: the classifier never answers
|
|
48
51
|
* it, the persisted ALLOW arm never clears it, and the rule-offer face reports `shadowed`); on an
|
|
@@ -55,11 +58,16 @@ export declare function disclosedRuleSet(rules: readonly PersistedRuleHitRule[])
|
|
|
55
58
|
* an allow tightens to an ask carrying `requiresRealApproval` (no automatic lane clears it; the gate
|
|
56
59
|
* derives the origin `rule_store_unavailable` from its own record of the read), an ask acquires the
|
|
57
60
|
* bit, a deny stays a deny.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
61
|
+
* · UNREADABLE STORE — a partition of the store (or all of it) could not be READ (`unreadable` without a `reason`): a deny
|
|
62
|
+
* or ask row inside it may forbid this call and nobody can tell, so it fails CLOSED the same way, to the question
|
|
63
|
+
* only a person answers — an allow becomes an ask carrying `requiresRealApproval`, an ask acquires the bit, a deny stays
|
|
64
|
+
* a deny. Every call the lane judges is in this state until a read succeeds again (the lane announces the skipped store,
|
|
65
|
+
* once per run, and reads again on the next call). The two forms differ in WHICH fact the gate records (the call form
|
|
66
|
+
* is the class `rule_unreadable`; the store form `governance_unavailable` — both bypass-immune) and in the
|
|
67
|
+
* resumed-call belt, which vetoes only the call form: a person already answered that call's question.
|
|
60
68
|
* An allow hit (or no hit) returns the decision untouched: the allow arm is the station's.
|
|
61
69
|
*/
|
|
62
|
-
export declare function applyPersistedTightening(decision: PermissionResult, read: PersistedRuleRead | undefined): {
|
|
70
|
+
export declare function applyPersistedTightening(decision: PermissionResult, read: PersistedRuleRead | undefined, call?: ToolCallRequest): {
|
|
63
71
|
decision: PermissionResult;
|
|
64
|
-
tightened?: "deny" | "ask" | "unreadable";
|
|
72
|
+
tightened?: "deny" | "ask" | "unreadable" | "store_unreadable";
|
|
65
73
|
};
|