@sema-agent/settings-schema 13.0.1 → 14.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 (136) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +18 -1
  3. package/conformance/VERSION +1 -1
  4. package/conformance/baseline.json +1 -3
  5. package/conformance/cases/c01-entry-salvage-then-typo/expected.json +1 -3
  6. package/conformance/cases/c02-hosts-clamp-stacked/expected.json +1 -3
  7. package/conformance/cases/c03-literal-dotted-top-key/expected.json +1 -3
  8. package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +1 -3
  9. package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +1 -3
  10. package/conformance/cases/c08-limits-unknown-carried-64/expected.json +1 -3
  11. package/conformance/cases/c09-limits-unknown-carried-65/expected.json +1 -3
  12. package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +1 -3
  13. package/conformance/cases/c11-unicode-unknown-keys/expected.json +1 -3
  14. package/conformance/cases/c12-prototype-name-top-keys/expected.json +1 -3
  15. package/conformance/cases/c13-models-field-null/expected.json +1 -3
  16. package/conformance/cases/c14-models-field-empty-array/expected.json +1 -3
  17. package/conformance/cases/c15-models-field-number/expected.json +1 -3
  18. package/conformance/cases/c16-deep-nesting-205/expected.json +1 -3
  19. package/conformance/cases/c17-empty-domain-file/expected.json +1 -3
  20. package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +1 -3
  21. package/conformance/cases/c19-wire-envelope-no-version/expected.json +1 -3
  22. package/conformance/cases/c21-governance-empty-object-wins/expected.json +1 -3
  23. package/conformance/cases/c23-mixed-validity-models/expected.json +1 -3
  24. package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +1 -3
  25. package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +1 -3
  26. package/conformance/cases/c26-hosts-array-1024/expected.json +1 -3
  27. package/conformance/cases/c27-hosts-array-1025/expected.json +1 -3
  28. package/conformance/cases/c28-large-document-over-1mb/expected.json +1 -3
  29. package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +1 -3
  30. package/conformance/cases/c30-version-triple-a-first/expected.json +1 -3
  31. package/conformance/cases/c31-version-triple-b-changed/expected.json +1 -3
  32. package/conformance/cases/c32-version-triple-a-restored/expected.json +1 -3
  33. package/conformance/cases/c33-limits-nested-carried-key/expected.json +1 -3
  34. package/conformance/cases/c34-skills-content-vs-manifest/expected.json +1 -3
  35. package/conformance/cases/c35-portable-domain-file-is-null/expected.json +1 -3
  36. package/conformance/cases/c36-skills-entry-without-content/expected.json +1 -3
  37. package/conformance/cases/c37-skills-field-null/expected.json +1 -3
  38. package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +1 -3
  39. package/conformance/cases/c39-hidden-file-in-config-d/expected.json +1 -3
  40. package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +1 -3
  41. package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +1 -3
  42. package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +1 -3
  43. package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +1 -3
  44. package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +1 -3
  45. package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +1 -3
  46. package/conformance/cases/c46-s811-host-name-url-userinfo-withheld/expected.json +1 -3
  47. package/conformance/cases/c47-s811-roles-key-url-userinfo-withheld/expected.json +1 -3
  48. package/conformance/cases/c48-s935-model-headers-transported/expected.json +1 -3
  49. package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +1 -3
  50. package/conformance/cases/c50-s784-lane-host-legal/expected.json +1 -3
  51. package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +1 -3
  52. package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +1 -3
  53. package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +1 -3
  54. package/conformance/cases/c54-s784-lane-adb-legal/expected.json +1 -3
  55. package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +1 -3
  56. package/conformance/cases/c56-s784-lane-device-legal/expected.json +1 -3
  57. package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +1 -3
  58. package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +1 -3
  59. package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +1 -3
  60. package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +1 -3
  61. package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +1 -3
  62. package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +1 -3
  63. package/conformance/cases/c63-s784-host-two-replicas/expected.json +1 -3
  64. package/conformance/cases/c64-s784-multitenant-local-store/expected.json +1 -3
  65. package/conformance/cases/c65-s784-pg-without-db/expected.json +1 -3
  66. package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +1 -3
  67. package/conformance/cases/c67-s784-retired-field-loud/expected.json +1 -3
  68. package/conformance/cases/c68-s784-device-local-store/expected.json +1 -3
  69. package/conformance/cases/c69-deep-encoded-token-past-every-reading/expected.json +1 -3
  70. package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/expected.json +1 -3
  71. package/conformance/cases/c71-one-scanner-write-reads-display-forms/expected.json +1 -3
  72. package/conformance/cases/c72-dangling-reference-write-seat-refuses/expected.json +1 -3
  73. package/conformance/cases/c73-projects-extended-registration-reads/expected.json +1 -3
  74. package/conformance/cases/c74-projects-over-bound-refused/expected.json +1 -3
  75. package/conformance/cases/c75-projects-patch-remote-uniqueness/expected.json +1 -3
  76. package/conformance/cases/c76-projects-entry-salvage-nested/expected.json +1 -3
  77. package/conformance/cases/c77-prompts-center-artifact-carried/expected.json +252 -0
  78. package/conformance/cases/c77-prompts-center-artifact-carried/input.json +93 -0
  79. package/conformance/cases/c77-prompts-center-artifact-carried/meta.json +5 -0
  80. package/conformance/cases/c78-prompts-empty-bindings-refused/expected.json +82 -0
  81. package/conformance/cases/c78-prompts-empty-bindings-refused/input.json +11 -0
  82. package/conformance/cases/c78-prompts-empty-bindings-refused/meta.json +5 -0
  83. package/conformance/cases/c79-prompts-bad-worker-artifact-not-salvaged/expected.json +82 -0
  84. package/conformance/cases/c79-prompts-bad-worker-artifact-not-salvaged/input.json +129 -0
  85. package/conformance/cases/c79-prompts-bad-worker-artifact-not-salvaged/meta.json +5 -0
  86. package/conformance/cases/c80-prompts-wire-v2-verbatim/expected.json +195 -0
  87. package/conformance/cases/c80-prompts-wire-v2-verbatim/input.json +65 -0
  88. package/conformance/cases/c80-prompts-wire-v2-verbatim/meta.json +5 -0
  89. package/conformance/cases/c81-prompts-declaration-bindings-refused/expected.json +82 -0
  90. package/conformance/cases/c81-prompts-declaration-bindings-refused/input.json +23 -0
  91. package/conformance/cases/c81-prompts-declaration-bindings-refused/meta.json +5 -0
  92. package/conformance/cases/c82-prompts-v1-catalog-refused/expected.json +82 -0
  93. package/conformance/cases/c82-prompts-v1-catalog-refused/input.json +35 -0
  94. package/conformance/cases/c82-prompts-v1-catalog-refused/meta.json +5 -0
  95. package/conformance/cases/c83-prompts-bad-overlay-not-salvaged/expected.json +82 -0
  96. package/conformance/cases/c83-prompts-bad-overlay-not-salvaged/input.json +56 -0
  97. package/conformance/cases/c83-prompts-bad-overlay-not-salvaged/meta.json +5 -0
  98. package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +1 -3
  99. package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +1 -3
  100. package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +1 -3
  101. package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +1 -3
  102. package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +1 -3
  103. package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +1 -3
  104. package/conformance/cases/cli04-env-value-with-newline/expected.json +1 -3
  105. package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +1 -3
  106. package/conformance/cases/t01-absent-domain-key-not-null/expected.json +1 -3
  107. package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +1 -3
  108. package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +1 -3
  109. package/conformance/cases/z01-every-domain-empty-object/expected.json +2 -6
  110. package/conformance/cases/z02-every-domain-unknown-key/expected.json +2 -6
  111. package/dist/config-fns.d.ts +7 -3
  112. package/dist/config-fns.js +19 -16
  113. package/dist/conformance/derive.js +2 -2
  114. package/dist/index.d.ts +1 -0
  115. package/dist/index.js +1 -0
  116. package/dist/mcp-request-entry.d.ts +93 -0
  117. package/dist/mcp-request-entry.js +157 -0
  118. package/dist/migration-receipt.d.ts +1 -1
  119. package/dist/migration-receipt.js +1 -1
  120. package/dist/node.d.ts +0 -1
  121. package/dist/node.js +0 -1
  122. package/dist/one-time-migrations.d.ts +1 -1
  123. package/dist/one-time-migrations.js +39 -0
  124. package/dist/safety-merge-spec.d.ts +6 -6
  125. package/dist/types.d.ts +438 -355
  126. package/dist/types.js +161 -136
  127. package/dist/url-shape.d.ts +8 -0
  128. package/dist/url-shape.js +10 -0
  129. package/docs/API-SURFACE.md +41 -27
  130. package/docs/CONFORMANCE.md +1 -1
  131. package/docs/CONSUMER-INTEGRATION.md +3 -2
  132. package/llms-full.txt +14 -11
  133. package/llms.txt +7 -2
  134. package/package.json +2 -2
  135. package/dist/prompts-digest.d.ts +0 -16
  136. package/dist/prompts-digest.js +0 -49
