@sema-agent/core 7.50.1 → 7.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/dist/agents/child-failure.d.ts +5 -16
  3. package/dist/agents/child-failure.js +4 -24
  4. package/dist/agents/subagent-steps.js +2 -2
  5. package/dist/agents/subagent.d.ts +4 -13
  6. package/dist/agents/subagent.js +94 -204
  7. package/dist/agents/tool-filter.d.ts +2 -2
  8. package/dist/brain/anthropic.d.ts +1 -1
  9. package/dist/brain/anthropic.js +7 -5
  10. package/dist/brain/open-responses.d.ts +1 -1
  11. package/dist/brain/open-responses.js +3 -2
  12. package/dist/brain/openai.d.ts +1 -1
  13. package/dist/brain/openai.js +3 -2
  14. package/dist/brain/request-route.d.ts +2 -2
  15. package/dist/brain/route-adjudicator.d.ts +16 -7
  16. package/dist/brain/route-adjudicator.js +23 -7
  17. package/dist/brain/route-conformance.js +23 -5
  18. package/dist/brain/stream-engine.js +9 -3
  19. package/dist/core/a2a.js +6 -8
  20. package/dist/core/allow-entry.d.ts +45 -26
  21. package/dist/core/allow-entry.js +48 -24
  22. package/dist/core/arg-summary.d.ts +8 -85
  23. package/dist/core/arg-summary.js +4 -386
  24. package/dist/core/ask-origin.d.ts +35 -33
  25. package/dist/core/ask-origin.js +9 -11
  26. package/dist/core/checkpoint-requirements.d.ts +6 -0
  27. package/dist/core/checkpoint-requirements.js +1 -0
  28. package/dist/core/checkpoint-store.d.ts +15 -34
  29. package/dist/core/checkpoint-store.js +5 -4
  30. package/dist/core/classifier-unavailable-arm.d.ts +12 -8
  31. package/dist/core/classifier-unavailable-arm.js +7 -2
  32. package/dist/core/credential-vocabulary.d.ts +3 -2
  33. package/dist/core/credential-vocabulary.js +27 -18
  34. package/dist/core/delegation-frames.d.ts +40 -15
  35. package/dist/core/delegation-frames.js +34 -0
  36. package/dist/core/engine-notice.d.ts +35 -11
  37. package/dist/core/gate-fold.js +9 -7
  38. package/dist/core/gate-lanes.js +6 -4
  39. package/dist/core/gate-outcome.d.ts +87 -11
  40. package/dist/core/gate-outcome.js +171 -61
  41. package/dist/core/governance-codes.d.ts +7 -1
  42. package/dist/core/governance-codes.js +9 -0
  43. package/dist/core/hooks.d.ts +4 -4
  44. package/dist/core/hooks.js +4 -3
  45. package/dist/core/mcp-connection-pool.d.ts +2 -0
  46. package/dist/core/mcp-connection-pool.js +2 -1
  47. package/dist/core/mcp-failure.js +2 -1
  48. package/dist/core/mcp-server-spec.d.ts +27 -5
  49. package/dist/core/mcp.d.ts +13 -38
  50. package/dist/core/mcp.js +28 -23
  51. package/dist/core/media-byte-cap.d.ts +25 -0
  52. package/dist/core/media-byte-cap.js +18 -2
  53. package/dist/core/memory-engine/consolidation-driver.js +1 -1
  54. package/dist/core/mode-policy.d.ts +25 -1
  55. package/dist/core/mode-policy.js +28 -13
  56. package/dist/core/own-property-receipt.d.ts +18 -0
  57. package/dist/core/own-property-receipt.js +39 -0
  58. package/dist/core/permission-rule-model.d.ts +32 -11
  59. package/dist/core/permission-rule-model.js +12 -7
  60. package/dist/core/persisted-rule-arms.d.ts +3 -3
  61. package/dist/core/prompt-section-seat.d.ts +54 -0
  62. package/dist/core/prompt-section-seat.js +49 -0
  63. package/dist/core/protocol-http-auth.d.ts +44 -0
  64. package/dist/core/protocol-http-auth.js +45 -0
  65. package/dist/core/read-face-seats.d.ts +30 -0
  66. package/dist/core/read-face-seats.js +38 -0
  67. package/dist/core/redaction-passes.d.ts +82 -0
  68. package/dist/core/redaction-passes.js +378 -0
  69. package/dist/core/remote-env.d.ts +4 -3
  70. package/dist/core/resume-startup-cause.d.ts +56 -0
  71. package/dist/core/resume-startup-cause.js +43 -0
  72. package/dist/core/rule-denial-sentence.d.ts +2 -2
  73. package/dist/core/runner/announce-once-ledger.js +2 -2
  74. package/dist/core/runner/classifier-ladder.d.ts +17 -13
  75. package/dist/core/runner/classifier-ladder.js +12 -10
  76. package/dist/core/runner/construction-doors.js +9 -20
  77. package/dist/core/runner/contracts.d.ts +4 -4
  78. package/dist/core/runner/gate-exit.js +1 -1
  79. package/dist/core/runner/memory-consolidation-write-judge.d.ts +53 -9
  80. package/dist/core/runner/memory-consolidation-write-judge.js +39 -1
  81. package/dist/core/runner/permission-rule-lanes.d.ts +8 -13
  82. package/dist/core/runner/person-path-rows.d.ts +4 -3
  83. package/dist/core/runner/policy-throw.js +4 -4
  84. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
  85. package/dist/core/runner/prepare-caps-and-workflow.js +8 -6
  86. package/dist/core/runner/prepare-gate-stations.js +3 -3
  87. package/dist/core/runner/prepare-hands-readface.js +1 -19
  88. package/dist/core/runner/prepare-inherited-gate.js +10 -9
  89. package/dist/core/runner/prepare-policy-chain.d.ts +3 -5
  90. package/dist/core/runner/prepare-policy-chain.js +10 -13
  91. package/dist/core/runner/prepare-project-context.d.ts +4 -4
  92. package/dist/core/runner/prepare-project-context.js +2 -1
  93. package/dist/core/runner/prepare-prompt-assembly.js +1 -0
  94. package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -1
  95. package/dist/core/runner/prepare-prompt-inputs.js +4 -4
  96. package/dist/core/runner/prepare-protocol-tools.d.ts +4 -3
  97. package/dist/core/runner/prepare-protocol-tools.js +3 -2
  98. package/dist/core/runner/prepare-task.js +6 -4
  99. package/dist/core/runner/prepare-turn-wiring.d.ts +16 -13
  100. package/dist/core/runner/prepare-turn-wiring.js +8 -7
  101. package/dist/core/runner/run-harness-handlers.js +2 -2
  102. package/dist/core/runner/run-leg.js +10 -9
  103. package/dist/core/runner/run-settle-and-teardown.js +5 -5
  104. package/dist/core/runner/run-turn-boundary.js +3 -2
  105. package/dist/core/runner/runtask.d.ts +1 -2
  106. package/dist/core/runner/runtask.js +8 -12
  107. package/dist/core/runner/session-rule-policy.d.ts +15 -11
  108. package/dist/core/runner/session-rule-policy.js +43 -33
  109. package/dist/core/runner/stream-lifecycle-verbs.js +2 -2
  110. package/dist/core/runner/stream-settle-backstop.js +2 -1
  111. package/dist/core/runner/sub-agent-deny-lists.d.ts +12 -8
  112. package/dist/core/runner/sub-agent-deny-lists.js +23 -12
  113. package/dist/core/runner/terminal-account.js +3 -3
  114. package/dist/core/runner/tool-output-projection.js +3 -8
  115. package/dist/core/runner/tool-result-media-seat.d.ts +19 -0
  116. package/dist/core/runner/tool-result-media-seat.js +13 -0
  117. package/dist/core/runner/turn-attachments.d.ts +13 -12
  118. package/dist/core/runner/turn-attachments.js +11 -7
  119. package/dist/core/runner-deps.d.ts +4 -3
  120. package/dist/core/safety-merge-corpus.js +21 -21
  121. package/dist/core/secret-env.js +1 -1
  122. package/dist/core/session-policy-store.d.ts +89 -72
  123. package/dist/core/session-policy-store.js +100 -93
  124. package/dist/core/shell-read-silence.d.ts +38 -4
  125. package/dist/core/shell-read-silence.js +102 -0
  126. package/dist/core/side-query.d.ts +7 -8
  127. package/dist/core/skill-grant-binding.d.ts +32 -11
  128. package/dist/core/skill-grant-binding.js +11 -3
  129. package/dist/core/skill-grant.d.ts +4 -3
  130. package/dist/core/skill-grant.js +5 -4
  131. package/dist/core/skills-directory.d.ts +12 -1
  132. package/dist/core/skills-directory.js +17 -8
  133. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  134. package/dist/core/store-contracts/checkpoint-store-contract.js +3 -0
  135. package/dist/core/swappable-deps.d.ts +15 -1
  136. package/dist/core/swappable-deps.js +25 -1
  137. package/dist/core/synthetic-read-verdict.d.ts +16 -14
  138. package/dist/core/synthetic-read-verdict.js +28 -8
  139. package/dist/core/task-notification.d.ts +13 -3
  140. package/dist/core/task-notification.js +12 -1
  141. package/dist/core/task-spec.d.ts +14 -15
  142. package/dist/core/tool-name-readings.d.ts +0 -4
  143. package/dist/core/tool-name-readings.js +0 -1
  144. package/dist/core/tool-policy.d.ts +40 -9
  145. package/dist/core/tool-policy.js +25 -7
  146. package/dist/core/types.d.ts +3 -3
  147. package/dist/core/types.js +1 -1
  148. package/dist/core/untrusted-egress.d.ts +60 -11
  149. package/dist/core/untrusted-egress.js +120 -9
  150. package/dist/core/untrusted-text.js +16 -0
  151. package/dist/core/wiring-manifest.d.ts +22 -8
  152. package/dist/core/wiring-manifest.js +12 -4
  153. package/dist/engine/harness/agent-harness.d.ts +2 -1
  154. package/dist/engine/harness/agent-harness.js +4 -1
  155. package/dist/engine/harness/types.d.ts +3 -0
  156. package/dist/engine/llm/types.d.ts +7 -9
  157. package/dist/engine/loop/agent-loop.d.ts +8 -3
  158. package/dist/engine/loop/agent-loop.js +26 -9
  159. package/dist/engine/loop/types.d.ts +10 -0
  160. package/dist/index.d.ts +17 -5
  161. package/dist/index.js +14 -5
  162. package/dist/orchestration/run-workflow-tool.js +8 -8
  163. package/dist/orchestration/workflow.js +2 -2
  164. package/dist/prompt-assembly/assemble.d.ts +3 -1
  165. package/dist/prompt-assembly/assemble.js +6 -0
  166. package/dist/prompts/cowork.d.ts +42 -0
  167. package/dist/prompts/cowork.js +529 -0
  168. package/dist/prompts/default.d.ts +14 -1
  169. package/dist/prompts/default.js +3 -0
  170. package/dist/stores/file/session-policy-store.d.ts +6 -3
  171. package/dist/stores/file/session-policy-store.js +5 -16
  172. package/dist/tools/artifact/artifact-tool.js +2 -0
  173. package/dist/tools/fs/image-admission.d.ts +57 -4
  174. package/dist/tools/fs/image-admission.js +85 -0
  175. package/dist/tools/fs/path-containment.d.ts +2 -0
  176. package/dist/tools/fs/path-containment.js +5 -0
  177. package/dist/tools/fs/shell-read-check.d.ts +31 -6
  178. package/dist/tools/fs/shell-read-check.js +2 -4
  179. package/dist/tools/fs/shell-read-environment.d.ts +10 -25
  180. package/dist/tools/fs/shell-read-environment.js +6 -18
  181. package/dist/tools/fs/shell-read-establish.d.ts +3 -2
  182. package/dist/tools/fs/shell-read-establish.js +5 -5
  183. package/dist/tools/fs/shell-read-judge.d.ts +38 -8
  184. package/dist/tools/fs/shell-read-judge.js +12 -10
  185. package/dist/tools/web.js +2 -2
  186. package/package.json +1 -1
  187. package/test/export-surface.snapshot.json +94 -10
  188. package/wire-changes/core@7.50.2.json +42 -0
  189. package/wire-changes/core@7.51.0.json +2421 -0
