@sema-agent/core 7.50.2 → 7.51.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 +263 -0
- package/dist/agents/child-failure.d.ts +5 -16
- package/dist/agents/child-failure.js +4 -24
- package/dist/agents/subagent-steps.js +2 -2
- package/dist/agents/subagent.d.ts +4 -13
- package/dist/agents/subagent.js +94 -204
- package/dist/agents/tool-filter.d.ts +2 -2
- package/dist/brain/anthropic.d.ts +1 -1
- package/dist/brain/anthropic.js +7 -5
- package/dist/brain/open-responses.d.ts +1 -1
- package/dist/brain/open-responses.js +3 -2
- package/dist/brain/openai.d.ts +1 -1
- package/dist/brain/openai.js +3 -2
- package/dist/brain/request-route.d.ts +2 -2
- package/dist/brain/route-adjudicator.d.ts +16 -7
- package/dist/brain/route-adjudicator.js +23 -7
- package/dist/brain/route-conformance.js +23 -5
- package/dist/brain/stream-engine.js +9 -3
- package/dist/core/a2a.js +6 -8
- package/dist/core/allow-entry.d.ts +45 -26
- package/dist/core/allow-entry.js +48 -24
- package/dist/core/arg-summary.d.ts +8 -85
- package/dist/core/arg-summary.js +4 -386
- package/dist/core/ask-origin.d.ts +35 -33
- package/dist/core/ask-origin.js +9 -11
- package/dist/core/checkpoint-requirements.d.ts +6 -0
- package/dist/core/checkpoint-requirements.js +1 -0
- package/dist/core/checkpoint-store.d.ts +15 -34
- package/dist/core/checkpoint-store.js +5 -4
- package/dist/core/classifier-unavailable-arm.d.ts +12 -8
- package/dist/core/classifier-unavailable-arm.js +7 -2
- package/dist/core/credential-vocabulary.d.ts +3 -2
- package/dist/core/credential-vocabulary.js +27 -18
- package/dist/core/delegation-frames.d.ts +40 -15
- package/dist/core/delegation-frames.js +34 -0
- package/dist/core/engine-notice.d.ts +35 -11
- package/dist/core/gate-fold.js +9 -7
- package/dist/core/gate-lanes.js +6 -4
- package/dist/core/gate-outcome.d.ts +81 -15
- package/dist/core/gate-outcome.js +168 -67
- package/dist/core/governance-codes.d.ts +7 -1
- package/dist/core/governance-codes.js +9 -0
- package/dist/core/hooks.d.ts +4 -4
- package/dist/core/hooks.js +4 -3
- package/dist/core/mcp-connection-pool.d.ts +2 -0
- package/dist/core/mcp-connection-pool.js +2 -1
- package/dist/core/mcp-failure.js +2 -1
- package/dist/core/mcp-server-spec.d.ts +27 -5
- package/dist/core/mcp.d.ts +13 -38
- package/dist/core/mcp.js +28 -23
- package/dist/core/media-byte-cap.d.ts +25 -0
- package/dist/core/media-byte-cap.js +18 -2
- package/dist/core/memory-engine/consolidation-driver.js +1 -1
- package/dist/core/mode-policy.d.ts +25 -1
- package/dist/core/mode-policy.js +28 -13
- package/dist/core/own-property-receipt.d.ts +18 -0
- package/dist/core/own-property-receipt.js +39 -0
- package/dist/core/permission-rule-model.d.ts +32 -11
- package/dist/core/permission-rule-model.js +12 -7
- package/dist/core/persisted-rule-arms.d.ts +3 -3
- package/dist/core/prompt-section-seat.d.ts +54 -0
- package/dist/core/prompt-section-seat.js +49 -0
- package/dist/core/protocol-http-auth.d.ts +44 -0
- package/dist/core/protocol-http-auth.js +45 -0
- package/dist/core/read-face-seats.d.ts +30 -0
- package/dist/core/read-face-seats.js +38 -0
- package/dist/core/redaction-passes.d.ts +82 -0
- package/dist/core/redaction-passes.js +378 -0
- package/dist/core/remote-env.d.ts +4 -3
- package/dist/core/resume-startup-cause.d.ts +56 -0
- package/dist/core/resume-startup-cause.js +43 -0
- package/dist/core/rule-denial-sentence.d.ts +2 -2
- package/dist/core/runner/announce-once-ledger.js +2 -2
- package/dist/core/runner/classifier-ladder.d.ts +17 -13
- package/dist/core/runner/classifier-ladder.js +12 -10
- package/dist/core/runner/construction-doors.js +9 -20
- package/dist/core/runner/contracts.d.ts +4 -4
- package/dist/core/runner/gate-exit.js +2 -2
- package/dist/core/runner/memory-consolidation-write-judge.d.ts +53 -9
- package/dist/core/runner/memory-consolidation-write-judge.js +39 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +8 -13
- package/dist/core/runner/person-path-rows.d.ts +4 -3
- package/dist/core/runner/policy-throw.js +4 -4
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
- package/dist/core/runner/prepare-caps-and-workflow.js +8 -6
- package/dist/core/runner/prepare-gate-stations.js +4 -4
- package/dist/core/runner/prepare-hands-readface.js +1 -19
- package/dist/core/runner/prepare-inherited-gate.js +10 -9
- package/dist/core/runner/prepare-policy-chain.d.ts +3 -5
- package/dist/core/runner/prepare-policy-chain.js +10 -13
- package/dist/core/runner/prepare-project-context.d.ts +4 -4
- package/dist/core/runner/prepare-project-context.js +2 -1
- package/dist/core/runner/prepare-prompt-assembly.js +1 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -1
- package/dist/core/runner/prepare-prompt-inputs.js +4 -4
- package/dist/core/runner/prepare-protocol-tools.d.ts +4 -3
- package/dist/core/runner/prepare-protocol-tools.js +3 -2
- package/dist/core/runner/prepare-task.js +6 -4
- package/dist/core/runner/prepare-turn-wiring.d.ts +16 -13
- package/dist/core/runner/prepare-turn-wiring.js +8 -7
- package/dist/core/runner/run-harness-handlers.js +2 -2
- package/dist/core/runner/run-leg.js +10 -9
- package/dist/core/runner/run-settle-and-teardown.js +5 -5
- package/dist/core/runner/run-turn-boundary.js +3 -2
- package/dist/core/runner/runtask.d.ts +1 -2
- package/dist/core/runner/runtask.js +8 -12
- package/dist/core/runner/session-rule-policy.d.ts +15 -11
- package/dist/core/runner/session-rule-policy.js +43 -33
- package/dist/core/runner/stream-lifecycle-verbs.js +2 -2
- package/dist/core/runner/stream-settle-backstop.js +2 -1
- package/dist/core/runner/sub-agent-deny-lists.d.ts +12 -8
- package/dist/core/runner/sub-agent-deny-lists.js +23 -12
- package/dist/core/runner/terminal-account.js +3 -3
- package/dist/core/runner/tool-output-projection.js +3 -8
- package/dist/core/runner/tool-result-media-seat.d.ts +19 -0
- package/dist/core/runner/tool-result-media-seat.js +13 -0
- package/dist/core/runner/turn-attachments.d.ts +13 -12
- package/dist/core/runner/turn-attachments.js +11 -7
- package/dist/core/runner-deps.d.ts +4 -3
- package/dist/core/safety-merge-corpus.js +21 -21
- package/dist/core/secret-env.js +1 -1
- package/dist/core/session-policy-store.d.ts +89 -72
- package/dist/core/session-policy-store.js +100 -93
- package/dist/core/shell-read-silence.d.ts +38 -4
- package/dist/core/shell-read-silence.js +102 -0
- package/dist/core/side-query.d.ts +7 -8
- package/dist/core/skill-grant-binding.d.ts +32 -11
- package/dist/core/skill-grant-binding.js +11 -3
- package/dist/core/skill-grant.d.ts +4 -3
- package/dist/core/skill-grant.js +5 -4
- package/dist/core/skills-directory.d.ts +12 -1
- package/dist/core/skills-directory.js +17 -8
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +3 -0
- package/dist/core/swappable-deps.d.ts +15 -1
- package/dist/core/swappable-deps.js +25 -1
- package/dist/core/synthetic-read-verdict.d.ts +16 -14
- package/dist/core/synthetic-read-verdict.js +28 -8
- package/dist/core/task-notification.d.ts +13 -3
- package/dist/core/task-notification.js +12 -1
- package/dist/core/task-spec.d.ts +14 -15
- package/dist/core/tool-name-readings.d.ts +0 -4
- package/dist/core/tool-name-readings.js +0 -1
- package/dist/core/tool-policy.d.ts +40 -9
- package/dist/core/tool-policy.js +25 -7
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-egress.d.ts +60 -11
- package/dist/core/untrusted-egress.js +120 -9
- package/dist/core/untrusted-text.js +16 -0
- package/dist/core/wiring-manifest.d.ts +22 -8
- package/dist/core/wiring-manifest.js +12 -4
- package/dist/engine/harness/agent-harness.d.ts +2 -1
- package/dist/engine/harness/agent-harness.js +4 -1
- package/dist/engine/harness/types.d.ts +3 -0
- package/dist/engine/llm/types.d.ts +7 -9
- package/dist/engine/loop/agent-loop.d.ts +8 -3
- package/dist/engine/loop/agent-loop.js +26 -9
- package/dist/engine/loop/types.d.ts +10 -0
- package/dist/index.d.ts +17 -5
- package/dist/index.js +14 -5
- package/dist/orchestration/run-workflow-tool.js +8 -8
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompt-assembly/assemble.d.ts +3 -1
- package/dist/prompt-assembly/assemble.js +6 -0
- package/dist/prompts/cowork.d.ts +42 -0
- package/dist/prompts/cowork.js +529 -0
- package/dist/prompts/default.d.ts +14 -1
- package/dist/prompts/default.js +3 -0
- package/dist/stores/file/session-policy-store.d.ts +6 -3
- package/dist/stores/file/session-policy-store.js +5 -16
- package/dist/tools/artifact/artifact-tool.js +2 -0
- package/dist/tools/fs/image-admission.d.ts +57 -4
- package/dist/tools/fs/image-admission.js +85 -0
- package/dist/tools/fs/path-containment.d.ts +2 -0
- package/dist/tools/fs/path-containment.js +5 -0
- package/dist/tools/fs/shell-read-check.d.ts +31 -6
- package/dist/tools/fs/shell-read-check.js +2 -4
- package/dist/tools/fs/shell-read-environment.d.ts +10 -25
- package/dist/tools/fs/shell-read-environment.js +6 -18
- package/dist/tools/fs/shell-read-establish.d.ts +3 -2
- package/dist/tools/fs/shell-read-establish.js +5 -5
- package/dist/tools/fs/shell-read-judge.d.ts +38 -8
- package/dist/tools/fs/shell-read-judge.js +12 -10
- package/dist/tools/web.js +2 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +94 -10
- package/wire-changes/core@7.51.0.json +2421 -0
|
@@ -1,3 +1,105 @@
|
|
|
1
|
+
import { types } from "node:util";
|
|
2
|
+
import { dispositionsOfRow, SHELL_READ_FINDINGS } from "../tools/fs/shell-read-judge.js";
|
|
3
|
+
import { receiveContract } from "./decision-receipt.js";
|
|
4
|
+
import { egressThrownText } from "./untrusted-egress.js";
|
|
5
|
+
const DISPOSITIONS = ["refuse", "mandated", "ordinary", "vouch"];
|
|
6
|
+
const MEMBER_KINDS = {
|
|
7
|
+
disposition: "word",
|
|
8
|
+
finding: "word",
|
|
9
|
+
environmentSilent: "mark",
|
|
10
|
+
rowsUnread: "mark",
|
|
11
|
+
face: "face",
|
|
12
|
+
sentence: "word",
|
|
13
|
+
details: "record",
|
|
14
|
+
readRootCandidate: "record",
|
|
15
|
+
unestablished: "mark",
|
|
16
|
+
writesPlaced: "mark",
|
|
17
|
+
writeProtected: "mark",
|
|
18
|
+
targetUnresolved: "mark",
|
|
19
|
+
unplacedOptions: "record",
|
|
20
|
+
admit: "admission",
|
|
21
|
+
};
|
|
22
|
+
const VERDICT_MEMBERS = Object.keys(MEMBER_KINDS);
|
|
23
|
+
const DATA_DEPTH = 8;
|
|
24
|
+
function ownedData(value, depth, path) {
|
|
25
|
+
if (value === null || value === undefined || typeof value === "string" || typeof value === "number" || typeof value === "boolean")
|
|
26
|
+
return { ok: true, value };
|
|
27
|
+
if (typeof value !== "object" || depth >= DATA_DEPTH || types.isProxy(value) || path.has(value))
|
|
28
|
+
return { ok: false };
|
|
29
|
+
const list = Array.isArray(value);
|
|
30
|
+
const proto = Object.getPrototypeOf(value);
|
|
31
|
+
if (list ? proto !== Array.prototype : proto !== Object.prototype && proto !== null)
|
|
32
|
+
return { ok: false };
|
|
33
|
+
const own = Object.getOwnPropertyDescriptors(value);
|
|
34
|
+
path.add(value);
|
|
35
|
+
try {
|
|
36
|
+
const out = list ? [] : {};
|
|
37
|
+
for (const key of Object.keys(own)) {
|
|
38
|
+
if (list && key === "length")
|
|
39
|
+
continue;
|
|
40
|
+
const d = own[key];
|
|
41
|
+
if (!Object.hasOwn(d, "value") || d.enumerable !== true)
|
|
42
|
+
return { ok: false };
|
|
43
|
+
const inner = ownedData(d.value, depth + 1, path);
|
|
44
|
+
if (!inner.ok)
|
|
45
|
+
return { ok: false };
|
|
46
|
+
Object.defineProperty(out, key, { value: inner.value, enumerable: true, writable: true, configurable: true });
|
|
47
|
+
}
|
|
48
|
+
return { ok: true, value: out };
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
path.delete(value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function receivedAnswer(answer, call) {
|
|
55
|
+
if (typeof answer !== "object" || answer === null)
|
|
56
|
+
return undefined;
|
|
57
|
+
const outer = receiveContract(answer, VERDICT_MEMBERS, "");
|
|
58
|
+
if (outer.kind !== "held")
|
|
59
|
+
return undefined;
|
|
60
|
+
const out = {};
|
|
61
|
+
for (const [member, kind] of Object.entries(MEMBER_KINDS)) {
|
|
62
|
+
let v = outer.held[member];
|
|
63
|
+
if (v === undefined)
|
|
64
|
+
continue;
|
|
65
|
+
if (kind === "word" ? typeof v !== "string" : kind === "mark" ? v !== true : kind === "face" ? v !== "write" : kind === "admission" ? typeof v !== "function" || types.isProxy(v) : false)
|
|
66
|
+
return undefined;
|
|
67
|
+
if (kind === "record") {
|
|
68
|
+
const data = ownedData(v, 0, new Set());
|
|
69
|
+
if (!data.ok || typeof data.value !== "object" || data.value === null || Array.isArray(data.value))
|
|
70
|
+
return undefined;
|
|
71
|
+
v = data.value;
|
|
72
|
+
}
|
|
73
|
+
if (kind === "admission") {
|
|
74
|
+
const admission = v;
|
|
75
|
+
v = () => {
|
|
76
|
+
try {
|
|
77
|
+
Reflect.apply(admission, undefined, []);
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
call.report?.(new Error(`shell read station's admission threw for tool "${call.toolName}"${call.toolCallId !== undefined ? ` (toolCallId ${call.toolCallId})` : ""} — nothing was admitted: ${egressThrownText("same-principal", e)}`));
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
Object.defineProperty(out, member, { value: v, enumerable: true, writable: true, configurable: true });
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
export function readStationAnswer(answer, call) {
|
|
89
|
+
const verdict = receivedAnswer(answer, call);
|
|
90
|
+
if (verdict?.disposition === "refuse")
|
|
91
|
+
return { kind: "refuse", verdict };
|
|
92
|
+
if (verdict !== undefined && (verdict.finding === "unanswered" || (verdict.finding === "none" && (verdict.environmentSilent === true || verdict.rowsUnread === true))))
|
|
93
|
+
return { kind: "silence", verdict };
|
|
94
|
+
if (verdict !== undefined && DISPOSITIONS.includes(verdict.disposition) && dispositionsOfRow(verdict.finding).has(verdict.disposition))
|
|
95
|
+
return { kind: verdict.disposition, verdict };
|
|
96
|
+
const word = (w, set) => (w !== undefined && set.includes(w) ? `"${w}"` : "a word outside its closed set");
|
|
97
|
+
const sentence = verdict === undefined
|
|
98
|
+
? `tool "${call.toolName}" was refused: the read station's answer is not one the engine can receive (an ordinary object holding its members as its own data properties, each of its kind), so it is not believed (denied fail-closed)`
|
|
99
|
+
: `tool "${call.toolName}" was refused: the read station answered ${word(verdict.disposition, DISPOSITIONS)} beside the finding ${word(verdict.finding, SHELL_READ_FINDINGS)}, an answer the read decision table never gives that finding — the station's answer contradicts its own finding and is not believed (denied fail-closed)`;
|
|
100
|
+
call.report?.(new Error(`shell read station answered out of its contract for tool "${call.toolName}"${call.toolCallId !== undefined ? ` (toolCallId ${call.toolCallId})` : ""} — ${sentence}`));
|
|
101
|
+
return { kind: "refuse", verdict: { disposition: "refuse", finding: "none", sentence }, unbelieved: true };
|
|
102
|
+
}
|
|
1
103
|
export function silencedBy(verdict, d) {
|
|
2
104
|
const environment = verdict.environmentSilent === true || (verdict.finding === "unanswered" && verdict.rowsUnread !== true);
|
|
3
105
|
const rows = verdict.rowsUnread === true && d.ruleUnreadable === undefined ? { ruleUnreadable: "persisted_rows" } : {};
|
|
@@ -63,17 +63,16 @@ export interface SideQuerySpec {
|
|
|
63
63
|
* Per-model auth — MIRRORS {@link TaskSpec.getApiKeyAndHeaders} (same signature, resolved per
|
|
64
64
|
* call against the RESOLVED model, exactly like the task path's per-call hook). The brain
|
|
65
65
|
* contract is the route pairing law (route-adjudicator.ts): a per-model credential (this hook,
|
|
66
|
-
* or an auth header on `Model.headers`) always rides; the deployment credential rides only
|
|
67
|
-
*
|
|
68
|
-
*
|
|
66
|
+
* or an auth header on `Model.headers`) always rides; the deployment credential rides only to the
|
|
67
|
+
* deployment's own root — a model carrying a root of its own is refused
|
|
68
|
+
* (`route.credential_mismatch` / `route.credential_missing`), never silently followed.
|
|
69
69
|
* Historically the fallback was unconditional (`options.apiKey ?? config.apiKey`, with a model's
|
|
70
70
|
* own `baseUrl` outranking the brain's), so a side query routed to a model carrying its own
|
|
71
71
|
* `baseUrl` + per-model key sent the GATEWAY key to the per-model (possibly external) URL — the
|
|
72
|
-
* credential leak the pairing law now stops. Absent seat ⇒ no options are minted
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* for a side query to fall back to.
|
|
72
|
+
* credential leak the pairing law now stops. Absent seat ⇒ no options are minted: the
|
|
73
|
+
* construction-time credential applies on the deployment's own root, and a model rooted elsewhere
|
|
74
|
+
* hard-fails the side query with the loud refusal (`stopReason: "error"`) instead of leaking —
|
|
75
|
+
* there is no primary model for a side query to fall back to.
|
|
77
76
|
*/
|
|
78
77
|
getApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"];
|
|
79
78
|
signal?: AbortSignal;
|
|
@@ -15,30 +15,51 @@ export interface BoundSkillGrants {
|
|
|
15
15
|
/** The skill's `disallowed-tools` entries — what activating it refuses for the rest of the run. */
|
|
16
16
|
readonly disallowedTools: readonly string[];
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Why a card carries no binding digest — the closed set of `boundGrantAbsence`: `not_bindable` (the call is not an
|
|
20
|
+
* activation of the engine's `Skill` tool: it binds no grant) or `unreadable` (the card binds a grant and what it binds
|
|
21
|
+
* cannot be read: a stored row whose digest is not a string, or that says neither).
|
|
22
|
+
*/
|
|
23
|
+
export declare const BOUND_GRANT_ABSENCES: readonly ["not_bindable", "unreadable"];
|
|
24
|
+
export type BoundGrantAbsence = (typeof BOUND_GRANT_ABSENCES)[number];
|
|
18
25
|
/**
|
|
19
26
|
* A stored value read as a {@link BoundSkillGrants}, whole or not at all: a fresh copy of the three members and nothing else,
|
|
20
27
|
* or `undefined` when any member is not of its type. Every member is read ONCE, so the value screened is the value published.
|
|
21
28
|
*/
|
|
22
29
|
export declare function readBoundSkillGrants(raw: unknown): BoundSkillGrants | undefined;
|
|
23
|
-
/** The card members a call to the engine's `Skill` tool carries
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
boundGrantDigest
|
|
27
|
-
boundGrants
|
|
28
|
-
|
|
30
|
+
/** The card members every ask carries about its binding — exactly one arm: a call to the engine's `Skill` tool carries the
|
|
31
|
+
* digest and the value it hashes, every other call the absence word. */
|
|
32
|
+
export type SkillGrantBindingMembers = {
|
|
33
|
+
readonly boundGrantDigest: string;
|
|
34
|
+
readonly boundGrants: BoundSkillGrants;
|
|
35
|
+
readonly boundGrantAbsence?: never;
|
|
36
|
+
} | {
|
|
37
|
+
readonly boundGrantAbsence: BoundGrantAbsence;
|
|
38
|
+
readonly boundGrantDigest?: never;
|
|
39
|
+
readonly boundGrants?: never;
|
|
40
|
+
};
|
|
41
|
+
/** The members of a card that is not a `Skill` activation: the factory's other arm, and what a tool's own consent card (never a
|
|
42
|
+
* `Skill` call) spreads. */
|
|
43
|
+
export declare const NOT_BINDABLE: Readonly<{
|
|
44
|
+
readonly boundGrantAbsence: "not_bindable";
|
|
45
|
+
}>;
|
|
29
46
|
/** The leg's binding factory ({@link SkillGrantBindingMembers} for a call over `args`), the one every ask-mint site spreads. */
|
|
30
47
|
export type GrantBindingOf = (call: {
|
|
31
48
|
readonly toolName: string;
|
|
32
49
|
}, args: unknown) => SkillGrantBindingMembers;
|
|
33
50
|
/**
|
|
34
|
-
* The binding members of a stored parked row, as the inbox projection publishes them: the digest when it
|
|
35
|
-
* only as the value that digest hashes — {@link readBoundSkillGrants} whole, and equal to the digest by
|
|
36
|
-
* disagrees, or lists beside no digest, would show an approver one list while the approval binds another
|
|
51
|
+
* The binding members of a stored parked row, as the inbox projection publishes them — exactly one of three: the digest when it
|
|
52
|
+
* is a string (and the lists only as the value that digest hashes — {@link readBoundSkillGrants} whole, and equal to the digest by
|
|
53
|
+
* hash; a pair that disagrees, or lists beside no digest, would show an approver one list while the approval binds another), else
|
|
54
|
+
* the row's absence word when it is a member of the set, else `unreadable` — the row does not say what it binds.
|
|
37
55
|
*/
|
|
38
56
|
export declare function readBoundBinding(row: {
|
|
39
57
|
readonly boundGrantDigest?: unknown;
|
|
40
58
|
readonly boundGrants?: unknown;
|
|
41
|
-
|
|
42
|
-
|
|
59
|
+
readonly boundGrantAbsence?: unknown;
|
|
60
|
+
}): {
|
|
61
|
+
boundGrantDigest: string;
|
|
43
62
|
boundGrants?: BoundSkillGrants;
|
|
63
|
+
} | {
|
|
64
|
+
boundGrantAbsence: BoundGrantAbsence;
|
|
44
65
|
};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { boundInputHashOf } from "./canonical-json.js";
|
|
2
|
+
export const BOUND_GRANT_ABSENCES = Object.freeze(["not_bindable", "unreadable"]);
|
|
3
|
+
const BOUND_GRANT_ABSENCE_SET = new Set(BOUND_GRANT_ABSENCES);
|
|
4
|
+
const isBoundGrantAbsence = (v) => BOUND_GRANT_ABSENCE_SET.has(v);
|
|
2
5
|
export function readBoundSkillGrants(raw) {
|
|
3
6
|
if (typeof raw !== "object" || raw === null)
|
|
4
7
|
return undefined;
|
|
@@ -15,8 +18,13 @@ export function readBoundSkillGrants(raw) {
|
|
|
15
18
|
const disallowedTools = entries(disallowed);
|
|
16
19
|
return allowedTools !== undefined && disallowedTools !== undefined ? { skill, allowedTools, disallowedTools } : undefined;
|
|
17
20
|
}
|
|
21
|
+
export const NOT_BINDABLE = Object.freeze({ boundGrantAbsence: "not_bindable" });
|
|
18
22
|
export function readBoundBinding(row) {
|
|
19
|
-
const digest = row
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
const digest = row.boundGrantDigest;
|
|
24
|
+
if (typeof digest === "string") {
|
|
25
|
+
const grants = readBoundSkillGrants(row.boundGrants);
|
|
26
|
+
return { boundGrantDigest: digest, ...(grants !== undefined && boundInputHashOf(grants) === digest ? { boundGrants: grants } : {}) };
|
|
27
|
+
}
|
|
28
|
+
const word = row.boundGrantAbsence;
|
|
29
|
+
return { boundGrantAbsence: digest === undefined && isBoundGrantAbsence(word) ? word : "unreadable" };
|
|
22
30
|
}
|
|
@@ -2,7 +2,7 @@ import type { PermissionResult, ToolCallRequest } from "./tool-policy.js";
|
|
|
2
2
|
import type { PermissionMode } from "./ask-origin.js";
|
|
3
3
|
import { type PermissionSeatInputs, type PermissionSeatReading } from "./mode-policy.js";
|
|
4
4
|
import type { AgentTool } from "../internal/harness-types.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type SkillGrantBindingMembers } from "./skill-grant-binding.js";
|
|
6
6
|
import { type AllowEntryGateSeats, type AllowEntryInert, type AllowEntryJudgeContext, type CompiledAllowEntry } from "./allow-entry.js";
|
|
7
7
|
/** Who wrote a row: the `Skill` tool on a successful activation, prepare for a skill the person invoked by name, or a
|
|
8
8
|
* delegated child's seed from its parent. */
|
|
@@ -53,8 +53,9 @@ export declare function skillGrantDigest(skill: Pick<SkillGrantSource, "name" |
|
|
|
53
53
|
* `AskRequest` an approver is handed, an ancestor's, the parked row and (through the row) the inbox projection.
|
|
54
54
|
* `boundGrants` is the skill's name and canonical lists, `boundGrantDigest` its hash — the same value, so what a person was
|
|
55
55
|
* shown and what an approving resume is judged by cannot differ. The binding is a property of the CALL, whoever asked and
|
|
56
|
-
* however the question was worded.
|
|
57
|
-
* mounted, so a call by that name is not an activation)
|
|
56
|
+
* however the question was worded. Every other call — and any call when no skill is loaded (no engine `Skill` tool is
|
|
57
|
+
* mounted, so a call by that name is not an activation) — carries the absence word `not_bindable` instead: one of the two,
|
|
58
|
+
* always, so no reader infers the binding from a tool name.
|
|
58
59
|
*/
|
|
59
60
|
export declare function skillGrantBindingMembers(call: {
|
|
60
61
|
readonly toolName: string;
|
package/dist/core/skill-grant.js
CHANGED
|
@@ -3,8 +3,9 @@ import { parsePermissionRule } from "./permission-rules.js";
|
|
|
3
3
|
import { mountedToolPermissionSeat } from "./mode-policy.js";
|
|
4
4
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
5
5
|
import { boundInputHashOf } from "./canonical-json.js";
|
|
6
|
+
import { NOT_BINDABLE } from "./skill-grant-binding.js";
|
|
6
7
|
import { readToolNameList } from "./tool-name-list.js";
|
|
7
|
-
import { allowingRows, autoStripped,
|
|
8
|
+
import { allowingRows, autoStripped, compileAllowList, readRuleEntry, inertEntryPhrase, judgeAllowEntries, nonListValueLabel, nonStringMemberLabel, renderListEntry } from "./allow-entry.js";
|
|
8
9
|
export const SKILL_GRANT_ORIGINS = Object.freeze(["model", "user", "inherited"]);
|
|
9
10
|
const FROZEN_SKILL_GRANT_ORIGINS = new Set(["model", "inherited"]);
|
|
10
11
|
export function skillNamedBy(args, skills) {
|
|
@@ -25,13 +26,13 @@ export function skillGrantDigest(skill) {
|
|
|
25
26
|
return skillGrantBinding(skill).boundGrantDigest;
|
|
26
27
|
}
|
|
27
28
|
export function skillGrantBindingMembers(call, args, skills, skillToolName) {
|
|
28
|
-
return isSkillActivationCall(call, skills, skillToolName) ? skillGrantBinding(skillNamedBy(args, skills)) :
|
|
29
|
+
return isSkillActivationCall(call, skills, skillToolName) ? skillGrantBinding(skillNamedBy(args, skills)) : NOT_BINDABLE;
|
|
29
30
|
}
|
|
30
31
|
function isSkillActivationCall(call, skills, skillToolName) {
|
|
31
32
|
return skills.length > 0 && wireOf("skillGrant.binding", call) === skillToolName;
|
|
32
33
|
}
|
|
33
34
|
export function parkedSkillApprovalRefusal(row, args, skills, skillToolName, site = "claim") {
|
|
34
|
-
if (row.boundGrantDigest === undefined &&
|
|
35
|
+
if (row.boundGrantDigest === undefined && !isSkillActivationCall(row, skills, skillToolName))
|
|
35
36
|
return undefined;
|
|
36
37
|
if (skillGrantDigest(skillNamedBy(args, skills)) === row.boundGrantDigest)
|
|
37
38
|
return undefined;
|
|
@@ -244,7 +245,7 @@ function readCarriedRecords(raw, carrier) {
|
|
|
244
245
|
const { skill, behavior, entry, origin } = r;
|
|
245
246
|
if (typeof skill !== "string" || typeof entry !== "string" || (behavior !== "allow" && behavior !== "deny"))
|
|
246
247
|
return undefined;
|
|
247
|
-
if (
|
|
248
|
+
if (readRuleEntry(entry, behavior).kind === "inert")
|
|
248
249
|
return undefined;
|
|
249
250
|
if (carrier === "capture") {
|
|
250
251
|
out.push({ skill, behavior, entry });
|
|
@@ -51,7 +51,9 @@ export type SkillsDirectoryWarningCode =
|
|
|
51
51
|
/** An attachment was left out (non-text bytes, over budget, unreadable, or resolving outside the skill). */
|
|
52
52
|
| "attachment_skipped"
|
|
53
53
|
/** A file or directory entry existed but could not be read/resolved. */
|
|
54
|
-
| "read_failed"
|
|
54
|
+
| "read_failed"
|
|
55
|
+
/** A skill directory or its `SKILL.md` resolves outside {@link SkillsDirectoryOptions.containWithin} — skipped. */
|
|
56
|
+
| "outside_root";
|
|
55
57
|
/** One non-fatal diagnostic from a load. Safe to log: no file contents, only names/reasons. */
|
|
56
58
|
export interface SkillsDirectoryWarning {
|
|
57
59
|
code: SkillsDirectoryWarningCode;
|
|
@@ -69,6 +71,15 @@ export interface SkillsDirectoryOptions {
|
|
|
69
71
|
* ride a separate delivery cap at invoke time, so a generous value here does not enlarge the prompt.
|
|
70
72
|
*/
|
|
71
73
|
maxAttachmentBytes?: number;
|
|
74
|
+
/**
|
|
75
|
+
* The directory this root's skills must physically lie inside. Declared ⇒ every skill directory, and every
|
|
76
|
+
* `SKILL.md`, resolves (links followed) inside it or is skipped with an `outside_root` warning; the attachments
|
|
77
|
+
* already lie inside their skill directory, so they are inside it too. Absent ⇒ each root entry is a mount the
|
|
78
|
+
* deployment declared, trusted like the root itself. A root a third party writes (a repository's skills
|
|
79
|
+
* directory) declares the repository root here, so a link in it cannot load a skill from elsewhere on disk.
|
|
80
|
+
* Must name an existing directory; anything else throws, like an unreadable root.
|
|
81
|
+
*/
|
|
82
|
+
containWithin?: string;
|
|
72
83
|
}
|
|
73
84
|
/**
|
|
74
85
|
* Load every `SKILL.md`-bearing subdirectory of `dir` into a {@link SkillSpec}, ready for
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { join } from "node:path";
|
|
3
3
|
import { physicalPathOf } from "./physical-path.js";
|
|
4
|
+
import { isPhysicallyWithin } from "../tools/fs/path-containment.js";
|
|
4
5
|
import { assertNoRetiredKeys } from "./retired-keys.js";
|
|
5
6
|
import { thrownText } from "./thrown-value.js";
|
|
6
7
|
const SKILL_FILE = "SKILL.md";
|
|
@@ -167,12 +168,6 @@ function unquote(value) {
|
|
|
167
168
|
const inner = value.slice(1, -1);
|
|
168
169
|
return q === '"' ? inner.replace(/\\t/g, "\t").replace(/\\n/g, "\n") : inner;
|
|
169
170
|
}
|
|
170
|
-
function isInsideRoot(root, p) {
|
|
171
|
-
if (p === root)
|
|
172
|
-
return true;
|
|
173
|
-
const rel = relative(root, p);
|
|
174
|
-
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
175
|
-
}
|
|
176
171
|
function listRelativeFiles(base, dir, prefix, out, walk) {
|
|
177
172
|
let entries;
|
|
178
173
|
try {
|
|
@@ -222,7 +217,7 @@ function resolveInsideSkill(abs, rel, walk) {
|
|
|
222
217
|
walk.warn({ code: "attachment_skipped", skill: walk.skill, detail: `${rel}: could not be resolved (${errText(err)})` });
|
|
223
218
|
return undefined;
|
|
224
219
|
}
|
|
225
|
-
if (!
|
|
220
|
+
if (!isPhysicallyWithin(walk.realRoot, real)) {
|
|
226
221
|
walk.warn({
|
|
227
222
|
code: "attachment_skipped",
|
|
228
223
|
skill: walk.skill,
|
|
@@ -337,6 +332,18 @@ export function createSkillsFromDirectory(dir, options = {}) {
|
|
|
337
332
|
catch (err) {
|
|
338
333
|
throw new Error(`skills directory could not be read: ${dir} (${errText(err)})`);
|
|
339
334
|
}
|
|
335
|
+
const containWithin = options.containWithin;
|
|
336
|
+
if (containWithin !== undefined) {
|
|
337
|
+
const isDir = typeof containWithin === "string" && statSync(containWithin, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
338
|
+
if (!isDir)
|
|
339
|
+
throw new Error(`skills directory containment root is not a readable directory: ${String(containWithin)}`);
|
|
340
|
+
}
|
|
341
|
+
const outside = (skill, path, what) => {
|
|
342
|
+
if (containWithin === undefined || isPhysicallyWithin(containWithin, path))
|
|
343
|
+
return false;
|
|
344
|
+
warn({ code: "outside_root", skill, detail: `${what} resolves outside ${containWithin} — skipped` });
|
|
345
|
+
return true;
|
|
346
|
+
};
|
|
340
347
|
const dirNames = [];
|
|
341
348
|
for (const e of entries) {
|
|
342
349
|
if (e.isDirectory()) {
|
|
@@ -357,6 +364,8 @@ export function createSkillsFromDirectory(dir, options = {}) {
|
|
|
357
364
|
const skills = [];
|
|
358
365
|
for (const name of dirNames) {
|
|
359
366
|
const skillDir = join(dir, name);
|
|
367
|
+
if (outside(name, skillDir, "its directory") || outside(name, join(skillDir, SKILL_FILE), SKILL_FILE))
|
|
368
|
+
continue;
|
|
360
369
|
let text;
|
|
361
370
|
try {
|
|
362
371
|
text = readFileSync(join(skillDir, SKILL_FILE), "utf8");
|
|
@@ -61,6 +61,7 @@ export declare const EXPECTED_LISTBYSCOPE_SUMMARIES: ({
|
|
|
61
61
|
mandated: boolean;
|
|
62
62
|
mandate: string;
|
|
63
63
|
origin: string;
|
|
64
|
+
boundGrantAbsence: string;
|
|
64
65
|
spentMicroUsd?: undefined;
|
|
65
66
|
} | {
|
|
66
67
|
token: string;
|
|
@@ -79,4 +80,5 @@ export declare const EXPECTED_LISTBYSCOPE_SUMMARIES: ({
|
|
|
79
80
|
mandated?: undefined;
|
|
80
81
|
mandate?: undefined;
|
|
81
82
|
origin?: undefined;
|
|
83
|
+
boundGrantAbsence?: undefined;
|
|
82
84
|
})[];
|
|
@@ -18,6 +18,7 @@ export function createCheckpointFixture(over = {}) {
|
|
|
18
18
|
toolName: "Write",
|
|
19
19
|
args: { path: "/x", content: "y" },
|
|
20
20
|
boundInputHash: "h0",
|
|
21
|
+
boundGrantAbsence: "not_bindable",
|
|
21
22
|
batchToolCallIds: ["call-3"],
|
|
22
23
|
completedCallIds: [],
|
|
23
24
|
},
|
|
@@ -461,6 +462,7 @@ export async function checkpointListByScopeSummaries(store) {
|
|
|
461
462
|
origin: "policy",
|
|
462
463
|
mandated: true,
|
|
463
464
|
mandate: "tool_marks",
|
|
465
|
+
boundGrantAbsence: "not_bindable",
|
|
464
466
|
toolCallId: "call-pr",
|
|
465
467
|
toolName: "open_pr",
|
|
466
468
|
args: { repo: "x" },
|
|
@@ -503,6 +505,7 @@ export const EXPECTED_LISTBYSCOPE_SUMMARIES = [
|
|
|
503
505
|
mandated: true,
|
|
504
506
|
mandate: "tool_marks",
|
|
505
507
|
origin: "policy",
|
|
508
|
+
boundGrantAbsence: "not_bindable",
|
|
506
509
|
},
|
|
507
510
|
{
|
|
508
511
|
token: "tok-resource",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
*/
|
|
39
39
|
import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
40
40
|
import type { RunnerDeps } from "./runner-deps.js";
|
|
41
|
+
import { type ReadDenyDeploymentSeat } from "./read-face-seats.js";
|
|
41
42
|
/**
|
|
42
43
|
* The closed set of hot-swappable deployment seats.
|
|
43
44
|
* - `models` — the model catalog generation (the same-name redirect, re-pricing and retirement rules
|
|
@@ -59,8 +60,12 @@ import type { RunnerDeps } from "./runner-deps.js";
|
|
|
59
60
|
* - `contextSettings` — the per-model context-window overlay ({@link RunnerDeps.contextSettings}); screened
|
|
60
61
|
* by the same value gate construction applies (`assertContextSettingsValue`). THE ONE SEAT
|
|
61
62
|
* IN-FLIGHT LEGS RE-READ — see the header: it carries geometry, not route.
|
|
63
|
+
* - `readDenyPatterns` / `readDenyBuiltinTiers` / `readDenyBuiltinExclude` — the deployment's read-deny
|
|
64
|
+
* declaration (additions, built-in tier selection, built-in row removal); screened by the
|
|
65
|
+
* gates a prepare applies ({@link screenReadDenySeat}). A resumed leg compiles against the
|
|
66
|
+
* seats in force at its prepare (the checkpoint keeps additions only, stricter-wins).
|
|
62
67
|
*/
|
|
63
|
-
export declare const SWAPPABLE_DEP_SEATS: readonly ["models", "tiers", "readFace", "writeFace", "contextSettings"];
|
|
68
|
+
export declare const SWAPPABLE_DEP_SEATS: readonly ["models", "tiers", "readFace", "writeFace", "contextSettings", "readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude"];
|
|
64
69
|
export type SwappableDepSeat = (typeof SWAPPABLE_DEP_SEATS)[number];
|
|
65
70
|
/** The candidate a swap takes: the swappable seats of {@link RunnerDeps}, each optional (presence = instruction). */
|
|
66
71
|
export type SwappableDeps = Pick<RunnerDeps, SwappableDepSeat>;
|
|
@@ -77,6 +82,9 @@ export declare const SWAP_SEAT_NOTICE: {
|
|
|
77
82
|
readonly readFace: "config.read_face_swapped";
|
|
78
83
|
readonly writeFace: "config.write_face_swapped";
|
|
79
84
|
readonly contextSettings: "config.context_settings_swapped";
|
|
85
|
+
readonly readDenyPatterns: "config.read_deny_swapped";
|
|
86
|
+
readonly readDenyBuiltinTiers: "config.read_deny_swapped";
|
|
87
|
+
readonly readDenyBuiltinExclude: "config.read_deny_swapped";
|
|
80
88
|
};
|
|
81
89
|
/** The fence over the notice table: `never` while every seat has a row. */
|
|
82
90
|
export type SwapNoticeTableCoversEverySeat = AssertAllKeysHandled<Exclude<SwappableDepSeat, keyof typeof SWAP_SEAT_NOTICE>>;
|
|
@@ -106,3 +114,9 @@ export interface ScreenedSwappableDeps {
|
|
|
106
114
|
* `assertContainmentFaceValue`), so a value the door admits is a value every later prepare admits.
|
|
107
115
|
*/
|
|
108
116
|
export declare function screenSwappableDeps(next: unknown): ScreenedSwappableDeps;
|
|
117
|
+
/**
|
|
118
|
+
* Announce a swap of the deployment's read-deny declaration, after the write: ONE `config.read_deny_swapped`
|
|
119
|
+
* line however many of its three seats the swap named (the line's contract lives at the notice catalog,
|
|
120
|
+
* `EngineNotice.code`).
|
|
121
|
+
*/
|
|
122
|
+
export declare function announceReadDenySwap(onNotice: RunnerDeps["onNotice"], seats: readonly SwappableDepSeat[], inForce: Pick<RunnerDeps, ReadDenyDeploymentSeat>): void;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { assertContainmentFaceValue } from "../tools/fs/containment-face.js";
|
|
2
2
|
import { assertContextSettingsValue } from "./context-settings.js";
|
|
3
|
-
|
|
3
|
+
import { READ_DENY_DEPLOYMENT_SEATS, screenReadDenySeat } from "./read-face-seats.js";
|
|
4
|
+
import { deliverEngineNotice } from "./engine-notice.js";
|
|
5
|
+
import { uuidv7 } from "../internal/harness.js";
|
|
6
|
+
export const SWAPPABLE_DEP_SEATS = Object.freeze(["models", "tiers", "readFace", "writeFace", "contextSettings", "readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude"]);
|
|
4
7
|
const SWAPPABLE_DEP_SEAT_SET = new Set(SWAPPABLE_DEP_SEATS);
|
|
5
8
|
export function isSwappableDepSeat(k) {
|
|
6
9
|
return SWAPPABLE_DEP_SEAT_SET.has(k);
|
|
@@ -11,6 +14,9 @@ export const SWAP_SEAT_NOTICE = {
|
|
|
11
14
|
readFace: "config.read_face_swapped",
|
|
12
15
|
writeFace: "config.write_face_swapped",
|
|
13
16
|
contextSettings: "config.context_settings_swapped",
|
|
17
|
+
readDenyPatterns: "config.read_deny_swapped",
|
|
18
|
+
readDenyBuiltinTiers: "config.read_deny_swapped",
|
|
19
|
+
readDenyBuiltinExclude: "config.read_deny_swapped",
|
|
14
20
|
};
|
|
15
21
|
export function screenSwappableDeps(next) {
|
|
16
22
|
if (next === null || typeof next !== "object" || Array.isArray(next)) {
|
|
@@ -54,6 +60,11 @@ export function screenSwappableDeps(next) {
|
|
|
54
60
|
case "contextSettings":
|
|
55
61
|
candidate.contextSettings = assertContextSettingsValue(v, "swapDeps");
|
|
56
62
|
break;
|
|
63
|
+
case "readDenyPatterns":
|
|
64
|
+
case "readDenyBuiltinTiers":
|
|
65
|
+
case "readDenyBuiltinExclude":
|
|
66
|
+
candidate[seat] = screenReadDenySeat(seat, v);
|
|
67
|
+
break;
|
|
57
68
|
default: {
|
|
58
69
|
const _exhaustive = seat;
|
|
59
70
|
throw new Error(`swapDeps: unhandled seat ${String(_exhaustive)}`);
|
|
@@ -62,3 +73,16 @@ export function screenSwappableDeps(next) {
|
|
|
62
73
|
}
|
|
63
74
|
return { seats: present, candidate: candidate };
|
|
64
75
|
}
|
|
76
|
+
export function announceReadDenySwap(onNotice, seats, inForce) {
|
|
77
|
+
const named = READ_DENY_DEPLOYMENT_SEATS.filter((s) => seats.includes(s));
|
|
78
|
+
if (named.length === 0)
|
|
79
|
+
return;
|
|
80
|
+
const patterns = inForce.readDenyPatterns?.length ?? null;
|
|
81
|
+
const tiers = inForce.readDenyBuiltinTiers !== undefined ? [...inForce.readDenyBuiltinTiers] : null;
|
|
82
|
+
const exclude = inForce.readDenyBuiltinExclude?.length ?? null;
|
|
83
|
+
deliverEngineNotice(onNotice, {
|
|
84
|
+
code: "config.read_deny_swapped",
|
|
85
|
+
message: `deployment read-deny declaration swapped (${named.join(", ")}): ${patterns ?? "no"} addition(s), built-in tiers ${tiers === null ? "(default)" : `[${tiers.join(", ")}]`}, ${exclude ?? "no"} built-in row removal(s); in-flight legs keep the deny face they prepared under, new legs compile against the new declaration`,
|
|
86
|
+
detail: { seats: named, patterns, tiers, exclude, occurrenceId: uuidv7() },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -52,20 +52,22 @@
|
|
|
52
52
|
* | any layer | the judgment's budget (one dependency timeout, or a | unanswerable `timed_out` |
|
|
53
53
|
* | | batch's shared deadline) is spent | |
|
|
54
54
|
*
|
|
55
|
-
* NEVER AN APPROVER.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
55
|
+
* NEVER AN APPROVER. Every probe STATES that nobody can be asked about it — `approvalReach: "none"` on the request
|
|
56
|
+
* (`ToolCallRequest.approvalReach`) — and every policy layer is handed a LIVE signal of its own: it ends when the judge stops
|
|
57
|
+
* waiting for that layer (the layer answered, or its share of the judgment's budget is spent) or when the run's signal ends.
|
|
58
|
+
* An abort means only that: the task ended, or the judge no longer waits — never "there is no approver". Every engine
|
|
59
|
+
* policy factory reads the statement before reaching a seat (the approval factory answers a deny attested `no_approver`,
|
|
60
|
+
* read here as `approval_seat`; the inherited PreToolUse constraint does the same before invoking its hook), and the
|
|
61
|
+
* engine's own guards composed into a deployment's policy judge the probe as they judge a call — a lookup bounded by a
|
|
62
|
+
* live deadline answers. A deny that carries no engine settlement is a rule's deny and stands. RESIDUALS (stated, not
|
|
63
|
+
* closed): a deployment closure that ignores the statement and reaches an approver IS asked — the judge waits for it at
|
|
64
|
+
* most the judgment's budget (`timed_out`: the write face refuses, the notice face stays silent), then its signal ends
|
|
65
|
+
* so a closure that honors it releases its approval; a deployment wrapper that answers the statement with a deny of its
|
|
66
|
+
* own (no settlement) is read as a deny — the attachment face then withholds, the notice face stays silent.
|
|
65
67
|
*
|
|
66
|
-
* THE PROBE'S IDENTITY. Every layer sees the probed tool's name (`Read` / `Write`), the probed arguments,
|
|
67
|
-
* synthetic call id (`<face>-visibility-probe` for a read, `<face>-write-probe` for a write;
|
|
68
|
-
* {@link syntheticWriteProbeId}), so a deployment's audit can tell a probe from a model's call. The write face stamps the
|
|
68
|
+
* THE PROBE'S IDENTITY. Every layer sees the probed tool's name (`Read` / `Write`), the probed arguments, the statement
|
|
69
|
+
* above, and a stable synthetic call id (`<face>-visibility-probe` for a read, `<face>-write-probe` for a write;
|
|
70
|
+
* {@link syntheticReadProbeId}, {@link syntheticWriteProbeId}), so a deployment's audit can tell a probe from a model's call. The write face stamps the
|
|
69
71
|
* facts the live gate stamps on a write (the roster's `Write` face, the tracked cwd and root, the advertised writable
|
|
70
72
|
* directories, the data root's exemption): a containment fence and a data-root exemption judge the probe as they judge the call.
|
|
71
73
|
*/
|
|
@@ -120,7 +122,7 @@ export interface SyntheticReadLayers {
|
|
|
120
122
|
};
|
|
121
123
|
/** The leg's target-reading stamp: the probe's request carries a reading of its target, as the live gate's does. */
|
|
122
124
|
stampCallTarget?: CallTargetStamp;
|
|
123
|
-
/** The run's signal
|
|
125
|
+
/** The run's signal: its end ends the judgment's every wait (each policy layer's own signal ends with it). */
|
|
124
126
|
signal?: AbortSignal;
|
|
125
127
|
}
|
|
126
128
|
/** The arguments a synthetic `Read` carries, and the facts a request stamps. */
|