@sema-agent/settings-schema 17.2.0 → 19.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 (141) hide show
  1. package/CHANGELOG.md +216 -0
  2. package/README.md +53 -6
  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/center-refs.d.ts +4 -3
  106. package/dist/center-refs.js +8 -6
  107. package/dist/cli.d.ts +1 -1
  108. package/dist/config-fns.d.ts +6 -1
  109. package/dist/config-fns.js +16 -2
  110. package/dist/execution-lanes.d.ts +158 -11
  111. package/dist/execution-lanes.js +76 -11
  112. package/dist/file-edit.d.ts +1 -1
  113. package/dist/file-edit.js +2 -1
  114. package/dist/hooks-config.d.ts +23 -5
  115. package/dist/hooks-config.js +33 -5
  116. package/dist/hooks.d.ts +6 -4
  117. package/dist/hooks.js +3 -1
  118. package/dist/mcp-request-entry.d.ts +11 -21
  119. package/dist/mcp-request-entry.js +21 -38
  120. package/dist/one-time-migrations.js +1 -1
  121. package/dist/portable-domains.d.ts +1 -1
  122. package/dist/portable-domains.js +1 -1
  123. package/dist/remote-exec.d.ts +5 -0
  124. package/dist/remote-exec.js +5 -0
  125. package/dist/reserved-member-name.d.ts +13 -0
  126. package/dist/reserved-member-name.js +14 -0
  127. package/dist/retired-envs.js +2 -0
  128. package/dist/secret-refs.d.ts +1 -1
  129. package/dist/secret-refs.js +5 -1
  130. package/dist/types.d.ts +70 -41
  131. package/dist/types.js +179 -32
  132. package/dist/url-credential.d.ts +12 -2
  133. package/dist/url-credential.js +164 -9
  134. package/dist/wire-name.d.ts +19 -0
  135. package/dist/wire-name.js +21 -0
  136. package/docs/API-SURFACE.md +27 -6
  137. package/docs/CC-DIVERGENCE.md +2 -2
  138. package/docs/CONSUMER-INTEGRATION.md +72 -7
  139. package/llms-full.txt +38 -18
  140. package/llms.txt +10 -10
  141. package/package.json +1 -1
@@ -570,6 +570,7 @@ export declare const DOMAIN_READ_FALLBACK: {
570
570
  readonly projects: "throw";
571
571
  readonly prompts: "default";
572
572
  readonly limits: "throw";
573
+ readonly a2a: "default";
573
574
  };
