@sema-agent/settings-schema 17.1.0 → 18.0.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 (154) hide show
  1. package/CHANGELOG.md +212 -0
  2. package/README.md +73 -5
  3. package/conformance/VERSION +1 -1
  4. package/conformance/baseline.json +3 -0
  5. package/conformance/cases/c01-entry-salvage-then-typo/expected.json +4 -1
  6. package/conformance/cases/c02-hosts-clamp-stacked/expected.json +4 -1
  7. package/conformance/cases/c03-literal-dotted-top-key/expected.json +4 -1
  8. package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +4 -1
  9. package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +4 -1
  10. package/conformance/cases/c08-limits-unknown-carried-64/expected.json +4 -1
  11. package/conformance/cases/c09-limits-unknown-carried-65/expected.json +4 -1
  12. package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +4 -1
  13. package/conformance/cases/c11-unicode-unknown-keys/expected.json +4 -1
  14. package/conformance/cases/c12-prototype-name-top-keys/expected.json +4 -1
  15. package/conformance/cases/c13-models-field-null/expected.json +4 -1
  16. package/conformance/cases/c14-models-field-empty-array/expected.json +4 -1
  17. package/conformance/cases/c15-models-field-number/expected.json +4 -1
  18. package/conformance/cases/c16-deep-nesting-205/expected.json +3 -0
  19. package/conformance/cases/c17-empty-domain-file/expected.json +3 -0
  20. package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +4 -1
  21. package/conformance/cases/c19-wire-envelope-no-version/expected.json +4 -1
  22. package/conformance/cases/c21-governance-empty-object-wins/expected.json +4 -1
  23. package/conformance/cases/c23-mixed-validity-models/expected.json +4 -1
  24. package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +4 -1
  25. package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +4 -1
  26. package/conformance/cases/c26-hosts-array-1024/expected.json +4 -1
  27. package/conformance/cases/c27-hosts-array-1025/expected.json +4 -1
  28. package/conformance/cases/c28-large-document-over-1mb/expected.json +4 -1
  29. package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +4 -1
  30. package/conformance/cases/c30-version-triple-a-first/expected.json +4 -1
  31. package/conformance/cases/c31-version-triple-b-changed/expected.json +4 -1
  32. package/conformance/cases/c32-version-triple-a-restored/expected.json +4 -1
  33. package/conformance/cases/c33-limits-nested-carried-key/expected.json +4 -1
  34. package/conformance/cases/c34-skills-content-vs-manifest/expected.json +7 -1
  35. package/conformance/cases/c35-portable-domain-file-is-null/expected.json +3 -0
  36. package/conformance/cases/c36-skills-entry-without-content/expected.json +7 -1
  37. package/conformance/cases/c37-skills-field-null/expected.json +6 -0
  38. package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +4 -1
  39. package/conformance/cases/c39-hidden-file-in-config-d/expected.json +4 -1
  40. package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +4 -1
  41. package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +4 -1
  42. package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +4 -1
  43. package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +4 -1
  44. package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +4 -1
  45. package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +4 -1
  46. package/conformance/cases/c46-s811-host-name-url-userinfo-withheld/expected.json +4 -1
  47. package/conformance/cases/c47-s811-roles-key-url-userinfo-withheld/expected.json +4 -1
  48. package/conformance/cases/c48-s935-model-headers-transported/expected.json +4 -1
  49. package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +4 -1
  50. package/conformance/cases/c50-s784-lane-host-legal/expected.json +4 -1
  51. package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +4 -1
  52. package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +4 -1
  53. package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +4 -1
  54. package/conformance/cases/c54-s784-lane-adb-legal/expected.json +4 -1
  55. package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +4 -1
  56. package/conformance/cases/c56-s784-lane-device-legal/expected.json +4 -1
  57. package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +4 -1
  58. package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +4 -1
  59. package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +4 -1
  60. package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +4 -1
  61. package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +4 -1
  62. package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +4 -1
  63. package/conformance/cases/c63-s784-host-two-replicas/expected.json +4 -1
  64. package/conformance/cases/c64-s784-multitenant-local-store/expected.json +4 -1
  65. package/conformance/cases/c65-s784-pg-without-db/expected.json +4 -1
  66. package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +4 -1
  67. package/conformance/cases/c67-s784-retired-field-loud/expected.json +4 -1
  68. package/conformance/cases/c68-s784-device-local-store/expected.json +4 -1
  69. package/conformance/cases/c69-deep-encoded-token-past-every-reading/expected.json +4 -1
  70. package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/expected.json +4 -1
  71. package/conformance/cases/c71-one-scanner-write-reads-display-forms/expected.json +4 -1
  72. package/conformance/cases/c72-dangling-reference-write-seat-refuses/expected.json +4 -1
  73. package/conformance/cases/c73-projects-extended-registration-reads/expected.json +4 -1
  74. package/conformance/cases/c74-projects-over-bound-refused/expected.json +4 -1
  75. package/conformance/cases/c75-projects-patch-remote-uniqueness/expected.json +4 -1
  76. package/conformance/cases/c76-projects-entry-salvage-nested/expected.json +4 -1
  77. package/conformance/cases/c77-prompts-center-artifact-carried/expected.json +4 -1
  78. package/conformance/cases/c78-prompts-empty-bindings-refused/expected.json +4 -1
  79. package/conformance/cases/c79-prompts-bad-worker-artifact-not-salvaged/expected.json +4 -1
  80. package/conformance/cases/c80-prompts-wire-v2-verbatim/expected.json +7 -1
  81. package/conformance/cases/c81-prompts-declaration-bindings-refused/expected.json +4 -1
  82. package/conformance/cases/c82-prompts-v1-catalog-refused/expected.json +4 -1
  83. package/conformance/cases/c83-prompts-bad-overlay-not-salvaged/expected.json +4 -1
  84. package/conformance/cases/c84-sandbox-git-block-carried-verbatim/expected.json +7 -1
  85. package/conformance/cases/c85-sandbox-git-refused-block-still-carried/expected.json +7 -1
  86. package/conformance/cases/c86-a2a-peers-legal/expected.json +135 -0
  87. package/conformance/cases/c86-a2a-peers-legal/input.json +32 -0
  88. package/conformance/cases/c86-a2a-peers-legal/meta.json +5 -0
  89. package/conformance/cases/c87-a2a-credential-refs-not-partially-salvaged/expected.json +123 -0
  90. package/conformance/cases/c87-a2a-credential-refs-not-partially-salvaged/input.json +39 -0
  91. package/conformance/cases/c87-a2a-credential-refs-not-partially-salvaged/meta.json +5 -0
  92. package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +4 -1
  93. package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +4 -1
  94. package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +4 -1
  95. package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +4 -1
  96. package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +4 -1
  97. package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +4 -1
  98. package/conformance/cases/cli04-env-value-with-newline/expected.json +4 -1
  99. package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +4 -1
  100. package/conformance/cases/t01-absent-domain-key-not-null/expected.json +4 -1
  101. package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +4 -1
  102. package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +4 -1
  103. package/conformance/cases/z01-every-domain-empty-object/expected.json +4 -1
  104. package/conformance/cases/z02-every-domain-unknown-key/expected.json +4 -1
  105. package/dist/atomic-write.d.ts +4 -3
  106. package/dist/atomic-write.js +25 -11
  107. package/dist/catalog-manages.d.ts +8 -3
  108. package/dist/catalog-manages.js +16 -7
  109. package/dist/center-refs.d.ts +4 -3
  110. package/dist/center-refs.js +8 -6
  111. package/dist/cli.d.ts +1 -1
  112. package/dist/config-d-file.d.ts +4 -2
  113. package/dist/config-d-file.js +27 -6
  114. package/dist/config-fns.d.ts +6 -1
  115. package/dist/config-fns.js +16 -2
  116. package/dist/config-root-documents.d.ts +2 -2
  117. package/dist/config-root-documents.js +1 -1
  118. package/dist/config-root-node.d.ts +8 -1
  119. package/dist/config-root-node.js +105 -23
  120. package/dist/env-model-table.js +3 -3
  121. package/dist/execution-lanes.d.ts +158 -11
  122. package/dist/execution-lanes.js +76 -11
  123. package/dist/file-edit.d.ts +1 -1
  124. package/dist/file-edit.js +2 -1
  125. package/dist/index.d.ts +1 -1
  126. package/dist/index.js +1 -1
  127. package/dist/mcp-request-entry.d.ts +11 -21
  128. package/dist/mcp-request-entry.js +21 -38
  129. package/dist/node.d.ts +4 -1
  130. package/dist/node.js +6 -2
  131. package/dist/one-time-migrations.js +1 -1
  132. package/dist/portable-domains.d.ts +1 -1
  133. package/dist/portable-domains.js +1 -1
  134. package/dist/remote-exec.d.ts +5 -0
  135. package/dist/remote-exec.js +5 -0
  136. package/dist/reserved-member-name.d.ts +13 -0
  137. package/dist/reserved-member-name.js +14 -0
  138. package/dist/retired-envs.js +2 -0
  139. package/dist/secret-refs.d.ts +1 -1
  140. package/dist/secret-refs.js +5 -1
  141. package/dist/settings-units.d.ts +3 -0
  142. package/dist/settings-units.js +18 -0
  143. package/dist/types.d.ts +70 -41
  144. package/dist/types.js +179 -32
  145. package/dist/url-credential.d.ts +12 -2
  146. package/dist/url-credential.js +164 -9
  147. package/dist/wire-name.d.ts +19 -0
  148. package/dist/wire-name.js +21 -0
  149. package/docs/API-SURFACE.md +31 -6
  150. package/docs/CC-DIVERGENCE.md +6 -0
  151. package/docs/CONSUMER-INTEGRATION.md +127 -7
  152. package/llms-full.txt +40 -17
  153. package/llms.txt +9 -9
  154. package/package.json +1 -1
