@sema-agent/settings-schema 6.4.0 → 7.0.1

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 (95) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +53 -2
  3. package/bin/settings-schema.mjs +22 -0
  4. package/conformance/VERSION +1 -1
  5. package/conformance/cases/c50-s784-lane-host-legal/expected.json +102 -0
  6. package/conformance/cases/c50-s784-lane-host-legal/input.json +29 -0
  7. package/conformance/cases/c50-s784-lane-host-legal/meta.json +5 -0
  8. package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +104 -0
  9. package/conformance/cases/c51-s784-lane-e2b-legal/input.json +32 -0
  10. package/conformance/cases/c51-s784-lane-e2b-legal/meta.json +5 -0
  11. package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +106 -0
  12. package/conformance/cases/c52-s784-lane-k8s-legal/input.json +34 -0
  13. package/conformance/cases/c52-s784-lane-k8s-legal/meta.json +5 -0
  14. package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +106 -0
  15. package/conformance/cases/c53-s784-lane-ssh-legal/input.json +34 -0
  16. package/conformance/cases/c53-s784-lane-ssh-legal/meta.json +5 -0
  17. package/conformance/cases/c54-s784-lane-adb-legal/expected.json +102 -0
  18. package/conformance/cases/c54-s784-lane-adb-legal/input.json +29 -0
  19. package/conformance/cases/c54-s784-lane-adb-legal/meta.json +5 -0
  20. package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +103 -0
  21. package/conformance/cases/c55-s784-lane-local-docker-legal/input.json +30 -0
  22. package/conformance/cases/c55-s784-lane-local-docker-legal/meta.json +5 -0
  23. package/conformance/cases/c56-s784-lane-device-legal/expected.json +109 -0
  24. package/conformance/cases/c56-s784-lane-device-legal/input.json +36 -0
  25. package/conformance/cases/c56-s784-lane-device-legal/meta.json +5 -0
  26. package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +83 -0
  27. package/conformance/cases/c57-s784-g1-enabled-no-lane/input.json +23 -0
  28. package/conformance/cases/c57-s784-g1-enabled-no-lane/meta.json +5 -0
  29. package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +83 -0
  30. package/conformance/cases/c58-s784-g2-multitenant-host/input.json +34 -0
  31. package/conformance/cases/c58-s784-g2-multitenant-host/meta.json +5 -0
  32. package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +83 -0
  33. package/conformance/cases/c59-s784-g3-k8s-missing-image/input.json +26 -0
  34. package/conformance/cases/c59-s784-g3-k8s-missing-image/meta.json +5 -0
  35. package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +83 -0
  36. package/conformance/cases/c60-s784-g3-e2b-missing-key/input.json +26 -0
  37. package/conformance/cases/c60-s784-g3-e2b-missing-key/meta.json +5 -0
  38. package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +83 -0
  39. package/conformance/cases/c61-s784-g3-other-lane-key/input.json +30 -0
  40. package/conformance/cases/c61-s784-g3-other-lane-key/meta.json +5 -0
  41. package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +83 -0
  42. package/conformance/cases/c62-s784-g7a-session-tidb/input.json +27 -0
  43. package/conformance/cases/c62-s784-g7a-session-tidb/meta.json +5 -0
  44. package/conformance/cases/c63-s784-host-two-replicas/expected.json +83 -0
  45. package/conformance/cases/c63-s784-host-two-replicas/input.json +27 -0
  46. package/conformance/cases/c63-s784-host-two-replicas/meta.json +5 -0
  47. package/conformance/cases/c64-s784-multitenant-local-store/expected.json +83 -0
  48. package/conformance/cases/c64-s784-multitenant-local-store/input.json +30 -0
  49. package/conformance/cases/c64-s784-multitenant-local-store/meta.json +5 -0
  50. package/conformance/cases/c65-s784-pg-without-db/expected.json +83 -0
  51. package/conformance/cases/c65-s784-pg-without-db/input.json +27 -0
  52. package/conformance/cases/c65-s784-pg-without-db/meta.json +5 -0
  53. package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +83 -0
  54. package/conformance/cases/c66-s784-memory-backend-mismatch/input.json +36 -0
  55. package/conformance/cases/c66-s784-memory-backend-mismatch/meta.json +5 -0
  56. package/conformance/cases/c67-s784-retired-field-loud/expected.json +83 -0
  57. package/conformance/cases/c67-s784-retired-field-loud/input.json +22 -0
  58. package/conformance/cases/c67-s784-retired-field-loud/meta.json +5 -0
  59. package/conformance/cases/c68-s784-device-local-store/expected.json +83 -0
  60. package/conformance/cases/c68-s784-device-local-store/input.json +26 -0
  61. package/conformance/cases/c68-s784-device-local-store/meta.json +5 -0
  62. package/conformance/cases/cli04-env-value-with-newline/expected.json +13 -2
  63. package/conformance/cases/cli04-env-value-with-newline/input.json +8 -1
  64. package/conformance/cases/cli04-env-value-with-newline/meta.json +1 -1
  65. package/conformance/migrate-workers/m01-g5-k8s-env-lifted/expected.json +49 -0
  66. package/conformance/migrate-workers/m01-g5-k8s-env-lifted/input.json +26 -0
  67. package/conformance/migrate-workers/m01-g5-k8s-env-lifted/meta.json +4 -0
  68. package/conformance/migrate-workers/m02-g5-idempotent/expected.json +38 -0
  69. package/conformance/migrate-workers/m02-g5-idempotent/input.json +33 -0
  70. package/conformance/migrate-workers/m02-g5-idempotent/meta.json +5 -0
  71. package/conformance/migrate-workers/m03-g5-refused-no-lane/expected.json +71 -0
  72. package/conformance/migrate-workers/m03-g5-refused-no-lane/input.json +42 -0
  73. package/conformance/migrate-workers/m03-g5-refused-no-lane/meta.json +4 -0
  74. package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/expected.json +52 -0
  75. package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/input.json +30 -0
  76. package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/meta.json +4 -0
  77. package/dist/cli.d.ts +8 -0
  78. package/dist/cli.js +110 -0
  79. package/dist/execution-lane-index.d.ts +38 -0
  80. package/dist/execution-lane-index.js +46 -0
  81. package/dist/execution-lanes.d.ts +64 -0
  82. package/dist/execution-lanes.js +152 -0
  83. package/dist/file-edit.js +26 -21
  84. package/dist/hooks.d.ts +4 -4
  85. package/dist/index.d.ts +2 -0
  86. package/dist/index.js +2 -0
  87. package/dist/migrate-workers.d.ts +63 -0
  88. package/dist/migrate-workers.js +327 -0
  89. package/dist/remote-exec.d.ts +23 -64
  90. package/dist/remote-exec.js +20 -71
  91. package/dist/secret-refs.d.ts +7 -1
  92. package/dist/secret-refs.js +9 -3
  93. package/dist/types.d.ts +1356 -455
  94. package/dist/types.js +267 -63
  95. package/package.json +9 -1
