@sema-agent/core 7.53.0 → 7.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +291 -1
- package/LICENSE +3 -1
- package/dist/agents/peer-session-drain.d.ts +0 -1
- package/dist/agents/peer-session-drain.js +0 -2
- package/dist/agents/send-message-tool.js +3 -4
- package/dist/agents/subagent.d.ts +3 -5
- package/dist/agents/subagent.js +18 -11
- package/dist/agents/tool-filter.d.ts +7 -4
- package/dist/agents/tool-filter.js +7 -3
- package/dist/core/a2a.js +4 -2
- package/dist/core/allow-entry.d.ts +1 -0
- package/dist/core/allow-entry.js +1 -0
- package/dist/core/approval-object.d.ts +87 -0
- package/dist/core/approval-object.js +85 -0
- package/dist/core/arg-summary.d.ts +3 -2
- package/dist/core/arg-summary.js +17 -11
- package/dist/core/ask-origin.d.ts +43 -27
- package/dist/core/ask-origin.js +20 -11
- package/dist/core/ask-question.d.ts +0 -1
- package/dist/core/ask-question.js +4 -67
- package/dist/core/ask-unresolvable-notice.d.ts +3 -2
- package/dist/core/auto-compaction.d.ts +49 -50
- package/dist/core/auto-compaction.js +41 -51
- package/dist/core/auto-mode-prompt.d.ts +4 -2
- package/dist/core/auto-mode-prompt.js +4 -3
- package/dist/core/canonical-json.d.ts +69 -0
- package/dist/core/canonical-json.js +130 -8
- package/dist/core/checkpoint-requirements.d.ts +13 -7
- package/dist/core/checkpoint-requirements.js +4 -1
- package/dist/core/checkpoint-store.d.ts +33 -18
- package/dist/core/checkpoint-store.js +5 -4
- package/dist/core/child-clamp-seats.d.ts +45 -6
- package/dist/core/child-clamp-seats.js +56 -4
- package/dist/core/consolidate-scope.d.ts +1 -1
- package/dist/core/cross-boundary-taint.d.ts +213 -0
- package/dist/core/cross-boundary-taint.js +196 -0
- package/dist/core/decision-receipt.d.ts +44 -1
- package/dist/core/decision-receipt.js +16 -0
- package/dist/core/effective-path-target.d.ts +3 -0
- package/dist/core/effective-path-target.js +4 -0
- package/dist/core/engine-notice.d.ts +34 -2
- package/dist/core/engine-notice.js +17 -1
- package/dist/core/engine-storage-backend.d.ts +73 -0
- package/dist/core/engine-storage-backend.js +1 -0
- package/dist/core/file-history-store.d.ts +6 -2
- package/dist/core/file-history-store.js +1 -0
- package/dist/core/gate-fold.js +10 -2
- package/dist/core/gate-lanes.js +30 -21
- package/dist/core/gate-outcome.d.ts +7 -3
- package/dist/core/governance-codes.d.ts +7 -2
- package/dist/core/governance-codes.js +6 -0
- package/dist/core/hooks.d.ts +14 -14
- package/dist/core/hooks.js +1 -2
- package/dist/core/locked-config.d.ts +28 -1
- package/dist/core/locked-config.js +30 -3
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/mcp-server-spec.d.ts +66 -9
- package/dist/core/mcp-server-spec.js +52 -19
- package/dist/core/mcp.js +5 -4
- package/dist/core/memory-engine/engine.js +2 -1
- package/dist/core/memory-engine/export-bundle.d.ts +1 -1
- package/dist/core/memory-engine/export-bundle.js +1 -1
- package/dist/core/memory-engine/file-backend.d.ts +2 -6
- package/dist/core/memory-engine/file-backend.js +1 -12
- package/dist/core/memory.d.ts +3 -3
- package/dist/core/mode-policy.d.ts +13 -8
- package/dist/core/mode-policy.js +3 -2
- package/dist/core/mods/kernel/receive.d.ts +2 -1
- package/dist/core/mods/kernel/receive.js +7 -2
- package/dist/core/parent-spec-seat.d.ts +10 -13
- package/dist/core/parent-spec-seat.js +14 -6
- package/dist/core/pause-registry.d.ts +35 -1
- package/dist/core/pause-registry.js +10 -0
- package/dist/core/permission-rule-consent.d.ts +18 -5
- package/dist/core/permission-rule-consent.js +13 -8
- package/dist/core/permission-rule-model.d.ts +29 -6
- package/dist/core/permission-rule-model.js +6 -1
- package/dist/core/permission-rule-org.d.ts +37 -0
- package/dist/core/permission-rule-org.js +34 -42
- package/dist/core/permission-rule-session.js +2 -2
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +1 -0
- package/dist/core/permission-rule-sync.js +3 -2
- package/dist/core/read-only-shell.d.ts +1 -1
- package/dist/core/read-only-shell.js +30 -9
- package/dist/core/remote-env.d.ts +41 -1
- package/dist/core/remote-env.js +18 -0
- package/dist/core/resume-startup-cause.d.ts +18 -0
- package/dist/core/resume-startup-cause.js +12 -0
- package/dist/core/runner/approval-feedback.d.ts +46 -0
- package/dist/core/runner/approval-feedback.js +41 -0
- package/dist/core/runner/compaction-call-options.d.ts +3 -3
- package/dist/core/runner/compaction-call-options.js +2 -2
- package/dist/core/runner/contracts.d.ts +33 -41
- package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
- package/dist/core/runner/cross-boundary-lane.js +190 -0
- package/dist/core/runner/decide-continuation.d.ts +4 -4
- package/dist/core/runner/decide-continuation.js +3 -1
- package/dist/core/runner/gate-exit.d.ts +32 -12
- package/dist/core/runner/gate-exit.js +75 -33
- package/dist/core/runner/inherited-ask-grants.js +2 -2
- package/dist/core/runner/person-path-rows.d.ts +2 -2
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +7 -9
- package/dist/core/runner/prepare-acquire-reconcile.js +8 -6
- package/dist/core/runner/prepare-ask-lane.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
- package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
- package/dist/core/runner/prepare-config-doors.d.ts +12 -15
- package/dist/core/runner/prepare-config-doors.js +8 -3
- package/dist/core/runner/prepare-context-lane.js +6 -3
- package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
- package/dist/core/runner/prepare-delegation-surface.js +2 -3
- package/dist/core/runner/prepare-execution-env.d.ts +1 -2
- package/dist/core/runner/prepare-execution-env.js +2 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
- package/dist/core/runner/prepare-gate-stations.js +4 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
- package/dist/core/runner/prepare-hands-readface.js +1 -1
- package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-park-ask.d.ts +1 -1
- package/dist/core/runner/prepare-park-ask.js +9 -3
- package/dist/core/runner/prepare-policy-chain.js +2 -7
- package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
- package/dist/core/runner/prepare-safety-scan.js +34 -27
- package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
- package/dist/core/runner/prepare-suspend-saga.js +6 -2
- package/dist/core/runner/prepare-task.js +13 -10
- package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
- package/dist/core/runner/prepare-turn-wiring.js +14 -49
- package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
- package/dist/core/runner/prepare-wiring-manifest.js +2 -2
- package/dist/core/runner/prepare-workspace-restore.js +2 -2
- package/dist/core/runner/resume-admission.js +9 -9
- package/dist/core/runner/resume-apply.js +4 -4
- package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
- package/dist/core/runner/resume-policy-outcome.js +18 -15
- package/dist/core/runner/resume-preflight.js +2 -2
- package/dist/core/runner/resume-review-outcome.js +3 -3
- package/dist/core/runner/run-leg.js +57 -33
- package/dist/core/runner/run-settle-and-teardown.js +2 -0
- package/dist/core/runner/run-terminal-adoption.js +15 -4
- package/dist/core/runner/session-file-state-replay.d.ts +5 -2
- package/dist/core/runner/session-file-state-replay.js +3 -2
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/tool-face-overlay.d.ts +24 -6
- package/dist/core/runner/tool-face-overlay.js +24 -15
- package/dist/core/runner-deps.d.ts +9 -9
- package/dist/core/session-store.d.ts +35 -16
- package/dist/core/session-store.js +20 -12
- package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
- package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
- package/dist/core/shell-wrapper-table.d.ts +5 -3
- package/dist/core/shell-wrapper-table.js +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
- package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
- package/dist/core/store-contracts/session-store-contract.js +683 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
- package/dist/core/task-event.d.ts +6 -4
- package/dist/core/task-registry-agent.js +1 -0
- package/dist/core/task-registry-shared.d.ts +5 -0
- package/dist/core/task-registry.d.ts +7 -0
- package/dist/core/task-registry.js +17 -0
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +6 -6
- package/dist/core/tighten-task-spec.d.ts +6 -4
- package/dist/core/tighten-task-spec.js +2 -2
- package/dist/core/tool-catalog-entries.d.ts +1 -1
- package/dist/core/tool-catalog-entries.js +52 -52
- package/dist/core/tool-face-reader.d.ts +49 -0
- package/dist/core/tool-face-reader.js +94 -0
- package/dist/core/tool-face.d.ts +17 -8
- package/dist/core/tool-face.js +1 -0
- package/dist/core/tool-policy.d.ts +64 -23
- package/dist/core/tool-policy.js +62 -56
- package/dist/core/tool-roster.d.ts +46 -28
- package/dist/core/tool-roster.js +91 -14
- package/dist/core/tool-spec-members.js +2 -2
- package/dist/core/tool-spec.d.ts +20 -4
- package/dist/core/tools.js +2 -0
- package/dist/core/trace.d.ts +24 -2
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +7 -0
- package/dist/core/untrusted-text.js +3 -8
- package/dist/core/user-attachment.d.ts +43 -0
- package/dist/core/user-attachment.js +92 -15
- package/dist/engine/compaction/compaction.d.ts +2 -2
- package/dist/engine/compaction/utils.d.ts +2 -2
- package/dist/engine/harness/types.d.ts +7 -4
- package/dist/engine/llm/types.d.ts +7 -2
- package/dist/engine/llm/types.js +2 -1
- package/dist/engine/session/parent-chain-guard.d.ts +10 -0
- package/dist/engine/session/parent-chain-guard.js +10 -0
- package/dist/engine/session/repo-utils.d.ts +5 -0
- package/dist/engine/session/repo-utils.js +9 -0
- package/dist/engine/session/storage-base.js +5 -2
- package/dist/index.d.ts +23 -9
- package/dist/index.js +19 -8
- package/dist/internal/harness.d.ts +1 -0
- package/dist/internal/harness.js +1 -0
- package/dist/internal/llm.d.ts +1 -1
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +1 -0
- package/dist/orchestration/workflow-governance.d.ts +1 -1
- package/dist/orchestration/workflow.d.ts +3 -0
- package/dist/orchestration/workflow.js +2 -1
- package/dist/prompt-assembly/catalog/schema.d.ts +3 -0
- package/dist/prompt-assembly/catalog/schema.js +3 -0
- package/dist/prompt-assembly/overlay.js +2 -2
- package/dist/prompt-assembly/select.js +2 -2
- package/dist/prompts/default.d.ts +1 -1
- package/dist/shell/lex.d.ts +87 -8
- package/dist/shell/lex.js +283 -49
- package/dist/stores/file/index.d.ts +8 -6
- package/dist/tools/artifact/artifact-tool.js +10 -0
- package/dist/tools/fs/bash-program-position.d.ts +3 -15
- package/dist/tools/fs/bash-program-position.js +3 -10
- package/dist/tools/fs/bash-readonly-classifier.js +2 -0
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-bash.js +72 -101
- package/dist/tools/fs/fs-read.d.ts +12 -0
- package/dist/tools/fs/fs-read.js +10 -9
- package/dist/tools/fs/fs-shared.d.ts +27 -31
- package/dist/tools/fs/fs-shared.js +12 -15
- package/dist/tools/fs/fs-write.js +1 -1
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/read-deny-home.d.ts +1 -1
- package/dist/tools/fs/read-deny-home.js +5 -4
- package/dist/tools/fs/read-deny.d.ts +16 -3
- package/dist/tools/fs/read-deny.js +12 -9
- package/dist/tools/fs/safety.d.ts +7 -6
- package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
- package/dist/tools/fs/shell-read-bindings.js +15 -16
- package/dist/tools/fs/shell-read-device.js +1 -1
- package/dist/tools/fs/shell-read-plan.js +36 -47
- package/dist/tools/fs/shell-read-word.d.ts +1 -6
- package/dist/tools/fs/shell-read-word.js +2 -20
- package/dist/tools/fs/shell-reentry-text.js +1 -1
- package/dist/tools/web.d.ts +2 -2
- package/package.json +4 -1
- package/test/export-surface.snapshot.json +5654 -5520
- package/wire-changes/core@7.53.0.json +30 -8
- package/wire-changes/core@7.54.0.json +2870 -0
- package/wire-changes/core@7.54.1.json +37 -0
- package/dist/core/shell-scan.d.ts +0 -60
- package/dist/core/shell-scan.js +0 -183
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { hasConversationContent } from "./session.js";
|
|
2
|
+
export const CROSS_BOUNDARY_TAINT_BITS = Object.freeze(["targetExecutionArmed", "offTargetReadObserved"]);
|
|
3
|
+
export const CROSS_BOUNDARY_POSTURES = Object.freeze(["single_user", "multi_tenant"]);
|
|
4
|
+
const postureSeatIsTheSet = true;
|
|
5
|
+
void postureSeatIsTheSet;
|
|
6
|
+
export const BOUNDARY_LEGS = Object.freeze(["env", "outside_read", "outside_write"]);
|
|
7
|
+
const LEG_SET = new Set(BOUNDARY_LEGS);
|
|
8
|
+
const OBLIGATION_TABLE = {
|
|
9
|
+
env_to_target: { host_lane: "none", armed: "none", armed_observed: "mandate_if_governed" },
|
|
10
|
+
outside_read: { host_lane: "none", armed: "record", armed_observed: "none" },
|
|
11
|
+
outside_write: { host_lane: "none", armed: "mandate_if_governed", armed_observed: "mandate_if_governed" },
|
|
12
|
+
};
|
|
13
|
+
const ENV_LEG_ROWS = { target: ["env_to_target"], off_target: ["outside_read", "outside_write"] };
|
|
14
|
+
const TREE_MEMBERS = [...CROSS_BOUNDARY_TAINT_BITS, "governed"];
|
|
15
|
+
function assertTree(tree, at) {
|
|
16
|
+
if (typeof tree !== "object" || tree === null || Array.isArray(tree))
|
|
17
|
+
throw new TypeError(`${at}: a cross-boundary tree is a record of presence bits (got ${tree === null ? "null" : Array.isArray(tree) ? "an array" : typeof tree})`);
|
|
18
|
+
for (const member of TREE_MEMBERS) {
|
|
19
|
+
const v = tree[member];
|
|
20
|
+
if (v !== undefined && v !== true)
|
|
21
|
+
throw new TypeError(`${at}: tree member "${member}" is presence-coded (true or absent), got ${typeof v}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function crossBoundaryObligation(legs, reachedEnvIsTarget, tree) {
|
|
25
|
+
if (!Array.isArray(legs))
|
|
26
|
+
throw new TypeError(`crossBoundaryObligation: legs is a list of ${BOUNDARY_LEGS.join(" | ")}`);
|
|
27
|
+
if (typeof reachedEnvIsTarget !== "boolean")
|
|
28
|
+
throw new TypeError("crossBoundaryObligation: reachedEnvIsTarget is a boolean");
|
|
29
|
+
assertTree(tree, "crossBoundaryObligation");
|
|
30
|
+
const column = tree.targetExecutionArmed !== true ? "host_lane" : tree.offTargetReadObserved === true ? "armed_observed" : "armed";
|
|
31
|
+
let mandate = false;
|
|
32
|
+
let record = false;
|
|
33
|
+
for (const leg of legs) {
|
|
34
|
+
if (!LEG_SET.has(leg))
|
|
35
|
+
throw new TypeError(`crossBoundaryObligation: ${JSON.stringify(leg)} is not a boundary leg (${BOUNDARY_LEGS.join(", ")})`);
|
|
36
|
+
const rows = leg === "env" ? ENV_LEG_ROWS[reachedEnvIsTarget ? "target" : "off_target"] : [leg];
|
|
37
|
+
for (const row of rows) {
|
|
38
|
+
const cell = OBLIGATION_TABLE[row][column];
|
|
39
|
+
if (cell === "record")
|
|
40
|
+
record = true;
|
|
41
|
+
else if (cell === "mandate_if_governed" && crossBoundaryTreeGoverned(tree))
|
|
42
|
+
mandate = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return Object.freeze({ mandate, record });
|
|
46
|
+
}
|
|
47
|
+
export function crossBoundaryTreeGoverned(tree) {
|
|
48
|
+
return tree.governed === true;
|
|
49
|
+
}
|
|
50
|
+
export function joinCrossBoundaryTree(a, b) {
|
|
51
|
+
assertTree(a, "joinCrossBoundaryTree");
|
|
52
|
+
assertTree(b, "joinCrossBoundaryTree");
|
|
53
|
+
const out = {};
|
|
54
|
+
if (a.targetExecutionArmed === true || b.targetExecutionArmed === true)
|
|
55
|
+
out.targetExecutionArmed = true;
|
|
56
|
+
if (a.offTargetReadObserved === true || b.offTargetReadObserved === true)
|
|
57
|
+
out.offTargetReadObserved = true;
|
|
58
|
+
if (a.governed === true || b.governed === true)
|
|
59
|
+
out.governed = true;
|
|
60
|
+
return Object.freeze(out);
|
|
61
|
+
}
|
|
62
|
+
export function crossBoundaryTreeCovers(covering, tree) {
|
|
63
|
+
assertTree(covering, "crossBoundaryTreeCovers");
|
|
64
|
+
assertTree(tree, "crossBoundaryTreeCovers");
|
|
65
|
+
return TREE_MEMBERS.every((m) => tree[m] !== true || covering[m] === true);
|
|
66
|
+
}
|
|
67
|
+
export const STRICTEST_CROSS_BOUNDARY_TREE = Object.freeze({ targetExecutionArmed: true, offTargetReadObserved: true, governed: true });
|
|
68
|
+
export const CROSS_BOUNDARY_RECORD_TYPE = "engine:cross_boundary";
|
|
69
|
+
export function crossBoundaryRecordData(tree) {
|
|
70
|
+
assertTree(tree, "crossBoundaryRecordData");
|
|
71
|
+
if (tree.targetExecutionArmed !== true)
|
|
72
|
+
throw new TypeError("crossBoundaryRecordData: only an armed tree is recorded");
|
|
73
|
+
return Object.freeze({ targetExecutionArmed: true, ...(tree.offTargetReadObserved === true ? { offTargetReadObserved: true } : {}), ...(tree.governed === true ? { governed: true } : {}) });
|
|
74
|
+
}
|
|
75
|
+
const RECORD_MEMBERS = TREE_MEMBERS;
|
|
76
|
+
function ownedRecordTree(data) {
|
|
77
|
+
if (typeof data !== "object" || data === null || Array.isArray(data) || Object.getPrototypeOf(data) !== Object.prototype)
|
|
78
|
+
return { why: `its data is ${data === null ? "null" : Array.isArray(data) ? "an array" : typeof data === "object" ? "not a plain record" : typeof data}` };
|
|
79
|
+
const owned = new Map();
|
|
80
|
+
for (const key of Reflect.ownKeys(data)) {
|
|
81
|
+
if (typeof key !== "string" || !RECORD_MEMBERS.includes(key))
|
|
82
|
+
return { why: `its data carries ${typeof key === "string" ? JSON.stringify(key.slice(0, 40)) : "a symbol key"}, which is not a member` };
|
|
83
|
+
const d = Object.getOwnPropertyDescriptor(data, key);
|
|
84
|
+
if (d === undefined || !("value" in d))
|
|
85
|
+
return { why: `"${key}" is not a data member` };
|
|
86
|
+
owned.set(key, d.value);
|
|
87
|
+
}
|
|
88
|
+
if (owned.get("targetExecutionArmed") !== true)
|
|
89
|
+
return { why: `"targetExecutionArmed" is not true` };
|
|
90
|
+
for (const m of ["offTargetReadObserved", "governed"])
|
|
91
|
+
if (owned.get(m) !== undefined && owned.get(m) !== true)
|
|
92
|
+
return { why: `"${m}" is neither true nor absent` };
|
|
93
|
+
return { tree: Object.freeze({ targetExecutionArmed: true, ...(owned.get("offTargetReadObserved") === true ? { offTargetReadObserved: true } : {}), ...(owned.get("governed") === true ? { governed: true } : {}) }) };
|
|
94
|
+
}
|
|
95
|
+
function readBranchEntry(branch, i) {
|
|
96
|
+
let id;
|
|
97
|
+
try {
|
|
98
|
+
const e = branch[i];
|
|
99
|
+
if (typeof e !== "object" || e === null)
|
|
100
|
+
return { kind: "other", content: false };
|
|
101
|
+
const { type, customType } = e;
|
|
102
|
+
if (type !== "custom" || customType !== CROSS_BOUNDARY_RECORD_TYPE)
|
|
103
|
+
return { kind: "other", content: typeof type === "string" && hasConversationContent([{ type }]) };
|
|
104
|
+
const rawId = e.id;
|
|
105
|
+
id = typeof rawId === "string" ? rawId : undefined;
|
|
106
|
+
const read = ownedRecordTree(e.data);
|
|
107
|
+
return "why" in read ? { kind: "unreadable", ...(id !== undefined ? { id } : {}), why: read.why } : { kind: "record", ...(id !== undefined ? { id } : {}), tree: read.tree };
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return { kind: "unreadable", ...(id !== undefined ? { id } : {}), why: "an entry on the branch could not be read" };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export function readCrossBoundaryRecord(branch) {
|
|
114
|
+
const strictest = (id, why) => Object.freeze({ kind: "malformed", ...(id !== undefined ? { entryId: id } : {}), tree: STRICTEST_CROSS_BOUNDARY_TREE, why: `the cross-boundary record on this branch cannot be read: ${why}` });
|
|
115
|
+
let isList;
|
|
116
|
+
let length;
|
|
117
|
+
try {
|
|
118
|
+
isList = Array.isArray(branch);
|
|
119
|
+
length = isList ? branch.length : undefined;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return strictest(undefined, "the branch could not be read");
|
|
123
|
+
}
|
|
124
|
+
if (!isList)
|
|
125
|
+
throw new TypeError("readCrossBoundaryRecord: the branch is a list of session entries");
|
|
126
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0)
|
|
127
|
+
return strictest(undefined, "the branch could not be read");
|
|
128
|
+
let joined;
|
|
129
|
+
let newest;
|
|
130
|
+
let uncoveredContent = false;
|
|
131
|
+
for (let i = 0; i < length; i++) {
|
|
132
|
+
const entry = readBranchEntry(branch, i);
|
|
133
|
+
if (entry.kind === "other") {
|
|
134
|
+
uncoveredContent ||= entry.content;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (entry.kind === "unreadable")
|
|
138
|
+
return strictest(entry.id, entry.why);
|
|
139
|
+
joined = joinCrossBoundaryTree(joined ?? {}, entry.tree);
|
|
140
|
+
newest = entry.id;
|
|
141
|
+
uncoveredContent = false;
|
|
142
|
+
}
|
|
143
|
+
return joined === undefined ? Object.freeze({ kind: "absent", uncoveredContent }) : Object.freeze({ kind: "recorded", ...(newest !== undefined ? { entryId: newest } : {}), tree: joined, uncoveredContent });
|
|
144
|
+
}
|
|
145
|
+
export function crossBoundaryPostureUndeclaredNotice(at) {
|
|
146
|
+
assertTree(at.tree, "crossBoundaryPostureUndeclaredNotice");
|
|
147
|
+
return {
|
|
148
|
+
code: "config.cross_boundary_posture_undeclared",
|
|
149
|
+
message: 'This run executes on an external-content target (the device lane) and no RunnerDeps.crossBoundaryPosture is configured — the engine reads the permissive "single_user" posture. ' +
|
|
150
|
+
'On a multi-tenant deployment declare "multi_tenant", so an org-side write from a tainted run is put to a person; on a single-user deployment declare "single_user" to silence this line.',
|
|
151
|
+
detail: { sessionId: at.sessionId, runId: at.runId, taint: CROSS_BOUNDARY_TAINT_BITS.filter((b) => at.tree[b] === true) },
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export function crossBoundaryRecordUnwrittenNotice(at) {
|
|
155
|
+
return {
|
|
156
|
+
code: "config.cross_boundary_record_unwritten",
|
|
157
|
+
message: `The session's cross-boundary record could not be written at ${at.phase} (${at.cause.slice(0, 300)}) — ` +
|
|
158
|
+
(at.governed
|
|
159
|
+
? "the tree is governed: the next leg on this session reads the content after its newest record as off-target, so its device calls are put to a person."
|
|
160
|
+
: "the tree is not governed: no judgment reads the record, so its failure refused nothing; the tree's growth, or the leg's end, writes next."),
|
|
161
|
+
detail: { sessionId: at.sessionId, runId: at.runId, phase: at.phase, governed: at.governed, cause: at.cause.slice(0, 300) },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const NO_LEGS = Object.freeze([]);
|
|
165
|
+
export function boundaryFlowFace(entry) {
|
|
166
|
+
return entry?.boundaryFlow === "engine" ? { boundaryLegs: NO_LEGS } : {};
|
|
167
|
+
}
|
|
168
|
+
function ownedLegList(v) {
|
|
169
|
+
try {
|
|
170
|
+
if (!Array.isArray(v))
|
|
171
|
+
return undefined;
|
|
172
|
+
const length = v.length;
|
|
173
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0)
|
|
174
|
+
return undefined;
|
|
175
|
+
const held = new Set();
|
|
176
|
+
for (let i = 0; i < length; i++) {
|
|
177
|
+
const leg = v[i];
|
|
178
|
+
if (!LEG_SET.has(leg))
|
|
179
|
+
return undefined;
|
|
180
|
+
held.add(leg);
|
|
181
|
+
}
|
|
182
|
+
return BOUNDARY_LEGS.filter((l) => held.has(l));
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export function rosterBoundaryLegs(source, declared, effect) {
|
|
189
|
+
const list = declared === undefined || declared === "per_call" ? undefined : ownedLegList(declared);
|
|
190
|
+
if (declared !== undefined && declared !== "per_call" && list === undefined)
|
|
191
|
+
return undefined;
|
|
192
|
+
const reads = source === "builtin" || source === "synthetic" || source === "mcp" || source === "a2a";
|
|
193
|
+
if (!reads || declared === undefined)
|
|
194
|
+
return effect === "read" ? ["outside_read"] : ["outside_write"];
|
|
195
|
+
return declared === "per_call" ? "per_call" : list;
|
|
196
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AssertAllKeysHandled } from "./ask-origin.js";
|
|
2
|
+
import type { DecisionMember, PermissionResult } from "./tool-policy.js";
|
|
2
3
|
export declare const refusedOutOfContract: (message: string) => PermissionResult;
|
|
3
4
|
/** Is `d` a refusal the engine itself minted (by object identity — a look-alike `deny` a caller built is not one)? The receipt's
|
|
4
5
|
* answer is one of two things (the engine's refusal or its detached copy), and this tells them apart. */
|
|
@@ -12,6 +13,48 @@ export declare const isEngineRefusal: (d: unknown) => boolean;
|
|
|
12
13
|
* two entry-list members) — one word per author, so "who said this needs no asking" is read off the decision. */
|
|
13
14
|
declare const DECISION_REASONS: readonly ["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable", "read_only", "skill", "deployment_rule", "request_rule"];
|
|
14
15
|
export type DecisionReason = (typeof DECISION_REASONS)[number];
|
|
16
|
+
/** The arms of the decision union that declare member `M`. */
|
|
17
|
+
type ArmOf<M extends DecisionMember> = PermissionResult extends infer U ? (U extends {
|
|
18
|
+
action: infer A;
|
|
19
|
+
} ? (M extends keyof U ? A : never) : never) : never;
|
|
20
|
+
/**
|
|
21
|
+
* Every member of the decision union with the ARMS that declare it — the one table the receipt's member list is read off
|
|
22
|
+
* (`DECISION_MEMBERS`), fenced to the union both ways: `satisfies` admits no member and no arm the union lacks, and
|
|
23
|
+
* {@link DecisionMemberArmsCoverTheUnion} no arm the union has and a row lacks. A present member on an arm outside its row is
|
|
24
|
+
* out of contract like a word outside a closed set ({@link screenContractValues}): an ask's mark on an allow or a deny is a
|
|
25
|
+
* decision that contradicts itself, and a consumer branching on the member would read the other arm's meaning.
|
|
26
|
+
*/
|
|
27
|
+
export declare const DECISION_MEMBER_ARMS: Readonly<{
|
|
28
|
+
readonly action: readonly ["allow", "ask", "deny"];
|
|
29
|
+
readonly updatedInput: readonly ["allow", "ask", "deny"];
|
|
30
|
+
readonly message: readonly ["allow", "ask", "deny"];
|
|
31
|
+
readonly decisionReason: readonly ["allow", "ask", "deny"];
|
|
32
|
+
readonly attestation: readonly ["allow", "ask", "deny"];
|
|
33
|
+
readonly requiresRealApproval: readonly ["ask"];
|
|
34
|
+
readonly denialLimitFallback: readonly ["ask"];
|
|
35
|
+
readonly classifierNoRuling: readonly ["ask"];
|
|
36
|
+
readonly allowRuleShadowed: readonly ["ask"];
|
|
37
|
+
readonly probeReason: readonly ["ask"];
|
|
38
|
+
readonly probeCause: readonly ["ask"];
|
|
39
|
+
readonly probeMandated: readonly ["ask"];
|
|
40
|
+
readonly crossBoundaryMandated: readonly ["ask"];
|
|
41
|
+
readonly mandated: readonly ["ask"];
|
|
42
|
+
readonly mandate: readonly ["ask"];
|
|
43
|
+
readonly probeReadRootCandidate: readonly ["ask"];
|
|
44
|
+
readonly matchedAskRule: readonly ["ask"];
|
|
45
|
+
readonly ruleEvidence: readonly ["ask"];
|
|
46
|
+
readonly segmentCoverage: readonly ["ask"];
|
|
47
|
+
readonly origin: readonly ["ask"];
|
|
48
|
+
readonly askClass: readonly ["ask"];
|
|
49
|
+
readonly classes: readonly ["ask"];
|
|
50
|
+
readonly guards: readonly ["ask"];
|
|
51
|
+
readonly ruleUnreadable: readonly ["ask"];
|
|
52
|
+
readonly authoredBy: readonly ["ask"];
|
|
53
|
+
readonly foldedAskClasses: readonly ["ask"];
|
|
54
|
+
}>;
|
|
55
|
+
export type DecisionMemberArmsCoverTheUnion = AssertAllKeysHandled<{
|
|
56
|
+
[M in DecisionMember]: Exclude<ArmOf<M>, (typeof DECISION_MEMBER_ARMS)[M][number]>;
|
|
57
|
+
}[DecisionMember]>;
|
|
15
58
|
export declare const RETIRED_WINDOW_MEMBER_DENY_MESSAGE: string;
|
|
16
59
|
export declare const carriesRetiredWindow: (fallback: unknown) => boolean;
|
|
17
60
|
/**
|
|
@@ -32,8 +32,17 @@ const CLOSED_MEMBERS = [
|
|
|
32
32
|
{ member: "classifierNoRuling", words: AUTO_MODE_FALLBACK_CAUSES, has: isAutoModeFallbackCause },
|
|
33
33
|
{ member: "requiresRealApproval", words: [true, false], has: (v) => v === true || v === false },
|
|
34
34
|
{ member: "probeMandated", words: [true], has: (v) => v === true },
|
|
35
|
+
{ member: "crossBoundaryMandated", words: [true], has: (v) => v === true },
|
|
35
36
|
{ member: "mandated", words: [true], has: (v) => v === true },
|
|
36
37
|
];
|
|
38
|
+
const EVERY_ARM = ["allow", "ask", "deny"];
|
|
39
|
+
const ASK = ["ask"];
|
|
40
|
+
export const DECISION_MEMBER_ARMS = Object.freeze({
|
|
41
|
+
action: EVERY_ARM, updatedInput: EVERY_ARM, message: EVERY_ARM, decisionReason: EVERY_ARM, attestation: EVERY_ARM,
|
|
42
|
+
requiresRealApproval: ASK, denialLimitFallback: ASK, classifierNoRuling: ASK, allowRuleShadowed: ASK, probeReason: ASK, probeCause: ASK,
|
|
43
|
+
probeMandated: ASK, crossBoundaryMandated: ASK, mandated: ASK, mandate: ASK, probeReadRootCandidate: ASK, matchedAskRule: ASK, ruleEvidence: ASK,
|
|
44
|
+
segmentCoverage: ASK, origin: ASK, askClass: ASK, classes: ASK, guards: ASK, ruleUnreadable: ASK, authoredBy: ASK, foldedAskClasses: ASK,
|
|
45
|
+
});
|
|
37
46
|
const RETIRED_WINDOW_MEMBER = "autoDenyAfterMs";
|
|
38
47
|
export const RETIRED_WINDOW_MEMBER_DENY_MESSAGE = `a permission decision's "denialLimitFallback" carries "${RETIRED_WINDOW_MEMBER}" — the decision names the counts only; ` +
|
|
39
48
|
`the auto-deny window is decided by the ask's route (a live approver arms it, every other route runs none) and cannot be ` +
|
|
@@ -91,6 +100,13 @@ export function screenContractValues(contract, opts) {
|
|
|
91
100
|
continue;
|
|
92
101
|
return unrecognized(member, v, words);
|
|
93
102
|
}
|
|
103
|
+
const arm = contract.action;
|
|
104
|
+
for (const [member, arms] of Object.entries(DECISION_MEMBER_ARMS)) {
|
|
105
|
+
if (contract[member] === undefined || (arm === undefined ? EVERY_ARM.every((a) => arms.includes(a)) : arms.includes(arm)))
|
|
106
|
+
continue;
|
|
107
|
+
return refusedOutOfContract(`a permission decision carries "${member}" on ${arm === undefined ? "a result with no action (no opinion)" : `its "${arm}" arm`} — the member belongs to ` +
|
|
108
|
+
`the ${arms.join("/")} arm only; denied fail-closed rather than letting consumers branch on a member its arm does not hold`);
|
|
109
|
+
}
|
|
94
110
|
if (carriesRetiredWindow(contract.denialLimitFallback))
|
|
95
111
|
return refusedOutOfContract(RETIRED_WINDOW_MEMBER_DENY_MESSAGE);
|
|
96
112
|
return undefined;
|
|
@@ -262,6 +262,9 @@ export declare function argsAsTheToolReads(req: {
|
|
|
262
262
|
export declare function argsReadBy(read: (args: unknown) => unknown, args: unknown): unknown;
|
|
263
263
|
/** The value `args` was read from, when a reading moved it; otherwise `args` itself. */
|
|
264
264
|
export declare function argsReadFrom(args: unknown): unknown;
|
|
265
|
+
/** `copy` is a detached copy of `original` (the decision receipt's): the same bytes, so read from what `original` was read
|
|
266
|
+
* from — the recorded step survives the copy instead of being lost to the copy's new identity. */
|
|
267
|
+
export declare function carryArgsReadFrom(original: unknown, copy: unknown): void;
|
|
265
268
|
/**
|
|
266
269
|
* `req` with its arguments replaced by `args` AS THE TOOL READS THEM ({@link argsAsTheToolReads}) and — when `req` carries
|
|
267
270
|
* a reading of its own target — a reading of the NEW args taken the same way (same environment, same root / cwd, same
|
|
@@ -167,6 +167,10 @@ const READ_FROM = new WeakMap();
|
|
|
167
167
|
export function argsReadFrom(args) {
|
|
168
168
|
return typeof args === "object" && args !== null && READ_FROM.has(args) ? READ_FROM.get(args) : args;
|
|
169
169
|
}
|
|
170
|
+
export function carryArgsReadFrom(original, copy) {
|
|
171
|
+
if (typeof original === "object" && original !== null && READ_FROM.has(original) && typeof copy === "object" && copy !== null)
|
|
172
|
+
READ_FROM.set(copy, READ_FROM.get(original));
|
|
173
|
+
}
|
|
170
174
|
export async function withArgsReading(req, args, signal) {
|
|
171
175
|
const read = argsAsTheToolReads(req, args);
|
|
172
176
|
const reading = callTargetReadingOf(req);
|
|
@@ -399,6 +399,16 @@ export interface EngineNotice {
|
|
|
399
399
|
* ({@link import("./secret-env.js").holdOwnCredentialValues}); `detail: { reason }` — the kind, never the value. Delivered
|
|
400
400
|
* to `RunnerDeps.onNotice` from the `new Runner` receipt; the intakes with no structured seat (a brain's construction,
|
|
401
401
|
* a request's route reading, a peer's dial, a directly built distiller seat) print its line on `console.warn`.
|
|
402
|
+
* - `"config.cross_boundary_posture_undeclared"` — a run whose tree executes on a target device (armed) on a deployment that
|
|
403
|
+
* configured no `RunnerDeps.crossBoundaryPosture`: the permissive `"single_user"` reading is in force. Once per run;
|
|
404
|
+
* `detail: { sessionId, runId, taint }` — `taint` lists the tree's taint bits
|
|
405
|
+
* ({@link import("./cross-boundary-taint.js").CROSS_BOUNDARY_TAINT_BITS}). Minted by
|
|
406
|
+
* {@link import("./cross-boundary-taint.js").crossBoundaryPostureUndeclaredNotice}. Audience `operator`.
|
|
407
|
+
* - `"config.cross_boundary_record_unwritten"` — a session's cross-boundary record the engine owed could not be written (the
|
|
408
|
+
* store threw, or answered with no entry id) and refused nothing: on a tree that is not governed no judgment reads the record;
|
|
409
|
+
* at a governed leg's prepare or settle the next leg reads the content after the newest record as off-target. Per failed write;
|
|
410
|
+
* `detail: { sessionId, runId, phase, governed, cause }` (`phase`: `prepare` / `exit` / `suspend` / `settle`). Minted by
|
|
411
|
+
* {@link import("./cross-boundary-taint.js").crossBoundaryRecordUnwrittenNotice}. Audience `operator`.
|
|
402
412
|
* - `"config.permission_mode_unavailable"` — the permission mode a task requested is not the mode the leg runs
|
|
403
413
|
* under: the deployment denies it (`by: "operator_deny"` — `RuntimeCaps.bypassPermissions` / `autoMode` is
|
|
404
414
|
* `false`), a requested `auto` has no classifier face (`by: "face_absent"` — neither the leg's deps nor its chain carry
|
|
@@ -477,7 +487,13 @@ export interface EngineNotice {
|
|
|
477
487
|
* frame's two count keys). They are NOT redelivered (a steer aimed at a finished run must not
|
|
478
488
|
* fire at the next one — unlike ENGINE notes, which pend per session); the loud half of the
|
|
479
489
|
* #257 contract's "accepted = enqueued, not consumed" sentence;
|
|
480
|
-
*
|
|
490
|
+
* The words a person wrote on an approval card (a durable `policy_ask` allow's `feedback`) that did not reach the
|
|
491
|
+
* model are counted in the SAME account under the steer code — one more row of that family, `detail.kind:
|
|
492
|
+
* "approval_feedback"` (absent on every other row) — when the message carrying them never committed and the row did not come back
|
|
493
|
+
* (a returned row does not carry them: its next decide is fresh); when the pause tried to carry them forward and the parked-steer
|
|
494
|
+
* carrier refused, the row also names the store's refusal as `detail.carry` (`steering.invalid_content` | `steering.queue_full`) and
|
|
495
|
+
* its sentence says so (`cause` stays `leg_end`: the leg ended with them owed — the closed set has no carrier words);
|
|
496
|
+
* `detail: { <countKey>, cause, kind?, carry?, taskId?, sessionId?, runId? }` (the `code` and the `countKey` of each family are the
|
|
481
497
|
* table {@link USER_INPUT_UNDRAINED_FAMILIES}, keyed by the lane the input was queued on — `injectionLaneOf` — and the
|
|
482
498
|
* minter writes exactly those; `cause` on both rows, closed `USER_INPUT_UNDRAINED_CAUSES`: `"leg_end"` — this engine's arm, the
|
|
483
499
|
* leg ended with it still owed; `"crashed"` — minted by the SERVING LAYER, not this engine, before the
|
|
@@ -874,7 +890,8 @@ export interface EngineNotice {
|
|
|
874
890
|
* toolCallId, cause, parkLaneExisted, remedy? }` — `parkLaneExisted` says whether a park lane was armed and
|
|
875
891
|
* declined/failed (`true`) or none existed (`false`, the usual shape); `remedy` (the two causes that say nobody
|
|
876
892
|
* could be asked — `approver_unavailable` and `no_approver_wired`; never `mandate_unreconstructible`)
|
|
877
|
-
* is the one sentence naming the caller-side change that would have let the call run, as the tool text carries it
|
|
893
|
+
* is the one sentence naming the caller-side change that would have let the call run, as the tool text carries it (for arguments
|
|
894
|
+
* no approval can be bound to, the binding's own sentence: the remedy's approver half would not let them run). Audience `"user"`: the refused
|
|
878
895
|
* work is the session user's own; the operator hears it through the sink like every code.
|
|
879
896
|
*
|
|
880
897
|
* - `"memory.consolidation_recommended"` (design/339 §2.2/§6.2) — the engine-minted per-scope
|
|
@@ -1015,6 +1032,16 @@ export interface OwedUserInputAccount {
|
|
|
1015
1032
|
seq?: number;
|
|
1016
1033
|
}>;
|
|
1017
1034
|
carried: number;
|
|
1035
|
+
/** The approving person's words (a `policy_ask` allow's `feedback`), counted APART from `frames`: owed, and how many the migration carried.
|
|
1036
|
+
* They are on no row of their own, and a returned row (the compensation reopened it) does not carry them either — its next decide is a
|
|
1037
|
+
* fresh one the host may re-supply them on — so, unlike the caller's own frames, they are not announced when the row came back (the row is
|
|
1038
|
+
* pending again and the person is asked again). `refusal`: the code the parked-steer carrier answered when the pause tried to carry them
|
|
1039
|
+
* (`steering.invalid_content` — over its bound; `steering.queue_full`), the store's own words; absent when no carry was refused. */
|
|
1040
|
+
approval?: {
|
|
1041
|
+
owed: number;
|
|
1042
|
+
carried: number;
|
|
1043
|
+
refusal?: ApprovalCarryRefusal;
|
|
1044
|
+
};
|
|
1018
1045
|
};
|
|
1019
1046
|
}
|
|
1020
1047
|
/**
|
|
@@ -1069,6 +1096,9 @@ export declare function settleOwedUserInput(account: OwedUserInputAccount, rowRe
|
|
|
1069
1096
|
*/
|
|
1070
1097
|
export declare const USER_INPUT_UNDRAINED_CAUSES: readonly ["leg_end", "crashed"];
|
|
1071
1098
|
export type UserInputUndrainedCause = (typeof USER_INPUT_UNDRAINED_CAUSES)[number];
|
|
1099
|
+
/** What the parked-steer carrier answered (the checkpoint store's own refusal codes, not a set of this notice) when a pause tried to carry an
|
|
1100
|
+
* approving person's words: refused as content / over its bound, or its queue was full. `detail.carry` of the approval row, absent otherwise. */
|
|
1101
|
+
export type ApprovalCarryRefusal = "steering.invalid_content" | "steering.queue_full";
|
|
1072
1102
|
/**
|
|
1073
1103
|
* The two families of the stranded-input account, keyed by the lane the input was queued on (`injectionLaneOf` maps a steer's
|
|
1074
1104
|
* tier to it): the notice `code` the family is announced under, and the `detail` key that carries its count
|
|
@@ -1113,5 +1143,7 @@ export declare function parentLegIds(internals: {
|
|
|
1113
1143
|
export declare function undrainedUserInputNotices(counts: {
|
|
1114
1144
|
steer: number;
|
|
1115
1145
|
followUp: number;
|
|
1146
|
+
approvalFeedback?: number;
|
|
1147
|
+
approvalCarry?: ApprovalCarryRefusal;
|
|
1116
1148
|
}, taskId?: string, sessionId?: string, runId?: string, parent?: ParentLegIds): EngineNotice[];
|
|
1117
1149
|
export {};
|
|
@@ -44,7 +44,10 @@ export function settleOwedUserInput(account, rowReturned, ids, onNotice) {
|
|
|
44
44
|
SETTLED_OWED_ACCOUNTS.add(account);
|
|
45
45
|
try {
|
|
46
46
|
const owed = owedUserInputCounts(account.held, account.migrated, { ...account.continuation, rowReturned });
|
|
47
|
-
|
|
47
|
+
const approval = account.continuation.approval;
|
|
48
|
+
const approvalFeedback = approval === undefined || rowReturned ? 0 : Math.max(0, approval.owed - approval.carried);
|
|
49
|
+
const approvalCarry = approvalFeedback > 0 ? approval?.refusal : undefined;
|
|
50
|
+
for (const notice of undrainedUserInputNotices({ ...owed, ...(approvalFeedback > 0 ? { approvalFeedback } : {}), ...(approvalCarry !== undefined ? { approvalCarry } : {}) }, ids.taskId, ids.sessionId, ids.runId, ids.parent))
|
|
48
51
|
deliverEngineNotice(onNotice, notice);
|
|
49
52
|
}
|
|
50
53
|
catch {
|
|
@@ -84,6 +87,19 @@ export function undrainedUserInputNotices(counts, taskId, sessionId, runId, pare
|
|
|
84
87
|
if (toParent)
|
|
85
88
|
copies.push({ code: STEER_UNDRAINED, message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
|
|
86
89
|
}
|
|
90
|
+
const approvalFeedback = counts.approvalFeedback ?? 0;
|
|
91
|
+
if (approvalFeedback > 0) {
|
|
92
|
+
const why = counts.approvalCarry === "steering.invalid_content"
|
|
93
|
+
? "the leg paused with them still owed and the parked-steer carrier refused them (longer than a parked steer may be)"
|
|
94
|
+
: counts.approvalCarry === "steering.queue_full"
|
|
95
|
+
? "the leg paused with them still owed and the parked-steer carrier refused them (its queue was full)"
|
|
96
|
+
: "the leg ended before the message carrying them was committed";
|
|
97
|
+
const message = `${approvalFeedback} approval-card feedback(s) (the words a person wrote when approving a tool call) never reached the model — ${why}. They are NOT redelivered; re-send if still wanted.`;
|
|
98
|
+
const count = { [USER_INPUT_UNDRAINED_FAMILIES.steer.countKey]: approvalFeedback, cause, kind: "approval_feedback", ...(counts.approvalCarry !== undefined ? { carry: counts.approvalCarry } : {}) };
|
|
99
|
+
own.push({ code: STEER_UNDRAINED, message, detail: { ...count, ...tid, ...sid, ...rid } });
|
|
100
|
+
if (toParent)
|
|
101
|
+
copies.push({ code: STEER_UNDRAINED, message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
|
|
102
|
+
}
|
|
87
103
|
if (counts.followUp > 0) {
|
|
88
104
|
const message = `${counts.followUp} user follow-up(s) ${tail}`;
|
|
89
105
|
const count = { [USER_INPUT_UNDRAINED_FAMILIES.followUp.countKey]: counts.followUp, cause };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine's storage seats as ONE backend contract.
|
|
3
|
+
*
|
|
4
|
+
* A backend is the thing a deployment constructs over one storage — a data root, a database — and whose
|
|
5
|
+
* members it wires into {@link import("./runner-deps.js").RunnerDeps}. Before this contract the shape of
|
|
6
|
+
* "a backend" was whatever the file family's aggregate class happened to carry, so a second backend had
|
|
7
|
+
* nothing to implement and a host composing several had nothing to type its engine half against. The
|
|
8
|
+
* shape now lives here, once: the bundled file backend implements it, and so does any other.
|
|
9
|
+
*
|
|
10
|
+
* Membership rule: a member is an engine seat whose durable store lives in the backend's storage and that
|
|
11
|
+
* every backend supplies, the bundled file backend included — `retention` is the one optional member,
|
|
12
|
+
* and a seat the file backend does not supply is not a member. Each member's NAME is its `RunnerDeps` seat and its TYPE is
|
|
13
|
+
* that seat's store type, so each member wires into its seat as it is (name the seats; a backend object may carry
|
|
14
|
+
* members that are not seats, so it is not spread). The member-to-seat correspondence is pinned in both directions
|
|
15
|
+
* by the test suite, together with the classification of the store-shaped seats that are not members. One reading per member:
|
|
16
|
+
* - `fileHistoryStore` is the store only. The seat also accepts the word `"disabled"`; that word is the
|
|
17
|
+
* deployment's wiring decision, not something a storage supplies.
|
|
18
|
+
*
|
|
19
|
+
* A host's own product stores do not belong here: a host that keeps more stores beside these composes
|
|
20
|
+
* an `EngineStorageBackend` with its own, rather than widening this contract.
|
|
21
|
+
*/
|
|
22
|
+
import type { SessionStore } from "./session.js";
|
|
23
|
+
import type { CheckpointStore } from "./checkpoint-store.js";
|
|
24
|
+
import type { ToolResultStore } from "./tool-result-store.js";
|
|
25
|
+
import type { SessionPolicyStore } from "./session-policy-store.js";
|
|
26
|
+
import type { WorkflowJournalStore } from "./workflow-journal-store.js";
|
|
27
|
+
import type { UsageWindowStore } from "./usage-window-store.js";
|
|
28
|
+
import type { BackgroundAgentStore } from "./background-agent-store.js";
|
|
29
|
+
import type { MailboxStore } from "./mailbox-store.js";
|
|
30
|
+
import type { FileHistoryStore } from "./file-history-store.js";
|
|
31
|
+
import type { ManagedRetentionCapability } from "./retention.js";
|
|
32
|
+
import type { RosterStore } from "../agents/roster-store.js";
|
|
33
|
+
export interface EngineStorageBackend {
|
|
34
|
+
/** `RunnerDeps.sessionStore`. */
|
|
35
|
+
readonly sessionStore: SessionStore;
|
|
36
|
+
/** `RunnerDeps.checkpointStore`. */
|
|
37
|
+
readonly checkpointStore: CheckpointStore;
|
|
38
|
+
/** `RunnerDeps.toolResultStore`. */
|
|
39
|
+
readonly toolResultStore: ToolResultStore;
|
|
40
|
+
/** `RunnerDeps.sessionPolicyStore`. */
|
|
41
|
+
readonly sessionPolicyStore: SessionPolicyStore;
|
|
42
|
+
/** `RunnerDeps.workflowJournalStore`. */
|
|
43
|
+
readonly workflowJournalStore: WorkflowJournalStore;
|
|
44
|
+
/** `RunnerDeps.usageWindowStore`. */
|
|
45
|
+
readonly usageWindowStore: UsageWindowStore;
|
|
46
|
+
/** `RunnerDeps.backgroundAgentStore` — and the same instance wherever a deployment-composed Agent tool
|
|
47
|
+
* takes its agent store (the seat's pairing rule). */
|
|
48
|
+
readonly backgroundAgentStore: BackgroundAgentStore;
|
|
49
|
+
/** `RunnerDeps.mailboxStore` (same single-instance pairing as `backgroundAgentStore`). */
|
|
50
|
+
readonly mailboxStore: MailboxStore;
|
|
51
|
+
/** `RunnerDeps.rosterStore`. */
|
|
52
|
+
readonly rosterStore: RosterStore;
|
|
53
|
+
/** `RunnerDeps.fileHistoryStore` — the store; the seat's `"disabled"` word is not a backend's to give. */
|
|
54
|
+
readonly fileHistoryStore: FileHistoryStore;
|
|
55
|
+
/**
|
|
56
|
+
* The backend-level managed-retention executor, when this backend has one.
|
|
57
|
+
*
|
|
58
|
+
* Retention execution belongs to the backend, not to each store: the capability's session deletion
|
|
59
|
+
* re-checks live references inside the deletion transaction, and those references are held by several
|
|
60
|
+
* stores at once (pending checkpoints, background-agent rows, workflow runs), so only a party that
|
|
61
|
+
* spans them can honor it. Absent ⇒ this backend executes no managed retention — the bundled file
|
|
62
|
+
* backend is one such backend.
|
|
63
|
+
*
|
|
64
|
+
* Not the same field as a store's `retention`: on a store that member is the declaration WORD
|
|
65
|
+
* ({@link import("./retention.js").RetentionDeclaring}, `"managed" | "none"`); here it is the
|
|
66
|
+
* executor OBJECT. A backend is not a store: handed to `assertRetentionCapability` as one, a backend
|
|
67
|
+
* that carries this member is refused as an unparseable declaration.
|
|
68
|
+
*/
|
|
69
|
+
readonly retention?: ManagedRetentionCapability;
|
|
70
|
+
/** Release everything the backend holds (handles, locks) so a successor over the same storage can open
|
|
71
|
+
* it. Call it at shutdown. */
|
|
72
|
+
dispose(): Promise<void>;
|
|
73
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -75,6 +75,10 @@ export declare function trackKeyOf(root: string, absPath: string): string;
|
|
|
75
75
|
* before; pass it and a foreign-family record is refused by name instead of coerced.
|
|
76
76
|
*/
|
|
77
77
|
export declare function resolveTrackKey(root: string, key: string, minted?: FileHistoryTrackKeyDescriptor): FileHistoryTrackKeyResolution;
|
|
78
|
+
/** The file-history failure codes, spelled once: {@link FileHistoryError}'s `code` is derived from this tuple (each
|
|
79
|
+
* word is documented there). A host that recognizes a code in failure text, or keeps a code in a store, reads this
|
|
80
|
+
* value rather than keeping its own list. */
|
|
81
|
+
export declare const FILE_HISTORY_ERROR_CODES: readonly ["track_failed", "snapshot_failed", "restore_failed", "adopt_failed", "not_found", "aborted", "unsupported_format", "conflict"];
|
|
78
82
|
export interface FileHistoryError {
|
|
79
83
|
/** `not_found` = no boundary for that (scope, entryId); `aborted` = the signal fired (or the
|
|
80
84
|
* fencing discarded a timed-out attempt); `conflict` = a non-destructive-target law refused
|
|
@@ -89,7 +93,7 @@ export interface FileHistoryError {
|
|
|
89
93
|
* work its shape drove; a fault in the PUBLISH step itself — the disk refusing a good graph —
|
|
90
94
|
* answers `adopt_failed`); `unsupported_format`
|
|
91
95
|
* = a foreign-epoch or wrong-version envelope; the rest name the verb that failed. */
|
|
92
|
-
code:
|
|
96
|
+
code: (typeof FILE_HISTORY_ERROR_CODES)[number];
|
|
93
97
|
message: string;
|
|
94
98
|
}
|
|
95
99
|
export type FileHistoryResult = {
|
|
@@ -291,7 +295,7 @@ export interface FileHistoryStore {
|
|
|
291
295
|
* discarding that baseline would be the fake-pristine hole DV-14 exists to weld shut.
|
|
292
296
|
*
|
|
293
297
|
* `verify` is the EVIDENCE arm (adversarial round 2). A caller whose write failed with a code the
|
|
294
|
-
* FileSystem contract defines as "nothing was written" (`already_exists`, `precondition_failed`)
|
|
298
|
+
* FileSystem contract defines as "nothing was written" (`already_exists`, `precondition_failed`, `target_unavailable`)
|
|
295
299
|
* has proof and passes nothing. Every other failure — a plain write is not contractually atomic,
|
|
296
300
|
* so ENOSPC, a dying transport or a thrown backend may have TRUNCATED the file before reporting —
|
|
297
301
|
* must hand an `env` here: the store then re-reads the path ONCE and retracts only if the world
|
|
@@ -66,6 +66,7 @@ export function resolveTrackKey(root, key, minted) {
|
|
|
66
66
|
return { ok: false, reason: `unsafe recorded key escapes root: ${key}` };
|
|
67
67
|
return { ok: true, abs: joinUnder(root, keyN) };
|
|
68
68
|
}
|
|
69
|
+
export const FILE_HISTORY_ERROR_CODES = Object.freeze(["track_failed", "snapshot_failed", "restore_failed", "adopt_failed", "not_found", "aborted", "unsupported_format", "conflict"]);
|
|
69
70
|
export const FILE_HISTORY_DIFF_LINE_BUDGET = 2000;
|
|
70
71
|
export function countRestoreLineDiff(current, target) {
|
|
71
72
|
const isBinary = (b) => b !== undefined && b.includes(0);
|
package/dist/core/gate-fold.js
CHANGED
|
@@ -165,8 +165,16 @@ export async function runGateFold(pass) {
|
|
|
165
165
|
pass.deniedBy = "write_protection";
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
-
if (
|
|
169
|
-
|
|
168
|
+
if (pass.decision.action !== "deny" && input.crossBoundaryCheck?.obligation({ toolName, args: pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput, toolCallId }).mandate === true) {
|
|
169
|
+
const marks = { requiresRealApproval: true, crossBoundaryMandated: true };
|
|
170
|
+
if (pass.decision.action === "ask")
|
|
171
|
+
pass.decision = { ...pass.decision, ...marks };
|
|
172
|
+
else
|
|
173
|
+
[pass.decision, pass.tightenedBy] = [{ action: "ask", message: `tool "${toolName}" crosses the device boundary of a run that executes on a target device — a person must confirm it`, decisionReason: "safety", ...marks }, "safety"];
|
|
174
|
+
}
|
|
175
|
+
const unstatedRuns = pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput;
|
|
176
|
+
if (asksUnstated(pass.decision, pass.req, unstatedRuns, permissionSeatAt(input, unstatedRuns)?.seat, input.permissionMode?.())) {
|
|
177
|
+
pass.decision = { action: "ask", message: permissionSeatAt(input, unstatedRuns)?.question ?? unstatedAskMessage(toolName), ...(pass.policyRewrite !== undefined ? { updatedInput: pass.policyRewrite } : {}) };
|
|
170
178
|
pass.tightenedBy = "unstated";
|
|
171
179
|
}
|
|
172
180
|
return await pass.afterFold(pass);
|