@@ -933,17 +933,21 @@ function firstEligibleAdmittingRule(rules, commandText, call) {
933
933
  return undefined;
934
934
  }
935
935
  export function findAdmittingRule(rules, call) {
936
+ const coverage = commandCoverageOf(rules, call);
937
+ return "covering" in coverage ? coverage.covering : undefined;
938
+ }
939
+ export function commandCoverageOf(rules, call) {
936
940
  const shape = ruleLaneShapeOf(call.command, MATCH_READING);
937
941
  if ("reject" in shape)
938
- return undefined;
942
+ return { uncovered: undefined };
939
943
  const whole = firstEligibleAdmittingRule(rules, call.command, call);
940
944
  if (whole !== undefined)
941
- return [whole];
945
+ return { covering: [whole] };
942
946
  const cdDirs = resolveCdSegmentDirectories(shape.segments, call.execCwd ?? call.cwd);
943
947
  if (shape.segments.length < 2) {
944
948
  const dir = cdDirs[0];
945
949
  const lone = dir !== undefined ? firstEligibleDirectoryRule(rules, dir, call) : undefined;
946
- return lone !== undefined ? [lone] : undefined;
950
+ return lone !== undefined ? { covering: [lone] } : { uncovered: shape.segments[0] ?? call.command };
947
951
  }
948
952
  const covering = [];
949
953
  const lineObjection = readOnlyLineObjection(call.command, call.execCwd ?? call.cwd);
@@ -951,14 +955,14 @@ export function findAdmittingRule(rules, call) {
951
955
  const dir = cdDirs[i];
952
956
  const first = firstEligibleAdmittingRule(rules, segment, call) ?? (dir !== undefined ? firstEligibleDirectoryRule(rules, dir, call) : undefined);
953
957
  if (first === undefined) {
954
- if (readsOnlyAsSegment(segment, lineObjection, call.command))
958
+ if (call.posture !== "narrowing" && readsOnlyAsSegment(segment, lineObjection, call.command))
955
959
  continue;
956
- return undefined;
960
+ return { uncovered: segment };
957
961
  }
958
962
  if (!covering.includes(first))
959
963
  covering.push(first);
960
964
  }
961
- return covering.length > 0 ? covering : undefined;
965
+ return covering.length > 0 ? { covering } : { uncovered: undefined };
962
966
  }