@@ -10,7 +10,7 @@
10
10
  * 判据与原因句逐字等于消费方请求腿的那一份(钉:`test/mcp-request-entry-14.0.0.test.ts` 逐例搬了消费方既有钉的夹具与期望)。
11
11
  * 本模块只答「这一格的形」;拒绝的整句(条目下标、条目名的回显清洗、收尾句)与「拒还是降级」的后果仍是消费方按自己的腿定的 —— 判据一份,后果按腿分。
12
12
  * 与最初搬来时相比有三处收严,都是收到与消费方请求腿同严、不是新语义,消费方改 import 这一份之后不再各留一份旧判据:申报名判官另判**首尾空白**;
13
- * `env` / `headers` 只收**纯数据**的「名 → 串」记录(Map / 类实例 / 自有 `__proto__` 成员不收,交出的是副本);两只回显描述器用消费方的回显口径(剥控制与双向字符、截 64 字、引号形)。
13
+ * `env` / `headers` 只收**纯数据**的「名 → 串」记录(Map / 类实例不收;保留成员名 {@link RESERVED_MEMBER_NAMES} 不进副本,交出的是副本);两只回显描述器用消费方的回显口径(剥控制与双向字符、截 64 字、引号形)。
14
14
  *
15
15
  * ## 依赖的引擎事实(镜像 + 漂移门)
16
16
  * · 申报名的线长界 {@link TOOL_WIRE_NAME_MAX_CHARS} = 引擎同名常量(值级对拍:上游漂移门 ⑧);
@@ -31,6 +31,8 @@ import { isJsonArray, isPlainJsonObject, jsonShapeOf } from "./plain-json-object
31
31
  import { MCP_CONTENT_ORIGINS } from "./types.js";
32
32
  import { RESERVED_MCP_SERVER_NAME_REASON, isReservedMcpServerName } from "./mcp-server-name.js";
33
33
  import { startsWithHttpScheme } from "./url-shape.js";
34
+ import { TOOL_WIRE_NAME_MAX_CHARS, hasEdgeWhitespace, isUsableWireName } from "./wire-name.js";
35
+ import { isReservedMemberName } from "./reserved-member-name.js";
34
36
  // ── 值的种类描述(拒句只说「你写的是什么形」,不回显值:值可能带凭据、可能任意长)──────────────────────────────────────
35
37
  /** 值的**种类词**(不回显值本身):`null` / `array` / `typeof` 词(`object` / `function` / `string` …);读不出的对象 ⇒ 全包唯一的那一句。形状只问座位(总,不抛):两只坏值描述器共用这一只。 */
