@sema-agent/core 5.31.0 → 5.33.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 +96 -0
- package/dist/agents/cascade.d.ts +49 -1
- package/dist/agents/cascade.js +2 -2
- package/dist/agents/verify.d.ts +70 -4
- package/dist/agents/verify.js +62 -16
- package/dist/core/checkpoint-store.d.ts +95 -0
- package/dist/core/checkpoint-store.js +40 -0
- package/dist/core/hooks.d.ts +14 -6
- package/dist/core/hooks.js +14 -3
- package/dist/core/memory-engine/file-backend.d.ts +172 -22
- package/dist/core/memory-engine/file-backend.js +877 -79
- package/dist/core/memory-engine/memory-backend-contract.js +33 -0
- package/dist/core/runner/assemble-result.d.ts +7 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +72 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +126 -0
- package/dist/core/runner/prepare-config-doors.d.ts +140 -0
- package/dist/core/runner/prepare-config-doors.js +250 -0
- package/dist/core/runner/prepare-safety-scan.d.ts +53 -0
- package/dist/core/runner/prepare-safety-scan.js +80 -0
- package/dist/core/runner/prepare-task.d.ts +28 -80
- package/dist/core/runner/prepare-task.js +102 -586
- package/dist/core/runner/prepare-workspace-restore.d.ts +102 -0
- package/dist/core/runner/prepare-workspace-restore.js +144 -0
- package/dist/core/runner/runtask.js +8 -2
- package/dist/core/tool-policy.d.ts +25 -0
- package/dist/core/types.d.ts +149 -13
- package/dist/index.d.ts +5 -4
- package/dist/index.js +2 -2
- package/dist/orchestration/workflow-governance.d.ts +6 -4
- package/dist/tools/fs/bash-readonly-classifier.d.ts +9 -3
- package/dist/tools/fs/bash-readonly-classifier.js +4 -1
- package/dist/tools/fs/fs-bash.d.ts +19 -3
- package/dist/tools/fs/fs-bash.js +26 -1
- package/dist/tools/fs/index.d.ts +27 -7
- package/dist/tools/fs/index.js +7 -2
- package/dist/tools/fs/read-deny.d.ts +66 -8
- package/dist/tools/fs/read-deny.js +75 -39
- package/dist/tools/fs/read-face.d.ts +24 -2
- package/dist/tools/fs/read-face.js +9 -0
- package/dist/tools/fs/search.js +2 -0
- package/package.json +1 -1
package/dist/core/hooks.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { decisionText, refuseOutOfContractDecision } from "./tool-policy.js";
|
|
2
2
|
import { brandPolicyAskClass } from "./ask-class.js";
|
|
3
3
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
4
|
+
import { PROBE_REASON_MAX, normalizeProbeCause } from "./checkpoint-store.js";
|
|
4
5
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
5
6
|
import { createSafeNotifier } from "./safe-notify.js";
|
|
6
7
|
import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
|
|
@@ -256,12 +257,20 @@ export async function runToolGate(input) {
|
|
|
256
257
|
}
|
|
257
258
|
if (decision.action === "allow" && (input.irreversibility === "always" || input.irreversibility === "maybe")) {
|
|
258
259
|
let tighten = true;
|
|
260
|
+
let probeReason;
|
|
261
|
+
let probeCause;
|
|
259
262
|
if (input.irreversibility === "maybe" && input.reversibilityProbe) {
|
|
260
263
|
let reversible = false;
|
|
261
264
|
const probeArgs = policyRewrite !== undefined ? policyRewrite : currentInput;
|
|
262
265
|
try {
|
|
263
266
|
const verdict = await withProbeTimeout(Promise.resolve(input.reversibilityProbe(probeArgs)), input.approvalTimeoutMs, input.abortSignal);
|
|
264
267
|
reversible = verdict?.reversible === true;
|
|
268
|
+
if (!reversible) {
|
|
269
|
+
const raw = verdict?.reason;
|
|
270
|
+
if (typeof raw === "string" && raw.trim().length > 0)
|
|
271
|
+
probeReason = inlineUntrusted(raw, PROBE_REASON_MAX);
|
|
272
|
+
probeCause = normalizeProbeCause(verdict?.cause);
|
|
273
|
+
}
|
|
265
274
|
}
|
|
266
275
|
catch {
|
|
267
276
|
reversible = false;
|
|
@@ -273,6 +282,8 @@ export async function runToolGate(input) {
|
|
|
273
282
|
action: "ask",
|
|
274
283
|
message: `tool "${toolName}" performs an irreversible action — explicit approval required`,
|
|
275
284
|
decisionReason: "safety",
|
|
285
|
+
...(probeReason !== undefined ? { probeReason } : {}),
|
|
286
|
+
...(probeCause !== undefined ? { probeCause } : {}),
|
|
276
287
|
};
|
|
277
288
|
denySource = input.shellGated === true ? "shellGate" : "safety";
|
|
278
289
|
}
|
|
@@ -436,7 +447,7 @@ export async function runToolGate(input) {
|
|
|
436
447
|
? { origin: orgAskOrigin !== undefined ? `org_${orgAskOrigin}` : "policy" }
|
|
437
448
|
: undefined;
|
|
438
449
|
if (suspendAsk && decision.action === "ask") {
|
|
439
|
-
const suspended = await suspendAsk(req, currentInput, safety, undefined, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined);
|
|
450
|
+
const suspended = await suspendAsk(req, currentInput, safety, undefined, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined);
|
|
440
451
|
if (suspended) {
|
|
441
452
|
if ("parkFailed" in suspended)
|
|
442
453
|
parkFailed = suspended.parkFailed;
|
|
@@ -455,7 +466,7 @@ export async function runToolGate(input) {
|
|
|
455
466
|
req.args = outcome.presentedInput;
|
|
456
467
|
}
|
|
457
468
|
if (suspendAsk && outcome.parkDeclined && parkFailed === undefined) {
|
|
458
|
-
const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined);
|
|
469
|
+
const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined);
|
|
459
470
|
if (suspended) {
|
|
460
471
|
if ("parkFailed" in suspended)
|
|
461
472
|
parkFailed = suspended.parkFailed;
|
|
@@ -483,7 +494,7 @@ export async function runToolGate(input) {
|
|
|
483
494
|
const resolved = await resolveAsk(decision, req);
|
|
484
495
|
decision = resolved;
|
|
485
496
|
if (resolved.action === "deny" && resolved.approverUnavailable === true && suspendAsk && parkFailed === undefined) {
|
|
486
|
-
const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.persistedRuleShadowed : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.decisionReason : undefined);
|
|
497
|
+
const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.persistedRuleShadowed : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.decisionReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeCause : undefined);
|
|
487
498
|
if (suspended) {
|
|
488
499
|
if ("parkFailed" in suspended)
|
|
489
500
|
parkFailed = suspended.parkFailed;
|
|
@@ -61,11 +61,15 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
61
61
|
/** Injected clock (opus 1.258 LOW: match the engine's `this.now` determinism discipline). */
|
|
62
62
|
private readonly now;
|
|
63
63
|
/**
|
|
64
|
-
* The COMMITTED-rev ledger (`revs.json`, id →
|
|
65
|
-
* entry files are the SAME files the session edits, so "read the current
|
|
66
|
-
* make every legitimate update self-conflict (the file already holds the
|
|
67
|
-
* records the rev as of the last COMMIT (applyPatches) — a CAS compares
|
|
68
|
-
* edit passes (baseRev == ledger) while a concurrently-committed
|
|
64
|
+
* The COMMITTED-rev ledger (`revs.json`, id → row; design/186 schema v2). Zero-copy makes it
|
|
65
|
+
* load-bearing for CAS: the entry files are the SAME files the session edits, so "read the current
|
|
66
|
+
* rev from the file" would make every legitimate update self-conflict (the file already holds the
|
|
67
|
+
* new content). Each row records the rev as of the last COMMIT (applyPatches) — a CAS compares
|
|
68
|
+
* against IT, so an in-session edit passes (baseRev == ledger) while a concurrently-committed
|
|
69
|
+
* change conflicts (ledger advanced) — PLUS the projection BINDING (owning scope + slug): bytes
|
|
70
|
+
* are model-plane material anyone can copy or relocate, so the account — model-invisible, outside
|
|
71
|
+
* every file-tool root — is what holds ownership. "rev matches" alone is no longer a serve
|
|
72
|
+
* credential; "rev matches AND the coordinates match the binding" is.
|
|
69
73
|
* Read-side (`listHeaders`/`getByIds`/`search`) SYNCS the ledger to disk THROUGH the inbound gate
|
|
70
74
|
* (B2 — out-of-session edits, e.g. a git pull into an in-repo memory dir, are secret/cap-scanned;
|
|
71
75
|
* rejects are quarantined + shadow-restored, never adopted); the applyPatches lookup does NOT sync
|
|
@@ -73,6 +77,33 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
73
77
|
* (fail-closed), never a silent empty-ledger downgrade.
|
|
74
78
|
*/
|
|
75
79
|
private ledger;
|
|
80
|
+
/** Top-level unknown keys of the loaded v2 envelope — write-preserved (§1.1). */
|
|
81
|
+
private envelopeExtras;
|
|
82
|
+
/**
|
|
83
|
+
* §3.1 (r3-④) — the migration retry judge: the PERSISTED schema form, set at every real disk read
|
|
84
|
+
* of the ledger and flipped only by the locked migration. Deliberately NOT derived from the
|
|
85
|
+
* in-memory ledger shape: the v1 compat arm upgrades rows in memory (§3.2), so the memory shape
|
|
86
|
+
* reads "already v2" after the first stand-down and the retry would die. `"v1"` arms the
|
|
87
|
+
* per-entry opportunistic retry (construction / reads / checkControlPlane / every locked
|
|
88
|
+
* mutation); undefined = the disk form has not been observed yet.
|
|
89
|
+
*/
|
|
90
|
+
private persistedSchemaVersion;
|
|
91
|
+
/** §3.1 — the v1-compat window is loud ONCE per instance (per-row unbound noise stays suppressed;
|
|
92
|
+
* the OVERALL state does not). */
|
|
93
|
+
private announcedV1Compat;
|
|
94
|
+
/** §3.4 — O(1) retry judge for the READ entries' unbound-convergence attempts: recomputed at
|
|
95
|
+
* every disk load and every persist (both already iterate the rows), never by scanning per read.
|
|
96
|
+
* Slightly stale-TRUE is harmless (one try-lock + one early-return reload); stale-FALSE cannot
|
|
97
|
+
* happen (every seat that changes the rows passes through a maintaining seat). While a blocker
|
|
98
|
+
* is genuinely stuck (a standing duplicate), each read pays one census — the same bounded cost
|
|
99
|
+
* the v1 read retry accepts for an incomplete store, in a state the sweeps warn about loudly. */
|
|
100
|
+
private unboundRowsKnown;
|
|
101
|
+
/** Adopting-face notice dedup (copy / undecidable-probe / ambiguous / census-incomplete): one
|
|
102
|
+
* still-pending state announces once per instance; changed bytes mint a new key and re-announce
|
|
103
|
+
* (same posture as the restricted finding keys, which are per-MOUNT — this face has no mount). */
|
|
104
|
+
private adoptionNoticeKeys;
|
|
105
|
+
/** Test seam (§7.4 r4-④ arm): force a post-commit-point transfer-append failure. */
|
|
106
|
+
private transfersAppendFault?;
|
|
76
107
|
/** B2 — inbound-gate findings accumulated by read-side syncs; the engine drains them into
|
|
77
108
|
* `HarvestReport.inboundFindings` at harvest. */
|
|
78
109
|
private inboundFindings;
|
|
@@ -94,8 +125,77 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
94
125
|
private shadowPath;
|
|
95
126
|
private loadLedger;
|
|
96
127
|
/** B3: atomic + fail-closed — a ledger that cannot be persisted throws (the commit is incomplete
|
|
97
|
-
* and the caller must know), never a silent swallow.
|
|
128
|
+
* and the caller must know), never a silent swallow. Always writes the v2 envelope; writing over
|
|
129
|
+
* an unmigrated v1 disk is refused loudly (mutations migrate FIRST — §3.1's "变异必先迁" is an
|
|
130
|
+
* invariant here, not a convention: a v2 write minus the census would strand every row unbound). */
|
|
98
131
|
private saveLedger;
|
|
132
|
+
/** v1-journal restore ONLY (§1.2 dual-read): the snapshot predates the upgrade and is persisted in
|
|
133
|
+
* ITS OWN form — the migration (which runs after recovery, under the lock) owns the v1→v2 swap
|
|
134
|
+
* together with its backup + evidence protocol. */
|
|
135
|
+
private persistLedgerV1;
|
|
136
|
+
/** Fail-open announcement convenience (S2-B queue posture — an unwritable queue never fails the
|
|
137
|
+
* operation that had something to say; the fail-CLOSED evidence lane is `transfers.jsonl`). */
|
|
138
|
+
private enqueueExternalItems;
|
|
139
|
+
/** One-shot dedup for adopting-face notices (see {@link adoptionNoticeKeys}). */
|
|
140
|
+
private announceAdoptionNotice;
|
|
141
|
+
/** Resolve a binding's absolute projection path WITHOUT registering anything (probes must stay
|
|
142
|
+
* side-effect-free; `scopeDir` would register an unknown scope name from a hand-edited row). */
|
|
143
|
+
private bindingAbsPath;
|
|
144
|
+
private bindingRelPath;
|
|
145
|
+
/**
|
|
146
|
+
* §3.3/§2.2 — the strict projection census: EVERY id-bearing file under EVERY registered scope,
|
|
147
|
+
* with `{complete}` honesty. Absence is ENOENT-only AT EVERY LEVEL — deliberately NOT
|
|
148
|
+
* `scanEntryFiles`, whose root arm folds an unreadable scope root into "empty scope" (`onSkip`
|
|
149
|
+
* fires for non-root only): copied here, one transient root EACCES would turn "only B was
|
|
150
|
+
* visible" into "only B exists" and mint a permanent wrong binding (bound rows are never
|
|
151
|
+
* re-derived). Symlinks / depth overflow / non-.md are exclusions by the entry-scan contract,
|
|
152
|
+
* not census gaps.
|
|
153
|
+
*/
|
|
154
|
+
private censusProjectionsLocked;
|
|
155
|
+
/** §3.6 — capture the pre-migration v1 bytes exactly once (`wx`: the first scene is never
|
|
156
|
+
* overwritten by a retry). */
|
|
157
|
+
private writeV1BackupOnce;
|
|
158
|
+
/** §3.1 — non-blocking txn-mutex acquisition for the opportunistic migration entries (the
|
|
159
|
+
* constructor cannot await). LIVE contention stands down — wait semantics belong to the async
|
|
160
|
+
* writers; every entry retries later. A STALE lock (past its deadline, owner token present) gets
|
|
161
|
+
* ONE synchronous single-winner steal, same judge and same token discipline as the async
|
|
162
|
+
* waiter's steal: without it, a crashed holder's leftover lock would stand down every sync entry
|
|
163
|
+
* forever, and a READ-ONLY deployment — which never runs an async writer to do the stealing —
|
|
164
|
+
* would sit in v1-compat indefinitely (the very suspension §3.1 bounds to seconds). Steal-safety
|
|
165
|
+
* is unchanged: the caller runs journal recovery INSIDE the acquired lock before touching state. */
|
|
166
|
+
private tryAcquireTxnLockSync;
|
|
167
|
+
/** §3.1 — one opportunistic migration attempt (read entries / construction / checkControlPlane):
|
|
168
|
+
* try-lock, recover, migrate, release. A busy lock is a clean stand-down (the mutation entries
|
|
169
|
+
* bootstrap the migration under their own lock regardless). */
|
|
170
|
+
private tryOpportunisticMigrate;
|
|
171
|
+
/**
|
|
172
|
+
* §3.3-§3.5 — the migration body. MUST hold the txn mutex. Reloads the DISK truth (the retry
|
|
173
|
+
* judge is {@link persistedSchemaVersion}, set by that reload — never the in-memory shape), then:
|
|
174
|
+
* v1 ⇒ full census-derived rebind + one-shot backup + journaled swap + one migration-summary
|
|
175
|
+
* evidence event; v2-with-unbound ⇒ re-derive the unbound rows only (bound rows are FINAL — only
|
|
176
|
+
* explicit transactions rewrite a binding), each new bind carrying a `migration-bind` evidence
|
|
177
|
+
* event. An INCOMPLETE census abandons the pass with ZERO writes ("only what was visible" is not
|
|
178
|
+
* "all that exists"; a wrong binding is worse than no binding) and retries at the next entry.
|
|
179
|
+
* P=0 rows stay unbound (「缺失≠删除」— dropping the row would endorse an unattributed delete);
|
|
180
|
+
* P≥2 rows stay unbound (the migration never adjudicates which projection is real — an existing
|
|
181
|
+
* dup audit keeps ringing until a harvest or a human resolves it). Idempotent: v2 + zero unbound
|
|
182
|
+
* is a no-op version check.
|
|
183
|
+
*/
|
|
184
|
+
private migrateLockedIfNeeded;
|
|
185
|
+
/** Read the evidence log's event ids, healing a TORN TAIL (a crash mid-append): the fragment is
|
|
186
|
+
* quarantined for evidence, the sound prefix stands (lines are self-contained — damage never
|
|
187
|
+
* spreads backward). Any NON-tail corruption is fail-closed (B3 family). */
|
|
188
|
+
private readTransferEvs;
|
|
189
|
+
/** §1.1 commit-point honesty: any failure BEFORE the journal write must refuse the whole
|
|
190
|
+
* transaction cleanly — so the append target's writability (and the log's soundness) is probed
|
|
191
|
+
* here, never discovered after the point of no return. */
|
|
192
|
+
private precheckTransfersAppendable;
|
|
193
|
+
/** Append evidence events, idempotent by `ev` (the recovery leg re-runs this for a journal whose
|
|
194
|
+
* append crashed). The ONLY failure class here is I/O — never capacity: a size threshold would
|
|
195
|
+
* freeze legitimate moves behind a full log, so growth is disclosed, not refused. A failure
|
|
196
|
+
* AFTER the commit point is COMMITTED-PENDING (the caller keeps the journal and throws — calling
|
|
197
|
+
* it "refused" would invite a retry of a transaction the next recovery completes anyway). */
|
|
198
|
+
private appendTransfers;
|
|
99
199
|
/**
|
|
100
200
|
* @param opts.unlocked RB-447 (2026-07-31, hardening review) — set by the two callers that run
|
|
101
201
|
* WITHOUT the txn mutex (the constructor and {@link checkControlPlane}). `applyPatches` serializes
|
|
@@ -120,7 +220,9 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
120
220
|
/** RB-447 — is another writer mid-transaction right now? True while the txn lock dir exists and is
|
|
121
221
|
* not past its stale line (a stale lock is a crashed holder: its artifacts ARE ours to recover).
|
|
122
222
|
* Only consulted by the two UNLOCKED callers — inside `applyPatches` the lock is ours by
|
|
123
|
-
* construction. Cheap: one `stat` on the lock dir, no lock acquisition, never blocks.
|
|
223
|
+
* construction. Cheap: one `stat` on the lock dir, no lock acquisition, never blocks.
|
|
224
|
+
* §3.1 ⓪ (r5-①): "no lock" is ENOENT-only — a stat FAILURE folded into "nobody is committing"
|
|
225
|
+
* let a transient EIO run the unlocked recovery (and its staged sweep) over a live transaction. */
|
|
124
226
|
private txnInFlight;
|
|
125
227
|
/** Remove orphaned `.stage-*` files (a crash BEFORE the journal rename = clean rollback: nothing
|
|
126
228
|
* was committed, the staged tmps are litter). Dotfiles are invisible to entry scans regardless. */
|
|
@@ -152,11 +254,46 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
152
254
|
* Ledger-write discipline: the sync=true adoption path WRITES `revs.json`, and that write is
|
|
153
255
|
* wholesale (atomic replace) — unserialized against applyPatches' transaction it is a
|
|
154
256
|
* lost update (the later writer drops every row the earlier one added). sync=true is therefore
|
|
155
|
-
* reachable ONLY through {@link readScopeAdopting}: `probeOnly` runs the scan with NO
|
|
156
|
-
* side effects and returns undefined at the FIRST
|
|
157
|
-
* caller re-runs the scan under the txn mutex against
|
|
158
|
-
* probe pass returns the entries
|
|
257
|
+
* reachable ONLY through {@link readScopeAdopting}: `probeOnly` runs the scan with NO ledger
|
|
258
|
+
* side effects and returns undefined at the FIRST entry that needs an ACCOUNT transaction (an
|
|
259
|
+
* edit adoption, or a move candidate — the caller re-runs the scan under the txn mutex against
|
|
260
|
+
* a freshly reloaded ledger); a completed probe pass returns the entries. A COPY or an
|
|
261
|
+
* undecidable binding probe needs no transaction (withheld + announced, account untouched), so
|
|
262
|
+
* the probe classifies those inline and keeps going — a store with a lingering copy never
|
|
263
|
+
* contends for the writer lock on every read. */
|
|
159
264
|
private readScope;
|
|
265
|
+
/**
|
|
266
|
+
* design-A RB-5 ([A3]) — the read-side whitewash gate, shared by the bound-here divergence lane
|
|
267
|
+
* and the move+edit lane so the two can never drift: an out-of-session disk change (git pull /
|
|
268
|
+
* hand edit) of a repo-ingested entry that strips `provenance`, or strips a present `trust`
|
|
269
|
+
* marker, is the same whitewash applyPatches' I-2 clause refuses — adoption must not be the
|
|
270
|
+
* laundering back door. Baseline = the COMMITTED state (control-plane shadow; the disk file is
|
|
271
|
+
* the very thing under suspicion). A shadow-less legacy entry has no independent baseline, so the
|
|
272
|
+
* gate cannot fire for it — but every adoption/commit backfills the shadow, closing that window
|
|
273
|
+
* on first contact. `containment` keeps the reason honest per lane (the move lane quarantines
|
|
274
|
+
* WITHOUT restoring at the never-bound path).
|
|
275
|
+
*/
|
|
276
|
+
private whitewashInboundFinding;
|
|
277
|
+
/**
|
|
278
|
+
* §2.2/§2.3 — one MOVE candidate under the txn mutex: a committed entry's bytes at non-bound
|
|
279
|
+
* coordinates whose bound projection is CONFIRMED absent (ENOENT, probed under this same lock).
|
|
280
|
+
* Adoption needs all three conditions — candidate present + bound path ENOENT + store-wide
|
|
281
|
+
* uniqueness through a census that proves its own completeness: with B and C both planted,
|
|
282
|
+
* single-condition ENOENT would let READ ORDER pick the owner; with a half census, "only B was
|
|
283
|
+
* visible" is not "only B exists" (r4-③/r5-②). Gate-clean adoption is an EXPLICIT transaction:
|
|
284
|
+
* row rebind + `prev` trace in one atomic ledger write, an `adopted-move` evidence event in the
|
|
285
|
+
* same journaled commit, and a loud announcement — external move/rename is the writeScope-null
|
|
286
|
+
* plane's PRIMARY legitimate input channel (git pull), so it is upgraded to a traced transaction,
|
|
287
|
+
* never refused wholesale. Candid boundary (D-1, ruled): a byte-identical delete-then-plant is
|
|
288
|
+
* THIS SAME disk observation — the adoption is a TRACED acceptance, not a rejection; what v2
|
|
289
|
+
* removes is the delete's invisibility (missing now rings), the relocation's tracelessness
|
|
290
|
+
* (transfers row + prev), and the silence. Out-of-band writers do not take this lock, so a
|
|
291
|
+
* TOCTOU window between the ENOENT re-verification and the commit remains for an adversary that
|
|
292
|
+
* can recreate the original mid-window — that actor is the delete-then-plant actor (same candid
|
|
293
|
+
* boundary), not a new capability; the tri-state probe closes FAULT misjudgment, and claims no
|
|
294
|
+
* more. Returns the entry to serve, or undefined (refused/deferred this pass).
|
|
295
|
+
*/
|
|
296
|
+
private adoptMoveCandidateLocked;
|
|
160
297
|
/**
|
|
161
298
|
* The adopting read: {@link readScope} sync=true with its ledger write serialized behind the
|
|
162
299
|
* SAME cross-process txn mutex applyPatches holds (acquisition/steal/timeout semantics shared,
|
|
@@ -208,12 +345,15 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
208
345
|
* WRITES ARE REFUSED, loudly — a view handed to a write path is a defect, not a fallback.
|
|
209
346
|
*/
|
|
210
347
|
retrievalView(): MemoryBackend;
|
|
211
|
-
/** Dedup keys for divergence findings the restricted view already recorded
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* lifecycle
|
|
216
|
-
*
|
|
348
|
+
/** Dedup keys for divergence findings the restricted view already recorded: one materialize reads
|
|
349
|
+
* a scope several times (listHeaders, then getByIds per scope), and a checkpoint-cadence harvest
|
|
350
|
+
* re-audits the same still-pending state — without the key, one untouched divergence would mint
|
|
351
|
+
* a finding per read. Per AUDITED MOUNT (design/186 §6): the keys live and die with the mount,
|
|
352
|
+
* same lifecycle as the id-less scope/baseline pair — one mount discloses a still-pending
|
|
353
|
+
* divergence once, and the NEXT audited mount (a later restricted session in this process, or a
|
|
354
|
+
* cross-process resume) re-discloses once, which is the desired "still pending" notice; the
|
|
355
|
+
* in-process and cross-process postures agree. A divergence whose bytes CHANGE mints a new key
|
|
356
|
+
* and is reported again within the mount. */
|
|
217
357
|
private restrictedFindingKeys;
|
|
218
358
|
private recordRestrictedFinding;
|
|
219
359
|
/** Id-less audit state for an adoption-restricted session's WRITE scope. Armed only by the
|
|
@@ -262,7 +402,11 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
262
402
|
private readScopeCommitted;
|
|
263
403
|
/** Does `id` have a projection file anywhere in the store OTHER than `excludeAbsPath`? Returns
|
|
264
404
|
* that projection's rel path, or undefined. Early-exits on the first hit; only consulted on the
|
|
265
|
-
* rare divergent-with-shadow path (the scope-binding guard above)
|
|
405
|
+
* rare divergent-with-shadow path (the scope-binding guard above) for UNBOUND rows — bound rows
|
|
406
|
+
* answer the same question in O(1) from the account binding (design/186 §2.4). RETIREMENT
|
|
407
|
+
* CONDITION: unbound rows are a migration transitional; when they are gone this scan form has no
|
|
408
|
+
* caller left and retires WITH them (delete the function, not just the call — a dead scan arm is
|
|
409
|
+
* the residue class the retirement registry exists for). */
|
|
266
410
|
private idProjectionElsewhere;
|
|
267
411
|
/**
|
|
268
412
|
* The ADOPTION-RESTRICTED projection of this backend (see {@link readScopeCommitted}) — what a
|
|
@@ -287,9 +431,13 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
287
431
|
* over the session's scopes — bytes landed AFTER materialize (a mid-session shell write) are
|
|
288
432
|
* detected here, at the very boundary where an unrestricted harvest would have admitted them —
|
|
289
433
|
* then check the committed account for entries MISSING from disk entirely (an unattributed delete
|
|
290
|
-
* is divergence too).
|
|
291
|
-
*
|
|
292
|
-
*
|
|
434
|
+
* is divergence too). BOUND rows (design/186 §2.2) are judged AT THEIR COMMITTED PROJECTION: a
|
|
435
|
+
* single-copy move / delete-then-plant empties the bound address and rings here even though the
|
|
436
|
+
* id is still "somewhere" (the v1 any-scope presence test read exactly that as fine — the
|
|
437
|
+
* registered residual this schema retires); byte-carriers of the id at other addresses are named
|
|
438
|
+
* in the same finding. UNBOUND rows (migration transitional) keep the v1 store-wide presence
|
|
439
|
+
* test — no false alarms while their address is underivable. Non-destructive throughout: nothing
|
|
440
|
+
* is restored, forgotten, or adopted here.
|
|
293
441
|
*
|
|
294
442
|
* `opts.idlessWriteScope` is the CALLER's per-call session verdict (same posture as the mount:
|
|
295
443
|
* the backend never guesses): when the audited session HAS a write scope, id-less files under it
|
|
@@ -345,7 +493,9 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
345
493
|
private assertTxnLockOwnership;
|
|
346
494
|
private applyPatchesLocked;
|
|
347
495
|
/** Plan one patch: decide CAS/conflicts against CURRENT state, emit journal ops (no entry-file
|
|
348
|
-
* writes here — staging/execution happen in {@link applyPatches}).
|
|
496
|
+
* writes here — staging/execution happen in {@link applyPatches}). Every row write is BOUND
|
|
497
|
+
* (§1.1) at the coordinates the batch ACTUALLY lands — a slug collision's `-n` suffix binds the
|
|
498
|
+
* suffixed slug, the same one `applied[].slug` reports (v1's ledger had no landing awareness). */
|
|
349
499
|
private planOne;
|
|
350
500
|
private locateById;
|
|
351
501
|
getConsolidationCursor(scope: string): Promise<string | undefined>;
|