@@ -165,8 +165,8 @@ export const DOMAIN_WRITE_ROLE = {
165
165
  // entitlement governs who-can-USE-what (models/skills/mcp/budget/runtime caps) per principal — a governance +
166
166
  // security surface (budget ceilings, bypass kill-switch) on par with systems. Same raised gate.
167
167
  entitlement: "publisher",
168
- // prompts (0.10.19, [ref]③/②d): declaration text goes STRAIGHT into the system prompt of
169
- // every worker×scenario the binding covers — the same injection surface (and rationale) as skills/mcp,
168
+ // prompts (0.10.19, [ref]③/②d): the center artifact's overlay text goes STRAIGHT into the system prompt of
169
+ // every worker×scenario it applies to — the same injection surface (and rationale) as skills/mcp,
170
170
  // so the same raised gate. Web-admin had pre-raised this locally per governance precedent; this row is the
171
171
  // contract-side source of truth so every consumer of the shared write gate gets publisher, not editor.
172
172
  prompts: "publisher",
@@ -969,9 +969,8 @@ export const DOMAIN_READ_FALLBACK = {
969
969
  entitlement: "throw",
970
970
  execution: "throw",
971
971
  projects: "throw",
972
- // prompts:内容域(scenarios 同类)——坏形→空默认(=「无 prompts 管理」当年语义,web [1064]②
973
- // backfill-empty-default 同口径);fail-closed 由 PUBLISH_MODE 发布闸承担,default 不扩权
974
- // (空 bindings=不下发任何内容)。
972
+ // prompts:内容域(scenarios 同类)——坏形→空默认 `{}`(=「不管提示词」:不下发中心层,引擎用内置);fail-closed 由 PUBLISH_MODE
973
+ // 发布闸承担,default 不扩权。14.0.0 起墓碑键(`bindings` / `catalog`)也走这一档(读不出 ⇒ 缺省 + domain-defaulted 点名 SS-M10)。
975
974
  prompts: "default",
976
975
  // limits(0.19.0):THROW —— 本域是成本 / 限流天花板的唯一发布位(11.0.0 起 runtime 域retired)。坏形
977
976
  // 降级成空域 = 中央发布的成本/限流天花板整片蒸发,消费方回自己的 env 底(可能宽得多)= 预算轴放权。
@@ -991,7 +990,7 @@ export const DOMAIN_READ_FALLBACK = {
991
990
  * it back would turn every dropped entry into a deletion — read-modify-write faces read the stored document with the document face.
992
991
  *
993
992
  * Per-domain rationale:
994
- * - models/skills/mcp/plugins/scenarios/systems/collab/prompts: additive capability / content catalogs — a dropped entry is
993
+ * - models/skills/mcp/plugins/scenarios/systems/collab: additive capability / content catalogs — a dropped entry is
995
994
  * one capability gone (fail-closed). Inside a surviving entry the same holds: the nested collections these entries carry
996
995
  * are allowlists / scopings (`allowTools`, `scenarios`, `rosters`, `callsWorkers`, …) where one entry fewer only NARROWS,
997
996
  * and the zero-survivor law (never mint an empty collection — several of these read "empty = all") holds at every level.
@@ -1001,7 +1000,11 @@ export const DOMAIN_READ_FALLBACK = {
1001
1000
  * schema refuses (12.2.1's UNC judgement on `contextItems[].value`) must not take the scope's other projects with it.
1002
1001
  * Its whenUnreadable stance stays `"throw"` (a ledger that cannot be read at all is never defaulted — see
1003
1002
  * {@link DOMAIN_READ_FALLBACK}).
1004
- * - rosters: NONE — a dropped roster makes resolveEffectiveForWorker fall back to the FULL models catalog for that worker
1003
+ * - prompts: NONE (14.0.0) — an artifact is digest-bound: a dropped overlay leaves an artifact whose digest no longer holds (the
1004
+ engine refuses the whole candidate it rides in, every domain with it), and a dropped worker artifact substitutes the scope's
1005
+ text for that worker (a replacement, not a narrowing). Its whenUnreadable stance stays `"default"`: unreadable ⇒ `{}` ⇒ no
1006
+ center layer is served (the engine's built-in prompt).
1007
+ - rosters: NONE — a dropped roster makes resolveEffectiveForWorker fall back to the FULL models catalog for that worker
1005
1008
  * (放权,不许).
1006
1009
  * - workers: NONE — a dropped worker row reads as "this worker is no longer declared": the reconciler would retire it. The
1007
1010
  * per-row tolerance this domain does have lives in `readWorkersRows` (rows are HELD with their original, never dropped).
@@ -1024,7 +1027,7 @@ export const DOMAIN_ENTRY_SALVAGE = {
1024
1027
  entitlement: "none",
1025
1028
  execution: "none",
1026
1029
  projects: "salvage",
1027
- prompts: "salvage",
1030
+ prompts: "none",
1028
1031
  limits: "none",
1029
1032
  };
1030
1033
  /**
@@ -1074,8 +1077,8 @@ export function buildEffective(version, getRaw, updatedAt, onWarning) {
1074
1077
  // execution (0.8.0, S3): plain scope-level parse — MIRRORS governance (no overlay/merge invented here).
1075
1078
  execution: parseOr("execution", getRaw("execution")),
1076
1079
  projects: parseOr("projects", getRaw("projects")),
1077
- // prompts (0.10.18, [1064]②): content domain(坏形回空默认=「无 prompts 管理」语义;fail-closed 由 PUBLISH_MODE 发布闸承担,
1078
- // 空 bindings 不下发内容,default 不扩权)。
1080
+ // prompts (0.10.18 起;14.0.0 单轴): content domain(坏形回空默认 `{}` =「不管提示词」;fail-closed 由 PUBLISH_MODE 发布闸承担,
1081
+ // `{}` 不下发中心层,default 不扩权)。
1079
1082
  prompts: parseOr("prompts", getRaw("prompts")),
1080
1083
  limits: parseOr("limits", getRaw("limits")),
1081
1084
  };
@@ -1109,10 +1112,10 @@ export function readEffectiveWire(raw, onWarning) {
1109
1112
  const updatedAt = typeof r.updatedAt === "string" ? r.updatedAt : "";
1110
1113
  // skills redirected to undefined: buildEffective must never see the manifest(it would fail the full-domain
1111
1114
  // schema and "default" it with a FALSE domain-defaulted warning);the manifest is judged separately below.
1112
- // prompts likewise redirected(#100):on the wire it is web-admin-assembled RESOLVED form(worker lane
1113
- // strips the bindings storage table — types.ts PromptsConfig 注 [1057]⑤),consumer-validated additive JSON;
1114
- // the storage-domain schema would swallow it to {bindings:[]} with ZERO warning(zod non-strict 剥键后
1115
- // "成功")— 3.24.0 起 server 侧 promptsGate 恒败的根因。verbatim,by presence(见 return)。
1115
+ // prompts likewise redirected(#100):on the wire it is the published form `EffectivePromptsWire`(`{schemaVersion: 2, artifact}`,
1116
+ // the one artifact selected for the pulling worker — never the storage shape), judged by the CONSUMER(that shape, then the engine's
1117
+ // verify). The read leg must not parse it with the storage schema: a bad prompts value would be degraded to "absent" here and read
1118
+ // downstream as "the center withdrew its layer" — the consumer's judge refuses it loudly instead. verbatim, by presence(见 return)。
1116
1119
  const built = buildEffective(r.version, (d) => (d === "skills" || d === "prompts" ? undefined : r[d]), updatedAt, onWarning);
1117
1120
  // retired 域名不是 open-world 的未知键:墓碑已在 buildEffective 里判过(带事实 ⇒ 抛),剩下的 `{}` 没有可承运的东西。
1118
1121
  let skills;
@@ -1143,8 +1146,8 @@ export function readEffectiveWire(raw, onWarning) {
1143
1146
  // `Object.fromEntries`,**不是**逐键 `passthrough[k] = v`:后者对 `"__proto__"` 走的是原型 setter
1144
1147
  // (改掉对象的原型,而不是加一个键),fromEntries 走 CreateDataProperty —— 同一条「键就是键」的律。
1145
1148
  const passthrough = Object.fromEntries(Object.entries(r).filter(([k]) => !Object.prototype.hasOwnProperty.call(built, k) && k !== "skills" && !isRetiredDomainName(k)));
1146
- // prompts:剥掉 buildEffective 恒物化的 {bindings:[]} 底座,按 wire 在场性 verbatim 挂回(#100:
1147
- // 缺席进缺席出——web-admin worker-lane 闸关是常态形,物化会让消费端「缺席」分支永不可达)。
1149
+ // prompts:剥掉 buildEffective 恒物化的 `{}` 底座,按 wire 在场性 verbatim 挂回(#100:
1150
+ // 缺席进缺席出——发布闸关是常态形,物化会让消费端「缺席」分支永不可达)。
1148
1151
  const { prompts: _promptsMaterialized, ...builtSansPrompts } = built;
1149
1152
  return { ...passthrough, ...builtSansPrompts, skills, ...("prompts" in r ? { prompts: r.prompts } : {}) };
1150
1153
  }
@@ -8,7 +8,7 @@
8
8
  * 显示成 diff。于是「有没有第四条变换没登记」这件事不再靠人回忆 —— 没登记的那条会自己红出来。
9
9
  *
10
10
  * 变换表(每条写明:动了什么、影响哪几条腿、出处):
11
- * · **T1 prompts 物化**(wire):`buildEffective` 恒物化 `prompts: {bindings:[]}`;wire 腿按**在场性**
11
+ * · **T1 prompts 物化**(wire):`buildEffective` 恒物化 `prompts`(域缺席 ⇒ `{}`;14.0.0 前是 `{bindings:[]}`);wire 腿按**在场性**
12
12
  * verbatim 挂回(`readEffectiveWire` 的 `"prompts" in r` 判据)—— 缺席进缺席出,且在场时是**未解析的
13
13
  * 原文档**。
14
14
  * · **T2 skills 表示**(wire):域形带 `content`,wire 形带 `contentHash`(manifest)。判据 schema 随表示走。
@@ -39,7 +39,7 @@ import { skillContentHash } from "../skills-manifest.js";
39
39
  import { DOMAINS, isDomainName } from "../types.js";
40
40
  /** 变换表(本文件头注的机器形;`docs/CONFORMANCE.md` 与 `test/conformance-coverage.test.ts` 都读它)。 */
41
41
  export const DERIVATION_TRANSFORMS = [
42
- { id: "T1-prompts-materialisation", legs: ["wire"], what: "buildEffective 恒物化 prompts:{bindings:[]};wire 按在场性 verbatim 挂回(缺席进缺席出)" },
42
+ { id: "T1-prompts-materialisation", legs: ["wire"], what: "buildEffective 恒物化 prompts(缺席 ⇒ {});wire 按在场性 verbatim 挂回(缺席进缺席出)" },
43
43
  { id: "T2-skills-representation", legs: ["wire"], what: "域形 content ↔ wire 形 contentHash(manifest),判据 schema 随表示走" },
44
44
  { id: "T3-non-portable-domains", legs: ["localRoot"], what: "config.d/ 只读 PORTABLE_DOMAINS;其余域取默认 + unread-config-file{non-portable-domain}" },
45
45
  { id: "T4-unknown-top-level-key", legs: ["wire", "localRoot"], what: "wire open-world 逐字承运;domains 腿按域名取文档故无声;local 报 unread-config-file{not-a-domain}" },
package/dist/index.d.ts CHANGED
@@ -36,3 +36,4 @@ export * from "./url-pattern.js";
36
36
  export * from "./http-hook-allowlists.js";
37
37
  export * from "./mcp-server-policy.js";
38
38
  export * from "./ui-language.js";
39
+ export * from "./mcp-request-entry.js";
package/dist/index.js CHANGED
@@ -36,6 +36,7 @@ export * from "./url-pattern.js"; // 13.0.0 (S-1597): THE URL-list pattern judge
36
36
  export * from "./http-hook-allowlists.js"; // 13.0.0 (U3): allowedHttpHookUrls / httpHookAllowedEnvVars — one layer reader + httpHookUrlVerdict / httpHookEnvVarsAllowed (admitted layers INTERSECT; SAFETY_MERGE_SPEC.httpHookAllowlists), pure/browser-safe
37
37
  export * from "./mcp-server-policy.js"; // 13.0.0 (U6): mcpServerPolicyVerdict → McpPolicyBlock{name, list, rule, target, written, sources} (structure, no sentence; target judged expanded, echoed only as written), pure/browser-safe
38
38
  export * from "./ui-language.js"; // 13.0.0 (S-1592): the UI-language settings fragment (semaUiLanguage, en | zh, category ui, user settings) + readUiLanguage, pure/browser-safe
39
+ export * from "./mcp-request-entry.js"; // 14.0.0: the request-plane MCP entry judges (isUsableWireName / isContentOriginWord / readMcpIdentityHeaders / readRequestMcpTransport + the value-shape describers) — one reading for the service legs and the shell pre-check, pure/browser-safe
39
40
  // NOTE: the Node-fs-backed helpers (FileConfigStore + the file editor) are NOT in this barrel — they pull
40
41
  // `node:fs`/`node:path` and would break a browser/client bundle that only wants the contract (e.g.
41
42
  // web-admin's client components import the zod types through here). Import them from the Node-only
@@ -0,0 +1,93 @@
1
+ /**
2
+ * 请求面 MCP 条目(一条任务提交体 `mcpServers[i]`)的形判官 —— 14.0.0 起住本包:一只读法,消费方的请求腿 / 中心腿 / 壳的发送前预检都 import 这一份。
3
+ * 纯函数、浏览器安全(唯一的值依赖是本包自己的内容出处词表)。
4
+ *
5
+ * ## 律(一条)
6
+ * **一条请求面 MCP 条目的每一格「能不能用」只有一只判官。** 此前这些判官是消费方私有的:壳拿不到,只能在发送前披露「判不准、交给服务端」,
7
+ * 或者自己抄一份 —— 两份判据就是将来漂开的那一份。现在判官在契约包,服务端与壳读同一份,同一条条目在两处同受理 / 同拒、同一个键路径、同一句原因。
8
+ *
9
+ * ## 语义是搬来的,不是重写的
10
+ * 判据与原因句逐字等于消费方 7.131.x 的那一份(钉:`test/mcp-request-entry-14.0.0.test.ts` 逐例搬了消费方既有钉的夹具与期望)。
11
+ * 本模块只答「这一格的形」;拒绝的整句(条目下标、条目名的回显清洗、收尾句)与「拒还是降级」的后果仍是消费方按自己的腿定的 —— 判据一份,后果按腿分。
12
+ *
13
+ * ## 依赖的引擎事实(镜像 + 漂移门)
14
+ * · 申报名的线长界 {@link TOOL_WIRE_NAME_MAX_CHARS} = 引擎同名常量(值级对拍:上游漂移门 ⑧);
15
+ * · 请求面传输形 {@link RequestMcpTransport} = 引擎 MCP 条目的传输形,http 形多一个本包承载的 `assertionHeader`(编译期双向对拍:门 ⑧);
16
+ * · 内容出处词表 = {@link MCP_CONTENT_ORIGINS}(已有的镜像,门 ⑥);
17
+ * · url 前缀守卫 = `./url-shape` 的 `startsWithHttpScheme`(本包判 URL 的正则只在那一个模块)。
18
+ *
19
+ * ## 与配置面的关系(如实)
20
+ * 配置面的 MCP 条目(`McpServerSpec`,部署声明:凭据只许 env-NAME 引用)与请求面(提交体:可带字面凭据 `env` / `headers`)是两份契约,本模块只管后者。
21
+ * 两份契约对「名」与「url」今天给的判据不同(配置面:名按字符集、url 按 `NON_SECRET_URL`;请求面:名按长度界、url 按 http(s) 前缀),本版刻意不动
22
+ * (语义逐字搬),是一项已知待收的分歧。
23
+ */
24
+ import { type McpContentOrigin } from "./types.js";
25
+ /** 值的**种类**(不回显值本身)。 */
26
+ export declare function describeValueShape(v: unknown): string;
27
+ /** 「不是字符串数组」的种类:数组里混了非串元素说那一句,否则回落到 {@link describeValueShape}。 */
28
+ export declare function describeNotStringArray(v: unknown): string;
29
+ /** 「不是字符串值的对象」的种类:对象里混了非串值说那一句,否则回落到 {@link describeValueShape}。 */
30
+ export declare function describeNotStringRecord(v: unknown): string;
31
+ /** 字符串数组(空数组合法)。 */
32
+ export declare function isStringArray(v: unknown): v is string[];
33
+ /** 字符串值的对象(空对象合法;数组不是)。 */
34
+ export declare function isStringRecord(v: unknown): v is Record<string, string>;
35
+ /** 引擎 `TOOL_WIRE_NAME_MAX_CHARS` 的镜像:一台 MCP server(或 A2A 对端)的申报名会成为线上工具名的命名空间段,引擎对超界名是**拒**(不截断)。 */
36
+ export declare const TOOL_WIRE_NAME_MAX_CHARS = 128;
37
+ /**
38
+ * 这个申报名能用吗。严判:非串、空串、超 {@link TOOL_WIRE_NAME_MAX_CHARS} 一律不可用。不截断、不规范化 —— 截短一个名字就是**换一个身份**,
39
+ * 而被换掉身份的那台 server 上挂的 allowTools / toolFaces 收窄声明会一起对不上号。
40
+ */
41
+ export declare function isUsableWireName(v: unknown): v is string;
42
+ /** 坏名的**可打印**摘要(进拒句 / 告警元数据):种类、`empty`,或截到 32 字加长度事实 —— 绝不把任意长度的申报串原样回显。 */
43
+ export declare function describeBadWireName(v: unknown): string;
44
+ /**
45
+ * 在内容出处词表({@link MCP_CONTENT_ORIGINS})内吗。严判:非串与大小写不符(`"LOCAL"`)一律不在 —— 引擎的词是字面量联合、不做规范化;
46
+ * 替它「宽容一下」等于自造第二份值域语义,而这条轴上宽容的方向恰好是**放宽信任**(把一个写坏的声明读成 `"local"`)。
47
+ */
48
+ export declare function isContentOriginWord(v: unknown): v is McpContentOrigin;
49
+ /** 坏内容出处值的**可打印**摘要:串 JSON 化、64 字截断加 `…`;非串只说种类。 */
50
+ export declare function describeBadContentOrigin(v: unknown): string;
51
+ /**
52
+ * 一份 http 传输上两个身份头(`principalHeader` / `assertionHeader`)的读法:字符串才收;**`principalHeader` 胜** —— 它在场(不论值形)时
53
+ * `assertionHeader` 不收。所以读后的形**结构上不可能同带两键**;同设(一份声明给出两个答案)由调用方在读之前对**原始**传输判(判的是写了什么,不是读后留下什么)。
54
+ */
55
+ export declare function readMcpIdentityHeaders(transport: Record<string, unknown>): {
56
+ principalHeader?: string;
57
+ assertionHeader?: string;
58
+ };
59
+ /** 请求面一条 MCP 条目的传输形:引擎的传输形,http 形多一个承载的 `assertionHeader`(引擎的 http 传输没有这个座;交给引擎之前由消费方剥掉)。 */
60
+ export type RequestMcpTransport = {
61
+ kind: "stdio";
62
+ command: string;
63
+ args?: string[];
64
+ env?: Record<string, string>;
65
+ } | {
66
+ kind: "http";
67
+ url: string;
68
+ headers?: Record<string, string>;
69
+ principalHeader?: string;
70
+ assertionHeader?: string;
71
+ };
72
+ /** 条目内一处坏形:`path` = 条目内的键路径(`transport.url`),`reason` = 判官的原因句(只说形与词表,不回显值)。 */
73
+ export interface McpEntryKeyIssue {
74
+ readonly path: string;
75
+ readonly reason: string;
76
+ }
77
+ /** {@link readRequestMcpTransport} 的结果:`ok:false` = 硬坏形(这条 server 无从挂载);`ok:true` 的 `lost` = 读器**丢掉**的可选子键(server 照挂、少一个声明)。 */
78
+ export type RequestMcpTransportRead = {
79
+ ok: true;
80
+ transport: RequestMcpTransport;
81
+ lost: readonly McpEntryKeyIssue[];
82
+ } | {
83
+ ok: false;
84
+ issue: McpEntryKeyIssue;
85
+ };
86
+ /**
87
+ * 请求面 `transport` 的**唯一读器**:读器投影 transport 用它,发送前 / 受理时的判形读它报的 `lost` —— 同一只函数两个消费点,「读器丢了什么」与「门拒什么」不可能分岔。
88
+ * · 硬坏形(`transport` 不是对象 / `kind` 出闭集 / stdio 缺 `command` / http 的 `url` 不是 http(s))⇒ `ok:false`,`path` 是读器点名的键路径;
89
+ * · 软坏形(`args` 不是串数组 / `env` `headers` 含非串值 / `principalHeader` 非串)⇒ 读器**丢这个键**(重放存量提交体时的升级前读法),并把它记进 `lost`
90
+ * (受理新提交的门据它响亮拒)。`assertionHeader` 不在 `lost` 里:两个身份头的键组合与 `assertionHeader` 的值形由调用方在读之前对原始传输判。
91
+ * url 的拒句不回显值:它常规地带凭据(userinfo)。
92
+ */
93
+ export declare function readRequestMcpTransport(t: unknown): RequestMcpTransportRead;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * 请求面 MCP 条目(一条任务提交体 `mcpServers[i]`)的形判官 —— 14.0.0 起住本包:一只读法,消费方的请求腿 / 中心腿 / 壳的发送前预检都 import 这一份。
3
+ * 纯函数、浏览器安全(唯一的值依赖是本包自己的内容出处词表)。
4
+ *
5
+ * ## 律(一条)
6
+ * **一条请求面 MCP 条目的每一格「能不能用」只有一只判官。** 此前这些判官是消费方私有的:壳拿不到,只能在发送前披露「判不准、交给服务端」,
7
+ * 或者自己抄一份 —— 两份判据就是将来漂开的那一份。现在判官在契约包,服务端与壳读同一份,同一条条目在两处同受理 / 同拒、同一个键路径、同一句原因。
8
+ *
9
+ * ## 语义是搬来的,不是重写的
10
+ * 判据与原因句逐字等于消费方 7.131.x 的那一份(钉:`test/mcp-request-entry-14.0.0.test.ts` 逐例搬了消费方既有钉的夹具与期望)。
11
+ * 本模块只答「这一格的形」;拒绝的整句(条目下标、条目名的回显清洗、收尾句)与「拒还是降级」的后果仍是消费方按自己的腿定的 —— 判据一份,后果按腿分。
12
+ *
13
+ * ## 依赖的引擎事实(镜像 + 漂移门)
14
+ * · 申报名的线长界 {@link TOOL_WIRE_NAME_MAX_CHARS} = 引擎同名常量(值级对拍:上游漂移门 ⑧);
15
+ * · 请求面传输形 {@link RequestMcpTransport} = 引擎 MCP 条目的传输形,http 形多一个本包承载的 `assertionHeader`(编译期双向对拍:门 ⑧);
16
+ * · 内容出处词表 = {@link MCP_CONTENT_ORIGINS}(已有的镜像,门 ⑥);
17
+ * · url 前缀守卫 = `./url-shape` 的 `startsWithHttpScheme`(本包判 URL 的正则只在那一个模块)。
18
+ *
19
+ * ## 与配置面的关系(如实)
20
+ * 配置面的 MCP 条目(`McpServerSpec`,部署声明:凭据只许 env-NAME 引用)与请求面(提交体:可带字面凭据 `env` / `headers`)是两份契约,本模块只管后者。
21
+ * 两份契约对「名」与「url」今天给的判据不同(配置面:名按字符集、url 按 `NON_SECRET_URL`;请求面:名按长度界、url 按 http(s) 前缀),本版刻意不动
22
+ * (语义逐字搬),是一项已知待收的分歧。
23
+ */
24
+ import { MCP_CONTENT_ORIGINS } from "./types.js";
25
+ import { startsWithHttpScheme } from "./url-shape.js";
26
+ // ── 值的种类描述(拒句只说「你写的是什么形」,不回显值:值可能带凭据、可能任意长)──────────────────────────────────────
27
+ /** 值的**种类**(不回显值本身)。 */
28
+ export function describeValueShape(v) {
29
+ if (v === undefined)
30
+ return "nothing (the key is absent)";
31
+ if (v === null)
32
+ return "null";
33
+ if (Array.isArray(v))
34
+ return "an array";
35
+ if (typeof v === "string")
36
+ return v.length === 0 ? "an empty string" : `a string of ${v.length} characters`;
37
+ if (typeof v === "object")
38
+ return "an object";
39
+ return `a ${typeof v}`;
40
+ }
41
+ /** 「不是字符串数组」的种类:数组里混了非串元素说那一句,否则回落到 {@link describeValueShape}。 */
42
+ export function describeNotStringArray(v) {
43
+ return Array.isArray(v) ? "an array with a non-string element" : describeValueShape(v);
44
+ }
45
+ /** 「不是字符串值的对象」的种类:对象里混了非串值说那一句,否则回落到 {@link describeValueShape}。 */
46
+ export function describeNotStringRecord(v) {
47
+ return isRecord(v) ? "an object with a non-string value" : describeValueShape(v);
48
+ }
49
+ /** 字符串数组(空数组合法)。 */
50
+ export function isStringArray(v) {
51
+ return Array.isArray(v) && v.every((a) => typeof a === "string");
52
+ }
53
+ /** 字符串值的对象(空对象合法;数组不是)。 */
54
+ export function isStringRecord(v) {
55
+ return !!v && typeof v === "object" && !Array.isArray(v) && Object.values(v).every((x) => typeof x === "string");
56
+ }
57
+ function isRecord(v) {
58
+ return v !== null && typeof v === "object" && !Array.isArray(v);
59
+ }
60
+ // ── 申报名的线长界 ──────────────────────────────────────────────────────────────────────────────────
61
+ /** 引擎 `TOOL_WIRE_NAME_MAX_CHARS` 的镜像:一台 MCP server(或 A2A 对端)的申报名会成为线上工具名的命名空间段,引擎对超界名是**拒**(不截断)。 */
62
+ export const TOOL_WIRE_NAME_MAX_CHARS = 128;
63
+ /**
64
+ * 这个申报名能用吗。严判:非串、空串、超 {@link TOOL_WIRE_NAME_MAX_CHARS} 一律不可用。不截断、不规范化 —— 截短一个名字就是**换一个身份**,
65
+ * 而被换掉身份的那台 server 上挂的 allowTools / toolFaces 收窄声明会一起对不上号。
66
+ */
67
+ export function isUsableWireName(v) {
68
+ return typeof v === "string" && v.length > 0 && v.length <= TOOL_WIRE_NAME_MAX_CHARS;
69
+ }
70
+ /** 坏名的**可打印**摘要(进拒句 / 告警元数据):种类、`empty`,或截到 32 字加长度事实 —— 绝不把任意长度的申报串原样回显。 */
71
+ export function describeBadWireName(v) {
72
+ if (typeof v !== "string")
73
+ return v === null ? "null" : Array.isArray(v) ? "array" : typeof v;
74
+ if (v.length === 0)
75
+ return "empty";
76
+ const head = v.length > 32 ? `${JSON.stringify(v.slice(0, 32))}…` : JSON.stringify(v);
77
+ return `${head} (${v.length} chars > ${TOOL_WIRE_NAME_MAX_CHARS})`;
78
+ }
79
+ // ── 内容出处词 ──────────────────────────────────────────────────────────────────────────────────────
80
+ const CONTENT_ORIGIN_WORDS = new Set(MCP_CONTENT_ORIGINS);
81
+ /**
82
+ * 在内容出处词表({@link MCP_CONTENT_ORIGINS})内吗。严判:非串与大小写不符(`"LOCAL"`)一律不在 —— 引擎的词是字面量联合、不做规范化;
83
+ * 替它「宽容一下」等于自造第二份值域语义,而这条轴上宽容的方向恰好是**放宽信任**(把一个写坏的声明读成 `"local"`)。
84
+ */
85
+ export function isContentOriginWord(v) {
86
+ return typeof v === "string" && CONTENT_ORIGIN_WORDS.has(v);
87
+ }
88
+ /** 坏内容出处值的**可打印**摘要:串 JSON 化、64 字截断加 `…`;非串只说种类。 */
89
+ export function describeBadContentOrigin(v) {
90
+ const raw = typeof v === "string" ? JSON.stringify(v) : v === null ? "null" : Array.isArray(v) ? "array" : typeof v;
91
+ return raw.length > 64 ? `${raw.slice(0, 64)}…` : raw;
92
+ }
93
+ // ── 两个身份头 ──────────────────────────────────────────────────────────────────────────────────────
94
+ /**
95
+ * 一份 http 传输上两个身份头(`principalHeader` / `assertionHeader`)的读法:字符串才收;**`principalHeader` 胜** —— 它在场(不论值形)时
96
+ * `assertionHeader` 不收。所以读后的形**结构上不可能同带两键**;同设(一份声明给出两个答案)由调用方在读之前对**原始**传输判(判的是写了什么,不是读后留下什么)。
97
+ */
98
+ export function readMcpIdentityHeaders(transport) {
99
+ return {
100
+ ...(typeof transport.principalHeader === "string" ? { principalHeader: transport.principalHeader } : {}),
101
+ ...(transport.principalHeader === undefined && typeof transport.assertionHeader === "string" ? { assertionHeader: transport.assertionHeader } : {}),
102
+ };
103
+ }
104
+ const transportBad = (path, reason) => ({ ok: false, issue: { path, reason } });
105
+ /**
106
+ * 请求面 `transport` 的**唯一读器**:读器投影 transport 用它,发送前 / 受理时的判形读它报的 `lost` —— 同一只函数两个消费点,「读器丢了什么」与「门拒什么」不可能分岔。
107
+ * · 硬坏形(`transport` 不是对象 / `kind` 出闭集 / stdio 缺 `command` / http 的 `url` 不是 http(s))⇒ `ok:false`,`path` 是读器点名的键路径;
108
+ * · 软坏形(`args` 不是串数组 / `env` `headers` 含非串值 / `principalHeader` 非串)⇒ 读器**丢这个键**(重放存量提交体时的升级前读法),并把它记进 `lost`
109
+ * (受理新提交的门据它响亮拒)。`assertionHeader` 不在 `lost` 里:两个身份头的键组合与 `assertionHeader` 的值形由调用方在读之前对原始传输判。
110
+ * url 的拒句不回显值:它常规地带凭据(userinfo)。
111
+ */
112
+ export function readRequestMcpTransport(t) {
113
+ if (!t || typeof t !== "object")
114
+ return transportBad("transport", `must be an object with a "kind" of "stdio" or "http" — got ${describeValueShape(t)}`);
115
+ const tr = t;
116
+ const lost = [];
117
+ if (tr.kind === "stdio") {
118
+ if (typeof tr.command !== "string" || tr.command.length === 0)
119
+ return transportBad("transport.command", `must be a non-empty string (the command to run) — got ${describeValueShape(tr.command)}`);
120
+ if (tr.args !== undefined && !isStringArray(tr.args))
121
+ lost.push({ path: "transport.args", reason: `must be an array of strings — got ${describeNotStringArray(tr.args)}` });
122
+ if (tr.env !== undefined && !isStringRecord(tr.env))
123
+ lost.push({ path: "transport.env", reason: `must be an object of string values — got ${describeNotStringRecord(tr.env)}` });
124
+ return {
125
+ ok: true,
126
+ transport: {
127
+ kind: "stdio",
128
+ command: tr.command,
129
+ ...(isStringArray(tr.args) ? { args: tr.args } : {}),
130
+ ...(isStringRecord(tr.env) ? { env: tr.env } : {}),
131
+ },
132
+ lost,
133
+ };
134
+ }
135
+ if (tr.kind === "http") {
136
+ // http(s) only — never file://, never a bare host (a syntactic guard, not the URL shape judge).
137
+ if (typeof tr.url !== "string" || !startsWithHttpScheme(tr.url))
138
+ return transportBad("transport.url", "must be an absolute http(s) URL (put credentials in transport.headers, never in the URL)");
139
+ const ids = readMcpIdentityHeaders(tr);
140
+ if (tr.headers !== undefined && !isStringRecord(tr.headers))
141
+ lost.push({ path: "transport.headers", reason: `must be an object of string values — got ${describeNotStringRecord(tr.headers)}` });
142
+ if (tr.principalHeader !== undefined && ids.principalHeader === undefined) {
143
+ lost.push({ path: "transport.principalHeader", reason: `must be a string (the HTTP header NAME the end-user principal is injected into) — got ${describeValueShape(tr.principalHeader)}` });
144
+ }
145
+ return {
146
+ ok: true,
147
+ transport: {
148
+ kind: "http",
149
+ url: tr.url,
150
+ ...(isStringRecord(tr.headers) ? { headers: tr.headers } : {}),
151
+ ...ids,
152
+ },
153
+ lost,
154
+ };
155
+ }
156
+ return transportBad("transport.kind", `must be "stdio" or "http" — got ${describeValueShape(tr.kind)}`);
157
+ }
@@ -6,7 +6,7 @@ import type { REMOTE_EXEC_DOCUMENT } from "./remote-exec.js";
6
6
  import type { DomainName, RetiredDomainName } from "./types.js";
7
7
  /** 本版登记的一次性**数据**迁移 id(闭集;每个 id 在 `ONE_TIME_MIGRATIONS` 登记表里恰一行)。收据只许这些 id —— 同表登记的 wire 删码位
8
8
  * (`WIRE_RETIREMENT_IDS`,`./one-time-migrations`)不改任何存储形,铸不出收据,不在本集。 */
9
- export declare const ONE_TIME_MIGRATION_IDS: readonly ["SS-M1", "SS-M2", "SS-M3", "SS-M4", "SS-M5", "SS-M6", "SS-M7", "SS-M8", "SS-M9"];
9
+ export declare const ONE_TIME_MIGRATION_IDS: readonly ["SS-M1", "SS-M2", "SS-M3", "SS-M4", "SS-M5", "SS-M6", "SS-M7", "SS-M8", "SS-M9", "SS-M10"];
10
10
  export type OneTimeMigrationId = (typeof ONE_TIME_MIGRATION_IDS)[number];
11
11
  /**
12
12
  * 一条收据说迁移**对一个坐标做了什么**(闭集,每词一义):
@@ -1,6 +1,6 @@
1
1
  /** 本版登记的一次性**数据**迁移 id(闭集;每个 id 在 `ONE_TIME_MIGRATIONS` 登记表里恰一行)。收据只许这些 id —— 同表登记的 wire 删码位
2
2
  * (`WIRE_RETIREMENT_IDS`,`./one-time-migrations`)不改任何存储形,铸不出收据,不在本集。 */
3
- export const ONE_TIME_MIGRATION_IDS = ["SS-M1", "SS-M2", "SS-M3", "SS-M4", "SS-M5", "SS-M6", "SS-M7", "SS-M8", "SS-M9"];
3
+ export const ONE_TIME_MIGRATION_IDS = ["SS-M1", "SS-M2", "SS-M3", "SS-M4", "SS-M5", "SS-M6", "SS-M7", "SS-M8", "SS-M9", "SS-M10"];
4
4
  /**
5
5
  * 一条收据说迁移**对一个坐标做了什么**(闭集,每词一义):
6
6
  * - `moved` — 值搬到新位(`to`),义不变;
package/dist/node.d.ts CHANGED
@@ -9,6 +9,5 @@ export * from "./file-edit.js";
9
9
  export * from "./bundle.js";
10
10
  export * from "./local-load.js";
11
11
  export * from "./scheduler-store-node.js";
12
- export * from "./prompts-digest.js";
13
12
  export { migrateConfigDirectory, type ConfigDirectoryDocument, type ConfigDirectoryIo, type ConfigDirectoryMigration, type ConfigDirectoryRefusal, type MigrateConfigDirectoryOptions } from "./config-dir-migration.js";
14
13
  export { nodeConfigDirectoryIo } from "./config-dir-io.js";
package/dist/node.js CHANGED
@@ -9,7 +9,6 @@ export * from "./file-edit.js";
9
9
  export * from "./bundle.js";
10
10
  export * from "./local-load.js";
11
11
  export * from "./scheduler-store-node.js"; // R7 self-wake scheduler store — fs binding of the ./scheduler-store contract
12
- export * from "./prompts-digest.js"; // prompts 下发包的内容摘要(4.0.0 从 scheduler-store-node 搬来 —— 与调度存储无关;出口路径不变)
13
12
  // 目录形的一次性迁移(读目录 → 迁 → 整域终判 → 原子写 → 删 retired 域文件)—— 语义只此一处,命令行是它的渲染器、引擎起服调同一只。显式列名:该模块的包内私有件(出门形/拼路径)不外泄。
14
13
  export { migrateConfigDirectory } from "./config-dir-migration.js";
15
14
  export { nodeConfigDirectoryIo } from "./config-dir-io.js"; // 上面那只函数的真 I/O 绑定:node:fs + 本包唯一的原子写器(config-document 模式规则)
@@ -39,7 +39,7 @@ export type StoredDocuments = Readonly<Partial<Record<DomainName | RetiredDomain
39
39
  export interface StoredDocumentsMigration {
40
40
  /** 迁移后的文档集:没改的文档是**同一引用**;被移除的(retired 域)不在;新建的(例:迁出的 `governance` / `limits`)在。 */
41
41
  readonly docs: Readonly<Record<string, unknown>>;
42
- /** 逐条收据(步序 SS-M1 → SS-M2 → SS-M5 → SS-M6 → SS-M8 → SS-M9 → SS-M7 → SS-M4:纯改写步在前,用新 schema 终判的 SS-M4 最后;步内按坐标)。
42
+ /** 逐条收据(步序 SS-M1 → SS-M2 → SS-M5 → SS-M6 → SS-M8 → SS-M9 → SS-M10 → SS-M7 → SS-M4:纯改写步在前,用新 schema 终判的 SS-M4 最后;步内按坐标)。
43
43
  * 只有 `refused` 的收据 = 什么都没改。 */
44
44
  readonly receipts: readonly MigrationReceipt[];
45
45
  }
@@ -161,6 +161,19 @@ export const ONE_TIME_MIGRATIONS = [
161
161
  removal: "the next major after the window: delete the SS-M9 step; the models.default tombstone field stays — its SS-M9 clause goes",
162
162
  afterRemoval: "the models.default tombstone field (permanent): a residual key is refused on read and write (the models domain degrades to its default)",
163
163
  },
164
+ {
165
+ id: "SS-M10",
166
+ oldForm: "refused",
167
+ introducedIn: "14.0.0",
168
+ releasedOn: "2026-10-17",
169
+ removableAfter: "2026-11-16",
170
+ rewrites: "prompts: an empty `bindings` array (the default every document carried before 14.0.0 — it served nothing) is removed; a non-empty " +
171
+ "`bindings` and a `catalog` are refused and kept verbatim — declarations compile to center overlays only through the center's card " +
172
+ "compiler (republish), and a catalog's digest is the engine's (rebuild it with the engine's buildPromptArtifact)",
173
+ removal: "the next major after the window: delete the SS-M10 step; the prompts `bindings` / `catalog` tombstone fields stay — their SS-M10 clause goes",
174
+ afterRemoval: "the prompts `bindings` / `catalog` tombstone fields (permanent): a residual key is refused on read and write (the prompts domain degrades " +
175
+ "to its default — no center layer is served)",
176
+ },
164
177
  {
165
178
  // 一条 wire 位的删码登记,不是数据迁移(id 在 WIRE_RETIREMENT_IDS,不在收据闭集)。
166
179
  id: "SS-W1",
@@ -205,6 +218,7 @@ export function migrateStoredDocuments(docs, opts) {
205
218
  stepAtMention(out, receipts);
206
219
  stepRetiredRoleWords(out, receipts);
207
220
  stepModelsDefault(out, receipts);
221
+ stepPromptsAxes(out, receipts);
208
222
  stepUrlFragments(out, receipts);
209
223
  stepWorkers6x(out, receipts);
210
224
  return { docs: out, receipts };
@@ -749,6 +763,31 @@ function stepModelsDefault(out, receipts) {
749
763
  receipts.push(moved("SS-M9", "models", ["default"], { document: "models", path: ["roles", "default"] }));
750
764
  out.models = { ...without(doc, "default"), roles: { ...roles, default: { model: name } } };
751
765
  }
766
+ // ── SS-M10:prompts 两轴退役 ───────────────────────────────────────────────────────────────────────────
767
+ /** 14.0.0 的 prompts 域只装中心 artifact;13.x 的两轴键是墓碑。迁移只做只认形的那一件:空的 `bindings: []`(13.x 每份文档恒物化的默认值,
768
+ * 从不下发任何内容)删掉。非空 `bindings` / 不是数组的 `bindings` / `catalog` 一律 refused、原样留着 —— 声明要经中心的卡编译器才编得成
769
+ * overlay,catalog 的 digest 是引擎铸的,替运维机械转就是第二个编译器 / 第二个铸点。`prompts` 不是对象 ⇒ 不动(判形是读者的事)。 */
770
+ function stepPromptsAxes(out, receipts) {
771
+ const doc = out.prompts;
772
+ if (!isPlainObject(doc))
773
+ return;
774
+ let next = doc;
775
+ if (has(doc, "bindings") && doc.bindings !== undefined) {
776
+ if (Array.isArray(doc.bindings) && doc.bindings.length === 0) {
777
+ next = without(next, "bindings");
778
+ receipts.push(receipt("SS-M10", "prompts", ["bindings"], "dropped-inert"));
779
+ }
780
+ else {
781
+ receipts.push(refused("SS-M10", "prompts", ["bindings"], "declaration bindings compile to center overlays only through the center's card compiler — republish the prompts from the web-admin editor " +
782
+ "(a local config.d copy was never read by the engine: delete the key)"));
783
+ }
784
+ }
785
+ if (has(doc, "catalog") && doc.catalog !== undefined) {
786
+ receipts.push(refused("SS-M10", "prompts", ["catalog"], "a v1 catalog cannot be converted (its digest is the engine's) — republish from the web-admin, or rebuild with the engine's buildPromptArtifact"));
787
+ }
788
+ if (next !== doc)
789
+ out.prompts = next;
790
+ }
752
791
  // ── SS-M7:URL fragment ────────────────────────────────────────────────────────────────────────────────
753
792
  /** SS-M7 走查的文档 → 它的 schema(座位从 schema 派生的那一只来源):配置域按 `DOMAIN_SCHEMAS`,`remote-exec` 按 `RemoteExecSpec`;别的文档没有 URL 座位。 */
754
793
  function urlSeatSchemaOf(document) {
@@ -69,8 +69,8 @@ export type MergeShape = z.infer<typeof MergeShape>;
69
69
  export declare const MinTrust: z.ZodEnum<{
70
70
  local: "local";
71
71
  session: "session";
72
- global: "global";
73
72
  project: "project";
73
+ global: "global";
74
74
  managed: "managed";
75
75
  task: "task";
76
76
  }>;
@@ -118,8 +118,8 @@ export declare const MemberAdmission: z.ZodObject<{
118
118
  minTrust: z.ZodOptional<z.ZodEnum<{
119
119
  local: "local";
120
120
  session: "session";
121
- global: "global";
122
121
  project: "project";
122
+ global: "global";
123
123
  managed: "managed";
124
124
  task: "task";
125
125
  }>>;
@@ -128,8 +128,8 @@ export declare const MemberAdmission: z.ZodObject<{
128
128
  minTrust: z.ZodEnum<{
129
129
  local: "local";
130
130
  session: "session";
131
- global: "global";
132
131
  project: "project";
132
+ global: "global";
133
133
  managed: "managed";
134
134
  task: "task";
135
135
  }>;
@@ -194,8 +194,8 @@ export declare const MergeCategorySpec: z.ZodObject<{
194
194
  minTrust: z.ZodEnum<{
195
195
  local: "local";
196
196
  session: "session";
197
- global: "global";
198
197
  project: "project";
198
+ global: "global";
199
199
  managed: "managed";
200
200
  task: "task";
201
201
  }>;
@@ -240,8 +240,8 @@ export declare const MergeCategorySpec: z.ZodObject<{
240
240
  minTrust: z.ZodOptional<z.ZodEnum<{
241
241
  local: "local";
242
242
  session: "session";
243
- global: "global";
244
243
  project: "project";
244
+ global: "global";
245
245
  managed: "managed";
246
246
  task: "task";
247
247
  }>>;
@@ -250,8 +250,8 @@ export declare const MergeCategorySpec: z.ZodObject<{
250
250
  minTrust: z.ZodEnum<{
251
251
  local: "local";
252
252
  session: "session";
253
- global: "global";
254
253
  project: "project";
254
+ global: "global";
255
255
  managed: "managed";
256
256
  task: "task";
257
257
  }>;