@sema-agent/settings-schema 10.1.0 → 11.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.
- package/CHANGELOG.md +419 -0
- package/README.md +141 -65
- package/bin/settings-schema.mjs +13 -6
- package/conformance/VERSION +1 -1
- package/conformance/baseline.json +3 -1
- package/conformance/cases/c01-entry-salvage-then-typo/expected.json +3 -1
- package/conformance/cases/c02-hosts-clamp-stacked/expected.json +12 -5
- package/conformance/cases/c02-hosts-clamp-stacked/meta.json +1 -1
- package/conformance/cases/c03-literal-dotted-top-key/expected.json +22 -2
- package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +3 -1
- package/conformance/cases/c06-legacy-governance-lift/expected.json +11 -101
- package/conformance/cases/c06-legacy-governance-lift/meta.json +1 -1
- package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +3 -1
- package/conformance/cases/c08-limits-unknown-carried-64/expected.json +203 -2
- package/conformance/cases/c09-limits-unknown-carried-65/expected.json +204 -2
- package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +3 -1
- package/conformance/cases/c11-unicode-unknown-keys/expected.json +20 -2
- package/conformance/cases/c12-prototype-name-top-keys/expected.json +3 -1
- package/conformance/cases/c13-models-field-null/expected.json +3 -1
- package/conformance/cases/c14-models-field-empty-array/expected.json +6 -1
- package/conformance/cases/c15-models-field-number/expected.json +3 -1
- package/conformance/cases/c16-deep-nesting-205/expected.json +14 -2
- package/conformance/cases/c17-empty-domain-file/expected.json +3 -1
- package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +3 -1
- package/conformance/cases/c19-wire-envelope-no-version/expected.json +3 -1
- package/conformance/cases/c20-governance-absent-lifts/expected.json +11 -86
- package/conformance/cases/c20-governance-absent-lifts/meta.json +1 -1
- package/conformance/cases/c21-governance-empty-object-wins/expected.json +6 -5
- package/conformance/cases/c21-governance-empty-object-wins/input.json +1 -4
- package/conformance/cases/c21-governance-empty-object-wins/meta.json +1 -1
- package/conformance/cases/c23-mixed-validity-models/expected.json +3 -1
- package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +22 -2
- package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +6 -1
- package/conformance/cases/c26-hosts-array-1024/expected.json +3 -1
- package/conformance/cases/c27-hosts-array-1025/expected.json +6 -7175
- package/conformance/cases/c27-hosts-array-1025/meta.json +1 -1
- package/conformance/cases/c28-large-document-over-1mb/expected.json +14 -2
- package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +3 -1
- package/conformance/cases/c30-version-triple-a-first/expected.json +6 -1
- package/conformance/cases/c31-version-triple-b-changed/expected.json +6 -1
- package/conformance/cases/c32-version-triple-a-restored/expected.json +6 -1
- package/conformance/cases/c33-limits-nested-carried-key/expected.json +18 -2
- package/conformance/cases/c34-skills-content-vs-manifest/expected.json +19 -3
- package/conformance/cases/c35-portable-domain-file-is-null/expected.json +3 -1
- package/conformance/cases/c36-skills-entry-without-content/expected.json +6 -2
- package/conformance/cases/c37-skills-field-null/expected.json +6 -2
- package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +3 -1
- package/conformance/cases/c39-hidden-file-in-config-d/expected.json +3 -1
- package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +11 -4
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +9 -3
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +39 -2
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +4 -1
- package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +3 -1
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +19 -10
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/meta.json +1 -1
- package/conformance/cases/{c46-s811-host-name-url-userinfo-characterization → c46-s811-host-name-url-userinfo-withheld}/expected.json +12 -5
- package/conformance/cases/c46-s811-host-name-url-userinfo-withheld/meta.json +9 -0
- package/conformance/cases/{c47-s811-roles-key-url-userinfo-characterization → c47-s811-roles-key-url-userinfo-withheld}/expected.json +23 -3
- package/conformance/cases/{c47-s811-roles-key-url-userinfo-characterization → c47-s811-roles-key-url-userinfo-withheld}/input.json +6 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-withheld/meta.json +10 -0
- package/conformance/cases/c48-s935-model-headers-transported/expected.json +6 -1
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +3 -1
- package/conformance/cases/c50-s784-lane-host-legal/expected.json +3 -1
- package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +3 -1
- package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +3 -1
- package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +3 -1
- package/conformance/cases/c54-s784-lane-adb-legal/expected.json +3 -1
- package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +3 -1
- package/conformance/cases/c56-s784-lane-device-legal/expected.json +3 -1
- package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +3 -1
- package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +3 -1
- package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +3 -1
- package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +3 -1
- package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +3 -1
- package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +3 -1
- package/conformance/cases/c63-s784-host-two-replicas/expected.json +3 -1
- package/conformance/cases/c64-s784-multitenant-local-store/expected.json +3 -1
- package/conformance/cases/c65-s784-pg-without-db/expected.json +3 -1
- package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +3 -1
- package/conformance/cases/c67-s784-retired-field-loud/expected.json +3 -1
- package/conformance/cases/c67-s784-retired-field-loud/meta.json +1 -1
- package/conformance/cases/c68-s784-device-local-store/expected.json +3 -1
- package/conformance/cases/c69-deep-encoded-token-past-every-reading/expected.json +3 -1
- package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/expected.json +3 -1
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/expected.json +119 -0
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/input.json +24 -0
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/meta.json +10 -0
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/expected.json +111 -0
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/input.json +26 -0
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/meta.json +6 -0
- package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +3 -1
- package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +3 -1
- package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +6 -1
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +11 -1
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +11 -1
- package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +16 -2
- package/conformance/cases/cli04-env-value-with-newline/expected.json +3 -1
- package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +6 -1
- package/conformance/cases/t01-absent-domain-key-not-null/expected.json +3 -1
- package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +29 -2
- package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +3 -1
- package/conformance/cases/z01-every-domain-empty-object/expected.json +8 -4
- package/conformance/cases/z01-every-domain-empty-object/input.json +0 -1
- package/conformance/cases/z01-every-domain-empty-object/meta.json +1 -1
- package/conformance/cases/z02-every-domain-unknown-key/expected.json +163 -23
- package/conformance/cases/z02-every-domain-unknown-key/input.json +0 -3
- package/conformance/cases/z02-every-domain-unknown-key/meta.json +1 -1
- package/dist/api/auth-bridge.d.ts +51 -181
- package/dist/api/auth-bridge.js +58 -33
- package/dist/api/auth.d.ts +37 -146
- package/dist/api/auth.js +16 -26
- package/dist/api/feedback.d.ts +87 -0
- package/dist/api/feedback.js +66 -0
- package/dist/api/pull-token.d.ts +32 -0
- package/dist/api/pull-token.js +88 -0
- package/dist/api/scopes.d.ts +82 -174
- package/dist/api/scopes.js +23 -18
- package/dist/bundle.d.ts +15 -7
- package/dist/bundle.js +28 -27
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +132 -81
- package/dist/config-d-file.d.ts +14 -0
- package/dist/config-d-file.js +67 -0
- package/dist/config-fns.d.ts +87 -186
- package/dist/config-fns.js +207 -262
- package/dist/conformance/contract-adapter.d.ts +9 -2
- package/dist/conformance/contract-adapter.js +15 -7
- package/dist/conformance/corpus.d.ts +3 -0
- package/dist/conformance/corpus.js +5 -0
- package/dist/conformance/derive.d.ts +3 -0
- package/dist/conformance/index.d.ts +2 -2
- package/dist/conformance/index.js +2 -2
- package/dist/conformance/run.js +67 -22
- package/dist/conformance/types.d.ts +31 -15
- package/dist/conformance/types.js +5 -0
- package/dist/conformance/warnings.d.ts +1 -1
- package/dist/conformance/warnings.js +1 -4
- package/dist/cross-domain.d.ts +66 -22
- package/dist/cross-domain.js +164 -93
- package/dist/cross-session.d.ts +29 -11
- package/dist/default-model.d.ts +43 -0
- package/dist/default-model.js +34 -0
- package/dist/execution-lane-index.d.ts +4 -8
- package/dist/execution-lane-index.js +4 -8
- package/dist/execution-lanes.d.ts +31 -8
- package/dist/execution-lanes.js +23 -5
- package/dist/file-edit.js +5 -2
- package/dist/file-store.d.ts +7 -6
- package/dist/file-store.js +37 -50
- package/dist/fleet.d.ts +364 -762
- package/dist/fleet.js +277 -224
- package/dist/hooks.d.ts +1151 -4737
- package/dist/hooks.js +59 -15
- package/dist/index.d.ts +10 -4
- package/dist/index.js +13 -7
- package/dist/integer.d.ts +11 -0
- package/dist/integer.js +11 -0
- package/dist/issue-path.d.ts +10 -0
- package/dist/issue-path.js +10 -0
- package/dist/loopback.d.ts +26 -0
- package/dist/loopback.js +54 -0
- package/dist/migration-receipt.d.ts +42 -0
- package/dist/migration-receipt.js +12 -0
- package/dist/one-time-migrations.d.ts +56 -0
- package/dist/one-time-migrations.js +819 -0
- package/dist/prompts-digest.d.ts +1 -1
- package/dist/prompts-digest.js +3 -2
- package/dist/reader.d.ts +2 -2
- package/dist/remote-exec.d.ts +45 -187
- package/dist/remote-exec.js +13 -7
- package/dist/resolve-roster.d.ts +18 -3
- package/dist/resolve-roster.js +28 -25
- package/dist/retired-envs.d.ts +24 -0
- package/dist/retired-envs.js +114 -0
- package/dist/retired-form.d.ts +31 -0
- package/dist/retired-form.js +28 -0
- package/dist/safety-merge-spec.d.ts +131 -191
- package/dist/safety-merge-spec.js +18 -14
- package/dist/scheduler-lifetime.d.ts +5 -0
- package/dist/scheduler-lifetime.js +7 -0
- package/dist/scheduler-store-migration.d.ts +14 -0
- package/dist/scheduler-store-migration.js +14 -0
- package/dist/scheduler-store-node.d.ts +24 -9
- package/dist/scheduler-store-node.js +25 -15
- package/dist/scheduler-store.d.ts +47 -14
- package/dist/scheduler-store.js +32 -61
- package/dist/secret-refs.d.ts +3 -3
- package/dist/secret-refs.js +1 -1
- package/dist/secret-scan.d.ts +14 -24
- package/dist/secret-scan.js +45 -23
- package/dist/sha256-core.d.ts +16 -0
- package/dist/sha256-core.js +138 -0
- package/dist/sha256.d.ts +6 -1
- package/dist/sha256.js +8 -98
- package/dist/skills-manifest.js +1 -1
- package/dist/types.d.ts +1501 -13536
- package/dist/types.js +494 -438
- package/dist/url-shape.d.ts +34 -0
- package/dist/url-shape.js +61 -0
- package/dist/web-admin-credential.d.ts +96 -0
- package/dist/{center-credential.js → web-admin-credential.js} +46 -43
- package/dist/web-admin-env.d.ts +33 -0
- package/dist/web-admin-env.js +33 -0
- package/dist/worker-row-verdict.d.ts +16 -0
- package/dist/worker-row-verdict.js +31 -0
- package/dist/workers-rows.d.ts +34 -0
- package/dist/workers-rows.js +42 -0
- package/dist/zod-unwrap.d.ts +11 -0
- package/dist/zod-unwrap.js +25 -0
- package/package.json +17 -9
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/meta.json +0 -6
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/meta.json +0 -6
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/expected.json +0 -49
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/input.json +0 -26
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/meta.json +0 -4
- package/conformance/migrate-workers/m02-g5-idempotent/expected.json +0 -38
- package/conformance/migrate-workers/m02-g5-idempotent/input.json +0 -33
- package/conformance/migrate-workers/m02-g5-idempotent/meta.json +0 -5
- package/conformance/migrate-workers/m03-g5-refused-no-lane/expected.json +0 -71
- package/conformance/migrate-workers/m03-g5-refused-no-lane/input.json +0 -42
- package/conformance/migrate-workers/m03-g5-refused-no-lane/meta.json +0 -4
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/expected.json +0 -52
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/input.json +0 -30
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/meta.json +0 -4
- package/conformance/migrate-workers/m05-row-verdict-reserved-env/expected.json +0 -82
- package/conformance/migrate-workers/m05-row-verdict-reserved-env/input.json +0 -68
- package/conformance/migrate-workers/m05-row-verdict-reserved-env/meta.json +0 -4
- package/dist/center-credential.d.ts +0 -116
- package/dist/issue-text.d.ts +0 -10
- package/dist/issue-text.js +0 -8
- package/dist/migrate-workers.d.ts +0 -71
- package/dist/migrate-workers.js +0 -351
- package/dist/migrate.d.ts +0 -255
- package/dist/migrate.js +0 -542
- /package/conformance/cases/{c46-s811-host-name-url-userinfo-characterization → c46-s811-host-name-url-userinfo-withheld}/input.json +0 -0
package/README.md
CHANGED
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
> deprecate 标指向新名。
|
|
23
23
|
|
|
24
24
|
Engine-agnostic, zero-dependency contract layer for the sema stack — **one source of truth** for the
|
|
25
|
-
config domain across
|
|
25
|
+
config domain across web-admin (the fleet admin product, today `sema-web-admin`), the
|
|
26
26
|
server (the workers), and the client (desktop/CLI) seams. No two copies of the schema.
|
|
27
27
|
|
|
28
|
-
> 命名说明:
|
|
28
|
+
> 命名说明:web-admin 这个**产品**曾叫 `sema-registry`,又改成 `sema-web`,现在是 `sema-web-admin`
|
|
29
29
|
> (clay 令 2026-08-26 仓改名;后续仍可能再改)。本**包**名与产品名解耦,**不随产品改名而动**(1.0.0 这次
|
|
30
|
-
> 改名是本包自身的正名,与产品名无关)—— 下文凡提「
|
|
30
|
+
> 改名是本包自身的正名,与产品名无关)—— 下文凡提「web-admin」即指该产品的当期实现。
|
|
31
31
|
|
|
32
32
|
- **Source of truth**: `github.com/sema-agent/sema-settings-schema`, branch `main` (private org repo; renamed from `sema-registry-core` on 2026-08-26, old URLs redirect).
|
|
33
33
|
仓名本步不动(只改包名)。
|
|
@@ -35,18 +35,49 @@ server (the workers), and the client (desktop/CLI) seams. No two copies of the s
|
|
|
35
35
|
(≤`0.19.0` 发在旧名 `@sema-agent/registry-core` 下,冻结)。
|
|
36
36
|
|
|
37
37
|
The package is **pure**: zero `next`/`react`/server imports, zero DB drivers. Its only runtime dependency is
|
|
38
|
-
`zod` (a `dependency` pinned `^
|
|
38
|
+
`zod` (a `dependency` pinned `^4.4.0`, also declared as a `peerDependency` with the same range). `npm ls --prod`
|
|
39
39
|
therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
40
40
|
|
|
41
|
-
> **zod
|
|
42
|
-
>
|
|
43
|
-
>
|
|
44
|
-
> package manager installs this package's
|
|
45
|
-
>
|
|
46
|
-
>
|
|
47
|
-
>
|
|
48
|
-
>
|
|
49
|
-
|
|
41
|
+
> **One zod instance in a zod-4 host** (11.0.0). The range is `^4.4.0` for both the dependency and the peer, so a host on
|
|
42
|
+
> zod 4 (`sema-server`, the CLI, the client core, the SDK) dedups this package's zod with its own: one instance, and schema objects
|
|
43
|
+
> may cross the seam. A host still on zod 3 (until it upgrades — today the web-admin product, `sema-web-admin`) does not
|
|
44
|
+
> satisfy the peer range, and its package manager installs this package's zod 4 NESTED under it: two instances there, so
|
|
45
|
+
> cross the seam with plain data only (parse with the schemas / functions this package exports; never `instanceof ZodError`
|
|
46
|
+
> across it, never compose a host schema with one of ours). Before 11.0.0 the picture was the mirror image (this package on
|
|
47
|
+
> zod 3, nested inside every zod-4 host). The move to zod 4 did not change what any schema accepts or produces —
|
|
48
|
+
> the conformance corpus reads byte-identically on both — only the wording of validation errors (see CHANGELOG 11.0.0).
|
|
49
|
+
|
|
50
|
+
## 升级指引:10.x → 11.0.0 · 持久面:一次性迁移(`./one-time-migrations`)
|
|
51
|
+
|
|
52
|
+
11.0.0 把读路径上为旧数据留的兼容臂全部收掉:旧形只有一种去处 —— **一次性迁移**把它改写成新形(你写回),读写两面遇旧形**响亮拒**,
|
|
53
|
+
拒句点名迁移 id。没迁的存量会被拒,所以**先迁再读**。
|
|
54
|
+
|
|
55
|
+
- **谁要跑**:持有存量配置的一方 —— 管理面起服时对每个 scope 的草稿、每个 principal 层(个人 models / mcp / skills / plugins)、每份冻结的
|
|
56
|
+
发布快照与回滚史各跑一次;引擎对自己的 last-known-good 文件跑 `migrateEffectiveSnapshot`;本地 `config.d` 目录跑命令行。
|
|
57
|
+
- **怎么调**(纯函数,不读环境、不读墙钟、不改入参、不抬任何版本号 —— 版本号归你):
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { migrateStoredDocuments, migrateEffectiveSnapshot, ONE_TIME_MIGRATIONS } from "@sema-agent/settings-schema/one-time-migrations";
|
|
61
|
+
|
|
62
|
+
// 一个 scope 的全部存储文档(域名 → 文档);缺席的域 = 从未设过。
|
|
63
|
+
const { docs, receipts } = migrateStoredDocuments(scopeDocs, { coverage: "scope" });
|
|
64
|
+
// principal 层 / 回滚史的单域行 / 任意子集:缺席的域 = 没给、不动。
|
|
65
|
+
migrateStoredDocuments({ models: personalModels, mcp: personalMcp }, { coverage: "partial" });
|
|
66
|
+
// 冻结的 effective(发布快照、引擎的 LKG):version / updatedAt 与其余信封键原样。
|
|
67
|
+
const { effective } = migrateEffectiveSnapshot(frozen);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`docs` 里没改的文档是**同一引用**、被移除的(退役的 `runtime` 域)不在、迁出的新文档(`governance` / `limits`)在 —— 据此写回 / 删除。
|
|
71
|
+
`receipts` 逐条说「对哪个坐标做了什么」(`moved` / `rewritten` / `dropped-shadowed` / `dropped-inert` / `refused`;不带值)。
|
|
72
|
+
`refused` 的坐标原样留着,读侧照旧拒:要人改。再跑一次 = 零改动。
|
|
73
|
+
- **目录形**:`npx @sema-agent/settings-schema migrate <config.d> [--check]`(同一只函数;目录当一个 scope;有 refused 一字不写、退出 1;
|
|
74
|
+
`--check` 有改动退出 3)。
|
|
75
|
+
- **迁什么**(七条,`ONE_TIME_MIGRATIONS` 每行写着改写规则、删法、删后由谁接住):退役 `runtime` 域(治理三闸 → `governance`、五个限额键 → `limits`)·
|
|
76
|
+
hosts 超界 · 调度店 durable 键(读时改写,下一个写者落盘)· workers 6.x 行 · 模型钥匙双设 · `atModelAllowlist` → `atMention` · URL fragment。
|
|
77
|
+
- **什么时候删**:每条的窗 = 11.0.0 发布日 + 30 天,窗关之后的那个 major 整族删(删法与删后的响亮臂见登记表那一行);本包的到期门到点即红。
|
|
78
|
+
删了之后还带旧形的存量,由墓碑照旧拒 —— 墓碑是拒,不会再被读成任何值。
|
|
79
|
+
- **回滚史里单独一行 `runtime`**:迁不了(没有同一 scope 的 governance / limits 可判),回滚到它会被墓碑拒 —— 不可恢复。
|
|
80
|
+
- **不要混跑**:迁移期间别让 10.x 与 11.x 的管理面副本同时在线(10.x 副本会照旧写出旧形,11.x 副本读它会拒)。
|
|
50
81
|
|
|
51
82
|
## 升级指引:9.0.0 → 10.0.0(BREAKING:写入校验认串中任意位置的私钥 PEM 头;读侧同套 schema ⇒ 存量私钥串的条目被丢 / 域 throw;导出面零变化)
|
|
52
83
|
|
|
@@ -58,7 +89,7 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
58
89
|
## 升级指引:8.3.1 → 9.0.0(BREAKING:两件,硬 breaking —— 删改直接做,零别名)
|
|
59
90
|
|
|
60
91
|
**① 能力读数改三态读器**:`readCenterCapabilities(body)` 删,换 `readCapabilityReading(reply): CapabilityReading`(类型 `CapabilityReading` 同入)。输入改成心跳 **2xx 的整只答复体**(键查找进读器),
|
|
61
|
-
答三态:`{ kind: "declared"; capabilities }`(`capabilities` 键在场且过 schema)· `{ kind: "undeclared" }`(体是对象、键缺席 =
|
|
92
|
+
答三态:`{ kind: "declared"; capabilities }`(`capabilities` 键在场且过 schema)· `{ kind: "undeclared" }`(体是对象、键缺席 = 老 web-admin)·
|
|
62
93
|
`{ kind: "unreadable"; why: "not-an-object" | "invalid-capabilities" }`(体不是对象 / 键在场但 schema 拒)。**只有 `declared` / `undeclared` 是读数;`unreadable` 不是读数 —— 引擎保留上一次状态与钟。**
|
|
63
94
|
|
|
64
95
|
- **谁需要**:引擎侧的租约客户端(server)—— 围栏这条轴上「形坏」不许等于「缺席」:以前 `capabilities` 键在场但形坏读成 `undefined` = 「没宣告」,引擎据此撤围栏。
|
|
@@ -114,11 +145,11 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
114
145
|
- `EngineSignal`(+ `EngineDraining`)与 `readEngineSignal(health)` —— 引擎自铸信号的**闭形**(`ready` 必填,`draining?` / `restart?` / `generation?` / `instanceId?` /
|
|
115
146
|
`leaseUntil?` / `configTargetVersion?` / `configAppliedVersion?` 可选);与既有读器走同一只受保护读口。⚠️ 8.2.0 订正:今天的心跳与 `/health` 都不整只
|
|
116
147
|
承载它(心跳恒带布尔 `draining`、`/health` 就绪时不带 `ready`、世代账两键是数),`readEngineSignal` 对今天的心跳体恒 `undefined` —— 见 8.2.0 升级指引(9.0.0 起这两键在 `EngineSignal` 里就是数,不再是读不出的原因)。
|
|
117
|
-
`undefined` = 没有这只信号,**不是就绪**;布尔 `draining` 的旧方言体读不出它(
|
|
148
|
+
`undefined` = 没有这只信号,**不是就绪**;布尔 `draining` 的旧方言体读不出它(两个就绪谓词当时的输入仍是布尔方言;11.0.0 起它们只收 `EngineSignal`,信号整只在 `engineSignal` 键下)。
|
|
118
149
|
- 代际 —— `generation` 是非负整数的**规范十进制串**(`"7"`,不是 `"07"`、不是数字型),判别器 `isCanonicalGeneration`、数值比较 `compareGeneration`;三处 wire 形共用这一条判据。
|
|
119
150
|
- 服务租约 API 型 —— `ServingLeaseRequest`、`ServingLeaseResponse`(`granted { ttlMs }` / `denied { reason: superseded | retired }` / `unknown` 三词闭集,
|
|
120
151
|
**`unknown` 不是 `denied` 的一种**,`denied` 只凭正面证据;读器按 `verdict` 键判,`denied` 的原因词读不出时仍是 `denied`,原因读成 `unknown_reason`)、`SERVING_LEASE_TTL_DEFAULT_MS` / `SERVING_LEASE_TTL_FLOOR_MS` / `SERVING_LEASE_DRIFT_MARGIN_MS`、读器 `readServingLeaseRequest` / `readServingLeaseResponse`;
|
|
121
|
-
|
|
152
|
+
web-admin 能力位 `CenterCapabilities { servingLease?: true }` + `readCenterCapabilities`(9.0.0 起删,由 `readCapabilityReading` 取代,见 9.0.0 升级指引)—— 租约客户端只在位在场时启用,缺席 = web-admin 不支持租约(不续租、不围栏)。
|
|
122
153
|
- 排空口型 —— `DrainRequest`、`DrainReceipt`(`state: draining | already_draining`)、读器 `readDrainRequest` / `readDrainReceipt`。
|
|
123
154
|
- `FLEET_GENERATION_ENV` —— 配置渲染器与引擎共用的 generation env 名常量(实例身份 `instanceId` 由引擎每进程自铸,没有 env 名);它同时进了 `RESERVED_WORKER_ENV`,`worker.env` / `secretEnv` 设不了它。
|
|
124
155
|
- `clampServingLeaseTtlMs(n)` —— 配置的租约 TTL 的唯一夹取点(读不出 ⇒ 缺省 600 000;低于地板 ⇒ 60 000;无上限)。
|
|
@@ -140,8 +171,9 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
140
171
|
|
|
141
172
|
## What's in it
|
|
142
173
|
|
|
143
|
-
- **The zod contract** (`types.ts`): `DOMAIN_SCHEMAS` (
|
|
144
|
-
identity ledger; 0.10.18 adds `prompts`; 0.19.0 adds `limits`, the cost/rate-limit publication slot
|
|
174
|
+
- **The zod contract** (`types.ts`): `DOMAIN_SCHEMAS` (16 domains — 0.10.0 adds `projects`, the project
|
|
175
|
+
identity ledger; 0.10.18 adds `prompts`; 0.19.0 adds `limits`, the cost/rate-limit publication slot; 11.0.0 retires
|
|
176
|
+
`runtime` — its tombstone is `RETIRED_DOMAINS`, see "One-time migrations" below),
|
|
145
177
|
`EffectiveConfig`, `EffectiveWire`,
|
|
146
178
|
`ENV_NAME`/`K8S_QUANTITY` boundaries, every domain's entry/config type.
|
|
147
179
|
- **Pure config fns** (`config-fns.ts`): `buildEffective` (deterministic — `updatedAt` is a required param),
|
|
@@ -150,24 +182,28 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
150
182
|
shapes (`User`, `PublishedSnapshot`, …).
|
|
151
183
|
- **Roster resolution** (`resolve-roster.ts`): `resolveEffectiveForWorker` → the per-worker wire payload.
|
|
152
184
|
- **Ref integrity** (`cross-domain.ts`): `refIntegrityIssues` + `createSiblingResolver` (the pure half — fleet
|
|
153
|
-
auto-placement stays in the
|
|
185
|
+
auto-placement stays in the web-admin 产品). The ref-checked domains are a RUNTIME closed set —
|
|
154
186
|
`REF_CHECKED_DOMAINS` + `isRefCheckedDomain` (`RefCheckedDomain` is derived from the value) — so a consumer
|
|
155
187
|
asks the contract instead of hand-copying the list; every `RefIssue.path` starts with the domain key.
|
|
156
188
|
- **Hashing** (`hash.ts`): `stableHash` (FNV-1a 64) for ETags / version detection.
|
|
157
189
|
- **Skills manifest** (`skills-manifest.ts`): content-addressing for the lazy-pull skill bodies.
|
|
158
190
|
- **Secret inventory** (`secret-refs.ts`): `collectEnvRefs` — every referenced env-NAME.
|
|
159
|
-
- **The narrow reader** (`reader.ts`): `ConfigReader` (3 methods) — the
|
|
191
|
+
- **The narrow reader** (`reader.ts`): `ConfigReader` (3 methods) — the web-admin 产品's fat `ConfigStore`
|
|
160
192
|
`extends` it; a local file source / test fixture implements it without stubbing ~32 methods.
|
|
161
193
|
- **`remoteExec` contract** (`remote-exec.ts`): the per-machine `config.d/remote-exec.json` execution-substrate
|
|
162
194
|
schema (`host` / `local-docker` / `e2b` / `k8s` / `ssh` / `adb`), discriminated on `provider`; every secret an
|
|
163
195
|
env-NAME ref. Every `provider` word is an execution-lane word (`EXECUTION_LANE_WORDS`, pinned at runtime and at
|
|
164
196
|
compile time). **7.0.0** deleted the `remote-docker` variant: no engine read point ever ran it; a remote docker
|
|
165
|
-
daemon is the `local-docker` lane pointed at it (`DOCKER_HOST`).
|
|
197
|
+
daemon is the `local-docker` lane pointed at it (`DOCKER_HOST`). **11.0.0**: retired lane VALUES have a tombstone,
|
|
198
|
+
`RETIRED_LANE_WORDS` (`[old, to, how]`, permanent, one hop); a worker spec's `runtime.execution.lane` or this file's
|
|
199
|
+
`provider` naming one is refused with the way out (`retiredLaneWordRefusal(word)` is the one sentence; the issue code
|
|
200
|
+
stays `invalid_value` / `invalid_union`).
|
|
166
201
|
- **WorkerSpec execution lane + store (7.0.0, M-R2)** (`execution-lanes.ts`, also subpath `./execution-lanes`, + `types.ts`): the lane / store
|
|
167
202
|
vocabulary has ONE source — the exported data `EXECUTION_LANE_WORDS`, `EXECUTION_LANES` (per lane:
|
|
168
|
-
`tenantSeparation`
|
|
203
|
+
`tenantSeparation` — and, on a `supported` lane only, `tenantSeparationBasis`: `isolation` | `principal-binding`, a
|
|
204
|
+
discriminated union (11.0.0), so a rule reads WHY a lane separates tenants instead of naming lanes — + the env names it reads, in four columns `requiredEnv` / `requiredSecretEnv` / `optionalEnv` /
|
|
169
205
|
`optionalSecretEnv`, copied from the engine with per-row coordinates), `STORE_BACKENDS`, `SESSION_BACKENDS`,
|
|
170
|
-
`MEMORY_ENGINE_BACKENDS`.
|
|
206
|
+
`MEMORY_ENGINE_BACKENDS`. Web-admin's write gate (this package's `WorkerSpec.safeParse`), web-admin's env
|
|
171
207
|
renderer and the engine's boot checks all read that one table, so they cannot drift. A worker declares
|
|
172
208
|
`runtime.execution = { lane, coords, secretRefs }` (non-secret coordinates as literals, secrets as host env-NAMEs,
|
|
173
209
|
keys ⊆ that lane's columns, required ones present and non-empty) and `runtime.store = { backend, session, memory,
|
|
@@ -244,40 +280,68 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
244
280
|
and the pure client helpers `normalizeUserCode` / `nextPollInterval`. Shipped M1 shapes frozen.
|
|
245
281
|
OpenAPI mirror: `docs/openapi-auth.yaml` (auth face only).
|
|
246
282
|
- **Auth bridge** (`api/auth-bridge.ts`, subpath `./api/auth-bridge`): the 鉴权桥 contract —
|
|
247
|
-
how a worker verifies a
|
|
248
|
-
kid-miss forced refresh + ≥60s throttle, exp/nbf clock skew (60s), 24h offline grace then fail-closed;
|
|
283
|
+
how a worker verifies a web-admin-minted user JWT: RS256-only, JWKS from `/api/auth/sso/jwks` with
|
|
284
|
+
kid-miss forced refresh + ≥60s throttle, exp/nbf/iat clock skew (60s), 24h offline grace then fail-closed;
|
|
249
285
|
`VerifierConfig` / `VerifiedIdentity` / `AuthBridgeErrorCode` + pure decision fns
|
|
250
286
|
(`shouldForceJwksRefresh`, `jwksWithinOfflineGrace`, `classifyTimeClaims`, `toVerifiedIdentity`).
|
|
251
287
|
The normative rules R1–R10 in its header are the service verifier's implementation spec.
|
|
252
|
-
`VerifiedIdentity` carries an optional `scope` (the token's active tenant scope — see `api/scopes`)
|
|
253
|
-
-
|
|
254
|
-
|
|
288
|
+
`VerifiedIdentity` carries an optional `scope` (the token's active tenant scope — see `api/scopes`) and an optional `iat`
|
|
289
|
+
(issued-at, integer seconds, verbatim — absent claim ⇒ no key; not a non-negative integer ⇒ the token is refused).
|
|
290
|
+
- **web-admin credential file + connection env names** (`web-admin-credential.ts`, main barrel; S-698): the file behind
|
|
291
|
+
`SEMA_WEB_ADMIN_TOKEN_FILE` is one of two KINDS — a bare line (one static token — the same kind of file as today:
|
|
255
292
|
its first line, printable ASCII with no control / non-ASCII characters; user JWT vs worker pull token stays the reader's `looksLikeJwt` call) or a
|
|
256
|
-
`
|
|
293
|
+
`WebAdminCredentialEnvelope` (strict JSON: `kind: "user-session"` = `WEB_ADMIN_CREDENTIAL_ENVELOPE_KIND`, a JWT
|
|
257
294
|
`accessToken` and a `refreshToken` under the same token law as the bare line, RFC 3339
|
|
258
|
-
`expiresAt` with seconds and a `Z` / `±hh:mm` offset in range, `issuer` = the issuing
|
|
295
|
+
`expiresAt` with seconds and a `Z` / `±hh:mm` offset in range, `issuer` = the issuing web-admin's base URL judged
|
|
259
296
|
by the same `NON_SECRET_URL` the `remoteExec` endpoints use — same-origin is the reader's comparison).
|
|
260
|
-
`
|
|
297
|
+
`parseWebAdminCredentialFile(text)` is the ONE discrimination law — trimmed text starting with `{` is read ONLY
|
|
261
298
|
as an envelope (a failure is `unreadable`, never a fallback to a bare line); anything else is a bare line,
|
|
262
299
|
and a first line that is not a token is `unreadable` too (never sent as a bearer); empty is `unreadable`. The `reason` names envelope fields / zod issue codes / an unknown-key COUNT, never file
|
|
263
300
|
content. Pure: reading, atomically writing and renewing the file are the consumers' job. S-742 lifts the
|
|
264
|
-
DECLARATION face here too: `
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
301
|
+
DECLARATION face here too: `WEB_ADMIN_ENV` — every env name an engine reads to reach web-admin, one table
|
|
302
|
+
(`url` / `token` / `tokenFile` / `worker` / `liveUrl` / `webUrl` / `dryRun` / `adminToken` / `assertionToken` /
|
|
303
|
+
`assertionTokenFile`, values `SEMA_WEB_ADMIN_*`; one spelling for web-admin rendering worker env, the shell writing its
|
|
304
|
+
local engine's env, and the server reading it) and `judgeWebAdminCredentialDeclaration(env, credential)` — `credential` ∈
|
|
305
|
+
`WEB_ADMIN_CREDENTIALS` (`access` reads `token` / `tokenFile`, `assertion` reads `assertionToken` / `assertionTokenFile`;
|
|
306
|
+
no default) → `env` / `file` / `absent`, the one pick-exactly-one rule for every credential: an empty string is unset, BOTH set (non-empty) throws (the refusal
|
|
268
307
|
names the two keys, never a value), the file path comes back verbatim (resolving a relative one against a cwd
|
|
269
308
|
is the reader's job).
|
|
309
|
+
- **Retired env names** (`retired-envs.ts`, main barrel): `RETIRED_ENVS` — the ONE tombstone table (literal tuples
|
|
310
|
+
`[old, to, "renamed"]` / `[old, to, "replaced", why]` / `[old, why, "removed"]`, a closed set of three families,
|
|
311
|
+
frozen). A tombstone is permanent: the old name present ⇒ refuse to start, whether or not the replacement is also
|
|
312
|
+
set (an empty string is unset); a chain is one hop (every row points at a current name). `retiredEnvRefusal(env)` is
|
|
313
|
+
the one refusal sentence (code `RETIRED_ENV_REFUSAL_CODE` = `config.env_key_retired`; names keys, never values;
|
|
314
|
+
pure — pass the env you are judging); `buildRetiredEnvWire()` is the catalog projection (`retired[]`). The engine's
|
|
315
|
+
boot, the shell starting a local engine and web-admin rendering worker env all judge this one table.
|
|
270
316
|
- **Multi-tenant scopes** (`api/scopes.ts`, subpath `./api/scopes`): the scope contract — the `Scope`
|
|
271
317
|
entity (`{id,name,createdAt}`, reserved default `"global"` always exists), per-scope `ScopeMember`
|
|
272
|
-
rows (`{scopeId,principal,role}` on the existing viewer<editor<publisher<admin ladder), the
|
|
273
|
-
|
|
274
|
-
scope-bound), and the wire shapes for `GET/POST /api/v1/scopes`, `POST /api/v1/auth/scope`
|
|
318
|
+
rows (`{scopeId,principal,role}` on the existing viewer<editor<publisher<admin ladder), the JWT
|
|
319
|
+
`scope` claim (every minted access token carries it; 11.0.0: a token without it is refused, not read as `"global"`;
|
|
320
|
+
refresh tokens are never scope-bound), and the wire shapes for `GET/POST /api/v1/scopes`, `POST /api/v1/auth/scope`
|
|
275
321
|
(scope-switch mint, OAuth-style errors) and `PUT/DELETE /api/v1/scopes/{id}/members`. The DECISION
|
|
276
|
-
SEMANTICS live here as pure functions
|
|
322
|
+
SEMANTICS live here as pure functions web-admin imports (never re-implements):
|
|
277
323
|
`resolveTokenScope` (mint-time scope binding: 1 membership → it; several incl. global → global;
|
|
278
324
|
several without global → lexicographically first; none → `{scope:"global", role:instanceRole}`),
|
|
279
325
|
`canSwitchScope` (member or instance admin), `effectiveScopeRole` (instance admin is `"admin"` in
|
|
280
|
-
every scope) and `
|
|
326
|
+
every scope) and `readTokenScope` (a set claim ⇒ `{ scope }`; a missing / empty / non-string claim ⇒ `{ refused:
|
|
327
|
+
"scope_claim_missing" }` — 11.0.0: no longer read as `"global"`). OpenAPI mirror: `docs/openapi-scopes.yaml`.
|
|
328
|
+
- **Scope-bound tokens** (`api/pull-token.ts`, subpath `./api/pull-token`): the FORM and minting rule of the per-worker pull
|
|
329
|
+
token web-admin mints for every worker (`WEB_ADMIN_ENV.token`) — one rule, token families are data
|
|
330
|
+
(`ScopedTokenFamily = { v1Prefix, v2Prefix, macTag }`; `WORKER_PULL_TOKEN` = `wpt_` / `wpt2_` / `worker-pull`). Default scope ⇒
|
|
331
|
+
v1 `<v1Prefix><worker>_<mac>`, any other scope ⇒ v2 `<v2Prefix><scope>_<worker>_<mac>`, `mac` = HMAC-SHA256(shared key,
|
|
332
|
+
`<macTag>:v1:<worker>` / `<macTag>:v2:<scope>:<worker>`) in lowercase hex (pure-JS HMAC, byte-identical to `node:crypto`).
|
|
333
|
+
`mintScopedToken` throws instead of minting a token that could never verify (empty key, a scope that is not a scope id, a
|
|
334
|
+
worker name that is empty or carries the `_` frame separator); `readScopedTokenBinding` reads what a token CLAIMS;
|
|
335
|
+
`verifyScopedToken` = claim → re-mint → constant-time compare (a v2 token claiming the default scope never verifies).
|
|
336
|
+
The engine only carries the token as an opaque bearer.
|
|
337
|
+
- **Principal length** (`api/auth-bridge.ts`): `PRINCIPAL_MAX_LENGTH` = 190 — the engine's principal gate refuses a longer
|
|
338
|
+
one; validate / mint principals against this value.
|
|
339
|
+
- **/bug reports** (`api/feedback.ts`, subpath `./api/feedback`): `FeedbackSubmitRequest` (the POST body the shell sends to
|
|
340
|
+
`FEEDBACK_V1_PATH`; `description` trimmed, non-empty, ≤ `FEEDBACK_DESCRIPTION_MAX_CHARS`; `transcript` serialized ≤
|
|
341
|
+
`FEEDBACK_TRANSCRIPT_MAX_JSON_CHARS`; `principal` is never a body field — it is the token subject), `FeedbackWire` (one
|
|
342
|
+
report row, server-stamped `createdAt`) and `FeedbackListResponse`.
|
|
343
|
+
- **Content digest form** (`sha256.ts`): `Sha256Digest` / `SHA256_DIGEST_RE` — the one `sha256:<64 lowercase hex>` spelling every
|
|
344
|
+
digest-bearing seat in this package uses.
|
|
281
345
|
- **Local file source** (`file-store.ts`, `file-edit.ts`): `FileConfigStore` reads `config.d/*.json` →
|
|
282
346
|
`EffectiveConfig`; `validateDomain` / `writeDomainFile` / `listRequiredEnvNames` are the TOC's
|
|
283
347
|
read/write/validate helpers. `getEffective({tolerant:true})` (0.10.12) isolates a malformed
|
|
@@ -288,7 +352,8 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
288
352
|
and any top-level key a domain schema strips are both NAMED through `onWarning`
|
|
289
353
|
(`unread-config-file` / `unknown-keys-dropped`) instead of vanishing silently (**6.3.0**: every warning coordinate —
|
|
290
354
|
unknown keys included — leaves through `redactSecrets`, so a secret-shaped key segment reads `[redacted-secret]`;
|
|
291
|
-
|
|
355
|
+
the unknown-key scans behind those warnings are module-private since 11.0.0 — `parseDomainLoud` is the one public
|
|
356
|
+
primitive). **2.0.0** adds per-ENTRY
|
|
292
357
|
tolerance on the same read path: one malformed catalog entry is dropped on its own (named through
|
|
293
358
|
`onWarning` as `entry-dropped`) and its siblings survive, instead of the whole domain falling to
|
|
294
359
|
its schema default. It is the SAME `DOMAIN_READ_FALLBACK` table that decides where this applies — gate
|
|
@@ -340,15 +405,15 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
340
405
|
(forward-compatible: unknown fields survive a round-trip, malformed records are dropped one by one).
|
|
341
406
|
- **Fleet contract** (`fleet.ts`, subpath `./fleet`): drain protocol, instance discovery, dispatch policy,
|
|
342
407
|
quota lease, usage report, quota-reset event. **S-741** adds the restart signal, defined ONCE:
|
|
343
|
-
`RestartSignal` (`{ reasons: string[] (≥1, non-empty words), version: number (the
|
|
408
|
+
`RestartSignal` (`{ reasons: string[] (≥1, non-empty words), version: number (the web-admin config version that
|
|
344
409
|
differs from boot — finite, judged exactly as `readEffectiveWire` judges a config version), since: number (epoch
|
|
345
410
|
ms, non-negative integer) }` — exactly what the server already ships on /health). The heartbeat carries it as
|
|
346
|
-
`InstanceAnnouncement.restart?` (so `InstanceRegistration` carries it too); on /health
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
411
|
+
`InstanceAnnouncement.restart?` (so `InstanceRegistration` carries it too); on /health it rode untyped
|
|
412
|
+
(passthrough, next to the redundant top-level `restartRequired`). Presence is the signal (present ⇔ restart required;
|
|
413
|
+
no `required` boolean inside). A heartbeat re-announces the full row, so one without it means "no longer required".
|
|
414
|
+
Since 11.0.0 it is engine state and is read in ONE place on both exits — inside `EngineSignal` under `engineSignal`
|
|
415
|
+
(`readEngineSignal(body)?.restart`); the flat `restart` both bodies still carry is the pre-11 dialect, read by nothing,
|
|
416
|
+
and the flat reader `readRestartSignal` is gone. **S-768** adds the model-refusals signal the same
|
|
352
417
|
way (one mint point, two exits, ONE reader): the wire face `ModelRefusalsWire` (`{ entries: { <catalog entry name>:
|
|
353
418
|
<code> }, plane?: <code> }` with codes as open strings — name → code is the whole wire, no upstream id / provider /
|
|
354
419
|
field keys / prose) rides the heartbeat as `InstanceAnnouncement.modelRefusals?` and /health (`InstanceHealth` again
|
|
@@ -364,30 +429,40 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
364
429
|
rule the dropped-entry identity on read warnings already uses: the `DISCLOSABLE_IDENTITY_RE` allowlist ∧ not
|
|
365
430
|
secret-shaped, 6.3.0). **8.1.0** (additive) puts the engine's own state in one closed shape: `RestartSignal` gains the
|
|
366
431
|
optional `withheld` ("the engine says this beat must not be handed to a new generation"; absent ≡ false);
|
|
367
|
-
`EngineSignal` (`{ ready, draining?: EngineDraining, restart?, generation?,
|
|
368
|
-
configTargetVersion?, configAppliedVersion? }
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
`
|
|
372
|
-
|
|
432
|
+
`EngineSignal` (`{ ready, draining?: EngineDraining, restart?, generation?, leaseState?, leaseUntil?,
|
|
433
|
+
configTargetVersion?, configAppliedVersion? }`; 11.0.0 took `instanceId` out — the instance's identity is the body's top
|
|
434
|
+
level) is the closed form of what the engine mints about itself. **11.0.0**: it rides
|
|
435
|
+
BOTH exits (`/health` and the heartbeat) WHOLE under the one key `ENGINE_SIGNAL_KEY` (`"engineSignal"`) — nested, because the
|
|
436
|
+
boolean `draining` dialect is still sent in its contraction window and one key cannot hold a boolean and an object;
|
|
437
|
+
`readEngineSignal(body)` reads that key only (same guarded reader as `readModelRefusals`; `undefined` =
|
|
438
|
+
"no such signal", never "ready"), both shapes type it (`InstanceAnnouncement.engineSignal` / `InstanceHealth.engineSignal`, a
|
|
439
|
+
malformed one is absent),
|
|
440
|
+
and the judges read nothing else: `judgeReadiness(signal)` / `judgeRoutability(row, nowMs)` → `{ routable: true }` |
|
|
441
|
+
`{ routable: false, code, why }` (closed set `NOT_ROUTABLE_CODES`: no-engine-signal / stale / observer-not-ready / draining /
|
|
442
|
+
not-ready), with `isReadyForTraffic` / `isRoutable` / `routableInstances` as their boolean forms. A row or body without the
|
|
443
|
+
signal — an engine that predates it — is `no-engine-signal`, never routable. One fact, one place: engine state only in the
|
|
444
|
+
signal, the row's identity (`instanceId`, `worker`, `address`, `version`, `heartbeatMs`) only at the top level; the flat state
|
|
445
|
+
keys both bodies still carry (`ready`, the boolean `draining` / `drainingSince`, `restart`, `generation`, `leaseState`,
|
|
446
|
+
`leaseUntil`) are the pre-11 dialect — shape unchanged, `@deprecated`, read by nothing here; their removal is registered in
|
|
447
|
+
`ONE_TIME_MIGRATIONS`. The serving-lease wire (`ServingLeaseRequest` / `ServingLeaseResponse`: `granted { ttlMs }` | `denied { reason:
|
|
373
448
|
superseded | retired }` | `unknown` — a closed three-word set in which `unknown` is NOT a kind of `denied`; plus
|
|
374
449
|
`SERVING_LEASE_TTL_DEFAULT_MS` 600 000, `SERVING_LEASE_TTL_FLOOR_MS` 60 000 and `SERVING_LEASE_DRIFT_MARGIN_MS`, derived
|
|
375
|
-
from `DEFAULT_HEARTBEAT_MS`; `denied` rests on positive evidence only), the
|
|
376
|
-
(`servingLease?: true` — absent means
|
|
450
|
+
from `DEFAULT_HEARTBEAT_MS`; `denied` rests on positive evidence only), the web-admin capability bit `WebAdminCapabilities`
|
|
451
|
+
(`servingLease?: true` — absent means web-admin has no lease, so the engine neither renews nor is fenced), the drain
|
|
377
452
|
endpoint's `DrainRequest` / `DrainReceipt`, one reader per wire shape, and the env name `FLEET_GENERATION_ENV` are defined
|
|
378
453
|
here once for the renderer and the engine (an instance's id is minted by the engine per process — it has no env name).
|
|
379
454
|
**8.2.0** (additive) adds the engine's lease vocabulary `LEASE_STATES` (`none | granted | expired | denied`) as
|
|
380
455
|
`EngineSignal.leaseState?`, keeps `generation` / `leaseState` / `leaseUntil` on the heartbeat (`InstanceAnnouncement` no longer
|
|
381
|
-
strips them; a malformed one drops to absent, the heartbeat still lands), spells
|
|
456
|
+
strips them; a malformed one drops to absent, the heartbeat still lands), spells web-admin's fleet endpoints once —
|
|
382
457
|
`FLEET_PATHS` (heartbeat, usage, quota leases, serving lease `/api/fleet/live/serving-lease`, host planner
|
|
383
458
|
`/api/fleet/live/host-planner/{host}`) and the liveness group `FLEET_LIVE_PATHS` (heartbeat, serving lease, host planner), also
|
|
384
459
|
shipped as `@sema-agent/settings-schema/fleet-paths.json` for tooling that cannot import TypeScript — and names the one carrier of
|
|
385
|
-
|
|
460
|
+
web-admin's capability bits: `HEARTBEAT_REPLY_CAPABILITIES_KEY` (`"capabilities"`) on the heartbeat's 2xx reply.
|
|
386
461
|
**8.3.0** (additive) adds the env name `FLEET_SCOPE_ENV` (`"FLEET_SCOPE"`) — the tenant scope a fleet member belongs to, set by the
|
|
387
462
|
config renderer and read by the engine as its own identity's scope (absent ⇒ `GLOBAL_SCOPE`); a reserved worker env name.
|
|
388
463
|
**9.0.0** (BREAKING) replaces `readCenterCapabilities` with the three-state reader `readCapabilityReading(reply)` (`declared` |
|
|
389
464
|
`undeclared` | `unreadable` — a malformed declaration is not an absent one) and types `EngineSignal`'s two config-version keys as numbers.
|
|
390
|
-
- **Declared-but-unvalidated keys** (`PARSE_TRANSPARENT_KEYS`, 2.0.0): the ONE
|
|
465
|
+
- **Declared-but-unvalidated keys** (`PARSE_TRANSPARENT_KEYS`, 2.0.0): the ONE web-admin of keys this package
|
|
391
466
|
carries verbatim WITHOUT checking their shape, each row naming who does check it (the engine's ingest side or
|
|
392
467
|
a consumer's enforcement point). Today: `models[].compat` (1.10.0), `models[].headers` (6.4.0; the header-name
|
|
393
468
|
allowlist × lane is sema-server's, not restated here), `mcp.servers[].source` (1.7.2), `mcp.servers[].toolFaces`
|
|
@@ -402,9 +477,10 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
402
477
|
## Cross-repo conformance gate (`./conformance`, 3.0.0)
|
|
403
478
|
|
|
404
479
|
契约仓自带**金样语料 + 比对器**,随包发(`conformance/cases/**`)。每个消费方把**自己的生产入口**塞给
|
|
405
|
-
kit 跑一遍 ⇒ 同一份 settings
|
|
480
|
+
kit 跑一遍 ⇒ 同一份 settings 语料在 web-admin、worker、管理面、契约仓自身读出的 effective 逐字节一致、
|
|
406
481
|
告警多重集一致;写入席对同一份文档收不收也一致。**四条腿**(8.0.0):`domains`(各域原始文档 → effective)、
|
|
407
|
-
`strictWrite`(同一份文档 → 严写受理;契约仓 = `
|
|
482
|
+
`strictWrite`(同一份文档 → 严写受理;契约仓 = `parseDomainLoud` + 跨域引用完整性,任一份不受理 ⇒ `rejected{validate}`,悬空引用 ⇒
|
|
483
|
+
`rejected{validate, ref-integrity}` 带 `refIssues` 坐标,读腿抢救处严写拒;受理时的剥键 / 承运告警进告警面)、
|
|
408
484
|
`wire`、`localRoot`。
|
|
409
485
|
|
|
410
486
|
```ts
|
|
@@ -465,15 +541,15 @@ defaults. Each `config.d/<domain>.json` is exactly that domain's zod object.
|
|
|
465
541
|
codex 复审持「严格 semver 应升 major」异议,原样留档候裁)。
|
|
466
542
|
- **文案 / 注释 / 测试 / bug 修复 = patch**:不改任何 wire 形、类型面、默认值与校验结果。行为面的
|
|
467
543
|
「修 bug」若改变了合法输入的解析结果,按上两档判,不按 patch。
|
|
468
|
-
- 契约破坏面动之前先盘
|
|
544
|
+
- 契约破坏面动之前先盘 web-admin / server / CLI 三方消费点;commit message 的 `feat!`/`BREAKING` 标记照旧,
|
|
469
545
|
但标记不豁免版号义务。每版 CHANGELOG 一节,git tag `v<version>` 同名。
|
|
470
546
|
- **持久数据面的破坏性变更(例:7.0.0)**:类型 / API 面直接删改、零别名(消费方 tsc 红即通知);**存下来的数据**不会自己红,
|
|
471
547
|
所以同版配三件:读到旧形即**响亮拒**(拒因指路迁移命令,不是被 zod 静默剥掉)、一只**一次性迁移**(纯函数、幂等、
|
|
472
|
-
表达不了就 refused 点名,不替运维猜)、以及一个**写明的删除窗**(7.0.0 的 `migrateWorkersDomain`
|
|
548
|
+
表达不了就 refused 点名,不替运维猜)、以及一个**写明的删除窗**(7.0.0 的 `migrateWorkersDomain` 在 web-admin 接上迁移后的下一版删除)——
|
|
473
549
|
绝不长成长命双读。
|
|
474
550
|
- **历史注记(0.x 时代)**:0.x 下 `^0.7.0` 这类 range 只自动收 patch,故当年规矩是「破坏性必 bump
|
|
475
551
|
minor,patch 只装 additive/修 bug/文档」。0.7.1(2026-07-10)曾携带两件预协调 BREAKING(撤 governance
|
|
476
|
-
effective 双写镜像、撤 QuotaLease 0.5 `tokens` 别名 shim),名义上违反该条;当时下游(
|
|
552
|
+
effective 双写镜像、撤 QuotaLease 0.5 `tokens` 别名 shim),名义上违反该条;当时下游(web-admin/service
|
|
477
553
|
1.150.0)均已显式迁移、实际破坏面为零,PM 拍板不重发 0.8.0(纯扰动)。该例外随 1.x 政策一并归档。
|
|
478
554
|
- 发布(单腿 npmjs,内网腿已废止):`npm run publish:npm`;发后必须 curl 直打 npmjs 复验 shasum。
|
|
479
555
|
|
|
@@ -482,4 +558,4 @@ defaults. Each `config.d/<domain>.json` is exactly that domain's zod object.
|
|
|
482
558
|
- 真源 = `sema-agent/sema-settings-schema` org 仓 `main`(2026-08-26 由 sema-registry-core 改名),所有变更直接落 org main(或 PR 进 main);
|
|
483
559
|
不再向任何个人位/镜像位 push。
|
|
484
560
|
- 提交门 = `npm run build` + `npx vitest run`;改域/契约后必跑 `npm run e2e`(examples/toc-e2e.mjs)。
|
|
485
|
-
- 契约破坏面动之前先盘
|
|
561
|
+
- 契约破坏面动之前先盘 web-admin / server / CLI 三方消费点;破坏性变更必 bump major(见上节)。
|
package/bin/settings-schema.mjs
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// `settings-schema` — this package's command line (
|
|
2
|
+
// `settings-schema` — this package's command line (11.0.0: `migrate <config.d directory> [--check]`, the one-time migrations).
|
|
3
3
|
// The ONLY place the package writes files with node:fs; the logic is `runSettingsSchemaCli` (dist/cli.js), which never
|
|
4
4
|
// touches the filesystem itself — this entry injects real file I/O into it.
|
|
5
|
-
import { readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import { readdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
6
6
|
import { runSettingsSchemaCli } from "../dist/cli.js";
|
|
7
7
|
|
|
8
8
|
const io = {
|
|
9
|
+
readDir: (path) => readdir(path),
|
|
9
10
|
readFile: (path) => readFile(path, "utf8"),
|
|
10
|
-
// atomic: write a sibling temp file (same mode as the original), then rename over it — a half-written
|
|
11
|
-
// document is worse than the old one.
|
|
11
|
+
// atomic: write a sibling temp file (same mode as the original when there is one), then rename over it — a half-written
|
|
12
|
+
// domain document is worse than the old one.
|
|
12
13
|
writeFileAtomic: async (path, text) => {
|
|
13
|
-
|
|
14
|
+
let mode = 0o600;
|
|
15
|
+
try {
|
|
16
|
+
mode = (await stat(path)).mode & 0o777;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
if (e?.code !== "ENOENT") throw e;
|
|
19
|
+
}
|
|
14
20
|
const tmp = `${path}.migrate-${process.pid}.tmp`;
|
|
15
|
-
await writeFile(tmp, text, { encoding: "utf8", mode
|
|
21
|
+
await writeFile(tmp, text, { encoding: "utf8", mode });
|
|
16
22
|
await rename(tmp, path);
|
|
17
23
|
},
|
|
24
|
+
removeFile: (path) => unlink(path),
|
|
18
25
|
stdout: (text) => process.stdout.write(text),
|
|
19
26
|
stderr: (text) => process.stderr.write(text),
|
|
20
27
|
};
|
package/conformance/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
11.0.0
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"servers": []
|
|
24
24
|
},
|
|
25
25
|
"models": {
|
|
26
|
+
"atMention": {
|
|
27
|
+
"mode": "all"
|
|
28
|
+
},
|
|
26
29
|
"models": [],
|
|
27
30
|
"roles": {},
|
|
28
31
|
"tierGroups": []
|
|
@@ -39,7 +42,6 @@
|
|
|
39
42
|
"rosters": {
|
|
40
43
|
"rosters": []
|
|
41
44
|
},
|
|
42
|
-
"runtime": {},
|
|
43
45
|
"scenarios": {
|
|
44
46
|
"scenarios": []
|
|
45
47
|
},
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
"servers": []
|
|
27
27
|
},
|
|
28
28
|
"models": {
|
|
29
|
+
"atMention": {
|
|
30
|
+
"mode": "all"
|
|
31
|
+
},
|
|
29
32
|
"models": [
|
|
30
33
|
{
|
|
31
34
|
"api": "openai-completions",
|
|
@@ -55,7 +58,6 @@
|
|
|
55
58
|
"rosters": {
|
|
56
59
|
"rosters": []
|
|
57
60
|
},
|
|
58
|
-
"runtime": {},
|
|
59
61
|
"scenarios": {
|
|
60
62
|
"scenarios": []
|
|
61
63
|
},
|
|
@@ -34,6 +34,9 @@
|
|
|
34
34
|
"servers": []
|
|
35
35
|
},
|
|
36
36
|
"models": {
|
|
37
|
+
"atMention": {
|
|
38
|
+
"mode": "all"
|
|
39
|
+
},
|
|
37
40
|
"models": [],
|
|
38
41
|
"roles": {},
|
|
39
42
|
"tierGroups": []
|
|
@@ -50,7 +53,6 @@
|
|
|
50
53
|
"rosters": {
|
|
51
54
|
"rosters": []
|
|
52
55
|
},
|
|
53
|
-
"runtime": {},
|
|
54
56
|
"scenarios": {
|
|
55
57
|
"scenarios": []
|
|
56
58
|
},
|
|
@@ -67,10 +69,15 @@
|
|
|
67
69
|
"warnings": [
|
|
68
70
|
{
|
|
69
71
|
"domain": "hosts",
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
+
"identity": {
|
|
73
|
+
"name": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
74
|
+
},
|
|
75
|
+
"kind": "entry-dropped",
|
|
76
|
+
"path": [
|
|
77
|
+
"hosts",
|
|
78
|
+
0
|
|
72
79
|
],
|
|
73
|
-
"
|
|
80
|
+
"reasonCode": "entry-schema-invalid"
|
|
74
81
|
},
|
|
75
82
|
{
|
|
76
83
|
"domain": "hosts",
|
|
@@ -93,7 +100,7 @@
|
|
|
93
100
|
"strictWrite": {
|
|
94
101
|
"outcome": "rejected",
|
|
95
102
|
"stage": "validate",
|
|
96
|
-
"why": "
|
|
103
|
+
"why": "拒:严写按现行 schema 判 —— 名长 > 63 与名字形不合法都拒;读腿逐条抢救(entry-dropped)。读写同一只 schema(11.0.0 起读侧不再夹紧)。"
|
|
97
104
|
},
|
|
98
105
|
"wire": {
|
|
99
106
|
"derive": true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"contributor": "contract",
|
|
3
|
-
"claim": "
|
|
3
|
+
"claim": "读路径上只剩一个压缩点(逐条抢救;11.0.0 起 hosts 的读侧夹紧臂退役,超长名那一条也由它剔)。超长名与形不合法的条目都作 entry-dropped 报原文档坐标;同文档里的拼错键回写到原下标。少回写一次,告警就指向别人的记录。",
|
|
4
4
|
"firstRedOn": [
|
|
5
5
|
"settings-schema@1.0.0",
|
|
6
6
|
"settings-schema@1.10.0",
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
"servers": []
|
|
27
27
|
},
|
|
28
28
|
"models": {
|
|
29
|
+
"atMention": {
|
|
30
|
+
"mode": "all"
|
|
31
|
+
},
|
|
29
32
|
"models": [
|
|
30
33
|
{
|
|
31
34
|
"api": "openai-completions",
|
|
@@ -67,7 +70,6 @@
|
|
|
67
70
|
"rosters": {
|
|
68
71
|
"rosters": []
|
|
69
72
|
},
|
|
70
|
-
"runtime": {},
|
|
71
73
|
"scenarios": {
|
|
72
74
|
"scenarios": []
|
|
73
75
|
},
|
|
@@ -106,6 +108,9 @@
|
|
|
106
108
|
"outcome": "effective",
|
|
107
109
|
"effective": {
|
|
108
110
|
"models": {
|
|
111
|
+
"atMention": {
|
|
112
|
+
"mode": "all"
|
|
113
|
+
},
|
|
109
114
|
"models": [
|
|
110
115
|
{
|
|
111
116
|
"api": "openai-completions",
|
|
@@ -136,7 +141,22 @@
|
|
|
136
141
|
"tierGroups": []
|
|
137
142
|
}
|
|
138
143
|
},
|
|
139
|
-
"warnings": [
|
|
144
|
+
"warnings": [
|
|
145
|
+
{
|
|
146
|
+
"domain": "models",
|
|
147
|
+
"keys": [
|
|
148
|
+
[
|
|
149
|
+
"models",
|
|
150
|
+
1,
|
|
151
|
+
"k"
|
|
152
|
+
],
|
|
153
|
+
[
|
|
154
|
+
"models.1.k"
|
|
155
|
+
]
|
|
156
|
+
],
|
|
157
|
+
"kind": "unknown-keys-dropped"
|
|
158
|
+
}
|
|
159
|
+
],
|
|
140
160
|
"metadata": {
|
|
141
161
|
"version": 7,
|
|
142
162
|
"updatedAt": "2026-09-16T00:00:00.000Z"
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
"servers": []
|
|
27
27
|
},
|
|
28
28
|
"models": {
|
|
29
|
+
"atMention": {
|
|
30
|
+
"mode": "all"
|
|
31
|
+
},
|
|
29
32
|
"models": [],
|
|
30
33
|
"roles": {},
|
|
31
34
|
"tierGroups": []
|
|
@@ -42,7 +45,6 @@
|
|
|
42
45
|
"rosters": {
|
|
43
46
|
"rosters": []
|
|
44
47
|
},
|
|
45
|
-
"runtime": {},
|
|
46
48
|
"scenarios": {
|
|
47
49
|
"scenarios": []
|
|
48
50
|
},
|