@sema-agent/core 7.53.0 → 7.54.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 +277 -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 +3 -4
- package/dist/core/runner/prepare-acquire-reconcile.js +3 -3
- 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 +639 -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/repo-utils.d.ts +5 -0
- package/dist/engine/session/repo-utils.js +9 -0
- package/dist/engine/session/storage-base.js +2 -2
- package/dist/index.d.ts +23 -9
- package/dist/index.js +19 -8
- 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/dist/core/shell-scan.d.ts +0 -60
- package/dist/core/shell-scan.js +0 -183
package/dist/core/gate-lanes.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { argsAsTheToolReads, withArgsReading } from "./effective-path-target.js";
|
|
2
|
-
import { attest, attestationOf, decisionText, foldHookAsk,
|
|
3
|
-
import { arbitrateByMode, classifyAsk, decisionAskClassesOf, fileCommandOptionsSentence, persistedRuleMandateOf,
|
|
2
|
+
import { attest, attestationOf, bindableArgsOf, decisionText, foldHookAsk, mergeAttestation, refusalOf, refuseOutOfContractDecision, withAttestation } from "./tool-policy.js";
|
|
3
|
+
import { arbitrateByMode, classifyAsk, decisionAskClassesOf, fileCommandOptionsSentence, persistedRuleMandateOf, safetyAxisOf, stampAskOrigin } from "./ask-origin.js";
|
|
4
4
|
import { classifierParseFailureDenyMessage, denialLimitFallbackMessage, rungDidNotRule, unarmedWindow } from "./auto-mode.js";
|
|
5
5
|
import { classifierNoRulingArm, settleNoRulingAsk } from "./classifier-unavailable-arm.js";
|
|
6
6
|
import { SETTLEMENT_NOBODY_JUDGED } from "./gate-outcome.js";
|
|
@@ -10,14 +10,14 @@ import { applyPersistedTightening, disclosedRuleSet } from "./persisted-rule-arm
|
|
|
10
10
|
import { skillAllowDecision, skillRowRefusal } from "./skill-grant.js";
|
|
11
11
|
import { allowRuleShadowOf } from "./allow-entry.js";
|
|
12
12
|
import { CD_GIT_COMPOUND_SENTENCE, changesDirectoryBeforeGit, readOnlyShellVerdict } from "./read-only-shell.js";
|
|
13
|
-
import { readShellCommand } from "./shell-
|
|
13
|
+
import { readShellCommand } from "./shell-run-candidates.js";
|
|
14
14
|
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
15
15
|
import { catalogRuleFaceOf } from "./tool-registry.js";
|
|
16
16
|
import { callFaceSeat } from "./tool-roster.js";
|
|
17
17
|
import { heldBy, wireOf } from "./tool-name-readings.js";
|
|
18
18
|
import { asksUnstated, modeAcceptsFileCommand, permissionSeatAt, unstatedAskMessage } from "./mode-policy.js";
|
|
19
19
|
import { ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, orgUnreadableClause } from "./permission-rule-org.js";
|
|
20
|
-
import { exitGate, passAskFace,
|
|
20
|
+
import { exitGate, passAskFace, untakenValueExit } from "./runner/gate-exit.js";
|
|
21
21
|
import { readOrgRuleLane, readPersistedRuleLane } from "./rule-lane-reads.js";
|
|
22
22
|
import { traceHookCrash } from "./gate-crash-trace.js";
|
|
23
23
|
import { createGateDeadline, awaitWriteProtectionJudgment, withGateTimeout } from "./gate-bounded-await.js";
|
|
@@ -34,9 +34,9 @@ const ruleStoreUnreadableOf = (lane) => lane?.unreadable === true && lane.reason
|
|
|
34
34
|
export async function runGateLanes(pass) {
|
|
35
35
|
const { input, toolName, toolCallId, callSignal, ledger, notifier, screening, adjudicate, resolveAsk, suspendAsk } = pass;
|
|
36
36
|
const boundedAwaitDeadlineMs = createGateDeadline(input, notifier);
|
|
37
|
-
let orgRealApprovalRequired = false;
|
|
38
37
|
let markedThisRound;
|
|
39
38
|
let approvedWithArgs = false;
|
|
39
|
+
let editAskedOrigin;
|
|
40
40
|
let carriedOwnAsk;
|
|
41
41
|
const takeApprovedEdit = (read) => {
|
|
42
42
|
carriedOwnAsk = read.outstanding ?? (pass.decision.action === "ask" ? pass.decision : undefined);
|
|
@@ -112,7 +112,6 @@ export async function runGateLanes(pass) {
|
|
|
112
112
|
const answer = await readOrgRuleLane(input.orgRules, Object.freeze({ toolName, args, toolCallId, ...callFaceSeat(pass.req.face) }), input.abortSignal);
|
|
113
113
|
if (answer.status === "unavailable") {
|
|
114
114
|
orgEvidence = { revisionAbsent: "unavailable", ruleAbsent: "unavailable" };
|
|
115
|
-
orgRealApprovalRequired = true;
|
|
116
115
|
await notifier.notifyAsync(() => input.orgRules?.onUnavailable?.({ toolName, toolCallId, message: answer.disclosures.join("; ") }), "toolGate.orgSnapshotUnavailable");
|
|
117
116
|
const orgUnavailableMessage = `tool "${toolName}" needs approval: this deployment is org-governed and cannot currently adjudicate ` +
|
|
118
117
|
`against an organization policy snapshot — every allow tightens to a real approval until it can`;
|
|
@@ -142,7 +141,6 @@ export async function runGateLanes(pass) {
|
|
|
142
141
|
if (decided.action === "deny")
|
|
143
142
|
return { decision: decided, org: undefined };
|
|
144
143
|
orgTightenCount += 1;
|
|
145
|
-
orgRealApprovalRequired = true;
|
|
146
144
|
const orgRuleMessage = unreadable !== undefined
|
|
147
145
|
? `an organization policy rule (${rule}) may reach this call, ${orgUnreadableClause(unreadable)} — a person must decide`
|
|
148
146
|
: `an organization policy rule (${rule}) requires approval for this call`;
|
|
@@ -223,19 +221,21 @@ export async function runGateLanes(pass) {
|
|
|
223
221
|
pass.currentInput = pass.policyRewrite;
|
|
224
222
|
pass.req = Object.freeze({ ...pass.req, args: pass.policyRewrite, ...(laneReq.targetReading !== undefined ? { targetReading: laneReq.targetReading } : {}) });
|
|
225
223
|
}
|
|
226
|
-
const
|
|
227
|
-
|
|
224
|
+
const originFacts = askOriginFacts(orgAnswer.org, laneAnswer);
|
|
225
|
+
if (pass.decision.action === "ask")
|
|
226
|
+
classifyStanding(pass.decision, originFacts);
|
|
227
|
+
const allowMayClear = () => pass.decision.action === "ask" && pass.askClassification?.row.ruleMayClear === true;
|
|
228
228
|
let personalEvidence = { dotsAbsent: input.persistedRules === undefined ? "not_wired" : "not_adjudicated" };
|
|
229
229
|
if (laneAnswer?.unreadable === true)
|
|
230
230
|
personalEvidence = { dotsAbsent: "unavailable" };
|
|
231
231
|
let laneCoverage;
|
|
232
232
|
const questionTool = wireOf("lanes.allowLayerQuestionSkip", pass.req) === ASK_USER_QUESTION_TOOL_NAME;
|
|
233
|
-
const
|
|
233
|
+
const laneIsProvenance = orgAnswer.org === undefined &&
|
|
234
234
|
pass.decision.action === "ask" &&
|
|
235
235
|
pass.decision.requiresRealApproval !== true &&
|
|
236
236
|
pass.decision.decisionReason !== "hook" &&
|
|
237
237
|
!questionTool &&
|
|
238
|
-
!markedUnresolvable
|
|
238
|
+
!originFacts.markedUnresolvable;
|
|
239
239
|
const unionDotsOf = (rules) => {
|
|
240
240
|
const seen = new Set();
|
|
241
241
|
const union = [];
|
|
@@ -250,7 +250,7 @@ export async function runGateLanes(pass) {
|
|
|
250
250
|
}
|
|
251
251
|
return { dots: union };
|
|
252
252
|
};
|
|
253
|
-
if (input.persistedRules &&
|
|
253
|
+
if (input.persistedRules && laneIsProvenance) {
|
|
254
254
|
const answer = laneAnswer ?? {};
|
|
255
255
|
laneCoverage = answer.coverage;
|
|
256
256
|
personalEvidence = answer.hit?.behavior === "ask" ? unionDotsOf(answer.hit.rules) : { dotsAbsent: answer.unreadable === true ? "unavailable" : "no_match" };
|
|
@@ -297,7 +297,7 @@ export async function runGateLanes(pass) {
|
|
|
297
297
|
const hit = match();
|
|
298
298
|
if (hit === undefined)
|
|
299
299
|
continue;
|
|
300
|
-
if (
|
|
300
|
+
if (allowMayClear()) {
|
|
301
301
|
pass.decision = hit.clear();
|
|
302
302
|
await hit.afterClear();
|
|
303
303
|
if (member !== "hook")
|
|
@@ -312,7 +312,6 @@ export async function runGateLanes(pass) {
|
|
|
312
312
|
const { allowRuleShadowed: _claimed, ...standing } = pass.decision;
|
|
313
313
|
pass.decision = shadows.length > 0 ? { ...standing, allowRuleShadowed: shadows } : standing;
|
|
314
314
|
}
|
|
315
|
-
const originFacts = askOriginFacts(orgAnswer.org, laneAnswer);
|
|
316
315
|
let noRulingMinted = false;
|
|
317
316
|
const fallbackAskOf = (standingAsk, fallback, latestBlockedAction) => {
|
|
318
317
|
const { classifierNoRuling: _why, ...standing } = standingAsk;
|
|
@@ -323,7 +322,7 @@ export async function runGateLanes(pass) {
|
|
|
323
322
|
const stamped = { ...pass.decision, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}) };
|
|
324
323
|
pass.decision = stampAskOrigin(stamped, originFacts, classifyStanding(stamped, originFacts));
|
|
325
324
|
}
|
|
326
|
-
if (
|
|
325
|
+
if (allowMayClear() && pass.probeBoundaryDeclined !== true && wireOf("lanes.readOnlyShellAllow", pass.req) === COMMAND_RULE_TOOL) {
|
|
327
326
|
const commandParam = catalogRuleFaceOf(COMMAND_RULE_TOOL)?.primaryParams[0] ?? "command";
|
|
328
327
|
const args = pass.req.args;
|
|
329
328
|
const command = typeof args === "object" && args !== null && !Array.isArray(args) ? args[commandParam] : undefined;
|
|
@@ -344,7 +343,7 @@ export async function runGateLanes(pass) {
|
|
|
344
343
|
}
|
|
345
344
|
}
|
|
346
345
|
if (pass.toolCheckRaised !== true && pass.decision.action !== "deny") {
|
|
347
|
-
const read = await checkTool(pass.decision, pass.currentInput, orgAnswer.org, pass.policyRewrite,
|
|
346
|
+
const read = await checkTool(pass.decision, pass.currentInput, orgAnswer.org, pass.policyRewrite, allowMayClear);
|
|
348
347
|
if (read?.kind === "denied")
|
|
349
348
|
refuseByExtension(read.deny);
|
|
350
349
|
else if (read?.kind === "cleared")
|
|
@@ -542,20 +541,25 @@ export async function runGateLanes(pass) {
|
|
|
542
541
|
}
|
|
543
542
|
}
|
|
544
543
|
approvedWithArgs = pass.decision.action === "allow" && pass.decision.updatedInput !== undefined;
|
|
544
|
+
if (approvedWithArgs && askBeforeResolve.action === "ask")
|
|
545
|
+
editAskedOrigin = askBeforeResolve.origin;
|
|
545
546
|
}
|
|
546
547
|
}
|
|
547
548
|
if (approvedWithArgs && pass.decision.action === "allow" && pass.decision.updatedInput !== undefined) {
|
|
548
549
|
let editArgs = pass.decision.updatedInput;
|
|
549
550
|
let editDenied;
|
|
550
551
|
let editRewrittenSinceHuman = false;
|
|
552
|
+
let editAnswer = pass.decision;
|
|
551
553
|
let unanswered;
|
|
552
554
|
const refusedAtResolution = (d) => { editDenied = d; pass.deniedBy = "ask_resolution"; };
|
|
553
555
|
for (let round = 0;; round++) {
|
|
554
|
-
const taken =
|
|
556
|
+
const taken = bindableArgsOf(toolName, editArgs);
|
|
555
557
|
if (!taken.ok) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
558
|
+
const origin = attestationOf(editAnswer, { toolCallId, toolName })?.origin ?? editAskedOrigin;
|
|
559
|
+
if (origin === undefined)
|
|
560
|
+
throw new Error(`the approved edit for "${toolName}" reached its refusal without an origin word — the origin stamp precedes every resolution`);
|
|
561
|
+
ledger.set({ settlement: { kind: "presentation_failed", who: { party: "none" }, when: Date.now() }, origin });
|
|
562
|
+
return await untakenValueExit(pass, { value: editArgs, message: taken.refusal, deniedBy: "ask_resolution" });
|
|
559
563
|
}
|
|
560
564
|
editArgs = argsAsTheToolReads(pass.req, taken.value);
|
|
561
565
|
if (round >= 3) {
|
|
@@ -654,7 +658,8 @@ export async function runGateLanes(pass) {
|
|
|
654
658
|
editArgs = argsAsTheToolReads(pass.req, recheck.updatedInput);
|
|
655
659
|
editRewrittenSinceHuman = true;
|
|
656
660
|
}
|
|
657
|
-
|
|
661
|
+
const answeredRecheck = withAttestation(recheck, mergeAttestation(pass.req, attestationOf(editAnswer, pass.req), attestationOf(recheck, pass.req), "answer"));
|
|
662
|
+
if (asksUnstated(answeredRecheck, pass.req, editArgs, permissionSeatAt(input, editArgs)?.seat, input.permissionMode?.())) {
|
|
658
663
|
recheck = { action: "ask", message: permissionSeatAt(input, editArgs)?.question ?? unstatedAskMessage(toolName) };
|
|
659
664
|
pass.tightenedBy = "unstated";
|
|
660
665
|
}
|
|
@@ -690,6 +695,7 @@ export async function runGateLanes(pass) {
|
|
|
690
695
|
if (editRead?.kind === "approved") {
|
|
691
696
|
carriedOwnAsk = editRead.outstanding ?? (editDecision.action === "ask" ? editDecision : undefined);
|
|
692
697
|
editArgs = editRead.allow.updatedInput;
|
|
698
|
+
editAnswer = editRead.allow;
|
|
693
699
|
editRewrittenSinceHuman = false;
|
|
694
700
|
continue;
|
|
695
701
|
}
|
|
@@ -735,6 +741,9 @@ export async function runGateLanes(pass) {
|
|
|
735
741
|
break;
|
|
736
742
|
}
|
|
737
743
|
editArgs = rr.updatedInput;
|
|
744
|
+
editAnswer = rr;
|
|
745
|
+
if (editAskDecision.action === "ask")
|
|
746
|
+
editAskedOrigin = editAskDecision.origin;
|
|
738
747
|
editRewrittenSinceHuman = false;
|
|
739
748
|
}
|
|
740
749
|
const rejudged = () => editDenied === undefined || editDenied === unanswered?.deny;
|
|
@@ -31,13 +31,16 @@ import { type AutoModeClassifierRound, type AutoModeFallbackCause } from "./auto
|
|
|
31
31
|
* timer over the synchronous leg) elapsed with no answer.
|
|
32
32
|
* - `park_sla_expired` — a DURABLE park's SLA deadline passed and the host's sweep resolved it as a deny
|
|
33
33
|
* (FACET A). A store `expire`/`reap` produces no `tool_end` and therefore no settlement.
|
|
34
|
-
* - `no_approver` — headless: no approver (or no question face) is wired, or the deny posture string
|
|
34
|
+
* - `no_approver` — headless: no approver (or no question face) is wired, or the deny posture string (arguments no approval
|
|
35
|
+
* can be bound to are refused there in the binding's own sentence, under this word).
|
|
35
36
|
* - `approver_unavailable` — the approver (or an ancestor's frozen approver) answered the ROUTING question
|
|
36
37
|
* "nobody reachable", and no park took the ask.
|
|
37
38
|
* - `approver_error` — the approver threw.
|
|
38
39
|
* - `approver_contract` — the approver answered outside its contract (a non-boolean verdict, an
|
|
39
40
|
* unreadable member, a timeout-settled allow, a refused attribution…).
|
|
40
|
-
* - `presentation_failed` —
|
|
41
|
+
* - `presentation_failed` — at a seat with an approver, the arguments could not be bound (`bindableArgsOf`: not a record of plain
|
|
42
|
+
* JSON data nested at most 256 levels deep): the asked ones — nobody was asked — or a resolution's edit (the engine's resolver's
|
|
43
|
+
* or, recorded by the gate, a host resolver's) — its yes is not used.
|
|
41
44
|
* - `blanket_allow_refused` — an allow NO PERSON JUDGED met a `requiresRealApproval` ask: a blanket
|
|
42
45
|
* `onAsk:"allow"`, or an approver's allow it reported as the deployment's policy (`settledBy: "policy"`).
|
|
43
46
|
* - `task_aborted` — the wait's abort signal ended it (the run's own end, or a turn-level interrupt).
|
|
@@ -218,7 +221,8 @@ export type SettlementRefusalTableCoversEveryKind = AssertAllKeysHandled<Exclude
|
|
|
218
221
|
* the ask on a durable queue instead), and the two sites that treat it as headless anyway say so at
|
|
219
222
|
* their own call, because they are the ones with no park route left.
|
|
220
223
|
* - `approver_error` / `approver_contract` / `presentation_failed` — the approver was there and the
|
|
221
|
-
* engine could not use
|
|
224
|
+
* engine could not use its answer, or could not put these arguments before it (or adopt its edit). Not a
|
|
225
|
+
* deployment with nobody home: with no approver, such arguments are refused under `no_approver`.
|
|
222
226
|
* - `task_aborted` — the run ended; nothing is escalating anywhere.
|
|
223
227
|
*/
|
|
224
228
|
export declare const SETTLEMENT_NOBODY_JUDGED: {
|
|
@@ -48,7 +48,8 @@ export declare function governanceRetryClass(code: string): GovernanceRetryClass
|
|
|
48
48
|
* fixed the roster (`config.tool_mount_denied`), a
|
|
49
49
|
* member over a roster row's wire bound (`config.tool_roster_bound` / `config.tool_name_too_long` /
|
|
50
50
|
* `config.tool_contract_too_long`), a schema over the row's key bounds (`config.tool_schema_bound`), a
|
|
51
|
-
* declared face or result card the schema / namespace cannot carry (`config.tool_face_invalid`
|
|
51
|
+
* declared face or result card the schema / namespace cannot carry (`config.tool_face_invalid` — also the code an MCP server entry's
|
|
52
|
+
* unreadable `toolFaces` is refused with at the task preparation door and at `materializeMcpTools`), a name or
|
|
52
53
|
* alias outside the caller tool name contract — one no rule can name, or one carrying the protocol namespace separator
|
|
53
54
|
* (`config.tool_name_invalid`), an egress tool declared other than
|
|
54
55
|
* `write` (`config.egress_requires_write_effect` — also minted, same meaning, for a protocol tool's resolved
|
|
@@ -138,7 +139,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
138
139
|
* the union's catalog ({@link EngineNotice.code}). The groups below say only which family a row joins,
|
|
139
140
|
* so the catalog is not written a second time here and the two cannot drift.
|
|
140
141
|
*/
|
|
141
|
-
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.read_deny_swapped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.path_view_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.credential_not_withheld", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.rule_row_dropped", "config.session_rules_earlier_grammar", "permission.allowed_tool_not_granted", "mods.registration_refused", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.credential_origin_mismatch", "route.response_model_mismatch", "route.tool_call_id_folded", "route.finish_reason_unknown", "route.stream_end_ignored", "route.extra_body_reserved_dropped", "context.tool_turn_repaired", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "task.tool_turn_incomplete", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed", "tool_result.media_refused", "instructions.source_changed", "session.records_skipped", "memory.layer_locked_legacy", "server.model_unreachable", "permission.entry_mode_clamped"];
|
|
142
|
+
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.read_deny_swapped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.path_view_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.credential_not_withheld", "config.cross_boundary_posture_undeclared", "config.cross_boundary_record_unwritten", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.rule_row_dropped", "config.session_rules_earlier_grammar", "permission.allowed_tool_not_granted", "mods.registration_refused", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.credential_origin_mismatch", "route.response_model_mismatch", "route.tool_call_id_folded", "route.finish_reason_unknown", "route.stream_end_ignored", "route.extra_body_reserved_dropped", "context.tool_turn_repaired", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "task.tool_turn_incomplete", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed", "tool_result.media_refused", "instructions.source_changed", "session.records_skipped", "memory.layer_locked_legacy", "server.model_unreachable", "permission.entry_mode_clamped"];
|
|
142
143
|
/** A catalogued code (see {@link ENGINE_NOTICE_CODES}); {@link NOTICE_MINTER} says who mints it. NOT the type of
|
|
143
144
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
144
145
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -219,6 +220,8 @@ declare const NOTICE_AUDIENCE_TABLE: {
|
|
|
219
220
|
readonly "config.peer_admission_out_of_range": "operator";
|
|
220
221
|
readonly "config.secret_env_scrubbed": "operator";
|
|
221
222
|
readonly "config.credential_not_withheld": "operator";
|
|
223
|
+
readonly "config.cross_boundary_posture_undeclared": "operator";
|
|
224
|
+
readonly "config.cross_boundary_record_unwritten": "operator";
|
|
222
225
|
readonly "peer.inbound_disposition": "user";
|
|
223
226
|
readonly "peer.held_settled": "user";
|
|
224
227
|
readonly "peer.idle_subscription": "user";
|
|
@@ -305,6 +308,8 @@ declare const NOTICE_MINTER_TABLE: {
|
|
|
305
308
|
readonly "config.peer_lane_unmounted": "engine";
|
|
306
309
|
readonly "config.secret_env_scrubbed": "engine";
|
|
307
310
|
readonly "config.credential_not_withheld": "engine";
|
|
311
|
+
readonly "config.cross_boundary_posture_undeclared": "engine";
|
|
312
|
+
readonly "config.cross_boundary_record_unwritten": "engine";
|
|
308
313
|
readonly "config.permission_mode_unavailable": "engine";
|
|
309
314
|
readonly "permission.ask_released_by_mode": "engine";
|
|
310
315
|
readonly "permission.read_deny_unapplied": "engine";
|
|
@@ -146,6 +146,8 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
|
|
|
146
146
|
"config.peer_lane_unmounted",
|
|
147
147
|
"config.secret_env_scrubbed",
|
|
148
148
|
"config.credential_not_withheld",
|
|
149
|
+
"config.cross_boundary_posture_undeclared",
|
|
150
|
+
"config.cross_boundary_record_unwritten",
|
|
149
151
|
"config.permission_mode_unavailable",
|
|
150
152
|
"permission.ask_released_by_mode",
|
|
151
153
|
"permission.read_deny_unapplied",
|
|
@@ -279,6 +281,8 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
279
281
|
"config.peer_admission_out_of_range": "operator",
|
|
280
282
|
"config.secret_env_scrubbed": "operator",
|
|
281
283
|
"config.credential_not_withheld": "operator",
|
|
284
|
+
"config.cross_boundary_posture_undeclared": "operator",
|
|
285
|
+
"config.cross_boundary_record_unwritten": "operator",
|
|
282
286
|
"peer.inbound_disposition": "user",
|
|
283
287
|
"peer.held_settled": "user",
|
|
284
288
|
"peer.idle_subscription": "user",
|
|
@@ -346,6 +350,8 @@ const NOTICE_MINTER_TABLE = {
|
|
|
346
350
|
"config.peer_lane_unmounted": "engine",
|
|
347
351
|
"config.secret_env_scrubbed": "engine",
|
|
348
352
|
"config.credential_not_withheld": "engine",
|
|
353
|
+
"config.cross_boundary_posture_undeclared": "engine",
|
|
354
|
+
"config.cross_boundary_record_unwritten": "engine",
|
|
349
355
|
"config.permission_mode_unavailable": "engine",
|
|
350
356
|
"permission.ask_released_by_mode": "engine",
|
|
351
357
|
"permission.read_deny_unapplied": "engine",
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { WiringLegKind } from "./wiring-manifest.js";
|
|
|
13
13
|
import type { PermissionSeatInputs } from "./mode-policy.js";
|
|
14
14
|
import type { SkillGateSeats } from "./skill-grant.js";
|
|
15
15
|
import type { AllowEntryGateSeats } from "./allow-entry.js";
|
|
16
|
+
import type { CrossBoundaryGateSeat } from "./cross-boundary-taint.js";
|
|
16
17
|
/**
|
|
17
18
|
* The classic stations' faces and the two-phase tool gate. A deployment extends the engine through the mods bus
|
|
18
19
|
* (`RunnerDeps.mods` / `TaskSpec.mods`, `src/core/mods/`): each sema station raises one `classic.*` event, and the
|
|
@@ -554,9 +555,10 @@ export interface ToolGateResult {
|
|
|
554
555
|
* as taken at the gate's entry, or what a hook, a policy or a person rewrote them to); re-validated by the loop. Present
|
|
555
556
|
* on every allow exit, whether or not anything rewrote (compare values to tell); absent on a block and a suspend. */
|
|
556
557
|
updatedInput?: unknown;
|
|
557
|
-
/** The input this pass decided on (what a refusal refused, what an allowed call runs with) as a deep, FROZEN
|
|
558
|
-
* at the exit — never the live args
|
|
559
|
-
*
|
|
558
|
+
/** The input this pass decided on (what a refusal refused, what an allowed call runs with) as a deep, FROZEN plain-data projection
|
|
559
|
+
* taken at the exit — never the live args: on a refusal every member that is not data (a bigint, a Map, a Date, a class instance, a
|
|
560
|
+
* function, an accessor, a symbol-keyed member) is a `[not recorded: …]` marker; an allow exit meets only the gate's own copy, so there
|
|
561
|
+
* just a bigint, a cycle's closing member and containers past 256 levels are. Beside {@link gate} on both terminal exits; absent on the suspend exit. */
|
|
560
562
|
finalInput?: unknown;
|
|
561
563
|
/** A blocking refusal's structured details, when the refusing station minted any (the shell read station's
|
|
562
564
|
* `read_path_denied` / `readonly_out_of_root` cards): the loop attaches them to the error result it mints
|
|
@@ -611,9 +613,8 @@ export type ContentAskOutcome = {
|
|
|
611
613
|
* happened" (no facility armed, or the closure took its sync-first decline) — that case hides nothing,
|
|
612
614
|
* while this one does: the gate falls back to the synchronous approver chain either way (the
|
|
613
615
|
* compensation contract — an uncommitted checkpoint is a safe fallback — is unchanged), but on a
|
|
614
|
-
* headless deployment that fallback is the auto-deny whose text is "no approver is wired". True as far
|
|
615
|
-
*
|
|
616
|
-
* `onError` face). So the reason travels with the fall-through and the deny quotes it.
|
|
616
|
+
* headless deployment that fallback is the auto-deny whose text is "no approver is wired". True as far as it goes, and the whole
|
|
617
|
+
* reason the call did not park lives somewhere else entirely (the host's `onError` face). So the reason travels with the fall-through and the deny quotes it.
|
|
617
618
|
*/
|
|
618
619
|
export interface ParkAttemptFailed {
|
|
619
620
|
/** Why the park could not be minted — a sentence, already worded for a reader. */
|
|
@@ -720,7 +721,7 @@ export type PersistedRuleAnswer = PersistedRuleHit | PersistedRuleCoverage | Per
|
|
|
720
721
|
* signal; when the caller also supplies {@link ToolGateInput.callSignal}, the Runner's closures
|
|
721
722
|
* additionally bind their waits to that per-call signal (`AbortSignal.any` of the two), so a turn
|
|
722
723
|
* interrupt releases a pending ask instead of leaving it hanging on the run's lifetime alone. */
|
|
723
|
-
export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, AllowEntryGateSeats {
|
|
724
|
+
export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, AllowEntryGateSeats, CrossBoundaryGateSeat {
|
|
724
725
|
/** RB-473 batch 2b — bounded disclosure for a HOST callback that throws inside this gate call's
|
|
725
726
|
* isolation scope (onHookError itself, the deny observer, the durable-park observer). The runner
|
|
726
727
|
* wires this to an `observer.notify_failed` trace event; absent = counted only (unchanged). */
|
|
@@ -1094,12 +1095,11 @@ export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, All
|
|
|
1094
1095
|
* The deny exit nobody judged: the call's approval could not be RESOLVED (`cause` — the approver
|
|
1095
1096
|
* answered `unavailable`, no approver is wired, or an inherited durable mandate has no park facility on
|
|
1096
1097
|
* this leg) and no durable park took the call, so the fail-closed deny STANDS. Fired once per such call
|
|
1097
|
-
* from the gate's single deny exit, after every park re-route has had its attempt, with the facts the
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1101
|
-
* Observe-only: a throwing seat never alters the deny. Absent ⇒ the deny stands silently (
|
|
1102
|
-
* directly, no notice sink).
|
|
1098
|
+
* from the gate's single deny exit, after every park re-route has had its attempt, with the facts the `delegation.ask_unresolvable`
|
|
1099
|
+
* notice is composed from (`ask-unresolvable-notice.ts`); `parkLaneExisted` = a park lane was armed for this run; `remedy` = the
|
|
1100
|
+
* caller-side change that would have let the call run, present for the causes that say nobody could be asked (`approver_unavailable`,
|
|
1101
|
+
* `no_approver_wired`) — for arguments no approval can be bound to, the binding's own sentence (the remedy's approver half would not
|
|
1102
|
+
* let them run). Observe-only: a throwing seat never alters the deny. Absent ⇒ the deny stands silently (no notice sink).
|
|
1103
1103
|
*/
|
|
1104
1104
|
onAskUnresolvable?: (info: {
|
|
1105
1105
|
toolName: string;
|
|
@@ -1311,7 +1311,7 @@ export declare function createPreToolUseScreening(input: Pick<ToolGateInput, "ev
|
|
|
1311
1311
|
* structural rather than restated.
|
|
1312
1312
|
*
|
|
1313
1313
|
* The mapping mirrors the gate's own phase 1 decision for decision:
|
|
1314
|
-
* - no opinion → `allow`, contributing nothing;
|
|
1314
|
+
* - no opinion, and an `allow` → `allow`, contributing nothing (a constraint only tightens: it clears no question);
|
|
1315
1315
|
* - `deny` → `deny` in the fold's own words;
|
|
1316
1316
|
* - `ask` → `ask`, stamped `decisionReason:"hook"` (and the mandate, when the answer set it) so every consumer that
|
|
1317
1317
|
* distinguishes a screening ask from a policy ask reads the same fact one level down;
|
package/dist/core/hooks.js
CHANGED
|
@@ -186,8 +186,7 @@ export function createPreToolUseConstraintPolicy(chain, env) {
|
|
|
186
186
|
...(r.updatedInput !== undefined ? { updatedInput: r.updatedInput } : {}),
|
|
187
187
|
};
|
|
188
188
|
}
|
|
189
|
-
|
|
190
|
-
return r.updatedInput !== undefined ? { action: "allow", updatedInput: r.updatedInput, ...stated } : { action: "allow", ...stated };
|
|
189
|
+
return r.updatedInput !== undefined ? { action: "allow", updatedInput: r.updatedInput } : { action: "allow" };
|
|
191
190
|
},
|
|
192
191
|
}, "hook");
|
|
193
192
|
recordScreeningChain(minted, chain);
|
|
@@ -109,7 +109,7 @@ export declare function resolveLockedKeys(config: LockedConfig | undefined): Rea
|
|
|
109
109
|
* and a peer's `headers` and `allowSkills` are the copy's own, and a host that edits its own nested object after
|
|
110
110
|
* the door (a header value, an `allowSkills.push`) reaches nothing the engine holds: the dial, a refresh and a
|
|
111
111
|
* re-dial all work from this copy, on both surfaces. The axes are then judged on that copy by their own reader
|
|
112
|
-
* ({@link judgeEntryToolAxes}).
|
|
112
|
+
* ({@link judgeEntryToolAxes}), and an MCP server's faces by theirs ({@link judgeEntryToolFaces}).
|
|
113
113
|
*
|
|
114
114
|
* There is no third outcome between "copied whole" and "refused": a value the copy cannot carry whole is a
|
|
115
115
|
* declaration the engine cannot read — a Proxy at any level, an object whose prototype is neither
|
|
@@ -167,6 +167,33 @@ export declare function judgeEntryToolAxes<E extends McpServerSpec | A2aServerSp
|
|
|
167
167
|
code: string;
|
|
168
168
|
};
|
|
169
169
|
};
|
|
170
|
+
/**
|
|
171
|
+
* The `toolFaces` question for ONE snapshot entry of an MCP server — asked by the preparation door and by the materialization entrance exactly
|
|
172
|
+
* as {@link judgeEntryToolAxes} asks its own, so the engine reads a face declaration one way (`readToolFaces`) and the mount consumes the copy
|
|
173
|
+
* that way gave it, never the caller's object: a mistyped member, an axis word written as a face member, a member of the wrong kind or an empty
|
|
174
|
+
* tool name is a refusal here, not a protection dropped in silence or a throw at the listing. `ok: true` carries the entry with the reader's
|
|
175
|
+
* frozen copy in place of the declaration (an empty record declares nothing and leaves no key); `ok: false` carries the coded refusal
|
|
176
|
+
* (`config.tool_face_invalid`) naming the server, the tool and the member.
|
|
177
|
+
*/
|
|
178
|
+
export declare function judgeEntryToolFaces(copy: McpServerSpec): {
|
|
179
|
+
ok: true;
|
|
180
|
+
entry: McpServerSpec;
|
|
181
|
+
} | {
|
|
182
|
+
ok: false;
|
|
183
|
+
error: Error & {
|
|
184
|
+
code: string;
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
/** Everything a snapshot MCP entry declares per tool, judged: its `toolAxes` ({@link judgeEntryToolAxes}), then its `toolFaces` ({@link judgeEntryToolFaces}). */
|
|
188
|
+
export declare function judgeMcpEntryDeclarations(copy: McpServerSpec): {
|
|
189
|
+
ok: true;
|
|
190
|
+
entry: McpServerSpec;
|
|
191
|
+
} | {
|
|
192
|
+
ok: false;
|
|
193
|
+
error: Error & {
|
|
194
|
+
code: string;
|
|
195
|
+
};
|
|
196
|
+
};
|
|
170
197
|
/**
|
|
171
198
|
* The single preflight resolver: validate the lock declaration, refuse a spec that tries to occupy
|
|
172
199
|
* a locked slot (`config.locked_key`, two-state — the whole prepare is rejected, nothing is
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { types } from "node:util";
|
|
2
|
-
import { DECLARATION_PATH_RULES, declarationFaultOf, declarerWord, readToolAxes, receiveDeclaration, toolAxesRefusal } from "./mcp-server-spec.js";
|
|
2
|
+
import { DECLARATION_PATH_RULES, NO_DECLARATION_PATH_RULES, declarationFaultOf, declarerWord, readToolAxes, receiveDeclaration, toolAxesRefusal } from "./mcp-server-spec.js";
|
|
3
3
|
import { isPlainRecord, receive } from "./mods/kernel/receive.js";
|
|
4
|
+
import { readToolFaces } from "./tool-face-reader.js";
|
|
4
5
|
import { CONFIG_RECEIPT_MODE } from "./own-property-receipt.js";
|
|
5
6
|
import { isToolContentOrigin, TOOL_CONTENT_ORIGINS } from "./memory-engine/content-origin.js";
|
|
6
7
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
@@ -32,7 +33,7 @@ export function resolveLockedKeys(config) {
|
|
|
32
33
|
return keys;
|
|
33
34
|
}
|
|
34
35
|
function declaredLockKeys(config) {
|
|
35
|
-
const received = receiveDeclaration(config);
|
|
36
|
+
const received = receiveDeclaration(config, NO_DECLARATION_PATH_RULES);
|
|
36
37
|
if (!received.ok)
|
|
37
38
|
throw lockDeclarationRefusal(memberPath(received.fault.path, "RunnerDeps.lockedConfig"), `cannot be read: ${received.fault.problem}`);
|
|
38
39
|
const copy = received.value;
|
|
@@ -116,6 +117,32 @@ export function judgeEntryToolAxes(copy, at) {
|
|
|
116
117
|
}
|
|
117
118
|
return { ok: true, entry: Object.freeze((axes.toolAxes === undefined ? rest : { ...rest, toolAxes: axes.toolAxes })) };
|
|
118
119
|
}
|
|
120
|
+
function deepFrozen(value) {
|
|
121
|
+
if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
|
|
122
|
+
Object.freeze(value);
|
|
123
|
+
for (const member of Object.values(value))
|
|
124
|
+
deepFrozen(member);
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
function toolFacesRefusal(name, problem) {
|
|
129
|
+
return codedError("config.tool_face_invalid", `${declarerWord("mcp")} ${name} declares toolFaces the engine cannot read: ${inlineUntrusted(problem, 240)}. A per-tool face declaration is the deployment's own ` +
|
|
130
|
+
`narrowing (which argument names a tool's path, how a write is confined, which approval card and rule vocabulary the tool wears), so it is refused ` +
|
|
131
|
+
`rather than partly applied — a member dropped or a word defaulted would mount the tool without the protection its operator declared.`);
|
|
132
|
+
}
|
|
133
|
+
export function judgeEntryToolFaces(copy) {
|
|
134
|
+
const { toolFaces: declared, ...rest } = copy;
|
|
135
|
+
if (declared === undefined)
|
|
136
|
+
return { ok: true, entry: copy };
|
|
137
|
+
const faces = readToolFaces(declared);
|
|
138
|
+
if (!faces.ok)
|
|
139
|
+
return { ok: false, error: toolFacesRefusal(declarerNameOf(copy.name), faces.problem) };
|
|
140
|
+
return { ok: true, entry: Object.freeze((faces.faces === undefined ? rest : { ...rest, toolFaces: deepFrozen(faces.faces) })) };
|
|
141
|
+
}
|
|
142
|
+
export function judgeMcpEntryDeclarations(copy) {
|
|
143
|
+
const axes = judgeEntryToolAxes(copy, "mcp");
|
|
144
|
+
return axes.ok ? judgeEntryToolFaces(axes.entry) : axes;
|
|
145
|
+
}
|
|
119
146
|
function declaredEntriesOf(declared, at) {
|
|
120
147
|
const field = at;
|
|
121
148
|
const received = receive(declared, CONFIG_RECEIPT_MODE);
|
|
@@ -157,7 +184,7 @@ function snapshotAndValidateMcpEntries(declared) {
|
|
|
157
184
|
`than folded to a class, because folding it would let a deployment believe a declaration is in force while every ` +
|
|
158
185
|
`call keeps writing a one-way durable mark.`);
|
|
159
186
|
}
|
|
160
|
-
const judged =
|
|
187
|
+
const judged = judgeMcpEntryDeclarations(copy);
|
|
161
188
|
if (!judged.ok)
|
|
162
189
|
throw judged.error;
|
|
163
190
|
return judged.entry;
|
package/dist/core/lsp.d.ts
CHANGED
|
@@ -260,7 +260,7 @@ export interface LspServerManager {
|
|
|
260
260
|
* ExecutionEnv (the SAME sandbox the agent runs in) — core passes it because a real in-env language
|
|
261
261
|
* server must run inside that sandbox, and core already holds the per-task env at mount time. Supplying
|
|
262
262
|
* it here means a manager can be **stateless** (it doesn't need a sessionId→env registry). It is
|
|
263
|
-
* optional/last
|
|
263
|
+
* optional/last by contract: a manager that resolves the env another way (e.g. a sessionId registry
|
|
264
264
|
* keyed off `executionEnvFactory`) may ignore it.
|
|
265
265
|
*/
|
|
266
266
|
sessionFor(filePath: string, signal?: AbortSignal, env?: ExecutionEnv): Promise<LspSession | undefined>;
|