36
38
  function describeKindWord(v) {
@@ -92,15 +94,15 @@ export function isStringArray(v) {
92
94
  return captureStringArray(v, "strict") !== undefined;
93
95
  }
94
96
  /**
95
- * 纯数据的「名 → 串」记录(空对象合法;数组、`Map`、类实例、自有 `__proto__` 成员、有非串成员的对象都不是)—— 类型守卫,所以是**严归类**:成员经内核的
97
+ * 纯数据的「名 → 串」记录(空对象合法;数组、`Map`、类实例、有保留名成员({@link isReservedMemberName})、有非串成员的对象都不是)—— 类型守卫,所以是**严归类**:成员经内核的
96
98
  * `ownEntriesOf` 读(整容器读的严归类:每个自有串键一份描述符;访问器成员或不可枚举的成员 ⇒ 不是,getter 一次都不调 —— 守卫为真,调用方随后的普通读读到的就是判过的那一份)。
97
- * 传输读器读 `env` / `headers`({@link readPlainStringRecord})交出去的是**副本**,按宽归类读(不可枚举的成员照读、访问器调一次);值与成员名的判据(是串、不叫 `__proto__`)两边同一条。
99
+ * 传输读器读 `env` / `headers`({@link readPlainStringRecord})交出去的是**副本**,按宽归类读(不可枚举的成员照读、访问器调一次);值与成员名的判据(是串、不是保留名)两边同一条。
98
100
  */
99
101
  export function isStringRecord(v) {
100
102
  if (!isPlainJsonObject(v))
101
103
  return false;
102
104
  const entries = ownEntriesOf(v);
103
- return entries.ok && entries.value.every(([name, value]) => name !== PLAIN_STRING_RECORD_RESERVED_MEMBER && typeof value === "string");
105
+ return entries.ok && entries.value.every(([name, value]) => !isReservedMemberName(name) && typeof value === "string");
104
106
  }
105
107
  // ── 调用方写的串的回显口径 ──────────────────────────────────────────────────────────────────────────
106
108
  /** 句子里点名一个值的写法:{@link echoCallerText} 外加一对双引号;内部的引号与反斜杠逐字(不转义)。 */
@@ -108,21 +110,9 @@ function quotedCallerText(text) {
108
110
  return `"${echoCallerText(text)}"`;
109
111
  }
110
112
  // ── 申报名的线长界 ──────────────────────────────────────────────────────────────────────────────────
111
- /** 引擎 `TOOL_WIRE_NAME_MAX_CHARS` 的镜像:一台 MCP server(或 A2A 对端)的申报名会成为线上工具名的命名空间段,引擎对超界名是**拒**(不截断)。 */
112
- export const TOOL_WIRE_NAME_MAX_CHARS = 128;
113
- /**
114
- * 首尾带空白吗 —— 「空白」就是 `String.prototype.trim` 剥的那一组(ECMAScript 的 WhiteSpace + LineTerminator:空格 / 制表 / 换行 / NBSP / BOM / 全角空格 / 行段分隔符等)。
115
- * {@link isUsableWireName} 与 {@link describeBadWireName} 共读这一个定义,判与说不会各按各的口径。
116
- */
117
- const hasEdgeWhitespace = (v) => v.trim() !== v;
118
- /**
119
- * 这个申报名能用吗。严判:非串、空串、超 {@link TOOL_WIRE_NAME_MAX_CHARS}、**首尾带空白**一律不可用。不截断、不 trim、不规范化 —— 截短或剥掉一个名字就是**换一个身份**,
120
- * 而被换掉身份的那台 server 上挂的 allowTools / toolFaces 收窄声明会一起对不上号。首尾空白 = 坏名(它不是另一个名字:名字是 `mcp__<server>__<tool>` 的身份片,
121
- * 带着空白的一端无从与不带的那个对上号);内部空白(`a b`)照旧是合法名,本判官不扩判。
122
- */
123
- export function isUsableWireName(v) {
124
- return typeof v === "string" && v.length > 0 && v.length <= TOOL_WIRE_NAME_MAX_CHARS && !hasEdgeWhitespace(v);
125
- }
113
+ // 判据住在零依赖叶子 `./wire-name`(18.0.0 搬过去:`./types` 的 `a2a` 对端名也问它,而本模块引 `./types` —— 留在这里会成环);公开名照旧经本模块转出,同一个函数对象。
114
+ export { TOOL_WIRE_NAME_MAX_CHARS, isUsableWireName } from "./wire-name.js";
115
+ export { RESERVED_MEMBER_NAMES, isReservedMemberName } from "./reserved-member-name.js"; // 18.0.0: the one reserved-member-name table (schema guard · this reader · the center-refs resolver)
126
116
  /**
127
117
  * 坏名的**可打印**摘要(进拒句 / 告警元数据):种类、`empty`,或引号形的回显加原因 —— 绝不把任意长度的申报串原样回显。回显口径见 {@link quotedCallerText}
128
118
  * (剥控制与双向字符、截 64 字)。串的原因是:超界 ⇒ `N chars > 128`(N 是送来的原长度,不是剥完的);首尾带空白 ⇒ `leading or trailing whitespace`
@@ -222,7 +212,7 @@ const inheritedIdentityIssue = (key) => ({
222
212
  * · 软坏形(`args` 不是串数组 / `env` `headers` 不是纯数据的串记录 / `principalHeader` 非串)⇒ 读器**丢这个键**(重放存量提交体时的升级前读法),并把它记进 `lost`
223
213
  * (受理新提交的门据它响亮拒)。`assertionHeader` 不在 `lost` 里:两个身份头的键组合与 `assertionHeader` 的值形由调用方在读之前对原始传输判。
224
214
  * · `env` / `headers` 只收**纯数据**的记录({@link readPlainStringRecord}):必须是朴素 JSON 对象(`./plain-json-object` 的判据;`Map`、`Set`、类实例整张不收),每个自有成员的值都是串;
225
- * 交出的是**副本**(不是调用方的对象,改原对象不影响读后的形)。名叫 `__proto__` 的成员(JSON 文本铸得出这个自有键,对象字面量写不出)不进副本、单独记进 `lost`。
215
+ * 交出的是**副本**(不是调用方的对象,改原对象不影响读后的形)。名叫保留名({@link RESERVED_MEMBER_NAMES})的成员不进副本、每个单独记进 `lost`。
226
216
  * 坏形的 `lost` 条目带机读位(见 {@link McpEntryKeyIssue})。
227
217
  * url 的拒句不回显值:它常规地带凭据(userinfo)。
228
218
  */
@@ -323,12 +313,8 @@ function snapshotTransport(tr) {
323
313
  return { ok: true, value: { kind: "other", value: kind.value } };
324
314
  }
325
315
  // ── 「名 → 串」记录的读法(`env` / `headers`)────────────────────────────────────────────────────────
326
- /**
327
- * 引擎的声明收据不收的成员名(core `mcp-server-spec`:按赋值拷贝它会变成原型):JSON 文本铸得出这个**自有**键,对象字面量写不出。
328
- * 公开,因为读器交出 `reservedMember: true` 时消费方要点名它 —— 名字只在这里定一次。
329
- */
330
- export const PLAIN_STRING_RECORD_RESERVED_MEMBER = "__proto__";
331
- const RESERVED_MEMBER_FAULT = `no member of a declaration may be named "${PLAIN_STRING_RECORD_RESERVED_MEMBER}"`;
316
+ /** 保留成员名的原因句(一个名一句;名字取自那一张表 {@link RESERVED_MEMBER_NAMES})。 */
317
+ const reservedMemberFault = (name) => `no member of a declaration may be named "${name}"`;
332
318
  /**
333
319
  * 「名 → 串」记录读不交(或交时点名保留名)的**原因词**,闭集,三个词、一张表:
334
320
  * 整张一词(`not_plain_record`)· 成员级一词(`member_not_string`:值读成了别的种类,或读成员时抛了 —— 句子分说)· 名字级一词(`member_name_reserved`)。
@@ -361,8 +347,8 @@ function notPlainDetail(shape, v) {
361
347
  * 读一份「名 → 串」记录 —— **公开读口**:传输读器读 `env` / `headers` 用的就是它,消费方读自己的「名 → 串」记录(中心名册的引用表)调同一只,不抄镜像。从不抛。记录交不交:是朴素 JSON 对象(座位的判据,见 `./plain-json-object`:原型是 `null`,或原型自己的原型是 `null`;纯数据 —— `Map`、`Set`、类实例、建在原型上的记录都不是),
362
348
  * 且每个成员的值都是串。成员 = `Object.getOwnPropertyNames` 的每一个名字(不可枚举的也算 —— 引擎的收据按全部自有属性名读),值经内核 `readKey` 读(一份描述符:数据属性取值,访问器调一次,不问 `get` 陷阱;访问器或描述符 trap 抛了同判为坏 —— 词与「值不是串」同是 `member_not_string`,句子说读不出)。
363
349
  * 读原型与成员名时代理 trap 抛了 ⇒ 整张不交(读器对调用方递来的任何对象都不抛;`JSON` 存体里不会有代理,这条只落在直接调读器的嵌入方身上)。
364
- * 交出去的是副本(原型 `Object.prototype`、成员全可枚举),调用方的对象不被交出去、也不被改;名叫 `__proto__` 的成员不进副本,`reservedMember` 记下它。
365
- * 名叫 `__proto__` 而值也坏的成员,句子一次说全两件事(名坏在前、值坏并列):调用方照值改完再撞第二条是两步才改对。
350
+ * 交出去的是副本(原型 `Object.prototype`、成员全可枚举),调用方的对象不被交出去、也不被改;名叫保留名({@link isReservedMemberName})的成员不进副本,`reservedMembers` 记下它们。
351
+ * 名叫保留名而值也坏的成员,句子一次说全两件事(名坏在前、值坏并列):调用方照值改完再撞第二条是两步才改对。
366
352
  */
367
353
  export function readPlainStringRecord(v) {
368
354
  // 形状归座位旁的分类(不在这里再推一遍;座位总 —— 读形状就抛的对象答 `uninspectable`)。成员名的读取(`ownKeys` 陷阱抛)读不出的对象也不是纯数据。
@@ -377,7 +363,7 @@ export function readPlainStringRecord(v) {
377
363
  // 累加器是零原型的袋、成员用 `Object.defineProperty` + 零原型描述符写(定义语义,不走 [[Set]]),最后用对象展开(同为定义语义)铸成普通对象:原型上被污染成只读的同名键不让读器抛,
378
364
  // 原型上的数字键 setter 吞不掉成员(此前 `entries.push` + `Object.fromEntries` 被继承的数字键 getter / setter 弄抛)
379
365
  const members = Object.create(null);
380
- let reservedMember = false;
366
+ const reservedMembers = names.filter(isReservedMemberName); // `filter` 走 CreateDataProperty,不走 [[Set]](继承的数字键 setter 吞不掉)
381
367
  for (const name of names) {
382
368
  // 成员经内核读(契约见函数头注):一份描述符里取值 —— 数据属性取值、访问器调一次、从不问 Proxy 的 `get` 陷阱;成员在两次读之间被删掉 ⇒ 不是串(不读继承来的值)
383
369
  const read = readKey(v, name);
@@ -385,28 +371,25 @@ export function readPlainStringRecord(v) {
385
371
  const value = read.presence === "own" ? read.value : undefined;
386
372
  if (unreadable || typeof value !== "string") {
387
373
  const valueFault = unreadable ? "could not be read (its getter threw)" : `must be a string — got ${describeValueShape(value)}`;
388
- const reserved = name === PLAIN_STRING_RECORD_RESERVED_MEMBER;
389
374
  // 词:名坏优先(句子里值坏并列说全);名没问题时,读不出与值不是串同词,句子分说
390
- const fault = reserved ? "member_name_reserved" : "member_not_string";
375
+ const fault = isReservedMemberName(name) ? "member_name_reserved" : "member_not_string";
391
376
  return {
392
377
  ok: false,
393
378
  fault,
394
379
  member: echoCallerText(name),
395
- detail: `its member "${echoCallerText(name)}" ${reserved ? `is not allowed (${RESERVED_MEMBER_FAULT}) and its value ${valueFault}` : valueFault}`,
380
+ detail: `its member "${echoCallerText(name)}" ${isReservedMemberName(name) ? `is not allowed (${reservedMemberFault(name)}) and its value ${valueFault}` : valueFault}`,
396
381
  };
397
382
  }
398
- if (name === PLAIN_STRING_RECORD_RESERVED_MEMBER)
399
- reservedMember = true;
400
- else
383
+ if (!isReservedMemberName(name))
401
384
  Object.defineProperty(members, name, dataDescriptor(value));
402
385
  }
403
- return { ok: true, record: { ...members }, reservedMember };
386
+ return { ok: true, record: { ...members }, reservedMembers };
404
387
  }
405
388
  /** 把诊断条目铸成数组:`undefined`(这一处没有诊断)略去。`filter` 的结果走 `CreateDataProperty`,不走 `[[Set]]` —— `lost` 是「门拒什么」的读数,不能被继承的数字键 setter 吞掉。 */
406
389
  const issuesOf = (...candidates) => candidates.filter((c) => c !== undefined);
407
390
  /**
408
391
  * `transport.env` / `transport.headers` 的读法(两键同一只):缺席 ⇒ 缺席;记录不交 ⇒ 一处诊断点名**这个键**(不交的是整张记录;坏在某个成员时句子与 `member` 点名它);
409
- * 交 ⇒ 纯数据副本,名叫 `__proto__` 的成员单记一处诊断、路径 `<键>.__proto__`。
392
+ * 交 ⇒ 纯数据副本,名叫保留名的成员每个单记一处诊断、路径 `<键>.<保留名>`。
410
393
  */
411
394
  function readTransportRecord(path, raw) {
412
395
  if (raw === undefined)
@@ -420,6 +403,6 @@ function readTransportRecord(path, raw) {
420
403
  }
421
404
  return {
422
405
  record: read.record,
423
- issues: read.reservedMember ? [{ path: `${path}.${PLAIN_STRING_RECORD_RESERVED_MEMBER}`, reason: RESERVED_MEMBER_FAULT, member: PLAIN_STRING_RECORD_RESERVED_MEMBER, fault: "member_name_reserved" }] : [],
406
+ issues: read.reservedMembers.map((name) => ({ path: `${path}.${name}`, reason: reservedMemberFault(name), member: name, fault: "member_name_reserved" })),
424
407
  };
425
408
  }
package/dist/node.d.ts CHANGED
@@ -8,8 +8,11 @@ export * from "./file-store.js";
8
8
  export * from "./file-edit.js";
9
9
  export * from "./bundle.js";
10
10
  export * from "./local-load.js";
11
+ export { readConfigDFileReadingSync, type ConfigDDocumentName, type ConfigDFileReason, type ConfigDReading, type ConfigDRefused } from "./config-d-file.js";
11
12
  export * from "./scheduler-store-node.js";
12
13
  export { migrateConfigDirectory, migrateConfigDocuments, // 14.5.0:同一段「迁 → 整域终判」的同步口(入参是已有的存量文档,无 io)—— 目录形调的就是它的内核
13
14
  type ConfigDirectoryDocument, type ConfigDirectoryIo, type ConfigDirectoryMigration, type ConfigDirectoryRefusal, type ConfigDocumentsMigration, type MigrateConfigDirectoryOptions, type MigrateConfigDocumentsOptions, } from "./config-dir-migration.js";
14
15
  export { nodeConfigDirectoryIo } from "./config-dir-io.js";
15
- export { migrateEnvModelsDecision, nodeConfigRootIo, readEnvModelsDecision, readMigrationMarker, writeEnvModelsDecision, type ConfigRootIo, type EnvModelsDecisionWrite, type EnvModelsMigration, type EnvModelsMint, } from "./config-root-node.js";
16
+ export { migrateEnvModelsDecision, nodeConfigRootIo, readEnvModelsDecision, readEnvModelsDecisionSync, // 同步孪生:同一个字节读法 / 缺席律 / 答案表(壳在同步具名锁里写模型池要在锁里读决定)
17
+ readMigrationMarker, writeEnvModelsDecision, writeEnvModelsDecisionSync, // 同步孪生:同一份写计划 + 同一对同步原子写原语
18
+ type ConfigRootIo, type EnvModelsDecisionWrite, type EnvModelsMigration, type EnvModelsMint, } from "./config-root-node.js";
package/dist/node.js CHANGED
@@ -8,10 +8,14 @@ export * from "./file-store.js";
8
8
  export * from "./file-edit.js";
9
9
  export * from "./bundle.js";
10
10
  export * from "./local-load.js";
11
+ // 一份 `config.d/<名>.json` 的读数(缺席 / 在场 / 被拒)的**同步读口**:与包内异步读口同一只纯判、同一个缺席尾(壳在同步具名锁里读 `config.d` 文档用它,不自读文本);纯判本身与异步读口不出门。显式列名。
12
+ export { readConfigDFileReadingSync } from "./config-d-file.js";
11
13
  export * from "./scheduler-store-node.js"; // R7 self-wake scheduler store — fs binding of the ./scheduler-store contract
12
14
  // 目录形的一次性迁移(读目录 → 迁 → 整域终判 → 原子写 → 删 retired 域文件)—— 语义只此一处,命令行是它的渲染器、引擎起服调同一只。显式列名:该模块的包内私有件(出门形/拼路径)不外泄。
13
15
  export { migrateConfigDirectory, migrateConfigDocuments, // 14.5.0:同一段「迁 → 整域终判」的同步口(入参是已有的存量文档,无 io)—— 目录形调的就是它的内核
14
16
  } from "./config-dir-migration.js";
15
17
  export { nodeConfigDirectoryIo } from "./config-dir-io.js"; // 上面那只函数的真 I/O 绑定:node:fs + 本包唯一的原子写器(config-document 模式规则)
16
- // 配置根上的决定文档(config.env-models.json)与按 id 的运行记录(config.migration.<id>.json)的两只读者、决定文档的唯一写函数(运行记录由迁移独占创建),与一次性迁移 SS-M11 的迁移体(名字与闭集答案表在包根)。显式列名。
17
- export { migrateEnvModelsDecision, nodeConfigRootIo, readEnvModelsDecision, readMigrationMarker, writeEnvModelsDecision, } from "./config-root-node.js";
18
+ // 配置根上的决定文档(config.env-models.json)与按 id 的运行记录(config.migration.<id>.json)的两只读者、决定文档的唯一写函数(运行记录由迁移独占创建;决定文档的读写各有同步孪生),与一次性迁移 SS-M11 的迁移体(名字与闭集答案表在包根)。显式列名。
19
+ export { migrateEnvModelsDecision, nodeConfigRootIo, readEnvModelsDecision, readEnvModelsDecisionSync, // 同步孪生:同一个字节读法 / 缺席律 / 答案表(壳在同步具名锁里写模型池要在锁里读决定)
20
+ readMigrationMarker, writeEnvModelsDecision, writeEnvModelsDecisionSync, // 同步孪生:同一份写计划 + 同一对同步原子写原语
21
+ } from "./config-root-node.js";
@@ -138,7 +138,7 @@ export const ONE_TIME_MIGRATIONS = [
138
138
  releasedOn: "2026-10-10",
139
139
  removableAfter: "2026-11-09",
140
140
  rewrites: "every NON_SECRET_URL seat in a domain document (derived from the domain schemas, not listed by hand — today models.models[].baseUrl, " +
141
- "mcp.servers[].transport.url, execution.sessionMirror.engineUrl, workers.workers[].coords.gitApiBaseUrl): `#` and everything after it are removed " +
141
+ "mcp.servers[].transport.url, a2a.peers[].url, a2a.peers[].cardUrl, execution.sessionMirror.engineUrl, workers.workers[].coords.gitApiBaseUrl): `#` and everything after it are removed " +
142
142
  "(a fragment never goes on the wire); the same walk covers config.d/remote-exec.json, derived from its schema the same way " +
143
143
  "(today the k8s apiUrl and s3Snapshot.endpoint) — the command-line migrate reads and rewrites it as one more document",
144
144
  removal: "the next major after the window: delete the SS-M7 step and its schema walk; the NON_SECRET_URL fragment rule loses its SS-M7 clause",
@@ -12,7 +12,7 @@
12
12
  * 同形**。当年 `limits` 域就是这样在单机腿静默丢了一整片限额发布(远端腿同批可用,所以更难看出来)。
13
13
  * 今日的兜底:漏登记的文件会被 `FileConfigStore`(`./node`)的 `"unread-config-file"` 警告点名(fail-loud,
14
14
  * 见 getEffective),但那只在消费方接了 `onWarning` 时才响 —— **登记仍是第一现场的义务**。 */
15
- export declare const PORTABLE_DOMAINS: readonly ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "governance", "execution", "prompts", "limits"];
15
+ export declare const PORTABLE_DOMAINS: readonly ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "governance", "execution", "prompts", "limits", "a2a"];
16
16
  export type PortableDomain = (typeof PORTABLE_DOMAINS)[number];
17
17
  /**
18
18
  * 非便携域的**排除理由表**(#282 件5 附带裁定:禁裸排除)。
@@ -12,7 +12,7 @@
12
12
  * 同形**。当年 `limits` 域就是这样在单机腿静默丢了一整片限额发布(远端腿同批可用,所以更难看出来)。
13
13
  * 今日的兜底:漏登记的文件会被 `FileConfigStore`(`./node`)的 `"unread-config-file"` 警告点名(fail-loud,
14
14
  * 见 getEffective),但那只在消费方接了 `onWarning` 时才响 —— **登记仍是第一现场的义务**。 */
15
- export const PORTABLE_DOMAINS = ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "governance", "execution", "prompts", "limits"];
15
+ export const PORTABLE_DOMAINS = ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "governance", "execution", "prompts", "limits", "a2a"];
16
16
  /**
17
17
  * 非便携域的**排除理由表**(#282 件5 附带裁定:禁裸排除)。
18
18
  *
@@ -12,6 +12,9 @@
12
12
  * - `host` run tools DIRECTLY on this machine, no container (the TOC default, like a local coding
13
13
  * agent). No isolation.
14
14
  * - `local-docker` containers on THIS machine's docker (isolation for parallel/untrusted fan-out).
15
+ * Either:
16
+ * - `none` (18.0.0) no execution environment for the agent's tools — the file form of `REMOTE_EXEC=none`. No fields:
17
+ * there is nothing to configure.
15
18
  *
16
19
  * 7.0.0: every `provider` word here is an execution lane word (`EXECUTION_LANE_WORDS`, `execution-lanes.ts` — the ONE
17
20
  * vocabulary; `test/execution-lanes-7.0.0.test.ts` pins `provider ⊆ EXECUTION_LANE_WORDS`, at runtime and at compile time).
@@ -90,5 +93,7 @@ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<[z.ZodObject<{
90
93
  serial: z.ZodString;
91
94
  adbPath: z.ZodOptional<z.ZodString>;
92
95
  mountPath: z.ZodOptional<z.ZodString>;
96
+ }, z.core.$strip>, z.ZodObject<{
97
+ provider: z.ZodLiteral<"none">;
93
98
  }, z.core.$strip>], "provider">;
94
99
  export type RemoteExecSpec = z.infer<typeof RemoteExecSpec>;
@@ -12,6 +12,9 @@
12
12
  * - `host` run tools DIRECTLY on this machine, no container (the TOC default, like a local coding
13
13
  * agent). No isolation.
14
14
  * - `local-docker` containers on THIS machine's docker (isolation for parallel/untrusted fan-out).
15
+ * Either:
16
+ * - `none` (18.0.0) no execution environment for the agent's tools — the file form of `REMOTE_EXEC=none`. No fields:
17
+ * there is nothing to configure.
15
18
  *
16
19
  * 7.0.0: every `provider` word here is an execution lane word (`EXECUTION_LANE_WORDS`, `execution-lanes.ts` — the ONE
17
20
  * vocabulary; `test/execution-lanes-7.0.0.test.ts` pins `provider ⊆ EXECUTION_LANE_WORDS`, at runtime and at compile time).
@@ -147,6 +150,8 @@ export const RemoteExecSpec = z.discriminatedUnion("provider", [
147
150
  adbPath: execStr.optional(),
148
151
  mountPath: execStr.optional(),
149
152
  }),
153
+ // ── none (18.0.0): no execution environment for the agent's tools (REMOTE_EXEC=none). Zero fields — nothing to configure, no secrets.
154
+ z.object({ provider: z.literal("none") }),
150
155
  ], {
151
156
  // A retired lane word as `provider` (no matching discriminator) gets the tombstone's way out; any other miss keeps zod's sentence.
152
157
  // `provider` is read from the input's OWN keys (the input is the caller's object: an inherited / polluted `provider` is not the word they wrote; a proxy that cannot be read has no word to refuse)
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 包内私有叶子模块(运行期零依赖,浏览器安全;不进 `package.json` 的 exports,主桶经 `./mcp-request-entry` 转出这里的公开名):
3
+ * **一个名字能不能作「名 → 串」记录的成员名**的唯一判据 —— 原型链上的三个名字永远不能:`__proto__`(按赋值拷贝会变成原型;zod 的 record 读键前先跳过它、不判它的值)、
4
+ * `constructor` / `prototype`(读方对普通对象做 `obj[名]` 时撞上原型成员)。
5
+ * 三个读口问它,同一张表、同一个答案:域 schema 的凭据引用集合(`./types` 的 `credentialRefs`:在场 ⇒ 集合坏)· 公开的记录读器(`./mcp-request-entry` 的 `readPlainStringRecord` /
6
+ * `isStringRecord`,请求面传输的 `env` / `headers` 同读:剔出副本并点名)· 中心引用解析口(`./center-refs`,经记录读器:在场 ⇒ `refs_unreadable`)。
7
+ * 住在叶子里,是因为 `./types` 与 `./mcp-request-entry` 都要它,而后者反过来引 `./types`。
8
+ */
9
+ /** 保留成员名,闭集。 */
10
+ export declare const RESERVED_MEMBER_NAMES: readonly ["__proto__", "constructor", "prototype"];
11
+ export type ReservedMemberName = (typeof RESERVED_MEMBER_NAMES)[number];
12
+ /** 这个名字是保留成员名吗(大小写敏感:`Constructor` / `X-Prototype` 不是)。 */
13
+ export declare function isReservedMemberName(name: string): name is ReservedMemberName;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 包内私有叶子模块(运行期零依赖,浏览器安全;不进 `package.json` 的 exports,主桶经 `./mcp-request-entry` 转出这里的公开名):
3
+ * **一个名字能不能作「名 → 串」记录的成员名**的唯一判据 —— 原型链上的三个名字永远不能:`__proto__`(按赋值拷贝会变成原型;zod 的 record 读键前先跳过它、不判它的值)、
4
+ * `constructor` / `prototype`(读方对普通对象做 `obj[名]` 时撞上原型成员)。
5
+ * 三个读口问它,同一张表、同一个答案:域 schema 的凭据引用集合(`./types` 的 `credentialRefs`:在场 ⇒ 集合坏)· 公开的记录读器(`./mcp-request-entry` 的 `readPlainStringRecord` /
6
+ * `isStringRecord`,请求面传输的 `env` / `headers` 同读:剔出副本并点名)· 中心引用解析口(`./center-refs`,经记录读器:在场 ⇒ `refs_unreadable`)。
7
+ * 住在叶子里,是因为 `./types` 与 `./mcp-request-entry` 都要它,而后者反过来引 `./types`。
8
+ */
9
+ /** 保留成员名,闭集。 */
10
+ export const RESERVED_MEMBER_NAMES = ["__proto__", "constructor", "prototype"];
11
+ /** 这个名字是保留成员名吗(大小写敏感:`Constructor` / `X-Prototype` 不是)。 */
12
+ export function isReservedMemberName(name) {
13
+ return RESERVED_MEMBER_NAMES.includes(name);
14
+ }
@@ -75,6 +75,8 @@ export const RETIRED_ENVS = [
75
75
  ["HOST_EXEC_SPOOL_DISABLED", "HOST_EXEC_SPOOL_ENABLED", "replaced", negativeAliasWhy("HOST_EXEC_SPOOL_DISABLED", "HOST_EXEC_SPOOL_ENABLED")],
76
76
  // 无人值守腿的模式旋钮退役:这些腿的答集是常量,不是旋钮。
77
77
  ["UNATTENDED_PERMISSION_MODE", "since server 7.109.0 the unattended legs (leader workers and their repair legs, run-local, and their durable resumes) always run permissionMode \"default\" with an approver that answers by ask class: a machine answers only \"ordinary\" asks, and every other class goes to the leg's approver seat (run-local's terminal prompt) or parks / is refused — there is no mode to choose", "removed"],
78
+ // 执行车道总开关:每个部署都点名自己的车道之后它恒真(「点名了没有」只剩一个答者 REMOTE_EXEC),留着 = 两个键答一个问题;「没有执行环境」也是一个声明(REMOTE_EXEC=none)。
79
+ ["REQUIRE_EXECUTION_LANE", "REMOTE_EXEC", "replaced", "every deployment names its execution lane; REMOTE_EXEC=none declares one with no execution environment"],
78
80
  ];
