@sema-agent/core 7.35.2 → 7.37.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 +142 -0
- package/dist/brain/anthropic.js +6 -6
- package/dist/brain/errors.d.ts +1 -14
- package/dist/brain/errors.js +2 -6
- package/dist/brain/open-responses.js +7 -7
- package/dist/brain/openai.js +7 -7
- package/dist/brain/provider-echo-shape.d.ts +26 -0
- package/dist/brain/provider-echo-shape.js +12 -0
- package/dist/brain/tool-call-id.d.ts +27 -15
- package/dist/brain/tool-call-id.js +43 -0
- package/dist/core/ask-origin.d.ts +35 -5
- package/dist/core/ask-origin.js +23 -4
- package/dist/core/ask-question.d.ts +0 -30
- package/dist/core/ask-question.js +3 -2
- package/dist/core/checkpoint-store.d.ts +18 -6
- package/dist/core/checkpoint-store.js +2 -1
- package/dist/core/context-edit.d.ts +4 -0
- package/dist/core/context-edit.js +5 -2
- package/dist/core/decision-receipt.d.ts +21 -0
- package/dist/core/decision-receipt.js +63 -0
- package/dist/core/engine-notice.d.ts +15 -5
- package/dist/core/gate-fold.js +2 -1
- package/dist/core/gate-lanes.js +20 -23
- package/dist/core/gate-outcome.d.ts +7 -5
- package/dist/core/gate-outcome.js +5 -1
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +14 -2
- package/dist/core/hooks.js +7 -3
- package/dist/core/mode-policy.js +2 -1
- package/dist/core/park-selfcheck.js +2 -0
- package/dist/core/permission-rule-model.d.ts +2 -2
- package/dist/core/permission-rule-model.js +2 -2
- package/dist/core/permission-rules.js +2 -5
- package/dist/core/runner/active-skill-scope.js +5 -5
- package/dist/core/runner/decide-continuation.js +2 -1
- package/dist/core/runner/denial-limit-arms.d.ts +28 -18
- package/dist/core/runner/denial-limit-arms.js +11 -7
- package/dist/core/runner/inherited-ask-grants.d.ts +32 -31
- package/dist/core/runner/inherited-ask-grants.js +5 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +3 -0
- package/dist/core/runner/permission-rule-lanes.js +7 -6
- package/dist/core/runner/prepare-context-lane.d.ts +4 -0
- package/dist/core/runner/prepare-context-lane.js +7 -5
- package/dist/core/runner/prepare-gate-stations.js +17 -12
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-park-ask.d.ts +4 -2
- package/dist/core/runner/prepare-park-ask.js +8 -11
- package/dist/core/runner/prepare-policy-chain.d.ts +5 -8
- package/dist/core/runner/prepare-policy-chain.js +50 -31
- package/dist/core/runner/prepare-safety-scan.js +9 -2
- package/dist/core/runner/prepare-task.js +3 -3
- package/dist/core/runner/resume-apply.js +2 -1
- package/dist/core/runner/run-attachment-seats.js +3 -1
- package/dist/core/runner/run-harness-handlers.js +6 -1
- package/dist/core/runner/run-leg.js +11 -9
- package/dist/core/runner/run-recovery-lanes.js +2 -0
- package/dist/core/runner/session-rule-policy.js +5 -4
- package/dist/core/runner/tool-call-id-fold-seat.d.ts +13 -0
- package/dist/core/runner/tool-call-id-fold-seat.js +31 -0
- package/dist/core/sensitive-path-policy.js +2 -2
- package/dist/core/side-query.d.ts +5 -1
- package/dist/core/side-query.js +5 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +18 -0
- package/dist/core/tool-name-readings.d.ts +370 -0
- package/dist/core/tool-name-readings.js +137 -0
- package/dist/core/tool-policy.d.ts +79 -89
- package/dist/core/tool-policy.js +86 -49
- package/dist/core/tool-registry.d.ts +1 -31
- package/dist/core/tool-registry.js +2 -22
- package/dist/core/tool-result-budget.d.ts +3 -0
- package/dist/core/tool-result-budget.js +5 -3
- package/dist/core/tool-roster.d.ts +30 -9
- package/dist/core/tool-roster.js +45 -5
- package/dist/engine/llm/types.d.ts +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/fs/file-read-check.d.ts +1 -1
- package/dist/tools/fs/file-read-check.js +4 -3
- package/dist/tools/fs/shell-read-check.d.ts +15 -1
- package/dist/tools/fs/shell-read-check.js +5 -2
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +7 -1
- package/wire-changes/core@7.36.0.json +288 -0
- package/wire-changes/core@7.37.0.json +405 -0
package/dist/core/gate-lanes.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { attestClassifierDenyCause, classifierDenyCauseOf, decisionText, describeThrown } from "./tool-policy.js";
|
|
1
|
+
import { attestClassifierDenyCause, classifierDenyCauseOf, decisionText, describeThrown, refuseOutOfContractDecision } from "./tool-policy.js";
|
|
2
2
|
import { arbitrateByMode, classifyAsk, fileCommandOptionsSentence, mandateDisclosureOf, persistedRuleMandateOf, ruleClearanceOf, safetyAxisOf, stampAskOrigin } from "./ask-origin.js";
|
|
3
3
|
import { classifierParseFailureDenyMessage, denialLimitFallbackMessage, unarmedWindow } from "./auto-mode.js";
|
|
4
4
|
import { classifierUnavailableArm } from "./classifier-unavailable-arm.js";
|
|
@@ -11,13 +11,15 @@ import { CD_GIT_COMPOUND_SENTENCE, changesDirectoryBeforeGit, readOnlyShellVerdi
|
|
|
11
11
|
import { readShellCommand } from "./shell-lexer.js";
|
|
12
12
|
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
13
13
|
import { catalogRuleFaceOf } from "./tool-registry.js";
|
|
14
|
-
import {
|
|
14
|
+
import { callFaceSeat } from "./tool-roster.js";
|
|
15
|
+
import { heldBy, wireOf } from "./tool-name-readings.js";
|
|
15
16
|
import { modeAcceptsFileCommand } from "./mode-policy.js";
|
|
16
17
|
import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
|
|
17
18
|
import { exitGate } from "./runner/gate-exit.js";
|
|
18
19
|
import { traceHookCrash } from "./gate-crash-trace.js";
|
|
19
20
|
import { createGateDeadline, awaitWriteProtectionJudgment, withGateTimeout } from "./gate-bounded-await.js";
|
|
20
21
|
import { thrownText } from "./thrown-value.js";
|
|
22
|
+
const QUESTION_TOOL_NAMES = new Set([ASK_USER_QUESTION_TOOL_NAME]);
|
|
21
23
|
function isPlainOwnRecord(x) {
|
|
22
24
|
try {
|
|
23
25
|
if (x === null || typeof x !== "object" || Array.isArray(x))
|
|
@@ -170,7 +172,7 @@ export async function runGateLanes(pass) {
|
|
|
170
172
|
writeProtectionUnresolved: pass.writeProtectionUnresolved === true,
|
|
171
173
|
});
|
|
172
174
|
const askOriginFacts = (org, ruleCallUnreadable) => ({
|
|
173
|
-
contentQuestion:
|
|
175
|
+
contentQuestion: heldBy("lanes.contentQuestionClass", QUESTION_TOOL_NAMES, pass.req),
|
|
174
176
|
markedUnresolvable: markedUnresolvable(),
|
|
175
177
|
org,
|
|
176
178
|
...(ruleCallUnreadable ? { ruleCallUnreadable } : {}),
|
|
@@ -179,9 +181,7 @@ export async function runGateLanes(pass) {
|
|
|
179
181
|
...(input.toolMarkSources !== undefined ? { toolMarkSources: input.toolMarkSources } : {}),
|
|
180
182
|
...(input.a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
|
|
181
183
|
});
|
|
182
|
-
const classifyStanding = (unstamped, facts) =>
|
|
183
|
-
pass.askClassification = classifyAsk(unstamped, facts);
|
|
184
|
-
};
|
|
184
|
+
const classifyStanding = (unstamped, facts) => (pass.askClassification = classifyAsk(unstamped, facts));
|
|
185
185
|
let orgTightenCount = 0;
|
|
186
186
|
let orgEvidence = (() => {
|
|
187
187
|
const w = input.orgRules === undefined ? "not_wired" : "not_adjudicated";
|
|
@@ -195,7 +195,7 @@ export async function runGateLanes(pass) {
|
|
|
195
195
|
const applyOrgLayer = async (current, args) => {
|
|
196
196
|
if (input.orgRules === undefined)
|
|
197
197
|
return { decision: current, org: undefined };
|
|
198
|
-
if (
|
|
198
|
+
if (wireOf("lanes.orgContentAskExemption", pass.req) === ASK_USER_QUESTION_TOOL_NAME && input.orgRules.contentAskToolMounted === true)
|
|
199
199
|
return { decision: current, org: undefined };
|
|
200
200
|
let decided = current;
|
|
201
201
|
let pending;
|
|
@@ -271,7 +271,7 @@ export async function runGateLanes(pass) {
|
|
|
271
271
|
return await settleOrgVerdictWithin(pendingHit.then(normalizePersistedRuleHit).catch(() => ({ unreadable: true })), { unreadable: true }, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
|
|
272
272
|
};
|
|
273
273
|
const laneReq = { ...pass.req, ...(pass.policyRewrite !== undefined ? { args: pass.policyRewrite } : {}) };
|
|
274
|
-
const laneAnswer = input.persistedRules !== undefined && pass.req
|
|
274
|
+
const laneAnswer = input.persistedRules !== undefined && wireOf("lanes.persistedLaneSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME ? await readPersistedRules(laneReq) : undefined;
|
|
275
275
|
{
|
|
276
276
|
const tightened = applyPersistedTightening(pass.decision, laneAnswer);
|
|
277
277
|
pass.decision = tightened.decision;
|
|
@@ -299,7 +299,7 @@ export async function runGateLanes(pass) {
|
|
|
299
299
|
pass.decision.action === "ask" &&
|
|
300
300
|
pass.decision.requiresRealApproval !== true &&
|
|
301
301
|
pass.decision.decisionReason !== "hook" &&
|
|
302
|
-
pass.req
|
|
302
|
+
wireOf("lanes.allowLayerQuestionSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
303
303
|
!markedUnresolvable();
|
|
304
304
|
if (input.persistedRules && allowLayerMayClear()) {
|
|
305
305
|
const answer = laneAnswer ?? {};
|
|
@@ -353,10 +353,9 @@ export async function runGateLanes(pass) {
|
|
|
353
353
|
const originFacts = askOriginFacts(orgAnswer.org, ruleCallUnreadableOf(laneAnswer));
|
|
354
354
|
if (pass.decision.action === "ask") {
|
|
355
355
|
const stamped = { ...pass.decision, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}) };
|
|
356
|
-
classifyStanding(stamped, originFacts);
|
|
357
|
-
pass.decision = stampAskOrigin(stamped, originFacts);
|
|
356
|
+
pass.decision = stampAskOrigin(stamped, originFacts, classifyStanding(stamped, originFacts));
|
|
358
357
|
}
|
|
359
|
-
if (allowLayerMayClear() && pass.decision.action === "ask" && ruleMayClear() && pass.probeBoundaryDeclined !== true && pass.req
|
|
358
|
+
if (allowLayerMayClear() && pass.decision.action === "ask" && ruleMayClear() && pass.probeBoundaryDeclined !== true && wireOf("lanes.readOnlyShellAllow", pass.req) === COMMAND_RULE_TOOL) {
|
|
360
359
|
const commandParam = catalogRuleFaceOf(COMMAND_RULE_TOOL)?.primaryParams[0] ?? "command";
|
|
361
360
|
const args = pass.req.args;
|
|
362
361
|
const command = typeof args === "object" && args !== null && !Array.isArray(args) ? args[commandParam] : undefined;
|
|
@@ -397,8 +396,7 @@ export async function runGateLanes(pass) {
|
|
|
397
396
|
else if (verdict?.kind === "unavailable") {
|
|
398
397
|
const arm = classifierUnavailableArm({ ask: pass.decision, req: pass.req, cause: verdict.cause, attribution: verdict.classifier, seat: input.autoMode.unavailable, aborted: classifySignal?.aborted === true });
|
|
399
398
|
if (arm.kind === "asked") {
|
|
400
|
-
classifyStanding(arm.ask, originFacts);
|
|
401
|
-
pass.decision = stampAskOrigin(arm.ask, originFacts);
|
|
399
|
+
pass.decision = stampAskOrigin(arm.ask, originFacts, classifyStanding(arm.ask, originFacts));
|
|
402
400
|
pass.classifierRound = undefined;
|
|
403
401
|
}
|
|
404
402
|
else
|
|
@@ -416,8 +414,7 @@ export async function runGateLanes(pass) {
|
|
|
416
414
|
requiresRealApproval: true,
|
|
417
415
|
denialLimitFallback: tracked.fallback,
|
|
418
416
|
};
|
|
419
|
-
classifyStanding(fallbackAsk, originFacts);
|
|
420
|
-
pass.decision = stampAskOrigin(fallbackAsk, originFacts);
|
|
417
|
+
pass.decision = stampAskOrigin(fallbackAsk, originFacts, classifyStanding(fallbackAsk, originFacts));
|
|
421
418
|
}
|
|
422
419
|
else {
|
|
423
420
|
const denied = {
|
|
@@ -435,7 +432,7 @@ export async function runGateLanes(pass) {
|
|
|
435
432
|
}
|
|
436
433
|
if (input.sandboxAdmission !== undefined &&
|
|
437
434
|
pass.decision.action === "ask" &&
|
|
438
|
-
pass.req
|
|
435
|
+
wireOf("lanes.sandboxQuestionSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
439
436
|
input.isMarkedUnresolvable?.(input.event.toolCallId) !== true &&
|
|
440
437
|
pass.decision.requiresRealApproval !== true &&
|
|
441
438
|
pass.decision.decisionReason !== "hook" &&
|
|
@@ -463,7 +460,7 @@ export async function runGateLanes(pass) {
|
|
|
463
460
|
if (pass.decision.action === "ask" && input.permissionMode !== undefined && pass.askClassification !== undefined) {
|
|
464
461
|
const standing = pass.decision;
|
|
465
462
|
const mode = input.permissionMode();
|
|
466
|
-
const fileCommandByWord = modeAcceptsFileCommand(mode,
|
|
463
|
+
const fileCommandByWord = modeAcceptsFileCommand(mode, wireOf("lanes.modeFileCommand", pass.req), pass.policyRewrite ?? pass.currentInput);
|
|
467
464
|
const verdict = arbitrateByMode(mode, pass.askClassification, { ...(standing.origin !== undefined ? { origin: standing.origin } : {}), ...(standing.mandate !== undefined ? { mandate: standing.mandate } : {}) }, { fileCommand: pass.shellWritesPlaced === true && fileCommandByWord });
|
|
468
465
|
if (verdict.kind === "ask" && fileCommandByWord && pass.shellWritesPlaced !== true && pass.shellUnplacedOptions !== undefined && pass.askClassification.classes.size === 1 && pass.askClassification.classes.has("ordinary")) {
|
|
469
466
|
pass.decision = { ...standing, message: fileCommandOptionsSentence(pass.shellUnplacedOptions.program) };
|
|
@@ -515,6 +512,7 @@ export async function runGateLanes(pass) {
|
|
|
515
512
|
...(callSignal !== undefined ? { signal: callSignal } : {}),
|
|
516
513
|
...(d.action === "ask" && d.denialLimitFallback !== undefined ? { denialLimitFallback: unarmedWindow(d.denialLimitFallback) } : {}),
|
|
517
514
|
...(d.action === "ask" && d.origin !== undefined ? { origin: d.origin } : {}),
|
|
515
|
+
...(d.action === "ask" && d.askClass !== undefined && d.classes !== undefined ? { askClass: d.askClass, classes: d.classes } : {}),
|
|
518
516
|
...(d.action === "ask" && d.mandated === true ? { mandated: true, ...(d.mandate !== undefined ? { mandate: d.mandate } : {}) } : {}),
|
|
519
517
|
...(d.action === "ask" && d.authoredBy === "deployment" ? { authoredBy: "deployment" } : {}),
|
|
520
518
|
});
|
|
@@ -528,7 +526,7 @@ export async function runGateLanes(pass) {
|
|
|
528
526
|
return { suspend: suspended, preToolContext: pass.preToolContext };
|
|
529
527
|
}
|
|
530
528
|
}
|
|
531
|
-
if (pass.decision.action === "ask" && pass.req
|
|
529
|
+
if (pass.decision.action === "ask" && wireOf("lanes.contentAskRoute", pass.req) === ASK_USER_QUESTION_TOOL_NAME) {
|
|
532
530
|
const outcome = input.resolveContentAsk !== undefined ? await (callSignal !== undefined ? input.resolveContentAsk(pass.req, callSignal) : input.resolveContentAsk(pass.req)) : { kind: "unavailable", parkDeclined: false };
|
|
533
531
|
if (outcome.kind === "answered") {
|
|
534
532
|
pass.decision = { action: "allow", updatedInput: outcome.presentedInput };
|
|
@@ -660,7 +658,7 @@ export async function runGateLanes(pass) {
|
|
|
660
658
|
if (adjudicate) {
|
|
661
659
|
try {
|
|
662
660
|
const editReq = { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
|
|
663
|
-
recheck = await (callSignal !== undefined ? adjudicate(editReq, callSignal) : adjudicate(editReq));
|
|
661
|
+
recheck = refuseOutOfContractDecision(await (callSignal !== undefined ? adjudicate(editReq, callSignal) : adjudicate(editReq)));
|
|
664
662
|
}
|
|
665
663
|
catch (err) {
|
|
666
664
|
recheck = {
|
|
@@ -692,7 +690,7 @@ export async function runGateLanes(pass) {
|
|
|
692
690
|
break;
|
|
693
691
|
}
|
|
694
692
|
let editRuleCallUnreadable = false;
|
|
695
|
-
if (input.persistedRules !== undefined &&
|
|
693
|
+
if (input.persistedRules !== undefined && wireOf("lanes.persistedEditSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME) {
|
|
696
694
|
const editLane = await readPersistedRules({ toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) });
|
|
697
695
|
editRuleCallUnreadable = ruleCallUnreadableOf(editLane);
|
|
698
696
|
const editTightened = applyPersistedTightening(recheck, editLane);
|
|
@@ -718,8 +716,7 @@ export async function runGateLanes(pass) {
|
|
|
718
716
|
const editAskSnapshot = { ...recheck, ruleEvidence: mintRuleEvidence({ dotsAbsent: "not_adjudicated" }) };
|
|
719
717
|
markedThisRound = undefined;
|
|
720
718
|
const editFacts = askOriginFacts(editOrg.org, editRuleCallUnreadable);
|
|
721
|
-
classifyStanding(editAskSnapshot, editFacts);
|
|
722
|
-
const editAskDecision = stampAskOrigin(editAskSnapshot, editFacts);
|
|
719
|
+
const editAskDecision = stampAskOrigin(editAskSnapshot, editFacts, classifyStanding(editAskSnapshot, editFacts));
|
|
723
720
|
if (input.permissionMode !== undefined && pass.askClassification !== undefined) {
|
|
724
721
|
const editVerdict = arbitrateByMode(input.permissionMode(), pass.askClassification, { ...(editAskDecision.origin !== undefined ? { origin: editAskDecision.origin } : {}), ...(editAskDecision.mandate !== undefined ? { mandate: editAskDecision.mandate } : {}) });
|
|
725
722
|
if (editVerdict.kind === "released") {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* policy composes: a policy says allow / ask / deny; this file says how the pass that consumed that
|
|
8
8
|
* verdict ended.
|
|
9
9
|
*/
|
|
10
|
-
import { type AskClass, type AskOrigin, type AssertAllKeysHandled, type PermissionMode, type PersistedRuleMandate } from "./ask-origin.js";
|
|
10
|
+
import { type AskClass, type AskOrigin, type AssertAllKeysHandled, type ModeReleaseReason, type PermissionMode, type PersistedRuleMandate } from "./ask-origin.js";
|
|
11
11
|
import { type AutoModeClassifierRound } from "./auto-mode.js";
|
|
12
12
|
/**
|
|
13
13
|
* How the wait an ask was in ENDED — the closed set. One word per way a wait can end; the `who` on the
|
|
@@ -303,7 +303,8 @@ export type GateDisposition = {
|
|
|
303
303
|
* A permission MODE released a surviving ask — the allowed disposition's account of the question it did not put
|
|
304
304
|
* to anyone: the mode, the disclosed class word (`askClass`, the first member of the folded set in `ASK_CLASSES`
|
|
305
305
|
* order — one of {@link RELEASABLE_ASK_CLASSES}), the whole set, and the origin / mandate words the card would
|
|
306
|
-
* have shown. `reason
|
|
306
|
+
* have shown. `reason` is a member of {@link import("./ask-origin.js").MODE_RELEASE_REASONS} (`isModeReleaseReason`):
|
|
307
|
+
* `"rule_unreadable"` marks a release over a rule the call could not be read against; absent, the release names none.
|
|
307
308
|
*/
|
|
308
309
|
export interface ModeRelease {
|
|
309
310
|
mode: PermissionMode;
|
|
@@ -311,7 +312,7 @@ export interface ModeRelease {
|
|
|
311
312
|
classes: readonly AskClass[];
|
|
312
313
|
origin?: AskOrigin;
|
|
313
314
|
mandate?: PersistedRuleMandate;
|
|
314
|
-
reason?:
|
|
315
|
+
reason?: ModeReleaseReason;
|
|
315
316
|
}
|
|
316
317
|
/** The two modes that refuse on their own account (`deniedBy: "mode"`): `dontAsk` every non-authority ask,
|
|
317
318
|
* `bypassPermissions` a write whose protection could not be judged. `plan` refuses nothing here — its questions ask. */
|
|
@@ -341,8 +342,9 @@ export interface ModeRefusal {
|
|
|
341
342
|
* {@link import("./auto-mode.js").AUTO_MODE_FALLBACK_CAUSES} — a record whose `to` and `modelUsed`
|
|
342
343
|
* disagree would let a reader answer "which model decided this call?" two ways;
|
|
343
344
|
* I7 `disposition.released` present ⇒ `kind === "allowed"`, no settlement, `mode` is `bypassPermissions`,
|
|
344
|
-
* `askClass` is a releasable class
|
|
345
|
-
*
|
|
345
|
+
* `askClass` is a releasable class, every member of `classes` is an ask class, `origin` / `reason`, when
|
|
346
|
+
* present, are members of their closed sets, and `reason` names a member of `classes` (the mint takes it from
|
|
347
|
+
* that set) — a mode releases nothing a person answered and nothing the table calls immune;
|
|
346
348
|
* I8 `deniedBy === "mode"` ⇔ `mode` present (`bypassPermissions` | `dontAsk`) with `askClass` and `classes`.
|
|
347
349
|
* Nothing here is read out of an ABSENCE: an ordinary allow with no ask is `{ disposition: { kind: "allowed" } }`,
|
|
348
350
|
* a direct policy deny is `{ disposition: { kind: "denied", deniedBy: "policy" } }`, and neither carries a
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isAskClass, isAskOrigin, isPermissionMode, RELEASABLE_ASK_CLASSES } from "./ask-origin.js";
|
|
1
|
+
import { isAskClass, isAskOrigin, isModeReleaseReason, isPermissionMode, RELEASABLE_ASK_CLASSES } from "./ask-origin.js";
|
|
2
2
|
import { isAutoModeFallbackCause } from "./auto-mode.js";
|
|
3
3
|
export const SETTLEMENT_KINDS = [
|
|
4
4
|
"human_allowed",
|
|
@@ -128,6 +128,10 @@ export function screenGateOutcome(g) {
|
|
|
128
128
|
defects.push("I7: released.classes must be a non-empty list of ask classes");
|
|
129
129
|
if (r.origin !== undefined && !isAskOrigin(r.origin))
|
|
130
130
|
defects.push(`I7: released.origin "${String(r.origin)}" is outside the closed set`);
|
|
131
|
+
if (r.reason !== undefined && !isModeReleaseReason(r.reason))
|
|
132
|
+
defects.push(`I7: released.reason "${String(r.reason)}" is outside the closed set`);
|
|
133
|
+
else if (r.reason !== undefined && Array.isArray(r.classes) && !r.classes.includes(r.reason))
|
|
134
|
+
defects.push(`I7: released.reason "${r.reason}" is not a member of released.classes`);
|
|
131
135
|
}
|
|
132
136
|
if (d.kind === "denied") {
|
|
133
137
|
const modeRefusal = d.deniedBy === "mode";
|
|
@@ -135,7 +135,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
135
135
|
* the union's catalog ({@link EngineNotice.code}). The groups below say only which family a row joins,
|
|
136
136
|
* so the catalog is not written a second time here and the two cannot drift.
|
|
137
137
|
*/
|
|
138
|
-
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.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.write_protection_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.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "resume.legacy_mode_seed_migrated", "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.base_url_changed_key_unchanged", "route.response_model_mismatch", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "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"];
|
|
138
|
+
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.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.write_protection_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.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "resume.legacy_mode_seed_migrated", "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.base_url_changed_key_unchanged", "route.response_model_mismatch", "route.tool_call_id_folded", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "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"];
|
|
139
139
|
/** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
|
|
140
140
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
141
141
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -178,6 +178,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
178
178
|
"route.classifier_unroutable",
|
|
179
179
|
"route.base_url_changed_key_unchanged",
|
|
180
180
|
"route.response_model_mismatch",
|
|
181
|
+
"route.tool_call_id_folded",
|
|
181
182
|
"task.user_steer_undrained",
|
|
182
183
|
"task.user_followup_undrained",
|
|
183
184
|
"steering.parked_input_blocked",
|
|
@@ -269,6 +270,7 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
269
270
|
"route.classifier_unroutable": "operator",
|
|
270
271
|
"route.base_url_changed_key_unchanged": "operator",
|
|
271
272
|
"route.response_model_mismatch": "operator",
|
|
273
|
+
"route.tool_call_id_folded": "operator",
|
|
272
274
|
"skills.listing_truncated": "operator",
|
|
273
275
|
"agents.listing_truncated": "operator",
|
|
274
276
|
"tool_result.offload_put_failed": "operator",
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -474,6 +474,9 @@ export interface PostToolUseFailureResult {
|
|
|
474
474
|
/** One resolved call in a postToolBatch payload (CC PostToolBatchToolCall shape, thin projection —
|
|
475
475
|
* `response` is a bounded text digest of the result, never the full details object). */
|
|
476
476
|
export interface PostToolBatchCall {
|
|
477
|
+
/** The name the model spelled on the tool_use block (an alias stays an alias) — the reference's PostToolBatch payload
|
|
478
|
+
* names each block the same way, while the per-call post hooks receive the resolved tool's wire name. Correlate
|
|
479
|
+
* the two faces by `toolCallId`. */
|
|
477
480
|
toolName: string;
|
|
478
481
|
input: unknown;
|
|
479
482
|
toolCallId: string;
|
|
@@ -1734,6 +1737,12 @@ export interface AskCarry {
|
|
|
1734
1737
|
/** The surviving ask's engine-stamped origin word ({@link import("./ask-origin.js").AskOrigin}), persisted
|
|
1735
1738
|
* on the row so a durable card is rendered by the same word the synchronous card is. */
|
|
1736
1739
|
readonly origin?: import("./ask-origin.js").AskOrigin;
|
|
1740
|
+
/** The surviving ask's engine-stamped class ({@link import("./tool-policy.js").AskRequest.askClass}), persisted on the
|
|
1741
|
+
* row so a durable approval seat answers by the class the synchronous one reads. Present exactly when
|
|
1742
|
+
* {@link classes} is. */
|
|
1743
|
+
readonly askClass?: import("./ask-origin.js").AskClass;
|
|
1744
|
+
/** The whole class set beside it ({@link import("./tool-policy.js").AskRequest.classes}), in `ASK_CLASSES` order. */
|
|
1745
|
+
readonly classes?: readonly import("./ask-origin.js").AskClass[];
|
|
1737
1746
|
/** The stamped mandate ({@link import("./tool-policy.js").AskRequest.mandated}) — the park site's
|
|
1738
1747
|
* rule-offer facts read it, so a parked mandated ask offers no rule, like the live card. */
|
|
1739
1748
|
readonly mandated?: true;
|
|
@@ -1744,11 +1753,14 @@ export interface AskCarry {
|
|
|
1744
1753
|
}
|
|
1745
1754
|
/** What a durable row persists of an {@link AskCarry}: the fallback member COPIED (the row never aliases
|
|
1746
1755
|
* the gate's object; its window is already `0` — the gate's carry mint is the one writer of that
|
|
1747
|
-
* number)
|
|
1748
|
-
* row is the boundary a host reads back through —
|
|
1756
|
+
* number), the origin word only when it is a member of the closed set, and the class pair only when it is
|
|
1757
|
+
* well-formed, as a fresh copy (the carry is typed, but the row is the boundary a host reads back through —
|
|
1758
|
+
* the same screen the summary applies to the word). */
|
|
1749
1759
|
export declare function askCarryRowMembers(carry: AskCarry | undefined): {
|
|
1750
1760
|
denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1751
1761
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
1762
|
+
askClass?: import("./ask-origin.js").AskClass;
|
|
1763
|
+
classes?: readonly import("./ask-origin.js").AskClass[];
|
|
1752
1764
|
};
|
|
1753
1765
|
/**
|
|
1754
1766
|
* The design/37 **two-phase tool gate** — the single chokepoint that makes the load-bearing invariant
|
package/dist/core/hooks.js
CHANGED
|
@@ -7,10 +7,11 @@ import { runGateLanes } from "./gate-lanes.js";
|
|
|
7
7
|
export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./gate-lanes.js";
|
|
8
8
|
export { cloneObserverInput } from "./runner/gate-exit.js";
|
|
9
9
|
import { brandPolicyAskClass } from "./ask-class.js";
|
|
10
|
-
import { isAskOrigin } from "./ask-origin.js";
|
|
10
|
+
import { isAskOrigin, screenAskClassMembers } from "./ask-origin.js";
|
|
11
11
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
12
12
|
export { formatHookFeedback } from "./reminder-mint.js";
|
|
13
13
|
import { createSafeNotifier } from "./safe-notify.js";
|
|
14
|
+
import { wireOf } from "./tool-name-readings.js";
|
|
14
15
|
export const DEFAULT_HOOK_TIMEOUT_MS = 600_000;
|
|
15
16
|
export const MAX_HOOK_TIMEOUT_MS = 2_147_483_647;
|
|
16
17
|
const POST_ABORT_GRACE_MS = 1_000;
|
|
@@ -238,18 +239,21 @@ export function askCarryRowMembers(carry) {
|
|
|
238
239
|
if (carry === undefined)
|
|
239
240
|
return {};
|
|
240
241
|
const origin = carry.origin;
|
|
242
|
+
const classed = screenAskClassMembers(carry.askClass, carry.classes);
|
|
241
243
|
return {
|
|
242
244
|
...(carry.denialLimitFallback !== undefined ? { denialLimitFallback: { ...carry.denialLimitFallback } } : {}),
|
|
243
245
|
...(isAskOrigin(origin) ? { origin } : {}),
|
|
246
|
+
...(classed !== undefined ? classed : {}),
|
|
244
247
|
};
|
|
245
248
|
}
|
|
246
249
|
export async function runToolGate(input) {
|
|
247
250
|
const { event, preToolUse, adjudicate, resolveAsk, suspendAsk } = input;
|
|
248
251
|
const { toolCallId, toolName } = event;
|
|
249
252
|
const callSignal = input.callSignal;
|
|
253
|
+
const screenedName = wireOf("gate.preToolUse", event);
|
|
250
254
|
const hookCtx = (seatSignal) => ({
|
|
251
255
|
toolCallId,
|
|
252
|
-
toolName,
|
|
256
|
+
toolName: screenedName,
|
|
253
257
|
...(seatSignal !== undefined ? { signal: seatSignal } : {}),
|
|
254
258
|
...(() => {
|
|
255
259
|
const c = input.trackedCwd?.();
|
|
@@ -284,7 +288,7 @@ export async function runToolGate(input) {
|
|
|
284
288
|
};
|
|
285
289
|
const screening = preToolUse !== undefined
|
|
286
290
|
? {
|
|
287
|
-
screen: (args) => runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(
|
|
291
|
+
screen: (args) => runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(screenedName, args, hookCtx(sig))),
|
|
288
292
|
screened: screenPreToolUseResult,
|
|
289
293
|
crashReason: preToolUseCrashReason,
|
|
290
294
|
unansweredReason: preToolUseUnansweredBlock,
|
package/dist/core/mode-policy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createFsWriteGatePolicy } from "./fs-write-gate-policy.js";
|
|
2
2
|
import { isProtectedWrite } from "./tool-registry.js";
|
|
3
3
|
import { rosterEntryNamed } from "./tool-roster.js";
|
|
4
|
+
import { wireOf } from "./tool-name-readings.js";
|
|
4
5
|
import { clampPermissionMode } from "./ask-origin.js";
|
|
5
6
|
import { argvOf, lexShellText } from "../shell/lex.js";
|
|
6
7
|
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
@@ -42,7 +43,7 @@ const ENGINE_ROSTER_SOURCES = new Set(["builtin", "synthetic"]);
|
|
|
42
43
|
export function planRefusesWrite(req, roster, readOnlyDeclared) {
|
|
43
44
|
if (isProtectedWrite(req))
|
|
44
45
|
return true;
|
|
45
|
-
const row = roster === undefined ? undefined : rosterEntryNamed(roster, req
|
|
46
|
+
const row = roster === undefined ? undefined : rosterEntryNamed(roster, wireOf("mode.planRosterRow", req));
|
|
46
47
|
return row !== undefined && !ENGINE_ROSTER_SOURCES.has(row.source) && row.effect !== "read" && !(row.effectProvenance === "default" && readOnlyDeclared?.(row.name) === true);
|
|
47
48
|
}
|
|
48
49
|
const planAsk = (message) => ({ action: "ask", message, decisionReason: "mode" });
|
|
@@ -77,6 +77,8 @@ function syntheticCheckpoint(scope) {
|
|
|
77
77
|
mandated: true,
|
|
78
78
|
denialLimitFallback: { consecutive: 3, total: 3, limit: "consecutive", autoDenyAfterMs: 0 },
|
|
79
79
|
origin: "denial_limit_fallback",
|
|
80
|
+
askClass: "denial_limit_fallback",
|
|
81
|
+
classes: ["denial_limit_fallback"],
|
|
80
82
|
batchToolCallIds: [`${scope}:call`],
|
|
81
83
|
completedCallIds: [],
|
|
82
84
|
},
|
|
@@ -141,8 +141,8 @@ export declare const READ_RULE_TOOL = "Read";
|
|
|
141
141
|
* catalog's declarations, exactly as the protective fences' covered set does.
|
|
142
142
|
*/
|
|
143
143
|
export declare function ruleToolGrammarOf(toolName: string): "command" | "path" | undefined;
|
|
144
|
-
/** The names the persisted grammar judges a call under: every
|
|
145
|
-
* name,
|
|
144
|
+
/** The names the persisted grammar judges a call under: every name the call answers to (the model's spelling, the wire
|
|
145
|
+
* name, the other aliases that resolve to it) the grammar speaks for, so a stored `Bash(…)` or `Edit(…)` row reaches a
|
|
146
146
|
* tool that answers to that name under another wire name, as a policy's rule does. Empty ⇒ the grammar speaks for none. */
|
|
147
147
|
export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").ToolCallRequest, "toolName" | "face">): readonly string[];
|
|
148
148
|
/**
|
|
@@ -4,7 +4,7 @@ import { compileReadDeny } from "../tools/fs/read-deny.js";
|
|
|
4
4
|
import { carriesShellRedirection, parseLeadingCommandName, splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
|
|
5
5
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { pathTargetOf } from "./tool-registry.js";
|
|
7
|
-
import {
|
|
7
|
+
import { heldValues } from "./tool-name-readings.js";
|
|
8
8
|
import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
|
|
9
9
|
import { readShellCommand } from "./shell-lexer.js";
|
|
10
10
|
import { readOnlyLineObjection, readOnlyShellVerdict } from "./read-only-shell.js";
|
|
@@ -32,7 +32,7 @@ export function ruleToolGrammarOf(toolName) {
|
|
|
32
32
|
return pathTargetOf(toolName) !== undefined ? "path" : undefined;
|
|
33
33
|
}
|
|
34
34
|
export function laneToolNamesOf(req) {
|
|
35
|
-
return
|
|
35
|
+
return heldValues("policy.persistedGrammar", { get: (n) => (ruleToolGrammarOf(n) !== undefined ? n : undefined) }, req);
|
|
36
36
|
}
|
|
37
37
|
export const MAX_RULE_TEXT_CHARS = 512;
|
|
38
38
|
export const BARE_INTERPRETER_NAMES = new Set([
|
|
@@ -6,7 +6,7 @@ import { pathFamilyOf, pathIdentityOf, spellingOfIdentity } from "../tools/fs/sa
|
|
|
6
6
|
import { effectivePathTargetOf } from "./effective-path-target.js";
|
|
7
7
|
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
8
8
|
import { ruleDenialSentence } from "./rule-denial-sentence.js";
|
|
9
|
-
import {
|
|
9
|
+
import { heldValues } from "./tool-name-readings.js";
|
|
10
10
|
export { parsePermissionRule };
|
|
11
11
|
export const BASH_GENERIC_PARAMS = new Set(catalogRuleFaceOf("Bash")?.params ?? []);
|
|
12
12
|
const DEFAULT_CAPS = {
|
|
@@ -381,10 +381,7 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
381
381
|
requiresRealApproval: true,
|
|
382
382
|
});
|
|
383
383
|
const entryFor = (req) => {
|
|
384
|
-
const entries =
|
|
385
|
-
const e = byTool.get(n);
|
|
386
|
-
return e !== undefined ? [e] : [];
|
|
387
|
-
});
|
|
384
|
+
const entries = heldValues("policy.permissionRules", byTool, req);
|
|
388
385
|
if (entries.length <= 1)
|
|
389
386
|
return entries[0];
|
|
390
387
|
const bare = {};
|
|
@@ -3,7 +3,7 @@ import { pathTargetValue } from "../tool-registry.js";
|
|
|
3
3
|
import { pathTargetBaseOf } from "../effective-path-target.js";
|
|
4
4
|
import { PATH_WRITE_TOOLS, isWithin } from "./session-rule-policy.js";
|
|
5
5
|
import { parseSkillToolEntry, skillSpecifierRejection } from "../skill-tool-specifier.js";
|
|
6
|
-
import {
|
|
6
|
+
import { heldValues, wireOf } from "../tool-name-readings.js";
|
|
7
7
|
export class ActiveSkillScope {
|
|
8
8
|
frames = [];
|
|
9
9
|
push(frame) {
|
|
@@ -23,7 +23,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
23
23
|
const { scope, env, rootPath, toolEffects } = opts;
|
|
24
24
|
return {
|
|
25
25
|
async check(req, signal) {
|
|
26
|
-
const
|
|
26
|
+
const entriesFor = (byName) => heldValues("policy.skillScope", byName, req).flat();
|
|
27
27
|
const frames = scope.active();
|
|
28
28
|
if (frames.length === 0)
|
|
29
29
|
return { action: "allow" };
|
|
@@ -48,7 +48,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
48
48
|
}
|
|
49
49
|
return { manifest: m, byName };
|
|
50
50
|
});
|
|
51
|
-
if (perFrame.length === 0 || perFrame.some((f) =>
|
|
51
|
+
if (perFrame.length === 0 || perFrame.some((f) => entriesFor(f.byName).length === 0)) {
|
|
52
52
|
const ids = manifests.map((m) => m.lineageId).join(", ");
|
|
53
53
|
return {
|
|
54
54
|
action: "deny",
|
|
@@ -57,7 +57,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
for (const frame of perFrame) {
|
|
60
|
-
const alternatives =
|
|
60
|
+
const alternatives = entriesFor(frame.byName);
|
|
61
61
|
const rejections = [];
|
|
62
62
|
let admitted = false;
|
|
63
63
|
for (const entry of alternatives) {
|
|
@@ -80,7 +80,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
80
80
|
const declaredTarget = req.face?.pathTarget;
|
|
81
81
|
const skillScopeEligible = declaredTarget !== undefined && declaredTarget.skillScopeEligible === true;
|
|
82
82
|
if (pathConstrainingActive && !skillScopeEligible) {
|
|
83
|
-
const eff = toolEffects?.get(req
|
|
83
|
+
const eff = toolEffects?.get(wireOf("policy.skillScope.effect", req)) ?? "write";
|
|
84
84
|
if (eff !== "read") {
|
|
85
85
|
const ids = manifests.filter((m) => m.allowPaths?.length).map((m) => m.lineageId).join(", ");
|
|
86
86
|
return {
|
|
@@ -3,6 +3,7 @@ import { boundInputHashOf } from "../canonical-json.js";
|
|
|
3
3
|
import { CheckpointError } from "../checkpoint-store.js";
|
|
4
4
|
import { formatHookFeedback } from "../hooks.js";
|
|
5
5
|
import { delimitUntrusted, REVIEWER_NOTE_MAX_BODY } from "../untrusted-text.js";
|
|
6
|
+
import { wireOf } from "../tool-name-readings.js";
|
|
6
7
|
export function resumeDecisionWasNegative(resume) {
|
|
7
8
|
const o = resume.outcome;
|
|
8
9
|
return o.decision === "reject" || o.decision === "deny";
|
|
@@ -49,7 +50,7 @@ export function sameWinner(incoming, persisted) {
|
|
|
49
50
|
(persisted.reason === undefined || incoming.reason === persisted.reason));
|
|
50
51
|
}
|
|
51
52
|
export function pendingContentAskCallId(cp) {
|
|
52
|
-
return cp.pendingAction.kind === "tool_approval" && cp.pendingAction
|
|
53
|
+
return cp.pendingAction.kind === "tool_approval" && wireOf("resume.contentAskRow", cp.pendingAction) === ASK_USER_QUESTION_TOOL_NAME
|
|
53
54
|
? cp.pendingAction.toolCallId
|
|
54
55
|
: undefined;
|
|
55
56
|
}
|
|
@@ -3,7 +3,7 @@ import { type AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
|
3
3
|
import { type ClassifierUnavailableSeat } from "../classifier-unavailable-arm.js";
|
|
4
4
|
import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
|
|
5
5
|
import { type EngineNotice } from "../types.js";
|
|
6
|
-
import { type AskOrigin, type AskOriginFacts } from "../ask-origin.js";
|
|
6
|
+
import { type AskClass, type AskClassFacts, type AskClassification, type AskOrigin, type AskOriginFacts } from "../ask-origin.js";
|
|
7
7
|
import type { Prepared, StopForDenialLimit } from "./contracts.js";
|
|
8
8
|
/**
|
|
9
9
|
* The classifier DENIAL-LIMIT arms of the tool gate's inherited (delegation) lane, extracted
|
|
@@ -98,34 +98,38 @@ export type InheritedAsk = Extract<PermissionResult, {
|
|
|
98
98
|
* classifier runs inside the policy fold, BEFORE the live gate's org layer has spoken and before its
|
|
99
99
|
* question-tool and marked-call lanes run, so they read as absent here and those asks meet their
|
|
100
100
|
* exclusions downstream, where the facts exist. Excluded asks fall through to the frozen approver
|
|
101
|
-
* chain, the delegation lane's original chain. The word
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* what the ancestor's ask carries on ITSELF still answers, read off the decision by the one predicate.
|
|
101
|
+
* chain, the delegation lane's original chain. The word and the class set are derived inside
|
|
102
|
+
* {@link judgeInheritedClassifier} and threaded to every reader ({@link inheritedAskCarry}).
|
|
103
|
+
* The MANDATE marks here are empty (the base names no seat): the policy-chain phase builds each call's facts over this
|
|
104
|
+
* base with THIS task's tool seat — the seat the request's risk axes are read from — so a marked tool's ask is
|
|
105
|
+
* classified by the seat at the station as at the gate. What the ancestor's ask carries on ITSELF still answers.
|
|
107
106
|
*/
|
|
108
107
|
export declare const INHERITED_STATION_FACTS: AskOriginFacts;
|
|
109
108
|
/** The members of a surviving ask that ride onto the approval request with a COMPUTED value — what the
|
|
110
109
|
* four request mint stations (the gate's own and the three inherited-lane ones) spread after the seats
|
|
111
|
-
* they spell themselves: the ask's origin word and
|
|
110
|
+
* they spell themselves: the ask's origin word, its class (the word and the set, carried together) and its
|
|
111
|
+
* denial-limit fallback. */
|
|
112
112
|
export interface AskRequestCarry {
|
|
113
113
|
origin?: AskOrigin;
|
|
114
|
+
askClass?: AskClass;
|
|
115
|
+
classes?: readonly AskClass[];
|
|
114
116
|
denialLimitFallback?: DenialLimitFallback;
|
|
115
117
|
}
|
|
116
|
-
/** The GATE's own mint station:
|
|
117
|
-
* OWN snapshot (its stamp point spreads the policy's object and stamps the word onto the copy),
|
|
118
|
-
* origin
|
|
118
|
+
/** The GATE's own mint station: every member is read off the surviving decision, which is the gate's
|
|
119
|
+
* OWN snapshot (its stamp point spreads the policy's object and stamps the word and the class onto the copy),
|
|
120
|
+
* so the origin and the class are the engine-derived ones and the fallback member is the one the gate's
|
|
121
|
+
* classifier step minted. */
|
|
119
122
|
export declare function gateAskCarry(decision: InheritedAsk, liveApprover: boolean, tracker: AutoModeDenialTracker | undefined): AskRequestCarry;
|
|
120
123
|
/** An INHERITED-lane mint station (the two wrapper arms and the approved-edit re-check): the ask is an
|
|
121
|
-
* ancestor policy's own decision, never engine-stamped, so
|
|
122
|
-
* {@link judgeInheritedClassifier} handed back — the word it derived under the station
|
|
123
|
-
* fallback member it captured — each read ONCE there and threaded here, never re-read off the
|
|
124
|
+
* ancestor policy's own decision, never engine-stamped, so EVERY member comes from the judgment
|
|
125
|
+
* {@link judgeInheritedClassifier} handed back — the word and the classification it derived under the station
|
|
126
|
+
* facts and the fallback member it captured — each read ONCE there and threaded here, never re-read off the
|
|
124
127
|
* caller-owned object (a stateful accessor could otherwise answer the card with one value and the
|
|
125
|
-
* settlement with another; a self-declared `origin` on the ancestor's decision is never
|
|
128
|
+
* settlement with another; a self-declared `origin` / `askClass` / `classes` on the ancestor's decision is never
|
|
129
|
+
* read). */
|
|
126
130
|
export declare function inheritedAskCarry(judged: Pick<Extract<InheritedClassifierJudgment<InheritedAsk>, {
|
|
127
131
|
kind: "resolve";
|
|
128
|
-
}>, "origin" | "fallback" | "ask">, liveApprover: boolean, tracker: AutoModeDenialTracker | undefined): AskRequestCarry;
|
|
132
|
+
}>, "origin" | "classification" | "fallback" | "ask">, liveApprover: boolean, tracker: AutoModeDenialTracker | undefined): AskRequestCarry;
|
|
129
133
|
/** What {@link judgeInheritedClassifier} hands back to the arm that called it. */
|
|
130
134
|
export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
131
135
|
/** The frozen classifier allowed. `result` is the allow the arm RETURNS — minted at the judging station so
|
|
@@ -146,8 +150,11 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
146
150
|
* `origin` is the ask's word under the station facts, threaded to the request mint: on the two
|
|
147
151
|
* unchanged-ask branches it is the SAME read the exclusion judged by (a caller-owned decision with a
|
|
148
152
|
* stateful accessor cannot answer the exclusion with one word and the card with another); on the
|
|
149
|
-
* bound branch it is derived from the re-spoken ask this station minted (its own object).
|
|
150
|
-
|
|
153
|
+
* bound branch it is derived from the re-spoken ask this station minted (its own object).
|
|
154
|
+
* `classification` is the class set of the ask handed on, under the same station facts: on the two unchanged-ask
|
|
155
|
+
* branches the one read the exclusion judged by, on the unavailable and bound branches a read of the object this
|
|
156
|
+
* station minted (each adds a real-approval bit, which the set must reflect). */
|
|
157
|
+
/** `ask` is the incoming ask unchanged (the arm's receipt snapshot of the ancestor's decision), or — an UNAVAILABLE classification with a live frozen
|
|
151
158
|
* approver — that ask with the classifier sentence appended; without one it is a `deny` whose attested cause reads `unavailable`. */
|
|
152
159
|
| {
|
|
153
160
|
kind: "resolve";
|
|
@@ -155,6 +162,7 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
155
162
|
fallback: UnarmedDenialLimitFallback | undefined;
|
|
156
163
|
mintedHere: boolean;
|
|
157
164
|
origin: AskOrigin;
|
|
165
|
+
classification: AskClassification;
|
|
158
166
|
};
|
|
159
167
|
/**
|
|
160
168
|
* The ancestor's frozen classifier, run BEFORE its frozen approver in the ancestor's own gate order,
|
|
@@ -195,6 +203,8 @@ export declare function judgeInheritedClassifier<A extends InheritedAsk>(opts: {
|
|
|
195
203
|
allowRewrite?: unknown;
|
|
196
204
|
/** An `unavailable` verdict's fork facts (`classifierUnavailableArm`): is the frozen approver live; who hears it. */
|
|
197
205
|
unavailableSeat?: ClassifierUnavailableSeat;
|
|
206
|
+
/** The station's facts for this call (the base with this task's tool seat); absent ⇒ {@link INHERITED_STATION_FACTS}. */
|
|
207
|
+
facts?: AskClassFacts;
|
|
198
208
|
}): Promise<InheritedClassifierJudgment<A>>;
|
|
199
209
|
/** The resolver's answer as the settlement reads it. */
|
|
200
210
|
export type FallbackResolution = Pick<ResolvedAsk, "action" | "settlement" | "approverUnavailable">;
|