963
967
  function readsOnlyAsSegment(segment, lineObjection, whole) {
964
968
  return lineObjection === undefined && !movesShellCursor(segment) && readOnlyShellVerdict(segment, undefined, undefined, () => lineDescriptorsOfText(whole)).readOnly;
@@ -1088,8 +1092,9 @@ export const UNCOVERED_SEGMENT_REASON_BASELINE = {
1088
1092
  };
1089
1093
  export const RULE_OFFERS_ABSENCE_BASELINE = {
1090
1094
  mandated: "This approval is required by the deployment's policy each time; a rule cannot waive it.",
1095
+ 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.",
1096
+ 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.",
1091
1097
  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.",
1092
- shadowed: "A rule you already have matches this call and still asks — that standing rule keeps deciding, so a new rule minted here would change nothing; review your existing rules instead.",
1093
1098
  };
1094
1099
  export function suggestRulesForCommand(command, ctx) {
1095
1100
  const shape = ruleLaneShapeOf(command, OFFER_READING);
@@ -48,7 +48,7 @@ export declare function disclosedRuleSet(rules: readonly PersistedRuleHitRule[])
48
48
  * reads the deny (the gate's exit, the approved-edit re-check, a resumed leg's belt) mints the same card from the same fact.
49
49
  * · ASK — on an allow, a fresh ask is minted carrying `matchedAskRule` (the marker an explicit `ask`
50
50
  * permission rule stamps, so the ask's origin derives as `ask_rule`: the classifier never answers
51
- * it, the persisted ALLOW arm never clears it, and the rule-offer face reports `shadowed`); on an
51
+ * it, the persisted ALLOW arm never clears it, and the rule-offer face reports `ask_rule_matched`); on an
52
52
  * ask, the marker is stamped if absent and the message names the rule — the standing question
53
53
  * outranks a remembered yes across lanes, exactly as the DSL's own order says within one; a deny
54
54
  * stays a deny.
@@ -62,8 +62,8 @@ export declare function disclosedRuleSet(rules: readonly PersistedRuleHitRule[])
62
62
  * or ask row inside it may forbid this call and nobody can tell, so it fails CLOSED the same way, to the question
63
63
  * only a person answers — an allow becomes an ask carrying `requiresRealApproval`, an ask acquires the bit, a deny stays
64
64
  * a deny. Every call the lane judges is in this state until a read succeeds again (the lane announces the skipped store,
65
- * once per run, and reads again on the next call). The two forms differ in WHICH fact the gate records (the call form
66
- * is the class `rule_unreadable`; the store form `governance_unavailable` — both bypass-immune) and in the
65
+ * once per run, and reads again on the next call). The two forms differ in WHICH fact the gate records (one class for
66
+ * both, `rule_unreadable`, bypass-immune) and in the
67
67
  * resumed-call belt, which vetoes only the call form: a person already answered that call's question.
