@sema-agent/core 7.52.0 → 7.53.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 (191) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/README.md +3 -1
  3. package/dist/agents/cross-session-envelope.d.ts +8 -0
  4. package/dist/agents/cross-session-envelope.js +6 -0
  5. package/dist/agents/cross-session-judge.d.ts +21 -1
  6. package/dist/agents/cross-session-judge.js +8 -0
  7. package/dist/agents/cross-session-ref.d.ts +5 -0
  8. package/dist/agents/cross-session-ref.js +1 -0
  9. package/dist/agents/org-delivery-gate.d.ts +122 -0
  10. package/dist/agents/org-delivery-gate.js +69 -0
  11. package/dist/agents/peer-held-queue.d.ts +9 -4
  12. package/dist/agents/peer-held-queue.js +6 -5
  13. package/dist/agents/peer-session-drain.d.ts +35 -5
  14. package/dist/agents/peer-session-drain.js +271 -180
  15. package/dist/agents/read-inbox-tool.d.ts +7 -0
  16. package/dist/agents/read-inbox-tool.js +5 -1
  17. package/dist/agents/subagent.d.ts +3 -0
  18. package/dist/agents/subagent.js +99 -72
  19. package/dist/agents/tool-filter.d.ts +18 -0
  20. package/dist/agents/tool-filter.js +6 -0
  21. package/dist/brain/anthropic.d.ts +6 -1
  22. package/dist/brain/anthropic.js +4 -4
  23. package/dist/brain/model-presets.js +2 -1
  24. package/dist/brain/route-adjudicator.d.ts +6 -0
  25. package/dist/brain/routing.d.ts +24 -1
  26. package/dist/brain/routing.js +54 -19
  27. package/dist/brain/timeout.js +5 -4
  28. package/dist/core/a2a.d.ts +5 -2
  29. package/dist/core/a2a.js +30 -13
  30. package/dist/core/allow-entry.d.ts +2 -1
  31. package/dist/core/allow-entry.js +2 -2
  32. package/dist/core/auto-compaction.d.ts +57 -0
  33. package/dist/core/auto-compaction.js +28 -2
  34. package/dist/core/checkpoint-store.d.ts +29 -20
  35. package/dist/core/checkpoint-store.js +3 -3
  36. package/dist/core/compliance.js +3 -1
  37. package/dist/core/effective-path-target.d.ts +25 -6
  38. package/dist/core/effective-path-target.js +24 -2
  39. package/dist/core/engine-notice.d.ts +13 -2
  40. package/dist/core/gate-fold.js +3 -2
  41. package/dist/core/gate-lanes.js +11 -13
  42. package/dist/core/gate-outcome.d.ts +11 -6
  43. package/dist/core/governance-codes.d.ts +3 -1
  44. package/dist/core/governance-codes.js +3 -0
  45. package/dist/core/hands-band-screen.d.ts +7 -0
  46. package/dist/core/hands-band-screen.js +32 -0
  47. package/dist/core/hooks.d.ts +7 -5
  48. package/dist/core/hooks.js +5 -3
  49. package/dist/core/locked-config.d.ts +84 -32
  50. package/dist/core/locked-config.js +128 -42
  51. package/dist/core/mcp-probe.js +1 -1
  52. package/dist/core/mcp-server-spec.d.ts +88 -10
  53. package/dist/core/mcp-server-spec.js +183 -33
  54. package/dist/core/mcp.d.ts +21 -14
  55. package/dist/core/mcp.js +55 -51
  56. package/dist/core/memory-engine/engine.d.ts +14 -19
  57. package/dist/core/memory-engine/engine.js +7 -5
  58. package/dist/core/mode-policy.d.ts +3 -18
  59. package/dist/core/mode-policy.js +2 -16
  60. package/dist/core/mods/kernel/dispatch.js +13 -15
  61. package/dist/core/mods/kernel/events.d.ts +9 -6
  62. package/dist/core/mods/kernel/events.js +2 -2
  63. package/dist/core/mods/kernel/receive.d.ts +39 -10
  64. package/dist/core/mods/kernel/receive.js +119 -54
  65. package/dist/core/mods/kernel/site-rules.d.ts +80 -15
  66. package/dist/core/mods/kernel/site-rules.js +232 -40
  67. package/dist/core/mods/stations.d.ts +0 -6
  68. package/dist/core/mods/stations.js +0 -7
  69. package/dist/core/mounted-mcp.d.ts +57 -0
  70. package/dist/core/mounted-mcp.js +71 -2
  71. package/dist/core/own-property-receipt.d.ts +13 -0
  72. package/dist/core/own-property-receipt.js +3 -1
  73. package/dist/core/parent-spec-seat.d.ts +2 -2
  74. package/dist/core/permission-rule-model.d.ts +58 -9
  75. package/dist/core/permission-rule-model.js +81 -7
  76. package/dist/core/permission-rule-org.d.ts +163 -33
  77. package/dist/core/permission-rule-org.js +205 -71
  78. package/dist/core/permission-rules.d.ts +7 -1
  79. package/dist/core/permission-rules.js +37 -8
  80. package/dist/core/retention.js +7 -2
  81. package/dist/core/retired-keys.d.ts +8 -2
  82. package/dist/core/retired-keys.js +12 -1
  83. package/dist/core/roles.js +60 -10
  84. package/dist/core/rule-lane-reads.js +1 -1
  85. package/dist/core/runner/contracts.d.ts +1 -0
  86. package/dist/core/runner/gate-exit.js +4 -3
  87. package/dist/core/runner/mcp-redial.d.ts +52 -10
  88. package/dist/core/runner/mcp-redial.js +38 -9
  89. package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
  90. package/dist/core/runner/permission-rule-lanes.js +22 -10
  91. package/dist/core/runner/person-path-rows.d.ts +3 -1
  92. package/dist/core/runner/person-path-rows.js +2 -2
  93. package/dist/core/runner/prepare-config-doors.d.ts +48 -2
  94. package/dist/core/runner/prepare-config-doors.js +161 -145
  95. package/dist/core/runner/prepare-defer-classify.js +4 -7
  96. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -2
  97. package/dist/core/runner/prepare-delegation-surface.js +7 -5
  98. package/dist/core/runner/prepare-hands-readface.d.ts +1 -1
  99. package/dist/core/runner/prepare-hands-readface.js +2 -1
  100. package/dist/core/runner/prepare-inherited-gate.js +2 -2
  101. package/dist/core/runner/prepare-listings.d.ts +1 -1
  102. package/dist/core/runner/prepare-listings.js +4 -2
  103. package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
  104. package/dist/core/runner/prepare-memory-engine-session.js +19 -6
  105. package/dist/core/runner/prepare-memory.d.ts +3 -2
  106. package/dist/core/runner/prepare-memory.js +6 -2
  107. package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
  108. package/dist/core/runner/prepare-offload-wrappers.js +2 -1
  109. package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
  110. package/dist/core/runner/prepare-policy-chain.js +12 -12
  111. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  112. package/dist/core/runner/prepare-project-context.js +6 -4
  113. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  114. package/dist/core/runner/prepare-prompt-assembly.js +20 -16
  115. package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
  116. package/dist/core/runner/prepare-prompt-inputs.js +6 -3
  117. package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
  118. package/dist/core/runner/prepare-protocol-tools.js +19 -12
  119. package/dist/core/runner/prepare-question-face.d.ts +4 -2
  120. package/dist/core/runner/prepare-question-face.js +4 -3
  121. package/dist/core/runner/prepare-task.js +10 -10
  122. package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -3
  123. package/dist/core/runner/prepare-wiring-manifest.js +8 -5
  124. package/dist/core/runner/resume-admission.d.ts +4 -1
  125. package/dist/core/runner/resume-admission.js +7 -5
  126. package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
  127. package/dist/core/runner/resume-internals-and-config.js +3 -8
  128. package/dist/core/runner/retired-runner-deps.d.ts +5 -6
  129. package/dist/core/runner/retired-runner-deps.js +1 -4
  130. package/dist/core/runner/run-leg.js +42 -12
  131. package/dist/core/runner/run-recovery-lanes.js +5 -3
  132. package/dist/core/runner/run-terminal-adoption.js +6 -5
  133. package/dist/core/runner/run-turn-boundary.js +9 -3
  134. package/dist/core/runner/runtask.js +1 -1
  135. package/dist/core/runner/skills-seat.d.ts +15 -0
  136. package/dist/core/runner/skills-seat.js +57 -0
  137. package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
  138. package/dist/core/runner/sub-agent-deny-lists.js +7 -5
  139. package/dist/core/runner/synthetic-tools.d.ts +5 -2
  140. package/dist/core/runner/synthetic-tools.js +2 -12
  141. package/dist/core/runner-deps.d.ts +29 -14
  142. package/dist/core/session-policy-store.d.ts +19 -0
  143. package/dist/core/session-policy-store.js +22 -2
  144. package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
  145. package/dist/core/task-event.d.ts +38 -2
  146. package/dist/core/task-notification.js +2 -2
  147. package/dist/core/task-spec.d.ts +1 -1
  148. package/dist/core/tool-policy.d.ts +3 -0
  149. package/dist/core/tool-policy.js +10 -4
  150. package/dist/core/types.d.ts +2 -2
  151. package/dist/core/types.js +1 -1
  152. package/dist/core/untrusted-text.d.ts +16 -0
  153. package/dist/core/untrusted-text.js +7 -2
  154. package/dist/core/user-attachment.d.ts +13 -2
  155. package/dist/core/user-attachment.js +47 -41
  156. package/dist/core/wiring-manifest.d.ts +4 -2
  157. package/dist/engine/harness/agent-harness.js +2 -1
  158. package/dist/engine/harness/types.d.ts +3 -0
  159. package/dist/engine/loop/agent-loop.d.ts +12 -1
  160. package/dist/engine/loop/agent-loop.js +17 -5
  161. package/dist/engine/loop/types.d.ts +7 -1
  162. package/dist/index.d.ts +15 -9
  163. package/dist/index.js +12 -6
  164. package/dist/internal/harness.d.ts +1 -0
  165. package/dist/internal/harness.js +1 -0
  166. package/dist/orchestration/goal.d.ts +24 -1
  167. package/dist/orchestration/goal.js +3 -2
  168. package/dist/prompt-assembly/artifact-store.d.ts +44 -9
  169. package/dist/prompt-assembly/artifact-store.js +38 -12
  170. package/dist/prompt-assembly/artifact.d.ts +12 -11
  171. package/dist/prompt-assembly/artifact.js +52 -21
  172. package/dist/prompt-assembly/assemble.d.ts +2 -0
  173. package/dist/prompt-assembly/assemble.js +2 -1
  174. package/dist/prompt-assembly/catalog/index.js +2 -1
  175. package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
  176. package/dist/prompt-assembly/catalog/memory.js +6 -0
  177. package/dist/prompt-assembly/catalog/schema.d.ts +16 -2
  178. package/dist/prompt-assembly/composer.d.ts +2 -0
  179. package/dist/prompt-assembly/composer.js +1 -1
  180. package/dist/prompt-assembly/overlay.d.ts +21 -11
  181. package/dist/prompt-assembly/overlay.js +55 -17
  182. package/dist/prompt-assembly/select.d.ts +21 -6
  183. package/dist/prompt-assembly/select.js +39 -2
  184. package/dist/prompts/default.d.ts +0 -11
  185. package/dist/prompts/default.js +0 -3
  186. package/dist/stores/file/permission-rule-store.d.ts +1 -1
  187. package/dist/stores/file/permission-rule-store.js +2 -1
  188. package/dist/tools/artifact/artifact-tool.js +4 -3
  189. package/package.json +1 -1
  190. package/test/export-surface.snapshot.json +65 -5
  191. package/wire-changes/core@7.53.0.json +1946 -0
