@sema-agent/core 7.36.0 → 7.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -0
- package/dist/brain/anthropic.js +7 -14
- package/dist/brain/open-responses.js +6 -13
- package/dist/brain/openai.js +7 -14
- package/dist/brain/stream-shared.d.ts +21 -0
- package/dist/brain/stream-shared.js +13 -0
- package/dist/core/ask-origin.d.ts +35 -5
- package/dist/core/ask-origin.js +23 -4
- package/dist/core/checkpoint-store.d.ts +13 -3
- package/dist/core/decision-receipt.d.ts +21 -0
- package/dist/core/decision-receipt.js +63 -0
- package/dist/core/engine-notice.d.ts +17 -5
- package/dist/core/gate-fold.js +2 -1
- package/dist/core/gate-lanes.js +8 -13
- 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 +11 -2
- package/dist/core/hooks.js +3 -1
- package/dist/core/park-selfcheck.js +2 -0
- 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/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-park-ask.d.ts +2 -2
- package/dist/core/runner/prepare-park-ask.js +1 -5
- package/dist/core/runner/prepare-policy-chain.d.ts +3 -8
- package/dist/core/runner/prepare-policy-chain.js +43 -25
- package/dist/core/runner/run-identity-wiring.d.ts +1 -1
- package/dist/core/runner/run-identity-wiring.js +2 -0
- package/dist/core/runner/tool-turn-repair-seat.d.ts +8 -0
- package/dist/core/runner/tool-turn-repair-seat.js +28 -0
- package/dist/core/session-reconcile.d.ts +35 -0
- package/dist/core/session-reconcile.js +31 -20
- package/dist/core/side-query.js +4 -2
- package/dist/core/store-contracts/checkpoint-store-contract.js +18 -0
- package/dist/core/tool-name-readings.d.ts +4 -0
- package/dist/core/tool-name-readings.js +1 -0
- package/dist/core/tool-policy.d.ts +74 -84
- package/dist/core/tool-policy.js +71 -36
- package/dist/engine/harness/agent-harness.d.ts +7 -0
- package/dist/engine/harness/agent-harness.js +14 -2
- package/dist/engine/llm/index.d.ts +1 -0
- package/dist/engine/llm/index.js +1 -0
- package/dist/engine/llm/tool-turns.d.ts +20 -0
- package/dist/engine/llm/tool-turns.js +91 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/llm.d.ts +2 -2
- package/dist/internal/llm.js +1 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +7 -1
- package/wire-changes/core@7.37.0.json +405 -0
- package/wire-changes/core@7.37.1.json +122 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PermissionResult } from "./tool-policy.js";
|
|
2
|
+
/** The screen's one refusal — every arm's deny, so an out-of-contract decision is one code whichever arm caught it. */
|
|
3
|
+
export declare const refusedOutOfContract: (message: string) => PermissionResult;
|
|
4
|
+
/** One level of the receipt, the same for the decision and each record it nests. The carrier is an ORDINARY object — it and
|
|
5
|
+
* every object on its prototype chain are looked at for being a Proxy before any descriptor is asked for (a Proxy's traps are
|
|
6
|
+
* the caller's code; the look invokes none of them) — so the receipt runs none of the carrier's code: a contract member is
|
|
7
|
+
* absent or an own enumerable DATA property, its value taken off the descriptor (no read at all). Anything else refuses the
|
|
8
|
+
* carrier, the member named by `path`: an accessor (never invoked), and a member the prototype or a non-enumerable property
|
|
9
|
+
* supplies, unless the nearest descriptor holding it spells absence (an `undefined` data property). Descriptors are looked up
|
|
10
|
+
* as OWN entries of the descriptor map, so a polluted `Object.prototype` names no member. Members outside the contract are
|
|
11
|
+
* not looked at. */
|
|
12
|
+
/** The receipt's own answers carry a `kind` discriminant as an own property: narrowing never asks the prototype chain (`in`) for a
|
|
13
|
+
* word, so a polluted `Object.prototype` cannot answer for the receipt either. */
|
|
14
|
+
export type Received = {
|
|
15
|
+
kind: "held";
|
|
16
|
+
held: Record<string, unknown>;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "refused";
|
|
19
|
+
refused: PermissionResult;
|
|
20
|
+
};
|
|
21
|
+
export declare function receiveContract(carrier: object, members: readonly string[], path: string): Received;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { types } from "node:util";
|
|
2
|
+
import { thrownText } from "./thrown-value.js";
|
|
3
|
+
export const refusedOutOfContract = (message) => ({ action: "deny", message, decisionReason: "rule" });
|
|
4
|
+
const spellsAbsence = (d) => Object.hasOwn(d, "value") && d.value === undefined;
|
|
5
|
+
function ordinaryChain(carrier) {
|
|
6
|
+
const chain = [];
|
|
7
|
+
const seen = new Set();
|
|
8
|
+
try {
|
|
9
|
+
for (let o = carrier; o !== null; o = Object.getPrototypeOf(o)) {
|
|
10
|
+
if (types.isProxy(o))
|
|
11
|
+
return { kind: "not", why: o === carrier ? "is a Proxy" : "has a Proxy on its prototype chain" };
|
|
12
|
+
if (seen.has(o))
|
|
13
|
+
return { kind: "not", why: "has a cyclic prototype chain" };
|
|
14
|
+
seen.add(o);
|
|
15
|
+
chain.push(o);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
return { kind: "not", why: `threw while its prototype chain was looked at (${thrownText(e)})` };
|
|
20
|
+
}
|
|
21
|
+
return { kind: "chain", chain };
|
|
22
|
+
}
|
|
23
|
+
export function receiveContract(carrier, members, path) {
|
|
24
|
+
const what = path === "" ? "a permission decision" : `a permission decision's "${path.slice(0, -1)}" record`;
|
|
25
|
+
const ordinary = ordinaryChain(carrier);
|
|
26
|
+
if (ordinary.kind === "not") {
|
|
27
|
+
return { kind: "refused", refused: refusedOutOfContract(`${what} ${ordinary.why} — the engine reads a decision off an ordinary object's own property descriptors and runs none of the carrier's code; denied fail-closed`) };
|
|
28
|
+
}
|
|
29
|
+
let own;
|
|
30
|
+
try {
|
|
31
|
+
own = Object.getOwnPropertyDescriptors(carrier);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
return { kind: "refused", refused: refusedOutOfContract(`${what} threw while its property descriptors were looked at (${thrownText(e)}) — not an ordinary object; denied fail-closed`) };
|
|
35
|
+
}
|
|
36
|
+
const held = Object.create(null);
|
|
37
|
+
for (const m of members) {
|
|
38
|
+
const d = Object.hasOwn(own, m) ? own[m] : undefined;
|
|
39
|
+
if (d !== undefined && !Object.hasOwn(d, "value")) {
|
|
40
|
+
return { kind: "refused", refused: refusedOutOfContract(`${what}'s contract member "${path}${m}" is held by an accessor — contract members must be own data properties, not values supplied through an accessor; denied fail-closed`) };
|
|
41
|
+
}
|
|
42
|
+
if (d !== undefined && d.enumerable === true) {
|
|
43
|
+
held[m] = d.value;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
let reached = d;
|
|
47
|
+
try {
|
|
48
|
+
for (let i = 1; reached === undefined && i < ordinary.chain.length; i++)
|
|
49
|
+
reached = Object.getOwnPropertyDescriptor(ordinary.chain[i], m);
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
return { kind: "refused", refused: refusedOutOfContract(`${what} threw while "${path}${m}" was looked for on its prototype chain (${thrownText(e)}) — not an ordinary object; denied fail-closed`) };
|
|
53
|
+
}
|
|
54
|
+
if (reached !== undefined && !spellsAbsence(reached)) {
|
|
55
|
+
return {
|
|
56
|
+
kind: "refused",
|
|
57
|
+
refused: refusedOutOfContract(`${what} supplies "${path}${m}" other than as its own enumerable property (its prototype or a non-enumerable property) — ` +
|
|
58
|
+
`contract fields must be own properties, so every reader sees the one value the engine read; denied fail-closed`),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { kind: "held", held };
|
|
63
|
+
}
|
|
@@ -258,6 +258,15 @@ export interface EngineNotice {
|
|
|
258
258
|
* call fact. Audience `operator`; `detail: { toolCallId, rawLength, reason, sessionId, runId }` —
|
|
259
259
|
* `toolCallId` the engine id, `reason` `"charset"` (a control byte, a space, a non-ASCII unit or `~`) or
|
|
260
260
|
* `"length"` (admitted characters, over 128); never the raw bytes. Absent ⇒ no call of the run was folded.
|
|
261
|
+
* - `"context.tool_turn_repaired"` (#1224) — a provider request was built from a transcript that breaks the
|
|
262
|
+
* tool-turn law, and the request view restored it (`engine/llm/tool-turns.ts`; the transcript is never
|
|
263
|
+
* rewritten): a block after an assistant's tool calls moved ahead of them (`trailing_text`), a result recorded
|
|
264
|
+
* later moved up behind its call (`late_result`), a call with no result closed with the reconcile family's
|
|
265
|
+
* interrupted result (`missing_result`), a second result recorded for one call folded into the first
|
|
266
|
+
* (`duplicate_result`; an interrupted close beside a recorded result is left out, no recorded result is). Advisory:
|
|
267
|
+
* the run proceeds on the repaired request. Audience `operator`; `detail: { repairs: [{ form, toolCallIds }],
|
|
268
|
+
* sessionId, runId }`. ONCE PER RUN PER (form, calls) — the same broken turn is repaired on every request of
|
|
269
|
+
* the run and announced on the first. Absent ⇒ every request of the run already had the structure.
|
|
261
270
|
* - `"config.read_face_deployment_clamped"` (#237) — a deployment-wide `readFace: "open"` is not
|
|
262
271
|
* in force beside a read-only (verifier) mount: it clamps to "roots" without throwing
|
|
263
272
|
* (stricter-wins; the clamp verdict stands, only its occurrence was undisclosed). Announced
|
|
@@ -337,7 +346,8 @@ export interface EngineNotice {
|
|
|
337
346
|
* `permissionMode` (`autoModeRequested: true` ⇒ `auto`; a posture is dropped — it never authorized anything;
|
|
338
347
|
* `shellGate` `"off"` / `"classify"` are dropped, `"always"` kept) and the next suspend writes the new member.
|
|
339
348
|
* `detail: { migrated: { permissionMode?, droppedKeys }, sessionId, runId }`. Audience `operator`. A row the
|
|
340
|
-
* migration cannot read refuses the resume instead (`resume.legacy_mode_seed`, the error-code row above).
|
|
349
|
+
* migration cannot read refuses the resume instead (`resume.legacy_mode_seed`, the error-code row above). The
|
|
350
|
+
* migration is kept through 7.37.x and removed in 7.38.0, which refuses a row still carrying these keys.
|
|
341
351
|
* - `"config.durable_gate_unavailable"` — a durable-approval MANDATE is in force on a leg with NO
|
|
342
352
|
* checkpoint store: the per-principal `forceDurableGate` entitlement, or the task's own
|
|
343
353
|
* `durableApproval` opt-in with no live approver seat (`durableMandateSourceOf`, the predicate the
|
|
@@ -712,10 +722,12 @@ export interface EngineNotice {
|
|
|
712
722
|
* `detail.settlement` names how: `"delivered"` (a re-judgment or an approval released it into
|
|
713
723
|
* this session's model context), `"refused"` (the setting moved to refuse, or an approval met a
|
|
714
724
|
* refusing policy), `"expired"` (the `dialogExpiry` deadline, an eviction when the 100-entry queue
|
|
715
|
-
* was full, a cancelled review,
|
|
716
|
-
* (the injection lane refused the released frame).
|
|
717
|
-
* (`deadline | evicted | shutdown | rejudge |
|
|
718
|
-
*
|
|
725
|
+
* was full, a cancelled review, a graceful shutdown, or a record another lease holder already settled),
|
|
726
|
+
* `"denied"` (the review face), `"dropped"` (the injection lane refused the released frame).
|
|
727
|
+
* `detail.reason` is the trigger (`PeerHeldSettleReason`: `deadline | evicted | shutdown | rejudge |
|
|
728
|
+
* approved | denied | cancelled | inject_failed | superseded`); the sender was sent the matching receipt
|
|
729
|
+
* whenever the record carried a reply address — except on `superseded`, where the lease holder that
|
|
730
|
+
* settled the record sent it.
|
|
719
731
|
* `detail: { settlement, reason, seq, box, fromSession?, heldCount, sessionId, runId }`. Audience
|
|
720
732
|
* `"user"`: it is this session's user who held (or was holding) the message.
|
|
721
733
|
* - `"peer.idle_subscription"` (design/385 §5.2, slice 4) — a peer session asked to be told when
|
package/dist/core/gate-fold.js
CHANGED
|
@@ -6,11 +6,12 @@ import { traceHookCrash } from "./gate-crash-trace.js";
|
|
|
6
6
|
import { writeProtectionMandated, writeProtectionTightenDecision, writeProtectionUnresolved } from "./write-protect.js";
|
|
7
7
|
import { createGateDeadline, awaitWriteProtectionJudgment, gateWaitFailureOf, withGateTimeout } from "./gate-bounded-await.js";
|
|
8
8
|
import { thrownText } from "./thrown-value.js";
|
|
9
|
+
import { refuseOutOfContractDecision } from "./tool-policy.js";
|
|
9
10
|
export async function runGateFold(pass) {
|
|
10
11
|
const { input, toolName, toolCallId, callSignal, ledger, notifier, adjudicate } = pass;
|
|
11
12
|
const boundedAwaitDeadlineMs = createGateDeadline(input, notifier);
|
|
12
13
|
pass.req = { toolName, args: pass.currentInput, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
|
|
13
|
-
pass.decision = adjudicate ? await (callSignal !== undefined ? adjudicate(pass.req, callSignal) : adjudicate(pass.req)) : { action: "allow" };
|
|
14
|
+
pass.decision = adjudicate ? refuseOutOfContractDecision(await (callSignal !== undefined ? adjudicate(pass.req, callSignal) : adjudicate(pass.req))) : { action: "allow" };
|
|
14
15
|
{
|
|
15
16
|
const refusedBy = ledger.fromDecision(pass.decision);
|
|
16
17
|
if (refusedBy !== undefined)
|
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";
|
|
@@ -181,9 +181,7 @@ export async function runGateLanes(pass) {
|
|
|
181
181
|
...(input.toolMarkSources !== undefined ? { toolMarkSources: input.toolMarkSources } : {}),
|
|
182
182
|
...(input.a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
|
|
183
183
|
});
|
|
184
|
-
const classifyStanding = (unstamped, facts) =>
|
|
185
|
-
pass.askClassification = classifyAsk(unstamped, facts);
|
|
186
|
-
};
|
|
184
|
+
const classifyStanding = (unstamped, facts) => (pass.askClassification = classifyAsk(unstamped, facts));
|
|
187
185
|
let orgTightenCount = 0;
|
|
188
186
|
let orgEvidence = (() => {
|
|
189
187
|
const w = input.orgRules === undefined ? "not_wired" : "not_adjudicated";
|
|
@@ -355,8 +353,7 @@ export async function runGateLanes(pass) {
|
|
|
355
353
|
const originFacts = askOriginFacts(orgAnswer.org, ruleCallUnreadableOf(laneAnswer));
|
|
356
354
|
if (pass.decision.action === "ask") {
|
|
357
355
|
const stamped = { ...pass.decision, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}) };
|
|
358
|
-
classifyStanding(stamped, originFacts);
|
|
359
|
-
pass.decision = stampAskOrigin(stamped, originFacts);
|
|
356
|
+
pass.decision = stampAskOrigin(stamped, originFacts, classifyStanding(stamped, originFacts));
|
|
360
357
|
}
|
|
361
358
|
if (allowLayerMayClear() && pass.decision.action === "ask" && ruleMayClear() && pass.probeBoundaryDeclined !== true && wireOf("lanes.readOnlyShellAllow", pass.req) === COMMAND_RULE_TOOL) {
|
|
362
359
|
const commandParam = catalogRuleFaceOf(COMMAND_RULE_TOOL)?.primaryParams[0] ?? "command";
|
|
@@ -399,8 +396,7 @@ export async function runGateLanes(pass) {
|
|
|
399
396
|
else if (verdict?.kind === "unavailable") {
|
|
400
397
|
const arm = classifierUnavailableArm({ ask: pass.decision, req: pass.req, cause: verdict.cause, attribution: verdict.classifier, seat: input.autoMode.unavailable, aborted: classifySignal?.aborted === true });
|
|
401
398
|
if (arm.kind === "asked") {
|
|
402
|
-
classifyStanding(arm.ask, originFacts);
|
|
403
|
-
pass.decision = stampAskOrigin(arm.ask, originFacts);
|
|
399
|
+
pass.decision = stampAskOrigin(arm.ask, originFacts, classifyStanding(arm.ask, originFacts));
|
|
404
400
|
pass.classifierRound = undefined;
|
|
405
401
|
}
|
|
406
402
|
else
|
|
@@ -418,8 +414,7 @@ export async function runGateLanes(pass) {
|
|
|
418
414
|
requiresRealApproval: true,
|
|
419
415
|
denialLimitFallback: tracked.fallback,
|
|
420
416
|
};
|
|
421
|
-
classifyStanding(fallbackAsk, originFacts);
|
|
422
|
-
pass.decision = stampAskOrigin(fallbackAsk, originFacts);
|
|
417
|
+
pass.decision = stampAskOrigin(fallbackAsk, originFacts, classifyStanding(fallbackAsk, originFacts));
|
|
423
418
|
}
|
|
424
419
|
else {
|
|
425
420
|
const denied = {
|
|
@@ -517,6 +512,7 @@ export async function runGateLanes(pass) {
|
|
|
517
512
|
...(callSignal !== undefined ? { signal: callSignal } : {}),
|
|
518
513
|
...(d.action === "ask" && d.denialLimitFallback !== undefined ? { denialLimitFallback: unarmedWindow(d.denialLimitFallback) } : {}),
|
|
519
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 } : {}),
|
|
520
516
|
...(d.action === "ask" && d.mandated === true ? { mandated: true, ...(d.mandate !== undefined ? { mandate: d.mandate } : {}) } : {}),
|
|
521
517
|
...(d.action === "ask" && d.authoredBy === "deployment" ? { authoredBy: "deployment" } : {}),
|
|
522
518
|
});
|
|
@@ -662,7 +658,7 @@ export async function runGateLanes(pass) {
|
|
|
662
658
|
if (adjudicate) {
|
|
663
659
|
try {
|
|
664
660
|
const editReq = { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
|
|
665
|
-
recheck = await (callSignal !== undefined ? adjudicate(editReq, callSignal) : adjudicate(editReq));
|
|
661
|
+
recheck = refuseOutOfContractDecision(await (callSignal !== undefined ? adjudicate(editReq, callSignal) : adjudicate(editReq)));
|
|
666
662
|
}
|
|
667
663
|
catch (err) {
|
|
668
664
|
recheck = {
|
|
@@ -720,8 +716,7 @@ export async function runGateLanes(pass) {
|
|
|
720
716
|
const editAskSnapshot = { ...recheck, ruleEvidence: mintRuleEvidence({ dotsAbsent: "not_adjudicated" }) };
|
|
721
717
|
markedThisRound = undefined;
|
|
722
718
|
const editFacts = askOriginFacts(editOrg.org, editRuleCallUnreadable);
|
|
723
|
-
classifyStanding(editAskSnapshot, editFacts);
|
|
724
|
-
const editAskDecision = stampAskOrigin(editAskSnapshot, editFacts);
|
|
719
|
+
const editAskDecision = stampAskOrigin(editAskSnapshot, editFacts, classifyStanding(editAskSnapshot, editFacts));
|
|
725
720
|
if (input.permissionMode !== undefined && pass.askClassification !== undefined) {
|
|
726
721
|
const editVerdict = arbitrateByMode(input.permissionMode(), pass.askClassification, { ...(editAskDecision.origin !== undefined ? { origin: editAskDecision.origin } : {}), ...(editAskDecision.mandate !== undefined ? { mandate: editAskDecision.mandate } : {}) });
|
|
727
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", "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"];
|
|
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", "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", "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. */
|
|
@@ -179,6 +179,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
179
179
|
"route.base_url_changed_key_unchanged",
|
|
180
180
|
"route.response_model_mismatch",
|
|
181
181
|
"route.tool_call_id_folded",
|
|
182
|
+
"context.tool_turn_repaired",
|
|
182
183
|
"task.user_steer_undrained",
|
|
183
184
|
"task.user_followup_undrained",
|
|
184
185
|
"steering.parked_input_blocked",
|
|
@@ -271,6 +272,7 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
271
272
|
"route.base_url_changed_key_unchanged": "operator",
|
|
272
273
|
"route.response_model_mismatch": "operator",
|
|
273
274
|
"route.tool_call_id_folded": "operator",
|
|
275
|
+
"context.tool_turn_repaired": "operator",
|
|
274
276
|
"skills.listing_truncated": "operator",
|
|
275
277
|
"agents.listing_truncated": "operator",
|
|
276
278
|
"tool_result.offload_put_failed": "operator",
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -1737,6 +1737,12 @@ export interface AskCarry {
|
|
|
1737
1737
|
/** The surviving ask's engine-stamped origin word ({@link import("./ask-origin.js").AskOrigin}), persisted
|
|
1738
1738
|
* on the row so a durable card is rendered by the same word the synchronous card is. */
|
|
1739
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[];
|
|
1740
1746
|
/** The stamped mandate ({@link import("./tool-policy.js").AskRequest.mandated}) — the park site's
|
|
1741
1747
|
* rule-offer facts read it, so a parked mandated ask offers no rule, like the live card. */
|
|
1742
1748
|
readonly mandated?: true;
|
|
@@ -1747,11 +1753,14 @@ export interface AskCarry {
|
|
|
1747
1753
|
}
|
|
1748
1754
|
/** What a durable row persists of an {@link AskCarry}: the fallback member COPIED (the row never aliases
|
|
1749
1755
|
* the gate's object; its window is already `0` — the gate's carry mint is the one writer of that
|
|
1750
|
-
* number)
|
|
1751
|
-
* 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). */
|
|
1752
1759
|
export declare function askCarryRowMembers(carry: AskCarry | undefined): {
|
|
1753
1760
|
denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1754
1761
|
origin?: import("./ask-origin.js").AskOrigin;
|
|
1762
|
+
askClass?: import("./ask-origin.js").AskClass;
|
|
1763
|
+
classes?: readonly import("./ask-origin.js").AskClass[];
|
|
1755
1764
|
};
|
|
1756
1765
|
/**
|
|
1757
1766
|
* The design/37 **two-phase tool gate** — the single chokepoint that makes the load-bearing invariant
|
package/dist/core/hooks.js
CHANGED
|
@@ -7,7 +7,7 @@ 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";
|
|
@@ -239,9 +239,11 @@ export function askCarryRowMembers(carry) {
|
|
|
239
239
|
if (carry === undefined)
|
|
240
240
|
return {};
|
|
241
241
|
const origin = carry.origin;
|
|
242
|
+
const classed = screenAskClassMembers(carry.askClass, carry.classes);
|
|
242
243
|
return {
|
|
243
244
|
...(carry.denialLimitFallback !== undefined ? { denialLimitFallback: { ...carry.denialLimitFallback } } : {}),
|
|
244
245
|
...(isAskOrigin(origin) ? { origin } : {}),
|
|
246
|
+
...(classed !== undefined ? classed : {}),
|
|
245
247
|
};
|
|
246
248
|
}
|
|
247
249
|
export async function runToolGate(input) {
|
|
@@ -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
|
},
|
|
@@ -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">;
|
|
@@ -5,7 +5,7 @@ import { attestClassifierDenyCause, attestClassifierRound } from "../tool-policy
|
|
|
5
5
|
import { SETTLEMENT_NOBODY_JUDGED } from "../gate-outcome.js";
|
|
6
6
|
import { deliverEngineNotice } from "../types.js";
|
|
7
7
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
8
|
-
import { askOriginOf, classifyAsk } from "../ask-origin.js";
|
|
8
|
+
import { askClassMembersOf, askOriginOf, classifyAsk } from "../ask-origin.js";
|
|
9
9
|
export function attachRebuiltDenialTrackers(entries, denialLimit) {
|
|
10
10
|
if (entries === undefined)
|
|
11
11
|
return undefined;
|
|
@@ -68,30 +68,34 @@ function fallbackCarry(fallback, liveApprover, tracker) {
|
|
|
68
68
|
export function gateAskCarry(decision, liveApprover, tracker) {
|
|
69
69
|
return {
|
|
70
70
|
...(decision.origin !== undefined ? { origin: decision.origin } : {}),
|
|
71
|
+
...(decision.askClass !== undefined && decision.classes !== undefined ? { askClass: decision.askClass, classes: decision.classes } : {}),
|
|
71
72
|
...fallbackCarry(decision.denialLimitFallback, liveApprover, tracker),
|
|
72
73
|
};
|
|
73
74
|
}
|
|
74
75
|
export function inheritedAskCarry(judged, liveApprover, tracker) {
|
|
75
76
|
return {
|
|
76
77
|
origin: judged.origin,
|
|
78
|
+
...askClassMembersOf(judged.classification),
|
|
77
79
|
...fallbackCarry(judged.fallback, liveApprover, tracker),
|
|
78
80
|
};
|
|
79
81
|
}
|
|
80
82
|
export async function judgeInheritedClassifier(opts) {
|
|
81
83
|
const { autoMode, ask, req } = opts;
|
|
82
|
-
const
|
|
84
|
+
const facts = opts.facts ?? INHERITED_STATION_FACTS;
|
|
85
|
+
const origin = askOriginOf(ask, facts);
|
|
86
|
+
const classification = classifyAsk(ask, facts);
|
|
83
87
|
const classifierStandsDown = () => {
|
|
84
88
|
const live = opts.liveMode?.();
|
|
85
89
|
return live === "dontAsk" || live === "bypassPermissions" || live === "plan" || (autoMode?.liveMode !== undefined && autoMode.liveMode.current !== "auto");
|
|
86
90
|
};
|
|
87
|
-
if (autoMode === undefined || classifierStandsDown() || !
|
|
88
|
-
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin };
|
|
91
|
+
if (autoMode === undefined || classifierStandsDown() || !classification.row.classifierEligible) {
|
|
92
|
+
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin, classification };
|
|
89
93
|
}
|
|
90
94
|
const verdict = await autoMode.decider
|
|
91
95
|
.decide({ req, ...(ask.message !== undefined ? { askMessage: ask.message } : {}) }, opts.signal)
|
|
92
96
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
93
97
|
if (verdict.kind === "allow" && classifierStandsDown())
|
|
94
|
-
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin };
|
|
98
|
+
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin, classification };
|
|
95
99
|
const attributed = (d) => (verdict.classifier !== undefined ? attestClassifierRound(d, verdict.classifier, req) : d);
|
|
96
100
|
if (verdict.kind === "allow") {
|
|
97
101
|
autoMode.denialTracking?.recordAllow();
|
|
@@ -100,7 +104,7 @@ export async function judgeInheritedClassifier(opts) {
|
|
|
100
104
|
if (verdict.kind === "unavailable") {
|
|
101
105
|
const arm = classifierUnavailableArm({ ask, req, cause: verdict.cause, seat: opts.unavailableSeat, aborted: opts.signal.aborted });
|
|
102
106
|
if (arm.kind === "asked")
|
|
103
|
-
return { kind: "resolve", ask: arm.ask, fallback: ask.denialLimitFallback, mintedHere: false, origin };
|
|
107
|
+
return { kind: "resolve", ask: arm.ask, fallback: ask.denialLimitFallback, mintedHere: false, origin, classification: classifyAsk(arm.ask, facts) };
|
|
104
108
|
return { kind: "deny", result: attributed(arm.deny) };
|
|
105
109
|
}
|
|
106
110
|
const reason = verdict.kind === "block" ? (verdict.reason ? inlineUntrusted(verdict.reason) : "") : classifierParseFailureDenyMessage(verdict.classifier);
|
|
@@ -116,7 +120,7 @@ export async function judgeInheritedClassifier(opts) {
|
|
|
116
120
|
}
|
|
117
121
|
const fallback = tracked.fallback;
|
|
118
122
|
const minted = { ...ask, message: denialLimitFallbackMessage(fallback, reason || category || req.toolName), decisionReason: "classifier", requiresRealApproval: true, denialLimitFallback: fallback };
|
|
119
|
-
return { kind: "resolve", ask: minted, fallback, mintedHere: true, origin: askOriginOf(minted,
|
|
123
|
+
return { kind: "resolve", ask: minted, fallback, mintedHere: true, origin: askOriginOf(minted, facts), classification: classifyAsk(minted, facts) };
|
|
120
124
|
}
|
|
121
125
|
export function headlessDenyAtFold(r) {
|
|
122
126
|
return r.settlement !== undefined && SETTLEMENT_NOBODY_JUDGED[r.settlement.kind];
|