@sema-agent/core 7.50.1 → 7.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/dist/agents/child-failure.d.ts +5 -16
  3. package/dist/agents/child-failure.js +4 -24
  4. package/dist/agents/subagent-steps.js +2 -2
  5. package/dist/agents/subagent.d.ts +4 -13
  6. package/dist/agents/subagent.js +94 -204
  7. package/dist/agents/tool-filter.d.ts +2 -2
  8. package/dist/brain/anthropic.d.ts +1 -1
  9. package/dist/brain/anthropic.js +7 -5
  10. package/dist/brain/open-responses.d.ts +1 -1
  11. package/dist/brain/open-responses.js +3 -2
  12. package/dist/brain/openai.d.ts +1 -1
  13. package/dist/brain/openai.js +3 -2
  14. package/dist/brain/request-route.d.ts +2 -2
  15. package/dist/brain/route-adjudicator.d.ts +16 -7
  16. package/dist/brain/route-adjudicator.js +23 -7
  17. package/dist/brain/route-conformance.js +23 -5
  18. package/dist/brain/stream-engine.js +9 -3
  19. package/dist/core/a2a.js +6 -8
  20. package/dist/core/allow-entry.d.ts +45 -26
  21. package/dist/core/allow-entry.js +48 -24
  22. package/dist/core/arg-summary.d.ts +8 -85
  23. package/dist/core/arg-summary.js +4 -386
  24. package/dist/core/ask-origin.d.ts +35 -33
  25. package/dist/core/ask-origin.js +9 -11
  26. package/dist/core/checkpoint-requirements.d.ts +6 -0
  27. package/dist/core/checkpoint-requirements.js +1 -0
  28. package/dist/core/checkpoint-store.d.ts +15 -34
  29. package/dist/core/checkpoint-store.js +5 -4
  30. package/dist/core/classifier-unavailable-arm.d.ts +12 -8
  31. package/dist/core/classifier-unavailable-arm.js +7 -2
  32. package/dist/core/credential-vocabulary.d.ts +3 -2
  33. package/dist/core/credential-vocabulary.js +27 -18
  34. package/dist/core/delegation-frames.d.ts +40 -15
  35. package/dist/core/delegation-frames.js +34 -0
  36. package/dist/core/engine-notice.d.ts +35 -11
  37. package/dist/core/gate-fold.js +9 -7
  38. package/dist/core/gate-lanes.js +6 -4
  39. package/dist/core/gate-outcome.d.ts +87 -11
  40. package/dist/core/gate-outcome.js +171 -61
  41. package/dist/core/governance-codes.d.ts +7 -1
  42. package/dist/core/governance-codes.js +9 -0
  43. package/dist/core/hooks.d.ts +4 -4
  44. package/dist/core/hooks.js +4 -3
  45. package/dist/core/mcp-connection-pool.d.ts +2 -0
  46. package/dist/core/mcp-connection-pool.js +2 -1
  47. package/dist/core/mcp-failure.js +2 -1
  48. package/dist/core/mcp-server-spec.d.ts +27 -5
  49. package/dist/core/mcp.d.ts +13 -38
  50. package/dist/core/mcp.js +28 -23
  51. package/dist/core/media-byte-cap.d.ts +25 -0
  52. package/dist/core/media-byte-cap.js +18 -2
  53. package/dist/core/memory-engine/consolidation-driver.js +1 -1
  54. package/dist/core/mode-policy.d.ts +25 -1
  55. package/dist/core/mode-policy.js +28 -13
  56. package/dist/core/own-property-receipt.d.ts +18 -0
  57. package/dist/core/own-property-receipt.js +39 -0
  58. package/dist/core/permission-rule-model.d.ts +32 -11
  59. package/dist/core/permission-rule-model.js +12 -7
  60. package/dist/core/persisted-rule-arms.d.ts +3 -3
  61. package/dist/core/prompt-section-seat.d.ts +54 -0
  62. package/dist/core/prompt-section-seat.js +49 -0
  63. package/dist/core/protocol-http-auth.d.ts +44 -0
  64. package/dist/core/protocol-http-auth.js +45 -0
  65. package/dist/core/read-face-seats.d.ts +30 -0
  66. package/dist/core/read-face-seats.js +38 -0
  67. package/dist/core/redaction-passes.d.ts +82 -0
  68. package/dist/core/redaction-passes.js +378 -0
  69. package/dist/core/remote-env.d.ts +4 -3
  70. package/dist/core/resume-startup-cause.d.ts +56 -0
  71. package/dist/core/resume-startup-cause.js +43 -0
  72. package/dist/core/rule-denial-sentence.d.ts +2 -2
  73. package/dist/core/runner/announce-once-ledger.js +2 -2
  74. package/dist/core/runner/classifier-ladder.d.ts +17 -13
  75. package/dist/core/runner/classifier-ladder.js +12 -10
  76. package/dist/core/runner/construction-doors.js +9 -20
  77. package/dist/core/runner/contracts.d.ts +4 -4
  78. package/dist/core/runner/gate-exit.js +1 -1
  79. package/dist/core/runner/memory-consolidation-write-judge.d.ts +53 -9
  80. package/dist/core/runner/memory-consolidation-write-judge.js +39 -1
  81. package/dist/core/runner/permission-rule-lanes.d.ts +8 -13
  82. package/dist/core/runner/person-path-rows.d.ts +4 -3
  83. package/dist/core/runner/policy-throw.js +4 -4
  84. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
  85. package/dist/core/runner/prepare-caps-and-workflow.js +8 -6
  86. package/dist/core/runner/prepare-gate-stations.js +3 -3
  87. package/dist/core/runner/prepare-hands-readface.js +1 -19
  88. package/dist/core/runner/prepare-inherited-gate.js +10 -9
  89. package/dist/core/runner/prepare-policy-chain.d.ts +3 -5
  90. package/dist/core/runner/prepare-policy-chain.js +10 -13
  91. package/dist/core/runner/prepare-project-context.d.ts +4 -4
  92. package/dist/core/runner/prepare-project-context.js +2 -1
  93. package/dist/core/runner/prepare-prompt-assembly.js +1 -0
  94. package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -1
  95. package/dist/core/runner/prepare-prompt-inputs.js +4 -4
  96. package/dist/core/runner/prepare-protocol-tools.d.ts +4 -3
  97. package/dist/core/runner/prepare-protocol-tools.js +3 -2
  98. package/dist/core/runner/prepare-task.js +6 -4
  99. package/dist/core/runner/prepare-turn-wiring.d.ts +16 -13
  100. package/dist/core/runner/prepare-turn-wiring.js +8 -7
  101. package/dist/core/runner/run-harness-handlers.js +2 -2
  102. package/dist/core/runner/run-leg.js +10 -9
  103. package/dist/core/runner/run-settle-and-teardown.js +5 -5
  104. package/dist/core/runner/run-turn-boundary.js +3 -2
  105. package/dist/core/runner/runtask.d.ts +1 -2
  106. package/dist/core/runner/runtask.js +8 -12
  107. package/dist/core/runner/session-rule-policy.d.ts +15 -11
  108. package/dist/core/runner/session-rule-policy.js +43 -33
  109. package/dist/core/runner/stream-lifecycle-verbs.js +2 -2
  110. package/dist/core/runner/stream-settle-backstop.js +2 -1
  111. package/dist/core/runner/sub-agent-deny-lists.d.ts +12 -8
  112. package/dist/core/runner/sub-agent-deny-lists.js +23 -12
  113. package/dist/core/runner/terminal-account.js +3 -3
  114. package/dist/core/runner/tool-output-projection.js +3 -8
  115. package/dist/core/runner/tool-result-media-seat.d.ts +19 -0
  116. package/dist/core/runner/tool-result-media-seat.js +13 -0
  117. package/dist/core/runner/turn-attachments.d.ts +13 -12
  118. package/dist/core/runner/turn-attachments.js +11 -7
  119. package/dist/core/runner-deps.d.ts +4 -3
  120. package/dist/core/safety-merge-corpus.js +21 -21
  121. package/dist/core/secret-env.js +1 -1
  122. package/dist/core/session-policy-store.d.ts +89 -72
  123. package/dist/core/session-policy-store.js +100 -93
  124. package/dist/core/shell-read-silence.d.ts +38 -4
  125. package/dist/core/shell-read-silence.js +102 -0
  126. package/dist/core/side-query.d.ts +7 -8
  127. package/dist/core/skill-grant-binding.d.ts +32 -11
  128. package/dist/core/skill-grant-binding.js +11 -3
  129. package/dist/core/skill-grant.d.ts +4 -3
  130. package/dist/core/skill-grant.js +5 -4
  131. package/dist/core/skills-directory.d.ts +12 -1
  132. package/dist/core/skills-directory.js +17 -8
  133. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  134. package/dist/core/store-contracts/checkpoint-store-contract.js +3 -0
  135. package/dist/core/swappable-deps.d.ts +15 -1
  136. package/dist/core/swappable-deps.js +25 -1
  137. package/dist/core/synthetic-read-verdict.d.ts +16 -14
  138. package/dist/core/synthetic-read-verdict.js +28 -8
  139. package/dist/core/task-notification.d.ts +13 -3
  140. package/dist/core/task-notification.js +12 -1
  141. package/dist/core/task-spec.d.ts +14 -15
  142. package/dist/core/tool-name-readings.d.ts +0 -4
  143. package/dist/core/tool-name-readings.js +0 -1
  144. package/dist/core/tool-policy.d.ts +40 -9
  145. package/dist/core/tool-policy.js +25 -7
  146. package/dist/core/types.d.ts +3 -3
  147. package/dist/core/types.js +1 -1
  148. package/dist/core/untrusted-egress.d.ts +60 -11
  149. package/dist/core/untrusted-egress.js +120 -9
  150. package/dist/core/untrusted-text.js +16 -0
  151. package/dist/core/wiring-manifest.d.ts +22 -8
  152. package/dist/core/wiring-manifest.js +12 -4
  153. package/dist/engine/harness/agent-harness.d.ts +2 -1
  154. package/dist/engine/harness/agent-harness.js +4 -1
  155. package/dist/engine/harness/types.d.ts +3 -0
  156. package/dist/engine/llm/types.d.ts +7 -9
  157. package/dist/engine/loop/agent-loop.d.ts +8 -3
  158. package/dist/engine/loop/agent-loop.js +26 -9
  159. package/dist/engine/loop/types.d.ts +10 -0
  160. package/dist/index.d.ts +17 -5
  161. package/dist/index.js +14 -5
  162. package/dist/orchestration/run-workflow-tool.js +8 -8
  163. package/dist/orchestration/workflow.js +2 -2
  164. package/dist/prompt-assembly/assemble.d.ts +3 -1
  165. package/dist/prompt-assembly/assemble.js +6 -0
  166. package/dist/prompts/cowork.d.ts +42 -0
  167. package/dist/prompts/cowork.js +529 -0
  168. package/dist/prompts/default.d.ts +14 -1
  169. package/dist/prompts/default.js +3 -0
  170. package/dist/stores/file/session-policy-store.d.ts +6 -3
  171. package/dist/stores/file/session-policy-store.js +5 -16
  172. package/dist/tools/artifact/artifact-tool.js +2 -0
  173. package/dist/tools/fs/image-admission.d.ts +57 -4
  174. package/dist/tools/fs/image-admission.js +85 -0
  175. package/dist/tools/fs/path-containment.d.ts +2 -0
  176. package/dist/tools/fs/path-containment.js +5 -0
  177. package/dist/tools/fs/shell-read-check.d.ts +31 -6
  178. package/dist/tools/fs/shell-read-check.js +2 -4
  179. package/dist/tools/fs/shell-read-environment.d.ts +10 -25
  180. package/dist/tools/fs/shell-read-environment.js +6 -18
  181. package/dist/tools/fs/shell-read-establish.d.ts +3 -2
  182. package/dist/tools/fs/shell-read-establish.js +5 -5
  183. package/dist/tools/fs/shell-read-judge.d.ts +38 -8
  184. package/dist/tools/fs/shell-read-judge.js +12 -10
  185. package/dist/tools/web.js +2 -2
  186. package/package.json +1 -1
  187. package/test/export-surface.snapshot.json +94 -10
  188. package/wire-changes/core@7.50.2.json +42 -0
  189. package/wire-changes/core@7.51.0.json +2421 -0
