@sema-agent/core 7.50.2 → 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 (188) hide show
  1. package/CHANGELOG.md +263 -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 +81 -15
  40. package/dist/core/gate-outcome.js +168 -67
  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 +2 -2
  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 +4 -4
  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.51.0.json +2421 -0
@@ -1,10 +1,11 @@
1
- import { type ProtocolId } from "./protocol-table.js";
1
+ import { type McpInstructionsEntry, type PromptSectionSeat } from "./prompt-section-seat.js";
2
2
  import { McpConnectionPool } from "./mcp-connection-pool.js";
3
3
  import { type McpDelivered, type McpFailureKind, type McpServerLiveness } from "./mcp-failure.js";
4
4
  import type { AgentTool } from "../internal/harness-types.js";
5
5
  import type { ImageContent, TextContent } from "../internal/llm.js";
6
6
  import { type McpImageResizer } from "./image-downsample.js";
7
7
  import type { McpServerSpec, OnElicit, ToolEffect } from "./types.js";
8
+ import type { McpRevocationLedger } from "./mcp-server-spec.js";
8
9
  import { type ReminderDisclosureCounts } from "./reminder-disclosure.js";
9
10
  export { resolveMcpDeclaredResultSize } from "./mcp-tool-meta.js";
10
11
  /**
@@ -100,13 +101,11 @@ export interface MaterializedMcp {
100
101
  toolAxes: McpToolAxis[];
101
102
  /**
102
103
  * MCP server `instructions` (design/64 §17.3, CC `# MCP Server Instructions`): the optional guidance a
103
- * connected server returns from `initialize`. The Runner injects these into the (stable) system prompt so
104
- * the model uses the server's tools correctly. One entry per server that provided non-empty instructions.
104
+ * connected server returns from `initialize`, after the host's disposition (`PromptProvider.section`,
105
+ * applied once at the dial: a dropped entry is absent, a replaced one carries `author: "host"`). The
106
+ * Runner injects these into the (stable) system prompt. One entry per server whose entry survived.
105
107
  */
106
- serverInstructions: Array<{
107
- server: string;
108
- text: string;
109
- }>;
108
+ serverInstructions: McpInstructionsEntry[];
110
109
  /**
111
110
  * G1 通告层续批 (CC `mcp_instructions_delta` parity) — the run-scoped instruction-delta ref the run
112
111
  * loop drains at turn boundaries (`spec.attachments.mcpInstructions` opt-in; `newTools` consumption
@@ -121,15 +120,12 @@ export interface MaterializedMcp {
121
120
  * CC deviation: prefix-cache-correct).
122
121
  */
123
122
  instructionsDelta: {
124
- pendingAdds: Array<{
125
- server: string;
126
- text: string;
127
- }>;
123
+ pendingAdds: McpInstructionsEntry[];
128
124
  pendingRemovals: string[];
129
125
  };
130
126
  /**
131
127
  * The OPERATOR warning lane for this materialization (the Runner forwards each entry to
132
- * `onError(phase:"mcp")`). Empty when every server connected and listed completely. Two populations,
128
+ * `onError(phase:"mcp")`). Empty when every server connected and listed completely. Populations,
133
129
  * distinguishable by the `code` each Error carries — a consumer must NOT read mere presence as "this
134
130
  * server was skipped":
135
131
  * - `mcp.server_unavailable` — a server that failed to connect / list its tools and was SKIPPED
@@ -138,7 +134,9 @@ export interface MaterializedMcp {
138
134
  * - `mcp.listing_incomplete` — a server that DID connect but whose `tools/list` pagination walk did
139
135
  * not reach the end ({@link McpListingIncomplete}): its tools past the stopping point are not
140
136
  * mounted this task, which is a capability loss the operator must see rather than infer from a
141
- * short tool list.
137
+ * short tool list;
138
+ * - `prompt.section_answer_invalid` — the host's disposition of a CONNECTED server's instructions
139
+ * could not be read, so the entry was included as computed.
142
140
  */
143
141
  warnings: Error[];
144
142
  /**
@@ -471,28 +469,6 @@ export * from "./image-downsample.js";
471
469
  * type, ≤10 object entries then `, ...`.
472
470
  */
473
471
  export declare function inferCompactSchema(value: unknown, depth?: number): string;
