@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.
Files changed (96) hide show
  1. package/CHANGELOG.md +191 -6
  2. package/dist/core/ask-origin.d.ts +16 -7
  3. package/dist/core/ask-origin.js +4 -2
  4. package/dist/core/auto-mode-directive.d.ts +114 -19
  5. package/dist/core/auto-mode-directive.js +67 -19
  6. package/dist/core/checkpoint-store.d.ts +72 -41
  7. package/dist/core/checkpoint-store.js +28 -48
  8. package/dist/core/classifier-unavailable-arm.js +2 -2
  9. package/dist/core/decision-receipt.d.ts +2 -0
  10. package/dist/core/decision-receipt.js +1 -1
  11. package/dist/core/engine-notice.d.ts +41 -17
  12. package/dist/core/engine-notice.js +26 -14
  13. package/dist/core/gate-outcome.d.ts +80 -42
  14. package/dist/core/gate-outcome.js +21 -6
  15. package/dist/core/governance-codes.d.ts +1 -3
  16. package/dist/core/governance-codes.js +0 -3
  17. package/dist/core/hooks.d.ts +1 -1
  18. package/dist/core/hooks.js +2 -2
  19. package/dist/core/human-input-projection.d.ts +12 -0
  20. package/dist/core/human-input-projection.js +12 -0
  21. package/dist/core/memory-engine/engine.js +11 -11
  22. package/dist/core/memory-engine/layout.d.ts +21 -2
  23. package/dist/core/memory-engine/layout.js +17 -5
  24. package/dist/core/mode-policy.d.ts +26 -0
  25. package/dist/core/mode-policy.js +37 -1
  26. package/dist/core/output-style.d.ts +21 -0
  27. package/dist/core/output-style.js +29 -0
  28. package/dist/core/real-approval.d.ts +25 -0
  29. package/dist/core/real-approval.js +27 -0
  30. package/dist/core/reminder-disclosure.d.ts +2 -2
  31. package/dist/core/reminder-mint.d.ts +6 -9
  32. package/dist/core/reminder-mint.js +3 -14
  33. package/dist/core/retired-keys.d.ts +2 -1
  34. package/dist/core/retired-keys.js +3 -1
  35. package/dist/core/runner/assemble-result.d.ts +2 -0
  36. package/dist/core/runner/assemble-result.js +1 -1
  37. package/dist/core/runner/compaction-restate.d.ts +45 -4
  38. package/dist/core/runner/compaction-restate.js +89 -11
  39. package/dist/core/runner/contracts.d.ts +6 -6
  40. package/dist/core/runner/denial-limit-arms.js +4 -4
  41. package/dist/core/runner/inherited-ask-grants.d.ts +4 -2
  42. package/dist/core/runner/inherited-ask-grants.js +0 -3
  43. package/dist/core/runner/permission-rule-lanes.d.ts +18 -0
  44. package/dist/core/runner/permission-rule-lanes.js +35 -25
  45. package/dist/core/runner/prepare-context-lane.d.ts +1 -0
  46. package/dist/core/runner/prepare-context-lane.js +6 -0
  47. package/dist/core/runner/prepare-inherited-gate.js +2 -9
  48. package/dist/core/runner/prepare-task.js +10 -24
  49. package/dist/core/runner/prepare-wiring-manifest.js +2 -2
  50. package/dist/core/runner/prompt-block.d.ts +9 -0
  51. package/dist/core/runner/prompt-block.js +8 -0
  52. package/dist/core/runner/resume-admission.js +4 -1
  53. package/dist/core/runner/resume-apply.js +28 -18
  54. package/dist/core/runner/resume-policy-outcome.js +10 -4
  55. package/dist/core/runner/run-attachment-seats.js +1 -1
  56. package/dist/core/runner/run-identity-wiring.js +9 -2
  57. package/dist/core/runner/run-leg.js +42 -31
  58. package/dist/core/runner/run-settle-and-teardown.js +4 -3
  59. package/dist/core/runner/run-turn-boundary.js +4 -3
  60. package/dist/core/runner/runtask.d.ts +4 -2
  61. package/dist/core/runner/steer-admission.d.ts +9 -0
  62. package/dist/core/runner/steer-admission.js +8 -0
  63. package/dist/core/runner/stream-steer-verb.js +16 -11
  64. package/dist/core/runner/synthetic-tools.d.ts +4 -0
  65. package/dist/core/runner/tool-turn-repair-seat.js +1 -1
  66. package/dist/core/runner/turn-attachments.d.ts +16 -22
  67. package/dist/core/runner/turn-attachments.js +3 -20
  68. package/dist/core/runner-deps.d.ts +1 -1
  69. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +8 -0
  70. package/dist/core/store-contracts/checkpoint-store-contract.js +23 -0
  71. package/dist/core/task-event.d.ts +7 -2
  72. package/dist/core/task-spec.d.ts +29 -7
  73. package/dist/core/terminal-cause.d.ts +44 -3
  74. package/dist/core/terminal-cause.js +5 -0
  75. package/dist/core/tool-policy.d.ts +55 -29
  76. package/dist/core/tool-policy.js +42 -31
  77. package/dist/core/types.d.ts +1 -1
  78. package/dist/core/types.js +1 -1
  79. package/dist/core/wiring-manifest.d.ts +17 -3
  80. package/dist/core/wiring-manifest.js +8 -3
  81. package/dist/engine/harness/agent-harness.d.ts +10 -0
  82. package/dist/engine/harness/agent-harness.js +12 -1
  83. package/dist/engine/llm/tool-turns.d.ts +15 -2
  84. package/dist/engine/llm/tool-turns.js +2 -2
  85. package/dist/index.d.ts +4 -3
  86. package/dist/index.js +4 -3
  87. package/dist/prompt-assembly/event-registry.js +2 -1
  88. package/dist/tools/fs/index.d.ts +2 -1
  89. package/dist/tools/fs/read-deny-home.d.ts +3 -2
  90. package/dist/tools/fs/read-deny-home.js +6 -4
  91. package/dist/tools/fs/shell-read-check.d.ts +10 -3
  92. package/dist/tools/fs/shell-read-check.js +2 -2
  93. package/package.json +1 -1
  94. package/test/export-surface.snapshot.json +21 -1
  95. package/wire-changes/core@7.39.0.json +999 -0
  96. 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` — an
282
- * earlier call with no recorded result, which the request view closed as interrupted, outcome unknown (the
283
- * `missing_result` repair above). Audience `"user"`; `detail: { cause, count, toolCallIds?, sessionId, runId }` —
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"`, `"config.shell_gate_value_retired"` and
338
- * `"resume.legacy_mode_seed"` are ERROR codes, not notices (named here only to keep the mode family's spellings
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`), a `shellGate` value other than
341
- * `"always"`, and a checkpoint row whose pre-mode seed the migration cannot read. Each is a door refusal: the
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 four are deliberately absent from {@link ENGINE_NOTICE_CODES} /
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 undrainedUserInputNotices(counts, taskId, sessionId, runId) {
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 out = [];
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
- out.push({
63
- code: "task.user_steer_undrained",
64
- message: `${counts.steer} user steer(s) ${tail}`,
65
- detail: { steer: counts.steer, cause: "leg_end", ...tid, ...sid, ...rid },
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
- out.push({
70
- code: "task.user_followup_undrained",
71
- message: `${counts.followUp} user follow-up(s) ${tail}`,
72
- detail: { followUp: counts.followUp, ...tid, ...sid, ...rid },
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 out;
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` — a blanket `onAsk:"allow"` met a `requiresRealApproval` ask.
32
- * - `policy_refused` — the DEPLOYMENT'S OWN POLICY refused: the approval channel reported that a machine,
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 human words carry a person, the window words say whose window, the park word the host,
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" | "policy_refused" | "task_aborted";
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
- /** The person arm of {@link Settlement} — the shape the two human words inhabit. Named so a site that
105
- * both composes one and quotes its note can read the seat off the record instead of re-deriving it. */
106
- export type PersonSettlement = Extract<Settlement, {
107
- kind: "human_allowed" | "human_refused";
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 person's settlement: the synchronous resolver's human arms and the durable decide
119
- * lane's person arm compose their record here, so one refusal plus one reason text cannot end as two
120
- * different records depending on which path carried it.
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 decider attached a reason, and
123
- * its absence is how the record says the refusal was bare (the runner reads that absence, and nothing
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
- * a second answer to the same question, which is how one path came to halt a batch the other let run.
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 settledByPerson(kind: PersonSettlement["kind"], approver: string | undefined, note?: string): PersonSettlement;
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 human words and the window words — someone WAS asked; a person answered, or their window ran
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 an
211
- * ask nobody is present to answer into a refusal, and `bypassPermissions` refuses a write whose protection
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 person already gave (invariant I3: a `human_allowed` settlement
219
- * beside a `denied` disposition names one of these). The approval-edit re-screen (hook), the policy, org
220
- * and persisted-rule re-checks, and the write-protection re-judge run AFTER the person's yes; the
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 person's yes, on bytes or targets the person never saw. */
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 non-authority ask,
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.kind === "human_allowed"` ∧ `disposition.kind === "denied"` ⇒ `deniedBy` is a veto
335
- * layer ({@link DENIED_BY_MAY_VETO}) — a person approved, a later re-check refused; the approver stays
336
- * on the settlement (it is not the approver's refusal);
337
- * I4 `disposition.kind === "allowed"` ⇒ `settlement` absent or `human_allowed`;
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–I5 above, plus the shape of each member). Runs
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 settledByPerson(kind, approver, note) {
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", ...(approver !== undefined ? { approver } : {}) }, when: Date.now(), ...(given !== undefined ? { note: given } : {}) };
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
- if (s !== undefined && s.kind === "human_allowed" && d.kind === "denied" && isDeniedBy(d.deniedBy) && !DENIED_BY_MAY_VETO[d.deniedBy]) {
109
- defects.push(`I3: a human_allowed settlement beside a deny must name a veto layer, not "${d.deniedBy}"`);
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 && s.kind !== "human_allowed")
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", "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", "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"];
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",
@@ -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 or an operator authority's (`deniedBy: "mode"`), the other modes hand the ask on. ABSENT ⇒ no
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
  */
@@ -1,4 +1,4 @@
1
- import { decisionText, describeThrown, refuseOutOfContractDecision, isEngineRefusal } from "./tool-policy.js";
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 refuseOutOfContractDecision(r, { reasonIsNonInput: true });
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
+ }