package/dist/types.js CHANGED
@@ -19,6 +19,8 @@ import { z } from "zod";
19
19
  import { MAX_EXTRA_BODY_DEPTH } from "./hash.js"; // reconcile the extraBody depth cap with the hash cap (R18)
20
20
  import { DispatchPolicy } from "./fleet.js"; // WorkerSpec.fleet.dispatch (scheduling framework D3)
21
21
  import { carriesUserinfo, percentLayers } from "./secret-scan.js"; // package-private normalization + the userinfo judgement (not in the barrel)
22
+ import { EXECUTION_LANES, EXECUTION_LANE_WORDS, MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "./execution-lanes.js"; // 7.0.0 M-R2: the lane / store vocabulary (one exported table)
23
+ import { LANE_ENV_OWNER, MIGRATION_SIGNPOST, isMigratableEnvKey, laneEnvSeat, laneRowOf } from "./execution-lane-index.js"; // package-private indexes derived from that table (not in the barrel)
22
24
  // COARSE, best-effort literal-secret SHAPE detector (NOT exhaustive — the real boundary is the env-NAME schema
23
25
  // on structured fields). Rejected in free-form value fields (worker `env`, model `extraBody`), URL components,
24
26
  // AND as an env-NAME value (an AWS key id is a valid env-NAME charset yet is a secret VALUE, not a NAME). Case-
@@ -253,7 +255,7 @@ function createFreeFormTransform(field) {
253
255
  /** A k8s resource quantity ("250m", "1", "0.5", "512Mi", "1Gi", "128M") — catches garbage at config-submit
254
256
  * rather than surfacing as a reconcile ApplyError at the apiserver (consistent with ENV_NAME/DURATION guards). */
255
257
  export const K8S_QUANTITY = z.string().regex(/^\d+(\.\d+)?(m|[kKMGTPE]i?)?$/, 'must be a k8s quantity like "250m", "512Mi", "1Gi"');
256
- /** A TCP port: 1–65535. ONE shared schema so every port field (workers.coords.tidbPort, ssh.port, …) bounds
258
+ /** A TCP port: 1–65535. ONE shared schema so every port field (workers.runtime.store.db.port, ssh.port, …) bounds
257
259
  * the SAME way — a port of 65536/0/-1 is config corruption, reject it at validation, not at connect time. R16. */
258
260
  export const TCP_PORT = z.number().int().positive().max(65535);
259
261
  /** Per-1M-token price ($/Mtok). Maps to the service's MODEL_COST_INPUT/OUTPUT/CACHE_READ/CACHE_WRITE
@@ -1324,8 +1326,9 @@ export const ExecutionPolicy = z.object({
1324
1326
  * shell locks sandbox-enabled by policy; user settings can't turn it off — CC 同款语义). */
1325
1327
  required: z.boolean().default(false),
1326
1328
  /** Which execution lanes are allowed. OPEN value domain — z.string(), NEVER an enum: adding a lane
1327
- * must not be a BREAKING schema change (promised to the service). Known lanes
1328
- * today: `host` / `e2b` / `k8s` / `ssh` / `adb` / `local-docker`. Unknown strings are consumer-defined. */
1329
+ * must not be a BREAKING schema change (promised to the service). The closed set of lanes a worker can
1330
+ * declare is `EXECUTION_LANE_WORDS` (`execution-lanes.ts`, 7.0.0) — read it there, not from a copy in this
1331
+ * comment. Unknown strings are consumer-defined. */
1329
1332
  allowedLanes: z.array(z.string().min(1)).default(["host"]),
1330
1333
  /** Org 默认会话镜像目标(0.10.1,附B 两层)。ABSENT = INERT(center 未管镜像,壳解析序里本腿为空)。
1331
1334
  * 读损档随域(DOMAIN_READ_FALLBACK.execution="throw" 已覆盖:损坏绝不静默退默认放权)。 */
@@ -1527,61 +1530,190 @@ const WorkerSecretRefs = z.object({
1527
1530
  serviceAuthTokenEnv: ENV_NAME.optional(),
1528
1531
  /** → GIT_API_TOKEN (只读,读被评审 repo). */
1529
1532
  gitTokenEnv: ENV_NAME.optional(),
1530
- /** → TIDB_PASSWORD. */
1531
- tidbPasswordEnv: ENV_NAME.optional(),
1533
+ /** 7.0.0:SQL 店的口令 → `MYSQL_PASSWORD` / `PG_PASSWORD`(渲染器按 `runtime.store.backend` 选名族)。
1534
+ * 在场 ⇔ `runtime.store.backend` ∈ {mysql, pg}(WorkerSpec 层判,与 `runtime.store.db` 同一条规则)。取代 6.x 的 `tidbPasswordEnv`。 */
1535
+ dbPasswordEnv: ENV_NAME.optional(),
1532
1536
  });
1533
1537
  /**
1534
- * Env keys the structured WorkerSpec fields already produce (worker-env.ts) — the general `env`/`secretEnv`
1535
- * passthrough may NOT set these (use the dedicated field). Blocks the council's two escape-hatch risks:
1536
- * redirecting model/traffic or disabling auth (PORT, MODEL_x, SESSION_BACKEND, REQUIRE_PRINCIPAL, gateway), and
1537
- * shadowing a projected secret with a literal (MODEL_API_KEY, SERVICE_AUTH_TOKEN, …). LOG_LEVEL is intentionally
1538
- * NOT reserved (benign, overridable for debugging). Keep in sync with workerToServiceEnv.
1538
+ * 每个**保留** env 名 → WorkerSpec 里产出它的那个结构字段(唯一写位)。`env` / `secretEnv` 逃生口拒这些名,拒因指路到这里写的字段
1539
+ * (例:`K8S_SANDBOX_IMAGE` → `runtime.execution.coords.K8S_SANDBOX_IMAGE`)。{@link RESERVED_WORKER_ENV} 由本表的键**派生**,
1540
+ * 不再是第二张要「保持同步」的手写表。
1539
1541
  *
1540
- * ⚠️ SCOPE (0.1.30, after the R22→0.1.29 detour): this ONLY blocks keys that have a DEDICATED structured field
1541
- * or secretRef — so an operator is steered to that field instead of duplicating/shadowing it via env. It does
1542
- * NOT block the per-worker EXECUTION-SUBSTRATE config an operator legitimately sets via worker.env, which the
1543
- * live fleet relies on: `REMOTE_EXEC` (direct=k8s, canary=e2b set it HERE), `K8S_*` (SANDBOX_IMAGE/NAMESPACE/
1544
- * RUNTIME_CLASS/…), `E2B_*`, `MINIO_*`, `SSH_*`, `ADB_*`. worker.env is the OPERATOR's per-worker deployment env
1545
- * (publisher-gated, projected by the orchestrator to the worker pod) — NOT untrusted-task-controlled, so there
1546
- * is no "sandbox escape" to block here. R22 wrongly added those substrate keys treating worker.env as
1547
- * task-controlled; it would have REJECTED the live direct/canary workers on deploy. Reverted in 0.1.30. (Literal
1548
- * SECRET values pasted into env are still caught separately by containsSecretToken; substrate CREDS belong in
1549
- * secretEnv as env-NAME refs.)
1542
+ * 封堵逃生口的两类风险(council):改路由 / 关鉴权(PORT、MODEL_x、REQUIRE_PRINCIPAL、gateway)与用字面量遮蔽已投射的密钥
1543
+ * (MODEL_API_KEY、SERVICE_AUTH_TOKEN …)。LOG_LEVEL 刻意不保留(无害,调试可覆盖)。
1544
+ *
1545
+ * 7.0.0 增删(M-R2 结构块):
1546
+ * · **加** `REMOTE_EXEC` / `REQUIRE_EXECUTION_LANE` + 车道表({@link EXECUTION_LANES})里**全部**车道 env 名 —— 车道有了结构位
1547
+ * `runtime.execution`,逃生口再能设车道就是两把尺。这是对 0.1.30「执行基座键不保留」的**反转**:当年不保留是因为没有结构位
1548
+ * (活舰队 direct = k8s、canary = e2b 只能在 `worker.env` 里设);存量行由一次性迁移 `migrateWorkersDomain` 搬进结构块。
1549
+ * 别的域也读的名(`MINIO_*`、`LOG_LEVEL`、`REMOTE_EXEC_HOME_DIR`)不在车道表里,照旧不保留。
1550
+ * · **加** `DB_BACKEND` / `MEMORY_ENGINE` / `MEMORY_ENGINE_BACKEND` —— `runtime.store` 产出它们。
1551
+ * · **删** `MEMORY_BACKEND`(引擎从不读的死名,渲染器不再产出)与 `TIDB_*` 族(引擎的墓碑名,见之拒启;结构字段不再产出它们 ——
1552
+ * 墓碑名由引擎导出的墓碑表把关,不是本表的职责)。MYSQL_* / PG_* 两族归 `runtime.store.db` + `secretRefs.dbPasswordEnv`。
1550
1553
  */
1551
- export const RESERVED_WORKER_ENV = new Set([
1552
- "PORT", "MODEL_PROVIDER", "MODEL_ID", "MODEL_REASONING", "MODEL_CHEAP_ID", "MODEL_MAX_TOKENS",
1553
- "MODEL_CONTEXT_WINDOW", "MODEL_COST_INPUT", "MODEL_COST_OUTPUT", "MODEL_COST_CACHE_READ",
1554
- "MODEL_COST_CACHE_WRITE", "MAX_PRINCIPAL_COST_USD", "SESSION_BACKEND", "MEMORY_BACKEND",
1555
- "REQUIRE_PRINCIPAL", "RATE_LIMIT_RPM", "DEFAULT_SCENARIO", "TIDB_DATABASE", "TIDB_HOST",
1556
- "TIDB_PORT", "TIDB_USER", "GIT_API_BASEURL", "MODEL_GATEWAY_BASEURL", "ANTHROPIC_BASEURL",
1557
- "MODEL_API_KEY", "ANTHROPIC_API_KEY", "SERVICE_AUTH_TOKEN", "GIT_API_TOKEN", "TIDB_PASSWORD",
1558
- // server ≥2.0 的中立后端面(kind mysql|pg|local):MYSQL_* 是首选族(TIDB_* 为其兼容别名)、PG_* 是
1559
- // 第三族——三族同为 coords 结构化字段的遮蔽面,缺任何一族=该族键可从 worker.env 静默压过结构化配置
1560
- // (2026-07-31 交叉终扫 G1 补齐;此前只列 TIDB_* 一族)。
1561
- "MYSQL_DATABASE", "MYSQL_HOST", "MYSQL_PORT", "MYSQL_USER", "MYSQL_PASSWORD",
1562
- "PG_DATABASE", "PG_HOST", "PG_PORT", "PG_USER", "PG_PASSWORD",
1563
- ]);
1564
- // Which structured WorkerSpec field owns each reserved env-NAME → so a rejection names the RIGHT field to use
1565
- // (e.g. TIDB_DATABASE → runtime.db, MODEL_ID → model.modelId), not a vague "use the structured field".
1566
- const RESERVED_ENV_FIELD = {
1554
+ const RESERVED_ENV_FIELD = Object.freeze({
1567
1555
  PORT: "(fixed at 8090 — not configurable)", MODEL_PROVIDER: "model.provider", MODEL_ID: "model.modelId",
1568
1556
  MODEL_REASONING: "model.reasoning", MODEL_CHEAP_ID: "model.cheapId", MODEL_MAX_TOKENS: "model.maxTokens",
1569
1557
  MODEL_CONTEXT_WINDOW: "model.contextWindow", MODEL_COST_INPUT: "model.cost.input", MODEL_COST_OUTPUT: "model.cost.output",
1570
1558
  MODEL_COST_CACHE_READ: "model.cost.cacheRead", MODEL_COST_CACHE_WRITE: "model.cost.cacheWrite",
1571
- MAX_PRINCIPAL_COST_USD: "runtime.maxPrincipalCostUsd", SESSION_BACKEND: "runtime.sessionBackend",
1572
- MEMORY_BACKEND: "runtime.memoryBackend", REQUIRE_PRINCIPAL: "runtime.requirePrincipal", RATE_LIMIT_RPM: "runtime.rateLimitRpm",
1573
- DEFAULT_SCENARIO: "runtime.scenario", TIDB_DATABASE: "runtime.db", TIDB_HOST: "coords.tidbHost", TIDB_PORT: "coords.tidbPort",
1574
- TIDB_USER: "coords.tidbUser", GIT_API_BASEURL: "coords.gitApiBaseUrl", MODEL_GATEWAY_BASEURL: "model.gatewayBaseUrl",
1575
- // G1 补齐的两族与 TIDB_* 同归属(coords.tidb* 字段名是历史命名,承 mysql-协议三家;PG 同面)
1576
- MYSQL_DATABASE: "runtime.db", MYSQL_HOST: "coords.tidbHost", MYSQL_PORT: "coords.tidbPort", MYSQL_USER: "coords.tidbUser", MYSQL_PASSWORD: "secretRefs.tidbPasswordEnv",
1577
- PG_DATABASE: "runtime.db", PG_HOST: "coords.tidbHost", PG_PORT: "coords.tidbPort", PG_USER: "coords.tidbUser", PG_PASSWORD: "secretRefs.tidbPasswordEnv",
1559
+ MAX_PRINCIPAL_COST_USD: "runtime.maxPrincipalCostUsd", REQUIRE_PRINCIPAL: "runtime.requirePrincipal", RATE_LIMIT_RPM: "runtime.rateLimitRpm",
1560
+ DEFAULT_SCENARIO: "runtime.scenario", GIT_API_BASEURL: "coords.gitApiBaseUrl", MODEL_GATEWAY_BASEURL: "model.gatewayBaseUrl",
1578
1561
  ANTHROPIC_BASEURL: "model.gatewayBaseUrl", MODEL_API_KEY: "secretRefs.apiKeyEnv", ANTHROPIC_API_KEY: "secretRefs.apiKeyEnv",
1579
1562
  SERVICE_AUTH_TOKEN: "secretRefs.serviceAuthTokenEnv", GIT_API_TOKEN: "secretRefs.gitTokenEnv",
1580
- TIDB_PASSWORD: "secretRefs.tidbPasswordEnv",
1581
- };
1582
- export const WorkerSpec = z.object({
1563
+ // ── runtime.store(7.0.0)
1564
+ DB_BACKEND: "runtime.store.backend", SESSION_BACKEND: "runtime.store.session",
1565
+ MEMORY_ENGINE: "runtime.store.memory.engine", MEMORY_ENGINE_BACKEND: "runtime.store.memory.backend",
1566
+ MYSQL_HOST: "runtime.store.db.host", MYSQL_PORT: "runtime.store.db.port", MYSQL_USER: "runtime.store.db.user",
1567
+ MYSQL_DATABASE: "runtime.store.db.database", MYSQL_PASSWORD: "secretRefs.dbPasswordEnv",
1568
+ PG_HOST: "runtime.store.db.host", PG_PORT: "runtime.store.db.port", PG_USER: "runtime.store.db.user",
1569
+ PG_DATABASE: "runtime.store.db.database", PG_PASSWORD: "secretRefs.dbPasswordEnv",
1570
+ // ── runtime.execution(7.0.0):车道词两键 + 车道表里每个名(派生,不手抄)
1571
+ REMOTE_EXEC: "runtime.execution.lane",
1572
+ REQUIRE_EXECUTION_LANE: "runtime.execution.lane (the renderer emits REQUIRE_EXECUTION_LANE=true for every declared lane)",
1573
+ ...Object.fromEntries([...LANE_ENV_OWNER].map(([name, owner]) => [name, laneEnvSeat(name, owner)])),
1574
+ });
1575
+ /** 结构化 WorkerSpec 字段已经产出的 env 名 —— 通用 `env` / `secretEnv` 逃生口**不许**设它们(用专属字段)。由
1576
+ * `RESERVED_ENV_FIELD`(名 → 唯一写位)的键派生;增删理由见那张表的头注。 */
1577
+ export const RESERVED_WORKER_ENV = new Set(Object.keys(RESERVED_ENV_FIELD));
1578
+ /**
1579
+ * 保留名被 `env` / `secretEnv` 逃生口设置时的拒因:点名键 + 它的唯一写位。句尾恒是「set it via <字段>」(下游按它抽归属字段,不动)。
1580
+ * **迁移指路只有一条规则(7.0.1)**:这个键「旧形 ⇒ 迁移能搬」({@link isMigratableEnvKey},与迁移自己识别的键集同一只谓词)就在键名后
1581
+ * 带 {@link MIGRATION_SIGNPOST};搬不了的保留名(`PORT` / `MODEL_*` / `MYSQL_*` …)不带 —— 指一条跑了也没用的命令比不指更糟。
1582
+ */
1583
+ function reservedKeyMessage(slot, key) {
1584
+ const signpost = isMigratableEnvKey(key) ? ` (${MIGRATION_SIGNPOST})` : "";
1585
+ return `${slot} cannot set reserved key "${key}"${signpost} — set it via ${RESERVED_ENV_FIELD[key]}`;
1586
+ }
1587
+ // ── 7.0.0 M-R2:`runtime.execution` / `runtime.store` 两个结构块 ─────────────────────────────────────────────
1588
+ // 词表只有一个正本(`execution-lanes.ts` 的导出表):本包写闸(`WorkerSpec.safeParse`)、中心渲染器、引擎起服三处读同一张表。
1589
+ // 拒因都在 zod issue 里(`path` 指到要改的字段、`message` 带出路)—— schema 本身是唯一判据,不另导出拒因函数。
1590
+ /** 一个非密字面值(车道坐标 / SQL 坐标):≤2048,不像密钥(与 `env` 逃生口同一条判据 —— 密钥走 env-NAME 引用)。 */
1591
+ const nonSecretLiteral = (minLength) => z
1592
+ .string()
1593
+ .min(minLength)
1594
+ .max(2048)
1595
+ .refine((s) => !containsSecretToken(s), "looks like a literal secret — a credential goes in a secretRefs field as an env-NAME, never a value here");
1596
+ /**
1597
+ * `runtime.execution` —— 这台 worker 的执行车道(渲染:`REMOTE_EXEC=<lane>` + `REQUIRE_EXECUTION_LANE=true` + coords / secretRefs 逐键)。
1598
+ * - `lane`:{@link EXECUTION_LANE_WORDS} 之一。没有「无手」词。
1599
+ * - `coords`:该车道的**非密坐标**(服务变量名 → 字面值),键 ⊆ 表[lane] 的 `requiredEnv ∪ optionalEnv`;必需键在场且非空串
1600
+ * (引擎把空串读作未设)。可选键的空串照收(例:`K8S_RUNTIME_CLASS=""` 在引擎里是「不用 Kata」)。
1601
+ * - `secretRefs`:该车道的**密钥**(服务变量名 → 宿主 env-NAME),键 ⊆ 表[lane] 的 `requiredSecretEnv ∪ optionalSecretEnv`,必需键在场。
1602
+ * 别车道的键 ⇒ 拒并点名它属于哪条车道;本车道的密钥写进 coords / 坐标写进 secretRefs ⇒ 拒并指路另一栏。
1603
+ */
1604
+ const ExecutionBlock = z
1605
+ .object({
1606
+ lane: z.enum(EXECUTION_LANE_WORDS),
1607
+ coords: z.record(ENV_NAME, nonSecretLiteral(0)).default({}),
1608
+ secretRefs: z.record(ENV_NAME, ENV_NAME).default({}),
1609
+ })
1610
+ .superRefine((x, ctx) => {
1611
+ const row = laneRowOf(x.lane);
1612
+ const issue = (slot, key, message) => ctx.addIssue({ code: z.ZodIssueCode.custom, path: [slot, key], message });
1613
+ const owned = (secret) => (secret ? [...row.requiredSecretEnv, ...row.optionalSecretEnv] : [...row.requiredEnv, ...row.optionalEnv]);
1614
+ const judgeKey = (slot, key) => {
1615
+ const wantSecret = slot === "secretRefs";
1616
+ const owner = LANE_ENV_OWNER.get(key);
1617
+ if (owner === undefined) {
1618
+ const allowed = owned(wantSecret);
1619
+ issue(slot, key, `${key} is not ${wantSecret ? "a secret" : "a coordinate"} of any execution lane — lane "${x.lane}" takes ${slot} ${allowed.length > 0 ? allowed.join(", ") : "(none)"}`);
1620
+ return false;
1621
+ }
1622
+ if (owner.lane !== x.lane) {
1623
+ issue(slot, key, `${key} belongs to lane "${owner.lane}", not "${x.lane}" — remove it, or set runtime.execution.lane to "${owner.lane}"`);
1624
+ return false;
1625
+ }
1626
+ if (owner.secret !== wantSecret) {
1627
+ issue(slot, key, owner.secret
1628
+ ? `${key} is a secret of lane "${x.lane}" — put it in runtime.execution.secretRefs as an env-NAME (never a value in coords)`
1629
+ : `${key} is a non-secret coordinate of lane "${x.lane}" — write its value in runtime.execution.coords`);
1630
+ return false;
1631
+ }
1632
+ return true;
1633
+ };
1634
+ for (const [key, value] of Object.entries(x.coords)) {
1635
+ if (judgeKey("coords", key) && value === "" && LANE_ENV_OWNER.get(key)?.required === true) {
1636
+ issue("coords", key, `required coordinate ${key} of lane "${x.lane}" is empty — the engine reads an empty value as unset and refuses to start`);
1637
+ }
1638
+ }
1639
+ for (const key of Object.keys(x.secretRefs))
1640
+ judgeKey("secretRefs", key);
1641
+ for (const key of row.requiredEnv)
1642
+ if (!Object.hasOwn(x.coords, key))
1643
+ issue("coords", key, `lane "${x.lane}" requires coords.${key} (the engine refuses to start without it)`);
1644
+ for (const key of row.requiredSecretEnv)
1645
+ if (!Object.hasOwn(x.secretRefs, key))
1646
+ issue("secretRefs", key, `lane "${x.lane}" requires secretRefs.${key} — the host env-NAME holding it`);
1647
+ });
1648
+ /** `MEMORY_ENGINE` 的两个词(引擎目录行的 `enumValues`;引擎还认 true/false/1/0… 两族同义词,结构位只收规范词)。 */
1649
+ const MEMORY_ENGINE_SWITCH = ["on", "off"];
1650
+ /** 店词 → 是不是 SQL 店。SQL 坐标(`runtime.store.db` + `secretRefs.dbPasswordEnv`)在场 ⇔ 是。穷举表:店词加词 ⇒ 编译红。 */
1651
+ const STORE_IS_SQL = { mysql: true, pg: true, local: false, memory: false };
1652
+ /** 记忆持久面方言 → 它要求的店(`file` 不要求)。穷举表:方言加词 ⇒ 编译红。 */
1653
+ const MEMORY_BACKEND_NEEDS_STORE = { file: undefined, pg: "pg", tidb: "mysql" };
1654
+ /**
1655
+ * `runtime.store` —— 这台 worker 的持久化(取代 6.x 散落的 `sessionBackend` / `memoryBackend` / `db` / `coords.tidb*` /
1656
+ * `secretRefs.tidbPasswordEnv`)。渲染(唯一写者):`DB_BACKEND` / `SESSION_BACKEND` / `MEMORY_ENGINE` / `MEMORY_ENGINE_BACKEND` 恒渲;
1657
+ * `backend` 为 mysql ⇒ `MYSQL_HOST/PORT/USER/DATABASE` + `MYSQL_PASSWORD`(← `secretRefs.dbPasswordEnv`),pg ⇒ `PG_*` 同形。
1658
+ * 跨字段四条在 WorkerSpec 层判(要看 `secretRefs` 与 `requirePrincipal`)。
1659
+ */
1660
+ const StoreBlock = z.object({
1661
+ backend: z.enum(STORE_BACKENDS),
1662
+ session: z.enum(SESSION_BACKENDS).default("auto"),
1663
+ memory: z
1664
+ .object({
1665
+ engine: z.enum(MEMORY_ENGINE_SWITCH).default("on"),
1666
+ backend: z.enum(MEMORY_ENGINE_BACKENDS).default("file"),
1667
+ })
1668
+ .default({}),
1669
+ /** SQL 服务器坐标(中立,一份;名族由 `backend` 决定)。 */
1670
+ db: z
1671
+ .object({
1672
+ host: nonSecretLiteral(1),
1673
+ port: TCP_PORT,
1674
+ user: nonSecretLiteral(1),
1675
+ database: nonSecretLiteral(1),
1676
+ })
1677
+ .optional(),
1678
+ });
1679
+ /** 7.0.0 删除的字段 → 它的新位。**迁移窗口代码**:与 `migrateWorkersDomain` 同窗删除(只为未迁移的旧形读到即响亮拒,而不是被
1680
+ * zod 静默剥掉 —— 剥掉的话,一行旧数据会以「什么都没声明」的样子过闸)。 */
1681
+ const RETIRED_WORKER_FIELDS = [
1682
+ { parent: "runtime", key: "sessionBackend", now: "runtime.store.session" },
1683
+ { parent: "runtime", key: "memoryBackend", now: "runtime.store.memory — the old field never reached the engine" },
1684
+ { parent: "runtime", key: "db", now: "runtime.store.db.database" },
1685
+ { parent: "coords", key: "tidbHost", now: "runtime.store.db.host" },
1686
+ { parent: "coords", key: "tidbPort", now: "runtime.store.db.port" },
1687
+ { parent: "coords", key: "tidbUser", now: "runtime.store.db.user" },
1688
+ { parent: "secretRefs", key: "tidbPasswordEnv", now: "secretRefs.dbPasswordEnv" },
1689
+ ];
1690
+ /** 一个对象的**自有数据属性**值(不触发访问器;不是普通对象 ⇒ undefined)。 */
1691
+ function ownDataValue(obj, key) {
1692
+ if (obj === null || typeof obj !== "object" || Array.isArray(obj))
1693
+ return undefined;
1694
+ const d = Object.getOwnPropertyDescriptor(obj, key);
1695
+ return d !== undefined && "value" in d ? d.value : undefined;
1696
+ }
1697
+ function retiredWorkerFieldIssues(raw, ctx) {
1698
+ for (const { parent, key, now } of RETIRED_WORKER_FIELDS) {
1699
+ const container = ownDataValue(raw, parent);
1700
+ if (container !== null && typeof container === "object" && !Array.isArray(container) && Object.hasOwn(container, key)) {
1701
+ ctx.addIssue({
1702
+ code: z.ZodIssueCode.custom,
1703
+ path: [parent, key],
1704
+ message: `${parent}.${key} was removed in 7.0.0 (now ${now}) — ${MIGRATION_SIGNPOST}`,
1705
+ });
1706
+ }
1707
+ }
1708
+ return raw;
1709
+ }
1710
+ /** 一台 worker 的名字(唯一 → 容器名 / compose project / 缺省路由键)—— **唯一**的名字判据。中心在租约 / 用量 /
1711
+ * discovery `?worker=` 等面上校验外部传来的 worker 名时读它(不抄窄正则)。7.0.0 起单独导出:`WorkerSpec` 不再是普通
1712
+ * 对象 schema(预处理 + 跨字段判据),`WorkerSpec.shape.name` 不复存在。 */
1713
+ export const WORKER_NAME = z.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "lowercase letters/digits/-, no spaces");
1714
+ const WorkerSpecObject = z.object({
1583
1715
  /** 唯一 → 容器名 / compose project. */
1584
- name: z.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "lowercase letters/digits/-, no spaces"),
1716
+ name: WORKER_NAME,
1585
1717
  enabled: z.boolean().default(true),
1586
1718
  /** 目标宿主机 id —— 哪个 per-host reconciler 认领它. 与 name 同样严格(精确匹配路由,松校验会静默不可达). */
1587
1719
  host: z.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "lowercase letters/digits/-, no spaces").default("default"),
@@ -1641,17 +1773,12 @@ export const WorkerSpec = z.object({
1641
1773
  // 归宿=roster.primaryModel/cheapModel 标记(0.6.3,service 已承接)。pre-0.7 存量 JSON 里的
1642
1774
  // `model` 键被 parse 静默剥除(zod strips unknown)→ workerSpecHash 变化=一次性重建;迁移工具输入=
1643
1775
  // 冻结的 LegacyWorkerModel(migrate.ts),必须在升级 0.7 前跑完(a2 已闭环,E3)。
1644
- /** 运行时 (非 secret). 映射到 service SESSION_BACKEND/REQUIRE_PRINCIPAL/RATE_LIMIT_RPM/… */
1776
+ /** 运行时 (非 secret). 映射到 service REQUIRE_PRINCIPAL / RATE_LIMIT_RPM / … 与两个结构块(7.0.0)。 */
1645
1777
  runtime: z.object({
1646
- // default "auto" (was "tidb" — Service AI's e2e-tag response traced the minimal-spec fatal here): the old
1647
- // default injected SESSION_BACKEND=tidb into EVERY spec that didn't set it, and the service fail-fasts on
1648
- // explicit tidb without TIDB_HOST (correct Tier-2 hardening — declared persistence must not silently
1649
- // degrade). "auto" = probe: with coords → tidb, without → memory, never fatal. A minimal `runtime:{}`
1650
- // worker now boots. No churn: previously-SAVED specs have "tidb" baked in (parseDomain persists defaults),
1651
- // so only NEW minimal specs see "auto"; explicit-"tidb" loud-fail semantics are untouched.
1652
- sessionBackend: z.enum(["tidb", "memory", "auto"]).default("auto"),
1653
- memoryBackend: z.enum(["off", "memory", "tidb"]).default("off"),
1654
- db: z.string().optional(), // TIDB_DATABASE (独立库)
1778
+ /** 7.0.0 执行车道块(见 `ExecutionBlock`)。enabled 的 worker 必填(WorkerSpec 层判);停用的可缺。 */
1779
+ execution: ExecutionBlock.optional(),
1780
+ /** 7.0.0 存储块(见 `StoreBlock`)。enabled 的 worker 必填(WorkerSpec 层判);停用的可缺。 */
1781
+ store: StoreBlock.optional(),
1655
1782
  scenario: z.string().optional(), // DEFAULT_SCENARIO
1656
1783
  requirePrincipal: z.boolean().default(false),
1657
1784
  rateLimitRpm: z.number().int().nonnegative().default(0),
@@ -1662,13 +1789,10 @@ export const WorkerSpec = z.object({
1662
1789
  * default → absent never materialises → no spec-hash churn. Hash-included (read at boot → cap change recreates). */
1663
1790
  maxPrincipalCostUsd: z.number().finite().nonnegative().optional(),
1664
1791
  }),
1665
- /** 物理坐标 (非 secret). 映射到 GIT_API_BASEURL / TIDB_HOST 等. */
1792
+ /** 物理坐标 (非 secret). 映射到 GIT_API_BASEURL。7.0.0 起 SQL 坐标在 `runtime.store.db`(`tidb*` 三键删除)。 */
1666
1793
  coords: z
1667
1794
  .object({
1668
1795
  gitApiBaseUrl: NON_SECRET_URL.optional().or(z.literal("")),
1669
- tidbHost: z.string().optional(),
1670
- tidbPort: TCP_PORT.optional(),
1671
- tidbUser: z.string().optional(),
1672
1796
  })
1673
1797
  .default({}),
1674
1798
  secretRefs: WorkerSecretRefs,
@@ -1683,7 +1807,7 @@ export const WorkerSpec = z.object({
1683
1807
  .superRefine((rec, ctx) => {
1684
1808
  for (const [k, v] of Object.entries(rec)) {
1685
1809
  if (RESERVED_WORKER_ENV.has(k))
1686
- ctx.addIssue({ code: "custom", message: `env cannot set reserved key "${k}" — set it via ${RESERVED_ENV_FIELD[k] ?? "the structured field"}` });
1810
+ ctx.addIssue({ code: "custom", message: reservedKeyMessage("env", k) });
1687
1811
  // 0.1.32: NO name exemption — a PUBLIC key is allowed because SECRET_LITERAL_RE now only flags PRIVATE keys
1688
1812
  // (root fix), so a literal private key / token in env (ANY name, incl `*_PUBKEY`) is still rejected.
1689
1813
  if (containsSecretToken(v))
@@ -1702,7 +1826,7 @@ export const WorkerSpec = z.object({
1702
1826
  .superRefine((rec, ctx) => {
1703
1827
  for (const k of Object.keys(rec))
1704
1828
  if (RESERVED_WORKER_ENV.has(k))
1705
- ctx.addIssue({ code: "custom", message: `secretEnv cannot set reserved key "${k}" — set it via ${RESERVED_ENV_FIELD[k] ?? "secretRefs"}` });
1829
+ ctx.addIssue({ code: "custom", message: reservedKeyMessage("secretEnv", k) });
1706
1830
  })
1707
1831
  .optional(),
1708
1832
  /** Opaque recreate trigger (hash-INCLUDED): bump it to force the reconciler to recreate the worker — e.g. to
@@ -1739,6 +1863,86 @@ export const WorkerSpec = z.object({
1739
1863
  signature: z.string().optional(),
1740
1864
  notes: z.string().optional(),
1741
1865
  });
1866
+ /** 跨字段判据(G1 / G2 / host∧多副本 / store 四条)。每条一个 issue,`path` 指到要改的字段。 */
1867
+ function workerSpecIssues(w, ctx) {
1868
+ const issue = (path, message) => ctx.addIssue({ code: z.ZodIssueCode.custom, path, message });
1869
+ const { execution, store, requirePrincipal } = w.runtime;
1870
+ // G1:enabled 的 worker 必须声明车道与店(停用的不渲染,可缺)。
1871
+ if (w.enabled && execution === undefined) {
1872
+ issue(["runtime", "execution", "lane"], `an enabled worker must declare its execution lane — runtime.execution.lane: ${EXECUTION_LANE_WORDS.join(" | ")}. ` +
1873
+ `Without one it would boot with no hands and still report tasks completed; there is no "no hands" lane — disable the worker instead`);
1874
+ }
1875
+ if (w.enabled && store === undefined) {
1876
+ issue(["runtime", "store", "backend"], `an enabled worker must declare its store — runtime.store.backend: ${STORE_BACKENDS.join(" | ")}`);
1877
+ }
1878
+ if (execution !== undefined) {
1879
+ // G2:多租户姿态不许跑不分租户的车道;出路按表现算(不手写第五份词表)。
1880
+ if (requirePrincipal && laneRowOf(execution.lane).tenantSeparation === "single-user-only") {
1881
+ const outlets = EXECUTION_LANES.filter((r) => r.tenantSeparation === "supported").map((r) => r.lane);
1882
+ issue(["runtime", "execution", "lane"], `runtime.requirePrincipal=true (multi-tenant posture) with the "${execution.lane}" execution lane runs every tenant's Bash on one shared target — ` +
1883
+ `this lane does not separate tenants; set runtime.execution.lane to ${outlets.join(" / ")}, or set runtime.requirePrincipal=false for a single-user worker`);
1884
+ }
1885
+ // host ∧ 多副本:副本数有两个旋钮(replicas / autoscaling.maxReplicas,二者互斥),一条规则看「最多几个副本」。
1886
+ if (execution.lane === "host") {
1887
+ const [path, replicas] = w.autoscaling !== undefined ? [["autoscaling", "maxReplicas"], w.autoscaling.maxReplicas] : [["replicas"], w.replicas ?? 1];
1888
+ if (replicas > 1) {
1889
+ issue(path, `the "host" execution lane runs tools on each replica's own filesystem — ${replicas} replicas would hold ${replicas} different workspaces behind one route, ` +
1890
+ `so a session's next turn can land where its files are not. Keep a single replica, or choose a lane other than "host"`);
1891
+ }
1892
+ }
1893
+ }
1894
+ // store 四条:① SQL 店 ⇒ db + dbPasswordEnv 在场;② 非 SQL 店 ⇒ 两者都不在场(①② 是一条规则:SQL 坐标 ⇔ SQL 店);
1895
+ // ③ memory.backend 与店相配;④ 多租户 ⇒ SQL 店。
1896
+ const sql = store !== undefined && STORE_IS_SQL[store.backend];
1897
+ // 「店真开出来了没有」(引擎 server 79de35f8 读数):SQL 池只在 SQL 店 ∧ session ≠ memory 时打开(`boot/stores.ts:85` wantDb;
1898
+ // `config.ts:1781` needsTidb —— 本包 SQL 店恒带 `db`,所以 auto 必开);任何店只在 backend ≠ memory 时存在(`plugins/store-backend.ts:927`)。
1899
+ // 读开出来的店的起服不变量,写闸前置同判(同表同词):
1900
+ const sqlPoolOpens = sql && store !== undefined && store.session !== "memory";
1901
+ // device 车道 ⇒ SQL 池必须开(引擎 `config-invariants.ts:318` `device-durable-store`:设备注册簿 / session↔设备绑定 / 审计都在 SQL 里)。
1902
+ // 与 G2 同族(gpt-6 腿 F2)。同族的 `device-durable-approval` 读的是 `DURABLE_APPROVAL`(逃生口里的 env + 姿态缺省),不在结构字段上,
1903
+ // 写闸判不了 —— 那一条只在起服拒。
1904
+ if (execution?.lane === "device" && store !== undefined && !sqlPoolOpens) {
1905
+ if (!sql) {
1906
+ issue(["runtime", "store", "backend"], `the "device" execution lane keeps its device registry, session↔device binding and audit trail in SQL — store.backend "${store.backend}" has none ` +
1907
+ `(the engine refuses to start: device-durable-store); set store.backend to mysql | pg`);
1908
+ }
1909
+ else {
1910
+ issue(["runtime", "store", "session"], `the "device" execution lane needs the SQL store actually built — store.session "memory" builds no SQL coordinates ` +
1911
+ `(the engine refuses to start: device-durable-store); set store.session to auto | mysql`);
1912
+ }
1913
+ }
1914
+ if (store !== undefined) {
1915
+ // ⑧ session mysql 要一个 durable 会话店,memory 后端一只都不建(`plugins/session-store.ts:17`「a DB session backend requires a store backend」)。
1916
+ if (store.session === "mysql" && store.backend === "memory") {
1917
+ issue(["runtime", "store", "session"], `store.session "mysql" asks for a durable session store, but store.backend "memory" builds none (the engine refuses to start: a DB session backend requires a store backend); ` +
1918
+ `set store.session to auto | memory, or store.backend to mysql | pg | local`);
1919
+ }
1920
+ // ⑨ 记忆引擎开 ∧ 方言 pg / tidb ⇒ 引擎绑 SQL 池(`boot/stores.ts:209` / `:232` / `:300`);session memory 时池不开 ⇒ 起服拒。
1921
+ if (store.memory.engine === "on" && MEMORY_BACKEND_NEEDS_STORE[store.memory.backend] !== undefined && sql && !sqlPoolOpens) {
1922
+ issue(["runtime", "store", "session"], `store.memory.backend "${store.memory.backend}" binds the SQL pool, but store.session "memory" never opens it (the engine refuses to start half-configured); ` +
1923
+ `set store.session to auto | mysql, or store.memory.backend to file, or store.memory.engine to off`);
1924
+ }
1925
+ }
1926
+ if (store !== undefined) {
1927
+ if (sql && store.db === undefined)
1928
+ issue(["runtime", "store", "db"], `store.backend "${store.backend}" needs runtime.store.db {host, port, user, database}`);
1929
+ if (!sql && store.db !== undefined)
1930
+ issue(["runtime", "store", "db"], `store.backend "${store.backend}" uses no SQL server — remove runtime.store.db, or set store.backend to mysql | pg`);
1931
+ const needs = MEMORY_BACKEND_NEEDS_STORE[store.memory.backend];
1932
+ if (needs !== undefined && store.backend !== needs) {
1933
+ issue(["runtime", "store", "memory", "backend"], `store.memory.backend "${store.memory.backend}" keeps memory in the ${needs} store — it needs store.backend "${needs}" (got "${store.backend}")`);
1934
+ }
1935
+ if (requirePrincipal && !sql) {
1936
+ issue(["runtime", "store", "backend"], `runtime.requirePrincipal=true (multi-tenant posture) needs a SQL store — store.backend "${store.backend}" has no tenant isolation; set store.backend to mysql | pg`);
1937
+ }
1938
+ }
1939
+ if (sql && w.secretRefs.dbPasswordEnv === undefined)
1940
+ issue(["secretRefs", "dbPasswordEnv"], `a SQL store (runtime.store.backend mysql | pg) needs secretRefs.dbPasswordEnv — the host env-NAME holding the database password`);
1941
+ if (!sql && w.secretRefs.dbPasswordEnv !== undefined)
1942
+ issue(["secretRefs", "dbPasswordEnv"], `secretRefs.dbPasswordEnv is set but there is no SQL store (runtime.store.backend mysql | pg) — remove it`);
1943
+ }
1944
+ /** 一台 worker 的声明。写闸 = `WorkerSpec.safeParse`(7.0.0 删除项读到即拒 + 两个结构块 + 跨字段判据)。 */
1945
+ export const WorkerSpec = z.preprocess(retiredWorkerFieldIssues, WorkerSpecObject.superRefine(workerSpecIssues));
1742
1946
  /** Effective route key for a worker = explicit routePrefix, else its name. */
1743
1947
  export function workerRouteKey(w) {
1744
1948
  return w.routePrefix ?? w.name;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/settings-schema",
3
- "version": "6.4.0",
3
+ "version": "7.0.1",
4
4
  "description": "Sema settings schema — pure config contract (zod domains + effective-config + roster resolution + ref-integrity + remoteExec) shared by sema-registry, sema-server, and the TOC desktop/CLI. One source of truth. (Renamed at 1.0.0 from @sema-agent/registry-core — name and docs only, no behavior change; note the range moves 0.19.0 -> 1.0.0, so a dependency must become ^1.0.0. The old names @sema-agent/registry-core <=0.19.0 and @sema-ai/registry-core remain published, frozen, for migration.)",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -18,6 +18,9 @@
18
18
  ],
19
19
  "main": "./dist/index.js",
20
20
  "types": "./dist/index.d.ts",
21
+ "bin": {
22
+ "settings-schema": "./bin/settings-schema.mjs"
23
+ },
21
24
  "exports": {
22
25
  ".": {
23
26
  "types": "./dist/index.d.ts",
@@ -43,6 +46,10 @@
43
46
  "types": "./dist/secret-refs.d.ts",
44
47
  "default": "./dist/secret-refs.js"
45
48
  },
49
+ "./execution-lanes": {
50
+ "types": "./dist/execution-lanes.d.ts",
51
+ "default": "./dist/execution-lanes.js"
52
+ },
46
53
  "./resolve-roster": {
47
54
  "types": "./dist/resolve-roster.d.ts",
48
55
  "default": "./dist/resolve-roster.js"
@@ -107,6 +114,7 @@
107
114
  },
108
115
  "files": [
109
116
  "dist",
117
+ "bin",
110
118
  "conformance",
111
119
  "README.md",
112
120
  "CHANGELOG.md"