@sema-agent/core 7.38.3 → 7.39.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 +191 -6
- package/dist/core/ask-origin.d.ts +16 -7
- package/dist/core/ask-origin.js +4 -2
- package/dist/core/auto-mode-directive.d.ts +114 -19
- package/dist/core/auto-mode-directive.js +67 -19
- package/dist/core/checkpoint-store.d.ts +72 -41
- package/dist/core/checkpoint-store.js +28 -48
- package/dist/core/classifier-unavailable-arm.js +2 -2
- package/dist/core/decision-receipt.d.ts +2 -0
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/engine-notice.d.ts +41 -17
- package/dist/core/engine-notice.js +26 -14
- package/dist/core/gate-outcome.d.ts +80 -42
- package/dist/core/gate-outcome.js +21 -6
- package/dist/core/governance-codes.d.ts +1 -3
- package/dist/core/governance-codes.js +0 -3
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/hooks.js +2 -2
- package/dist/core/human-input-projection.d.ts +12 -0
- package/dist/core/human-input-projection.js +12 -0
- package/dist/core/memory-engine/engine.js +11 -11
- package/dist/core/memory-engine/layout.d.ts +21 -2
- package/dist/core/memory-engine/layout.js +17 -5
- package/dist/core/mode-policy.d.ts +26 -0
- package/dist/core/mode-policy.js +37 -1
- package/dist/core/output-style.d.ts +21 -0
- package/dist/core/output-style.js +29 -0
- package/dist/core/real-approval.d.ts +25 -0
- package/dist/core/real-approval.js +27 -0
- package/dist/core/reminder-disclosure.d.ts +2 -2
- package/dist/core/reminder-mint.d.ts +6 -9
- package/dist/core/reminder-mint.js +3 -14
- package/dist/core/retired-keys.d.ts +2 -1
- package/dist/core/retired-keys.js +3 -1
- package/dist/core/runner/assemble-result.d.ts +2 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/compaction-restate.d.ts +45 -4
- package/dist/core/runner/compaction-restate.js +89 -11
- package/dist/core/runner/contracts.d.ts +6 -6
- package/dist/core/runner/denial-limit-arms.js +4 -4
- package/dist/core/runner/inherited-ask-grants.d.ts +4 -2
- package/dist/core/runner/inherited-ask-grants.js +0 -3
- package/dist/core/runner/permission-rule-lanes.d.ts +18 -0
- package/dist/core/runner/permission-rule-lanes.js +35 -25
- package/dist/core/runner/prepare-context-lane.d.ts +1 -0
- package/dist/core/runner/prepare-context-lane.js +6 -0
- package/dist/core/runner/prepare-inherited-gate.js +2 -9
- package/dist/core/runner/prepare-task.js +10 -24
- package/dist/core/runner/prepare-wiring-manifest.js +2 -2
- package/dist/core/runner/prompt-block.d.ts +9 -0
- package/dist/core/runner/prompt-block.js +8 -0
- package/dist/core/runner/resume-admission.js +4 -1
- package/dist/core/runner/resume-apply.js +28 -18
- package/dist/core/runner/resume-policy-outcome.js +10 -4
- package/dist/core/runner/run-attachment-seats.js +1 -1
- package/dist/core/runner/run-identity-wiring.js +9 -2
- package/dist/core/runner/run-leg.js +42 -31
- package/dist/core/runner/run-settle-and-teardown.js +4 -3
- package/dist/core/runner/run-turn-boundary.js +4 -3
- package/dist/core/runner/runtask.d.ts +4 -2
- package/dist/core/runner/steer-admission.d.ts +9 -0
- package/dist/core/runner/steer-admission.js +8 -0
- package/dist/core/runner/stream-steer-verb.js +16 -11
- package/dist/core/runner/synthetic-tools.d.ts +4 -0
- package/dist/core/runner/tool-turn-repair-seat.js +1 -1
- package/dist/core/runner/turn-attachments.d.ts +16 -22
- package/dist/core/runner/turn-attachments.js +3 -20
- package/dist/core/runner-deps.d.ts +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +8 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +23 -0
- package/dist/core/task-event.d.ts +7 -2
- package/dist/core/task-spec.d.ts +29 -7
- package/dist/core/terminal-cause.d.ts +44 -3
- package/dist/core/terminal-cause.js +5 -0
- package/dist/core/tool-policy.d.ts +55 -29
- package/dist/core/tool-policy.js +42 -31
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +17 -3
- package/dist/core/wiring-manifest.js +8 -3
- package/dist/engine/harness/agent-harness.d.ts +10 -0
- package/dist/engine/harness/agent-harness.js +12 -1
- package/dist/engine/llm/tool-turns.d.ts +15 -2
- package/dist/engine/llm/tool-turns.js +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/prompt-assembly/event-registry.js +2 -1
- package/dist/tools/fs/index.d.ts +2 -1
- package/dist/tools/fs/read-deny-home.d.ts +3 -2
- package/dist/tools/fs/read-deny-home.js +6 -4
- package/dist/tools/fs/shell-read-check.d.ts +10 -3
- package/dist/tools/fs/shell-read-check.js +2 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +21 -1
- package/wire-changes/core@7.39.0.json +999 -0
- package/wire-changes/core@7.39.1.json +74 -0
|
@@ -278,9 +278,10 @@ export interface EngineNotice {
|
|
|
278
278
|
* the note the model itself reads. `detail.cause` (closed `TOOL_TURN_INCOMPLETE_CAUSES`): `truncated` — calls whose
|
|
279
279
|
* arguments were cut before they parsed, dropped (they did not run; the model is asked to re-issue them);
|
|
280
280
|
* `unnamed` — calls that arrived with no tool name, dropped the same way; `bad_frame` — streamed response frames
|
|
281
|
-
* that could not be read, dropped (the response may be missing content); `missing_result_synthesized` —
|
|
282
|
-
*
|
|
283
|
-
* `missing_result` repair above
|
|
281
|
+
* that could not be read, dropped (the response may be missing content); `missing_result_synthesized` — a
|
|
282
|
+
* call of this run with no recorded result, which the request view closed as interrupted, outcome unknown (the
|
|
283
|
+
* `missing_result` repair above; a call that went unanswered before the run began is the transcript's history and
|
|
284
|
+
* is on that operator line only). Audience `"user"`; `detail: { cause, count, toolCallIds?, sessionId, runId }` —
|
|
284
285
|
* `count` the calls (or frames) of that cause in the turn or request that first showed it; `toolCallIds` on
|
|
285
286
|
* `missing_result_synthesized` only, each id in the provider echo shape (a stored id outside it is not shown;
|
|
286
287
|
* `count` still counts it). Closed-set words, a count and call ids only: never a tool name, an argument or the
|
|
@@ -334,13 +335,13 @@ export interface EngineNotice {
|
|
|
334
335
|
* `sessionId` beside the parent's — a child's own manifest is not shown to the person). The leg runs in
|
|
335
336
|
* `effective` (always `default` for the first two arms), announced once per leg and once per steer that folds;
|
|
336
337
|
* `detail: { requested, effective, by, sessionId, runId }`. Audience `user`.
|
|
337
|
-
* - `"config.permission_mode_invalid"`, `"config.spec_retired_key"
|
|
338
|
-
*
|
|
338
|
+
* - `"config.permission_mode_invalid"`, `"config.spec_retired_key"` and `"config.shell_gate_value_retired"`
|
|
339
|
+
* are ERROR codes, not notices (named here only to keep the mode family's spellings
|
|
339
340
|
* in one place): a `permissionMode` word outside the closed set (on the spec or on a checkpoint row), a spec
|
|
340
|
-
* carrying a retired mode key (`autoModeRequested`, `approverPosture`)
|
|
341
|
-
* `"always"
|
|
341
|
+
* carrying a retired mode key (`autoModeRequested`, `approverPosture`) and a `shellGate` value other than
|
|
342
|
+
* `"always"`. Each is a door refusal: the
|
|
342
343
|
* code rides the thrown error (the run fails, or the resume is refused) and never crosses
|
|
343
|
-
* {@link deliverEngineNotice}, so all
|
|
344
|
+
* {@link deliverEngineNotice}, so all three are deliberately absent from {@link ENGINE_NOTICE_CODES} /
|
|
344
345
|
* {@link NOTICE_AUDIENCE}, like `config.deps_retired_key`. A consumer diffing its own table against the
|
|
345
346
|
* catalog must not add a row for them; it reads them on the error's `code`.
|
|
346
347
|
* - `"permission.ask_released_by_mode"` — the `bypassPermissions` mode released a surviving ask instead of
|
|
@@ -359,13 +360,6 @@ export interface EngineNotice {
|
|
|
359
360
|
* (one call's own rule check — every later call reads again, so the rows apply as soon as a read succeeds). Once
|
|
360
361
|
* per run, whichever read fails first; the trace keeps every read's line (`permission.rule_store_unreadable`).
|
|
361
362
|
* `detail: { at, sessionId, runId }`. Audience `user`.
|
|
362
|
-
* - `"resume.legacy_mode_seed_migrated"` — a checkpoint row written before the one-key mode (7.34.0) carried
|
|
363
|
-
* `autoModeRequested` / `approverPosture` / a doctrine `shellGate`; the resume leg read them once into
|
|
364
|
-
* `permissionMode` (`autoModeRequested: true` ⇒ `auto`; a posture is dropped — it never authorized anything;
|
|
365
|
-
* `shellGate` `"off"` / `"classify"` are dropped, `"always"` kept) and the next suspend writes the new member.
|
|
366
|
-
* `detail: { migrated: { permissionMode?, droppedKeys }, sessionId, runId }`. Audience `operator`. A row the
|
|
367
|
-
* migration cannot read refuses the resume instead (`resume.legacy_mode_seed`, the error-code row above). The
|
|
368
|
-
* migration is kept through 7.38.x and removed in 7.39.0, which refuses a row still carrying these keys.
|
|
369
363
|
* - `"config.durable_gate_unavailable"` — a durable-approval MANDATE is in force on a leg with NO
|
|
370
364
|
* checkpoint store: the per-principal `forceDurableGate` entitlement, or the task's own
|
|
371
365
|
* `durableApproval` opt-in with no live approver seat (`durableMandateSourceOf`, the predicate the
|
|
@@ -399,7 +393,10 @@ export interface EngineNotice {
|
|
|
399
393
|
* serving layer's, so only it can count them. One code for both, so a consumer de-duplicating on `code` keeps
|
|
400
394
|
* one row; `sessionId` = #433's routing half for these two `"user"`-audience rows; `runId` = #499's
|
|
401
395
|
* per-run identity, the key this account is actually about — see
|
|
402
|
-
* {@link undrainedUserInputNotices}; all omitted when the caller has none, never fabricated).
|
|
396
|
+
* {@link undrainedUserInputNotices}; all omitted when the caller has none, never fabricated). A delegated child
|
|
397
|
+
* leg is announced on its PARENT's session as well — the direct parent only — as a second row per family,
|
|
398
|
+
* `detail: { steer | followUp, cause?, taskId?, sessionId: <the parent's>, childSessionId }` (no `runId`: nothing
|
|
399
|
+
* carries the parent's), message tail `(a child leg, <childSessionId>)`; `childSessionId` present = a child reported it.
|
|
403
400
|
* Per-run, at most once per family (the terminal sweep is a single site) — which is why
|
|
404
401
|
* `taskId`/`sessionId` cannot key it: both answer the same value for two runs of one session.
|
|
405
402
|
* ① The family fires on the INTERRUPT path too, `abort()` being barred from emptying either queue
|
|
@@ -950,6 +947,7 @@ export declare function settleOwedUserInput(account: OwedUserInputAccount, rowRe
|
|
|
950
947
|
taskId?: string;
|
|
951
948
|
sessionId?: string;
|
|
952
949
|
runId?: string;
|
|
950
|
+
parent?: ParentLegIds | undefined;
|
|
953
951
|
}, onNotice: ((notice: EngineNotice) => void) | undefined): void;
|
|
954
952
|
/**
|
|
955
953
|
* Why a person's steer is reported as not consumed — `task.user_steer_undrained`'s `detail.cause`. Closed:
|
|
@@ -962,7 +960,33 @@ export declare function settleOwedUserInput(account: OwedUserInputAccount, rowRe
|
|
|
962
960
|
*/
|
|
963
961
|
export declare const USER_STEER_UNDRAINED_CAUSES: readonly ["leg_end", "crashed"];
|
|
964
962
|
export type UserSteerUndrainedCause = (typeof USER_STEER_UNDRAINED_CAUSES)[number];
|
|
963
|
+
/** Where a child leg's account is ALSO announced: its spawner's session, and that run's task id when the spawner carried one
|
|
964
|
+
* (it never carries the run id, and the row never invents one). */
|
|
965
|
+
interface ParentLegIds {
|
|
966
|
+
readonly sessionId: string;
|
|
967
|
+
readonly taskId?: string;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* The parent a leg reports to, read off the trusted run-scoped channel (`RunInternals.parentSessionId` / `parentTaskId`,
|
|
971
|
+
* threaded by the delegation lanes from the spawner's own ids). `undefined` for a leg with no parent SESSION — a row
|
|
972
|
+
* with no session to address cannot be projected anywhere, so a bare `parentTaskId` names no one.
|
|
973
|
+
*/
|
|
974
|
+
export declare function parentLegIds(internals: {
|
|
975
|
+
readonly parentSessionId?: string;
|
|
976
|
+
readonly parentTaskId?: string;
|
|
977
|
+
} | undefined): ParentLegIds | undefined;
|
|
978
|
+
/**
|
|
979
|
+
* The rows for what a leg still owes a person. One session's view is the leg's own rows (`taskId` / `sessionId` /
|
|
980
|
+
* `runId` — each omitted when the caller has none, never fabricated). A leg that has a PARENT is announced to it as well,
|
|
981
|
+
* the way a child's folded permission mode is: after the leg's own rows come one copy per row, `detail.sessionId` the
|
|
982
|
+
* parent's (and `taskId` the parent run's, when carried), `childSessionId` the leg's own session — the key a
|
|
983
|
+
* consumer reads "a child reported this" from — and the message tail `(a child leg, <childSessionId>)`. The copy carries
|
|
984
|
+
* no `runId`: the account is per run and nothing carries the parent's. Only the DIRECT parent hears: a grandchild's
|
|
985
|
+
* row reaches its parent's session (the middle leg), not the root's. A parent that is no addressee — null, no or an empty session, or the leg's own session (which already
|
|
986
|
+
* hears the row) — gets no copy, and the call never throws on it.
|
|
987
|
+
*/
|
|
965
988
|
export declare function undrainedUserInputNotices(counts: {
|
|
966
989
|
steer: number;
|
|
967
990
|
followUp: number;
|
|
968
|
-
}, taskId?: string, sessionId?: string, runId?: string): EngineNotice[];
|
|
991
|
+
}, taskId?: string, sessionId?: string, runId?: string, parent?: ParentLegIds): EngineNotice[];
|
|
992
|
+
export {};
|
|
@@ -44,7 +44,7 @@ export function settleOwedUserInput(account, rowReturned, ids, onNotice) {
|
|
|
44
44
|
SETTLED_OWED_ACCOUNTS.add(account);
|
|
45
45
|
try {
|
|
46
46
|
const owed = owedUserInputCounts(account.held, account.migrated, { ...account.continuation, rowReturned });
|
|
47
|
-
for (const notice of undrainedUserInputNotices(owed, ids.taskId, ids.sessionId, ids.runId))
|
|
47
|
+
for (const notice of undrainedUserInputNotices(owed, ids.taskId, ids.sessionId, ids.runId, ids.parent))
|
|
48
48
|
deliverEngineNotice(onNotice, notice);
|
|
49
49
|
}
|
|
50
50
|
catch {
|
|
@@ -52,25 +52,37 @@ export function settleOwedUserInput(account, rowReturned, ids, onNotice) {
|
|
|
52
52
|
}
|
|
53
53
|
const SETTLED_OWED_ACCOUNTS = new WeakSet();
|
|
54
54
|
export const USER_STEER_UNDRAINED_CAUSES = Object.freeze(["leg_end", "crashed"]);
|
|
55
|
-
export function
|
|
55
|
+
export function parentLegIds(internals) {
|
|
56
|
+
const parentSessionId = internals?.parentSessionId;
|
|
57
|
+
if (typeof parentSessionId !== "string" || parentSessionId === "")
|
|
58
|
+
return undefined;
|
|
59
|
+
const parentTaskId = internals?.parentTaskId;
|
|
60
|
+
return { sessionId: parentSessionId, ...(typeof parentTaskId === "string" && parentTaskId !== "" ? { taskId: parentTaskId } : {}) };
|
|
61
|
+
}
|
|
62
|
+
export function undrainedUserInputNotices(counts, taskId, sessionId, runId, parent) {
|
|
56
63
|
const tid = taskId !== undefined ? { taskId } : {};
|
|
57
64
|
const sid = sessionId !== undefined ? { sessionId } : {};
|
|
58
65
|
const rid = runId !== undefined ? { runId } : {};
|
|
59
66
|
const tail = `accepted as "queued" were never consumed — the run ended first. They are NOT redelivered; re-send against a live run if still wanted.`;
|
|
60
|
-
const
|
|
67
|
+
const parentSession = typeof parent?.sessionId === "string" && parent.sessionId !== "" ? parent.sessionId : undefined;
|
|
68
|
+
const toParent = parentSession !== undefined && typeof sessionId === "string" && sessionId !== "" && parentSession !== sessionId;
|
|
69
|
+
const ptid = typeof parent?.taskId === "string" && parent.taskId !== "" ? { taskId: parent.taskId } : {};
|
|
70
|
+
const copyTail = ` (a child leg, ${sessionId})`;
|
|
71
|
+
const own = [];
|
|
72
|
+
const copies = [];
|
|
61
73
|
if (counts.steer > 0) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
const message = `${counts.steer} user steer(s) ${tail}`;
|
|
75
|
+
const count = { steer: counts.steer, cause: "leg_end" };
|
|
76
|
+
own.push({ code: "task.user_steer_undrained", message, detail: { ...count, ...tid, ...sid, ...rid } });
|
|
77
|
+
if (toParent)
|
|
78
|
+
copies.push({ code: "task.user_steer_undrained", message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
|
|
67
79
|
}
|
|
68
80
|
if (counts.followUp > 0) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
const message = `${counts.followUp} user follow-up(s) ${tail}`;
|
|
82
|
+
const count = { followUp: counts.followUp };
|
|
83
|
+
own.push({ code: "task.user_followup_undrained", message, detail: { ...count, ...tid, ...sid, ...rid } });
|
|
84
|
+
if (toParent)
|
|
85
|
+
copies.push({ code: "task.user_followup_undrained", message: `${message}${copyTail}`, detail: { ...count, ...ptid, sessionId: parentSession, childSessionId: sessionId } });
|
|
74
86
|
}
|
|
75
|
-
return
|
|
87
|
+
return [...own, ...copies];
|
|
76
88
|
}
|
|
@@ -14,6 +14,16 @@ import { type AutoModeClassifierRound } from "./auto-mode.js";
|
|
|
14
14
|
* record says which party ended it and is fixed BY the word (see {@link Settlement}).
|
|
15
15
|
* - `human_allowed` / `human_refused` — a person (or the approver acting for one) decided; the refusal's
|
|
16
16
|
* own note, when the decider attached one, rides `note`.
|
|
17
|
+
* - `policy_allowed` / `policy_refused` — the DEPLOYMENT'S OWN POLICY decided: the approval channel reported
|
|
18
|
+
* that a machine, not a person, answered this ask (an unattended lane answering by class, a headless lane
|
|
19
|
+
* with no interactive approval surface, a standing deployment posture). A person and a policy are the two
|
|
20
|
+
* DECIDERS: each answers either way, with the same capabilities, and differs only in whose decision the
|
|
21
|
+
* record names. A policy's refusal is distinct from `human_refused` because the model is told a different
|
|
22
|
+
* thing: a person's "no" is the user's direction, a policy's is a capability of this run. Distinct from
|
|
23
|
+
* `no_approver` because an approval channel WAS wired and answered — core did not fall back to its own
|
|
24
|
+
* headless posture. The refusal's own note, when the channel attached one, reaches the model as the
|
|
25
|
+
* policy's words and NOT as a person's; it is not on this record (the note seat belongs to the person's
|
|
26
|
+
* two words, whose ABSENCE is a control-flow fact the policy's do not have).
|
|
17
27
|
* - `approval_window_expired` — an approval window elapsed with no answer: the engine's own approval
|
|
18
28
|
* factory window (`createApprovalPolicy.approvalTimeoutMs`), or a synchronous host approver that
|
|
19
29
|
* reported its own window elapsed.
|
|
@@ -28,26 +38,19 @@ import { type AutoModeClassifierRound } from "./auto-mode.js";
|
|
|
28
38
|
* - `approver_contract` — the approver answered outside its contract (a non-boolean verdict, an
|
|
29
39
|
* unreadable member, a timeout-settled allow, a refused attribution…).
|
|
30
40
|
* - `presentation_failed` — the args or the edit could not be safely presented/adopted (unclonable).
|
|
31
|
-
* - `blanket_allow_refused` —
|
|
32
|
-
*
|
|
33
|
-
* not a person, ended this wait (a headless lane with no interactive approval surface, a standing
|
|
34
|
-
* deployment posture). Distinct from `human_refused` because the party differs and the model is told a
|
|
35
|
-
* different thing: a person's "no" is the user's direction, a policy's is a capability of this run.
|
|
36
|
-
* Distinct from `no_approver` because an approval channel WAS wired and answered — core did not fall
|
|
37
|
-
* back to its own headless posture. The refusal's own note, when the channel attached one, reaches the
|
|
38
|
-
* model as the policy's words and NOT as a person's; it is not on this record (the note seat belongs to
|
|
39
|
-
* the two human words, whose ABSENCE is a control-flow fact this family does not have).
|
|
41
|
+
* - `blanket_allow_refused` — an allow NO PERSON JUDGED met a `requiresRealApproval` ask: a blanket
|
|
42
|
+
* `onAsk:"allow"`, or an approver's allow it reported as the deployment's policy (`settledBy: "policy"`).
|
|
40
43
|
* - `task_aborted` — the wait's abort signal ended it (the run's own end, or a turn-level interrupt).
|
|
41
44
|
*/
|
|
42
|
-
export declare const SETTLEMENT_KINDS: readonly ["human_allowed", "human_refused", "approval_window_expired", "denial_limit_window_expired", "park_sla_expired", "no_approver", "approver_unavailable", "approver_error", "approver_contract", "presentation_failed", "blanket_allow_refused", "policy_refused", "task_aborted"];
|
|
45
|
+
export declare const SETTLEMENT_KINDS: readonly ["human_allowed", "human_refused", "approval_window_expired", "denial_limit_window_expired", "park_sla_expired", "no_approver", "approver_unavailable", "approver_error", "approver_contract", "presentation_failed", "blanket_allow_refused", "policy_allowed", "policy_refused", "task_aborted"];
|
|
43
46
|
export type SettlementKind = (typeof SETTLEMENT_KINDS)[number];
|
|
44
47
|
/** Membership test for {@link SettlementKind} — the word crosses process boundaries on `tool_end`, so a
|
|
45
48
|
* consumer validating one it received must not hand-roll the set. */
|
|
46
49
|
export declare function isSettlementKind(v: unknown): v is SettlementKind;
|
|
47
50
|
/**
|
|
48
51
|
* The settlement record: WHAT ended the wait (`kind`), WHO ended it (`who` — its shape is decided by the
|
|
49
|
-
* word: the two
|
|
50
|
-
* the fail-closed family nobody), WHEN (epoch ms, written at the settlement site), and for a person's
|
|
52
|
+
* word: the person's two words carry a person, the policy's two words the policy, the window words say whose
|
|
53
|
+
* window, the park word the host, the fail-closed family nobody), WHEN (epoch ms, written at the settlement site), and for a person's
|
|
51
54
|
* refusal the decider's own NOTE when one was attached (a refusal WITHOUT a note is a bare "no", the arm
|
|
52
55
|
* the runner treats as a control-flow boundary for the issuing batch).
|
|
53
56
|
*
|
|
@@ -57,7 +60,7 @@ export declare function isSettlementKind(v: unknown): v is SettlementKind;
|
|
|
57
60
|
*
|
|
58
61
|
* @contract settlement.single_mint — every `Settlement` kind is composed by CORE at the site whose wait
|
|
59
62
|
* ended (the ask resolver's arms, the approval factory's arms, the decide lane over a host's decision
|
|
60
|
-
* FACTS). A host never mints a kind: a synchronous approver reports `"human"`/`"timeout"`/`"policy"` on its
|
|
63
|
+
* FACTS), and the four DECIDER words by one composer ({@link settledByDecider}). A host never mints a kind: a synchronous approver reports `"human"`/`"timeout"`/`"policy"` on its
|
|
61
64
|
* outcome and a durable decide reports `hostDecision.decidedBy` (`"person"`/`"sla_timeout"`/`"policy"`) — facts
|
|
62
65
|
* core turns into the word. Nothing on the caller-composable policy face (`PermissionResult`) carries a settlement,
|
|
63
66
|
* so a policy cannot self-declare one; the engine's own settlement sites that hand a settled decision
|
|
@@ -71,6 +74,13 @@ export type Settlement = {
|
|
|
71
74
|
};
|
|
72
75
|
when: number;
|
|
73
76
|
note?: string;
|
|
77
|
+
} | {
|
|
78
|
+
kind: "policy_allowed" | "policy_refused";
|
|
79
|
+
who: {
|
|
80
|
+
party: "policy";
|
|
81
|
+
approver?: string;
|
|
82
|
+
};
|
|
83
|
+
when: number;
|
|
74
84
|
} | {
|
|
75
85
|
kind: "approval_window_expired";
|
|
76
86
|
who: {
|
|
@@ -95,16 +105,33 @@ export type Settlement = {
|
|
|
95
105
|
};
|
|
96
106
|
when: number;
|
|
97
107
|
} | {
|
|
98
|
-
kind: "no_approver" | "approver_unavailable" | "approver_error" | "approver_contract" | "presentation_failed" | "blanket_allow_refused" | "
|
|
108
|
+
kind: "no_approver" | "approver_unavailable" | "approver_error" | "approver_contract" | "presentation_failed" | "blanket_allow_refused" | "task_aborted";
|
|
99
109
|
who: {
|
|
100
110
|
party: "none";
|
|
101
111
|
};
|
|
102
112
|
when: number;
|
|
103
113
|
};
|
|
104
|
-
/**
|
|
105
|
-
*
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
/**
|
|
115
|
+
* WHO ended a wait — the closed set of `Settlement.who.party` words, one per party a record can name:
|
|
116
|
+
* `person` (a person decided), `policy` (the deployment's own policy decided), `engine` (one of the engine's
|
|
117
|
+
* own windows elapsed), `host` (the host's window or its park sweep), `none` (nobody: the engine ended the
|
|
118
|
+
* wait itself — no approver, a throw, an abort, an out-of-contract answer). Which party a word names is
|
|
119
|
+
* fixed by the word ({@link Settlement}); the word crosses process boundaries on `tool_end`, so a consumer
|
|
120
|
+
* validating a record it received reads this set rather than hand-rolling one.
|
|
121
|
+
*/
|
|
122
|
+
export declare const SETTLEMENT_PARTIES: readonly ["person", "policy", "engine", "host", "none"];
|
|
123
|
+
export type SettlementParty = (typeof SETTLEMENT_PARTIES)[number];
|
|
124
|
+
/** Compile-time fence: `never` while the closed set and the record's `who.party` union are the same words. */
|
|
125
|
+
export type SettlementPartiesMatchTheRecord = AssertAllKeysHandled<Exclude<Settlement["who"]["party"], SettlementParty> | Exclude<SettlementParty, Settlement["who"]["party"]>>;
|
|
126
|
+
/** Membership test for {@link SettlementParty}. */
|
|
127
|
+
export declare function isSettlementParty(v: unknown): v is SettlementParty;
|
|
128
|
+
/** The DECIDER arms of {@link Settlement} — a person's and the deployment policy's, the two parties that
|
|
129
|
+
* answer an ask either way. Named so a site that both composes one and quotes its note can read the seat
|
|
130
|
+
* off the record instead of re-deriving it. */
|
|
131
|
+
export type DeciderSettlement = Extract<Settlement, {
|
|
132
|
+
who: {
|
|
133
|
+
party: "person" | "policy";
|
|
134
|
+
};
|
|
108
135
|
}>;
|
|
109
136
|
/**
|
|
110
137
|
* A decider's NOTE, normalised — the one rule for "did the decider say anything": empty text is a
|
|
@@ -115,17 +142,21 @@ export type PersonSettlement = Extract<Settlement, {
|
|
|
115
142
|
*/
|
|
116
143
|
export declare function decidersNote(text: string | undefined): string | undefined;
|
|
117
144
|
/**
|
|
118
|
-
* THE mint for a
|
|
119
|
-
*
|
|
120
|
-
*
|
|
145
|
+
* THE mint for a decider's settlement — the party (a person, or the deployment's own policy) and its verdict
|
|
146
|
+
* pick the word, and the channel's attribution is transcribed onto `who`. The synchronous resolver's arms,
|
|
147
|
+
* the approval factory's arms and the durable decide lane all compose their decider record here, so one
|
|
148
|
+
* answer cannot end as two different records depending on which path carried it, and the two parties are
|
|
149
|
+
* one rule with a parameter rather than two composers.
|
|
121
150
|
*
|
|
122
|
-
* @contract settlement.note — the `note` seat is filled exactly when the
|
|
123
|
-
* its absence is how the record says the refusal was bare (the runner reads that absence, and
|
|
124
|
-
* else, as the issuing batch's control-flow boundary). Empty text folds to absence HERE, once, for
|
|
125
|
-
* every path that fills the seat ({@link decidersNote}); a site that hand-builds the record instead is
|
|
126
|
-
*
|
|
151
|
+
* @contract settlement.note — the `note` seat is the PERSON's: filled exactly when the person attached a
|
|
152
|
+
* reason, and its absence is how the record says the refusal was bare (the runner reads that absence, and
|
|
153
|
+
* nothing else, as the issuing batch's control-flow boundary). Empty text folds to absence HERE, once, for
|
|
154
|
+
* every path that fills the seat ({@link decidersNote}); a site that hand-builds the record instead is a
|
|
155
|
+
* second answer to the same question, which is how one path came to halt a batch the other let run. A
|
|
156
|
+
* policy's record has no note seat — its words reach the model inside the policy sentence, and a policy's
|
|
157
|
+
* refusal is never a control-flow boundary — so a note handed with the policy party is not filed here.
|
|
127
158
|
*/
|
|
128
|
-
export declare function
|
|
159
|
+
export declare function settledByDecider(party: DeciderSettlement["who"]["party"], verdict: "allowed" | "refused", approver: string | undefined, note?: string): DeciderSettlement;
|
|
129
160
|
/** Which settlement kinds are REFUSALS (the ask ended as a deny). Total over the closed set — the fence
|
|
130
161
|
* below refuses a member without a row — so invariant I2 is a table lookup, not a list in a condition. */
|
|
131
162
|
export declare const SETTLEMENT_IS_REFUSAL: {
|
|
@@ -140,6 +171,7 @@ export declare const SETTLEMENT_IS_REFUSAL: {
|
|
|
140
171
|
readonly approver_contract: true;
|
|
141
172
|
readonly presentation_failed: true;
|
|
142
173
|
readonly blanket_allow_refused: true;
|
|
174
|
+
readonly policy_allowed: false;
|
|
143
175
|
readonly policy_refused: true;
|
|
144
176
|
readonly task_aborted: true;
|
|
145
177
|
};
|
|
@@ -159,8 +191,9 @@ export type SettlementRefusalTableCoversEveryKind = AssertAllKeysHandled<Exclude
|
|
|
159
191
|
* the same lane reached when it refused by returning a bare `false`).
|
|
160
192
|
*
|
|
161
193
|
* The rows that say `false` each say something:
|
|
162
|
-
* - the two
|
|
163
|
-
* out. The run is not out of judges.
|
|
194
|
+
* - the person's two words, `policy_allowed` and the window words — someone WAS asked; a decider answered,
|
|
195
|
+
* or a window ran out. The run is not out of judges. (`policy_refused` is `true`: the deployment's own
|
|
196
|
+
* policy declined a question it exists to answer, so nothing in this run will say yes to it.)
|
|
164
197
|
* - `approver_unavailable` — a ROUTING answer, not a posture: the main gate owns that arm (it may park
|
|
165
198
|
* the ask on a durable queue instead), and the two sites that treat it as headless anyway say so at
|
|
166
199
|
* their own call, because they are the ones with no park route left.
|
|
@@ -180,6 +213,7 @@ export declare const SETTLEMENT_NOBODY_JUDGED: {
|
|
|
180
213
|
readonly approver_contract: false;
|
|
181
214
|
readonly presentation_failed: false;
|
|
182
215
|
readonly blanket_allow_refused: true;
|
|
216
|
+
readonly policy_allowed: false;
|
|
183
217
|
readonly policy_refused: true;
|
|
184
218
|
readonly task_aborted: false;
|
|
185
219
|
};
|
|
@@ -194,7 +228,8 @@ export type NobodyJudgedTableCoversEveryKind = AssertAllKeysHandled<Exclude<Sett
|
|
|
194
228
|
* approval-edit chain hit its round cap.
|
|
195
229
|
* - `hook` — a PreToolUse hook denied, threw, or never answered (the engine's fail-closed block on a
|
|
196
230
|
* hook that made no decision counts as the hook layer's refusal for attribution).
|
|
197
|
-
* - `org` — an organization policy rule denied
|
|
231
|
+
* - `org` — an organization policy rule denied; or, on a durable resume, the org's current verdict demands a person and
|
|
232
|
+
* the recorded decider is the deployment's policy, not a person.
|
|
198
233
|
* - `classifier` — the auto-mode classifier denied directly (its ASK-side role is `origin:
|
|
199
234
|
* "denial_limit_fallback"`).
|
|
200
235
|
* - `persisted_rule` — the person's persisted-rule lane refused: a deny row (live), or, on a durable resume, a deny
|
|
@@ -207,22 +242,23 @@ export type NobodyJudgedTableCoversEveryKind = AssertAllKeysHandled<Exclude<Sett
|
|
|
207
242
|
* - `read_boundary` — the shell read station refused: a deny-listed read (refused on every seat, with no
|
|
208
243
|
* ask), a read the read-only shell face may not make, or a rewritten / re-established command whose reads
|
|
209
244
|
* no longer are what a person approved.
|
|
210
|
-
* - `mode` — the permission MODE refused a surviving ask at the gate's arbitration point: `dontAsk` turns
|
|
211
|
-
*
|
|
245
|
+
* - `mode` — the permission MODE refused a surviving ask at the gate's arbitration point: `dontAsk` turns every ask
|
|
246
|
+
* but a hook's own into a refusal, and `bypassPermissions` refuses a write whose protection
|
|
212
247
|
* could not be judged. The disposition carries `mode`, `askClass` and `classes` beside the word.
|
|
213
248
|
*/
|
|
214
249
|
export declare const DENIED_BY_VALUES: readonly ["policy", "hook", "org", "persisted_rule", "classifier", "compliance", "write_protection", "ask_resolution", "read_boundary", "mode"];
|
|
215
250
|
export type DeniedBy = (typeof DENIED_BY_VALUES)[number];
|
|
216
251
|
/** Membership test for {@link DeniedBy}. */
|
|
217
252
|
export declare function isDeniedBy(v: unknown): v is DeniedBy;
|
|
218
|
-
/** Which layers may VETO an approval a
|
|
219
|
-
* beside a `denied` disposition names one of these). The
|
|
220
|
-
* and persisted-rule re-checks, and the write-protection
|
|
253
|
+
/** Which layers may VETO an approval a decider already gave (invariant I3: an approval settlement — a
|
|
254
|
+
* non-refusal kind, a person's or a policy's — beside a `denied` disposition names one of these). The
|
|
255
|
+
* approval-edit re-screen (hook), the policy, org and persisted-rule re-checks, and the write-protection
|
|
256
|
+
* re-judge run AFTER the decider's yes; the
|
|
221
257
|
* classifier and compliance run before any ask exists, and `ask_resolution` IS the
|
|
222
258
|
* settlement, not a veto of it. `persisted_rule` = a person's own standing DENY row (their settings'
|
|
223
259
|
* deny list, imported) — the personal-store sibling of `org`, and like it re-judged on an edited
|
|
224
260
|
* command. `read_boundary` vetoes too: the rewrite re-judge and the pre-spawn re-establishment run after
|
|
225
|
-
* the
|
|
261
|
+
* the decider's yes, on bytes or targets the decider never saw. */
|
|
226
262
|
export declare const DENIED_BY_MAY_VETO: {
|
|
227
263
|
readonly policy: true;
|
|
228
264
|
readonly hook: true;
|
|
@@ -314,7 +350,7 @@ export interface ModeRelease {
|
|
|
314
350
|
mandate?: PersistedRuleMandate;
|
|
315
351
|
reason?: ModeReleaseReason;
|
|
316
352
|
}
|
|
317
|
-
/** The two modes that refuse on their own account (`deniedBy: "mode"`): `dontAsk` every
|
|
353
|
+
/** The two modes that refuse on their own account (`deniedBy: "mode"`): `dontAsk` every ask but a hook's own,
|
|
318
354
|
* `bypassPermissions` a write whose protection could not be judged. `plan` refuses nothing here — its questions ask. */
|
|
319
355
|
export interface ModeRefusal {
|
|
320
356
|
mode: "bypassPermissions" | "dontAsk";
|
|
@@ -331,10 +367,11 @@ export interface ModeRefusal {
|
|
|
331
367
|
* The three members are orthogonal but bound by four invariants ({@link screenGateOutcome}):
|
|
332
368
|
* I1 `settlement` present ⇔ `origin` present (they describe the same ask);
|
|
333
369
|
* I2 `disposition.deniedBy === "ask_resolution"` ⇒ `settlement` present and a refusal kind;
|
|
334
|
-
* I3 `settlement
|
|
335
|
-
* layer ({@link DENIED_BY_MAY_VETO}) — a
|
|
336
|
-
* on the settlement (it is not the approver's
|
|
337
|
-
*
|
|
370
|
+
* I3 `settlement` an approval (a kind {@link SETTLEMENT_IS_REFUSAL} says is not a refusal) ∧
|
|
371
|
+
* `disposition.kind === "denied"` ⇒ `deniedBy` is a veto layer ({@link DENIED_BY_MAY_VETO}) — a decider
|
|
372
|
+
* approved, a later re-check refused; the approver stays on the settlement (it is not the approver's
|
|
373
|
+
* refusal);
|
|
374
|
+
* I4 `disposition.kind === "allowed"` ⇒ `settlement` absent or an approval (not a refusal kind);
|
|
338
375
|
* I5 `disposition.cause` present ⇒ a member of {@link CLASSIFIER_DENY_CAUSES} beside a layer word
|
|
339
376
|
* {@link DENIED_BY_MAY_CARRY_CAUSE} admits (the classifier station, or the inherited chain's `policy`);
|
|
340
377
|
* I6 `disposition.classifier` present ⇒ both model ids are non-empty strings, and a `fallback` on it
|
|
@@ -356,7 +393,8 @@ export interface GateOutcome {
|
|
|
356
393
|
origin?: AskOrigin;
|
|
357
394
|
}
|
|
358
395
|
/**
|
|
359
|
-
* The one invariant screen for a {@link GateOutcome} (I1–
|
|
396
|
+
* The one invariant screen for a {@link GateOutcome} (I1–I8 above, plus the closed-set words each member carries —
|
|
397
|
+
* `settlement.who.party` among them, {@link SETTLEMENT_PARTIES}). Runs
|
|
360
398
|
* where an outcome enters a wire face — the engine's settlement sideband before the `tool_end` frame, and
|
|
361
399
|
* the decide lane before a durable row is resolved. Returns the defects as sentences (an empty list is a
|
|
362
400
|
* clean record); the caller decides the fail-loud form (report + withhold, or refuse), never silently
|
|
@@ -12,6 +12,7 @@ export const SETTLEMENT_KINDS = Object.freeze([
|
|
|
12
12
|
"approver_contract",
|
|
13
13
|
"presentation_failed",
|
|
14
14
|
"blanket_allow_refused",
|
|
15
|
+
"policy_allowed",
|
|
15
16
|
"policy_refused",
|
|
16
17
|
"task_aborted",
|
|
17
18
|
]);
|
|
@@ -19,12 +20,20 @@ const SETTLEMENT_KIND_SET = new Set(SETTLEMENT_KINDS);
|
|
|
19
20
|
export function isSettlementKind(v) {
|
|
20
21
|
return SETTLEMENT_KIND_SET.has(v);
|
|
21
22
|
}
|
|
23
|
+
export const SETTLEMENT_PARTIES = Object.freeze(["person", "policy", "engine", "host", "none"]);
|
|
24
|
+
const SETTLEMENT_PARTY_SET = new Set(SETTLEMENT_PARTIES);
|
|
25
|
+
export function isSettlementParty(v) {
|
|
26
|
+
return SETTLEMENT_PARTY_SET.has(v);
|
|
27
|
+
}
|
|
22
28
|
export function decidersNote(text) {
|
|
23
29
|
return text === "" ? undefined : text;
|
|
24
30
|
}
|
|
25
|
-
export function
|
|
31
|
+
export function settledByDecider(party, verdict, approver, note) {
|
|
32
|
+
const attributed = approver !== undefined ? { approver } : {};
|
|
33
|
+
if (party === "policy")
|
|
34
|
+
return { kind: verdict === "allowed" ? "policy_allowed" : "policy_refused", who: { party: "policy", ...attributed }, when: Date.now() };
|
|
26
35
|
const given = decidersNote(note);
|
|
27
|
-
return { kind, who: { party: "person", ...
|
|
36
|
+
return { kind: verdict === "allowed" ? "human_allowed" : "human_refused", who: { party: "person", ...attributed }, when: Date.now(), ...(given !== undefined ? { note: given } : {}) };
|
|
28
37
|
}
|
|
29
38
|
export const SETTLEMENT_IS_REFUSAL = {
|
|
30
39
|
human_allowed: false,
|
|
@@ -38,6 +47,7 @@ export const SETTLEMENT_IS_REFUSAL = {
|
|
|
38
47
|
approver_contract: true,
|
|
39
48
|
presentation_failed: true,
|
|
40
49
|
blanket_allow_refused: true,
|
|
50
|
+
policy_allowed: false,
|
|
41
51
|
policy_refused: true,
|
|
42
52
|
task_aborted: true,
|
|
43
53
|
};
|
|
@@ -53,6 +63,7 @@ export const SETTLEMENT_NOBODY_JUDGED = {
|
|
|
53
63
|
approver_contract: false,
|
|
54
64
|
presentation_failed: false,
|
|
55
65
|
blanket_allow_refused: true,
|
|
66
|
+
policy_allowed: false,
|
|
56
67
|
policy_refused: true,
|
|
57
68
|
task_aborted: false,
|
|
58
69
|
};
|
|
@@ -98,6 +109,9 @@ export function screenGateOutcome(g) {
|
|
|
98
109
|
const s = g.settlement;
|
|
99
110
|
if (s !== undefined && !isSettlementKind(s.kind))
|
|
100
111
|
defects.push(`settlement.kind "${String(s.kind)}" is outside the closed set`);
|
|
112
|
+
const party = s === undefined ? undefined : s.who?.party;
|
|
113
|
+
if (s !== undefined && !isSettlementParty(party))
|
|
114
|
+
defects.push(`settlement.who.party "${String(party)}" is outside the closed set`);
|
|
101
115
|
if (g.origin !== undefined && !isAskOrigin(g.origin))
|
|
102
116
|
defects.push(`origin "${String(g.origin)}" is outside the closed set`);
|
|
103
117
|
if ((s !== undefined) !== (g.origin !== undefined))
|
|
@@ -105,11 +119,12 @@ export function screenGateOutcome(g) {
|
|
|
105
119
|
if (d.kind === "denied" && d.deniedBy === "ask_resolution" && (s === undefined || !isSettlementKind(s.kind) || !SETTLEMENT_IS_REFUSAL[s.kind])) {
|
|
106
120
|
defects.push("I2: deniedBy ask_resolution requires a settlement whose kind is a refusal");
|
|
107
121
|
}
|
|
108
|
-
|
|
109
|
-
|
|
122
|
+
const approval = s !== undefined && isSettlementKind(s.kind) && !SETTLEMENT_IS_REFUSAL[s.kind];
|
|
123
|
+
if (approval && d.kind === "denied" && isDeniedBy(d.deniedBy) && !DENIED_BY_MAY_VETO[d.deniedBy]) {
|
|
124
|
+
defects.push(`I3: a ${s.kind} settlement beside a deny must name a veto layer, not "${d.deniedBy}"`);
|
|
110
125
|
}
|
|
111
|
-
if (d.kind === "allowed" && s !== undefined &&
|
|
112
|
-
defects.push(`I4: an allowed disposition cannot carry a "${s.kind}" settlement`);
|
|
126
|
+
if (d.kind === "allowed" && s !== undefined && !approval)
|
|
127
|
+
defects.push(`I4: an allowed disposition cannot carry a "${String(s.kind)}" settlement`);
|
|
113
128
|
if (d.kind === "denied" && d.cause !== undefined) {
|
|
114
129
|
if (!isClassifierDenyCause(d.cause))
|
|
115
130
|
defects.push(`disposition.cause "${String(d.cause)}" is outside the closed set`);
|
|
@@ -136,7 +136,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
136
136
|
* the union's catalog ({@link EngineNotice.code}). The groups below say only which family a row joins,
|
|
137
137
|
* so the catalog is not written a second time here and the two cannot drift.
|
|
138
138
|
*/
|
|
139
|
-
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", "
|
|
139
|
+
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", "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", "task.tool_turn_incomplete", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed", "instructions.source_changed", "session.records_skipped", "memory.layer_locked_legacy", "server.model_unreachable"];
|
|
140
140
|
/** A catalogued code (see {@link ENGINE_NOTICE_CODES}); {@link NOTICE_MINTER} says who mints it. NOT the type of
|
|
141
141
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
142
142
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -186,7 +186,6 @@ declare const NOTICE_AUDIENCE_TABLE: {
|
|
|
186
186
|
readonly "memory.consolidation_withheld": "operator";
|
|
187
187
|
readonly "delegation.ask_unresolvable": "user";
|
|
188
188
|
readonly "config.autocompact_window_clamped": "operator";
|
|
189
|
-
readonly "resume.legacy_mode_seed_migrated": "operator";
|
|
190
189
|
readonly "config.env_timeout_discarded": "operator";
|
|
191
190
|
readonly "config.materialize_env_discarded": "operator";
|
|
192
191
|
readonly "config.models_swapped": "operator";
|
|
@@ -294,7 +293,6 @@ declare const NOTICE_MINTER_TABLE: {
|
|
|
294
293
|
readonly "permission.ask_released_by_mode": "engine";
|
|
295
294
|
readonly "permission.read_deny_unapplied": "engine";
|
|
296
295
|
readonly "permission.rule_store_unavailable": "engine";
|
|
297
|
-
readonly "resume.legacy_mode_seed_migrated": "engine";
|
|
298
296
|
readonly "peer.inbound_disposition": "engine";
|
|
299
297
|
readonly "peer.held_settled": "engine";
|
|
300
298
|
readonly "peer.idle_subscription": "engine";
|
|
@@ -143,7 +143,6 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
|
|
|
143
143
|
"permission.ask_released_by_mode",
|
|
144
144
|
"permission.read_deny_unapplied",
|
|
145
145
|
"permission.rule_store_unavailable",
|
|
146
|
-
"resume.legacy_mode_seed_migrated",
|
|
147
146
|
"peer.inbound_disposition",
|
|
148
147
|
"peer.held_settled",
|
|
149
148
|
"peer.idle_subscription",
|
|
@@ -233,7 +232,6 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
233
232
|
"memory.consolidation_withheld": "operator",
|
|
234
233
|
"delegation.ask_unresolvable": "user",
|
|
235
234
|
"config.autocompact_window_clamped": "operator",
|
|
236
|
-
"resume.legacy_mode_seed_migrated": "operator",
|
|
237
235
|
"config.env_timeout_discarded": "operator",
|
|
238
236
|
"config.materialize_env_discarded": "operator",
|
|
239
237
|
"config.models_swapped": "operator",
|
|
@@ -323,7 +321,6 @@ const NOTICE_MINTER_TABLE = {
|
|
|
323
321
|
"permission.ask_released_by_mode": "engine",
|
|
324
322
|
"permission.read_deny_unapplied": "engine",
|
|
325
323
|
"permission.rule_store_unavailable": "engine",
|
|
326
|
-
"resume.legacy_mode_seed_migrated": "engine",
|
|
327
324
|
"peer.inbound_disposition": "engine",
|
|
328
325
|
"peer.held_settled": "engine",
|
|
329
326
|
"peer.idle_subscription": "engine",
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -1454,7 +1454,7 @@ export interface ToolGateInput {
|
|
|
1454
1454
|
* The leg's EFFECTIVE permission mode, read at every arbitration (a steer changes the next call's answer). The
|
|
1455
1455
|
* gate's one mode read point sits after the classifier and the sandbox leg and before the park / resolution:
|
|
1456
1456
|
* `bypassPermissions` releases every non-immune class (`GateDisposition.released`), `dontAsk` refuses every ask
|
|
1457
|
-
* but a hook's
|
|
1457
|
+
* but a hook's own (`deniedBy: "mode"`), the other modes hand the ask on. ABSENT ⇒ no
|
|
1458
1458
|
* arbitration (a host driving the gate directly keeps the pre-mode chain); the classifier station then reads
|
|
1459
1459
|
* `autoMode` presence alone as the arming fact.
|
|
1460
1460
|
*/
|
package/dist/core/hooks.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decisionText, describeThrown,
|
|
1
|
+
import { decisionText, describeThrown, receiveHookResult, isEngineRefusal } from "./tool-policy.js";
|
|
2
2
|
import { asThrownError } from "./thrown-value.js";
|
|
3
3
|
import { createSettlementLedger, engineFailClosedExit, hookDenyExit } from "./runner/gate-exit.js";
|
|
4
4
|
import { traceHookCrash } from "./gate-crash-trace.js";
|
|
@@ -190,7 +190,7 @@ function preToolUseUnansweredReason(subject, timeoutMs, cause) {
|
|
|
190
190
|
function screenPreToolUseResult(r) {
|
|
191
191
|
if (r === undefined)
|
|
192
192
|
return undefined;
|
|
193
|
-
return
|
|
193
|
+
return receiveHookResult(r);
|
|
194
194
|
}
|
|
195
195
|
export function createPreToolUseConstraintPolicy(preToolUse, env, onCrash, timeoutMs) {
|
|
196
196
|
return brandPolicyAskClass({
|
|
@@ -65,3 +65,15 @@ export declare function projectHumanInput(frame: HumanInputFrame): string;
|
|
|
65
65
|
export declare function buildHumanInputEvent(input: Omit<HumanInputEvent, "type" | "inputId" | keyof TaskEventIdentity> & {
|
|
66
66
|
inputId?: string;
|
|
67
67
|
}): HumanInputEvent;
|
|
68
|
+
/** The one constructor of the `"applied"` frame for an input that entered a model turn on a core carrier: the resume replay
|
|
69
|
+
* (a parked entry drained into the continuation prompt) and the live lane (a caller steer that became a transcript message)
|
|
70
|
+
* both mint here, so the two paths say the same word with the same attribution — `issuer` mirrored from `actor.issuer`,
|
|
71
|
+
* `principal` when the leg has one, no `entryId` (the frame precedes the commit). */
|
|
72
|
+
export declare function buildAppliedHumanInputEvent(input: {
|
|
73
|
+
carrier: string;
|
|
74
|
+
source: HumanInputSource;
|
|
75
|
+
sessionSeq: number;
|
|
76
|
+
inputId: string;
|
|
77
|
+
actor?: ActorAssertion | undefined;
|
|
78
|
+
principal?: string | undefined;
|
|
79
|
+
}): HumanInputEvent;
|
|
@@ -47,3 +47,15 @@ export function buildHumanInputEvent(input) {
|
|
|
47
47
|
...(input.entryId !== undefined ? { entryId: input.entryId } : {}),
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
+
export function buildAppliedHumanInputEvent(input) {
|
|
51
|
+
return buildHumanInputEvent({
|
|
52
|
+
carrier: input.carrier,
|
|
53
|
+
source: input.source,
|
|
54
|
+
delivery: "applied",
|
|
55
|
+
sessionSeq: input.sessionSeq,
|
|
56
|
+
inputId: input.inputId,
|
|
57
|
+
...(input.actor !== undefined ? { actor: input.actor } : {}),
|
|
58
|
+
...(input.actor?.issuer !== undefined ? { issuer: input.actor.issuer } : {}),
|
|
59
|
+
...(input.principal !== undefined ? { principal: input.principal } : {}),
|
|
60
|
+
});
|
|
61
|
+
}
|