574
575
  /** Per-domain ENTRY-SALVAGE policy (12.3.0, S-1494) — may the view-face domain reader ({@link parseDomainSalvaged}) drop single bad
575
576
  * entries (an array element, a Record value, and — recursively — a bad entry inside a surviving entry) and read the rest?
@@ -605,7 +606,10 @@ export declare const DOMAIN_READ_FALLBACK: {
605
606
  * - governance: NONE — a dropped commandPolicy / approvalRequire row is a guardrail silently gone (放权,不许).
606
607
  * - entitlement: NONE — a dropped tier / subject / binding / budget group changes who may do what (放权,不许).
607
608
  * - execution: NONE — a dropped lane / public key changes the execution mandate (放权,不许).
608
- * - limits: NONE — a dropped allowlist row changes a published ceiling (the domain is the one publishing seat of those ceilings). */
609
+ * - limits: NONE — a dropped allowlist row changes a published ceiling (the domain is the one publishing seat of those ceilings).
610
+ * - a2a: SALVAGE (18.0.0) — the peer roster is a capability catalog like mcp: a dropped peer is one remote agent fewer. A peer's
611
+ * credential references (`headerRefs`) are in {@link ATOMIC_SALVAGE}: a bad reference drops the whole peer, never the reference
612
+ * alone (a peer kept with half of its credentials is the wrong failure direction). */
609
613
  export declare const DOMAIN_ENTRY_SALVAGE: {
610
614
  readonly models: "salvage";
611
615
  readonly rosters: "none";
@@ -623,6 +627,7 @@ export declare const DOMAIN_ENTRY_SALVAGE: {
623
627
  readonly projects: "salvage";
624
628
  readonly prompts: "none";
625
629
  readonly limits: "none";
630
+ readonly a2a: "salvage";
626
631
  };
627
632
  /** 一个读者向店要文档的口:配置域,以及墓碑表里的retired 域({@link RETIRED_DOMAINS})—— 后者只为让墓碑看见没迁的旧文档。 */
628
633
  export type StoredDocumentReader = (d: DomainName | RetiredDomainName) => unknown;
@@ -23,7 +23,7 @@ import { isJsonArray, isPlainJsonObject, jsonShapeOf } from "./plain-json-object
23
23
  import { carriesUserinfo, secretScanForms } from "./secret-scan.js"; // package-private normalization (both scanners read it) + the userinfo judgement (not in the barrel)
24
24
  import { sha256Hex } from "./sha256.js";
25
25
  import { unwrapSchema } from "./zod-unwrap.js"; // package-private: the ONE reading of zod's wrapper layers (salvage + the one-time migrations share it)
26
- import { DOMAIN_SCHEMAS, InfraCostRates, LimitsConfig, PRIVATE_KEY_PEM_HEADER, RETIRED_DOMAINS, SkillsManifestSchema, containsSecretToken, isDomainName, isRetiredDomainName, renderQuotedSentence, retiredDomainSentence } from "./types.js";
26
+ import { ATOMIC_SALVAGE, DOMAIN_SCHEMAS, InfraCostRates, LimitsConfig, PRIVATE_KEY_PEM_HEADER, RETIRED_DOMAINS, SkillsManifestSchema, containsSecretToken, isDomainName, isRetiredDomainName, renderQuotedSentence, retiredDomainSentence } from "./types.js";
27
27
  /** Redact secret-shaped tokens from a validation MESSAGE — a zod error echoes the offending value (e.g.
28
28
  * `received 'sk-live-…'`), so a secret pasted into the WRONG field would leak via the error string. Redacts
29
29
  * space-spanning auth credentials (`Bearer <token>`) FIRST (the token pass can't see across the space), then
@@ -197,6 +197,10 @@ export const DOMAIN_WRITE_ROLE = {
197
197
  // limits (0.19.0): editor —— 与它的前身 runtime 域(11.0.0 retired)同档,刻意不抬 publisher:本域是纯数值
198
198
  // 旋钮,无 prompt 注入面、无凭据、无可执行内容;真正的执法点在消费方的限额闸,发布闸(PUBLISH_MODE /
199
199
  // 审批流)才是"要不要多一道人"的属主。default 即 editor,故不列条目;此注释即该裁定的落点。
200
+ // a2a (18.0.0): a peer is a remote agent whose skills are mounted into the agent's tool surface, plus the env-NAMEs that
201
+ // carry its credentials — the same executable-surface + credential-reference profile as mcp, so the same raised gate.
202
+ // Written out (not left to the default) because the default would be the WRONG answer here.
203
+ a2a: "publisher",
200
204
  };
201
205
  /** The minimum role required to write `domain` (the raised gate for skills/mcp, else editor). */
202
206
  export function domainWriteRole(domain) {
@@ -495,6 +499,9 @@ function salvageLayerUnsafe(schema, raw) {
495
499
  const patch = {};
496
500
  for (const [field, fieldSchema] of Object.entries(shape)) {
497
501
  const collection = unwrapSchema(fieldSchema);
502
+ // 凭据引用集合(按 schema 身份,见 ATOMIC_SALVAGE)不下钻:它是一个值,坏一项 ⇒ 它所属的条目在上一层整条判不过、整条丢。
503
+ if (ATOMIC_SALVAGE.has(collection))
504
+ continue;
498
505
  const isArray = collection instanceof z.ZodArray;
499
506
  if (!isArray && !(collection instanceof z.ZodRecord))
500
507
  continue; // 不是条目集合:连读都不读(少触发一次访问器)
@@ -1006,6 +1013,8 @@ export const DOMAIN_READ_FALLBACK = {
1006
1013
  // 降级成空域 = 中央发布的成本/限流天花板整片蒸发,消费方回自己的 env 底(可能宽得多)= 预算轴放权。
1007
1014
  // 消费方 fail-static 在上一份好配置严格更好。
1008
1015
  limits: "throw",
1016
+ // a2a(18.0.0):能力目录(同 mcp)—— 整域读不进 ⇒ 空名册 = 不拨任何对端(能力消失,fail-closed;没有闸因此丢掉)。
1017
+ a2a: "default",
1009
1018
  };
1010
1019
  /** Per-domain ENTRY-SALVAGE policy (12.3.0, S-1494) — may the view-face domain reader ({@link parseDomainSalvaged}) drop single bad
1011
1020
  * entries (an array element, a Record value, and — recursively — a bad entry inside a surviving entry) and read the rest?
@@ -1041,7 +1050,10 @@ export const DOMAIN_READ_FALLBACK = {
1041
1050
  * - governance: NONE — a dropped commandPolicy / approvalRequire row is a guardrail silently gone (放权,不许).
1042
1051
  * - entitlement: NONE — a dropped tier / subject / binding / budget group changes who may do what (放权,不许).
1043
1052
  * - execution: NONE — a dropped lane / public key changes the execution mandate (放权,不许).
1044
- * - limits: NONE — a dropped allowlist row changes a published ceiling (the domain is the one publishing seat of those ceilings). */
1053
+ * - limits: NONE — a dropped allowlist row changes a published ceiling (the domain is the one publishing seat of those ceilings).
1054
+ * - a2a: SALVAGE (18.0.0) — the peer roster is a capability catalog like mcp: a dropped peer is one remote agent fewer. A peer's
1055
+ * credential references (`headerRefs`) are in {@link ATOMIC_SALVAGE}: a bad reference drops the whole peer, never the reference
1056
+ * alone (a peer kept with half of its credentials is the wrong failure direction). */
1045
1057
  export const DOMAIN_ENTRY_SALVAGE = {
1046
1058
  models: "salvage",
1047
1059
  rosters: "none",
@@ -1059,6 +1071,7 @@ export const DOMAIN_ENTRY_SALVAGE = {
1059
1071
  projects: "salvage",
1060
1072
  prompts: "none",
1061
1073
  limits: "none",
1074
+ a2a: "salvage",
1062
1075
  };
1063
1076
  /**
1064
1077
  * Merge every domain into the effective config a consumer pulls. Shared by all store impls.
@@ -1111,6 +1124,7 @@ export function buildEffective(version, getRaw, updatedAt, onWarning) {
1111
1124
  // `{}` 不下发中心层,default 不扩权)。
1112
1125
  prompts: parseOr("prompts", getRaw("prompts")),
1113
1126
  limits: parseOr("limits", getRaw("limits")),
1127
+ a2a: parseOr("a2a", getRaw("a2a")),
1114
1128
  };
1115
1129
  }
1116
1130
  /** READ-side tolerant judgment for a GET /effective **wire payload**(the {@link EffectiveWire} contract)at
@@ -1,6 +1,6 @@
1
1
  /**
2
- * 执行车道 + 存储后端的**词表正本**(7.0.0,M-R2 WorkerSpec 结构块)。纯数据常量,外加两只纯判官函数(退役车道词的拒句 `retiredLaneWordRefusal`、
3
- * 车道坐标的空白判官 `isBlankCoordinate`),零依赖(叶模块:本文件不 import 任何东西,`types.ts` 反过来引它)。
2
+ * 执行车道 + 存储后端的**词表正本**(7.0.0,M-R2 WorkerSpec 结构块)。纯数据常量,外加三只纯判官函数(退役车道词的拒句 `retiredLaneWordRefusal`、
3
+ * 车道坐标的空白判官 `isBlankCoordinate`、车道表合法性的唯一判官 `laneSetFlaws`〔18.0.0〕),零依赖(叶模块:本文件不 import 任何东西,`types.ts` 反过来引它)。
4
4
  *
5
5
  * ## 为什么在这里
6
6
  * 车道词、每条车道读哪些 env、哪条车道分租户、存储后端词 —— 这几张表以前在四处各抄一份(引擎的能力表、
@@ -12,8 +12,8 @@
12
12
  * 写闸与起服读同一张表 ⇒ 不会分叉。
13
13
  *
14
14
  * ## 行内容的出处(逐行抄自引擎真码,坐标 = server 7.112.0 树)
15
- * · 车道词与次序:`src/execution-lane-caps.ts` `EXECUTION_LANE_WORDS`(:335)去掉 `in-process` =
16
- * `REMOTE_EXEC_PROVIDER_WORDS`(:366)。次序照抄,于是引擎改派生之后,拒启句里的 `valid:` 列表与出路列表逐字不变。
15
+ * · 车道词与次序:前七词抄自 `src/execution-lane-caps.ts` `REMOTE_EXEC_PROVIDER_WORDS`(:366)。次序照抄,于是引擎改派生之后,拒启句里的 `valid:` 列表与出路列表逐字不变。
16
+ * `none`(18.0.0)尾插:它的出处就是本表(见下「`none`」一节),引擎提货本版后从本表派生词表与拒句。
17
17
  * · `tenantSeparation`:同文件 `executionLaneCaps` 每臂的 `tenantSeparation` 位(:209 host / :230 e2b / :249 k8s /
18
18
  * :269 ssh / :288 adb / :308 local-docker / :327 device)。`tenantSeparationBasis`(11.0.0):分租户的那几臂凭什么分 ——
19
19
  * e2b / k8s / local-docker 每任务一只隔离目标(`isolation`),device 是发起者自己的设备(`principal-binding`)。
@@ -30,15 +30,22 @@
30
30
  * 收进车道栏会把它们保留成「k8s 专属」,别的车道就再也设不了快照对象存储。也**不收**臂外的适配器调参键
31
31
  * (`HOST_BG_*` / `E2B_BG_*` / `K8S_BG_*` / `HOST_SHELL_PATH` …):它们不是车道坐标,照旧走 `env` 逃生口。
32
32
  *
33
+ * ## `none`:不给智能体的工具提供执行环境(18.0.0)
34
+ * 每个部署都点名自己的执行车道;没有执行环境的部署点名 `none` —— 这是一个声明,不是「没写车道」的副作用。用户面可观察的事实:
35
+ * · 每个 run:工具的手不挂(两个执行环境座都缺席;引擎的装配清单报 hands 未挂、原因是没有执行环境);
36
+ * · 部署级:执行车道读数是 `none`、工具不在本机执行、没有隔离读数;引擎能力表的 `none` 行除分租户外整排不支持;
37
+ * · 分租户(依据 `no-execution-target`):没有执行目标,也就没有跨租户共享的目标;不挂在副本上(`singleReplica: false`);四栏 env 全空(没有要配的坐标与密钥);
38
+ * · 只能独成一集:含依据为 `no-execution-target` 的车道的车道表恰好一项 —— 判官 {@link laneSetFlaws} 按依据列判(拒因 `not_alone`),不点名车道词。
39
+ * 不在它的射程内(各有自己的属主轴,不随车道):请求体里的 hook 命令条目与 stdio MCP 服务器条目在 worker 宿主上起进程,它们的闸是多租户姿态 / 锁键 / 合规档。
40
+ * 它取代引擎内部「没有声明车道」那个状态的名字 `in-process`(那从来不是可声明的词,本表也从未收过);引擎侧的改名随它提货本版的那一版。
41
+ *
33
42
  * ## 不在表里的词
34
- * · `in-process`:引擎内部「没有 remoteExec」这个状态的名字,不是可声明的车道 —— 显式「无手」的 worker 在模型要用工具时
35
- * 仍回 completed + 零工具,就是那个病换了个合法名字。要无手 worker 先另立设计、定用户面行为再给词。
36
43
  * · `remote-docker`:引擎有一只没接线的适配器工厂,但没有任何读点能把这个词变成一条在跑的车道
37
44
  * (`REMOTE_EXEC=remote-docker` 起服即拒;远端 docker 守护进程今天的真形 = `local-docker` + `DOCKER_HOST`)。
38
45
  * 它在退役词墓碑 {@link RETIRED_LANE_WORDS} 里:读到即拒,拒句带出路。
39
46
  */
40
- /** 可声明的执行车道闭集(= 引擎 `REMOTE_EXEC` 收的词,逐字同序)。 */
41
- export declare const EXECUTION_LANE_WORDS: readonly ["host", "e2b", "k8s", "ssh", "adb", "local-docker", "device"];
47
+ /** 可声明的执行车道闭集(= 引擎 `REMOTE_EXEC` 收的词,逐字同序;18.0.0 尾插 `none`)。 */
48
+ export declare const EXECUTION_LANE_WORDS: readonly ["host", "e2b", "k8s", "ssh", "adb", "local-docker", "device", "none"];
42
49
  export type ExecutionLane = (typeof EXECUTION_LANE_WORDS)[number];
43
50
  /**
44
51
  * 已退役的车道**值** —— 墓碑(与退役 env 名的墓碑表 `RETIRED_ENVS` 同一条线:永久、只增不减、一跳 —— 去向恒是现行车道词)。
@@ -53,9 +60,10 @@ export declare function retiredLaneWordRefusal(word: unknown): string | undefine
53
60
  * `supported` = 每个租户的命令落在各自的目标上;`single-user-only` = 所有任务落同一个共享目标 ⇒ 只许单用户 worker。 */
54
61
  export declare const TENANT_SEPARATION_WORDS: readonly ["supported", "single-user-only"];
55
62
  export type TenantSeparation = (typeof TENANT_SEPARATION_WORDS)[number];
56
- /** 分租户车道**凭什么**分租户(两档):`isolation` = 每个任务一只隔离的目标(microVM / pod / 容器);`principal-binding` = 目标绑在
57
- * 发起者身上(设备 ↔ principal 绑定),目标本身不隔离。只有 `tenantSeparation: "supported"` 的车道有依据。 */
58
- export declare const TENANT_SEPARATION_BASES: readonly ["isolation", "principal-binding"];
63
+ /** 分租户车道**凭什么**分租户(三档):`isolation` = 每个任务一只隔离的目标(microVM / pod / 容器);`principal-binding` = 目标绑在
64
+ * 发起者身上(设备 ↔ principal 绑定),目标本身不隔离;`no-execution-target`(18.0.0)= 根本没有执行目标(工具没有执行环境),也就没有跨租户共享的目标 ——
65
+ * 这一档还带一条集合规则:含这档车道的车道表只能有它这一项(判官 {@link laneSetFlaws} 读这一列)。只有 `tenantSeparation: "supported"` 的车道有依据。 */
66
+ export declare const TENANT_SEPARATION_BASES: readonly ["isolation", "principal-binding", "no-execution-target"];
59
67
  export type TenantSeparationBasis = (typeof TENANT_SEPARATION_BASES)[number];
60
68
  /** 车道的租户分离位与它的依据 —— 判别联合:分租户 ⇔ 依据在场(类型保证,不靠一句注释)。消费方的规则读依据列,不点名车道。 */
61
69
  export type LaneTenancy = {
@@ -78,6 +86,102 @@ export type ExecutionLaneRow = {
78
86
  readonly optionalEnv: readonly string[];
79
87
  readonly optionalSecretEnv: readonly string[];
80
88
  } & LaneTenancy;
89
+ /** 逐车道一行。`satisfies Record<ExecutionLane, …>` = 词表加词而这里不加行 ⇒ 编译红。 */
90
+ declare const LANE_ROWS: {
91
+ /** server `config.ts` :4014 臂(无必需键);`execution-lane-caps.ts` :209 `single-user-only`(所有任务落 worker 宿主)。 */
92
+ readonly host: {
93
+ readonly tenantSeparation: "single-user-only";
94
+ readonly singleReplica: true;
95
+ readonly requiredEnv: readonly [];
96
+ readonly requiredSecretEnv: readonly [];
97
+ readonly optionalEnv: readonly ["HOST_WORKSPACE_BASE", "HOST_COMMAND_TIMEOUT_MS"];
98
+ readonly optionalSecretEnv: readonly [];
99
+ };
100
+ /** server `config.ts` :3925 臂;`E2B_API_KEY` 目录 :351 类型 `secret`、`E2B_SANDBOX_ENV` :359 类型 `secret`;
101
+ * `execution-lane-caps.ts` :230 `supported`(每任务一只 microVM)。 */
102
+ readonly e2b: {
103
+ readonly tenantSeparation: "supported";
104
+ readonly tenantSeparationBasis: "isolation";
105
+ readonly singleReplica: false;
106
+ readonly requiredEnv: readonly [];
107
+ readonly requiredSecretEnv: readonly ["E2B_API_KEY"];
108
+ readonly optionalEnv: readonly ["E2B_TEMPLATE", "E2B_TIMEOUT_MS", "E2B_LIVENESS_MS", "E2B_ALLOW_NET", "E2B_MOUNT_PATH", "E2B_ORPHAN_RECLAIM"];
109
+ readonly optionalSecretEnv: readonly ["E2B_SANDBOX_ENV"];
110
+ };
111
+ /** server `config.ts` :3957 臂;`K8S_TOKEN` 目录 :412 类型 `secret`、`K8S_CA_CERT_B64` :403 类型 `opaque`;
112
+ * `execution-lane-caps.ts` :249 `supported`(每任务一只 pod)。`MINIO_*` 刻意不收(见文件头)。
113
+ * `K8S_NAMESPACE`(沙箱 pod 所在的命名空间)是**必需**坐标:pod 住哪是部署自己的声明,表不替它留缺省;服务端的必需 env 门按本行 `requiredEnv` 拒启,
114
+ * 所以这里没有「可选」一说。 */
115
+ readonly k8s: {
116
+ readonly tenantSeparation: "supported";
117
+ readonly tenantSeparationBasis: "isolation";
118
+ readonly singleReplica: false;
119
+ readonly requiredEnv: readonly ["K8S_SANDBOX_IMAGE", "K8S_NAMESPACE"];
120
+ readonly requiredSecretEnv: readonly [];
121
+ readonly optionalEnv: readonly ["K8S_API_URL", "K8S_INSECURE_TLS", "K8S_RUNTIME_CLASS", "K8S_MOUNT_PATH", "K8S_TIMEOUT_MS", "K8S_MEMORY", "K8S_CPU", "WORKSPACE_SNAPSHOT_BUCKET", "WORKSPACE_SNAPSHOT_PREFIX", "WORKSPACE_SNAPSHOT_TTL_SEC"];
122
+ readonly optionalSecretEnv: readonly ["K8S_TOKEN", "K8S_CA_CERT_B64"];
123
+ };
124
+ /** server `config.ts` :3991 臂 + `parseSshHostKeyEnv` :1521;`SSH_PRIVATE_KEY` 目录 :687 类型 `secret`;
125
+ * `execution-lane-caps.ts` :269 `single-user-only`(一台跨任务共享的常驻真机)。 */
126
+ readonly ssh: {
127
+ readonly tenantSeparation: "single-user-only";
128
+ readonly singleReplica: false;
129
+ readonly requiredEnv: readonly ["SSH_HOST", "SSH_USER"];
130
+ readonly requiredSecretEnv: readonly ["SSH_PRIVATE_KEY"];
131
+ readonly optionalEnv: readonly ["SSH_PORT", "SSH_MOUNT_PATH", "SSH_HOST_FINGERPRINT", "SSH_KNOWN_HOSTS"];
132
+ readonly optionalSecretEnv: readonly [];
133
+ };
134
+ /** server `config.ts` :4006 臂;`execution-lane-caps.ts` :288 `single-user-only`(一台跨任务共享的设备)。 */
135
+ readonly adb: {
136
+ readonly tenantSeparation: "single-user-only";
137
+ readonly singleReplica: false;
138
+ readonly requiredEnv: readonly ["ADB_SERIAL"];
139
+ readonly requiredSecretEnv: readonly [];
140
+ readonly optionalEnv: readonly ["ADB_PATH", "ADB_MOUNT_PATH"];
141
+ readonly optionalSecretEnv: readonly [];
142
+ };
143
+ /** server `config.ts` :4026 臂;`DOCKER_SANDBOX_ENV` 目录 :345 类型 `secret`;`execution-lane-caps.ts` :308 `supported`
144
+ * (每任务一只容器)。`DOCKER_HOST` 在本行:远端 docker 守护进程 = 本车道 + `coords.DOCKER_HOST`。 */
145
+ readonly "local-docker": {
146
+ readonly tenantSeparation: "supported";
147
+ readonly tenantSeparationBasis: "isolation";
148
+ readonly singleReplica: false;
149
+ readonly requiredEnv: readonly ["DOCKER_IMAGE"];
150
+ readonly requiredSecretEnv: readonly [];
151
+ readonly optionalEnv: readonly ["DOCKER_MOUNT_PATH", "DOCKER_PATH", "DOCKER_HOST", "DOCKER_MEMORY", "DOCKER_CPUS", "DOCKER_PIDS_LIMIT", "DOCKER_DROP_CAPS", "DOCKER_NETWORK", "DOCKER_COMMAND_TIMEOUT_MS"];
152
+ readonly optionalSecretEnv: readonly ["DOCKER_SANDBOX_ENV"];
153
+ };
154
+ /** server `config.ts` :4055 臂 + `parseDevicePreAuthPerRemoteEnv` :871 / `parseDeviceEnrollTokenTtlEnv` :857;`execution-lane-caps.ts` :327
155
+ * `supported`(发起者自己的设备)。16.0.0:`DEVICE_RELAY_URL`(设备拨哪里)是**必需**坐标 —— 引擎的 device 臂缺它即拒启
156
+ * (`parseDeviceRelayUrlEnv`;此前它是 device 臂读而本表漏登记的键),值只有两种:ws(s) 绝对 URL 或字面 `none`(值的文法见 `./device-relay`)。
157
+ * 17.1.0:`DEVICE_TRUSTED_PROXY_CIDRS`(可选)= 设备 WS 未鉴权握手 per-remote 帽键的可信代理名单(CIDR 闭集,缺省空 = 不读任何代理头);值文法由引擎起服判。 */
158
+ readonly device: {
159
+ readonly tenantSeparation: "supported";
160
+ readonly tenantSeparationBasis: "principal-binding";
161
+ readonly singleReplica: true;
162
+ readonly requiredEnv: readonly ["DEVICE_RELAY_URL"];
163
+ readonly requiredSecretEnv: readonly [];
164
+ readonly optionalEnv: readonly ["DEVICE_DELIVERY_TIMEOUT_MS", "DEVICE_RECONNECT_GRACE_MS", "DEVICE_EXEC_TIMEOUT_MS", "DEVICE_MAX_INFLIGHT_PER_DEVICE", "DEVICE_MAX_PREAUTH_PER_REMOTE", "DEVICE_ENROLL_TOKEN_TTL_SEC", "DEVICE_TRUSTED_PROXY_CIDRS", "DEVICE_SUPERSEDE"];
165
+ readonly optionalSecretEnv: readonly [];
166
+ };
167
+ /** 18.0.0:没有执行环境(见文件头「`none`」一节)。分租户,依据 `no-execution-target`(没有执行目标,就没有跨租户共享的目标);不挂在副本上;
168
+ * 四栏全空(没有要配的坐标与密钥)。本行先于引擎:引擎提货本版后它的能力表 / 必需键门 / 拒句从本行派生。 */
169
+ readonly none: {
170
+ readonly tenantSeparation: "supported";
171
+ readonly tenantSeparationBasis: "no-execution-target";
172
+ readonly singleReplica: false;
173
+ readonly requiredEnv: readonly [];
174
+ readonly requiredSecretEnv: readonly [];
175
+ readonly optionalEnv: readonly [];
176
+ readonly optionalSecretEnv: readonly [];
177
+ };
178
+ };
179
+ /** 依据为 `no-execution-target` 的车道(型从表行的依据列派生,不点名车道词;今天 = `none`):声明了它的车道表只能有它这一项。 */
180
+ export type StandaloneLane = {
181
+ [L in ExecutionLane]: (typeof LANE_ROWS)[L] extends {
182
+ readonly tenantSeparationBasis: "no-execution-target";
183
+ } ? L : never;
184
+ }[ExecutionLane];
81
185
  /** 车道表(次序 = {@link EXECUTION_LANE_WORDS})。整张表深冻结 —— 它是唯一正本,消费方拿到的不许被改。 */
82
186
  export declare const EXECUTION_LANES: readonly ExecutionLaneRow[];
83
187
  /**
@@ -90,6 +194,48 @@ export declare const EXECUTION_LANES: readonly ExecutionLaneRow[];
90
194
  * 车道表的派生索引与写闸也从这里 import,全包只有这一处函数体。
91
195
  */
92
196
  export declare function isBlankCoordinate(value: string): boolean;
197
+ /**
198
+ * 一张车道表**不合法**的拒因 —— 闭集,判别字段 `kind`(消费方穷举 `switch`:加一种拒因 ⇒ 消费方编译红,不会静默落进「别的」)。`at` = 那一项在表里的下标(从 0 起)。
199
+ * · `empty_set`:表是空的(一项都没有);
200
+ * · `blank_entry`:那一项是空串或只含空白(判官 {@link isBlankCoordinate});
201
+ * · `unknown`:那一项不是车道词({@link EXECUTION_LANE_WORDS};逐字比:不 trim、不折大小写),也不是车道值墓碑里的旧词 —— 包括根本不是字符串的项(数、`null`、对象 …:
202
+ * 读 JSON 的写闸会见到;`word` 是那一项的原值,判官只比较、不读它的任何属性,渲染给人看时由调用方自己描述它的形);
203
+ * · `retired`:那一项是车道值墓碑 {@link RETIRED_LANE_WORDS} 里的旧词(出路取 {@link retiredLaneWordRefusal},一句);
204
+ * · `duplicate`:那一项是车道词,但表里更早已经有它了(报在第二次及以后出现的那一格;不是车道词的重复不报这一条 —— 它们各自已是拒因);
205
+ * · `not_alone`(18.0.0):表里有独占车道({@link StandaloneLane},依据列为 `no-execution-target`),却还有不是它的项 —— 独占车道只能独成一集。
206
+ * 这是整张表的拒因(没有 `at`);独占车道自己的重复只报 `duplicate`。
207
+ */
208
+ export type LaneSetFlaw = {
209
+ readonly kind: "empty_set";
210
+ } | {
211
+ readonly kind: "blank_entry";
212
+ readonly at: number;
213
+ } | {
214
+ readonly kind: "unknown";
215
+ readonly at: number;
216
+ readonly word: unknown;
217
+ } | {
218
+ readonly kind: "retired";
219
+ readonly at: number;
220
+ readonly word: string;
221
+ } | {
222
+ readonly kind: "duplicate";
223
+ readonly at: number;
224
+ readonly lane: ExecutionLane;
225
+ } | {
226
+ readonly kind: "not_alone";
227
+ readonly lane: StandaloneLane;
228
+ };
229
+ /** 车道表的规则句(不含值,与 `DEVICE_RELAY_RULE` 同形):写闸的拒句、引擎的拒启句、表单的提示读这一句,句子只在这里写。 */
230
+ export declare const LANE_SET_RULE: string;
231
+ /**
232
+ * 车道表合不合法的**唯一判官**:合法 ⇒ `[]`;否则按表的次序交回每一条拒因({@link LaneSetFlaw})。纯函数,不抛。
233
+ * 谁问它:写闸(`WorkerSpec` 的 `runtime.execution.lanes`)逐条映射成 issue;引擎把 `REMOTE_EXEC` 按逗号切段后交给它(引擎只留切段);web-admin 表单同问。
234
+ * 入参是任意值的表(读 JSON 的写闸原样交进来;引擎切段后交的是字符串):不是字符串的项不是车道词 ⇒ `unknown`,与写错的词同一条拒因 —— 「每一项都是车道词」只有这一处判。
235
+ * 按下标读(`0 … length-1`):稀疏表的空洞读成 `undefined`,是一项不是字符串的项 —— 与写闸的下标快照同答(`forEach` / `some` 会跳过空洞)。
236
+ * 只判「这张表合不合法」;每条车道缺不缺必需坐标、本引擎版本服务不服务这条车道,是调用方各自的门。
237
+ */
238
+ export declare function laneSetFlaws(lanes: readonly unknown[]): readonly LaneSetFlaw[];
93
239
  /** 存储后端(→ `DB_BACKEND`)。= server `src/config-types.ts` `DB_BACKEND_WORDS`(:67)逐字同序。 */
94
240
  export declare const STORE_BACKENDS: readonly ["mysql", "pg", "local", "memory"];
95
241
  export type StoreBackend = (typeof STORE_BACKENDS)[number];
@@ -101,3 +247,4 @@ export type SessionBackend = (typeof SESSION_BACKENDS)[number];
101
247
  * 收成一个字,与 {@link STORE_BACKENDS} 同字;硬破坏,`tidb` 读写两面拒、指路一次性迁移 SS-M12)。次序 file → pg → mysql。 */
102
248
  export declare const MEMORY_ENGINE_BACKENDS: readonly ["file", "pg", "mysql"];
103
249
  export type MemoryEngineBackend = (typeof MEMORY_ENGINE_BACKENDS)[number];
250
+ export {};
@@ -1,6 +1,6 @@
1
1
  /**
2
- * 执行车道 + 存储后端的**词表正本**(7.0.0,M-R2 WorkerSpec 结构块)。纯数据常量,外加两只纯判官函数(退役车道词的拒句 `retiredLaneWordRefusal`、
3
- * 车道坐标的空白判官 `isBlankCoordinate`),零依赖(叶模块:本文件不 import 任何东西,`types.ts` 反过来引它)。
2
+ * 执行车道 + 存储后端的**词表正本**(7.0.0,M-R2 WorkerSpec 结构块)。纯数据常量,外加三只纯判官函数(退役车道词的拒句 `retiredLaneWordRefusal`、
3
+ * 车道坐标的空白判官 `isBlankCoordinate`、车道表合法性的唯一判官 `laneSetFlaws`〔18.0.0〕),零依赖(叶模块:本文件不 import 任何东西,`types.ts` 反过来引它)。
4
4
  *
5
5
  * ## 为什么在这里
6
6
  * 车道词、每条车道读哪些 env、哪条车道分租户、存储后端词 —— 这几张表以前在四处各抄一份(引擎的能力表、
@@ -12,8 +12,8 @@
12
12
  * 写闸与起服读同一张表 ⇒ 不会分叉。
13
13
  *
14
14
  * ## 行内容的出处(逐行抄自引擎真码,坐标 = server 7.112.0 树)
15
- * · 车道词与次序:`src/execution-lane-caps.ts` `EXECUTION_LANE_WORDS`(:335)去掉 `in-process` =
16
- * `REMOTE_EXEC_PROVIDER_WORDS`(:366)。次序照抄,于是引擎改派生之后,拒启句里的 `valid:` 列表与出路列表逐字不变。
15
+ * · 车道词与次序:前七词抄自 `src/execution-lane-caps.ts` `REMOTE_EXEC_PROVIDER_WORDS`(:366)。次序照抄,于是引擎改派生之后,拒启句里的 `valid:` 列表与出路列表逐字不变。
16
+ * `none`(18.0.0)尾插:它的出处就是本表(见下「`none`」一节),引擎提货本版后从本表派生词表与拒句。
17
17
  * · `tenantSeparation`:同文件 `executionLaneCaps` 每臂的 `tenantSeparation` 位(:209 host / :230 e2b / :249 k8s /
18
18
  * :269 ssh / :288 adb / :308 local-docker / :327 device)。`tenantSeparationBasis`(11.0.0):分租户的那几臂凭什么分 ——
19
19
  * e2b / k8s / local-docker 每任务一只隔离目标(`isolation`),device 是发起者自己的设备(`principal-binding`)。
@@ -30,15 +30,22 @@
30
30
  * 收进车道栏会把它们保留成「k8s 专属」,别的车道就再也设不了快照对象存储。也**不收**臂外的适配器调参键
31
31
  * (`HOST_BG_*` / `E2B_BG_*` / `K8S_BG_*` / `HOST_SHELL_PATH` …):它们不是车道坐标,照旧走 `env` 逃生口。
32
32
  *
33
+ * ## `none`:不给智能体的工具提供执行环境(18.0.0)
34
+ * 每个部署都点名自己的执行车道;没有执行环境的部署点名 `none` —— 这是一个声明,不是「没写车道」的副作用。用户面可观察的事实:
35
+ * · 每个 run:工具的手不挂(两个执行环境座都缺席;引擎的装配清单报 hands 未挂、原因是没有执行环境);
36
+ * · 部署级:执行车道读数是 `none`、工具不在本机执行、没有隔离读数;引擎能力表的 `none` 行除分租户外整排不支持;
37
+ * · 分租户(依据 `no-execution-target`):没有执行目标,也就没有跨租户共享的目标;不挂在副本上(`singleReplica: false`);四栏 env 全空(没有要配的坐标与密钥);
38
+ * · 只能独成一集:含依据为 `no-execution-target` 的车道的车道表恰好一项 —— 判官 {@link laneSetFlaws} 按依据列判(拒因 `not_alone`),不点名车道词。
39
+ * 不在它的射程内(各有自己的属主轴,不随车道):请求体里的 hook 命令条目与 stdio MCP 服务器条目在 worker 宿主上起进程,它们的闸是多租户姿态 / 锁键 / 合规档。
40
+ * 它取代引擎内部「没有声明车道」那个状态的名字 `in-process`(那从来不是可声明的词,本表也从未收过);引擎侧的改名随它提货本版的那一版。
41
+ *
33
42
  * ## 不在表里的词
34
- * · `in-process`:引擎内部「没有 remoteExec」这个状态的名字,不是可声明的车道 —— 显式「无手」的 worker 在模型要用工具时
35
- * 仍回 completed + 零工具,就是那个病换了个合法名字。要无手 worker 先另立设计、定用户面行为再给词。
36
43
  * · `remote-docker`:引擎有一只没接线的适配器工厂,但没有任何读点能把这个词变成一条在跑的车道
37
44
  * (`REMOTE_EXEC=remote-docker` 起服即拒;远端 docker 守护进程今天的真形 = `local-docker` + `DOCKER_HOST`)。
38
45
  * 它在退役词墓碑 {@link RETIRED_LANE_WORDS} 里:读到即拒,拒句带出路。
39
46
  */
40
- /** 可声明的执行车道闭集(= 引擎 `REMOTE_EXEC` 收的词,逐字同序)。 */
41
- export const EXECUTION_LANE_WORDS = ["host", "e2b", "k8s", "ssh", "adb", "local-docker", "device"];
47
+ /** 可声明的执行车道闭集(= 引擎 `REMOTE_EXEC` 收的词,逐字同序;18.0.0 尾插 `none`)。 */
48
+ export const EXECUTION_LANE_WORDS = ["host", "e2b", "k8s", "ssh", "adb", "local-docker", "device", "none"];
42
49
  export const RETIRED_LANE_WORDS = Object.freeze([
43
50
  Object.freeze(["remote-docker", "local-docker", "point it at the remote docker daemon with DOCKER_HOST (in a worker spec: runtime.execution.coords.DOCKER_HOST)"]),
44
51
  ]);
@@ -50,9 +57,10 @@ export function retiredLaneWordRefusal(word) {
50
57
  /** 车道分不分租户(引擎能力表的 `tenantSeparation` 位,两档,没有「不表态」)。
51
58
  * `supported` = 每个租户的命令落在各自的目标上;`single-user-only` = 所有任务落同一个共享目标 ⇒ 只许单用户 worker。 */
52
59
  export const TENANT_SEPARATION_WORDS = ["supported", "single-user-only"];
53
- /** 分租户车道**凭什么**分租户(两档):`isolation` = 每个任务一只隔离的目标(microVM / pod / 容器);`principal-binding` = 目标绑在
54
- * 发起者身上(设备 ↔ principal 绑定),目标本身不隔离。只有 `tenantSeparation: "supported"` 的车道有依据。 */
55
- export const TENANT_SEPARATION_BASES = ["isolation", "principal-binding"];
60
+ /** 分租户车道**凭什么**分租户(三档):`isolation` = 每个任务一只隔离的目标(microVM / pod / 容器);`principal-binding` = 目标绑在
61
+ * 发起者身上(设备 ↔ principal 绑定),目标本身不隔离;`no-execution-target`(18.0.0)= 根本没有执行目标(工具没有执行环境),也就没有跨租户共享的目标 ——
62
+ * 这一档还带一条集合规则:含这档车道的车道表只能有它这一项(判官 {@link laneSetFlaws} 读这一列)。只有 `tenantSeparation: "supported"` 的车道有依据。 */
63
+ export const TENANT_SEPARATION_BASES = ["isolation", "principal-binding", "no-execution-target"];
56
64
  /** 逐车道一行。`satisfies Record<ExecutionLane, …>` = 词表加词而这里不加行 ⇒ 编译红。 */
57
65
  const LANE_ROWS = {
58
66
  /** server `config.ts` :4014 臂(无必需键);`execution-lane-caps.ts` :209 `single-user-only`(所有任务落 worker 宿主)。 */
@@ -161,7 +169,25 @@ const LANE_ROWS = {
161
169
  ],
162
170
  optionalSecretEnv: [],
163
171
  },
172
+ /** 18.0.0:没有执行环境(见文件头「`none`」一节)。分租户,依据 `no-execution-target`(没有执行目标,就没有跨租户共享的目标);不挂在副本上;
173
+ * 四栏全空(没有要配的坐标与密钥)。本行先于引擎:引擎提货本版后它的能力表 / 必需键门 / 拒句从本行派生。 */
174
+ none: {
175
+ tenantSeparation: "supported",
176
+ tenantSeparationBasis: "no-execution-target",
177
+ singleReplica: false,
178
+ requiredEnv: [],
179
+ requiredSecretEnv: [],
180
+ optionalEnv: [],
181
+ optionalSecretEnv: [],
182
+ },
164
183
  };
184
+ /** 一条车道是不是独占车道 —— 读表行的依据列(运行期判据与 {@link StandaloneLane} 的型读同一列)。 */
185
+ function isStandaloneLane(lane) {
186
+ const row = LANE_ROWS[lane];
187
+ return row.tenantSeparation === "supported" && row.tenantSeparationBasis === "no-execution-target";
188
+ }
189
+ /** 独占车道,按词表次序(从依据列现算)。 */
190
+ const STANDALONE_LANES = EXECUTION_LANE_WORDS.filter(isStandaloneLane);
165
191
  /** 车道表(次序 = {@link EXECUTION_LANE_WORDS})。整张表深冻结 —— 它是唯一正本,消费方拿到的不许被改。 */
166
192
  export const EXECUTION_LANES = Object.freeze(EXECUTION_LANE_WORDS.map((lane) => {
167
193
  const row = LANE_ROWS[lane];
@@ -188,6 +214,45 @@ export const EXECUTION_LANES = Object.freeze(EXECUTION_LANE_WORDS.map((lane) =>
188
214
  export function isBlankCoordinate(value) {
189
215
  return value.trim() === "";
190
216
  }
217
+ /** 车道表的规则句(不含值,与 `DEVICE_RELAY_RULE` 同形):写闸的拒句、引擎的拒启句、表单的提示读这一句,句子只在这里写。 */
218
+ export const LANE_SET_RULE = `must be a non-empty list — the ordered execution lane set (the first one is the default) of ${EXECUTION_LANE_WORDS.join(" | ")}, each at most once; a lane set with no execution environment for the agent's tools is ${STANDALONE_LANES.map((lane) => `[${JSON.stringify(lane)}]`).join(" or ")}, alone`;
219
+ /**
220
+ * 车道表合不合法的**唯一判官**:合法 ⇒ `[]`;否则按表的次序交回每一条拒因({@link LaneSetFlaw})。纯函数,不抛。
221
+ * 谁问它:写闸(`WorkerSpec` 的 `runtime.execution.lanes`)逐条映射成 issue;引擎把 `REMOTE_EXEC` 按逗号切段后交给它(引擎只留切段);web-admin 表单同问。
222
+ * 入参是任意值的表(读 JSON 的写闸原样交进来;引擎切段后交的是字符串):不是字符串的项不是车道词 ⇒ `unknown`,与写错的词同一条拒因 —— 「每一项都是车道词」只有这一处判。
223
+ * 按下标读(`0 … length-1`):稀疏表的空洞读成 `undefined`,是一项不是字符串的项 —— 与写闸的下标快照同答(`forEach` / `some` 会跳过空洞)。
224
+ * 只判「这张表合不合法」;每条车道缺不缺必需坐标、本引擎版本服务不服务这条车道,是调用方各自的门。
225
+ */
226
+ export function laneSetFlaws(lanes) {
227
+ if (lanes.length === 0)
228
+ return [{ kind: "empty_set" }];
229
+ const flaws = [];
230
+ const seen = [];
231
+ const entries = Array.from({ length: lanes.length }, (_, at) => lanes[at]); // 按下标:空洞 ⇒ undefined(见上)
232
+ for (const [at, entry] of entries.entries()) {
233
+ if (typeof entry !== "string") {
234
+ flaws.push({ kind: "unknown", at, word: entry }); // 不是字符串:不是车道词(不读它的属性)
235
+ continue;
236
+ }
237
+ if (isBlankCoordinate(entry)) {
238
+ flaws.push({ kind: "blank_entry", at });
239
+ continue;
240
+ }
241
+ const lane = EXECUTION_LANE_WORDS.find((w) => w === entry); // 裸比较让编译器收窄(不把开放串断言成闭集词)
242
+ if (lane !== undefined) {
243
+ if (seen.includes(lane))
244
+ flaws.push({ kind: "duplicate", at, lane });
245
+ else
246
+ seen.push(lane);
247
+ continue;
248
+ }
249
+ flaws.push(retiredLaneWordRefusal(entry) === undefined ? { kind: "unknown", at, word: entry } : { kind: "retired", at, word: entry });
250
+ }
251
+ for (const lane of STANDALONE_LANES)
252
+ if (seen.includes(lane) && entries.some((entry) => entry !== lane))
253
+ flaws.push({ kind: "not_alone", lane });
254
+ return flaws;
255
+ }
191
256
  /** 存储后端(→ `DB_BACKEND`)。= server `src/config-types.ts` `DB_BACKEND_WORDS`(:67)逐字同序。 */
192
257
  export const STORE_BACKENDS = ["mysql", "pg", "local", "memory"];
193
258
  /** session 店(→ `SESSION_BACKEND`)。= server `src/config.ts` :1760 `enumEnv("SESSION_BACKEND", …)` 的词表逐字同序
@@ -30,7 +30,7 @@ export interface RequiredEnvName {
30
30
  /** true if this env-NAME resolves in the supplied environment (.env merged over process.env). */
31
31
  present: boolean;
32
32
  }
33
- /** LIST every env-NAME the current local config requires (across models/workers/mcp + the
33
+ /** LIST every env-NAME the current local config requires (across models/workers/mcp/a2a + the
34
34
  * remote-exec backend), marking which are MISSING from the resolved env — the TOC "you still need to set
35
35
  * these secrets" panel. Reuses collectEnvRefs for the domain side, then appends the remoteExec secret refs. */
36
36
  export declare function listRequiredEnvNames(eff: EffectiveConfig, remoteExec: RemoteExecSpec | undefined, resolvedEnv: Record<string, string | undefined>): RequiredEnvName[];
package/dist/file-edit.js CHANGED
@@ -69,7 +69,7 @@ export async function writeDomainFile(root, domain, value) {
69
69
  writeFileAtomic(path.join(dir, `${domain}.json`), configDDocumentText(parsed), "config-document");
70
70
  return parsed;
71
71
  }
72
- /** LIST every env-NAME the current local config requires (across models/workers/mcp + the
72
+ /** LIST every env-NAME the current local config requires (across models/workers/mcp/a2a + the
73
73
  * remote-exec backend), marking which are MISSING from the resolved env — the TOC "you still need to set
74
74
  * these secrets" panel. Reuses collectEnvRefs for the domain side, then appends the remoteExec secret refs. */
75
75
  export function listRequiredEnvNames(eff, remoteExec, resolvedEnv) {
@@ -108,6 +108,7 @@ export function listRequiredEnvNames(eff, remoteExec, resolvedEnv) {
108
108
  case "adb":
109
109
  case "host":
110
110
  case "local-docker":
111
+ case "none":
111
112
  break; // these carry no secret env-NAME refs
112
113
  default: {
113
114
  const unhandled = re;
@@ -52,24 +52,39 @@ export declare const HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostTo
52
52
  /** One hook event name (the same union as `./hooks`' `HookEventName`, which is the zod enum over {@link HOOK_EVENTS}). */
53
53
  type HookEventWord = (typeof HOOK_EVENTS)[number];
54
54
  /**
55
- * The hook entry `type` closed set — CC 2.1.296's five (`cli296.js:237454`): `command` (shell command on the host that runs the hook),
55
+ * The hook entry `type` closed set — CC 2.1.296's five (`cli296.js:237454`): `command` (a command on the host that runs the hook: a shell command line, or — with `args` — an executable spawned directly, no shell),
56
56
  * `prompt` (one-shot LLM evaluation of the hook input), `agent` (agentic verifier loop), `http` (POST the hook input to a URL),
57
57
  * `mcp_tool` (call a tool on an already-configured MCP server). An entry of another type is a bad piece — and in a guard event that rejects
58
58
  * the whole document, which is why `test/upstream-drift.test.ts` ①b holds this set against the CC anchor snapshot.
59
59
  */
60
60
  export declare const HOOK_ENTRY_TYPES: readonly ["command", "prompt", "agent", "http", "mcp_tool"];
61
61
  export type HookEntryType = (typeof HOOK_ENTRY_TYPES)[number];
62
+ /**
63
+ * The `onFailure` closed set (CC `cli309.js:252382`, `onFailure:At()`): what a FAILURE of the hook does — it could not start (a missing script), timed out, exited with a code other
64
+ * than 0 or 2, or printed JSON that is invalid or fails validation. `continue` (CC's default when the key is absent; this package does not fill it in — an absent key stays absent): the
65
+ * failure is reported and the action goes ahead. `block`: the failure counts as exit code 2, so the action the event guards is blocked. Carried by `command` and `http` entries only.
66
+ * CARRIED, not executed here: a consumer that reads `block` executes it or refuses it — ignoring it makes a guard that cannot start let the action through.
67
+ * The classifier reads this set through a closure-private projection that the compiler ties to it in both directions.
68
+ */
69
+ export declare const HOOK_ON_FAILURE_WORDS: readonly ["continue", "block"];
70
+ /** One word of {@link HOOK_ON_FAILURE_WORDS}. */
71
+ export type HookOnFailure = (typeof HOOK_ON_FAILURE_WORDS)[number];
62
72
  /** `shell` values: 'bash' = the user's $SHELL family; 'powershell' = pwsh. Default bash. */
63
73
  declare const HOOK_SHELLS: readonly ["bash", "powershell"];
64
74
  /**
65
75
  * THE field table of a hook entry, per type — read by {@link classifyHooksConfig} (which field is bad, what is carried) and by the entry
66
76
  * types below (derived from it). Field order = the order a loaded entry's keys come out in.
67
- * · command: `shell` (see above), `async` = run in background without blocking, `asyncRewake` = background + wake the model on exit 2;
77
+ * · command: `args` = exec form (CC `cli309.js:252382`: when present — `[]` included — `command` is an executable spawned directly with these arguments, no shell; the mere
78
+ * presence of `args` is the switch), `shell` (see above), `async` = run in background without blocking, `asyncRewake` = background + wake the model on exit 2,
79
+ * `onFailure` (see {@link HOOK_ON_FAILURE_WORDS});
68
80
  * · prompt / agent: `prompt` ($ARGUMENTS = the hook input JSON), `model`;
69
81
  * · http: `url`, `headers` (values may reference $NAMEs listed in `allowedEnvVars` — env-NAME interpolation only, the config never carries a
70
- * secret value; `interpolateHookEnvVars` is the one reading), `allowedEnvVars`;
82
+ * secret value; `interpolateHookEnvVars` is the one reading), `allowedEnvVars`, `onFailure` (the other schema CC gives it to);
71
83
  * · mcp_tool: `server` (an already-configured MCP server's name), `tool`, `input` (the tool's arguments; string values may interpolate
72
84
  * `${path}` from the hook input JSON — CC 2.1.296 semantics, the runner's business).
85
+ * NOT in the table, on purpose: CC's `@internal` command / http keys `rewakeMessage` / `rewakeSummary` / `cloud` (not public settings keys; `cloud` even reads a bad value
86
+ * as 'skip' and loads the file) and the prompt entry's `continueOnBlock`. A key outside a type's row is not carried and not a bad piece (`docs/CC-DIVERGENCE.md` H7) —
87
+ * which is also why `args` on a non-command entry and `onFailure` on a prompt / agent / mcp_tool entry are such keys.
73
88
  */
74
89
  declare const HOOK_ENTRY_FIELDS: {
75
90
  readonly command: {
@@ -78,9 +93,11 @@ declare const HOOK_ENTRY_FIELDS: {
78
93
  readonly statusMessage: "string?";
79
94
  readonly once: "boolean?";
80
95
  readonly command: "text";
96
+ readonly args: "argv?";
81
97
  readonly shell: "shell?";
82
98
  readonly async: "boolean?";
83
99
  readonly asyncRewake: "boolean?";
100
+ readonly onFailure: "onFailure?";
84
101
  };
85
102
  readonly prompt: {
86
103
  readonly if: "string?";
@@ -106,6 +123,7 @@ declare const HOOK_ENTRY_FIELDS: {
106
123
  readonly url: "text";
107
124
  readonly headers: "stringRecord?";
108
125
  readonly allowedEnvVars: "stringList?";
126
+ readonly onFailure: "onFailure?";
109
127
  };
110
128
  readonly mcp_tool: {
111
129
  readonly if: "string?";
@@ -117,7 +135,7 @@ declare const HOOK_ENTRY_FIELDS: {
117
135
  readonly input: "record?";
118
136
  };
119
137
  };
120
- type HookFieldBaseValue<B> = B extends "text" | "string" ? string : B extends "shell" ? (typeof HOOK_SHELLS)[number] : B extends "boolean" ? boolean : B extends "seconds" ? number : B extends "stringRecord" ? Record<string, string> : B extends "stringList" ? string[] : B extends "record" ? Record<string, unknown> : never;
138
+ type HookFieldBaseValue<B> = B extends "text" | "string" ? string : B extends "shell" ? (typeof HOOK_SHELLS)[number] : B extends "onFailure" ? HookOnFailure : B extends "boolean" ? boolean : B extends "seconds" ? number : B extends "stringRecord" ? Record<string, string> : B extends "stringList" | "argv" ? string[] : B extends "record" ? Record<string, unknown> : never;
121
139
  type HookFieldValue<K> = K extends `${infer B}?` ? HookFieldBaseValue<B> : HookFieldBaseValue<K>;
122
140
  type Flat<O> = {
123
141
  [K in keyof O]: O[K];
@@ -130,7 +148,7 @@ type HookEntryOf<T extends HookEntryType, F = (typeof HOOK_ENTRY_FIELDS)[T]> = F
130
148
  } & {
131
149
  -readonly [K in keyof F as F[K] extends `${string}?` ? K : never]?: HookFieldValue<F[K]>;
132
150
  }>;
133
- /** `command` — shell command on the host that runs the hook (DESIGN POINT ② in `./hooks`). */
151
+ /** `command` — a command on the host that runs the hook: a shell command line, or — when the entry carries `args` — an executable spawned directly with those arguments, no shell (DESIGN POINT ② in `./hooks`). */
134
152
  export type CommandHookEntry = HookEntryOf<"command">;
135
153
  /** `prompt` — one-shot LLM evaluation of the hook input ($ARGUMENTS = input JSON). */
136
154
  export type PromptHookEntry = HookEntryOf<"prompt">;