68
68
  * An allow hit (or no hit) returns the decision untouched: the allow arm is the station's.
69
69
  */
@@ -0,0 +1,54 @@
1
+ /** The named sections whose entries the host may dispose of — a closed set. */
2
+ export declare const DISPOSABLE_PROMPT_SECTIONS: readonly ["core/discovery.mcp-instructions"];
3
+ export type DisposablePromptSection = (typeof DISPOSABLE_PROMPT_SECTIONS)[number];
4
+ /** One entry of a named section, as the engine computed it, before it reaches the model. A frozen copy:
5
+ * writing to it changes nothing. */
6
+ export interface PromptSectionEntry {
7
+ readonly section: DisposablePromptSection;
8
+ /** The entry's key within the section: the MCP server's declared name. */
9
+ readonly server: string;
10
+ /** The text the engine would place: the server's `instructions`, already neutralized. */
11
+ readonly text: string;
12
+ /** The tools this leg mounted from that server, by their namespaced names. */
13
+ readonly mountedTools: readonly string[];
14
+ }
15
+ /** The host's answer. `undefined` = include the entry as computed. */
16
+ export type PromptSectionDisposition = {
17
+ readonly op: "drop";
18
+ } | {
19
+ readonly op: "replace";
20
+ readonly text: string;
21
+ };
22
+ /** Who wrote the text an entry carries into the model's view. */
23
+ export type PromptSectionAuthor = "server" | "host";
24
+ export interface PromptSectionSeat {
25
+ /**
26
+ * Answer the disposition of one entry of a named section ({@link DISPOSABLE_PROMPT_SECTIONS}): return
27
+ * nothing to include it, `{ op: "drop" }` to leave it out of the model's view on every lane, or
28
+ * `{ op: "replace", text }` to place the host's own non-blank text instead — host text, rendered
29
+ * without the untrusted-data fence the server's text rides in, under the same per-entry bound and the same
30
+ * disclosed cut. Called once each time the entry's text
31
+ * enters the run (a re-dial asks again). Synchronous. A throw, a Promise, or any other value includes
32
+ * the entry and raises a coded operator warning (`prompt.section_answer_invalid`).
33
+ */
34
+ section?(entry: PromptSectionEntry): PromptSectionDisposition | undefined;
35
+ }
36
+ /** One server's entry of the MCP instructions section as the run carries it, after the host's disposition. */
37
+ export interface McpInstructionsEntry {
38
+ readonly server: string;
39
+ readonly text: string;
40
+ readonly author: PromptSectionAuthor;
41
+ }
42
+ /** What one fold leaves in the run: the entry's text and author, absent when dropped, plus the defect
43
+ * sentence when the answer could not be read. */
44
+ export interface DisposedSectionEntry {
45
+ readonly kept?: {
46
+ readonly text: string;
47
+ readonly author: PromptSectionAuthor;
48
+ };
49
+ readonly defect?: string;
50
+ }
51
+ /** Read the seat's answer for one entry. Each member of the answer is read once. */
52
+ export declare function disposeSectionEntry(seat: PromptSectionSeat | undefined, entry: PromptSectionEntry): DisposedSectionEntry;
53
+ /** The operator warning for an answer the fold could not read. */
54
+ export declare function sectionAnswerWarning(defect: string): Error;
@@ -0,0 +1,49 @@
1
+ import { thrownText } from "./thrown-value.js";
2
+ import { inlineUntrusted } from "./untrusted-text.js";
3
+ import { egressText } from "./untrusted-egress.js";
4
+ export const DISPOSABLE_PROMPT_SECTIONS = Object.freeze(["core/discovery.mcp-instructions"]);
5
+ const ANSWER_KEYS = { drop: ["op"], replace: ["op", "text"] };
6
+ const FOREIGN_VALUE_MAX = 200;
7
+ const STRAY_KEYS_NAMED = 5;
8
+ const DEFECT_MAX = 600;
9
+ const foreign = (s, max) => inlineUntrusted(egressText("same-principal", s), max);
10
+ export function disposeSectionEntry(seat, entry) {
11
+ const computed = { text: entry.text, author: "server" };
12
+ if (seat?.section === undefined)
13
+ return { kept: computed };
14
+ const offered = Object.freeze({ ...entry, mountedTools: Object.freeze([...entry.mountedTools]) });
15
+ const where = `prompt section "${entry.section}" entry "${inlineUntrusted(entry.server, 160)}"`;
16
+ const unread = (why) => ({ kept: computed, defect: inlineUntrusted(`${where}: ${why} — the entry is included as the engine computed it`, DEFECT_MAX) });
17
+ try {
18
+ const answer = seat.section(offered);
19
+ if (answer === undefined)
20
+ return { kept: computed };
21
+ if (answer === null || typeof answer !== "object")
22
+ return unread(`the answer is ${answer === null ? "null" : typeof answer}, not undefined, { op: "drop" } or { op: "replace", text }`);
23
+ const record = answer;
24
+ if (typeof record["then"] === "function") {
25
+ void Promise.resolve(answer).catch(() => undefined);
26
+ return unread("the section seat returned a Promise; it is synchronous");
27
+ }
28
+ const op = record["op"];
29
+ const text = record["text"];
30
+ if (op !== "drop" && op !== "replace")
31
+ return unread(`the answer's op is ${typeof op === "string" ? `"${foreign(op, 40)}"` : typeof op}, not "drop" or "replace"`);
32
+ const stray = Object.keys(record).filter((k) => !ANSWER_KEYS[op].includes(k));
33
+ if (stray.length > 0) {
34
+ const named = stray.slice(0, STRAY_KEYS_NAMED).map((k) => foreign(k, 40)).join(", ");
35
+ return unread(`a "${op}" answer carries ${named}${stray.length > STRAY_KEYS_NAMED ? ` and ${stray.length - STRAY_KEYS_NAMED} more` : ""}`);
36
+ }
37
+ if (op === "drop")
38
+ return {};
39
+ if (typeof text !== "string" || text.trim() === "")
40
+ return unread(`a "replace" answer needs non-blank text`);
41
+ return { kept: { text, author: "host" } };
42
+ }
43
+ catch (err) {
44
+ return unread(`the section seat (or reading its answer) threw (${foreign(thrownText(err), FOREIGN_VALUE_MAX)})`);
45
+ }
46
+ }
47
+ export function sectionAnswerWarning(defect) {
48
+ return Object.assign(new Error(defect), { code: "prompt.section_answer_invalid" });
49
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The HTTP credentials a protocol declaration carries — its static `headers` and the per-task principal
3
+ * header (`principalHeader`) — and the one rule they obey, for MCP and A2A alike: they travel only to the
4
+ * endpoints the declaration names, and every header name has exactly one owner, compared the way HTTP
5
+ * compares names (case-insensitively). The principal header's owner is the engine: it sends the task's
6
+ * principal under that name, or nothing under it when the task has none.
7
+ *
8
+ * A declaration that breaks the rule is that peer's defect: the resolver throws inside the peer's own dial,
9
+ * so the peer is skipped with a coded warning and its siblings still mount. The MCP transport binds its
10
+ * endpoint by dialing only the declared URL with redirects not followed (`declaredEndpointFetch`); the A2A
11
+ * peer, whose card may name another endpoint, is bound by {@link assertEndpointBound} and by
12
+ * {@link redirectPolicy}.
13
+ */
14
+ import type { ProtocolId } from "./protocol-table.js";
15
+ /** The per-peer HTTP auth declaration every protocol spec carries in the same two fields. */
16
+ export interface ProtocolHttpAuth {
17
+ headers?: Record<string, string>;
18
+ principalHeader?: string;
19
+ }
20
+ /** The first header name (any case) the declaration gives two owners, with both spellings. */
21
+ export declare function headerOwnershipCollision(t: ProtocolHttpAuth): {
22
+ readonly first: string;
23
+ readonly second: string;
24
+ } | undefined;
25
+ /**
26
+ * Resolve the HTTP request headers for one remote peer of `protocol`: the static headers, plus the
27
+ * principal under `principalHeader` when both are present. Throws `config.protocol_header_collision` when
28
+ * one header name has two owners (two static spellings, or a static entry under the principal header's
29
+ * name — which would otherwise be sent merged beside the principal, or alone when the task has none), and
30
+ * on a principal carrying CR/LF. Callers invoke it INSIDE their per-peer boundary.
31
+ */
32
+ export declare function resolveProtocolHttpHeaders(protocol: ProtocolId, t: ProtocolHttpAuth, principal?: string): Readonly<Record<string, string>> | undefined;
33
+ /** The engine's default request headers under the declared ones: a declared name, in any case, owns that header
34
+ * (the default of the same name is not sent beside it). */
35
+ export declare function underDeclaredHeaders(defaults: Readonly<Record<string, string>>, declared: Record<string, string> | undefined): Record<string, string>;
36
+ /** Whether a resolved header set carries anything the declaration bound to its endpoints. */
37
+ export declare function carriesDeclaredHeaders(headers: Record<string, string> | undefined): boolean;
38
+ /** A request carrying declared headers does not follow a redirect (the next hop is not a declared endpoint). */
39
+ export declare function redirectPolicy(headers: Record<string, string> | undefined): "manual" | "follow";
40
+ /**
41
+ * Refuse an endpoint a peer advertised (an A2A card's interface URL) when it lies outside every origin the
42
+ * declaration names and the peer's requests carry declared headers: `config.protocol_endpoint_unbound`.
43
+ */
44
+ export declare function assertEndpointBound(protocol: ProtocolId, peer: string, endpoint: string, declared: readonly string[], headers: Record<string, string> | undefined): void;
@@ -0,0 +1,45 @@
1
+ import { inlineUntrusted } from "./untrusted-text.js";
2
+ export function headerOwnershipCollision(t) {
3
+ const owners = new Map();
4
+ for (const name of [...Object.keys(t.headers ?? {}), ...(t.principalHeader ? [t.principalHeader] : [])]) {
5
+ const prior = owners.get(name.toLowerCase());
6
+ if (prior !== undefined)
7
+ return { first: prior, second: name };
8
+ owners.set(name.toLowerCase(), name);
9
+ }
10
+ return undefined;
11
+ }
12
+ export function resolveProtocolHttpHeaders(protocol, t, principal) {
13
+ const principalHeader = t.principalHeader;
14
+ const declared = t.headers;
15
+ const own = { ...(declared !== undefined ? { headers: { ...declared } } : {}), ...(principalHeader !== undefined ? { principalHeader } : {}) };
16
+ const collision = headerOwnershipCollision(own);
17
+ if (collision !== undefined) {
18
+ throw Object.assign(new Error(`${protocol}: the declaration names the header "${inlineUntrusted(collision.first, 80)}" twice ("${inlineUntrusted(collision.second, 80)}" — header names are case-insensitive); ${principalHeader !== undefined && collision.second === principalHeader ? "the principal header is the engine's to send, so remove the static entry" : "keep one spelling"} — this peer is not dialed`), { code: "config.protocol_header_collision" });
19
+ }
20
+ if (principalHeader && principal !== undefined) {
21
+ if (/[\r\n]/.test(principal))
22
+ throw new Error(`${protocol}: principal must not contain CR/LF (header injection guard)`);
23
+ return Object.freeze({ ...own.headers, [principalHeader]: principal });
24
+ }
25
+ return own.headers === undefined ? undefined : Object.freeze(own.headers);
26
+ }
27
+ export function underDeclaredHeaders(defaults, declared) {
28
+ const owned = new Set(Object.keys(declared ?? {}).map((k) => k.toLowerCase()));
29
+ return { ...Object.fromEntries(Object.entries(defaults).filter(([k]) => !owned.has(k.toLowerCase()))), ...declared };
30
+ }
31
+ export function carriesDeclaredHeaders(headers) {
32
+ return headers !== undefined && Object.keys(headers).length > 0;
33
+ }
34
+ export function redirectPolicy(headers) {
35
+ return carriesDeclaredHeaders(headers) ? "manual" : "follow";
36
+ }
37
+ export function assertEndpointBound(protocol, peer, endpoint, declared, headers) {
38
+ if (!carriesDeclaredHeaders(headers))
39
+ return;
40
+ const origins = declared.map((u) => new URL(u).origin);
41
+ const at = new URL(endpoint).origin;
42
+ if (origins.includes(at))
43
+ return;
44
+ throw Object.assign(new Error(`${protocol}: peer "${inlineUntrusted(peer, 80)}" advertises the endpoint origin ${inlineUntrusted(at, 120)}, outside the origins its declaration names (${origins.map((o) => inlineUntrusted(o, 120)).join(", ")}); the declared headers travel only to those — declare that origin as the peer's url (and the card's location as cardUrl) or drop the headers`), { code: "config.protocol_endpoint_unbound" });
45
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The value gates of the read-face list seats, shared by the two places that admit a value into them: the
3
+ * prepare door (every leg reads the seats it was handed) and the hot-swap door (`Runner.swapDeps`). One gate,
4
+ * so a value the swap admits is a value every later prepare admits.
5
+ */
6
+ import { type ReadDenyEntry } from "../tools/fs/index.js";
7
+ /** Array-shape door for the read-face list seats (the tool-face door's twin).
8
+ * These seats are typed `readonly string[]` but arrive from wire JSON / plain-JS deployments with no
9
+ * runtime narrowing, and every consumer ITERATES them — `for (const dir of dirs)` and spread.
10
+ * A bare string satisfies both iteration protocols CHARACTER BY CHARACTER, so `"/tmp/x"` admits the
11
+ * single-character root `"/"` into a write-capable allowlist and a string deny "pattern" dissolves
12
+ * into meaningless one-character entries with zero refusal. Garbage must refuse loudly at the door,
13
+ * never be consumed letter by letter. */
14
+ export declare function assertStringArraySeat(value: unknown, seat: string, entryLaw: "strings" | "compiler-owned"): void;
15
+ /** The three DEPLOYMENT read-deny seats. */
16
+ export declare const READ_DENY_DEPLOYMENT_SEATS: readonly ["readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude"];
17
+ export type ReadDenyDeploymentSeat = (typeof READ_DENY_DEPLOYMENT_SEATS)[number];
18
+ /**
19
+ * The copy a read-deny seat keeps of its declared value — the one copy law of both doors that admit one (`new Runner`
20
+ * and the hot swap): a list becomes a frozen list of frozen entry copies (each entry's members read once), so a later
21
+ * write to the caller's list or entry objects cannot move the policy in force; anything else is kept as given, for the
22
+ * seat's value gate to refuse.
23
+ */
24
+ export declare function copyReadDenySeat(value: unknown): unknown;
25
+ /**
26
+ * Screen one deployment read-deny seat's value with the laws a prepare applies to it (the array door and the
27
+ * one compiler's entry law for the additions; the built-in table's tier / row-name law for the other two) and
28
+ * return a copy; `undefined` stays `undefined` (no declaration).
29
+ */
30
+ export declare function screenReadDenySeat(seat: ReadDenyDeploymentSeat, value: unknown): readonly ReadDenyEntry[] | readonly string[] | undefined;
@@ -0,0 +1,38 @@
1
+ import { compileReadDeny, readDenyBuiltinConfigOf } from "../tools/fs/index.js";
2
+ export function assertStringArraySeat(value, seat, entryLaw) {
3
+ if (value === undefined)
4
+ return;
5
+ if (!Array.isArray(value)) {
6
+ const e = new Error(`${seat} must be an array or absent (got ${value === null ? "null" : typeof value}) — a non-array here would be iterated character by character, so it refuses loudly instead.`);
7
+ e.code = "config.read_face_seat";
8
+ throw e;
9
+ }
10
+ if (entryLaw !== "strings")
11
+ return;
12
+ const entries = value;
13
+ for (const entry of entries) {
14
+ if (typeof entry !== "string") {
15
+ const e = new Error(`${seat} entries must be strings (got ${entry === null ? "null" : typeof entry}).`);
16
+ e.code = "config.read_face_seat";
17
+ throw e;
18
+ }
19
+ }
20
+ }
21
+ export const READ_DENY_DEPLOYMENT_SEATS = Object.freeze(["readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude"]);
22
+ export function copyReadDenySeat(value) {
23
+ if (!Array.isArray(value))
24
+ return value;
25
+ return Object.freeze(value.map((e) => (e !== null && typeof e === "object" ? Object.freeze({ ...e }) : e)));
26
+ }
27
+ export function screenReadDenySeat(seat, value) {
28
+ if (value === undefined)
29
+ return undefined;
30
+ if (seat === "readDenyPatterns") {
31
+ assertStringArraySeat(value, `RunnerDeps.${seat}`, "compiler-owned");
32
+ const entries = copyReadDenySeat(value);
33
+ compileReadDeny(entries, seat, { tiers: [] });
34
+ return entries;
35
+ }
36
+ const config = readDenyBuiltinConfigOf(seat === "readDenyBuiltinTiers" ? { tiers: value } : { exclude: value });
37
+ return Object.freeze([...((seat === "readDenyBuiltinTiers" ? config.tiers : config.exclude) ?? [])]);
38
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The redaction pass engine every egress tier runs on, and the core bracket RENDERER of the vocabulary's answer.
3
+ * Which text is a credential is the vocabulary's answer (credential-vocabulary.ts); which passes a text meets is
4
+ * its tier's (untrusted-egress.ts).
5
+ */
6
+ import type { RedactionConfidence, RedactionSource, ValueFamilyRow } from "./credential-vocabulary.js";
7
+ export type { RedactionConfidence, RedactionSource } from "./credential-vocabulary.js";
8
+ /** One redaction hit: what was detected, where (ORIGINAL-input coordinates), how confidently, the marker
9
+ * literal written in its place, which rule table minted it, and — present when a vocabulary row minted it — the
10
+ * value family (the row's stable id; a host-leak hit such as a URL or a path names none). */
11
+ export interface RedactionFinding {
12
+ kind: string;
13
+ span: readonly [number, number];
14
+ confidence: RedactionConfidence;
15
+ marker: string;
16
+ source: RedactionSource;
17
+ family?: string;
18
+ }
19
+ /** The minimum a finding must declare to be summarizable. Both text findings ({@link RedactionFinding})
20
+ * and the env arm's entry findings satisfy it, so {@link summarizeRedactions} is ONE helper rather than
21
+ * one per material kind. */
22
+ export interface SummarizableFinding {
23
+ kind: string;
24
+ confidence: RedactionConfidence;
25
+ }
26
+ /** ANG-3 (envelope declaration) — render findings as ONE human-readable line, e.g.
27
+ * `"3 redactions: prefixed-token (high) x1, url (medium) x1, fs-path (low) x1"`, so an upper layer can
28
+ * declare to its consumer how much was removed and how confidently.
29
+ *
30
+ * PURE, and deliberately only that: it reads the array, returns a string, and injects nothing anywhere.
31
+ * Attaching this line to an output is the caller's decision — a redaction pipeline that silently
32
+ * appended prose to the very text it was asked to bound would break both the size contract
33
+ * (`boundedString`) and the byte-invariance the whole channel is built on.
34
+ *
35
+ * Grouping is by kind AND confidence together: collapsing on kind alone would report whichever
36
+ * confidence happened to arrive first for the rest of that group, which is exactly the silent
37
+ * mis-statement ANG-4 exists to prevent. */
38
+ export declare function summarizeRedactions(findings: readonly SummarizableFinding[]): string;
39
+ /** The optional collector every redaction entry point accepts. `preexistingMarkers` (ANG-11) counts
40
+ * marker-shaped literals ALREADY present in the input before any pass ran — a nonzero value means the
41
+ * text arrived pre-redacted or carries caller-declared markers, either way "marker present" is not proof this
42
+ * pipeline ran. Caller-owned: pass `{ findings: [] }` and read it back after the call. */
43
+ export interface RedactionReport {
44
+ findings: RedactionFinding[];
45
+ preexistingMarkers?: number;
46
+ }
47
+ /** A declared redaction pass — the unit {@link runRedactionPasses} executes and reports on. */
48
+ export interface RedactionPass {
49
+ kind: string;
50
+ confidence: RedactionConfidence;
51
+ marker: string;
52
+ /** The rule table that owns this pass's rule — copied verbatim onto every finding it mints (ANG-11), whoever
53
+ * runs the pass. */
54
+ source: RedactionSource;
55
+ re: RegExp;
56
+ /** A literal replacement (only `$1` substitution is used by these passes) or a replacer fn. */
57
+ replace: string | ((match: string, ...groups: string[]) => string);
58
+ /** The value family a hit belongs to: one id, or — for a pass joining several rows — the id of the row that
59
+ * matches at the hit's offset in the pass input. Absent = the pass is no vocabulary row. */
60
+ family?: string | ((input: string, at: number) => string);
61
+ }
62
+ /**
63
+ * Run the declared passes in order, collecting findings (original-input spans) when a report is given.
64
+ *
65
+ * FORMAT CHARACTERS (#532): the passes scan a VIEW of the input with every `\p{Cf}` removed, so a
66
+ * credential split by a zero-width character is one token to the scanner, not two fragments of which
67
+ * only the first (or neither) matches. The OUTPUT keeps every format character that sits OUTSIDE a
68
+ * replaced span — a joiner in an emoji sequence, a non-joiner in Persian text, a soft hyphen in a word
69
+ * — and drops only those INSIDE the span the marker replaces (they were part of the secret). A
70
+ * format-free input takes the exact historical path (byte-identical output). The view's replacements and
71
+ * every boundary scan's are taken as disjoint spans of their input (a later pass that matched across an
72
+ * earlier pass's inserted text is rendered from the removals instead), so the spans always map back
73
+ * exactly and no scan is skipped.
74
+ */
75
+ export declare function runRedactionPasses(input: string, passes: readonly RedactionPass[], report?: RedactionReport): string;
76
+ /**
77
+ * The egress view rendered as passes, in the vocabulary's order: one pass per group (a group of several rows is ONE
78
+ * leftmost alternation, its hits attributed to the row that matches at the hit's offset). A row whose wide form
79
+ * captures a label keeps it ahead of the marker; `replacers` renders a kind whose marker placement is a policy (the
80
+ * URL userinfo).
81
+ */
82
+ export declare function egressPasses(groups: ReadonlyArray<readonly ValueFamilyRow[]>, replacers?: Readonly<Record<string, (match: string, ...groups: string[]) => string>>): RedactionPass[];