474
- /** The per-peer HTTP auth declaration every protocol spec carries in the same two fields — the static
475
- * headers the deployment configured, and the header name the per-task principal rides on (design/62).
476
- * Named rather than inlined because it is a cross-module signature: {@link McpServerSpec}'s http
477
- * transport and A2A's peer spec both satisfy it, and the resolver below serves both. */
478
- export interface ProtocolHttpAuth {
479
- headers?: Record<string, string>;
480
- principalHeader?: string;
481
- }
482
- /**
483
- * Resolve the HTTP request headers for one remote PEER of `protocol`, injecting the per-task end-user
484
- * principal (design/62). The principal is added **only** when the spec declares a `principalHeader` AND a
485
- * `principal` is present; it is injected **last** so it overrides any same-named static header; an absent
486
- * principal sends no such header (the remote must then default to deny/public, never admin). Pure +
487
- * exported for testing and so a consumer can verify what its config sends.
488
- *
489
- * Shared across protocols (A2A reuses it verbatim), which is why the failure names its protocol from the
490
- * ARGUMENT rather than from a hard-coded literal: a copy of this guard per protocol is exactly the
491
- * "second mint" shape whose copies stop tracking each other. Callers must invoke it INSIDE their per-peer
492
- * boundary — the throw is one peer's configuration defect, and the surrounding per-peer catch is what
493
- * turns it into a skipped peer instead of a failed task.
494
- */
495
- export declare function resolveProtocolHttpHeaders(protocol: ProtocolId, t: ProtocolHttpAuth, principal?: string): Record<string, string> | undefined;
496
472
  /** `kind` is the dial's ONE read of `spec.transport.kind` (taken by the caller beside the dial and threaded
497
473
  * here, to the idle bucket and to the failure classifier alike): a nested `transport` object is the
498
474
  * caller's, and a getter that answered differently to two readers would otherwise connect over one
@@ -624,13 +600,12 @@ export declare function materializeMcpTools(specs: McpServerSpec[], principal?:
624
600
  reminderDisclosure?: {
625
601
  reminderMark?: string;
626
602
  counts?: ReminderDisclosureCounts;
627
- }, mcpRevocations?: {
628
- isRevoked(serverName: string): boolean;
603
+ }, mcpRevocations?: McpRevocationLedger & {
629
604
  onProbeFailure?: (error: unknown) => void;
630
605
  }, poolOpts?: {
631
606
  pool: McpConnectionPool;
632
607
  ownsPool?: boolean;
633
- }): Promise<MaterializedMcp>;
608
+ }, promptSections?: PromptSectionSeat): Promise<MaterializedMcp>;
634
609
  /**
635
610
  * Fold the caller's AUTHORITATIVE per-tool override (design F: caller = trust root) over the server-hint axis.
636
611
  * Unlike server hints, the caller may RAISE or LOWER any axis: `effect` sets the repeat-safety class (lower to
package/dist/core/mcp.js CHANGED
@@ -1,7 +1,9 @@
1
- import { redactSecrets } from "./untrusted-egress.js";
1
+ import { egressText } from "./untrusted-egress.js";
2
2
  import { __resetEnvKnobAnnouncements } from "./env-knob-announce.js";
3
3
  import { resolveEnvBudget } from "./env-budget.js";
4
4
  import { MCP_NAMESPACE } from "./protocol-table.js";
5
+ import { resolveProtocolHttpHeaders } from "./protocol-http-auth.js";
6
+ import { disposeSectionEntry, sectionAnswerWarning } from "./prompt-section-seat.js";
5
7
  import { findNamespacePrefixCollision, mintNamespacePrefix, mintNamespacedToolName, normalizeNameSegment } from "./protocol-naming.js";
6
8
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
9
  import { McpConnectionPool } from "./mcp-connection-pool.js";
@@ -179,7 +181,7 @@ export function collapseMcpErrorPrefix(message) {
179
181
  function sanitizeMcpErrorTextInPlace(err) {
180
182
  if (!(err instanceof Error))
181
183
  return;
182
- const sanitized = redactSecrets(err instanceof McpError ? collapseMcpErrorPrefix(err.message) : err.message);
184
+ const sanitized = egressText("same-principal", err instanceof McpError ? collapseMcpErrorPrefix(err.message) : err.message);
183
185
  if (sanitized !== err.message)
184
186
  err.message = sanitized;
185
187
  }
@@ -190,7 +192,7 @@ function namedMcpFailureText(err) {
190
192
  }
191
193
  function remoteErrorText(err, opts) {
192
194
  const raw = thrownText(err);
193
- return redactSecrets(opts?.collapse === true ? collapseMcpErrorPrefix(raw) : raw);
195
+ return egressText("same-principal", opts?.collapse === true ? collapseMcpErrorPrefix(raw) : raw);
194
196
  }
195
197
  function writeEffectWarning(writeEffect) {
196
198
  return writeEffect
@@ -372,14 +374,6 @@ export function inferCompactSchema(value, depth = 2) {
372
374
  }
373
375
  return typeof value;
374
376
  }
375
- export function resolveProtocolHttpHeaders(protocol, t, principal) {
376
- if (t.principalHeader && principal !== undefined) {
377
- if (/[\r\n]/.test(principal))
378
- throw new Error(`${protocol}: principal must not contain CR/LF (header injection guard)`);
379
- return { ...t.headers, [t.principalHeader]: principal };
380
- }
381
- return t.headers;
382
- }
383
377
  export const declaredEndpointFetch = (url, init) => fetch(url, { ...init, redirect: "manual" });
384
378
  function buildTransport(spec, principal, kind) {
385
379
  const t = spec.transport;
@@ -408,8 +402,10 @@ export async function mapContent(content, serverName, imageResizer) {
408
402
  if (item.type === "text" && typeof item.text === "string") {
409
403
  out.push({ type: "text", text: item.text });
410
404
  }
411
- else if (item.type === "image" && item.data && item.mimeType) {
412
- out.push(await boundedImageBlock(item.data, item.mimeType, serverName, resizer));
405
+ else if (item.type === "image") {
406
+ const data = typeof item.data === "string" ? item.data : "";
407
+ const mimeType = typeof item.mimeType === "string" ? item.mimeType : "";
408
+ out.push(data !== "" && mimeType !== "" ? await boundedImageBlock(data, mimeType, serverName, resizer) : { type: "image", data, mimeType });
413
409
  }
414
410
  else if (item.type === "audio" && typeof item.data === "string" && item.data) {
415
411
  out.push(await blobToTextBlock(item.data, item.mimeType, serverName, `[Audio from ${serverName ?? "MCP server"}] `));
@@ -660,7 +656,7 @@ export function mcpToolSchemaProblem(schema) {
660
656
  }
661
657
  return undefined;
662
658
  }
663
- export async function materializeMcpTools(specs, principal, onElicit, imageResizer, reminderDisclosure, mcpRevocations, poolOpts) {
659
+ export async function materializeMcpTools(specs, principal, onElicit, imageResizer, reminderDisclosure, mcpRevocations, poolOpts, promptSections) {
664
660
  const entries = snapshotMcpEntries(specs);
665
661
  {
666
662
  const collision = findNamespacePrefixCollision(MCP_NAMESPACE, entries.map((s) => s.name));
@@ -678,7 +674,8 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
678
674
  if (mcpRevocations === undefined)
679
675
  return false;
680
676
  try {
681
- const r = mcpRevocations.isRevoked(serverName);
677
+ const fingerprint = entries.find((e) => e.name === serverName)?.fingerprint;
678
+ const r = fingerprint === undefined ? mcpRevocations.isRevoked(serverName) : mcpRevocations.isRevoked(serverName, { fingerprint });
682
679
  if (typeof r !== "boolean") {
683
680
  throw new Error(`isRevoked returned ${r instanceof Promise ? "a Promise — the ledger seat is SYNCHRONOUS by contract" : `a non-boolean (${typeof r})`}`);
684
681
  }
@@ -751,6 +748,8 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
751
748
  droppedTools.push({ server: inlineUntrusted(spec.name, 160), ...d });
752
749
  for (const a of s.schemaAdvisories)
753
750
  raised.push(schemaAdvisoryWarning(spec.name, a.tool, a.reason));
751
+ if (s.instructionsDefect !== undefined)
752
+ raised.push(sectionAnswerWarning(s.instructionsDefect));
754
753
  if (s.toolsCapabilityAbsent === true && spec.allowTools !== undefined && spec.allowTools.length > 0) {
755
754
  raised.push(toolsCapabilityAbsentWarning(spec.name));
756
755
  }
@@ -791,7 +790,7 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
791
790
  hadPendingAdd = true;
792
791
  }
793
792
  if (after !== undefined) {
794
- instructionsDelta.pendingAdds.push({ server: name, text: after });
793
+ instructionsDelta.pendingAdds.push(after);
795
794
  return;
796
795
  }
797
796
  if (connected)
@@ -799,7 +798,7 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
799
798
  if (before !== undefined || owedRemoval || hadPendingAdd)
800
799
  instructionsDelta.pendingRemovals.push(name);
801
800
  };
802
- const settled = await Promise.allSettled(entries.map((spec, i) => connectServer(spec, principal, onElicit, imageResizer, mcpDisclosure, isServerRevoked, dialKinds[i], poolOpts?.pool)));
801
+ const settled = await Promise.allSettled(entries.map((spec, i) => connectServer(spec, principal, onElicit, imageResizer, mcpDisclosure, isServerRevoked, dialKinds[i], poolOpts?.pool, { sections: promptSections })));
803
802
  try {
804
803
  for (let i = 0; i < entries.length; i++) {
805
804
  const r = settled[i];
@@ -808,9 +807,8 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
808
807
  const s = r.value;
809
808
  tools.push(...s.tools);
810
809
  toolAxes.push(...s.axes);
811
- if (s.instructions) {
812
- serverInstructions.push({ server: spec.name, text: s.instructions });
813
- }
810
+ if (s.instructions)
811
+ serverInstructions.push(s.instructions);
814
812
  commitConnectedServer(spec, s, dialKinds[i]);
815
813
  }
816
814
  else {
@@ -857,7 +855,7 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
857
855
  const dialKind = readDialKind(spec);
858
856
  let connected;
859
857
  try {
860
- connected = await connectServer(spec, principal, onElicit, imageResizer, mcpDisclosure, isServerRevoked, dialKind, poolOpts?.pool, { fresh: true });
858
+ connected = await connectServer(spec, principal, onElicit, imageResizer, mcpDisclosure, isServerRevoked, dialKind, poolOpts?.pool, { fresh: true, sections: promptSections });
861
859
  }
862
860
  catch (err) {
863
861
  const { kind, delivered, httpStatus } = classifyMcpFailure(err, { phase: "connect", transport: dialKind });
@@ -1523,6 +1521,10 @@ async function listToolsLenient(client, options) {
1523
1521
  return { tools: walk.items, ...(walk.incomplete !== undefined ? { incomplete: walk.incomplete } : {}) };
1524
1522
  }
1525
1523
  async function connectServer(spec, principal, onElicit, imageResizer, reminderDisclosure, isServerRevoked, transportKind, pool, dialOpts) {
1524
+ const fp = spec.fingerprint;
1525
+ if (fp !== undefined && !(typeof fp === "string" && fp.length > 0 && fp.length <= 128)) {
1526
+ throw Object.assign(new Error(`MCP server "${inlineUntrusted(spec.name, 160)}": fingerprint must be a non-empty string of at most 128 characters (got ${typeof fp === "string" ? `${fp.length} characters` : typeof fp}) — not dialed`), { code: "config.mcp_fingerprint_invalid" });
1527
+ }
1526
1528
  const elicitOn = spec.elicitation === true && onElicit !== undefined;
1527
1529
  const health = { dead: false, pendingElicitations: 0, lastElicitationClosedAt: 0 };
1528
1530
  const announce = {};
@@ -1588,7 +1590,9 @@ async function connectServer(spec, principal, onElicit, imageResizer, reminderDi
1588
1590
  }
1589
1591
  : undefined;
1590
1592
  const rawInstructions = client.getInstructions();
1591
- const instructions = rawInstructions && rawInstructions.trim() ? sanitizeUntrustedText(rawInstructions.trim()) : undefined;
1593
+ const sanitized = rawInstructions && rawInstructions.trim() ? sanitizeUntrustedText(rawInstructions.trim()) : undefined;
1594
+ const disposed = sanitized === undefined ? undefined : disposeSectionEntry(dialOpts?.sections, { section: "core/discovery.mcp-instructions", server: spec.name, text: sanitized, mountedTools: serverTools.map((t) => t.name) });
1595
+ const instructions = disposed?.kept === undefined ? undefined : { server: spec.name, ...disposed.kept };
1592
1596
  const info = client.getServerVersion();
1593
1597
  const status = {
1594
1598
  name: spec.name,
@@ -1611,6 +1615,7 @@ async function connectServer(spec, principal, onElicit, imageResizer, reminderDi
1611
1615
  ...(listed.toolsCapabilityAbsent === true ? { toolsCapabilityAbsent: true } : {}),
1612
1616
  listedTools: listed.tools,
1613
1617
  ...(instructions ? { instructions } : {}),
1618
+ ...(disposed?.defect !== undefined ? { instructionsDefect: disposed.defect } : {}),
1614
1619
  ...(listed.incomplete !== undefined ? { listingIncomplete: listed.incomplete } : {}),
1615
1620
  ...(resourceInfo && (resourceInfo.listAllowed || resourceInfo.readAllowed) ? { resourceServer: { server: spec.name, client, health, transportKind: idleKindOf(transportKind), ...resourceInfo } } : {}),
1616
1621
  };
@@ -1761,7 +1766,7 @@ function intakeListedTools(listed, spec, client, health, imageResizer, reminderD
1761
1766
  const errScLine = structuredContentErrorLine(res.structuredContent, parts.join("\n"));
1762
1767
  if (errScLine !== undefined)
1763
1768
  parts.push(errScLine);
1764
- const body = truncateMcpErrorText(redactSecrets(parts.join("\n").trim()));
1769
+ const body = truncateMcpErrorText(egressText("same-principal", parts.join("\n").trim()));
1765
1770
  const msg = body
1766
1771
  ? `MCP tool ${inlineUntrusted(remoteName)} reported an error. The server's error content follows as external/untrusted data:\n${delimitUntrusted(`${spec.name} tool error`, body)}`
1767
1772
  : `MCP tool ${inlineUntrusted(remoteName)} reported an error`;
@@ -24,6 +24,31 @@ export declare function mediaBudgetBytes(cap: number | undefined): number;
24
24
  * (`0` and `Infinity` both mean "no budget"). A non-number, `NaN` or a negative is refused (`config.invalid_argument`),
25
25
  * never read as "no budget". */