@@ -1,4 +1,5 @@
1
- import type { McpServerSpec, RunnerDeps, TaskSpec } from "./types.js";
1
+ import type { A2aServerSpec, McpServerSpec, RunnerDeps, TaskSpec } from "./types.js";
2
+ import { type ToolAxesSurface } from "./mcp-server-spec.js";
2
3
  import type { ToolPolicy } from "./tool-policy.js";
3
4
  /** The closed set of administratively lockable configuration keys. Adding a member is a deliberate
4
5
  * edit HERE (tsc forces the registry row), never a free-form string. */
@@ -34,7 +35,7 @@ export interface LockedConfig {
34
35
  keys: readonly LockedKey[];
35
36
  }
36
37
  /** The preflight's resolved snapshot — the ONLY thing downstream task assembly reads for the
37
- * guarded slots (never the raw `spec.mcp` / `spec.toolPolicy` fields). */
38
+ * guarded slots (never the raw `spec.mcp` / `spec.a2a` / `spec.toolPolicy` fields). */
38
39
  export interface LockedPreflight {
39
40
  /** The validated lock set (empty when the deployment declares none). */
40
41
  lockedKeys: ReadonlySet<LockedKey>;
@@ -45,6 +46,10 @@ export interface LockedPreflight {
45
46
  * `undefined` (core has no deployment-level MCP seat — a locked deployment mounts no
46
47
  * task-supplied servers). */
47
48
  mcp: TaskSpec["mcp"];
49
+ /** The task's A2A peers: a frozen SNAPSHOT of `spec.a2a` whose `toolAxes` were judged here by the same reader and with the same
50
+ * disposition as an MCP server's (a declaration that does not fit refused the whole prepare), each peer carrying the reader's plain
51
+ * copy; `undefined` when the spec declares no peer (absent or empty). Read once, here: the peers dialed are the peers judged. */
52
+ a2a: TaskSpec["a2a"];
48
53
  /** Effective caller tool policy: the same `spec.toolPolicy ?? deps.toolPolicy` slot the gate has
49
54
  * always enforced, resolved ONCE here. With `toolPolicy` locked, the deployment's own
50
55
  * `deps.toolPolicy` is the whole answer (a spec-supplied policy has already been refused). */
@@ -56,12 +61,27 @@ export interface LockedPreflight {
56
61
  * site). Unlocked: `spec.basePolicyForResumeEdit ?? deps.basePolicyForResumeEdit`. */
57
62
  basePolicyForResumeEdit: ToolPolicy | undefined;
58
63
  }
64
+ /**
65
+ * How one configuration door is entered: `seat` names the input that carries the value the door judges — the task's configuration or the
66
+ * deployment's deps. A prepare enters each door as it stands ({@link ENTER_AS_IS}: the door's own coded throw); a resume's screen before the
67
+ * claim enters the same door through the resume's refusal channel, which names the seat (`asResumeConfigRefusal`). One door, one function,
68
+ * two entrances: the seat is written once, where the door is called.
69
+ */
70
+ export type ConfigDoorEntry = <T>(seat: "taskConfig" | "deps", door: () => T) => T;
71
+ export declare const ENTER_AS_IS: ConfigDoorEntry;
59
72
  /**
60
73
  * Validate a deployment's lock declaration against the closed registry. Fail-loud on any unknown
61
74
  * key (`config.locked_registry`): a security control that doesn't parse must refuse to arm, not
62
75
  * degrade to "that key silently unlocked". Exported for deployments that want the check at their
63
76
  * own startup; the task-preparation preflight runs it on every leg regardless (a mis-declared lock
64
- * then fails the first prepare instead of silently guarding nothing).
77
+ * then fails the first prepare instead of silently guarding nothing — and, on a resume, the screen before
78
+ * the claim, which calls the same preflight).
79
+ *
80
+ * Total over the declaration: anything but `undefined` (no locks) or plain data whose `keys` is a list of registry names — `null`, a
81
+ * list, a primitive, a `keys` that is absent or not a list, a declaration that is not plain data (a Proxy, a `Set` of keys, a class
82
+ * instance, a getter that throws), an element that is not a string, a string that is not a registry name — is the same coded refusal
83
+ * naming `RunnerDeps.lockedConfig` and the member (an element by its index), never a TypeError and never "no locks". The declaration is
84
+ * read once, by the one declaration reading.
65
85
  */
66
86
  export declare function resolveLockedKeys(config: LockedConfig | undefined): ReadonlySet<LockedKey>;
67
87
  /**
@@ -74,23 +94,33 @@ export declare function resolveLockedKeys(config: LockedConfig | undefined): Rea
74
94
  * read off a LIVE object is only true for as long as the object holds still, and the fail-open
75
95
  * direction is the reachable one — so both boundaries consume data instead.
76
96
  *
77
- * The read discipline is the load-bearing part, and it is why this is a spread rather than
78
- * "read the property, then copy the object": a getter answering ALTERNATING LEGAL values would
97
+ * The read discipline is the load-bearing part, and it is why the copy is made in the pass that reads
98
+ * rather than "read the property, then copy the object": a getter answering ALTERNATING LEGAL values would
79
99
  * satisfy any number of re-reads (`"external"` to a validator, `"local"` to a mint site), and no
80
- * vocabulary guard anywhere has purchase on a legal value. One spread evaluates every own property
81
- * EXACTLY ONCE and materializes the result as plain data; every later reader — validator, connect,
82
- * refresh — then sees the same byte. Frozen so nothing downstream rewrites it.
100
+ * vocabulary guard anywhere has purchase on a legal value. One pass evaluates every member EXACTLY ONCE
101
+ * and materializes the result as plain data; every later reader — validator, connect, refresh — then sees
102
+ * the same byte. Frozen so nothing downstream rewrites it.
83
103
  *
84
- * Boundary, stated: the copy is one level deep — nested option objects (transport, headers, env,
85
- * toolAxes) stay shared with the caller's entry exactly as before, because the axis this owns is the
86
- * content class; readers of the nested shapes normalize their own values (the transport DISCRIMINATOR
87
- * is derived, never echoed raw). `toolAxes` is the one nested shape both entrances replace with its
88
- * reader's plain copy ({@link judgeEntryToolAxes}), and the one member this snapshot reads BY NAME rather
89
- * than by spread, once: a declaration an entry carries as a non-enumerable or inherited property would
90
- * otherwise vanish before any reader sees it, and an absent caller axis is a tightening lost, not a
91
- * fail-closed absence. Every other member is taken as own ENUMERABLE properties only: an entry whose
92
- * fields live on a prototype was never the plain configuration record this type describes, and its
93
- * absent class reads fail-closed like any other absence.
104
+ * Plain data, stated: an entry IS plain data, to its whole depth, or it is refused whole. The entry is read
105
+ * by the one declaration reading ({@link receiveDeclaration} — the engine's one receipt of a caller's object,
106
+ * in its declaration mode): every own member read once off its captured descriptor, every level copied, every
107
+ * copied container frozen; an object the entry reaches through two members is read once, and both hold the one
108
+ * copy. So a server's `transport` with its `args` / `env` / `headers`, `allowTools`, `toolFaces`, `toolAxes`
109
+ * and a peer's `headers` and `allowSkills` are the copy's own, and a host that edits its own nested object after
110
+ * the door (a header value, an `allowSkills.push`) reaches nothing the engine holds: the dial, a refresh and a
111
+ * re-dial all work from this copy, on both surfaces. The axes are then judged on that copy by their own reader
112
+ * ({@link judgeEntryToolAxes}).
113
+ *
114
+ * There is no third outcome between "copied whole" and "refused": a value the copy cannot carry whole is a
115
+ * declaration the engine cannot read — a Proxy at any level, an object whose prototype is neither
116
+ * `Object.prototype` nor null (a class instance, a map, `process.env`, a record built on a prototype or in another
117
+ * realm, whose members a copy would drop or could not tell from its own), a function, a structure that contains
118
+ * itself, a getter that throws (whatever it throws), a nesting too deep to read, a list element that is not a
119
+ * string, a member named `__proto__`, an entry that is not an object. Each is refused as `config.invalid_argument`
120
+ * naming the declarer and the member where reading stopped (`toolFaces.write_file`, `allowTools[2]`), never as the
121
+ * raw error the host's code threw, and never by handing the host's object on: a member some of whose getters have
122
+ * already run would be read again downstream, and could answer differently. The door refuses the whole prepare and
123
+ * a library entrance the whole call, with this one sentence either way.
94
124
  *
95
125
  * Exported because the SECOND boundary needs the identical guarantee: `materializeMcpTools` is public
96
126
  * and a library caller reaches it without passing any door, so it snapshots its own input through
@@ -98,16 +128,39 @@ export declare function resolveLockedKeys(config: LockedConfig | undefined): Rea
98
128
  */
99
129
  export declare function snapshotMcpEntries(entries: readonly McpServerSpec[]): McpServerSpec[];
100
130
  /**
101
- * The `toolAxes` question for ONE snapshot entry — asked by the preparation door (a refusal ends the prepare) and by the materialization
102
- * entrance (a refusal fails that server's dial), so there is one reading, one copy and one sentence whichever time it is asked.
103
- * `ok: true` carries the entry with the reader's PLAIN COPY of the axes in place of the caller's object: what was judged is the very value
104
- * the connect and every refresh read, not a live object that could answer differently later. The copy is frozen with the entry — a tool
105
- * that shallow-copies a server (`mountedMcp()`) to customise its axes must not rewrite what the parent reads. `ok: false` carries the
106
- * coded refusal (`config.invalid_argument`) naming the server, the tool and the member, never a value.
131
+ * The refusal of a declaration the engine cannot read (`config.invalid_argument`). `declarer` is the entry's own name as the engine captured
132
+ * it ({@link capturedName}) — absent when there was none to capture — so a consumer that holds the refused entry by a name (the A2A
133
+ * entrance's warning, its collision check) takes it from here and never reads the caller's object again.
134
+ */
135
+ export type DeclarationRefusal = Error & {
136
+ code: "config.invalid_argument";
137
+ declarer?: string;
138
+ };
139
+ /**
140
+ * The name a refused entry is held under by a consumer that holds it by a name (the A2A entrance): the refusal's own `declarer`, read as
141
+ * own data — never through anything the refusal's prototype carries — or `undefined` (the consumer names the entry by its place).
142
+ */
143
+ export declare function refusedDeclarer(refusal: unknown): string | undefined;
144
+ /**
145
+ * One entry of EITHER surface that declares `toolAxes` (an MCP server, an A2A peer) as frozen plain data, read by the discipline
146
+ * {@link snapshotMcpEntries} states: the entry and everything in it read once by the one declaration reading and copied to its whole
147
+ * depth, or the entry refused ({@link DeclarationRefusal}), naming the member where reading stopped. The A2A peer's door and its
148
+ * materialization snapshot through this same function, so the two surfaces' declarations reach the one judge ({@link judgeEntryToolAxes})
149
+ * read the same way; `at` is the surface the entry sits on, which only decides what the refusal calls the declarer.
150
+ */
151
+ export declare function snapshotDeclaredEntry<E extends McpServerSpec | A2aServerSpec>(entry: E, at: ToolAxesSurface): E;
152
+ /**
153
+ * The `toolAxes` question for ONE snapshot entry of either surface (`at`: an MCP server, an A2A peer) — asked by the preparation door (a
154
+ * refusal ends the prepare) and by each materialization entrance (a refusal fails that server's or that peer's dial), so there is one
155
+ * reading, one copy and one sentence whichever surface and whichever time it is asked. `ok: true` carries the entry with the reader's
156
+ * PLAIN COPY of the axes in place of the caller's object: what was judged is the very value the connect and every refresh read, not a
157
+ * live object that could answer differently later. The copy is frozen with the entry — a tool that shallow-copies a server
158
+ * (`mountedMcp()`) to customise its axes must not rewrite what the parent reads. `ok: false` carries the coded refusal
159
+ * (`config.invalid_argument`) naming the declarer, the tool (or skill) and the member, never a value.
107
160
  */
108
- export declare function judgeEntryToolAxes(copy: McpServerSpec): {
161
+ export declare function judgeEntryToolAxes<E extends McpServerSpec | A2aServerSpec>(copy: E, at: ToolAxesSurface): {
109
162
  ok: true;
110
- entry: McpServerSpec;
163
+ entry: E;
111
164
  } | {
112
165
  ok: false;
113
166
  error: Error & {
@@ -118,14 +171,13 @@ export declare function judgeEntryToolAxes(copy: McpServerSpec): {
118
171
  * The single preflight resolver: validate the lock declaration, refuse a spec that tries to occupy
119
172
  * a locked slot (`config.locked_key`, two-state — the whole prepare is rejected, nothing is
120
173
  * silently dropped or merged), and produce the resolved snapshot downstream assembly consumes.
121
- * Runs BEFORE any side effect of task preparation.
174
+ * Runs BEFORE any side effect of task preparation — and, on a resume, before the claim: the resume's
175
+ * screen calls this same function (`enter` naming each refusal's seat) and hands the leg the frozen
176
+ * entries it judged, so the leg's prepare judges engine-owned plain data and dials what was judged.
122
177
  */
123
- export declare function preflightLockedConfig(spec: TaskSpec, deps: RunnerDeps): LockedPreflight;
178
+ export declare function preflightLockedConfig(spec: Pick<TaskSpec, "mcp" | "a2a" | "toolPolicy" | "basePolicyForResumeEdit">, deps: RunnerDeps, enter?: ConfigDoorEntry): LockedPreflight;
124
179
  /**
125
180
  * The two caller policies a prepare resolves to: the task's own seat over the deployment's, or the deployment's alone
126
- * under a `toolPolicy` lock. One resolution for the preflight above and for the resume ladder's pre-claim door.
181
+ * under a `toolPolicy` lock — one resolution, read by the preflight above (which a resume enters before its claim).
127
182
  */
128
183
  export declare function callerPolicySeats(spec: Pick<TaskSpec, "toolPolicy" | "basePolicyForResumeEdit">, deps: Pick<RunnerDeps, "toolPolicy" | "basePolicyForResumeEdit">, toolPolicyLocked: boolean): Pick<LockedPreflight, "toolPolicy" | "basePolicyForResumeEdit">;
129
- /** The retired-member door over the resolved caller policies: a member the engine no longer reads is refused by name
130
- * (`config.deps_retired_key`), never dropped in silence. */
131
- export declare function assertCallerPoliciesCurrent(policies: Pick<LockedPreflight, "toolPolicy" | "basePolicyForResumeEdit">): void;
@@ -1,4 +1,7 @@
1
- import { readMcpToolAxes } from "./mcp-server-spec.js";
1
+ import { types } from "node:util";
2
+ import { DECLARATION_PATH_RULES, declarationFaultOf, declarerWord, readToolAxes, receiveDeclaration, toolAxesRefusal } from "./mcp-server-spec.js";
3
+ import { isPlainRecord, receive } from "./mods/kernel/receive.js";
4
+ import { CONFIG_RECEIPT_MODE } from "./own-property-receipt.js";
2
5
  import { isToolContentOrigin, TOOL_CONTENT_ORIGINS } from "./memory-engine/content-origin.js";
3
6
  import { inlineUntrusted } from "./untrusted-text.js";
4
7
  import { assertNoRetiredToolPolicyKeys } from "./retired-keys.js";
@@ -8,6 +11,7 @@ export const LOCKED_KEY_REGISTRY = {
8
11
  compliancePosture: { specFields: [] },
9
12
  retentionPolicy: { specFields: [] },
10
13
  };
14
+ export const ENTER_AS_IS = (_seat, door) => door();
11
15
  function codedError(code, message) {
12
16
  const e = new Error(message);
13
17
  e.code = code;
@@ -17,21 +21,74 @@ export function resolveLockedKeys(config) {
17
21
  if (config === undefined)
18
22
  return new Set();
19
23
  const keys = new Set();
20
- for (const key of config.keys) {
21
- if (!Object.prototype.hasOwnProperty.call(LOCKED_KEY_REGISTRY, key)) {
22
- throw codedError("config.locked_registry", `lockedConfig.keys contains "${String(key)}", which is not a member of the locked-key registry ` +
23
- `(${Object.keys(LOCKED_KEY_REGISTRY).join(", ")}). A lock that does not parse must not half-arm — ` +
24
- `fix the deployment's lock declaration.`);
24
+ const listed = declaredLockKeys(config);
25
+ for (let index = 0; index < listed.length; index++) {
26
+ const key = listed[index];
27
+ if (!Object.hasOwn(LOCKED_KEY_REGISTRY, key)) {
28
+ throw lockDeclarationRefusal(`RunnerDeps.lockedConfig.keys[${index}]`, `is ${inlineUntrusted(JSON.stringify(key), 80)}, which is not a member of the locked-key registry (${Object.keys(LOCKED_KEY_REGISTRY).join(", ")})`);
25
29
  }
26
30
  keys.add(key);
27
31
  }
28
32
  return keys;
29
33
  }
34
+ function declaredLockKeys(config) {
35
+ const received = receiveDeclaration(config);
36
+ if (!received.ok)
37
+ throw lockDeclarationRefusal(memberPath(received.fault.path, "RunnerDeps.lockedConfig"), `cannot be read: ${received.fault.problem}`);
38
+ const copy = received.value;
39
+ if (!isPlainRecord(copy))
40
+ throw lockDeclarationRefusal("RunnerDeps.lockedConfig", `is ${inlineUntrusted(describeDeclared(copy), 80)}, not an object with a \`keys\` list`);
41
+ const keys = Object.hasOwn(copy, "keys") ? copy["keys"] : undefined;
42
+ if (!Array.isArray(keys))
43
+ throw lockDeclarationRefusal("RunnerDeps.lockedConfig.keys", `is ${inlineUntrusted(describeDeclared(keys), 80)}, not a list of locked keys`);
44
+ return keys;
45
+ }
46
+ function lockDeclarationRefusal(member, problem) {
47
+ return codedError("config.locked_registry", `${member} ${problem}. A lock that does not parse must not half-arm — fix the deployment's lock declaration.`);
48
+ }
30
49
  export function snapshotMcpEntries(entries) {
31
- return entries.map((entry) => {
32
- const { toolAxes, ...rest } = entry;
33
- return Object.freeze((toolAxes === undefined ? rest : { ...rest, toolAxes }));
34
- });
50
+ return entries.map((entry) => snapshotDeclaredEntry(entry, "mcp"));
51
+ }
52
+ function memberPath(path, base = "") {
53
+ let rendered = base;
54
+ for (const key of path) {
55
+ const name = typeof key === "string" && /^\d+$/.test(key) ? JSON.stringify(key) : String(key);
56
+ rendered += typeof key === "number" ? `[${key}]` : rendered === "" ? name : `.${name}`;
57
+ if (rendered.length > 120 + base.length)
58
+ break;
59
+ }
60
+ return rendered;
61
+ }
62
+ function unreadableDeclaration(at, declarer, member, problem) {
63
+ const refusal = codedError("config.invalid_argument", `${declarerWord(at)}${declarer === undefined ? "" : ` ${declarerNameOf(declarer)}`} declares ${inlineUntrusted(member, 80)} the engine cannot read: ${problem}. A declaration the engine cannot read is refused ` +
64
+ `rather than partly applied — what the caller wrote and what the engine runs on would otherwise differ without anyone being told.`);
65
+ if (declarer !== undefined)
66
+ Object.defineProperty(refusal, "declarer", { value: declarer, enumerable: true, writable: false, configurable: false });
67
+ return refusal;
68
+ }
69
+ function ownDataString(holder, key) {
70
+ const slot = Object.getOwnPropertyDescriptor(holder, key);
71
+ const value = slot !== undefined && Object.hasOwn(slot, "value") ? slot.value : undefined;
72
+ return typeof value === "string" ? value : undefined;
73
+ }
74
+ export function refusedDeclarer(refusal) {
75
+ return typeof refusal === "object" && refusal !== null && !types.isProxy(refusal) ? ownDataString(refusal, "declarer") : undefined;
76
+ }
77
+ function capturedName(entry) {
78
+ return typeof entry === "object" && entry !== null && !types.isProxy(entry) ? ownDataString(entry, "name") : undefined;
79
+ }
80
+ function entryFaultRefusal(at, declarer, path, problem) {
81
+ return unreadableDeclaration(at, declarer, path.length === 0 ? "an entry" : memberPath(path), problem);
82
+ }
83
+ export function snapshotDeclaredEntry(entry, at) {
84
+ const declared = entry;
85
+ const declarer = capturedName(declared);
86
+ const received = receiveDeclaration(declared, DECLARATION_PATH_RULES[at]);
87
+ if (!received.ok)
88
+ throw entryFaultRefusal(at, declarer, received.fault.path, received.fault.problem);
89
+ if (!isPlainRecord(received.value))
90
+ throw unreadableDeclaration(at, declarer, "an entry", `it is ${inlineUntrusted(describeDeclared(received.value), 80)}, not an object`);
91
+ return received.value;
35
92
  }
36
93
  function describeDeclared(value) {
37
94
  try {
@@ -46,58 +103,91 @@ function describeDeclared(value) {
46
103
  }
47
104
  }
48
105
  }
49
- const serverNameOf = (copy) => inlineUntrusted(typeof copy.name === "string" ? JSON.stringify(copy.name) : describeDeclared(copy.name), 80);
50
- export function judgeEntryToolAxes(copy) {
106
+ const declarerNameOf = (name) => inlineUntrusted(typeof name === "string" ? JSON.stringify(name) : describeDeclared(name), 80);
107
+ export function judgeEntryToolAxes(copy, at) {
51
108
  const { toolAxes: declaredAxes, ...rest } = copy;
52
- const axes = readMcpToolAxes(declaredAxes);
53
- if (!axes.ok) {
54
- return {
55
- ok: false,
56
- error: codedError("config.invalid_argument", `MCP server ${serverNameOf(copy)} declares toolAxes the engine cannot read: ${inlineUntrusted(axes.problem, 240)}. A per-tool safety-axis declaration is the ` +
57
- `caller's trust root for the approval gate, so it is refused rather than partly applied — dropping one key or defaulting one word ` +
58
- `would leave a declaration that reads as in force while it is not.`),
59
- };
60
- }
109
+ const axes = readToolAxes(declaredAxes, at);
110
+ if (!axes.ok)
111
+ return { ok: false, error: toolAxesRefusal(at, declarerNameOf(copy.name), axes.problem) };
61
112
  if (axes.toolAxes !== undefined) {
62
113
  for (const perTool of Object.values(axes.toolAxes))
63
114
  Object.freeze(perTool);
64
115
  Object.freeze(axes.toolAxes);
65
116
  }
66
- return { ok: true, entry: Object.freeze(axes.toolAxes === undefined ? rest : { ...rest, toolAxes: axes.toolAxes }) };
117
+ return { ok: true, entry: Object.freeze((axes.toolAxes === undefined ? rest : { ...rest, toolAxes: axes.toolAxes })) };
118
+ }
119
+ function declaredEntriesOf(declared, at) {
120
+ const field = at;
121
+ const received = receive(declared, CONFIG_RECEIPT_MODE);
122
+ let problem;
123
+ if (received.ok) {
124
+ const list = received.value;
125
+ if (Array.isArray(list) && list !== declared)
126
+ return Array.from({ length: list.length }, (_, index) => list[index]);
127
+ problem = typeof list === "object" && list !== null ? "it is not an array" : `it is ${inlineUntrusted(describeDeclared(list), 80)}`;
128
+ }
129
+ else {
130
+ const fault = declarationFaultOf(received.fault);
131
+ if (fault.path.length > 0)
132
+ throw unreadableDeclaration(at, undefined, "an entry", fault.problem);
133
+ problem = fault.problem;
134
+ }
135
+ throw codedError("config.invalid_argument", `TaskSpec.${field} must be a readable array of ${declarerWord(at)} declarations; ${problem}. A list the engine cannot read ` +
136
+ `is refused rather than read as "no entries" — the declared ${declarerWord(at)}s would otherwise be dropped without anyone being told.`);
67
137
  }
68
- function snapshotAndValidateMcpEntries(entries) {
69
- return snapshotMcpEntries(entries).map((copy) => {
138
+ function snapshotAndValidateA2aEntries(declared) {
139
+ const entries = declaredEntriesOf(declared, "a2a");
140
+ if (entries.length === 0)
141
+ return undefined;
142
+ return entries.map((entry) => {
143
+ const judged = judgeEntryToolAxes(snapshotDeclaredEntry(entry, "a2a"), "a2a");
144
+ if (!judged.ok)
145
+ throw judged.error;
146
+ return judged.entry;
147
+ });
148
+ }
149
+ function snapshotAndValidateMcpEntries(declared) {
150
+ return snapshotMcpEntries(declaredEntriesOf(declared, "mcp")).map((copy) => {
70
151
  const declared = copy.contentOrigin;
71
152
  if (declared !== undefined && !isToolContentOrigin(declared)) {
72
- throw codedError("config.mcp_content_class", `MCP server ${serverNameOf(copy)} declares contentOrigin ` +
153
+ throw codedError("config.mcp_content_class", `MCP server ${declarerNameOf(copy.name)} declares contentOrigin ` +
73
154
  `${inlineUntrusted(describeDeclared(declared), 80)}, which is not one of ` +
74
155
  `${TOOL_CONTENT_ORIGINS.map((v) => `"${v}"`).join(" | ")}. The declaration decides whether this server's tools ` +
75
156
  `mark the session's memory externally exposed — a value this engine cannot read is refused at the door rather ` +
76
157
  `than folded to a class, because folding it would let a deployment believe a declaration is in force while every ` +
77
158
  `call keeps writing a one-way durable mark.`);
78
159
  }
79
- const judged = judgeEntryToolAxes(copy);
160
+ const judged = judgeEntryToolAxes(copy, "mcp");
80
161
  if (!judged.ok)
81
162
  throw judged.error;
82
163
  return judged.entry;
83
164
  });
84
165
  }
85
- export function preflightLockedConfig(spec, deps) {
86
- const lockedKeys = resolveLockedKeys(deps.lockedConfig);
87
- for (const key of lockedKeys) {
88
- for (const field of LOCKED_KEY_REGISTRY[key].specFields) {
89
- if (spec[field] !== undefined) {
90
- throw codedError("config.locked_key", `TaskSpec.${field} is administratively locked by this deployment (locked key "${key}") — ` +
91
- `the task-supplied value is refused wholesale (locks are config-time; there is no request-time ` +
92
- `unlock). Remove the field from the task, or change the deployment's lock configuration.`);
166
+ export function preflightLockedConfig(spec, deps, enter = ENTER_AS_IS) {
167
+ const lockedKeys = enter("deps", () => resolveLockedKeys(deps.lockedConfig));
168
+ enter("taskConfig", () => {
169
+ for (const key of lockedKeys) {
170
+ for (const field of LOCKED_KEY_REGISTRY[key].specFields) {
171
+ if (spec[field] !== undefined) {
172
+ throw codedError("config.locked_key", `TaskSpec.${field} is administratively locked by this deployment (locked key "${key}") — ` +
173
+ `the task-supplied value is refused wholesale (locks are config-time; there is no request-time ` +
174
+ `unlock). Remove the field from the task, or change the deployment's lock configuration.`);
175
+ }
93
176
  }
94
177
  }
95
- }
96
- const policies = callerPolicySeats(spec, deps, lockedKeys.has("toolPolicy"));
97
- assertCallerPoliciesCurrent(policies);
178
+ });
179
+ const toolPolicyLocked = lockedKeys.has("toolPolicy");
180
+ const task = toolPolicyLocked ? {} : { toolPolicy: spec.toolPolicy, basePolicyForResumeEdit: spec.basePolicyForResumeEdit };
181
+ const policies = callerPolicySeats(task, deps, toolPolicyLocked);
182
+ enter(!toolPolicyLocked && task.toolPolicy != null ? "taskConfig" : "deps", () => assertNoRetiredToolPolicyKeys(policies.toolPolicy));
183
+ enter(!toolPolicyLocked && task.basePolicyForResumeEdit != null ? "taskConfig" : "deps", () => assertNoRetiredToolPolicyKeys(policies.basePolicyForResumeEdit));
184
+ const declaredServers = lockedKeys.has("mcp") ? undefined : spec.mcp;
185
+ const mcp = declaredServers === undefined ? undefined : enter("taskConfig", () => snapshotAndValidateMcpEntries(declaredServers));
186
+ const declaredPeers = spec.a2a;
98
187
  return {
99
188
  lockedKeys,
100
- mcp: lockedKeys.has("mcp") || spec.mcp === undefined ? undefined : snapshotAndValidateMcpEntries(spec.mcp),
189
+ mcp,
190
+ a2a: declaredPeers === undefined ? undefined : enter("taskConfig", () => snapshotAndValidateA2aEntries(declaredPeers)),
101
191
  ...policies,
102
192
  };
103
193
  }
@@ -107,7 +197,3 @@ export function callerPolicySeats(spec, deps, toolPolicyLocked) {
107
197
  basePolicyForResumeEdit: toolPolicyLocked ? deps.basePolicyForResumeEdit : (spec.basePolicyForResumeEdit ?? deps.basePolicyForResumeEdit),
108
198
  };
109
199
  }
110
- export function assertCallerPoliciesCurrent(policies) {
111
- assertNoRetiredToolPolicyKeys(policies.toolPolicy);
112
- assertNoRetiredToolPolicyKeys(policies.basePolicyForResumeEdit);
113
- }
@@ -128,5 +128,5 @@ export async function probeMcpServers(specs, opts = {}) {
128
128
  fatal = { cause: abortCause(signal) };
129
129
  if (fatal !== undefined)
130
130
  throw fatal.cause;
131
- return { entries: mcpManifestEntries(declared, statuses), servers };
131
+ return { entries: mcpManifestEntries(declared, statuses, servers.flatMap((s) => s?.tools ?? [])), servers };
132
132
  }
@@ -1,3 +1,4 @@
1
+ import { type ReceiptFault } from "./mods/kernel/receive.js";
1
2
  import type { ToolContentOrigin, ToolEffect } from "./tool-spec.js";
2
3
  import type { ToolFamily, ToolPathTarget, ToolRenderHints, ToolRuleFace } from "./tool-face.js";
3
4
  /** Definition of an MCP server to connect to for the duration of one task, then discard. */
@@ -61,7 +62,7 @@ export interface McpServerSpec {
61
62
  * a read-only tool can return third-party text, and a deployment's own writer brings back nothing
62
63
  * external — so vouching on one axis never quietly vouches on the other.
63
64
  *
64
- * THE SHAPE IS JUDGED, NOT TRUSTED: {@link readMcpToolAxes} is the one reader of this declaration. The
65
+ * THE SHAPE IS JUDGED, NOT TRUSTED: {@link readToolAxes} is the one reader of this declaration (an A2A peer's `toolAxes` is read by it too). The
65
66
  * preparation door runs it on every entry, and a declaration it cannot read refuses the whole prepare
66
67
  * (`config.invalid_argument`) — one bad tool refuses the entry, a bad key is never dropped and a bad word
67
68
  * never defaults, because a caller's safety axis that silently reads as absent is a trust root that is not
@@ -167,12 +168,17 @@ export interface McpServerSpec {
167
168
  }
168
169
  type ToolAxes = NonNullable<McpServerSpec["toolAxes"]>;
169
170
  /**
170
- * {@link readMcpToolAxes}'s answer. `ok: true` carries the declaration as a PLAIN COPY the reader built from
171
+ * The surfaces that declare `toolAxes`, which {@link readToolAxes} names its reading by: an MCP server's keys are the server's
172
+ * remote TOOL names, an A2A peer's keys are the peer's remote SKILL ids.
173
+ */
174
+ export type ToolAxesSurface = "mcp" | "a2a";
175
+ /**
176
+ * {@link readToolAxes}'s answer. `ok: true` carries the declaration as a PLAIN COPY the reader built from
171
177
  * what it judged (never the caller's own object); `toolAxes` is absent when the entry declares nothing — no
172
178
  * key at all, or only empty per-tool objects (which the engine reads the same as no declaration). `ok: false`
173
- * carries one sentence naming the tool and the member that did not fit, never a value.
179
+ * carries one sentence naming the tool (or skill) and the member that did not fit, never a value.
174
180
  */
175
- export type McpToolAxesRead = {
181
+ export type ToolAxesRead = {
176
182
  ok: true;
177
183
  toolAxes?: ToolAxes;
178
184
  } | {
@@ -180,18 +186,82 @@ export type McpToolAxesRead = {
180
186
  problem: string;
181
187
  };
182
188
  /**
183
- * The one reader of a raw {@link McpServerSpec.toolAxes} declaration — the engine's preparation door and
184
- * every host that assembles the field from JSON ask this same function. Absent ⇒ `{ ok: true }` (nothing
185
- * declared). Anything else must be an object of per-tool objects whose members are exactly `effect` /
189
+ * Where a declaration stops being one: the path from the declaration's root — a list index as a number, a member name as a string — and
190
+ * the clause that says what is there ("it contains itself; …"): a kind and a remedy, never a value the declarer wrote.
191
+ */
192
+ export interface DeclarationFault {
193
+ readonly path: readonly (string | number)[];
194
+ readonly problem: string;
195
+ }
196
+ /** {@link receiveDeclaration}'s answer: the engine's own frozen copy, or where and why the declaration could not be received. */
197
+ export type DeclarationRead = {
198
+ readonly ok: true;
199
+ readonly value: unknown;
200
+ } | {
201
+ readonly ok: false;
202
+ readonly fault: DeclarationFault;
203
+ };
204
+ /**
205
+ * A receipt's refusal of a declaration (or of the list that carries declarations), said as a declaration refusal says it. The receipt's
206
+ * path does not say which kind of container each name came from, so a segment of digits alone is read as a list index.
207
+ */
208
+ export declare function declarationFaultOf(fault: ReceiptFault): DeclarationFault;
209
+ /** What a declaration's own type fixes at one path, beyond being plain data: a string there, or a record of names to strings. */
210
+ export interface DeclarationPathRule {
211
+ readonly path: readonly string[];
212
+ readonly holds: "a string" | "names to strings";
213
+ /** The member must be there (a declaration with no value at the path is refused, as one with a value of another kind is). */
214
+ readonly required?: true;
215
+ }
216
+ /**
217
+ * Per surface, every path its declaration's type fixes beyond plain data — the declarer's `name` (a string, always there) and every record
218
+ * the type writes as `Record<string, string>`: an MCP server's `transport.env` (a stdio server's environment) and `transport.headers` (an
219
+ * HTTP server's headers); an A2A peer's `headers`. A path that is not its surface's (an A2A peer's `transport`) is not judged by another
220
+ * surface's row: it is a member the surface does not read.
221
+ */
222
+ export declare const DECLARATION_PATH_RULES: Readonly<Record<ToolAxesSurface, readonly DeclarationPathRule[]>>;
223
+ /**
224
+ * The one reading of a declaration a caller hands the engine — an MCP server's or an A2A peer's entry (the snapshot), a `toolAxes`
225
+ * record ({@link readToolAxes}), a lock declaration: the engine's one receipt of a caller's object in its declaration mode, so the
226
+ * value is plain data held to the engine's one depth ({@link MODS_VALUE_DEPTH} levels below it) and every own member is read once, then, when the copy is a record, the declaration's own
227
+ * vocabulary over the copy (a list holds names; no member is named `__proto__`; each path `rules` names holds what its rule says — an
228
+ * entry's rules are its surface's {@link DECLARATION_PATH_RULES}). The copy is the answer — the caller's object is never handed on and
229
+ * never read again. A copy that is not a record is answered as it is, for the reader that expected a record to word.
230
+ */
231
+ export declare function receiveDeclaration(declared: unknown, rules?: readonly DeclarationPathRule[]): DeclarationRead;
232
+ /**
233
+ * The one reader of a raw `toolAxes` declaration — {@link McpServerSpec.toolAxes} and {@link A2aServerSpec.toolAxes} are the same
234
+ * declaration, and the engine's preparation door, its materialization of an MCP server and of an A2A peer, and every host that
235
+ * assembles the field from JSON ask this same function. `at` names the surface the declaration sits on, because that decides what its
236
+ * keys are called in the sentence: `"mcp"` — the server's remote tools; `"a2a"` — the peer's remote skills. Absent ⇒ `{ ok: true }`
237
+ * (nothing declared). Anything else must be an object of per-tool objects whose members are exactly `effect` /
186
238
  * `egress` / `irreversibility` with legal values; a declaration of any other shape, an empty tool name, an
187
239
  * unknown member, a word outside the vocabulary or a non-boolean `egress` on ANY one tool refuses the whole
188
240
  * declaration (`ok: false`) — the good tools are not kept and nothing is defaulted, since a caller axis is
189
241
  * two-way (it can vouch a tool down as well as mark it up) and a silently lost half is a trust root that is
190
242
  * not in force. The reader judges SHAPE only; whether the axes contradict each other (`egress` over a
191
- * non-write effect) is the ingestion's own arithmetic. The reader is total: a getter, a proxy trap or a tag
192
- * that throws while the declaration is read is a refusal that says so (never the thrown text), not a throw.
243
+ * non-write effect) is the ingestion's own arithmetic. The declaration is read once, as plain data, by {@link receiveDeclaration} — a
244
+ * Proxy, a map, a class instance, a record built on a prototype or in another realm, a getter that throws, a structure that contains
245
+ * itself, a tool named `__proto__` are refusals that say where they sit (never the thrown text), not a throw — and judged on that copy.
246
+ * `at` is the caller's own word, not the declarer's: one outside {@link ToolAxesSurface} throws `config.invalid_argument` naming the
247
+ * surfaces, whatever is declared.
248
+ */
249
+ export declare function readToolAxes(declared: unknown, at: ToolAxesSurface): ToolAxesRead;
250
+ /** {@link readToolAxes} for an MCP server's declaration — the entry a host that mounts MCP servers asks. */
251
+ export declare function readMcpToolAxes(declared: unknown): ToolAxesRead;
252
+ /**
253
+ * The word a sentence about one declaration of the surface `at` names the declarer by — `MCP server`, `A2A peer`. The same row the
254
+ * `toolAxes` refusal below reads, so every sentence the engine writes about a declaration it cannot read calls the declarer one thing.
255
+ */
256
+ export declare function declarerWord(at: ToolAxesSurface): string;
257
+ /**
258
+ * The refusal an entry's unreadable `toolAxes` becomes (`config.invalid_argument`), ONE sentence for every surface that declares them:
259
+ * the subject is the surface's declarer word and `name` (already quoted and bounded by the caller: `MCP server "db"`, `A2A peer "agent"`),
260
+ * `problem` is {@link readToolAxes}'s own sentence.
193
261
  */
194
- export declare function readMcpToolAxes(declared: unknown): McpToolAxesRead;
262
+ export declare function toolAxesRefusal(at: ToolAxesSurface, name: string, problem: string): Error & {
263
+ code: "config.invalid_argument";
264
+ };
195
265
  /** The identity of one mount of a declared server, as the host minted it ({@link McpServerSpec.fingerprint}). */
196
266
  export interface McpMountIdentity {
197
267
  readonly fingerprint: string;
@@ -242,6 +312,14 @@ export interface A2aServerSpec {
242
312
  * raise it — e.g. `{ egress: false, effect: "read" }` vouches that a skill only reads. Note that
243
313
  * lowering `effect` WITHOUT clearing `egress` is a contradiction prepare-task refuses by design (an
244
314
  * external write must be repeat-unsafe).
315
+ *
316
+ * THE SHAPE IS JUDGED, NOT TRUSTED, by the same reader as {@link McpServerSpec.toolAxes} ({@link readToolAxes} with `at: "a2a"`) and
317
+ * with the same disposition: a task's preparation door runs it on every peer of `TaskSpec.a2a`, and a declaration it cannot read
318
+ * refuses the whole prepare (`config.invalid_argument`, subject `A2A peer "<name>"`) before any peer is dialed — one bad skill refuses
319
+ * the task, a bad key is never dropped and a bad word never defaults. The materialization entrance (`materializeA2aTools`, the library
320
+ * entrance) asks it once per peer, when it reads that peer's declaration: there a declaration it cannot read fails THAT peer's dial
321
+ * (never dialed, a warning whose cause is the same refusal), its siblings unaffected; a refresh does not ask again, it dials the copy
322
+ * that was judged (a peer refused at the entrance is refused again by every refresh, from the stored refusal).
245
323
  */
246
324
  toolAxes?: Record<string, {
247
325
  effect?: ToolEffect;