@@ -53,8 +53,8 @@ import type { ContainmentFace } from "../tools/fs/containment-face.js";
53
53
  * reads a word, an unterminated quote, a syntax error — the answer's `reason`): whether one of those
54
54
  * rows forbids this call is unknown, so an allow tightens to an ask a person must clear and no automatic
55
55
  * lane may answer. A store (or a partition of it) that could not be READ is this word too — a row inside it
56
- * may forbid the call and nobody can tell — with the class `governance_unavailable` instead of the releasable
57
- * `rule_unreadable`, both bypass-immune; the run also carries one `permission.rule_store_unavailable` notice.
56
+ * may forbid the call and nobody can tell. Both carry the class `rule_unreadable` (bypass-immune); the run also
57
+ * carries one `permission.rule_store_unavailable` notice.
58
58
  * - `hook` — a deployment's PreToolUse screening face raised or co-raised the ask; the word is
59
59
  * engine-stamped at the fold and is the deployment's own per-call adjudication.
60
60
  * - `ask_rule` — an EXPLICIT `ask` permission rule matched: a person's standing "ask me each time",
@@ -103,13 +103,12 @@ export interface AskOriginFacts {
103
103
  readonly org: "rule" | "unavailable" | undefined;
104
104
  /** The gate's own record of its persisted-rule lane's READ: the store was read, but THIS CALL could not be read
105
105
  * against the person's deny/ask rows (the lane's answer carried the lexer's reason) — the origin
106
- * `rule_store_unavailable`, the class `rule_unreadable` (bypass-immune, like the store form's). Absent: the lane read the call, or the store
106
+ * `rule_store_unavailable`, the class `rule_unreadable`. Absent: the lane read the call, or the store
107
107
  * could not be read (see {@link ruleStoreUnreadable}). Written only by the gate. */
108
108
  readonly ruleCallUnreadable?: boolean;
109
109
  /** The gate's own record of the same read when a PARTITION of the store (or all of it) could not be read: a deny / ask
110
- * row inside it may forbid this call and nobody can tell — the same origin `rule_store_unavailable`, the
111
- * bypass-immune class `governance_unavailable` (a source of governance that cannot be read: only a person answers).
112
- * Exclusive with {@link ruleCallUnreadable}. Written only by the gate. */
110
+ * row inside it may forbid this call and nobody can tell — the same origin and the same class as {@link ruleCallUnreadable}.
111
+ * Exclusive with it. Written only by the gate. */
113
112
  readonly ruleStoreUnreadable?: boolean;
114
113
  /** Which of the gate's OWN post-fold stations minted the standing ask, if one did — again the gate's
115
114
  * record of its own layer (a station replaces an `allow` wholesale, so at most one fires per pass),
@@ -156,6 +155,15 @@ export declare function stampAskOrigin<T extends Extract<PermissionResult, {
156
155
  }>>(decision: T, facts: AskOriginFacts, classification: AskClassification): T & {
157
156
  origin: AskOrigin;
158
157
  } & AskClassMembers;
158
+ /**
159
+ * WHY an approval card offers no persisted allow rule, one word per reason — the closed set of `ruleOffersAbsence`
160
+ * on the ask and its park twin, written by the one offer factory: `mandated` (no persisted rule may clear the ask),
161
+ * `allow_outranked` (an allow-layer member already matched this call and the question outranks it), `ask_rule_matched`
162
+ * (an explicit `ask` rule matched: it keeps asking), `lane_cannot_speak` (a rule could clear the ask and the lane has no
163
+ * text for this call). The first three are {@link ruleClearanceOf}'s doors.
164
+ */
165
+ export declare const RULE_OFFERS_ABSENCES: readonly ["mandated", "allow_outranked", "ask_rule_matched", "lane_cannot_speak"];
166
+ export type RuleOffersAbsence = (typeof RULE_OFFERS_ABSENCES)[number];
159
167
  /**
160
168
  * CAN a persisted allow rule clear this ask — and, if not, which door closed? ONE derivation with TWO
161
169
  * readers, which is the whole point of the shape: the approval card's `ruleOffersAbsence` word and the
@@ -177,20 +185,20 @@ export declare function stampAskOrigin<T extends Extract<PermissionResult, {
177
185
  * approver and this task's rule lane never adjudicates it;
178
186
  * · the bit already stamped on the ask, which no layer below the mint may clear (tightening-only).
179
187
  *
180
- * And the SHADOW doors, which are not mandates: an allow-layer member already matched and is outranked
181
- * (`allowRuleShadowed`), or the person's explicit "ask me each time" rule matched
182
- * (`matchedAskRule`). A rule minted from such a card silences nothing, so the lane offers none — but
188
+ * And two doors that are not mandates, each its own word: an allow-layer member already matched and is outranked
189
+ * (`allowRuleShadowed` ⇒ `allow_outranked`), or the person's explicit "ask me each time" rule matched
190
+ * (`matchedAskRule` ⇒ `ask_rule_matched`). A rule minted from such a card silences nothing, so the lane offers none — but
183
191
  * the reason is that a RULE is already speaking, not that the question is beyond rules, and a surface
184
192
  * that auto-answers by posture is not crossing a mandate when it answers one.
185
193
  *
186
194
  * FOUR named readings of the one pass, because four callers need four different things and taking
187
195
  * the wrong one is how this boundary drifts:
188
- * · `word` — the DISCLOSED reason, first door wins, in the display order the card has always used
189
- * (governance, the shadow, the hook, the ask rule, the two ancestor doors, then the marks). Display
196
+ * · `word` — the DISCLOSED reason ({@link RuleOffersAbsence}), first door wins, in the display order the card has always
197
+ * used (governance, the outranked allow, the hook, the ask rule, the two ancestor doors, then the marks). Display
190
198
  * only. A structural door the offer factory owns (no lane wired, a tool it does not speak for, a
191
199
  * task that cannot hold a rule) is decided BEFORE this and is deliberately silent;
192
200
  * · `mandated` — the FACT the ask carries as its own member. Read off the whole mandate set, so which
193
- * door is DISPLAYED cannot change it: an ask that is both shadowed and mandated is mandated;
201
+ * door is DISPLAYED cannot change it: an ask that is both outranked and mandated is mandated;
194
202
  * · `ruleMayClear` — the ENFORCEMENT reading the gate's clearing arms take. It is `mandated` plus the
195
203
  * person's explicit `ask` rule, and deliberately NOT `word !== undefined`: `allowRuleShadowed`
196
204
  * is an ACCOUNT of a refusal that already happened, display-only by contract, and reading it as a
@@ -201,7 +209,7 @@ export declare function stampAskOrigin<T extends Extract<PermissionResult, {
201
209
  * not one word.
202
210
  */
203
211
  export declare function ruleClearanceOf(ask: RuleClearanceFacts | undefined, marks: Omit<MandateMarks, "probeMandated">): {
204
- word?: "mandated" | "shadowed";
212
+ word?: Exclude<RuleOffersAbsence, "lane_cannot_speak">;
205
213
  mandated: boolean;
206
214
  ruleMayClear: boolean;
207
215
  mandate?: PersistedRuleMandate;
@@ -347,7 +355,7 @@ export declare function mandateDisclosureOf(mandate: PersistedRuleMandate | unde
347
355
  * in {@link irreversibilityMandateOf} which {@link safetyAxisOf} shares: such a write is not an irreversible action, and
348
356
  * its park stays budgetable.
349
357
  *
350
- * Two consumers, one derivation: the gate's silencing arm (a matching rule is disclosed as shadowed
358
+ * Two consumers, one derivation: the gate's silencing arm (a matching rule is disclosed as outranked
351
359
  * instead of clearing the ask) and the runner's suggestion factory (a mandated ask offers no
352
360
  * "stop asking me this" option — a rule minted from it would never clear it). A drift between the
353
361
  * two would let a card offer a rule the lane then refuses to honor. The per-call member reaches both
@@ -379,7 +387,7 @@ export interface MandateMarks {
379
387
  targetUnresolved?: boolean;
380
388
  /** The pass's own record that the probe was consulted and did not answer (`GatePass.probeUnanswered`):
381
389
  * the boundary is unknown, which no standing yes may stand in for. The runner's suggestion factory does
382
- * not see the pass and never passes it — a card may offer a rule the lane then discloses as shadowed
390
+ * not see the pass and never passes it — a card may offer a rule the lane then discloses as outranked
383
391
  * (loud, never a silent override), the same drift window the `matchedAskRule` conjunct already has. */
384
392
  probeUnanswered?: boolean;
385
393
  }
@@ -467,11 +475,12 @@ export type ToolMarkSource = "deployment" | "mcp_annotation" | "a2a_default";
467
475
  * working directory — or an ancestor of one): the reference implementation's dangerous-removal breaker, which no mode,