26
26
  export declare function checkedMediaByteCap(value: unknown, seat: string): void;
27
+ /** A media block: an image or a document whose payload is a base64 string. */
28
+ export type MediaBlock = ImageContent | DocumentContent;
29
+ /** The media type a block claims — its `type`, read once, when that is a media type — or `undefined` for any other value. */
30
+ export declare function claimedMediaType(block: unknown): MediaBlock["type"] | undefined;
31
+ /** Whether a value is a media block: its `type` is a media type and its `data` a string. A block of a media type with no
32
+ * string `data` is not one — a {@link MediaDescriptor} is such a block. */
33
+ export declare function isMediaBlock(block: unknown): block is MediaBlock;
34
+ /** What media becomes where its bytes do not ride: one block's identity (its type, its media type when it can be one, its
35
+ * decoded size), or one summary of every block from there on. Never a string, never the bytes. */
36
+ export type MediaDescriptor = {
37
+ type: MediaBlock["type"];
38
+ mimeType?: string;
39
+ bytes: number;
40
+ truncated: true;
41
+ } | {
42
+ type: "omitted";
43
+ blocks: number;
44
+ bytes: number;
45
+ truncated: true;
46
+ };
47
+ /** A media block's descriptor. The media type is echoed only when it can be one (RFC 6838: type and subtype are at most
48
+ * 127 characters each); `bytes` is {@link imageBytes}. */
49
+ export declare function mediaBlockDescriptor(block: MediaBlock): MediaDescriptor;
50
+ /** The one summary of media blocks that leave together: how many, and their decoded size. */
51
+ export declare function omittedMediaDescriptor(blocks: readonly MediaBlock[]): MediaDescriptor;
27
52
  /** Decoded byte size of a base64 media payload (4 base64 chars ≈ 3 bytes). Approximate — fine for a budget.
28
53
  * PDF read: `document` blocks (base64 PDFs) count against the SAME aggregate budget — same class of inline
29
54
  * media bytes, same unbounded-accumulation failure mode. */
