@sema-agent/core 7.43.0 → 7.45.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 +245 -0
- package/dist/agents/subagent.d.ts +2 -1
- package/dist/agents/subagent.js +33 -14
- package/dist/agents/tool-filter.d.ts +65 -4
- package/dist/agents/tool-filter.js +127 -4
- package/dist/brain/errors.d.ts +23 -2
- package/dist/brain/errors.js +26 -2
- package/dist/brain/stream-engine.d.ts +2 -1
- package/dist/brain/stream-engine.js +10 -9
- package/dist/core/agent-definition.d.ts +2 -2
- package/dist/core/allow-entry.d.ts +25 -1
- package/dist/core/allow-entry.js +29 -4
- package/dist/core/ask-origin.d.ts +23 -4
- package/dist/core/ask-origin.js +10 -4
- package/dist/core/checkpoint-store.d.ts +28 -15
- package/dist/core/checkpoint-store.js +4 -13
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/effective-path-target.d.ts +7 -6
- package/dist/core/effective-path-target.js +3 -7
- package/dist/core/engine-notice.d.ts +33 -14
- package/dist/core/engine-notice.js +14 -7
- package/dist/core/gate-lanes.js +2 -0
- package/dist/core/gate-outcome.d.ts +5 -0
- package/dist/core/gate-outcome.js +2 -0
- package/dist/core/governance-codes.d.ts +4 -3
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/hooks.d.ts +2 -0
- package/dist/core/memory-engine/content-origin.d.ts +0 -34
- package/dist/core/memory-engine/content-origin.js +9 -4
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/memory-engine/engine.js +7 -14
- package/dist/core/memory-engine/file-backend.d.ts +2 -2
- package/dist/core/memory-engine/file-backend.js +5 -7
- package/dist/core/memory-engine/layout.d.ts +41 -4
- package/dist/core/memory-engine/layout.js +35 -5
- package/dist/core/permission-rules.d.ts +4 -0
- package/dist/core/permission-rules.js +5 -2
- package/dist/core/real-approval.d.ts +9 -6
- package/dist/core/real-approval.js +5 -5
- package/dist/core/runner/child-tree.d.ts +13 -0
- package/dist/core/runner/child-tree.js +3 -0
- package/dist/core/runner/contracts.d.ts +9 -18
- package/dist/core/runner/prepare-ask-lane.d.ts +2 -0
- package/dist/core/runner/prepare-ask-lane.js +2 -2
- package/dist/core/runner/prepare-config-doors.js +3 -1
- package/dist/core/runner/prepare-execution-env.js +2 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
- package/dist/core/runner/prepare-gate-stations.js +2 -1
- package/dist/core/runner/prepare-listings.d.ts +4 -4
- package/dist/core/runner/prepare-listings.js +2 -22
- package/dist/core/runner/prepare-park-ask.d.ts +2 -0
- package/dist/core/runner/prepare-park-ask.js +2 -2
- package/dist/core/runner/prepare-policy-chain.d.ts +2 -0
- package/dist/core/runner/prepare-policy-chain.js +4 -2
- package/dist/core/runner/prepare-task.js +10 -6
- package/dist/core/runner/resume-policy-outcome.js +2 -1
- package/dist/core/runner/run-leg.js +1 -1
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/runtask.d.ts +2 -0
- package/dist/core/runner/runtask.js +3 -5
- package/dist/core/runner/steer-admission.d.ts +10 -11
- package/dist/core/runner/steer-admission.js +6 -8
- package/dist/core/runner/stream-steer-verb.d.ts +5 -4
- package/dist/core/runner/stream-steer-verb.js +8 -10
- package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
- package/dist/core/runner/sub-agent-deny-lists.js +121 -0
- package/dist/core/runner/synthetic-tools.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.js +3 -2
- package/dist/core/session-policy-store.d.ts +6 -2
- package/dist/core/session-policy-store.js +2 -1
- package/dist/core/skill-grant.d.ts +10 -2
- package/dist/core/skill-grant.js +20 -4
- package/dist/core/steer-replay.d.ts +46 -0
- package/dist/core/steer-replay.js +22 -0
- package/dist/core/task-event.d.ts +3 -2
- package/dist/core/task-notification.d.ts +12 -1
- package/dist/core/task-notification.js +13 -1
- package/dist/core/task-spec.d.ts +5 -3
- package/dist/core/task-stream.d.ts +12 -6
- package/dist/core/tool-errors.d.ts +2 -2
- package/dist/core/tool-policy.d.ts +15 -4
- package/dist/core/tool-policy.js +9 -13
- package/dist/core/tool-spec.d.ts +20 -2
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +7 -4
- package/dist/orchestration/workflow-types.d.ts +5 -0
- package/dist/orchestration/workflow.d.ts +3 -2
- package/dist/orchestration/workflow.js +16 -7
- package/dist/tools/fs/removal-targets.d.ts +82 -0
- package/dist/tools/fs/removal-targets.js +236 -0
- package/dist/tools/fs/shell-read-write-targets.d.ts +27 -0
- package/dist/tools/fs/shell-read-write-targets.js +1 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +43 -5
- package/wire-changes/core@7.44.0.json +469 -0
- package/wire-changes/core@7.45.0.json +1084 -0
package/dist/core/ask-origin.js
CHANGED
|
@@ -170,6 +170,7 @@ export const ASK_CLASSES = Object.freeze([
|
|
|
170
170
|
"content_question",
|
|
171
171
|
"hook",
|
|
172
172
|
"ancestor",
|
|
173
|
+
"dangerous_delete",
|
|
173
174
|
"unverifiable_delete",
|
|
174
175
|
"transcript_write",
|
|
175
176
|
"org_rule",
|
|
@@ -200,6 +201,7 @@ export const MODE_ARBITRATION = {
|
|
|
200
201
|
content_question: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
201
202
|
hook: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
202
203
|
ancestor: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
204
|
+
dangerous_delete: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
203
205
|
unverifiable_delete: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
204
206
|
transcript_write: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
205
207
|
org_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
@@ -222,7 +224,8 @@ export const MODE_ARBITRATION = {
|
|
|
222
224
|
unstated: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: true, hookAskFloor: false },
|
|
223
225
|
ordinary: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: true, hookAskFloor: false },
|
|
224
226
|
};
|
|
225
|
-
|
|
227
|
+
const CLASSIFIER_WITHHELD_ON_ROOTS = new Set(["probe"]);
|
|
228
|
+
export const RRA_EXPLAINERS = new Set(["dangerous_delete", "unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "denial_limit_fallback", "classifier_no_ruling", "ancestor", "artifact_shared_read", "requires_user_interaction"]);
|
|
226
229
|
export const RELEASABLE_ASK_CLASSES = ASK_CLASSES.filter((c) => !MODE_ARBITRATION[c].bypassImmune);
|
|
227
230
|
export const MODE_RELEASE_REASONS = Object.freeze(["rule_unreadable"]);
|
|
228
231
|
const MODE_RELEASE_REASON_SET = new Set(MODE_RELEASE_REASONS);
|
|
@@ -243,6 +246,8 @@ export function screenAskClassMembers(askClass, classes) {
|
|
|
243
246
|
}
|
|
244
247
|
export function decisionAskClassesOf(d) {
|
|
245
248
|
const out = new Set();
|
|
249
|
+
if (d.guard === "dangerous_delete")
|
|
250
|
+
out.add("dangerous_delete");
|
|
246
251
|
if (d.guard === "unverifiable_delete")
|
|
247
252
|
out.add("unverifiable_delete");
|
|
248
253
|
if (d.guard === "transcript_write")
|
|
@@ -273,10 +278,11 @@ export function decisionAskClassesOf(d) {
|
|
|
273
278
|
out.add(c);
|
|
274
279
|
return out;
|
|
275
280
|
}
|
|
276
|
-
export function foldArbitrationRow(classes) {
|
|
281
|
+
export function foldArbitrationRow(classes, readFace) {
|
|
277
282
|
if (classes.size === 0)
|
|
278
283
|
return MODE_ARBITRATION.ordinary;
|
|
279
|
-
|
|
284
|
+
const withheld = readFace === "roots" && [...classes].some((c) => CLASSIFIER_WITHHELD_ON_ROOTS.has(c));
|
|
285
|
+
let bypassImmune = false, classifierEligible = !withheld, sandboxAdmission = true, ruleMayClear = true, hookAskFloor = false;
|
|
280
286
|
for (const c of classes) {
|
|
281
287
|
const row = MODE_ARBITRATION[c];
|
|
282
288
|
bypassImmune ||= row.bypassImmune;
|
|
@@ -334,7 +340,7 @@ export function classifyAsk(d, facts) {
|
|
|
334
340
|
if (classes.size === 0)
|
|
335
341
|
classes.add("ordinary");
|
|
336
342
|
const askClass = ASK_CLASSES.find((c) => classes.has(c));
|
|
337
|
-
return { classes, askClass, row: foldArbitrationRow(classes) };
|
|
343
|
+
return { classes, askClass, row: foldArbitrationRow(classes, facts.readFace) };
|
|
338
344
|
}
|
|
339
345
|
export function headlessDenialMessage(toolName, askMessage) {
|
|
340
346
|
return `Permission for this tool use was denied: it requires interactive approval, and permission prompts are not available in this session (nobody can be asked to approve "${toolName}" in this run — headless auto-deny). The action was NOT performed. Do not claim it succeeded, and do not retry it in this session — report the limitation to the user, or suggest an alternative. What was requested: ${askMessage}`;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { GateOutcome } from "./gate-outcome.js";
|
|
1
|
+
import type { GateOutcome, HostDecider } from "./gate-outcome.js";
|
|
2
2
|
import { type ExecutionOutcomeRecordWord } from "./checkpoint-execution-record.js";
|
|
3
3
|
import type { CheckpointPause } from "./pause-registry.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { SystemInjectionPriority } from "./task-notification.js";
|
|
5
|
+
import { type SteerReplayFacet } from "./steer-replay.js";
|
|
5
6
|
import { type QuestionAnswer } from "./ask-question.js";
|
|
6
7
|
import type { ReadEntry } from "../tools/fs/safety.js";
|
|
7
8
|
import type { RepairBundle } from "../agents/repair-loop.js";
|
|
@@ -624,7 +625,7 @@ export type ResumeOutcome = {
|
|
|
624
625
|
* authorization; a replay states its own facts.
|
|
625
626
|
*/
|
|
626
627
|
hostDecision: {
|
|
627
|
-
decidedBy:
|
|
628
|
+
decidedBy: HostDecider;
|
|
628
629
|
approver?: string;
|
|
629
630
|
};
|
|
630
631
|
/**
|
|
@@ -2130,10 +2131,12 @@ export declare class CheckpointError extends Error {
|
|
|
2130
2131
|
* the caller delivers or removes a parked steer first. Nothing is written on this path. */
|
|
2131
2132
|
| "steering.queue_full"
|
|
2132
2133
|
/** design/171 §6.3 (falsification review adoption): the append reused an `inputId` already parked on
|
|
2133
|
-
* this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL
|
|
2134
|
-
*
|
|
2135
|
-
*
|
|
2136
|
-
*
|
|
2134
|
+
* this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL steer stays the idempotent
|
|
2135
|
+
* no-op a retry needs; a differing one is refused, because swallowing it would lose the second
|
|
2136
|
+
* operator's instruction — the exact silent loss the queue replaced. `detail.differs` names the facets
|
|
2137
|
+
* that differ: with `text` among them it is another instruction (retry it under a fresh id); without,
|
|
2138
|
+
* it is the parked one re-sent at another tier / trust / attribution (a fresh id would park it twice —
|
|
2139
|
+
* replay it with its original values). Nothing is written on this path. */
|
|
2137
2140
|
| "steering.duplicate_input_id"
|
|
2138
2141
|
/** design/373 §4.3 (D2): the deployment's `userPromptSubmit` hook refused a wake resume's
|
|
2139
2142
|
* `message` at the entrance screen (block verdict, or a fail-closed timeout/crash — the
|
|
@@ -2217,8 +2220,8 @@ export declare class CheckpointError extends Error {
|
|
|
2217
2220
|
* computable from a self-reported closure, so the edited resume is refused typed pre-CAS (the
|
|
2218
2221
|
* checkpoint stays `pending`, still decidable); an UNEDITED approval is untouched. */
|
|
2219
2222
|
| "resume.constraint_unprojectable";
|
|
2220
|
-
/** Structured discrimination a caller can branch on where the code alone is ambiguous.
|
|
2221
|
-
*
|
|
2223
|
+
/** Structured discrimination a caller can branch on where the code alone is ambiguous. `field`
|
|
2224
|
+
* names WHICH part of the decision payload was rejected on a
|
|
2222
2225
|
* `checkpoint.invalid_outcome` — `"boundCallId"` (the action you decided on has been replaced:
|
|
2223
2226
|
* re-fetch the pending list), `"boundInputHash"` (the input you reviewed has changed under the same
|
|
2224
2227
|
* action: re-review), `"boundGrantDigest"` (a parked `Skill` approval does not cover the skill the decision selects — another skill, or the card's with other lists: deny, re-activate), or `"answer"` (the content-ask answer is missing, or was attached to a
|
|
@@ -2248,6 +2251,10 @@ export declare class CheckpointError extends Error {
|
|
|
2248
2251
|
* (`code` remains the only REQUIRED discriminant), which is exactly why the obligation to SPELL it
|
|
2249
2252
|
* is a gate rather than a type: see the closed set's own doc. */
|
|
2250
2253
|
reason?: CheckpointErrorReason;
|
|
2254
|
+
/** On `steering.duplicate_input_id` — the facets the repeat differs from the parked steer in (closed
|
|
2255
|
+
* {@link SteerReplayFacet}, in that set's order; always present on that code, never empty). Absent on
|
|
2256
|
+
* every other code. The live verb's refusal carries the same value. */
|
|
2257
|
+
differs?: readonly SteerReplayFacet[];
|
|
2251
2258
|
} | undefined;
|
|
2252
2259
|
constructor(code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found"
|
|
2253
2260
|
/** `runner.resume` was handed an {@link ResumeOutcome} whose `gate` arm does not match the
|
|
@@ -2322,10 +2329,12 @@ export declare class CheckpointError extends Error {
|
|
|
2322
2329
|
* the caller delivers or removes a parked steer first. Nothing is written on this path. */
|
|
2323
2330
|
| "steering.queue_full"
|
|
2324
2331
|
/** design/171 §6.3 (falsification review adoption): the append reused an `inputId` already parked on
|
|
2325
|
-
* this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL
|
|
2326
|
-
*
|
|
2327
|
-
*
|
|
2328
|
-
*
|
|
2332
|
+
* this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL steer stays the idempotent
|
|
2333
|
+
* no-op a retry needs; a differing one is refused, because swallowing it would lose the second
|
|
2334
|
+
* operator's instruction — the exact silent loss the queue replaced. `detail.differs` names the facets
|
|
2335
|
+
* that differ: with `text` among them it is another instruction (retry it under a fresh id); without,
|
|
2336
|
+
* it is the parked one re-sent at another tier / trust / attribution (a fresh id would park it twice —
|
|
2337
|
+
* replay it with its original values). Nothing is written on this path. */
|
|
2329
2338
|
| "steering.duplicate_input_id"
|
|
2330
2339
|
/** design/373 §4.3 (D2): the deployment's `userPromptSubmit` hook refused a wake resume's
|
|
2331
2340
|
* `message` at the entrance screen (block verdict, or a fail-closed timeout/crash — the
|
|
@@ -2409,8 +2418,8 @@ export declare class CheckpointError extends Error {
|
|
|
2409
2418
|
* computable from a self-reported closure, so the edited resume is refused typed pre-CAS (the
|
|
2410
2419
|
* checkpoint stays `pending`, still decidable); an UNEDITED approval is untouched. */
|
|
2411
2420
|
| "resume.constraint_unprojectable", message: string,
|
|
2412
|
-
/** Structured discrimination a caller can branch on where the code alone is ambiguous.
|
|
2413
|
-
*
|
|
2421
|
+
/** Structured discrimination a caller can branch on where the code alone is ambiguous. `field`
|
|
2422
|
+
* names WHICH part of the decision payload was rejected on a
|
|
2414
2423
|
* `checkpoint.invalid_outcome` — `"boundCallId"` (the action you decided on has been replaced:
|
|
2415
2424
|
* re-fetch the pending list), `"boundInputHash"` (the input you reviewed has changed under the same
|
|
2416
2425
|
* action: re-review), `"boundGrantDigest"` (a parked `Skill` approval does not cover the skill the decision selects — another skill, or the card's with other lists: deny, re-activate), or `"answer"` (the content-ask answer is missing, or was attached to a
|
|
@@ -2440,6 +2449,10 @@ export declare class CheckpointError extends Error {
|
|
|
2440
2449
|
* (`code` remains the only REQUIRED discriminant), which is exactly why the obligation to SPELL it
|
|
2441
2450
|
* is a gate rather than a type: see the closed set's own doc. */
|
|
2442
2451
|
reason?: CheckpointErrorReason;
|
|
2452
|
+
/** On `steering.duplicate_input_id` — the facets the repeat differs from the parked steer in (closed
|
|
2453
|
+
* {@link SteerReplayFacet}, in that set's order; always present on that code, never empty). Absent on
|
|
2454
|
+
* every other code. The live verb's refusal carries the same value. */
|
|
2455
|
+
differs?: readonly SteerReplayFacet[];
|
|
2443
2456
|
} | undefined);
|
|
2444
2457
|
}
|
|
2445
2458
|
/**
|
|
@@ -8,7 +8,7 @@ import { readBoundBinding } from "./skill-grant-binding.js";
|
|
|
8
8
|
import { checkpointExecutionRecorded, executionRecordDisposition, ExecutionOutcomeConflictError, } from "./checkpoint-execution-record.js";
|
|
9
9
|
import { renderUntrustedCommandText, stripFormatCharacters } from "./permission-rule-model.js";
|
|
10
10
|
import { redactSecrets } from "./untrusted-egress.js";
|
|
11
|
-
import {
|
|
11
|
+
import { steerReplayRefusal } from "./steer-replay.js";
|
|
12
12
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
13
13
|
import { isPermissionMode } from "./ask-origin.js";
|
|
14
14
|
import { wireOf } from "./tool-name-readings.js";
|
|
@@ -548,22 +548,13 @@ export function readPendingSteerQueue(state) {
|
|
|
548
548
|
}
|
|
549
549
|
return [...(state.pendingSteerQueue ?? [])].sort((a, b) => a.seq - b.seq);
|
|
550
550
|
}
|
|
551
|
-
function samePendingSteerPayload(a, b) {
|
|
552
|
-
return (a.text === b.text &&
|
|
553
|
-
a.trusted === b.trusted &&
|
|
554
|
-
steerPriorityOf(a) === steerPriorityOf(b) &&
|
|
555
|
-
a.actor?.id === b.actor?.id &&
|
|
556
|
-
a.actor?.hostAsserted === b.actor?.hostAsserted &&
|
|
557
|
-
a.actor?.issuer === b.actor?.issuer);
|
|
558
|
-
}
|
|
559
551
|
export function appendPendingSteer(state, entry) {
|
|
560
552
|
const current = readPendingSteerQueue(state);
|
|
561
553
|
const collision = current.find((e) => e.inputId === entry.inputId);
|
|
562
554
|
if (collision !== undefined) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
555
|
+
const refusal = steerReplayRefusal(collision, entry, { seat: "parked", inputId: entry.inputId });
|
|
556
|
+
if (refusal !== undefined)
|
|
557
|
+
throw new CheckpointError("steering.duplicate_input_id", refusal.message, refusal.detail);
|
|
567
558
|
return current;
|
|
568
559
|
}
|
|
569
560
|
const nextSeq = current.reduce((max, e) => Math.max(max, e.seq), 0) + 1;
|
|
@@ -11,7 +11,7 @@ export const refusedOutOfContract = (message) => {
|
|
|
11
11
|
export const isEngineRefusal = (d) => typeof d === "object" && d !== null && ENGINE_REFUSALS.has(d);
|
|
12
12
|
const DECISION_REASONS = Object.freeze(["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable", "read_only", "skill", "deployment_rule", "request_rule"]);
|
|
13
13
|
const DECISION_ACTIONS = Object.keys({ allow: 0, deny: 0, ask: 0 });
|
|
14
|
-
const GUARDS = Object.keys({ unverifiable_delete: 0, transcript_write: 0, artifact_shared_read: 0 });
|
|
14
|
+
const GUARDS = Object.keys({ dangerous_delete: 0, unverifiable_delete: 0, transcript_write: 0, artifact_shared_read: 0 });
|
|
15
15
|
const RULE_UNREADABLE_WORDS = Object.keys({ content_deny: 0, content_ask: 0, persisted_call: 0 });
|
|
16
16
|
const AUTHORED_BY_WORDS = Object.keys({ deployment: 0 });
|
|
17
17
|
const memberOf = (words) => {
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
*
|
|
29
29
|
* Nothing here judges anything: it answers "which absolute path is this call about". Containment, the
|
|
30
30
|
* sensitive-path deny face and the rule precedence walk are unchanged and still run where they ran.
|
|
31
|
-
* `undefined` means "this call
|
|
32
|
-
*
|
|
31
|
+
* `undefined` means "this call names no path" (no declaration, a non-string slot, an absent slot that targets
|
|
32
|
+
* nothing); a path the call does name whose base this reading lacks is `unreadable` — never a guess, never silence.
|
|
33
33
|
*
|
|
34
34
|
* RepoMap shares the search tools' root-relative `path` semantics but declares NO path target at all (no
|
|
35
35
|
* rule family reaches it, and the rule grammar is derived from the catalog's path targets), so it has no
|
|
@@ -71,8 +71,8 @@ export declare function splitAbsoluteGlobPattern(pattern: string, family: PathFa
|
|
|
71
71
|
} | undefined;
|
|
72
72
|
/**
|
|
73
73
|
* The reading of a call's path slot under ONE declaration: `path` = the absolute target's identity; `unreadable`
|
|
74
|
-
* = the slot spells a target but the base it needs is not a usable base of
|
|
75
|
-
* only, as the ask a person clears — never to the allow arm); `refused` = a namespace refusal of the
|
|
74
|
+
* = the slot spells a target (or targets its base) but the base it needs is not supplied or not a usable base of
|
|
75
|
+
* this tree (fed to TIGHTENING rows only, as the ask a person clears — never to the allow arm); `refused` = a namespace refusal of the
|
|
76
76
|
* spelling itself (a rule speaks about no such spelling: no verdict, no ask — the fs throat delivers the
|
|
77
77
|
* refusal with the same word); `undefined` = no target in this reading.
|
|
78
78
|
*/
|
|
@@ -97,8 +97,9 @@ export type PathTargetReading = {
|
|
|
97
97
|
* ③ otherwise a `patternParam`'s ABSOLUTE pattern supplies its own base directory;
|
|
98
98
|
* ④ otherwise the base itself (the tool searches all of it).
|
|
99
99
|
*
|
|
100
|
-
* A base the
|
|
101
|
-
*
|
|
100
|
+
* A base the caller did not supply yields `unreadable`: a relative spelling with nothing to resolve against
|
|
101
|
+
* names a file this reading cannot locate — claiming it names `/<spelling>` would be a fabrication, and
|
|
102
|
+
* claiming it names nothing would let every rule about that file stay silent.
|
|
102
103
|
*/
|
|
103
104
|
export declare function effectivePathTargetOf(call: ToolCallLike, pt: ToolCallPathTarget | undefined, bases: ToolPathBases): PathTargetReading;
|
|
104
105
|
/**
|
|
@@ -31,10 +31,8 @@ export function effectivePathTargetOf(call, pt, bases) {
|
|
|
31
31
|
if (spelling.spelled !== undefined)
|
|
32
32
|
return resolveSpelling(spelling.spelled, spelling.base, bases);
|
|
33
33
|
const declaredBase = spelling.base;
|
|
34
|
-
if (declaredBase === undefined)
|
|
35
|
-
return undefined;
|
|
36
34
|
const family = pathFamilyOf(bases);
|
|
37
|
-
return isAbsoluteForFamily(family, declaredBase) ? readingOf(pathIdentityOf(declaredBase, family, bases.root ?? bases.cwd)) : unreadableBase(declaredBase);
|
|
35
|
+
return declaredBase !== undefined && isAbsoluteForFamily(family, declaredBase) ? readingOf(pathIdentityOf(declaredBase, family, bases.root ?? bases.cwd)) : unreadableBase(declaredBase);
|
|
38
36
|
}
|
|
39
37
|
export function effectivePathSpellingOf(call, pt, bases) {
|
|
40
38
|
if (pt === undefined)
|
|
@@ -63,9 +61,7 @@ function resolveSpelling(spelled, declaredBase, bases) {
|
|
|
63
61
|
const tree = bases.root ?? bases.cwd;
|
|
64
62
|
if (isAbsoluteForFamily(family, screened.spelled))
|
|
65
63
|
return readingOf(pathIdentityOf(screened.spelled, family, tree));
|
|
66
|
-
if (declaredBase === undefined || family === undefined)
|
|
67
|
-
return undefined;
|
|
68
|
-
if (!isAbsoluteForFamily(family, declaredBase))
|
|
64
|
+
if (declaredBase === undefined || family === undefined || !isAbsoluteForFamily(family, declaredBase))
|
|
69
65
|
return unreadableBase(declaredBase);
|
|
70
66
|
return readingOf(pathIdentityOf(joinForFamily(family, declaredBase, screened.spelled), family, tree));
|
|
71
67
|
}
|
|
@@ -75,5 +71,5 @@ function readingOf(r) {
|
|
|
75
71
|
return r.reason === "relative" || r.reason === "unanchored" ? undefined : { kind: "refused", reason: r.reason };
|
|
76
72
|
}
|
|
77
73
|
function unreadableBase(base) {
|
|
78
|
-
return { kind: "unreadable", reason: `the base this call resolves against (${JSON.stringify(base)}) is not a fully-qualified path of this tree's family` };
|
|
74
|
+
return { kind: "unreadable", reason: base === undefined ? "the call's path is relative to a base that was not supplied" : `the base this call resolves against (${JSON.stringify(base)}) is not a fully-qualified path of this tree's family` };
|
|
79
75
|
}
|
|
@@ -394,16 +394,18 @@ export interface EngineNotice {
|
|
|
394
394
|
* frame's two count keys). They are NOT redelivered (a steer aimed at a finished run must not
|
|
395
395
|
* fire at the next one — unlike ENGINE notes, which pend per session); the loud half of the
|
|
396
396
|
* #257 contract's "accepted = enqueued, not consumed" sentence;
|
|
397
|
-
* `detail: {
|
|
398
|
-
*
|
|
397
|
+
* `detail: { <countKey>, cause, taskId?, sessionId?, runId? }` (the `code` and the `countKey` of each family are the
|
|
398
|
+
* table {@link USER_INPUT_UNDRAINED_FAMILIES}, keyed by the lane the input was queued on — `injectionLaneOf` — and the
|
|
399
|
+
* minter writes exactly those; `cause` on both rows, closed `USER_INPUT_UNDRAINED_CAUSES`: `"leg_end"` — this engine's arm, the
|
|
399
400
|
* leg ended with it still owed; `"crashed"` — minted by the SERVING LAYER, not this engine, before the
|
|
400
|
-
* session's next run, for
|
|
401
|
-
* serving layer's, so only it can count them
|
|
401
|
+
* session's next run, for inputs it had accepted whose consumption was not confirmed before the process died: their queue was the
|
|
402
|
+
* serving layer's, so only it can count them, and it picks the family by the LANE the input was queued on (a `later`
|
|
403
|
+
* steer rides the follow-up lane). One code per family for both arms, so a consumer de-duplicating on `code` keeps
|
|
402
404
|
* one row; `sessionId` = #433's routing half for these two `"user"`-audience rows; `runId` = #499's
|
|
403
405
|
* per-run identity, the key this account is actually about — see
|
|
404
406
|
* {@link undrainedUserInputNotices}; all omitted when the caller has none, never fabricated). A delegated child
|
|
405
407
|
* leg is announced on its PARENT's session as well — the direct parent only — as a second row per family,
|
|
406
|
-
* `detail: {
|
|
408
|
+
* `detail: { <countKey>, cause, taskId?, sessionId: <the parent's>, childSessionId }` (no `runId`: nothing
|
|
407
409
|
* carries the parent's), message tail `(a child leg, <childSessionId>)`; `childSessionId` present = a child reported it.
|
|
408
410
|
* Per-run, at most once per family (the terminal sweep is a single site) — which is why
|
|
409
411
|
* `taskId`/`sessionId` cannot key it: both answer the same value for two runs of one session.
|
|
@@ -959,16 +961,33 @@ export declare function settleOwedUserInput(account: OwedUserInputAccount, rowRe
|
|
|
959
961
|
parent?: ParentLegIds | undefined;
|
|
960
962
|
}, onNotice: ((notice: EngineNotice) => void) | undefined): void;
|
|
961
963
|
/**
|
|
962
|
-
* Why a person's
|
|
963
|
-
*
|
|
964
|
-
* · `
|
|
965
|
-
*
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
*
|
|
964
|
+
* Why a person's input is reported as not consumed — the `detail.cause` of BOTH undrained families
|
|
965
|
+
* (`task.user_steer_undrained` / `task.user_followup_undrained`; the family is the lane the input was queued on). Closed:
|
|
966
|
+
* · `leg_end` — the leg ended with the input still owed (this engine mints it, at the leg's settlement);
|
|
967
|
+
* · `crashed` — the engine process ended before the leg settled, and an input the serving layer had accepted for it was
|
|
968
|
+
* not confirmed consumed before the process died (a live leg gives the serving layer no evidence that an input reached
|
|
969
|
+
* the model, so such an input MAY have been consumed). Only the serving layer can count these (the queue was its own), so the serving layer
|
|
970
|
+
* mints this arm, choosing the family by the lane the input was queued on, before the next run of the session starts —
|
|
971
|
+
* the one member-level exception to {@link import("./governance-codes.js").NOTICE_MINTER}'s rows for the two codes, which stay `"engine"`.
|
|
969
972
|
*/
|
|
970
|
-
export declare const
|
|
971
|
-
export type
|
|
973
|
+
export declare const USER_INPUT_UNDRAINED_CAUSES: readonly ["leg_end", "crashed"];
|
|
974
|
+
export type UserInputUndrainedCause = (typeof USER_INPUT_UNDRAINED_CAUSES)[number];
|
|
975
|
+
/**
|
|
976
|
+
* The two families of the stranded-input account, keyed by the lane the input was queued on (`injectionLaneOf` maps a steer's
|
|
977
|
+
* tier to it): the notice `code` the family is announced under, and the `detail` key that carries its count
|
|
978
|
+
* (`detail[countKey]`, beside `cause`). {@link undrainedUserInputNotices} writes its rows with exactly these two spellings, so a
|
|
979
|
+
* serving layer that mints the `crashed` arm picks its family by lane and spells code and count key the way the engine does.
|
|
980
|
+
*/
|
|
981
|
+
export declare const USER_INPUT_UNDRAINED_FAMILIES: Readonly<{
|
|
982
|
+
readonly steer: Readonly<{
|
|
983
|
+
readonly code: "task.user_steer_undrained";
|
|
984
|
+
readonly countKey: "steer";
|
|
985
|
+
}>;
|
|
986
|
+
readonly followUp: Readonly<{
|
|
987
|
+
readonly code: "task.user_followup_undrained";
|
|
988
|
+
readonly countKey: "followUp";
|
|
989
|
+
}>;
|
|
990
|
+
}>;
|
|
972
991
|
/** Where a child leg's account is ALSO announced: its spawner's session, and that run's task id when the spawner carried one
|
|
973
992
|
* (it never carries the run id, and the row never invents one). */
|
|
974
993
|
interface ParentLegIds {
|
|
@@ -51,7 +51,13 @@ export function settleOwedUserInput(account, rowReturned, ids, onNotice) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
const SETTLED_OWED_ACCOUNTS = new WeakSet();
|
|
54
|
-
export const
|
|
54
|
+
export const USER_INPUT_UNDRAINED_CAUSES = Object.freeze(["leg_end", "crashed"]);
|
|
55
|
+
const STEER_UNDRAINED = "task.user_steer_undrained";
|
|
56
|
+
const FOLLOWUP_UNDRAINED = "task.user_followup_undrained";
|
|
57
|
+
export const USER_INPUT_UNDRAINED_FAMILIES = Object.freeze({
|
|
58
|
+
steer: Object.freeze({ code: STEER_UNDRAINED, countKey: "steer" }),
|
|
59
|
+
followUp: Object.freeze({ code: FOLLOWUP_UNDRAINED, countKey: "followUp" }),
|
|
60
|
+
});
|
|
55
61
|
export function parentLegIds(internals) {
|
|
56
62
|
const parentSessionId = internals?.parentSessionId;
|
|
57
63
|
if (typeof parentSessionId !== "string" || parentSessionId === "")
|
|
@@ -70,19 +76,20 @@ export function undrainedUserInputNotices(counts, taskId, sessionId, runId, pare
|
|
|
70
76
|
const copyTail = ` (a child leg, ${sessionId})`;
|
|
71
77
|
const own = [];
|
|
72
78
|
const copies = [];
|
|
79
|
+
const cause = "leg_end";
|
|
73
80
|
if (counts.steer > 0) {
|
|
74
81
|
const message = `${counts.steer} user steer(s) ${tail}`;
|
|
75
|
-
const count = { steer: counts.steer, cause
|
|
76
|
-
own.push({ code:
|
|
82
|
+
const count = { [USER_INPUT_UNDRAINED_FAMILIES.steer.countKey]: counts.steer, cause };
|
|
83
|
+
own.push({ code: STEER_UNDRAINED, message, detail: { ...count, ...tid, ...sid, ...rid } });
|
|
77
84
|
if (toParent)
|
|
78
|
-
copies.push({ code:
|
|
85
|
+
copies.push({ code: STEER_UNDRAINED, message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
|
|
79
86
|
}
|
|
80
87
|
if (counts.followUp > 0) {
|
|
81
88
|
const message = `${counts.followUp} user follow-up(s) ${tail}`;
|
|
82
|
-
const count = { followUp: counts.followUp };
|
|
83
|
-
own.push({ code:
|
|
89
|
+
const count = { [USER_INPUT_UNDRAINED_FAMILIES.followUp.countKey]: counts.followUp, cause };
|
|
90
|
+
own.push({ code: FOLLOWUP_UNDRAINED, message, detail: { ...count, ...tid, ...sid, ...rid } });
|
|
84
91
|
if (toParent)
|
|
85
|
-
copies.push({ code:
|
|
92
|
+
copies.push({ code: FOLLOWUP_UNDRAINED, message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
|
|
86
93
|
}
|
|
87
94
|
return [...own, ...copies];
|
|
88
95
|
}
|
package/dist/core/gate-lanes.js
CHANGED
|
@@ -40,6 +40,7 @@ export async function runGateLanes(pass) {
|
|
|
40
40
|
writeProtected: pass.writeProtected === true,
|
|
41
41
|
writeProtectionUnresolved: pass.writeProtectionUnresolved === true,
|
|
42
42
|
});
|
|
43
|
+
const readFaceNow = input.readFace?.();
|
|
43
44
|
const askOriginFacts = (org, ruleCallUnreadable) => ({
|
|
44
45
|
contentQuestion: heldBy("lanes.contentQuestionClass", QUESTION_TOOL_NAMES, pass.req),
|
|
45
46
|
markedUnresolvable: markedUnresolvable(),
|
|
@@ -49,6 +50,7 @@ export async function runGateLanes(pass) {
|
|
|
49
50
|
mandateMarks: mandateMarksNow(),
|
|
50
51
|
...(input.toolMarkSources !== undefined ? { toolMarkSources: input.toolMarkSources } : {}),
|
|
51
52
|
...(input.a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
|
|
53
|
+
...(readFaceNow !== undefined ? { readFace: readFaceNow } : {}),
|
|
52
54
|
});
|
|
53
55
|
const classifyStanding = (unstamped, facts) => (pass.askClassification = classifyAsk(unstamped, facts));
|
|
54
56
|
let orgTightenCount = 0;
|
|
@@ -125,6 +125,11 @@ export type SettlementParty = (typeof SETTLEMENT_PARTIES)[number];
|
|
|
125
125
|
export type SettlementPartiesMatchTheRecord = AssertAllKeysHandled<Exclude<Settlement["who"]["party"], SettlementParty> | Exclude<SettlementParty, Settlement["who"]["party"]>>;
|
|
126
126
|
/** Membership test for {@link SettlementParty}. */
|
|
127
127
|
export declare function isSettlementParty(v: unknown): v is SettlementParty;
|
|
128
|
+
/** Who REPORTED a durable decide (`hostDecision.decidedBy`): a person, the host's own SLA sweep at the row's deadline, or the
|
|
129
|
+
* deployment's policy — the closed set; a host's decide door mirrors the words it accepts from this one. */
|
|
130
|
+
export declare const HOST_DECIDERS: readonly ["person", "sla_timeout", "policy"];
|
|
131
|
+
export type HostDecider = (typeof HOST_DECIDERS)[number];
|
|
132
|
+
export declare const isHostDecider: (word: unknown) => word is HostDecider;
|
|
128
133
|
/** The DECIDER arms of {@link Settlement} — a person's and the deployment policy's, the two parties that
|
|
129
134
|
* answer an ask either way. Named so a site that both composes one and quotes its note can read the seat
|
|
130
135
|
* off the record instead of re-deriving it. */
|
|
@@ -25,6 +25,8 @@ const SETTLEMENT_PARTY_SET = new Set(SETTLEMENT_PARTIES);
|
|
|
25
25
|
export function isSettlementParty(v) {
|
|
26
26
|
return SETTLEMENT_PARTY_SET.has(v);
|
|
27
27
|
}
|
|
28
|
+
export const HOST_DECIDERS = Object.freeze(["person", "sla_timeout", "policy"]);
|
|
29
|
+
export const isHostDecider = (word) => HOST_DECIDERS.some((w) => w === word);
|
|
28
30
|
export function decidersNote(text) {
|
|
29
31
|
return text === "" ? undefined : text;
|
|
30
32
|
}
|
|
@@ -259,9 +259,10 @@ export declare function noticeAudienceOf(code: string): NoticeAudience;
|
|
|
259
259
|
* {@link NOTICE_AUDIENCE} like every other row, and NEVER minted by this package (the catalog gate reds a mint site for
|
|
260
260
|
* one in src/). One code, one minter: a code two processes mint would be two facts under one spelling.
|
|
261
261
|
*
|
|
262
|
-
* Member-level exception, stated where a consumer reads the minter: `task.user_steer_undrained`
|
|
263
|
-
*
|
|
264
|
-
* (see `
|
|
262
|
+
* Member-level exception, stated where a consumer reads the minter: `task.user_steer_undrained` and
|
|
263
|
+
* `task.user_followup_undrained` are engine rows, and their `detail.cause: "crashed"` arm is minted by the server — the
|
|
264
|
+
* engine's process is gone when that loss is counted (see `USER_INPUT_UNDRAINED_CAUSES`); the server picks the code by the
|
|
265
|
+
* lane the input was queued on, as this engine does. Each code keeps one row here and one spelling for both arms.
|
|
265
266
|
*/
|
|
266
267
|
export type NoticeMinter = "engine" | "server";
|
|
267
268
|
declare const NOTICE_MINTER_TABLE: {
|
|
@@ -84,6 +84,7 @@ export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
|
|
84
84
|
"memory.capture_optout_unavailable",
|
|
85
85
|
"memory.capture_optout_sweep_failed",
|
|
86
86
|
"memory.target_outside_store",
|
|
87
|
+
"memory.scope_home_not_directory",
|
|
87
88
|
]);
|
|
88
89
|
export function governanceRetryClass(code) {
|
|
89
90
|
if (Object.prototype.hasOwnProperty.call(GOVERNANCE_CODES, code)) {
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -1466,6 +1466,8 @@ export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, All
|
|
|
1466
1466
|
permissionMode?: () => import("./ask-origin.js").PermissionMode;
|
|
1467
1467
|
/** The leg's approval-seat form (`WiringManifest.ask.form`): the deny exit's way-out clause names a declaration, never a missing approver. */
|
|
1468
1468
|
askSeatForm?: import("./wiring-manifest.js").AskSeamForm;
|
|
1469
|
+
/** The read face the leg resolved, read per call into the class derivation (`roots` withholds the classifier from a `probe` question); absent ⇒ none resolved. */
|
|
1470
|
+
readFace?: () => import("../tools/fs/containment-face.js").ContainmentFace | undefined;
|
|
1469
1471
|
/** WHO put the tool's egress / irreversibility marks on the seat ({@link import("./ask-origin.js").ToolMarkSource});
|
|
1470
1472
|
* absent or empty with a mark present reads as the deployment's own (fail-closed: an operator's mandate). */
|
|
1471
1473
|
toolMarkSources?: ReadonlySet<import("./ask-origin.js").ToolMarkSource>;
|
|
@@ -1,37 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* design/178 §3 — the tool content-origin classification (three tiers by CAPABILITY, fail-closed).
|
|
3
|
-
*
|
|
4
|
-
* The question this face answers per mounted tool: can invoking it bring content from OUTSIDE the
|
|
5
|
-
* deployment's trust boundary into the session — and therefore, via the automatic harvest path,
|
|
6
|
-
* into long-term memory? Three tiers:
|
|
7
|
-
*
|
|
8
|
-
* - "external" — network-retrieval families and external protocol channels. Invocation marks the
|
|
9
|
-
* session's memory EXTERNALLY EXPOSED (one-way, durable; what the mark does at
|
|
10
|
-
* harvest is the provenance mode's question — design/336: tag-admit with an origin
|
|
11
|
-
* marker under "carry", full quarantine under "off").
|
|
12
|
-
* - "execution" — general execution (shell/exec). A NAMED residual channel: it can reach external
|
|
13
|
-
* content indirectly, but the two industry anchors this tier is calibrated against
|
|
14
|
-
* gate on network/protocol families only, and excluding every shell session would
|
|
15
|
-
* disable memory for the main coding scenario. Not polluting by default; the
|
|
16
|
-
* `execIsExternalContent` strict knob upgrades it to external.
|
|
17
|
-
* - "local" — purely local reads/computation.
|
|
18
|
-
*
|
|
19
|
-
* WHO CLASSIFIES (single-source rule): core built-ins declare `contentOrigin` on their own ToolSpec
|
|
20
|
-
* (the classification lives with the tool definition); protocol tools (MCP/A2A) are structurally
|
|
21
|
-
* external (the channel itself crosses the boundary); a HOST tool declares its own — and when it
|
|
22
|
-
* does not, the default is EXTERNAL (fail-closed: unknown = external), overridable only through the
|
|
23
|
-
* explicit `TaskSpec.memory.trustedTools` allowlist. A core-mounted built-in without a declaration
|
|
24
|
-
* defaults local: core owns those definitions, and the fail-closed default exists for tools core
|
|
25
|
-
* CANNOT vouch for, not for its own. A declaration is honored only when it SPELLS a member of the
|
|
26
|
-
* closed vocabulary — an unreadable one is not a declaration.
|
|
27
|
-
*
|
|
28
|
-
* The three tier names are BOUNDARY-RELATIVE, not statements about topology: `"local"` means "does
|
|
29
|
-
* not bring content from outside the deployment's trust boundary", which a unix socket to another
|
|
30
|
-
* process the deployment itself runs satisfies and a loopback HTTP call to somebody else's service
|
|
31
|
-
* does not. Distance is not the axis; whose content it is, is.
|
|
32
|
-
*
|
|
33
|
-
* There is deliberately no "gate inactive" state: every mounted tool gets a class.
|
|
34
|
-
*/
|
|
35
1
|
import type { ToolContentOrigin } from "../types.js";
|
|
36
2
|
export interface ClassifyToolContentOriginInput {
|
|
37
3
|
/** The tool's own `contentOrigin` declaration (ToolSpec/AgentTool carry) — wins when present AND
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { allowedToolNames, readDeniedEntry, readToolNameList } from "../../agents/tool-filter.js";
|
|
1
2
|
const TOOL_CONTENT_ORIGIN_SET = { external: true, execution: true, local: true };
|
|
2
3
|
export const TOOL_CONTENT_ORIGINS = Object.keys(TOOL_CONTENT_ORIGIN_SET);
|
|
3
4
|
export function isToolContentOrigin(value) {
|
|
@@ -27,10 +28,14 @@ export function delegationCallIsExternal(input) {
|
|
|
27
28
|
if (face === undefined)
|
|
28
29
|
return true;
|
|
29
30
|
const selectedFor = (f) => {
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
31
|
+
const deniedList = readToolNameList(f.denyTools);
|
|
32
|
+
const d = deniedList.kind === "entries" ? deniedList.entries : [];
|
|
33
|
+
const a = allowedToolNames(f.allowTools);
|
|
34
|
+
const denies = (t) => d.some((x) => {
|
|
35
|
+
const read = readDeniedEntry(x);
|
|
36
|
+
return read.kind === "name" && selectedBy(read.name, t);
|
|
37
|
+
});
|
|
38
|
+
return input.pool.filter((t) => (a === undefined || [...a].some((x) => selectedBy(x, t))) && !denies(t));
|
|
34
39
|
};
|
|
35
40
|
if (selectedFor(face).some((t) => input.isPolluting(t)))
|
|
36
41
|
return true;
|
|
@@ -1789,7 +1789,7 @@ export declare class MemoryEngine {
|
|
|
1789
1789
|
*/
|
|
1790
1790
|
private gateDerivedIndex;
|
|
1791
1791
|
/** The file backend's verdict at an act the engine itself performs on a store path ({@link
|
|
1792
|
-
*
|
|
1792
|
+
* landsOutsideStore}): throws `memory.target_outside_store` when `path` would land outside `root`;
|
|
1793
1793
|
* each caller's existing failure lane reports it. `root` is the mount's canonical root where a
|
|
1794
1794
|
* handle exists, so a root re-pointed after the mount is judged against the one it mounted. */
|
|
1795
1795
|
private refuseOutsideStore;
|
|
@@ -1798,7 +1798,7 @@ export declare class MemoryEngine {
|
|
|
1798
1798
|
* is never a supported layout (the harvest scan refuses symlinked entry files outright), and this
|
|
1799
1799
|
* site already reports write failures by throwing, so ELOOP joins the failures it already had. A
|
|
1800
1800
|
* target whose directory resolves outside `root` (a directory on the path is a link) is refused
|
|
1801
|
-
* the same way, before anything is read or written ({@link
|
|
1801
|
+
* the same way, before anything is read or written ({@link landsOutsideStore}). */
|
|
1802
1802
|
private writeIfChanged;
|
|
1803
1803
|
/** Persist the canonical projection back to the session file (id minting / frontmatter completion).
|
|
1804
1804
|
* C-F6 (S2-0): called ONLY after the backend transaction committed — never ahead of the journal
|
|
@@ -18,7 +18,7 @@ import { landedUndecided, layerDigest, layerObservationLookup, readOnlyLayerCand
|
|
|
18
18
|
import { markOriginClearanceTombstoned, openOriginClearance, readOriginClearances, settleOriginClearance } from "./origin-clearance.js";
|
|
19
19
|
import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, canonicalJsonStringify, captureErasureInput, erasureRequestInvalid, erasureSelectHash, scanEntryFiles, } from "./file-backend.js";
|
|
20
20
|
import { assembleMemoryExportBundle, computeMemoryBundleHash, memoryBundleInvalid, } from "./export-bundle.js";
|
|
21
|
-
import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, restoreContainedBaseline, indexLineIsStale, readIndexRevs, writeIndexRevs, bumpScanFuse, scanFuseKey, canonicalize, landingPathOf,
|
|
21
|
+
import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, restoreContainedBaseline, indexLineIsStale, readIndexRevs, writeIndexRevs, bumpScanFuse, scanFuseKey, canonicalize, landingPathOf, landsOutsideStore, storeActRefusal, storeRootMovedRefusal, adoptRootScopeLayout, clearScanFuse, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, markSessionPolluted, readSessionPollution, fileSessionCaptureRecordStore, recordRetrievedAccount, writeFileNoFollow, withPermissionRemedy, permissionRemedyOf, readRetrievedAccount, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, appendChallengeEvents, appendLineageAudit, rebuildStrictControlPlaneLedger, isStrictControlPlaneLedgerCorrupt, CHALLENGE_LEDGER_MAX_EVENTS, adjudicateLineagePending, challengedEntryIds, clearLineageForEntries, discardLineagePending, lineageAccountOfEntry, lineageContributionsOfSession, lineageLatchedIds, promoteLineagePending, readChallengeEvents, readChallengedHistory, readLineageRecord, recordChallengedHistory, recordLineageCredential, reconcileLineage, readProjectionDebts, resolveChallengeEvent, settleProjectionDebts, stageLineagePending, stageProjectionDebts, } from "./layout.js";
|
|
22
22
|
import { createSessionIncarnationSeat, recalledForAnyIdentity, recalledForIdentity, rememberForIdentity } from "./session-incarnation.js";
|
|
23
23
|
import { scanMemoryFileName, scanMemoryWrite, scanRemediation } from "./scan.js";
|
|
24
24
|
import { writableRootOf } from "./types.js";
|
|
@@ -1416,11 +1416,8 @@ export class MemoryEngine {
|
|
|
1416
1416
|
ensureDirExists(this.memoryDir);
|
|
1417
1417
|
const physicalRoot = landingPathOf(this.memoryDir);
|
|
1418
1418
|
this.mountedRoot ??= physicalRoot;
|
|
1419
|
-
if (physicalRoot !== this.mountedRoot)
|
|
1420
|
-
|
|
1421
|
-
e.code = TARGET_OUTSIDE_STORE;
|
|
1422
|
-
throw e;
|
|
1423
|
-
}
|
|
1419
|
+
if (physicalRoot !== this.mountedRoot)
|
|
1420
|
+
throw storeRootMovedRefusal("engine", this.memoryDir);
|
|
1424
1421
|
ensureDirExists(this.controlDir);
|
|
1425
1422
|
await this.adoptLayoutBeforeMount(writeScope);
|
|
1426
1423
|
const restricted = opts?.adoptionRestricted === true;
|
|
@@ -2216,7 +2213,7 @@ export class MemoryEngine {
|
|
|
2216
2213
|
if (content === undefined)
|
|
2217
2214
|
continue;
|
|
2218
2215
|
try {
|
|
2219
|
-
this.refuseOutsideStore(handle.memoryDir, m.path, "restoring");
|
|
2216
|
+
this.refuseOutsideStore(handle.memoryDir, m.path, "restoring a memory file");
|
|
2220
2217
|
mkdirSync(dirname(m.path), { recursive: true });
|
|
2221
2218
|
writeFileNoFollow(m.path, content);
|
|
2222
2219
|
report.restored.push(m.relPath);
|
|
@@ -4947,15 +4944,11 @@ export class MemoryEngine {
|
|
|
4947
4944
|
};
|
|
4948
4945
|
}
|
|
4949
4946
|
refuseOutsideStore(root, path, act) {
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
return;
|
|
4953
|
-
const e = new Error(`${TARGET_OUTSIDE_STORE}: ${act} ${path} was refused — ${escape}; not performed`);
|
|
4954
|
-
e.code = TARGET_OUTSIDE_STORE;
|
|
4955
|
-
throw e;
|
|
4947
|
+
if (landsOutsideStore(root, path))
|
|
4948
|
+
throw storeActRefusal(act, path);
|
|
4956
4949
|
}
|
|
4957
4950
|
writeIfChanged(target, text, root) {
|
|
4958
|
-
this.refuseOutsideStore(root, target, "the projection write
|
|
4951
|
+
this.refuseOutsideStore(root, target, "the projection write");
|
|
4959
4952
|
const current = readNoFollowSafe(target);
|
|
4960
4953
|
if (current === text)
|
|
4961
4954
|
return;
|