79
81
  // 冻结:拿到导出的人改不了拒启判据(读拒句的与投影 wire 的是同一个数组对象)。
80
82
  for (const row of RETIRED_ENVS)
@@ -18,7 +18,7 @@ export interface EnvRef {
18
18
  envName: string;
19
19
  refs: EnvRefUse[];
20
20
  }
21
- /** Every env-NAME referenced across models / workers / mcp, grouped + sorted by name.
21
+ /** Every env-NAME referenced across models / workers / mcp / a2a, grouped + sorted by name.
22
22
  * (integrations domain DELETED in 0.6.0 — OA/Gitea coordinates ride worker.env/secretEnv, EXPERT-REDESIGN §9.) */
23
23
  export declare function collectEnvRefs(eff: EffectiveConfig): EnvRef[];
24
24
  /**
@@ -1,4 +1,4 @@
1
- /** Every env-NAME referenced across models / workers / mcp, grouped + sorted by name.
1
+ /** Every env-NAME referenced across models / workers / mcp / a2a, grouped + sorted by name.
2
2
  * (integrations domain DELETED in 0.6.0 — OA/Gitea coordinates ride worker.env/secretEnv, EXPERT-REDESIGN §9.) */
3
3
  export function collectEnvRefs(eff) {
4
4
  const map = new Map();
@@ -33,6 +33,10 @@ export function collectEnvRefs(eff) {
33
33
  for (const [key, envName] of Object.entries(refs ?? {}))
34
34
  add(envName, "mcp", `server "${s.name}" ${kind}.${key}`);
35
35
  }
36
+ // 18.0.0: an A2A peer's header references are env-NAMEs the reading side must provide, like an http MCP server's.
37
+ for (const p of eff.a2a.peers)
38
+ for (const [key, envName] of Object.entries(p.headerRefs ?? {}))
39
+ add(envName, "a2a", `peer "${p.name}" headerRefs.${key}`);
36
40
  return [...map.entries()].map(([envName, refs]) => ({ envName, refs })).sort((a, b) => a.envName.localeCompare(b.envName));
37
41
  }