468
476
  * allow rule or classifier answers. `unverifiable_delete` is its sibling for a target the command text cannot resolve;
469
477
  * `unverifiable_shell_text` is a shell handed text the command does not spell (a program's output, a substitution, a
470
- * file's content, an argv a stream fills in): the engine cannot read what that shell will execute. Its row is
471
- * `unverifiable_delete`'s save the classifier column — such a shell was `dangerous_delete` before it had a word of its own,
472
- * and the classifier did not answer that.
478
+ * file's content, an argv a stream fills in): the engine cannot read what that shell will execute. The three read one
479
+ * row ({@link REMOVAL_BREAKER_ROW}).
480
+ * `rule_unreadable` is a rule that may govern the call and cannot be read for it: the organization's snapshot, a partition of
481
+ * the person's store, or this call against a deny / ask row (the lexer's word, a content row, the read station's rows).
473
482
  */
474
- export declare const ASK_CLASSES: readonly ["content_question", "hook", "ancestor", "dangerous_delete", "unverifiable_delete", "unverifiable_shell_text", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "ask_rule", "denial_limit_fallback", "classifier_no_ruling", "requires_user_interaction", "operator_forced", "shell_floor", "target_unresolved", "probe", "write_protection", "tool_marks_mcp", "tool_marks_a2a", "policy_rra", "artifact_shared_read", "mode", "unstated", "ordinary"];
483
+ export declare const ASK_CLASSES: readonly ["content_question", "hook", "ancestor", "dangerous_delete", "unverifiable_delete", "unverifiable_shell_text", "transcript_write", "org_rule", "rule_unreadable", "ask_rule", "denial_limit_fallback", "classifier_no_ruling", "requires_user_interaction", "operator_forced", "shell_floor", "target_unresolved", "probe", "write_protection", "tool_marks_mcp", "tool_marks_a2a", "policy_rra", "artifact_shared_read", "mode", "unstated", "ordinary"];
475
484
  export type AskClass = (typeof ASK_CLASSES)[number];
476
485
  /** Membership test for {@link AskClass} — the word crosses the wire on `tool_end.gate.disposition`. */
477
486
  export declare function isAskClass(v: unknown): v is AskClass;
@@ -531,27 +540,27 @@ export declare const MODE_ARBITRATION: {
531
540
  readonly ruleMayClear: false;
532
541
  readonly hookAskFloor: false;
533
542
  };
534
- readonly dangerous_delete: {
543
+ readonly dangerous_delete: Readonly<{
535
544
  readonly bypassImmune: true;
536
545
  readonly classifierEligible: false;
537
546
  readonly sandboxAdmission: false;
538
547
  readonly ruleMayClear: false;
539
548
  readonly hookAskFloor: true;
540
- };
541
- readonly unverifiable_delete: {
549
+ }>;
550
+ readonly unverifiable_delete: Readonly<{
542
551
  readonly bypassImmune: true;
543
- readonly classifierEligible: true;
552
+ readonly classifierEligible: false;
544
553
  readonly sandboxAdmission: false;
545
554
  readonly ruleMayClear: false;
546
555
  readonly hookAskFloor: true;
547
- };
548
- readonly unverifiable_shell_text: {
556
+ }>;
557
+ readonly unverifiable_shell_text: Readonly<{
549
558
  readonly bypassImmune: true;
550
559
  readonly classifierEligible: false;
551
560
  readonly sandboxAdmission: false;
552
561
  readonly ruleMayClear: false;
553
562
  readonly hookAskFloor: true;
554
- };
563
+ }>;
555
564
  readonly transcript_write: {
556
565
  readonly bypassImmune: false;
557
566
  readonly classifierEligible: true;
@@ -566,13 +575,6 @@ export declare const MODE_ARBITRATION: {
566
575
  readonly ruleMayClear: false;
567
576
  readonly hookAskFloor: false;
568
577
  };
569
- readonly governance_unavailable: {
570
- readonly bypassImmune: true;
571
- readonly classifierEligible: false;
572
- readonly sandboxAdmission: false;
573
- readonly ruleMayClear: false;
574
- readonly hookAskFloor: false;
575
- };
576
578
  readonly rule_unreadable: {
577
579
  readonly bypassImmune: true;
578
580
  readonly classifierEligible: false;
@@ -23,6 +23,7 @@ export function stampAskOrigin(decision, facts, classification) {
23
23
  const { probeReadRootCandidate: _withdrawn, ...kept } = mandated;
24
24
  return { ...kept, origin, ...classed };
25
25
  }
26
+ export const RULE_OFFERS_ABSENCES = Object.freeze(["mandated", "allow_outranked", "ask_rule_matched", "lane_cannot_speak"]);
26
27
  export function ruleClearanceOf(ask, marks) {
27
28
  const marksWord = persistedRuleMandateOf({ ...marks, probeMandated: ask?.probeMandated === true });
28
29
  const foldedDoor = (ask?.foldedAskClasses ?? []).some((c) => !MODE_ARBITRATION[c].ruleMayClear);
@@ -34,7 +35,7 @@ export function ruleClearanceOf(ask, marks) {
34
35
  if (ask?.requiresRealApproval === true)
35
36
  return "mandated";
36
37
  if (Array.isArray(ask?.allowRuleShadowed) && ask.allowRuleShadowed.length > 0)
37
- return "shadowed";
38
+ return "allow_outranked";
38
39
  if (ask?.decisionReason === "hook")
39
40
  return "mandated";
40
41
  if (ask?.decisionReason === "mode" || foldedDoor)
@@ -42,7 +43,7 @@ export function ruleClearanceOf(ask, marks) {
42
43
  if (ask?.authoredBy === "deployment")
43
44
  return "mandated";
44
45
  if (ask?.matchedAskRule !== undefined)
45
- return "shadowed";
46
+ return "ask_rule_matched";
46
47
  if (ask?.inheritedUnresolved === true)
47
48
  return "mandated";
48
49
  if (ask?.ancestorResolved === true)
@@ -177,7 +178,6 @@ export const ASK_CLASSES = Object.freeze([
177
178
  "unverifiable_shell_text",
178
179
  "transcript_write",
179
180
  "org_rule",
180
- "governance_unavailable",
181
181
  "rule_unreadable",
182
182
  "ask_rule",
183
183
  "denial_limit_fallback",
@@ -200,16 +200,16 @@ const ASK_CLASS_SET = new Set(ASK_CLASSES);
200
200
  export function isAskClass(v) {
201
201
  return ASK_CLASS_SET.has(v);
202
202
  }
203
+ const REMOVAL_BREAKER_ROW = Object.freeze({ bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true });
203
204
  export const MODE_ARBITRATION = {
204
205
  content_question: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
205
206
  hook: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
206
207
  ancestor: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
207
- dangerous_delete: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
208
- unverifiable_delete: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
209
- unverifiable_shell_text: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
208
+ dangerous_delete: REMOVAL_BREAKER_ROW,
209
+ unverifiable_delete: REMOVAL_BREAKER_ROW,
210
+ unverifiable_shell_text: REMOVAL_BREAKER_ROW,
210
211
  transcript_write: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
211
212
  org_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
212
- governance_unavailable: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
213
213
  rule_unreadable: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
214
214
  ask_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
215
215
  denial_limit_fallback: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
@@ -237,7 +237,7 @@ function bypassImmuneOn(c, face) {
237
237
  return MODE_ARBITRATION[c].bypassImmune && !(face === "shell" && RELEASED_ON_SHELL_FACE.has(c));
238
238
  }
239
239
  const CLASSIFIER_WITHHELD_ON_ROOTS = new Set(["probe"]);
240
- export const RRA_EXPLAINERS = new Set(["dangerous_delete", "unverifiable_delete", "unverifiable_shell_text", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "denial_limit_fallback", "classifier_no_ruling", "ancestor", "artifact_shared_read", "requires_user_interaction"]);
240
+ export const RRA_EXPLAINERS = new Set(["dangerous_delete", "unverifiable_delete", "unverifiable_shell_text", "transcript_write", "org_rule", "rule_unreadable", "denial_limit_fallback", "classifier_no_ruling", "ancestor", "artifact_shared_read", "requires_user_interaction"]);
241
241
  const RELEASABLE_BY_FACE = Object.freeze({
242
242
  shell: Object.freeze(ASK_CLASSES.filter((c) => !bypassImmuneOn(c, "shell"))),
243
243
  other: Object.freeze(ASK_CLASSES.filter((c) => !bypassImmuneOn(c, "other"))),
@@ -316,9 +316,7 @@ export function classifyAsk(d, facts) {
316
316
  classes.add("ancestor");
317
317
  if (facts.org === "rule")
318
318
  classes.add("org_rule");
319
- if (facts.org === "unavailable" || facts.ruleStoreUnreadable === true)
320
- classes.add("governance_unavailable");
321
- if (facts.ruleCallUnreadable === true)
319
+ if (facts.org === "unavailable" || facts.ruleStoreUnreadable === true || facts.ruleCallUnreadable === true)
322
320
  classes.add("rule_unreadable");
323
321
  const marks = facts.mandateMarks;
324
322
  if (marks.requiresUserInteraction === true)
@@ -290,6 +290,9 @@ export declare const PENDING_ACTION_REQUIREMENTS: {
290
290
  readonly boundGrants: {
291
291
  readonly ignorable: "shown, never decided (card, listing, transcript or reported count): every read of the name renders, carries transcript or counts";
292
292
  };
293
+ readonly boundGrantAbsence: {
294
+ readonly ignorable: "shown, never decided (card, listing, transcript or reported count): every read of the name renders, carries transcript or counts";
295
+ };
293
296
  readonly capabilityId: {
294
297
  readonly digest: "decision-read";
295
298
  };
@@ -784,6 +787,9 @@ export declare const CHECKPOINT_REQUIREMENTS: {
784
787
  readonly boundGrants: {
785
788
  readonly ignorable: "shown, never decided (card, listing, transcript or reported count): every read of the name renders, carries transcript or counts";
786
789
  };
790
+ readonly boundGrantAbsence: {
791
+ readonly ignorable: "shown, never decided (card, listing, transcript or reported count): every read of the name renders, carries transcript or counts";
792
+ };
787
793
  readonly capabilityId: {
788
794
  readonly digest: "decision-read";
789
795
  };
@@ -114,6 +114,7 @@ export const PENDING_ACTION_REQUIREMENTS = {
114
114
  boundInputHash: DECISION_READ,
115
115
  boundGrantDigest: { word: "skill_card_binding" },
116
116
  boundGrants: SHOWN,
117
+ boundGrantAbsence: SHOWN,
117
118
  capabilityId: DECISION_READ,
118
119
  contractId: DECISION_READ,
119
120
  shapeDigest: DECISION_READ,
@@ -10,6 +10,7 @@ import type { WorkspaceHandle } from "./remote-env.js";
10
10
  import type { SessionPermissionRules } from "./session-policy-store.js";
11
11
  import type { NestedUsageAccum, PermissionMode, ShellGateDoctrine, TaskResult } from "./types.js";
12
12
  import type { ActiveWorktreeSession } from "../internal/harness-types.js";
13
+ import type { ReopenReason } from "./resume-startup-cause.js";
13
14
  /**
14
15
  * design/45 — the **durable-checkpoint** primitive: cross-process / resumable `suspend`/`resume`.
15
16
  *
@@ -768,23 +769,13 @@ export type PendingAction = {
768
769
  * — the PARK twin of `AskRequest.ruleOffersAbsence`, minted by the SAME one factory as
769
770
  * {@link ruleOffers} so the parked card and the live card cannot answer differently. Present ⇔
770
771
  * the rule-offer lane is in play for this call and has nothing to give, naming which door
771
- * closed: `"mandated"` (no persisted rule could ever clear this ask — a real-approval demand,
772
- * the tool's own mandate marks, a hook-raised ask, an ancestor's authority, or — #502 — a
773
- * demotion this CALL's reversibility probe declared structural, which for the built-in shell
774
- * probe means a read outside the directories the session declared; an inbox must NOT point at
775
- * rule-writing on this arm — "allow rules silence the classifier's questions, never a mandated
776
- * one" — nor at a directory grant: the subpath-rule family exists, but the out-of-root arm has
777
- * no clearing configuration in this version, so a grant minted in answer to this line would
778
- * never take effect where it was minted; confirming the call is the whole of what a person can
779
- * do about it),
780
- * `"shadowed"` (a rule the person already wrote is
781
- * speaking and does not clear it), `"lane_cannot_speak"` (this card has no rule to offer — the
782
- * grammar has no text for the command, or the card's array-order contract declines to present
783
- * the one text that exists; it is NOT a claim that no rule could ever be written).
784
- * Mutually exclusive with {@link ruleOffers}. ABSENCE is not a claim — it also covers
785
- * "there are offers" and the structural doors (no lane wired, another tool, a task that cannot
786
- * hold a rule) — so read presence. See the synchronous seat's doc for the full contract. */
787
- ruleOffersAbsence?: "mandated" | "lane_cannot_speak" | "shadowed";
772
+ * closed, one word of {@link import("./ask-origin.js").RULE_OFFERS_ABSENCES} per reason (`"mandated"`:
773
+ * an inbox must NOT point at rule-writing nor at a directory grant on this arm — confirming the call
774
+ * is the whole of what a person can do about it). Mutually exclusive with {@link ruleOffers}.
775
+ * ABSENCE is not a claim — it also covers "there are offers" and the structural doors (no lane
776
+ * wired, another tool, a task that cannot hold a rule) — so read presence. See the synchronous
777
+ * seat's doc for the full contract. */
778
+ ruleOffersAbsence?: import("./ask-origin.js").RuleOffersAbsence;
788
779
  /** design/382 §2.4 (adversarial-review r3; additive — no checkpoint-version bump, the
789
780
  * `previewWithheld` precedent: an optional field an older reader ignores and the resume
790
781
  * path never reads) — the PARK twin of `AskRequest.execCwd`, minted by the SAME factory as
@@ -807,7 +798,7 @@ export type PendingAction = {
807
798
  * Minted from the SAME facts object the row's rule-offer factory reads, through
808
799
  * {@link import("./ask-origin.js").askMandateMember} (the live card's derivation). Three independent
809
800
  * facts: {@link requiresRealApproval} (only a person — implies this one), this one (no rule), and
810
- * {@link ruleOffersAbsence} (what the offer lane has to give: silent off its tool and wiring, `"shadowed"`
801
+ * {@link ruleOffersAbsence} (what the offer lane has to give: silent off its tool and wiring, `"ask_rule_matched"`
811
802
  * when a person's rule also speaks — never a substitute for this one). Display metadata: no gate, CAS or
812
803
  * resume path reads it. */
813
804
  mandated?: true;
@@ -856,6 +847,8 @@ export type PendingAction = {
856
847
  boundGrantDigest?: string;
857
848
  /** The value {@link boundGrantDigest} hashes — the card's twin of `AskRequest.boundGrants`, minted by the same factory; written exactly when the digest is. Display metadata (additive, no version bump): the resume judges the digest alone. */
858
849
  boundGrants?: import("./skill-grant-binding.js").BoundSkillGrants;
850
+ /** The PARK twin of `AskRequest.boundGrantAbsence`, minted by the same factory: written exactly when the digest is not. Display metadata. */
851
+ boundGrantAbsence?: import("./skill-grant-binding.js").BoundGrantAbsence;
859
852
  /**
860
853
  * design/388 §2.5 boundary ② (B18; additive — no checkpoint-version bump, the `previewWithheld`
861
854
  * precedent: an optional group an older reader ignores): the IDENTITY of the row the park minted
@@ -1602,22 +1595,8 @@ export interface ResolvedOutcome {
1602
1595
  * (`when` differs per replay; the record is a consequence of the decision, not its substance). */
1603
1596
  gateOutcome?: import("./gate-outcome.js").GateOutcome;
1604
1597
  }
1605
- /**
1606
- * design/80 D-1: why a consumed checkpoint was {@link CheckpointStore.reopen | reopened} (resolved →
1607
- * pending). The reason drives re-resume validation (§3 invariant #1): an `env_failed` reopen is a SYSTEM
1608
- * RETRY of the ALREADY-APPROVED action — the re-resume MUST replay the persisted {@link ResolvedOutcome}
1609
- * winner, never a new vote — while a `tool_unavailable` reopen (the action could not run, may now be
1610
- * invalid — P-7) lets a human RE-DECIDE with the tool present, so a fresh operator decision IS allowed.
1611
- *
1612
- * `constraint_rejected` (F-012 L3) carries the same FRESH-REDECISION semantics as `tool_unavailable`:
1613
- * a resolve was retracted because its decided INPUT failed a constraint re-adjudication (a frozen
1614
- * ancestor projection / resume-edit policy denied the approver's edit) — the ask itself is unconsumed,
1615
- * so the returned-to-pending row takes a fresh operator decision, never a forced replay of the
1616
- * rejected one. Core's own edit re-adjudication runs PRE-CAS (nothing to reopen there); this reason
1617
- * exists for a deployment lane that discovers the same rejection only after its own resolve landed
1618
- * and must return the row to pending without inventing an out-of-vocabulary reason.
1619
- */
1620
- export type ReopenReason = "env_failed" | "tool_unavailable" | "constraint_rejected";
1598
+ /** Why a consumed checkpoint was reopened — declared beside the one table that picks it. */
1599
+ export { REOPEN_REASONS, type ReopenReason } from "./resume-startup-cause.js";
1621
1600
  /**
1622
1601
  * design/80 D-1 (atomicity fix): the optimistic-concurrency precondition a {@link CheckpointStore.resolve}
1623
1602
  * caller passes so its CAS is atomic with the validation it did against an earlier `get()` snapshot. The
@@ -2013,6 +1992,8 @@ export interface CheckpointSummary {
2013
1992
  boundGrantDigest?: string;
2014
1993
  /** The row's `boundGrants` — a fresh copy of the skill and its two lists, published only when it hashes to {@link boundGrantDigest} (never a list beside a digest that binds another). Deployment text: render with contextual escaping. ECHO-ONLY. */
2015
1994
  boundGrants?: import("./skill-grant-binding.js").BoundSkillGrants;
1995
+ /** Every `tool_approval` summary carries {@link boundGrantDigest} or this, never both: the row's word when it is a member and the row has no digest, else `unreadable` (the row does not say what it binds). ECHO-ONLY. */
1996
+ boundGrantAbsence?: import("./skill-grant-binding.js").BoundGrantAbsence;
2016
1997
  /** The row's `PendingAction.tool_approval.origin`, projected for the same one-call reason: a card
2017
1998
  * rendered by the ask's origin word must not re-open the row to learn it. Echoed ONLY when the stored
2018
1999
  * value is a member of the closed set ({@link import("./ask-origin.js").isAskOrigin}) — a bad value
@@ -7,7 +7,7 @@ import { isAskOrigin, isPersistedRuleMandate } from "./ask-origin.js";
7
7
  import { readBoundBinding } from "./skill-grant-binding.js";
8
8
  import { checkpointExecutionRecorded, executionRecordDisposition, ExecutionOutcomeConflictError, } from "./checkpoint-execution-record.js";
9
9
  import { renderUntrustedCommandText, stripFormatCharacters } from "./permission-rule-model.js";
10
- import { redactSecrets } from "./untrusted-egress.js";
10
+ import { egressText } from "./untrusted-egress.js";
11
11
  import { steerReplayRefusal } from "./steer-replay.js";
12
12
  import { isToolApprovalCard } from "./tool-face.js";
13
13
  import { isPermissionMode } from "./ask-origin.js";
@@ -131,7 +131,7 @@ export function buildRiskDescriptor(input) {
131
131
  if (shell || toolName === "Bash") {
132
132
  const cmd = isPlainRecord(args) ? safeDataValue(args, "command") : undefined;
133
133
  if (typeof cmd === "string" && cmd.length > 0)
134
- summary = renderUntrustedCommandText(stripFormatCharacters(redactSecrets(cmd)), SUMMARY_CMD_MAX);
134
+ summary = renderUntrustedCommandText(stripFormatCharacters(egressText("same-principal", cmd)), SUMMARY_CMD_MAX);
135
135
  const bg = isPlainRecord(args) ? safeDataValue(args, "run_in_background") : undefined;
136
136
  if (bg === true)
137
137
  summary = `[background persistent process — no per-step recheck] ${summary ?? ""}`.trimEnd();
@@ -146,7 +146,7 @@ export function buildRiskDescriptor(input) {
146
146
  if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
147
147
  const encDigest = (s) => s.replace(/%/g, "%25").replace(/=/g, "%3D").replace(/ /g, "%20");
148
148
  const k = encDigest(renderUntrustedCommandText(key, 40));
149
- const val = encDigest(renderUntrustedCommandText(stripFormatCharacters(redactSecrets(String(v))), SUMMARY_VALUE_MAX));
149
+ const val = encDigest(renderUntrustedCommandText(stripFormatCharacters(egressText("same-principal", String(v))), SUMMARY_VALUE_MAX));
150
150
  parts.push(`${k}=${val}`);
151
151
  }
152
152
  }
@@ -247,6 +247,7 @@ export function remainingTokens(ledger) {
247
247
  return 0;
248
248
  return Math.max(0, total - (spent ?? 0));
249
249
  }
250
+ export { REOPEN_REASONS } from "./resume-startup-cause.js";
250
251
  export function summarizeCheckpoint(cp) {
251
252
  const riskDescriptor = cp.gate.kind === "human" || cp.gate.kind === "irreversible_ask" ? cp.gate.riskDescriptor : undefined;
252
253
  const severity = riskDescriptor !== undefined ? riskSeverity(riskDescriptor.axes) : undefined;
@@ -300,7 +301,7 @@ export function summarizeCheckpoint(cp) {
300
301
  const why = tool?.classifierNoRuling;
301
302
  return { ...(isAskOrigin(origin) ? { origin } : {}), ...(isPersistedRuleMandate(mandate) ? { mandate } : {}), ...(isAutoModeFallbackCause(why) ? { classifierNoRuling: why } : {}) };
302
303
  })(),
303
- ...readBoundBinding(tool),
304
+ ...(tool !== undefined ? readBoundBinding(tool) : {}),
304
305
  ...(hasBidi ? { hasBidiControls: true } : {}),
305
306
  };
306
307
  }
@@ -3,8 +3,8 @@
3
3
  * budget ran out — `error` / `timeout` — or it replied with no verdict at all — `parse_error`) — shared by both classifier
4
4
  * stations, the leg's gate and an ancestor's frozen classifier in the policy fold, so the rule is written once.
5
5
  *
6
- * ONE fork, on the APPROVAL SEAT (does an ask this station hands on reach a person — a live approver, or a durable park):
7
- * with one, the ask goes on carrying why (`classifierNoRuling`, its text saying it too), and a person decides — never
6
+ * ONE fork, on the APPROVAL ROUTE the station states (`ApprovalRoute`, the one seat decision — `live_approver`,
7
+ * `durable_park` or `none`): with a person on the route, the ask goes on carrying why (`classifierNoRuling`, its text saying it too), and a person decides — never
8
8
  * counted (a person's refusal of it is a person's); without one, an unavailable classification is refused with CC's `x1t`
9
9
  * sentence (its FORM, `unavailable`, attested on the deny) and is never counted, while a reply with no verdict goes back to
10
10
  * the station's ruling path (`counted`): refused with its own sentence and counted on the denial limit, so the bound falls
@@ -18,22 +18,26 @@
18
18
  import { type AutoModeClassifierRound, type AutoModeDenialTracker, type AutoModeFallbackCause, type UnarmedDenialLimitFallback } from "./auto-mode.js";
19
19
  import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "./tool-policy.js";
20
20
  import type { Settlement } from "./gate-outcome.js";
21
- /** Where a classification that did not rule sent the call: `asked` = on to the approval seat, `denied` = refused. */
21
+ import type { ApprovalRoute } from "./wiring-manifest.js";
22
+ /** Where a classification that did not rule sent the call: `asked` = on to the approval seat, `denied` = refused — the
23
+ * projection of the route ({@link classifierUnavailableDispositionOf}), never a second reading of it. */
22
24
  export type ClassifierUnavailableDisposition = "asked" | "denied";
25
+ /** The disposition a route projects: a route with a person on it is `asked`, `none` is `denied`. */
26
+ export declare function classifierUnavailableDispositionOf(route: ApprovalRoute): ClassifierUnavailableDisposition;
23
27
  /** What the stations' observer receives — the facts the `classifier.unavailable` notice is composed from. `cause` is why
24
28
  * the classification did not rule ({@link AutoModeFallbackCause}: `error` / `timeout`, or `parse_error` for a reply with no
25
- * verdict). */
29
+ * verdict); `route` is where the station sent the call (the disposition the notice's detail carries is its projection). */
26
30
  export interface ClassifierUnavailableInfo {
27
31
  toolName: string;
28
32
  toolCallId: string;
29
33
  cause: AutoModeFallbackCause;
30
- disposition: ClassifierUnavailableDisposition;
34
+ route: ApprovalRoute;
31
35
  }
32
36
  export type ClassifierUnavailableObserver = (info: ClassifierUnavailableInfo) => void;
33
- /** The station's two facts for the fork: whether an ask it hands on reaches a person (`true` only — anything else is
34
- * no seat), and who hears it. */
37
+ /** The station's two facts for the fork: the route an ask it hands on takes (`approvalRouteOf` / `frozenApproverRoute` —
38
+ * a value outside the two person routes, or none stated, is `none`), and who hears it. */
35
39
  export interface ClassifierUnavailableSeat {
36
- approvalSeat?: boolean;
40
+ route?: ApprovalRoute;
37
41
  onUnavailable?: ClassifierUnavailableObserver;
38
42
  }
39
43
  /** The arm the fork chose: the ask to hand on, the refusal, or — a reply with no verdict and no seat — the station's own
@@ -1,11 +1,16 @@
1
1
  import { classifierNoRulingAskMessage, classifierUnavailableDenyMessage } from "./auto-mode.js";
2
2
  import { attest } from "./tool-policy.js";
3
+ export function classifierUnavailableDispositionOf(route) {
4
+ return route === "none" ? "denied" : "asked";
5
+ }
6
+ const routeOfSeat = (seat) => (seat?.route === "live_approver" || seat?.route === "durable_park" ? seat.route : "none");
3
7
  export function classifierNoRulingArm(args) {
4
8
  const { ask, req, cause } = args;
5
- const seated = args.seat?.approvalSeat === true;
9
+ const route = routeOfSeat(args.seat);
10
+ const seated = route !== "none";
6
11
  if (!args.aborted) {
7
12
  try {
8
- args.seat?.onUnavailable?.({ toolName: req.toolName, toolCallId: req.toolCallId, cause, disposition: seated ? "asked" : "denied" });
13
+ args.seat?.onUnavailable?.({ toolName: req.toolName, toolCallId: req.toolCallId, cause, route });
9
14
  }
10
15
  catch {
11
16
  }
@@ -115,8 +115,9 @@ export interface EnvNameView {
115
115
  /** A credential word in its position, case-insensitive. */
116
116
  readonly credentialWord: RegExp;
117
117
  readonly connectionStringName: RegExp;
118
- /** The counted plurals as a regex source (a plural no count word touches). */
119
- readonly countedPlural: string;
118
+ /** A credential word that is a whole `_` segment of a name (every position but `embedded`; a counted plural no count
119
+ * word touches), as a regex source. */
120
+ readonly segmentWord: string;
120
121
  readonly tokenPrefix: RegExp;
121
122
  }
122
123
  export declare function envNameView(vocabulary: CredentialVocabulary): EnvNameView;
@@ -8,8 +8,10 @@ const PLAIN_QUOTES = [`"`, `'`, `&quot;`, `&apos;`, `&#34;`, `&#39;`, `%22`, `%2
8
8
  const ESCAPED_QUOTES = [`"`, `'`, `x22`, `x27`, `u0022`, `u0027`, `042`, `047`];
9
9
  const BACKSLASHES = String.raw `\\{1,${ESCAPE_DEPTH}}`;
10
10
  const QUOTE = `(?:${PLAIN_QUOTES.join("|")}|${BACKSLASHES}(?:${ESCAPED_QUOTES.join("|")}))`;
11
+ const WORD_START = `(?:(?<![A-Za-z0-9])|(?<=${QUOTE}))`;
11
12
  const AFTER_CLOSING_QUOTE = String.raw `(?:[\s,}\]:]|["']|$)`;
12
- const GLUED_RUN = `${VALUE_CHAR}*(?:(?<!\\\\)|(?!\\\\{0,${ESCAPE_DEPTH}}["']))`;
13
+ const VALUE_RUN_END = `(?:(?<!\\\\)|(?!\\\\{0,${ESCAPE_DEPTH}}["']))`;
14
+ const GLUED_RUN = `${VALUE_CHAR}*${VALUE_RUN_END}`;
13
15
  const pairedValue = (open, close) => `${open}(?!${AFTER_CLOSING_QUOTE})[^\\n]{0,${QUOTED_VALUE_MAX}}?${close}`;
14
16
  const QUOTED_VALUE = [
15
17
  ...PLAIN_QUOTES.map((q) => pairedValue(q, q)),
@@ -17,29 +19,35 @@ const QUOTED_VALUE = [
17
19
  `${QUOTE}(?!${AFTER_CLOSING_QUOTE})${GLUED_RUN}`,
18
20
  ].join("|");
19
21
  const BEARER_TAIL = `(?:(?!${QUOTE})\\S${GLUED_RUN}|(?<==)(?:${QUOTED_VALUE}))`;
22
+ const HEADER_PARAM = String.raw `=[ \t]*(\\{0,${ESCAPE_DEPTH}})(["'])(?:(?!(?<!\\)\2\3)[^\r\n])*(?:(?<!\\)\2\3(?=[\s,;]|\\{0,${ESCAPE_DEPTH}}["']|$)|(?=[\r\n]|$))`;
23
+ const HEADER_VALUE = String.raw `(?:[A-Za-z][A-Za-z0-9!#$%&*+.^_|~-]*[ \t]+)?(?:${HEADER_PARAM}|[^\s"',]|[ \t]*,[ \t]*(?=[^\s"',])|[ \t]+(?==)|(?<==)[ \t]+(?=[^\s"',]))+${VALUE_RUN_END}`;
24
+ const headed = (body, flags = "g") => new RegExp(`${WORD_START}${body}`, flags);
20
25
  export const CREDENTIAL_VOCABULARY = {
21
26
  values: [
22
- { id: "github-token", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: new RegExp(`(?<![A-Za-z0-9])gh[opsur][-_]${VENDOR_BODY}`, "g"), strict: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}\b/ },
23
- { id: "github-fine-grained-pat", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: new RegExp(`(?<![A-Za-z0-9])github_pat[-_]${VENDOR_BODY}`, "g"), strict: /\bgithub_pat_[A-Za-z0-9_]{60,}\b/ },
24
- { id: "openai-api-key", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: new RegExp(`(?<![A-Za-z0-9])sk-${VENDOR_BODY}`, "g"), strict: /\bsk-(?:ant-(?:api03-)?|proj-)?[A-Za-z0-9_]{20,}\b/ },
25
- { id: "stripe-secret-key", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: new RegExp(`(?<![A-Za-z0-9])(?:sk_|rk[-_])${VENDOR_BODY}`, "g"), strict: /\b[sr]k_(?:live|test)_[A-Za-z0-9]{24,}\b/ },
26
- { id: "stripe-publishable-key", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: new RegExp(`(?<![A-Za-z0-9])pk[-_]${VENDOR_BODY}`, "g") },
27
- { id: "gitlab-pat", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: /(?<![A-Za-z0-9])glpat-[A-Za-z0-9_-]{20,}/g },
28
- { id: "google-api-key", kind: "prefixed-token", source: "arg-summary", confidence: "high", match: /(?<![A-Za-z0-9])AIza[A-Za-z0-9_-]{20,}/g, strict: /\bAIza[0-9A-Za-z_-]{35}\b/ },
29
- { id: "slack-token", kind: "prefixed-token", source: "arg-summary", confidence: "high", match: /(?<![A-Za-z0-9])xox[baprs]-[A-Za-z0-9-]{10,}/g, strict: /\bxox[baprs]-(?:[A-Za-z0-9]+-){1,3}[A-Za-z0-9]{16,}\b/ },
30
- { id: "aws-access-key-id", kind: "prefixed-token", source: "arg-summary", confidence: "high", match: /(?<![A-Za-z0-9])(?:AKIA|ASIA)[0-9A-Z]{12,}/g, strict: /\bAKIA[0-9A-Z]{16}\b/ },
31
- { id: "huggingface-token", kind: "prefixed-token", source: "arg-summary", confidence: "high", match: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{20,}/g, strict: /\bhf_(?=[A-Za-z0-9]*[0-9])[A-Za-z0-9]{20,}\b/ },
32
- { id: "jwt", kind: "jwt", source: "arg-summary", confidence: "high", match: /(?<![A-Za-z0-9])eyJ[A-Za-z0-9._-]{20,}/g, strict: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]{16,}\b/ },
27
+ { id: "github-token", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: headed(`gh[opsur][-_]${VENDOR_BODY}`), strict: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}\b/ },
28
+ { id: "github-fine-grained-pat", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: headed(`github_pat[-_]${VENDOR_BODY}`), strict: /\bgithub_pat_[A-Za-z0-9_]{60,}\b/ },
29
+ { id: "openai-api-key", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: headed(`sk-${VENDOR_BODY}`), strict: /\bsk-(?:ant-(?:api03-)?|proj-)?[A-Za-z0-9_]{20,}\b/ },
30
+ { id: "stripe-secret-key", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: headed(`(?:sk_|rk[-_])${VENDOR_BODY}`), strict: /\b[sr]k_(?:live|test)_[A-Za-z0-9]{24,}\b/ },
31
+ { id: "stripe-publishable-key", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: headed(`pk[-_]${VENDOR_BODY}`) },
32
+ { id: "gitlab-pat", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: headed("glpat-[A-Za-z0-9_-]{20,}") },
33
+ { id: "npm-access-token", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: headed("npm_[A-Za-z0-9]{36,}") },
34
+ { id: "google-oauth-access-token", kind: "prefixed-token", source: "arg-summary", confidence: "high", pass: "vendor-prefixed", match: headed(String.raw `ya29\.[A-Za-z0-9_.-]{20,}`) },
35
+ { id: "google-api-key", kind: "prefixed-token", source: "arg-summary", confidence: "high", match: headed("AIza[A-Za-z0-9_-]{20,}"), strict: /\bAIza[0-9A-Za-z_-]{35}\b/ },
36
+ { id: "slack-token", kind: "prefixed-token", source: "arg-summary", confidence: "high", match: headed("xox[baprs]-[A-Za-z0-9-]{10,}"), strict: /\bxox[baprs]-(?:[A-Za-z0-9]+-){1,3}[A-Za-z0-9]{16,}\b/ },
37
+ { id: "aws-access-key-id", kind: "prefixed-token", source: "arg-summary", confidence: "high", match: headed("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{12,}"), strict: /\bAKIA[0-9A-Z]{16}\b/ },
38
+ { id: "huggingface-token", kind: "prefixed-token", source: "arg-summary", confidence: "high", match: headed("hf_[A-Za-z0-9]{20,}"), strict: /\bhf_(?=[A-Za-z0-9]*[0-9])[A-Za-z0-9]{20,}\b/ },
39
+ { id: "jwt", kind: "jwt", source: "arg-summary", confidence: "high", match: headed("eyJ[A-Za-z0-9._-]{20,}"), strict: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]{16,}\b/ },
33
40
  { id: "private-key", kind: "private-key-block", source: "arg-summary", confidence: "high", match: /-----BEGIN[ A-Z0-9]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END[ A-Z0-9]*PRIVATE KEY(?: BLOCK)?-----|$)/g, strict: /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----/ },
34
- { id: "bearer-token", kind: "bearer-token", source: "arg-summary", confidence: "high", match: new RegExp(String.raw `(?<![A-Za-z0-9])()[Bb][Ee][Aa][Rr][Ee][Rr]\s+[A-Za-z0-9._~+/-]{8,}=*(?:${BEARER_TAIL})?`, "g"), strict: /\bAuthorization:\s*Bearer\s+(?=[A-Za-z0-9._~+/-]*[0-9])[A-Za-z0-9._~+/-]{20,}=*/ },
41
+ { id: "bearer-token", kind: "bearer-token", source: "arg-summary", confidence: "high", match: headed(String.raw `()[Bb][Ee][Aa][Rr][Ee][Rr]\s+[A-Za-z0-9._~+/-]{8,}=*(?:${BEARER_TAIL})?`), strict: /\bAuthorization:\s*Bearer\s+(?=[A-Za-z0-9._~+/-]*[0-9])[A-Za-z0-9._~+/-]{20,}=*/ },
42
+ { id: "authorization-header", kind: "authorization-header", source: "arg-summary", confidence: "high", match: headed(String.raw `(authorization(?:\\{0,${ESCAPE_DEPTH}}["'])?[ \t]*[=:][ \t]*(?:\\{0,${ESCAPE_DEPTH}}["'])?)${HEADER_VALUE}`, "gi") },
35
43
  { id: "azure-storage-account-key", kind: "prefixed-token", source: "arg-summary", confidence: "high", match: /(AccountKey=)[A-Za-z0-9+/]{40,}={0,2}/g, strict: /AccountKey=[A-Za-z0-9+/]{40,}={0,2}/ },
36
- { id: "keyword-value", kind: "keyword-value", source: "arg-summary", confidence: "medium", match: new RegExp(String.raw `(?<![A-Za-z0-9])((?:token|secret|key|password|passwd|credential|authorization)["']?\s*[=:]\s*["']?)${VALUE_CHAR}{4,}`, "gi") },
44
+ { id: "keyword-value", kind: "keyword-value", source: "arg-summary", confidence: "medium", match: headed(String.raw `((?:token|secret|key|password|passwd|credential)["']?\s*[=:]\s*["']?)${VALUE_CHAR}{4,}${VALUE_RUN_END}`, "gi") },
37
45
  { id: "url-userinfo", kind: "url-credentials", source: "untrusted-egress", confidence: "medium", match: new RegExp(String.raw `(${URI_SCHEME_SOURCE}:\/\/)((?:[^/\s"'@]*@)+)`, "gi") },
38
46
  { id: "signed-url-param", kind: "signed-url-param", source: "untrusted-egress", confidence: "high", match: /([?&#](?:sig|signature|sas|code)=)[^&\s"'#]+/gi },
39
47
  ],
40
48
  words: [
41
49
  ...["TOKEN", "SECRET", "PASSWORD", "PASSWD", "PASSPHRASE", "KEY"].map((word) => ({ word, position: "leading" })),
42
- { word: "AUTH", position: "leading", recordKeys: ["AUTHORIZATION"] },
50
+ { word: "AUTH", position: "leading", recordKeys: ["AUTHORIZATION", "AUTHENTICATION", "OAUTH", "AUTHN", "AUTHZ"] },
43
51
  ...["COOKIE", "PAT", "DSN", "WEBHOOK", "CREDENTIAL", "CREDENTIALS", "CREDS", "APIKEY", "ACCESSKEY", "SECRETKEY", "ACCOUNTKEY", "PRIVATEKEY", "AUTHKEY", "SSHKEY", "SIGNINGKEY", "MASTERKEY", "DEPLOYKEY", "ENCRYPTIONKEY", "PGPASSWORD", "SSHPASS"].map((word) => ({ word, position: "leading" })),
44
52
  ...["KEY", "SECRET", "PASSWORD", "CREDENTIAL"].map((word) => ({ word, position: "plural" })),
45
53
  { word: "TOKEN", position: "counted-plural" },
@@ -109,10 +117,11 @@ function credentialWordPattern(countWords, rows) {
109
117
  ...(separated.length > 0 ? [`_(${separated.join("|")})`] : []),
110
118
  ...(embedded.length > 0 ? [`(${embedded.join("|")})`] : []),
111
119
  ];
112
- return { credentialWord: new RegExp(`(${positions.join("|")})(?=$|[_0-9])`, "i"), countedPlural };
120
+ const segmentWord = `(?:${[...at("leading"), ...at("plural").map((w) => `${w}S`), ...separated, countedPlural].join("|")})`;
121
+ return { credentialWord: new RegExp(`(${positions.join("|")})(?=$|[_0-9])`, "i"), segmentWord };
113
122
  }
114
123
  export function envNameView(vocabulary) {
115
- const { credentialWord, countedPlural } = credentialWordPattern(vocabulary.countWords, vocabulary.words);
124
+ const { credentialWord, segmentWord } = credentialWordPattern(vocabulary.countWords, vocabulary.words);
116
125
  return {
117
126
  exactNames: new Set(vocabulary.exactEnvNames.flatMap((name) => [name, `INPUT_${name}`])),
118
127
  nonSecretExactNames: new Set(vocabulary.nonSecretExactNames),
@@ -120,7 +129,7 @@ export function envNameView(vocabulary) {
120
129
  nonCredentialPrefix: vocabulary.nonCredentialPrefix,
121
130
  credentialWord,
122
131
  connectionStringName: vocabulary.connectionStringName,
123
- countedPlural,
132
+ segmentWord,
124
133
  tokenPrefix: vocabulary.tokenPrefix,
125
134
  };
126
135
  }