@@ -1,4 +1,5 @@
1
1
  import { describeRejectedValue } from "./tool-search-setting.js";
2
+ import { readMember } from "./thrown-value.js";
2
3
  export const AGGREGATE_MEDIA_BUDGET_BYTES = 30 * 1024 * 1024;
3
4
  export function mediaBudgetBytes(cap) {
4
5
  const limit = cap ?? AGGREGATE_MEDIA_BUDGET_BYTES;
@@ -12,6 +13,21 @@ export function checkedMediaByteCap(value, seat) {
12
13
  throw e;
13
14
  }
14
15
  const MEDIA_STRIPPED_MARKER = "[media removed to fit the request media budget — re-read the file if you still need to see it]";
16
+ const MEDIA_BLOCK_TYPES = new Set(["image", "document"]);
17
+ export function claimedMediaType(block) {
18
+ const type = readMember(block, "type");
19
+ return MEDIA_BLOCK_TYPES.has(type) ? type : undefined;
20
+ }
21
+ const mediaTyped = (b) => claimedMediaType(b) !== undefined;
22
+ export function isMediaBlock(block) {
23
+ return mediaTyped(block) && typeof readMember(block, "data") === "string";
24
+ }
25
+ export function mediaBlockDescriptor(block) {
26
+ return { type: block.type, ...(typeof block.mimeType === "string" && block.mimeType.length <= 255 ? { mimeType: block.mimeType } : {}), bytes: imageBytes(block), truncated: true };
27
+ }
28
+ export function omittedMediaDescriptor(blocks) {
29
+ return { type: "omitted", blocks: blocks.length, bytes: blocks.reduce((n, b) => n + imageBytes(b), 0), truncated: true };
30
+ }
15
31
  export function imageBytes(b) {
16
32
  return typeof b.data === "string" ? Math.floor((b.data.length * 3) / 4) : 0;
17
33
  }
@@ -28,7 +44,7 @@ export function capAggregateMediaBytes(messages, opts) {
28
44
  const blocks = blocksOf(m);
29
45
  if (blocks)
30
46
  for (const b of blocks)
31
- if (b.type === "image" || b.type === "document")
47
+ if (mediaTyped(b))
32
48
  total += imageBytes(b);
33
49
  }
34
50
  if (total <= limitBytes)
@@ -43,7 +59,7 @@ export function capAggregateMediaBytes(messages, opts) {
43
59
  continue;
44
60
  let changed = false;
45
61
  const next = blocks.map((b) => {
46
- if (remaining > limitBytes && (b.type === "image" || b.type === "document")) {
62
+ if (remaining > limitBytes && mediaTyped(b)) {
47
63
  const bytes = imageBytes(b);
48
64
  remaining -= bytes;
49
65
  removedBytes += bytes;
@@ -119,7 +119,7 @@ async function refuseUnlessWriteAllowed(judge, target, consequence) {
119
119
  why = `the write judge could not answer${where !== undefined ? ` at ${where}` : ""}${text(answer.cause) !== undefined ? ` (${text(answer.cause)})` : ""}${text(answer.reason) !== undefined ? `: ${text(answer.reason)}` : ""}, so whether a standing rule forbids this write is unknown`;
120
120
  else
121
121
  why = "the write judge answered something outside its closed set of verdicts";
122
- throw new ConsolidationRefusedError(CONSOLIDATION_WRITE_REFUSED_CODE, `memory consolidation refused for scope ${inlineUntrusted(target.scope, 80)} (${inlineUntrusted(target.path, 200)}): ${why}${consequence !== undefined ? ` — ${consequence}` : ""}`);
122
+ throw new ConsolidationRefusedError(CONSOLIDATION_WRITE_REFUSED_CODE, `memory consolidation refused for scope ${inlineUntrusted(target.scope, 80)}: ${why}${consequence !== undefined ? ` — ${consequence}` : ""} (library ${inlineUntrusted(target.path, 200)})`);
123
123
  }
124
124
  const MAX_THROTTLE_WAITS_PER_SNAPSHOT = 4;
125
125
  const MAX_BUSY_WAITS = 2;
@@ -19,6 +19,7 @@ import type { PermissionResult, ToolCallRequest, ToolPolicy } from "./tool-polic
19
19
  import { type ToolRoster } from "./tool-roster.js";
20
20
  import { type PermissionMode } from "./ask-origin.js";
21
21
  import type { RuntimeCaps } from "./runtime-caps.js";
22
+ import type { RunnerDeps } from "./runner-deps.js";
22
23
  import { recordedModeSeed } from "./checkpoint-store.js";
23
24
  import type { TaskSpec } from "./task-spec.js";
24
25
  /** WHY an effective mode is not the requested one: the deployment's deny bit (`RuntimeCaps.bypassPermissions` /
@@ -196,6 +197,20 @@ export interface PermissionModeArming {
196
197
  * or a rejection from it is contained. */
197
198
  readonly onArmFault?: (error: unknown) => void;
198
199
  }
200
+ /**
201
+ * The auto-mode classifier FACE a leg arms from — ONE reading for the arming fact ({@link PermissionModeArming.autoFacePresent}),
202
+ * the arming itself and the face this leg hands its children: the leg's own deployment face (`RunnerDeps.autoMode`), else
203
+ * the face its ancestor armed from, riding the trusted chain (`InheritedGate.autoModeFace`). A delegated child whose own
204
+ * runner wires no face is classified from its ancestor's deployment face — each of a subagent's actions goes through the
205
+ * same decision order as the parent's — on the child's own transcript and model; the child's own face wins where it has one.
206
+ */
207
+ export declare function autoModeFaceOf(deps: Pick<RunnerDeps, "autoMode">, inherited: InheritedAutoModeFace | undefined): RunnerDeps["autoMode"];
208
+ /** The chain's carriage of that face (`InheritedGate` extends it). */
209
+ export interface InheritedAutoModeFace {
210
+ /** The auto-mode face the spawning leg armed from — opaque half, never serialized; present only when that leg's deps (or its own
211
+ * chain) carried one. A child whose runner wires no face of its own arms its classifier from it ({@link autoModeFaceOf}). */
212
+ autoModeFace?: RunnerDeps["autoMode"];
213
+ }
199
214
  /** A live ceiling: the parent leg's own controller (`current` tracks the parent's steers), or a recorded word. A ceiling
200
215
  * that can start something on a read (a controller, the engine's leg ceiling) also answers `observed` — the same word,
201
216
  * read without starting anything; one without it is read through `current` on an observation too. */
@@ -219,8 +234,11 @@ export declare class PermissionModeController {
219
234
  private arming;
220
235
  private readonly onFold;
221
236
  private prePlan;
237
+ /** `onFold` hears every fold with the word the fold FOLDED — what a fold notice names: the leg's own request for the
238
+ * ceiling fold (the word the parent's mode outranks); for the deployment's folds the declared word (the request after
239
+ * the ceiling, or — a leg that requested nothing — the word it inherited). */
222
240
  constructor(requestedBySpec: PermissionMode | undefined, ceiling: PermissionModeCeiling | undefined, opts?: {
223
- onFold?: (receipt: PermissionModeReceipt) => void;
241
+ onFold?: (receipt: PermissionModeReceipt, folded: PermissionMode) => void;
224
242
  prePlanMode?: PermissionMode;
225
243
  });
226
244
  /** The declared word the leg had when a steer (or the model's `EnterPlanMode`) last moved it into `plan`, while it is
@@ -249,6 +267,12 @@ export declare class PermissionModeController {
249
267
  /** The three-member answer as it stands right now. */
250
268
  receipt(): PermissionModeReceipt;
251
269
  private receiptOf;
270
+ /** The receipt and, when it records a fold, the word that fold folded — one declaration read for both. */
271
+ private answerOf;
272
+ /** Hand a fold to the observer with the word it folded (an answer without a fold says nothing). */
273
+ private announce;
274
+ /** The construction-time announcement: a fold the leg STARTS with (a ceiling fold, before any arming is bound). */
275
+ announceStart(): PermissionModeReceipt;
252
276
  /** The ceiling's source moved to another leg (the parent session's next leg); `before` is this leg's receipt under the
253
277
  * previous one. A ceiling fold the move produced — or changed — is announced, as the construction-time fold is. */
254
278
  ceilingRebound(before: PermissionModeReceipt): void;
@@ -131,6 +131,9 @@ function screenAs(field, screen) {
131
131
  throw e;
132
132
  }
133
133
  }
134
+ export function autoModeFaceOf(deps, inherited) {
135
+ return deps.autoMode ?? inherited?.autoModeFace;
136
+ }
134
137
  export class PermissionModeController {
135
138
  requestedBySpec;
136
139
  ceiling;
@@ -183,6 +186,9 @@ export class PermissionModeController {
183
186
  return this.receiptOf(false);
184
187
  }
185
188
  receiptOf(observe) {
189
+ return this.answerOf(observe).receipt;
190
+ }
191
+ answerOf(observe) {
186
192
  const { effective: declared, clamped } = this.declaration(observe);
187
193
  const requested = this.requested;
188
194
  const base = requested !== undefined ? { requested } : {};
@@ -197,29 +203,39 @@ export class PermissionModeController {
197
203
  ? "operator_deny"
198
204
  : undefined;
199
205
  if (armingFold !== undefined)
200
- return { ...base, effective: "default", foldedBy: armingFold };
206
+ return { receipt: { ...base, effective: "default", foldedBy: armingFold }, folded: declared };
201
207
  if (clamped)
202
- return { ...base, effective: declared, foldedBy: "inherited_ceiling" };
203
- return { ...base, effective: declared };
208
+ return { receipt: { ...base, effective: declared, foldedBy: "inherited_ceiling" }, folded: requested ?? declared };
209
+ return { receipt: { ...base, effective: declared } };
210
+ }
211
+ announce(answer) {
212
+ if (answer.receipt.foldedBy !== undefined && answer.folded !== undefined)
213
+ this.onFold?.(answer.receipt, answer.folded);
214
+ }
215
+ announceStart() {
216
+ const answer = this.answerOf(false);
217
+ this.announce(answer);
218
+ return answer.receipt;
204
219
  }
205
220
  ceilingRebound(before) {
206
- const after = this.receipt();
207
- if (after.foldedBy === "inherited_ceiling" && (before.foldedBy !== "inherited_ceiling" || before.effective !== after.effective))
208
- this.onFold?.(after);
221
+ const after = this.answerOf(false);
222
+ if (after.receipt.foldedBy === "inherited_ceiling" && (before.foldedBy !== "inherited_ceiling" || before.effective !== after.receipt.effective))
223
+ this.announce(after);
209
224
  }
210
225
  bindArming(arming) {
211
226
  const before = this.receipt();
212
227
  this.arming = arming;
213
- const receipt = this.receipt();
214
- if (receipt.foldedBy !== undefined && receipt.foldedBy !== before.foldedBy)
215
- this.onFold?.(receipt);
216
- return receipt;
228
+ const answer = this.answerOf(false);
229
+ if (answer.receipt.foldedBy !== before.foldedBy)
230
+ this.announce(answer);
231
+ return answer.receipt;
217
232
  }
218
233
  async steer(mode) {
219
234
  const entry = { word: mode };
220
235
  const before = this.declared;
221
236
  this.steers.push(entry);
222
- const receipt = this.receipt();
237
+ const answer = this.answerOf(false);
238
+ const receipt = answer.receipt;
223
239
  const arming = this.arming;
224
240
  if (receipt.effective === "auto" && arming?.armAuto !== undefined) {
225
241
  this.armedForAuto = true;
@@ -243,8 +259,7 @@ export class PermissionModeController {
243
259
  else if (before !== "plan")
244
260
  this.prePlan = before;
245
261
  }
246
- if (receipt.foldedBy !== undefined)
247
- this.onFold?.(receipt);
262
+ this.announce(answer);
248
263
  return receipt;
249
264
  }
250
265
  }
@@ -0,0 +1,18 @@
1
+ /** How a refusal names the object being received. */
2
+ export interface OwnPropertyReceiptWords {
3
+ /** The object, e.g. `RunnerDeps`. */
4
+ readonly subject: string;
5
+ /** One of its properties, e.g. `seat`. */
6
+ readonly unit: string;
7
+ /** When the one read happens, e.g. `at new Runner`. */
8
+ readonly moment: string;
9
+ }
10
+ /** The receipt's coded refusal. */
11
+ export declare function receiptRefusal(message: string): Error & {
12
+ code: "config.invalid_argument";
13
+ };
14
+ /** The receipt's first look, before ANY reflection on the object (a door that must look at the caller's object before
15
+ * the receipt — a retired-name door reading its prototype chain — calls this first): a Proxy is refused. */
16
+ export declare function refuseProxy(given: object, words: OwnPropertyReceiptWords): void;
17
+ /** Read `given` by the receipt law (module note): the plain copy, every own member taken once from its captured descriptor. */
18
+ export declare function receiveOwnProperties<T extends object>(given: T, words: OwnPropertyReceiptWords): T;
@@ -0,0 +1,39 @@
1
+ import { types } from "node:util";
2
+ export function receiptRefusal(message) {
3
+ const e = new Error(message);
4
+ e.code = "config.invalid_argument";
5
+ return e;
6
+ }
7
+ export function refuseProxy(given, words) {
8
+ if (types.isProxy(given))
9
+ throw receiptRefusal(`${words.subject} must be a plain object, not a Proxy: its ${words.unit}s are read once off its own descriptors, and a Proxy would run its traps in that read — refused rather than wired through it`);
10
+ }
11
+ export function receiveOwnProperties(given, words) {
12
+ refuseProxy(given, words);
13
+ const proto = Object.getPrototypeOf(given);
14
+ if (proto !== Object.prototype && proto !== null) {
15
+ throw receiptRefusal(`${words.subject} must be a plain object (its prototype is neither Object.prototype nor null): every ${words.unit} is read once as an own property, and a ${words.unit} its prototype would supply is not read — refused rather than wired without it`);
16
+ }
17
+ const captured = Object.getOwnPropertyDescriptors(given);
18
+ const received = Object.create(Object.prototype);
19
+ for (const name of Object.getOwnPropertyNames(captured)) {
20
+ const d = captured[name];
21
+ let value;
22
+ if (Object.hasOwn(d, "value")) {
23
+ value = d.value;
24
+ }
25
+ else {
26
+ const get = Object.hasOwn(d, "get") ? d.get : undefined;
27
+ try {
28
+ value = get === undefined ? undefined : get.call(given);
29
+ }
30
+ catch {
31
+ throw receiptRefusal(`${words.subject}.${name} threw while it was read ${words.moment} — a ${words.unit} is read once, here; refused rather than wired without it`);
32
+ }
33
+ }
34
+ Object.defineProperty(received, name, { value, enumerable: true, writable: true, configurable: true });
35
+ }
36
+ if (Object.getPrototypeOf(given) !== proto)
37
+ throw receiptRefusal(`${words.subject}'s prototype changed while its ${words.unit}s were read ${words.moment} — refused rather than wired from a moving object`);
38
+ return received;
39
+ }
@@ -73,7 +73,7 @@
73
73
  * `Bash(git status:*)`) — the last two are strict-side divergences from upstream, registered as such.
74
74
  */
75
75
  import { type DenyFace } from "../tools/fs/read-deny.js";
76
- import type { AssertAllKeysHandled } from "./ask-origin.js";
76
+ import type { AssertAllKeysHandled, RuleOffersAbsence } from "./ask-origin.js";
77
77
  import { type ToolCallLike } from "./tool-registry.js";
78
78
  import { type PathDenialEvidence, type PathFamily, type PathIdentity } from "../tools/fs/safety.js";
79
79
  import { type TargetViews } from "./path-rule-binding.js";
@@ -928,7 +928,13 @@ export declare function spelledScope<C extends {
928
928
  * coverage (the eligibility predicate's behavior conjunct). The mixed-table walk that orders the three
929
929
  * behaviors is {@link adjudicatePersistedRules}.
930
930
  */
931
- export declare function findAdmittingRule(rules: readonly PersistedRule[], call: {
931
+ export declare function findAdmittingRule(rules: readonly PersistedRule[], call: AdmissionCall): readonly PersistedRule[] | undefined;
932
+ /** Whose admission a list's allow rows answer: a GRANT (a person's recorded yes, a list that releases a call — the reference's
933
+ * per-segment form, where a read-only segment of a compound allows itself) or a NARROWING (a list outside of which nothing
934
+ * runs — every segment must be named by a row; nothing allows itself). The posture is the list's, never the call's. */
935
+ export type AdmissionPosture = "grant" | "narrowing";
936
+ /** The call {@link findAdmittingRule} / {@link commandCoverageOf} judge. */
937
+ export interface AdmissionCall {
932
938
  tool: string;
933
939
  command: string;
934
940
  cwd: string | undefined;
@@ -938,7 +944,16 @@ export declare function findAdmittingRule(rules: readonly PersistedRule[], call:
938
944
  * `./x` against the moved cursor, not the task root). Defaults to `cwd`; the scope/eligibility
939
945
  * axis stays `cwd` either way. */
940
946
  execCwd?: string;
941
- }): readonly PersistedRule[] | undefined;
947
+ /** {@link AdmissionPosture}; absent = `grant`. */
948
+ posture?: AdmissionPosture;
949
+ }
950
+ /** {@link findAdmittingRule}'s answer with its miss named: the rules that cover the command, or the first segment no row covers
951
+ * (`undefined` when the command cannot be read at all — the fail-closed floor). One walk; the miss is for a sentence only. */
952
+ export declare function commandCoverageOf(rules: readonly PersistedRule[], call: AdmissionCall): {
953
+ readonly covering: readonly PersistedRule[];
954
+ } | {
955
+ readonly uncovered: string | undefined;
956
+ };
942
957
  /** The answer of {@link adjudicatePersistedRules}: which behavior decided, and the rule(s) it stands
943
958
  * on — a one-element set for a deny/ask (the first table-order rule of the deciding behavior that
944
959
  * reaches the call) and the allow direction's coverage SET ({@link findAdmittingRule}). */
@@ -1251,15 +1266,21 @@ export interface UncoveredSegmentDetail {
1251
1266
  */
1252
1267
  export declare const UNCOVERED_SEGMENT_REASON_BASELINE: Readonly<Record<UncoveredSegmentDetail["reason"], string>>;
1253
1268
  /**
1254
- * design/382 §3.5-2 — the BASELINE sentence per card-level offer-ABSENCE reason (#490 修②'s typed
1255
- * seat: `ruleOffersAbsence` on the ask and its durable park twin; the closed vocabulary is declared
1256
- * literally at those two faces, and this table spells the same literals — the copy row is data, not
1257
- * the type's home). Same reword-never-invert contract as the table above. The load-bearing
1258
- * asymmetry: `mandated` must NOT point at writing rules — on a mandated ask no rule is consulted,
1259
- * and directing a person to mint one that can never take effect is the #457④ anti-shape; the other
1260
- * two reasons name their real outlets.
1269
+ * design/382 §3.5-2 — the BASELINE sentence per card-level offer-ABSENCE reason (`ruleOffersAbsence` on the ask and its
1270
+ * durable park twin; the closed set is {@link import("./ask-origin.js").RULE_OFFERS_ABSENCES}). Same reword-never-invert
1271
+ * contract as the table above. The load-bearing asymmetries: `mandated` and `allow_outranked` must NOT point at writing
1272
+ * rules — on a mandated ask no rule is consulted, an outranked allow shows another allow would be outranked the same way, and
1273
+ * directing a person to mint one that can never take effect is the #457④ anti-shape; the other two reasons name their real
1274
+ * outlets.
1261
1275
  */
1262
- export declare const RULE_OFFERS_ABSENCE_BASELINE: Readonly<Record<"mandated" | "lane_cannot_speak" | "shadowed", string>>;
1276
+ export declare const RULE_OFFERS_ABSENCE_BASELINE: {
1277
+ readonly mandated: "This approval is required by the deployment's policy each time; a rule cannot waive it.";
1278
+ readonly allow_outranked: "An allow rule already matches this call, and this question outranks it — another allow rule would be outranked the same way; it is approved per use.";
1279
+ readonly ask_rule_matched: "An ask-each-time rule matches this call and keeps asking — a new allow rule changes nothing while it stands; to stop being asked, review the existing ask rules.";
1280
+ readonly lane_cannot_speak: "This card has no rule to offer for this exact command; it can be approved per use. A coverable form of what you trust can still get a rule — through a card drawn for that form, or settings.";
1281
+ };
1282
+ /** The fence over the baseline table: `never` while every word has a sentence. */
1283
+ export type OfferAbsenceBaselineCoversEveryWord = AssertAllKeysHandled<Exclude<RuleOffersAbsence, keyof typeof RULE_OFFERS_ABSENCE_BASELINE>>;
1263
1284
  /**
1264
1285
  * The offers presented on an approval card for `command` (design/375 §5.2).
1265
1286
  *