38
42
  /**
@@ -75,6 +75,9 @@ export declare function unitTighten(unit: SettingsUnitName, member?: string): Ti
75
75
  * are `"manual"` ⇒ `"default"` (permissionMode) and `"disable"` ⇒ `true` (kill-switches); `"bypassPermissions"` is `launch_only`.
76
76
  * The last step of every reading is the unit comparator's own domain: whatever reads, compares (file header).
77
77
  * Addressing is {@link unitTighten}'s (same RangeErrors: a `members` unit needs a declared member, …).
78
+ * `unreadable` on a `member` / `members` unit is NOT absence, and the reading carries no direction: what the caller does with it depends on the member's
79
+ * `memberAdmission(category, member).direction` (`loosen` ⇒ may be dropped, loudly; `tighten` ⇒ must be kept and answered strictest on its own surface) —
80
+ * see WHEN A MEMBER DOES NOT READ in the file header.
78
81
  */
79
82
  export declare function readSettingsUnit(unit: SettingsUnitName, raw: unknown, member?: string): {
80
83
  readonly ok: true;
@@ -38,6 +38,21 @@
38
38
  * owner, the comparator; a reading only adds what it needs on top (non-empty list elements, the hooks judge, …). So a value the
39
39
  * shape half accepts but the comparator cannot place — a hooks document the hooks judge loads while a key it strips hides data
40
40
  * nested deeper than `MAX_HASH_DEPTH`, or an `undefined` — is `unreadable`, never "read but not comparable".
41
+ * WHEN A MEMBER DOES NOT READ (the disposition law). On a `member` / `members` unit, `unreadable` is NOT absence: absence is "the document does
42
+ * not set this member", `unreadable` is "the document sets it, in a form the contract cannot place". The two facts a caller needs to dispose of
43
+ * it are already tables, and neither is carried by the reading (a reading has no `direction`: a `whole` unit has no direction source, so the field
44
+ * could only be optional and every consumer would still branch on its absence — and it would be a second reader of a member row's `direction`):
45
+ * the member's direction is `memberAdmission(category, member).direction`, and the category's failure direction is `SAFETY_MERGE_SPEC[category].fail`.
46
+ * For a member of a category whose `fail` is `closed` (today every category that declares members: `permissions`, `sandboxNetwork`, `killSwitches`):
47
+ * · a `loosen` member — the caller MAY drop it (dropping grants less), loudly: it says which member of which layer was dropped and why;
48
+ * · a `tighten` member — the caller must NOT drop it and must NOT read it as absent: it keeps the member and answers on ITS surface with the
49
+ * strictest answer it has (the rules surface refuses every call it cannot place; a kill-switch counts as ON; a sandbox switch takes its strict
50
+ * pole), because dropping a tighten member reads a restriction its author wrote — in a form the contract cannot place — as no restriction at all;
51
+ * the contract cannot tell what it was meant to forbid, and the strictest answer is the only one that cannot be looser than what the author meant.
52
+ * The package enforces nothing here (the surfaces are the consumers'); what it pins is that both inputs exist for every such member and that every
53
+ * out-of-domain value reads `unreadable` without throwing. Not covered, and not settled here: the `whole` units (`permissionMode`, `additionalDirectories`,
54
+ * `hooks`, `env`, `model`, `outputStyle`) have no direction column; whether "unreadable = absent" closes for them depends on the order of the
55
+ * unit's own value domain, which is a design question of its own.
41
56
  * The env DOCUMENT's literal grammar (boolean words, csv + empty-set sentinel, a "≤ 0 means off" cap) is not here: it belongs to
42
57
  * the engine's env catalog, whose one implementation is the server's.
43
58
  */
@@ -165,6 +180,9 @@ function shapedReading(unit, row, rule, raw) {
165
180
  * are `"manual"` ⇒ `"default"` (permissionMode) and `"disable"` ⇒ `true` (kill-switches); `"bypassPermissions"` is `launch_only`.
166
181
  * The last step of every reading is the unit comparator's own domain: whatever reads, compares (file header).
167
182
  * Addressing is {@link unitTighten}'s (same RangeErrors: a `members` unit needs a declared member, …).
183
+ * `unreadable` on a `member` / `members` unit is NOT absence, and the reading carries no direction: what the caller does with it depends on the member's
184
+ * `memberAdmission(category, member).direction` (`loosen` ⇒ may be dropped, loudly; `tighten` ⇒ must be kept and answered strictest on its own surface) —
185
+ * see WHEN A MEMBER DOES NOT READ in the file header.
168
186
  */
169
187
  export function readSettingsUnit(unit, raw, member) {
170
188
  const t = unitTarget(unit, member);
package/dist/types.d.ts CHANGED
@@ -1428,17 +1428,9 @@ export declare const WorkerSpec: z.ZodObject<{
1428
1428
  runtime: z.ZodObject<{
1429
1429
  execution: z.ZodOptional<z.ZodObject<{
1430
1430
  lane: z.ZodOptional<z.ZodNever>;
1431
- lanes: z.ZodArray<z.ZodEnum<{
1432
- host: "host";
1433
- e2b: "e2b";
1434
- k8s: "k8s";
1435
- ssh: "ssh";
1436
- adb: "adb";
1437
- "local-docker": "local-docker";
1438
- device: "device";
1439
- }>>;
1431
+ lanes: z.ZodPreprocess<z.ZodPipe<z.ZodUnknown, z.ZodTransform<("none" | "host" | "e2b" | "k8s" | "ssh" | "adb" | "local-docker" | "device")[], unknown>>, unknown>;
1440
1432
  coords: z.ZodPrefault<z.ZodRecord<z.ZodString, z.ZodString>>;
1441
- secretRefs: z.ZodPrefault<z.ZodRecord<z.ZodString, z.ZodString>>;
1433
+ secretRefs: z.ZodPrefault<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
1442
1434
  }, z.core.$strip>>;
1443
1435
  store: z.ZodOptional<z.ZodObject<{
1444
1436
  backend: z.ZodEnum<{
@@ -1492,7 +1484,7 @@ export declare const WorkerSpec: z.ZodObject<{
1492
1484
  tidbPasswordEnv: z.ZodOptional<z.ZodNever>;
1493
1485
  }, z.core.$strip>;
1494
1486
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1495
- secretEnv: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1487
+ secretEnv: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
1496
1488
  recreateNonce: z.ZodOptional<z.ZodString>;
1497
1489
  lifecycle: z.ZodOptional<z.ZodObject<{
1498
1490
  kind: z.ZodDefault<z.ZodEnum<{
@@ -1597,17 +1589,9 @@ export declare const WorkersConfig: z.ZodObject<{
1597
1589
  runtime: z.ZodObject<{
1598
1590
  execution: z.ZodOptional<z.ZodObject<{
1599
1591
  lane: z.ZodOptional<z.ZodNever>;
1600
- lanes: z.ZodArray<z.ZodEnum<{
1601
- host: "host";
1602
- e2b: "e2b";
1603
- k8s: "k8s";
1604
- ssh: "ssh";
1605
- adb: "adb";
1606
- "local-docker": "local-docker";
1607
- device: "device";
1608
- }>>;
1592
+ lanes: z.ZodPreprocess<z.ZodPipe<z.ZodUnknown, z.ZodTransform<("none" | "host" | "e2b" | "k8s" | "ssh" | "adb" | "local-docker" | "device")[], unknown>>, unknown>;
1609
1593
  coords: z.ZodPrefault<z.ZodRecord<z.ZodString, z.ZodString>>;
1610
- secretRefs: z.ZodPrefault<z.ZodRecord<z.ZodString, z.ZodString>>;
1594
+ secretRefs: z.ZodPrefault<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
1611
1595
  }, z.core.$strip>>;
1612
1596
  store: z.ZodOptional<z.ZodObject<{
1613
1597
  backend: z.ZodEnum<{
@@ -1661,7 +1645,7 @@ export declare const WorkersConfig: z.ZodObject<{
1661
1645
  tidbPasswordEnv: z.ZodOptional<z.ZodNever>;
1662
1646
  }, z.core.$strip>;
1663
1647
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1664
- secretEnv: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1648
+ secretEnv: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
1665
1649
  recreateNonce: z.ZodOptional<z.ZodString>;
1666
1650
  lifecycle: z.ZodOptional<z.ZodObject<{
1667
1651
  kind: z.ZodDefault<z.ZodEnum<{
@@ -1844,15 +1828,28 @@ export declare const SkillsManifestSchema: z.ZodObject<{
1844
1828
  contentHash: z.ZodString;
1845
1829
  }, z.core.$strip>>>;
1846
1830
  }, z.core.$strip>;
1831
+ /**
1832
+ * 18.0.0 —— **凭据引用集合不可部分抢救**(按 schema 身份的集合)。成员是「名 → 宿主 env-NAME」的凭据引用 Record:mcp 的 `envRefs` / `headerRefs`
1833
+ * (a2a 对端的 `headerRefs` 复用后者,因此同在)、车道块的 `secretRefs` 与 worker 的 `secretEnv`。逐条抢救(`salvageEntries`)走到成员**不下钻**、把它当一个值判:
1834
+ * 集合里任一键 / 值坏形 ⇒ 它所属的那一条条目整条 `entry-dropped`,兄弟条目保留。理由:丢掉一条坏引用不是「更窄」,而是让条目带着**一半凭据**活下来 ——
1835
+ * 拨一个远端服务 / 智能体时错误的失败方向(读方对「任一引用的 env 缺席」本来就整条跳过,同一个方向)。
1836
+ * 既有读数只变一处:直接对 `WorkersConfig` 调 `salvageEntries` 时,`secretEnv` 带坏引用的 worker 由「只丢那一条引用、worker 存活」变「整条丢」
1837
+ * (各域读数不变:workers 域不逐条抢救;mcp 的两只住在传输判别联合里,走查不进联合;车道块住在不是集合的 `runtime` 对象里)
1838
+ * —— 由 17.2.0 包的真读数逐行对拍钉住(`test/atomic-salvage-18.0.0.test.ts`,改了读数的那一行逐行登记)。别的集合字段(allowTools / scenarios / allowSkills …)照旧逐条抢救:丢坏元素只会更窄。
1839
+ * 成员读键按自有属性名判:`__proto__` / `constructor` / `prototype` 任一在场 ⇒ 集合坏 ⇒ 条目整条丢(见 `credentialRefs`;zod 的 record 读键前跳过 `__proto__`、不判它的值)。
1840
+ * 新的凭据引用集合必须复用这几只 schema 或由 `credentialRefs` 铸出并登记进这里(按身份判,换一只「形一样」的新 schema 不算成员)。
1841
+ * 登记的是剥掉包装之后的那只 `record`(与抢救走查同一只读法 `unwrapSchema`)。
1842
+ */
1843
+ export declare const ATOMIC_SALVAGE: ReadonlySet<z.ZodType>;
1847
1844
  export declare const McpTransport: z.ZodDiscriminatedUnion<[z.ZodObject<{
1848
1845
  kind: z.ZodLiteral<"stdio">;
1849
1846
  command: z.ZodString;
1850
1847
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
1851
- envRefs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1848
+ envRefs: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
1852
1849
  }, z.core.$strip>, z.ZodObject<{
1853
1850
  kind: z.ZodLiteral<"http">;
1854
1851
  url: z.ZodString;
1855
- headerRefs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1852
+ headerRefs: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
1856
1853
  principalHeader: z.ZodOptional<z.ZodString>;
1857
1854
  assertionHeader: z.ZodOptional<z.ZodString>;
1858
1855
  }, z.core.$strip>], "kind">;
@@ -1870,11 +1867,11 @@ export declare const McpServerSpec: z.ZodObject<{
1870
1867
  kind: z.ZodLiteral<"stdio">;
1871
1868
  command: z.ZodString;
1872
1869
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
1873
- envRefs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1870
+ envRefs: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
1874
1871
  }, z.core.$strip>, z.ZodObject<{
1875
1872
  kind: z.ZodLiteral<"http">;
1876
1873
  url: z.ZodString;
1877
- headerRefs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1874
+ headerRefs: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
1878
1875
  principalHeader: z.ZodOptional<z.ZodString>;
1879
1876
  assertionHeader: z.ZodOptional<z.ZodString>;
1880
1877
  }, z.core.$strip>], "kind">;
@@ -1905,11 +1902,11 @@ export declare const McpConfig: z.ZodObject<{
1905
1902
  kind: z.ZodLiteral<"stdio">;
1906
1903
  command: z.ZodString;
1907
1904
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
1908
- envRefs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1905
+ envRefs: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
1909
1906
  }, z.core.$strip>, z.ZodObject<{
1910
1907
  kind: z.ZodLiteral<"http">;
1911
1908
  url: z.ZodString;
1912
- headerRefs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1909
+ headerRefs: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
1913
1910
  principalHeader: z.ZodOptional<z.ZodString>;
1914
1911
  assertionHeader: z.ZodOptional<z.ZodString>;
1915
1912
  }, z.core.$strip>], "kind">;
@@ -1934,6 +1931,32 @@ export declare const McpConfig: z.ZodObject<{
1934
1931
  }, z.core.$strip>>>;
1935
1932
  }, z.core.$strip>;
1936
1933
  export type McpConfig = z.infer<typeof McpConfig>;
1934
+ /** 一个常驻出站 A2A 对端。 */
1935
+ export declare const A2aPeerSpec: z.ZodObject<{
1936
+ name: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
1937
+ url: z.ZodString;
1938
+ cardUrl: z.ZodOptional<z.ZodString>;
1939
+ headerRefs: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
1940
+ principalHeader: z.ZodOptional<z.ZodString>;
1941
+ allowSkills: z.ZodOptional<z.ZodArray<z.ZodString>>;
1942
+ scenarios: z.ZodPrefault<z.ZodArray<z.ZodString>>;
1943
+ enabled: z.ZodDefault<z.ZodBoolean>;
1944
+ }, z.core.$strip>;
1945
+ export type A2aPeerSpec = z.infer<typeof A2aPeerSpec>;
1946
+ /** `a2a` 域:常驻出站 A2A 对端名册。空 / 缺席 = 不拨任何对端。对端名唯一(重名拒,同 mcp)。 */
1947
+ export declare const A2aConfig: z.ZodObject<{
1948
+ peers: z.ZodPrefault<z.ZodArray<z.ZodObject<{
1949
+ name: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
1950
+ url: z.ZodString;
1951
+ cardUrl: z.ZodOptional<z.ZodString>;
1952
+ headerRefs: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
1953
+ principalHeader: z.ZodOptional<z.ZodString>;
1954
+ allowSkills: z.ZodOptional<z.ZodArray<z.ZodString>>;
1955
+ scenarios: z.ZodPrefault<z.ZodArray<z.ZodString>>;
1956
+ enabled: z.ZodDefault<z.ZodBoolean>;
1957
+ }, z.core.$strip>>>;
1958
+ }, z.core.$strip>;
1959
+ export type A2aConfig = z.infer<typeof A2aConfig>;
1937
1960
  /** A plugin/marketplace ORIGIN string: **https only**(contract freeze [ref] tightening-① /
1938
1961
  * [1366]①,0.10.23 单真源修:此前这条规则只落在 web UI 层的表单校验,API 直写仍放行任意形——
1939
1962
  * 同一条规则两处真源,API 这处敞了 8 天)。`ssh:`/`scp 形`/`git:`/`file:`/裸 `http:`/`owner/repo`
@@ -2509,11 +2532,11 @@ export declare const DOMAIN_SCHEMAS: {
2509
2532
  kind: z.ZodLiteral<"stdio">;
2510
2533
  command: z.ZodString;
2511
2534
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
2512
- envRefs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
2535
+ envRefs: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
2513
2536
  }, z.core.$strip>, z.ZodObject<{
2514
2537
  kind: z.ZodLiteral<"http">;
2515
2538
  url: z.ZodString;
2516
- headerRefs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
2539
+ headerRefs: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
2517
2540
  principalHeader: z.ZodOptional<z.ZodString>;
2518
2541
  assertionHeader: z.ZodOptional<z.ZodString>;
2519
2542
  }, z.core.$strip>], "kind">;
@@ -2677,17 +2700,9 @@ export declare const DOMAIN_SCHEMAS: {
2677
2700
  runtime: z.ZodObject<{
2678
2701
  execution: z.ZodOptional<z.ZodObject<{
2679
2702
  lane: z.ZodOptional<z.ZodNever>;
2680
- lanes: z.ZodArray<z.ZodEnum<{
2681
- host: "host";
2682
- e2b: "e2b";
2683
- k8s: "k8s";
2684
- ssh: "ssh";
2685
- adb: "adb";
2686
- "local-docker": "local-docker";
2687
- device: "device";
2688
- }>>;
2703
+ lanes: z.ZodPreprocess<z.ZodPipe<z.ZodUnknown, z.ZodTransform<("none" | "host" | "e2b" | "k8s" | "ssh" | "adb" | "local-docker" | "device")[], unknown>>, unknown>;
2689
2704
  coords: z.ZodPrefault<z.ZodRecord<z.ZodString, z.ZodString>>;
2690
- secretRefs: z.ZodPrefault<z.ZodRecord<z.ZodString, z.ZodString>>;
2705
+ secretRefs: z.ZodPrefault<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
2691
2706
  }, z.core.$strip>>;
2692
2707
  store: z.ZodOptional<z.ZodObject<{
2693
2708
  backend: z.ZodEnum<{
@@ -2741,7 +2756,7 @@ export declare const DOMAIN_SCHEMAS: {
2741
2756
  tidbPasswordEnv: z.ZodOptional<z.ZodNever>;
2742
2757
  }, z.core.$strip>;
2743
2758
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
2744
- secretEnv: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
2759
+ secretEnv: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
2745
2760
  recreateNonce: z.ZodOptional<z.ZodString>;
2746
2761
  lifecycle: z.ZodOptional<z.ZodObject<{
2747
2762
  kind: z.ZodDefault<z.ZodEnum<{
@@ -3083,6 +3098,18 @@ export declare const DOMAIN_SCHEMAS: {
3083
3098
  runStaleSec: z.ZodOptional<z.ZodNumber>;
3084
3099
  toolResultTtlSec: z.ZodOptional<z.ZodNumber>;
3085
3100
  }, z.core.$loose>;
3101
+ readonly a2a: z.ZodObject<{
3102
+ peers: z.ZodPrefault<z.ZodArray<z.ZodObject<{
3103
+ name: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
3104
+ url: z.ZodString;
3105
+ cardUrl: z.ZodOptional<z.ZodString>;
3106
+ headerRefs: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodString>, unknown>>;
3107
+ principalHeader: z.ZodOptional<z.ZodString>;
3108
+ allowSkills: z.ZodOptional<z.ZodArray<z.ZodString>>;
3109
+ scenarios: z.ZodPrefault<z.ZodArray<z.ZodString>>;
3110
+ enabled: z.ZodDefault<z.ZodBoolean>;
3111
+ }, z.core.$strip>>>;
3112
+ }, z.core.$strip>;
3086
3113
  };
3087
3114
  export type DomainName = keyof typeof DOMAIN_SCHEMAS;
3088
3115
  export type DomainConfig = {
@@ -3130,6 +3157,8 @@ export interface EffectiveConfig {
3130
3157
  /** cost/限流族的热更新发布位(0.19.0,消费方 #322 批1)。域缺席 ⇒ `{}`(每一键都 ABSENT =
3131
3158
  * web-admin 不管 ⇒ 消费方回 env 底);11.0.0 起五个限额键的唯一位。 */
3132
3159
  limits: LimitsConfig;
3160
+ /** 常驻出站 A2A 对端名册(18.0.0)。域缺席 ⇒ `{ peers: [] }`(缺席 ≡ 空表:不拨任何对端);header 值只以 env-NAME 引用出现,读方从自己的环境取值。 */
3161
+ a2a: A2aConfig;
3133
3162
  }
3134
3163
  /** One provenance source in a key's resolution trail. Two orthogonal axes: the TOC file LAYER, or the TOB
3135
3164
  * PRINCIPAL dimension. `applied:false` + `reason` mark a source whose attempt was REJECTED by the monotone