@sema-agent/settings-schema 13.0.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +108 -0
- package/README.md +20 -2
- package/conformance/VERSION +1 -1
- package/conformance/baseline.json +1 -3
- package/conformance/cases/c01-entry-salvage-then-typo/expected.json +1 -3
- package/conformance/cases/c02-hosts-clamp-stacked/expected.json +1 -3
- package/conformance/cases/c03-literal-dotted-top-key/expected.json +1 -3
- package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +1 -3
- package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +1 -3
- package/conformance/cases/c08-limits-unknown-carried-64/expected.json +1 -3
- package/conformance/cases/c09-limits-unknown-carried-65/expected.json +1 -3
- package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +1 -3
- package/conformance/cases/c11-unicode-unknown-keys/expected.json +1 -3
- package/conformance/cases/c12-prototype-name-top-keys/expected.json +1 -3
- package/conformance/cases/c13-models-field-null/expected.json +1 -3
- package/conformance/cases/c14-models-field-empty-array/expected.json +1 -3
- package/conformance/cases/c15-models-field-number/expected.json +1 -3
- package/conformance/cases/c16-deep-nesting-205/expected.json +1 -3
- package/conformance/cases/c17-empty-domain-file/expected.json +1 -3
- package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +1 -3
- package/conformance/cases/c19-wire-envelope-no-version/expected.json +1 -3
- package/conformance/cases/c21-governance-empty-object-wins/expected.json +1 -3
- package/conformance/cases/c23-mixed-validity-models/expected.json +1 -3
- package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +1 -3
- package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +1 -3
- package/conformance/cases/c26-hosts-array-1024/expected.json +1 -3
- package/conformance/cases/c27-hosts-array-1025/expected.json +1 -3
- package/conformance/cases/c28-large-document-over-1mb/expected.json +1 -3
- package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +1 -3
- package/conformance/cases/c30-version-triple-a-first/expected.json +1 -3
- package/conformance/cases/c31-version-triple-b-changed/expected.json +1 -3
- package/conformance/cases/c32-version-triple-a-restored/expected.json +1 -3
- package/conformance/cases/c33-limits-nested-carried-key/expected.json +1 -3
- package/conformance/cases/c34-skills-content-vs-manifest/expected.json +1 -3
- package/conformance/cases/c35-portable-domain-file-is-null/expected.json +1 -3
- package/conformance/cases/c36-skills-entry-without-content/expected.json +1 -3
- package/conformance/cases/c37-skills-field-null/expected.json +1 -3
- package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +1 -3
- package/conformance/cases/c39-hidden-file-in-config-d/expected.json +1 -3
- package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +1 -3
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +1 -3
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +1 -3
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +1 -3
- package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +1 -3
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +1 -3
- package/conformance/cases/c46-s811-host-name-url-userinfo-withheld/expected.json +1 -3
- package/conformance/cases/c47-s811-roles-key-url-userinfo-withheld/expected.json +1 -3
- package/conformance/cases/c48-s935-model-headers-transported/expected.json +1 -3
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +1 -3
- package/conformance/cases/c50-s784-lane-host-legal/expected.json +1 -3
- package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +1 -3
- package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +1 -3
- package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +1 -3
- package/conformance/cases/c54-s784-lane-adb-legal/expected.json +1 -3
- package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +1 -3
- package/conformance/cases/c56-s784-lane-device-legal/expected.json +1 -3
- package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +1 -3
- package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +1 -3
- package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +1 -3
- package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +1 -3
- package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +1 -3
- package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +1 -3
- package/conformance/cases/c63-s784-host-two-replicas/expected.json +1 -3
- package/conformance/cases/c64-s784-multitenant-local-store/expected.json +1 -3
- package/conformance/cases/c65-s784-pg-without-db/expected.json +1 -3
- package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +1 -3
- package/conformance/cases/c67-s784-retired-field-loud/expected.json +1 -3
- package/conformance/cases/c68-s784-device-local-store/expected.json +1 -3
- package/conformance/cases/c69-deep-encoded-token-past-every-reading/expected.json +1 -3
- package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/expected.json +1 -3
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/expected.json +1 -3
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/expected.json +1 -3
- package/conformance/cases/c73-projects-extended-registration-reads/expected.json +1 -3
- package/conformance/cases/c74-projects-over-bound-refused/expected.json +1 -3
- package/conformance/cases/c75-projects-patch-remote-uniqueness/expected.json +1 -3
- package/conformance/cases/c76-projects-entry-salvage-nested/expected.json +1 -3
- package/conformance/cases/c77-prompts-center-artifact-carried/expected.json +252 -0
- package/conformance/cases/c77-prompts-center-artifact-carried/input.json +93 -0
- package/conformance/cases/c77-prompts-center-artifact-carried/meta.json +5 -0
- package/conformance/cases/c78-prompts-empty-bindings-refused/expected.json +82 -0
- package/conformance/cases/c78-prompts-empty-bindings-refused/input.json +11 -0
- package/conformance/cases/c78-prompts-empty-bindings-refused/meta.json +5 -0
- package/conformance/cases/c79-prompts-bad-worker-artifact-not-salvaged/expected.json +82 -0
- package/conformance/cases/c79-prompts-bad-worker-artifact-not-salvaged/input.json +129 -0
- package/conformance/cases/c79-prompts-bad-worker-artifact-not-salvaged/meta.json +5 -0
- package/conformance/cases/c80-prompts-wire-v2-verbatim/expected.json +195 -0
- package/conformance/cases/c80-prompts-wire-v2-verbatim/input.json +65 -0
- package/conformance/cases/c80-prompts-wire-v2-verbatim/meta.json +5 -0
- package/conformance/cases/c81-prompts-declaration-bindings-refused/expected.json +82 -0
- package/conformance/cases/c81-prompts-declaration-bindings-refused/input.json +23 -0
- package/conformance/cases/c81-prompts-declaration-bindings-refused/meta.json +5 -0
- package/conformance/cases/c82-prompts-v1-catalog-refused/expected.json +82 -0
- package/conformance/cases/c82-prompts-v1-catalog-refused/input.json +35 -0
- package/conformance/cases/c82-prompts-v1-catalog-refused/meta.json +5 -0
- package/conformance/cases/c83-prompts-bad-overlay-not-salvaged/expected.json +82 -0
- package/conformance/cases/c83-prompts-bad-overlay-not-salvaged/input.json +56 -0
- package/conformance/cases/c83-prompts-bad-overlay-not-salvaged/meta.json +5 -0
- package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +1 -3
- package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +1 -3
- package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +1 -3
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +1 -3
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +1 -3
- package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +1 -3
- package/conformance/cases/cli04-env-value-with-newline/expected.json +1 -3
- package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +1 -3
- package/conformance/cases/t01-absent-domain-key-not-null/expected.json +1 -3
- package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +1 -3
- package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +1 -3
- package/conformance/cases/z01-every-domain-empty-object/expected.json +2 -6
- package/conformance/cases/z02-every-domain-unknown-key/expected.json +2 -6
- package/dist/config-fns.d.ts +7 -3
- package/dist/config-fns.js +19 -16
- package/dist/conformance/derive.js +2 -2
- package/dist/hooks-config.d.ts +9 -1
- package/dist/hooks-config.js +23 -15
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp-request-entry.d.ts +93 -0
- package/dist/mcp-request-entry.js +157 -0
- package/dist/migration-receipt.d.ts +1 -1
- package/dist/migration-receipt.js +1 -1
- package/dist/node.d.ts +0 -1
- package/dist/node.js +0 -1
- package/dist/one-time-migrations.d.ts +1 -1
- package/dist/one-time-migrations.js +39 -0
- package/dist/safety-merge-spec.d.ts +6 -6
- package/dist/types.d.ts +438 -355
- package/dist/types.js +161 -136
- package/dist/url-shape.d.ts +8 -0
- package/dist/url-shape.js +10 -0
- package/docs/API-SURFACE.md +1409 -0
- package/docs/CC-DIVERGENCE.md +42 -0
- package/docs/CONFORMANCE.md +344 -0
- package/docs/CONSUMER-INTEGRATION.md +283 -0
- package/docs/openapi-auth.yaml +357 -0
- package/docs/openapi-scopes.yaml +281 -0
- package/llms-full.txt +659 -0
- package/llms.txt +179 -0
- package/package.json +5 -2
- package/dist/prompts-digest.d.ts +0 -16
- package/dist/prompts-digest.js +0 -49
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# CONSUMER-INTEGRATION — 怎么消费 `@sema-agent/settings-schema`
|
|
2
|
+
|
|
3
|
+
**受众:** sema-server(引擎)、agent-client(CLI/desktop)、未来 TOC desktop。
|
|
4
|
+
**定位:** 这是消费这个包的**单一真值**,把跨仓频道里零散的接入答疑一次性固化。**接入前读这一份,不必再追问。** owner = web-admin。
|
|
5
|
+
|
|
6
|
+
> **本档订正到 3.0.0**(此前停在改名定版前的开发代号一节,2.0.0/2.1.0/3.0.0 三版未跟)。逐条改动见
|
|
7
|
+
> S-570 收车档;3.0.0 之后的下一次真实改动,请同一批把这份文档也带上,别再停摆。**7.0.0**(M-R2 WorkerSpec 结构块)的
|
|
8
|
+
> 三方迁移句在 §0.2,`remoteExec` 删 `remote-docker` 在 §5。**8.0.0**(一致性门第四条腿 + 表态律 + CLI `--check`)在 §0.3。
|
|
9
|
+
> **11.0.0** 的持久面(`runtime` 域退役、读侧兼容臂退役、一次性迁移族 `./one-time-migrations`)在 §0.4。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 0. 一个心智模型
|
|
14
|
+
|
|
15
|
+
- **一份契约+resolver,两个源**(sema-web-admin HTTP `/effective` | 本地文件)→ **同一个 `EffectiveConfig`** → fleet 与单机不可能 drift。
|
|
16
|
+
- **契约里只有 env-NAME ref,绝无 secret VALUE**。值住在 `.env`/`process.env`(+ `file:` ref)。这个包永不携带密钥。
|
|
17
|
+
- **纯 barrel 浏览器安全**(主入口);碰 `node:fs` 的本地 helper 在 `./node` 子路径。
|
|
18
|
+
- 验证你的接入是否对:跑 `examples/toc-e2e.mjs`(可独立复跑的参考闭环);要做**跨仓**一致性核验(你的生产读腿 vs 本包自身、vs 别的消费方)用 `./conformance` kit,见 §11。
|
|
19
|
+
|
|
20
|
+
### 0.1 3.0.0 BREAKING 速览(接线时最容易踩的三点)
|
|
21
|
+
|
|
22
|
+
- **告警坐标改铸结构化段 `ConfigPath`**:`EffectiveReadWarning`/丢弃条目的 `path`/`keys` 字段不再是拼接好的字符串,是 `readonly (string | number)[]`(见 §1 表、§2)。要出门(日志/UI)显示,唯一铸点是 `formatConfigPath(path)` —— 别自己拼字符串,拼法以后可能变。
|
|
23
|
+
- **`./api/wire` 已删**:成案理由「让零依赖客户端运行期直取常量」从未兑现,四仓对现包名零 import;常量已经回到 `./api/auth` / `./api/scopes` 的契约原处。还在 import `./api/wire` 的旧码要迁到那两条。
|
|
24
|
+
- **新增 `./conformance`**:跨仓一致性门 kit,见 §11 / `docs/CONFORMANCE.md`。
|
|
25
|
+
|
|
26
|
+
### 0.2 7.0.0 BREAKING 速览(M-R2:执行车道 + 存储两个结构块)
|
|
27
|
+
|
|
28
|
+
> 11.0.0 起本节的迁移入口已换:`migrateWorkersDomain` 与 `migrate-workers` 子命令删了 —— 6.x 行的改写进一次性迁移 SS-M4
|
|
29
|
+
> (`migrateStoredDocuments`,见 §0.4),逐行终判单立成永久读语义 `readWorkersRows`;下面的规则原文保留作 7.0.0 的读数。
|
|
30
|
+
|
|
31
|
+
- **规则一句话**:车道与店各有**一个**结构位(`WorkerSpec.runtime.execution` / `runtime.store`),词表只有**一个**正本(主桶导出的
|
|
32
|
+
`EXECUTION_LANES` 等六个常量),写闸 = `WorkerSpec.safeParse`(拒因就在 zod issue 里:`path` 指到字段、`message` 带出路,
|
|
33
|
+
不另导出拒因函数)。`env` / `secretEnv` 逃生口从此设不了车道与店(`RESERVED_WORKER_ENV` 加词)。写闸前置同判引擎读车道的起服不变量里
|
|
34
|
+
结构字段能表达的那几条(多租户 × 不分租户车道、host × 多副本、device × 非 SQL 店);读 `DURABLE_APPROVAL` 的 `device-durable-approval`
|
|
35
|
+
不在结构字段上,只在起服拒。
|
|
36
|
+
- **删除项 ⇒ tsc 红即通知**:`runtime.sessionBackend` / `runtime.memoryBackend` / `runtime.db` / `coords.tidbHost|tidbPort|tidbUser` /
|
|
37
|
+
`secretRefs.tidbPasswordEnv` 类型上消失;数据里出现 ⇒ 读到即拒(拒因指路迁移,不被静默剥)。零别名、零双读。
|
|
38
|
+
- **存量数据 ⇒ 跑一次迁移**:在 workers 域的**唯一读口**、`parseDomain` / `DOMAIN_SCHEMAS.workers.safeParse` **之前**对**原始 JSON** 跑
|
|
39
|
+
`migrateWorkersDomain(raw) → { doc, changed, dropped, refused }`(纯函数、幂等:已迁形再跑零变化、`doc` 同一引用)。终判**逐行**
|
|
40
|
+
(8.2.0 起迁过的行与没动的行同一只):每一只认得出名字的 worker 都过读侧自己的判据 —— 只含这一行的 workers 域读不读得进(`WorkersConfig`)
|
|
41
|
+
—— 读不进就进 `refused`,所以 `doc` 里每一行单独都读得进;整域 `parse` 仍可能拒的只剩两类:认不出名字的条目(不是对象 / 没有字符串
|
|
42
|
+
名字,迁移原样留下)与跨行规则(名字 / 路由前缀唯一)。`dropped` = 引擎本就不读的旧字段(通知一行);`refused` = 迁不了或逐行读不进的
|
|
43
|
+
worker(**不在 `doc` 里**;`field` 点名要人改的格,`original` = 输入里那一行逐字)。🔴 **web-admin 写回 `doc` 之前必须把每个 `refused[].original` 按 `index` 放回(或另存),否则运维的 spec
|
|
44
|
+
随写回从存储里消失**;`doc` ∪ `refused[].original` 覆盖输入的全部 worker。文件后端 / 离线:`npx @sema-agent/settings-schema migrate-workers
|
|
45
|
+
<workers.json> [--check]`(有 refused 一字不写、退出 1;`--check` 有改动退出 3)。迁移窗口有删除期(CHANGELOG 7.0.0「迁移代码删除窗」)。
|
|
46
|
+
- **CLI 报告形(7.0.1)**:stdout 恰两行 —— 第 1 行**唯一结论行** `outcome=<refused|applied|noop|pending> written=<true|false>`,第 2 行 JSON
|
|
47
|
+
(`event` / `outcome` / `written` / `file` / 名单 / `refused[]`;**解析取最后一行**)。只有 `applied` 写盘(`written` 由 `outcome` 派生;
|
|
48
|
+
`pending` = `--check` 有改动)。名单的键名带时态:`changed` / `dropped` **仅在 `written=true` 时存在**;没写(refused / noop / pending)时是
|
|
49
|
+
`wouldChange` / `wouldDrop` —— 只解析 stdout 的运维脚本读不出「某只 worker 已迁」。旧字段读法的后果:7.0.0 的 `changed` 恒在,
|
|
50
|
+
7.0.1 起未写盘时它**不再出现**(读到 undefined,而不是一份没落盘的「已迁」名单);要判「写没写」读 `written` 或结论行,不要靠退出码之外的猜测。
|
|
51
|
+
- **拒因里的迁移指路(7.0.1)**:一句话一个常量(`MIGRATION_SIGNPOST`,命令恒为 `npx @sema-agent/settings-schema migrate-workers <file>`)。
|
|
52
|
+
凡「旧形 ⇒ 迁移能搬」的拒因带它 —— 七个被删旧键,以及 `env` / `secretEnv` 里迁移会搬 / 丢的键(车道键含 `REMOTE_EXEC` /
|
|
53
|
+
`REQUIRE_EXECUTION_LANE`、店键 `DB_BACKEND` / `MEMORY_ENGINE` / `MEMORY_ENGINE_BACKEND`);迁移搬不了的保留名(`PORT` / `MODEL_*` /
|
|
54
|
+
`MYSQL_*` …)不带。拒因句尾的「set it via <归属字段>」不变,读它抽归属字段的下游不受影响。
|
|
55
|
+
- **三方各自要做的**:
|
|
56
|
+
- **web-admin / 编排器(sema-web-admin)**:渲染器按两块改真 —— `REMOTE_EXEC=<lane>` + `REQUIRE_EXECUTION_LANE=true` + `coords` 逐键
|
|
57
|
+
+ `secretRefs` 逐键(服务变量名 → 宿主 env-NAME);`DB_BACKEND` / `SESSION_BACKEND` / `MEMORY_ENGINE` / `MEMORY_ENGINE_BACKEND` 恒渲;
|
|
58
|
+
按 `store.backend` 选 `MYSQL_*` 或 `PG_*` 名族(`HOST/PORT/USER/DATABASE` ← `store.db`,`*_PASSWORD` ← `secretRefs.dbPasswordEnv`);
|
|
59
|
+
`MEMORY_BACKEND` 不再产出。workers 读口接迁移;批量轮换已在本包 `workersReferencingSecret` 里看得见 `runtime.execution.secretRefs`。
|
|
60
|
+
`WorkerSpec` 不再是 `ZodObject`:读 `WorkerSpec.shape.name` 的地方改 `WORKER_NAME`(同一只名字判据);编排器读 `runtime.db` 的
|
|
61
|
+
改 `runtime.store.db.database`;新建向导要建出两块(`runtime: {}` 过不了写闸)。执行域 `allowedLanes`(开放词)的标签表:可声明车道取
|
|
62
|
+
`EXECUTION_LANE_WORDS`;`in-process` 是引擎「没有 remoteExec」的状态名,不在可声明表里,要留就单独写明来源。
|
|
63
|
+
- **server(引擎)**:车道词 / 必需键 / `tenantSeparation` 从 `EXECUTION_LANES` 派生(运行期事实留在按导入联合穷举的 switch);
|
|
64
|
+
`DB_BACKEND` / `SESSION_BACKEND` / `MEMORY_ENGINE_BACKEND` 的闭集读 `STORE_BACKENDS` / `SESSION_BACKENDS` / `MEMORY_ENGINE_BACKENDS`。
|
|
65
|
+
引擎不 import `WorkerSpec`,tsc 不会替它红 —— 派生是提货车的显式动作。
|
|
66
|
+
- **cli / desktop**:不读 `WorkerSpec`,零改动;本机 `config.d/remote-exec.json` 若手写了 `remote-docker` 会被拒(§5)。
|
|
67
|
+
|
|
68
|
+
### 0.3 8.0.0 BREAKING 速览(一致性门第四条腿 `strictWrite` + 每条腿都要表态 + CLI `--check`)
|
|
69
|
+
|
|
70
|
+
- **规则一句话**:kit 有四条腿(`domains` / `strictWrite` / `wire` / `localRoot`),每个消费方对**每条**腿表态 —— 实现它,或
|
|
71
|
+
`{ declined: "no-production-path", why: "<你仓的事实>" }`。不表态不是一种表态(缺 = release 违例,不再「静默跳过、默认当通过」)。
|
|
72
|
+
- **`strictWrite`(严写受理)**:入参与 `domains` 同一份(各域原始文档);全部受理 ⇒ `effective` = 提交的键 → 受理后的域文档(不补缺席域,告警面空);
|
|
73
|
+
任一份不受理 ⇒ `rejected{ stage: "validate" }`。契约仓的入口是 `parseDomain`;读腿抢救的地方(丢坏条目 / 整域降级 / 主机名夹紧)严写拒,
|
|
74
|
+
不是域名的键严写拒。详见 `docs/CONFORMANCE.md` §1.1。
|
|
75
|
+
- **各方要做的**:
|
|
76
|
+
- **server(只读 worker)**:适配器 `legs` 从 `Partial<Record<LegName, ConformanceLeg>>` 改成全量四条 —— `wire` / `localRoot` 照旧实现,
|
|
77
|
+
`domains` 与 `strictWrite` 写 `{ declined: "no-production-path", why }`(worker 不从各域原始文档建 effective;worker 只读不写,写入只经 web-admin)。
|
|
78
|
+
不改 ⇒ tsc 红;`as` 绕过 ⇒ release 违例 `has no stance`。账本不受影响(严写格对你是 `skipped`)。`./migrate` 你零读点。
|
|
79
|
+
- **管理面(写入席)**:`domains` = 现在的往返腿;**`strictWrite` = 域写受理那一段**(对文档集里每个键跑写受理;任一份不受理 ⇒
|
|
80
|
+
`{ outcome: "rejected", stage: "validate", diagnostics }`;全部受理 ⇒ `{ outcome: "effective", effective: { <键>: <受理后的文档> }, warnings: [],
|
|
81
|
+
version: input.version, updatedAt: input.updatedAt }`);`wire` / `localRoot` 声明 `no-production-path`。已知两处先看清:你回报的剥键名单
|
|
82
|
+
在 kit 里没有位置(按 `warnings: []` 交);你的写受理做跨域引用完整性而 `parseDomain` 不做(今天语料零分叉)。`./migrate` 三处 import 不受影响。
|
|
83
|
+
- **cli / client-core**:不实现 kit 适配器 ⇒ 提货零改动;`./migrate` 零 import。
|
|
84
|
+
- **CLI `migrate-workers --check`**:与真跑过同一只判定 —— 整域会被拒的文档 `--check` 也报 `outcome=refused` / 退出 1(7.x:`pending` / 3),
|
|
85
|
+
`refused[]` 逐条带出被拒项(`name` 可为 `null`)。按 `--check` 做闸的运维脚本:退出码 3 仍只表示「会改、且写得进去」。
|
|
86
|
+
|
|
87
|
+
### 0.4 11.0.0 持久面:读路径零抬升臂 + 一次性迁移族(`./one-time-migrations`)
|
|
88
|
+
|
|
89
|
+
- **规则一句话**:旧形只有一种去处 —— 登记过窗的一次性迁移把它改写成新形(调用方写回、幂等、只认形);读写两面遇旧形一律**响亮拒**,
|
|
90
|
+
拒句点名迁移 id。11.0.0 之前同样的旧形由读路径上五只常驻臂每次读都抬一遍(治理三闸从 `runtime` 抬升、未知键扫描为 `runtime` 豁免、
|
|
91
|
+
hosts 夹紧、调度店 durable 键重铸、workers 6.x 读时抬升);现在 0 只,唯一的读时改写是调度店 SS-M3(登记在迁移表里,lazy-on-read)。
|
|
92
|
+
- **`runtime` 域 11.0.0 退役**(16 域):五个限额键只住 `limits`,治理三闸只住 `governance`。墓碑 `RETIRED_DOMAINS`:`parseDomain`
|
|
93
|
+
(写)、`buildEffective`(会向店要 `"runtime"` —— 你的 `getRaw` 要能答它)、`readEffectiveWire`(wire 上)、`FileConfigStore`
|
|
94
|
+
(`config.d/runtime.json`)遇**带事实**的 runtime 文档一律抛 `RetiredDomainError`(`code: "retired_domain"`、`migration: "SS-M1"`);缺席或 `{}`
|
|
95
|
+
不理会。告警闭集少两员(`legacy-governance-lifted` / `hosts-grandfathered`):穷举 switch 删两臂。
|
|
96
|
+
- **迁移入口**(纯函数:不读环境、不读墙钟、不改入参、**不抬任何版本号**):
|
|
97
|
+
- `migrateStoredDocuments(docs, { coverage })` —— 一组存储文档(域名 → 文档)。一个 scope 的全部草稿传 `coverage: "scope"`(缺席的域 =
|
|
98
|
+
从未设过);**principal 层**(个人 models / mcp / skills / plugins)、回滚史里的单域行、任意子集传 `"partial"`(缺席的域 = 没给、不动、
|
|
99
|
+
不新建)。`runtime` 只能连同它那个 scope 的 `governance` 与 `limits` 一起迁;回滚史里单独一行 runtime 迁不了(收据 `refused`)——
|
|
100
|
+
回滚到它会被墓碑拒(不可恢复,管理面映射成 409)。
|
|
101
|
+
- `migrateEffectiveSnapshot(effective)` —— 冻结的发布快照 / 回滚快照 / 引擎的 last-known-good 文件;规则与草稿同(零语义变,不越过发布审批),
|
|
102
|
+
`version` / `updatedAt` 与其余信封键原样。
|
|
103
|
+
- 返回 `{ docs | effective, receipts }`:没改的文档**同一引用**、被移除的(退役域)不在、迁出的新文档(`governance` / `limits`)在 ——
|
|
104
|
+
据此决定写回哪几份、删哪几份。`receipts` 是 `MigrationReceipt[]`(闭集 `appliedAs`:`moved` / `rewritten` / `dropped-shadowed` /
|
|
105
|
+
`dropped-inert` / `refused`;只点名坐标与处置、不带值)。只剩 `refused` 的收据 = 什么都没改。再跑一次 = 零改动(幂等)。
|
|
106
|
+
- **版本号归你**:草稿版本(待审的发布申请据它判「草稿动没动」)、快照的版本 / 全局修订号,迁移一概不碰 —— 要不要因为表示改写而抬版本,
|
|
107
|
+
是你的决定。
|
|
108
|
+
- 目录形:`npx @sema-agent/settings-schema migrate <config.d directory> [--check]`(同一只函数,目录当一个 scope;有 refused 一字不写、退出 1;
|
|
109
|
+
`--check` 有改动退出 3;目录里有条目却没有一份域文档 —— 填成了上一层 / 只有非域文件 —— 退出 2、stdout 空、stderr 点名目录;目录真空(零条目或只有 `.` 开头的条目)= noop 退出 0;
|
|
110
|
+
stdout 两行:结论行 `outcome=… written=…` + JSON 报告 `event: "stored_documents_migrated"`)。
|
|
111
|
+
- **十条迁移**(登记表 `ONE_TIME_MIGRATIONS` 是窗的唯一属主,各条的 `rewrites` / `removal` / `afterRemoval` 写在那里):SS-M1 退役 runtime 域 ·
|
|
112
|
+
SS-M2 hosts 超界(名 > 63 删条目、数组 > 1024 截断)· SS-M3 调度店 durable 键(读时)· SS-M4 workers 6.x 行 · SS-M5 钥匙双设(删
|
|
113
|
+
`apiKeyEnv`,sealed 赢)· SS-M6 `atModelAllowlist` → `atMention` · SS-M7 URL fragment(`#` 及其后剥掉)· SS-M8 退役角色词(删 `roles.team` /
|
|
114
|
+
`roles.synthesize`,12.0.0 随引擎 core 7.49.0)· SS-M9 `models.default` → `roles.default`(缺省模型只有一位)· SS-M10 prompts 两轴(删空的 `bindings: []`;非空
|
|
115
|
+
`bindings` 与 `catalog` 拒、原样留,经中心编辑器重新发布)。
|
|
116
|
+
- **读侧的新形**:`ModelEntry` 钥匙二选一(`sealedApiKey` 与 `apiKeyEnv` 同设 ⇒ 拒);`atMention`(`{mode:"all"}` 缺省 /
|
|
117
|
+
`{mode:"none"}` / `{mode:"only",models}`,roster 位缺席 = 继承,收窄读法一只 `narrowAtMention` —— 收窄到一个都不剩就是 `none`);
|
|
118
|
+
workers 6.x 字段与 `atModelAllowlist` 是 schema 里原位的**墓碑字段**(在场即拒,句子点名迁移);`readWorkersRows(raw) → { doc, held }`
|
|
119
|
+
(workers 域的逐行终判,取代 `migrateWorkersDomain` 的终判半)。
|
|
120
|
+
- **调度店**:`parseSchedulerStore(text, { nowMs })` / `loadSchedulerStore(path, { nowMs })` 恒返回 `{ records, warnings }`(没有静默模式);
|
|
121
|
+
整份文件不是 JSON 数组 ⇒ `unreadable-file`,`mutateSchedulerStore(path, fn, { nowMs })` 遇它不调 `fn`、不写(`reason: "unreadable_file"`);
|
|
122
|
+
会话链上的记录(显式 `lifetime: "session"`)过期 ≥ 7 天 ⇒ 读时丢 + `stale-session-record`(`lifetime` 缺席读作 durable,不丢);所有写者走 `mutateSchedulerStore` 这一只原语(裸 load→save 是丢更新的竞争)。
|
|
123
|
+
- **运维约束**(管理面):迁移期间不混跑 10.x 与 11.x 的 web-admin 副本 —— 10.x 副本会照旧写出旧形,11.x 副本的读侧会拒它。
|
|
124
|
+
- **其余**:`statusRowsToPrune` 的 `now` 必填(本包不读墙钟)。
|
|
125
|
+
|
|
126
|
+
## 1. 入口(import 哪个)
|
|
127
|
+
|
|
128
|
+
| import | 内容 | 浏览器安全 |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `@sema-agent/settings-schema` | 纯契约+resolver:zod 域类型 / `EffectiveConfig`·`EffectiveWire` / `buildEffective` / `resolveEffectiveForWorker` / `ConfigReader` / `isDomainName` / `NON_SECRET_URL` / `RemoteExecSpec` / `ConfigPath`·`formatConfigPath`(3.0.0,§0.1)/ `EXECUTION_LANES` 等六个词表常量与 `migrateWorkersDomain`(7.0.0,§0.2) | ✅ |
|
|
131
|
+
| `@sema-agent/settings-schema/execution-lanes` | 7.0.0 车道 / 店词表正本(`EXECUTION_LANES` 等,与主桶同一模块实例),给只要词表的客户端组件 | ✅ |
|
|
132
|
+
| `@sema-agent/settings-schema/node` | node-only:`FileConfigStore` / `loadLocalConfig`·`loadRemoteExec`·`writeRemoteExec`·`loadEnvFile` / `writeBundle`·`readBundle`·`exportBundle` / `validateDomain`·`writeDomainFile`·`listRequiredEnvNames` | ❌(node) |
|
|
133
|
+
| `@sema-agent/settings-schema/conformance` | 跨仓一致性门 kit(3.0.0 新增;8.0.0 四条腿 + 表态律):金样语料 + 比对器,见 §11 | ❌(node,语料落盘) |
|
|
134
|
+
|
|
135
|
+
## 2. 单机(本地)模式 —— `FileConfigStore` 是真实接线,`loadLocalConfig(root)` 是便捷一把梭
|
|
136
|
+
|
|
137
|
+
包给了两条腿,**别搞混哪条是今天真的接线**:
|
|
138
|
+
|
|
139
|
+
- **`FileConfigStore`(class)** —— **今天唯一有生产消费方的那条**:sema-server `config-provider.ts` 的
|
|
140
|
+
`LocalConfigProvider` 直接 `new FileConfigStore(root)`,自己调 `getEffective()` / `loadRemoteExec` /
|
|
141
|
+
`loadEnvFile` 拼装。
|
|
142
|
+
- **`loadLocalConfig(root)`(function)** —— 上面三步的便捷封装,一把梭返回 `{effective, remoteExec, env,
|
|
143
|
+
requiredEnv, warnings}`。**3.0.1 订正**:此前这里写「service 引擎和 client CLI/desktop 都调
|
|
144
|
+
`loadLocalConfig`,所以两边零漂移」——**四仓(server / cli / client-core / web-admin)生产码今天对
|
|
145
|
+
`loadLocalConfig` 的真实 import = 0**(server 的两处命中都在 test 的注释里)。它不是「既有接线」,是包给
|
|
146
|
+
「要一把梭、不想自己拼三步」的新消费方留的便捷入口;server 走的是上面的 `FileConfigStore` 直连。两条腿
|
|
147
|
+
读同一份文件、同一套校验,**结果不会漂移**,但「都调同一个函数」这句话不成立,已删。
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const { effective, remoteExec, env, requiredEnv, warnings } = await loadLocalConfig(root);
|
|
151
|
+
```
|
|
152
|
+
- **`effective`** = **完整 `EffectiveConfig`**(skills 全文内联,**不是** `/effective` wire/manifest;rosters 不清空)。直接喂 `applyEffective`。(0.6.0:access/integrations 域已整删,EXPERT-REDESIGN §9;治理三件住 `governance` 域;`runtime` 域 11.0.0 退役,五个限额键只住 `limits`,见 §0.4。)
|
|
153
|
+
- **`remoteExec`** = `RemoteExecSpec | undefined`。`undefined` → 引擎默认 `host`。(读 `config.d/remote-exec.json`,见 §5 三态。)
|
|
154
|
+
- **`env`** = `.env` merge over `process.env`(file 赢);`file:` ref 已解析。解析 env-NAME→值的来源。
|
|
155
|
+
- **`requiredEnv`** = `[{ envName, refs, present }]` —— doctor 的"还差这些密钥"清单(§3)。
|
|
156
|
+
- **`warnings`** = 本次读盘的降级/静默告警(`EffectiveReadWarning[]`;0.19.0 起不再被吞)。坐标字段是
|
|
157
|
+
`ConfigPath`(§0.1),出门显示走 `formatConfigPath`。空数组 = 本次读盘无告警。
|
|
158
|
+
- **`entry-dropped.identity`**(被丢条目的 `name` / `id` / `enabled`)只带**可披露**的身份串,判据只有一只
|
|
159
|
+
`isDisclosableIdentity(v)`(主 barrel):**6.3.0 起** = 落在白名单闭形 `DISCLOSABLE_IDENTITY_RE`
|
|
160
|
+
(`^[A-Za-z0-9._:/@+-]{1,256}$`,主 barrel 导出;上限与本 web-admin 读侧名长 256 对齐)∧ 坐标脱敏器 `redactSecrets` 原样放行。身份是**标识符**不是自由文本:
|
|
161
|
+
上游模型 id 的真实形(`accounts/fireworks/models/…`、`Qwen/Qwen2.5-72B-Instruct`、`claude-3-5-sonnet@20240620`、
|
|
162
|
+
`deepseek-v4-flash:free`、`o3-mini+high`)都在集内;`%` / `=` / 空白 / 引号 / 反斜杠 / 控制与零宽字符 / 非 ASCII / 超 256 字
|
|
163
|
+
一律不可披露(编码过的私钥、会在日志里伪造行的串都进不来);集内的密钥形(`sk-…` 等)与带 userinfo 的 URL(`postgres://svc:…@h`,
|
|
164
|
+
与 `NON_SECRET_URL` 同一只判定)由第二项否决。不可披露 ⇒ 该键**缺席**
|
|
165
|
+
(按无名处置,不打码、没有占位词)—— 你的 UI 把缺席渲成「身份不可披露 / 未知」,别回读原文档去补(那正是它不带的原因)。
|
|
166
|
+
自己要决定「日志 / 拒因句里能不能写出一个名字」,也问 `isDisclosableIdentity`,**不要**拿 `DISCLOSABLE_IDENTITY_RE` 自拼
|
|
167
|
+
(集内仍有密钥形)。注意 CollabTemplate 的 `name` 是自由展示名(schema 不限字符集),含空格 / 中文的被丢模板 6.3.0 起身份里只剩
|
|
168
|
+
`id`(BudgetGroup 的 `name` 也是展示名,但它在 entitlement 这个 gate 域里,坏形整域 fail-loud,不会成为 `entry-dropped`)。
|
|
169
|
+
|
|
170
|
+
`root` = `CONFIG_LOCAL_DIR ?? AGENT_DATA_DIR ?? ~/.ai-agent`(server 侧的实际约定;包本身不读 env,只收
|
|
171
|
+
`root` 入参 —— 这两个 env 名不在本包 `src/` 里)。远程/本地切换靠消费方自己的开关(server:
|
|
172
|
+
`CONFIG_PROVIDER=local|remote`)。**⚠️ 别再认 `CONFIG_CENTER_URL`**:那是两代前的老名字,本包的墓碑表
|
|
173
|
+
`RETIRED_ENVS` 把它列为**在场即拒启**(renamed → `WEB_ADMIN_ENV.url`,§8 的拉取凭据用的就是现名)—— 照旧文档接旧名的人会被
|
|
174
|
+
server 直接拒启,不是静默降级。上一代 `SEMA_REGISTRY_*` 八名(`URL` / `TOKEN` / `TOKEN_FILE` / `WORKER` / `LIVE_URL` / `WEB_URL` / `DRY_RUN` / `ADMIN_TOKEN`,现名各是 `WEB_ADMIN_ENV` 同键的值)同样在表里:
|
|
175
|
+
引擎换读现名之后,它们在场即拒启 —— **读侧同代要求**:换读现名的引擎才拒它们,更老的引擎仍把它们当活旋钮读,所以给 worker 渲染 env 的一方要按**目标引擎的代**写名(混版本车队按目标版本渲)。拒启句的唯一铸点是 `retiredEnvRefusal(env)`(纯函数:读给它的 env 记录,有旧名在场 ⇒ 整句,
|
|
176
|
+
否则 `undefined`);引擎起服、壳起本机引擎、web-admin 渲染 worker env 都判同一张表,**别手抄名单**。
|
|
177
|
+
|
|
178
|
+
## 3. doctor(client/desktop)—— 渲染 `requiredEnv`
|
|
179
|
+
|
|
180
|
+
- `present=false` → 高亮成缺失。**别先造新 CLI**(包是纯 lib 无 bin);先在**已有消费方**(run-local 子命令 / desktop 面板)渲染就绪数据。
|
|
181
|
+
- 扩到 **schema/引用错误**:`validateDomain(domain, raw)` 返回 zod issues —— 覆盖"配了个不存在的 model 名 / 引用了不存在的 mcp"这类**非密钥型**错误(普通用户最常踩)。
|
|
182
|
+
- 空串值 `KEY=` 判 present(你那层可加"present 但空=弱告警");`file:` 指向**空/缺文件 → 抛错**(fail-closed,绝不解析成空密钥)。
|
|
183
|
+
|
|
184
|
+
## 4. 密钥 —— env-NAME + `file:`
|
|
185
|
+
|
|
186
|
+
- 契约字段是 **env-NAME**(`ENV_NAME = ^[A-Z_][A-Z0-9_]*$`,粘 `sk-…` 会被 schema 直接拒)。
|
|
187
|
+
- `.env` 值两种:`KEY=value`,或 **`KEY=file:<path>`**(解析成文件 trimmed 内容;`~/` 展开;相对路径=root 下;**缺/空文件抛错**)。schema 仍只存 NAME,仍拒明文。
|
|
188
|
+
- **URL 字段**(model baseUrl / gateway / mcp url / k8s apiUrl / s3 endpoint 走 `NON_SECRET_URL` —— 存量里的 fragment 由一次性迁移 SS-M7 剥掉,拒句带指路;凭据信封的 `issuer` 走它的孪生 `NON_SECRET_URL_NO_MIGRATION` —— 同一判官,fragment 拒句不带迁移指路):**拒 `user:pass@host` userinfo 凭据**(凭据进 `*Env`,绝不进 URL)。
|
|
189
|
+
|
|
190
|
+
## 5. `remoteExec`(service 执行层选择)
|
|
191
|
+
|
|
192
|
+
- `RemoteExecSpec` 6 档:`host`/`local-docker`/`e2b`/`k8s`/`ssh`/`adb`(每个 provider 词 ⊆ `EXECUTION_LANE_WORDS`)。**直接消费包的 `RemoteExecSpec`/`validateRemoteExec`/`loadRemoteExec`,别各维护一份**(防漂移,core/service 合意)。
|
|
193
|
+
- **7.0.0 删 `remote-docker`**:引擎从没有读点把它变成在跑的车道(写它的文件只换来一句「未接线」告警);远端 docker 守护进程 = `local-docker` + `DOCKER_HOST`。文件里还写着 `remote-docker` 的,7.0.0 起读文件即拒(fail-closed,同「空/损坏 → 抛错」)。
|
|
194
|
+
- **三态**(`config.d/remote-exec.json`):**缺失 → host 默认**;**存在 → 赢**;**空/损坏 → 抛错**(fail-closed —— 空文件静默回退 host 在用户本意 local-docker 时是隔离降级)。
|
|
195
|
+
- host 臂:`workdir`(≡ service 的 `workspaceBase`)/`allowedPaths`(写围栏)/`shell`/`commandTimeoutMs`。
|
|
196
|
+
- ⚠️ **层级**:契约是 **env-NAME-ref 形**(`apiKeyEnv`);service 把 NAME→值解析成自己的**已解析 union**(`apiKey`)。两层并存 —— "消费 RemoteExecSpec" = 用它 parse 文件源 + 解析,**不是**把已解析 union 整个替成 `RemoteExecSpec`。
|
|
197
|
+
|
|
198
|
+
## 6. 🔴 governance 治理:`autonomy` + `commandPolicy`(TRAP 区 —— 评审逮过静默破坏安全)
|
|
199
|
+
|
|
200
|
+
> 域坐标(0.6.0 拆三刀 + 0.7.1 撤双写;`runtime` 域 11.0.0 退役):治理三件(autonomy/commandPolicy/approvalRequire)只住
|
|
201
|
+
> **`governance` 域**(`eff.governance.*`)。0.5 期的 runtime 旧文档不再读时抬升:读写两面遇它响亮拒(墓碑 `RETIRED_DOMAINS`),
|
|
202
|
+
> 存量由一次性迁移 SS-M1 搬进 governance(§0.4)。
|
|
203
|
+
|
|
204
|
+
消费这两个 declaration **必须小心**(经逐条评审,踩过静默破坏安全):
|
|
205
|
+
|
|
206
|
+
- **`governance.commandPolicy` → 编译进 `TaskSpec.toolPolicy`**:必须 `combinePolicies(部署默认, coarse策略)`,**绝不裸赋值** —— 🔴 `TaskSpec.toolPolicy` **覆盖**(非合并)`RunnerDeps.toolPolicy`(core `types.ts`),裸赋值**静默清掉部署级护栏**。**DENY-WINS**。core 建 `createCoarseCommandNamePolicy` 消费它;它 **COARSE/非沙箱**(只匹 argv[0] 命令名;`$()`/`sh -c`/`xargs`/`find -exec` 绕得过),是 egress/irreversibility 之下的纵深防御,非唯一隔离。
|
|
207
|
+
- 🔴 **`command` 是精确 argv[0] 名,NOT a glob**(service 裁决,0.1.26 起):core 1.114.0 的 `createCoarseCommandNamePolicy` 是**精确 Set 匹配**,glob/path/shell 片段(`git*`/`/usr/bin/rm`/`rm -rf`)运行时永不匹配 = 静默放行(fail-open)→ 契约层已 **fail-loud 拒收**这些字符,逼写成能真正生效的精确名。扩面靠 allowlist/blocklist 模式,不靠通配。
|
|
208
|
+
- **`governance.autonomy`(read-only|ask|plan|auto)→ 展开到 core 已有三原语**(`handsReadOnly` / `shellGate`+`onAsk` / `plan_review`),**经 core 的收紧-only `tightenTaskSpec(base,overrides)`**。🔴 **绝不在 core 加并行 autonomy 枚举**(双实现陷阱);🔴 **绝不直接写 override 字段**——`toolPolicy`/`hooks`/`onAsk` 是覆盖字段会静默抹掉调用者值;**`auto` 模式若清 toolPolicy/设 onAsk:"allow" 会静默禁用所有门控**;`handsReadOnly:true` 使 `shellGate` 失效。
|
|
209
|
+
- 预算注意:`shellGate:"classify"` 下普通 glob-ask 会被 irreversibility 提升成不可预算的 `irreversible_ask`(core `hooks.ts`)。
|
|
210
|
+
|
|
211
|
+
## 7. `EffectiveConfig → TaskSpec` 映射速查
|
|
212
|
+
|
|
213
|
+
| EffectiveConfig 域 | 去向 | 备注 |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| models / roles | `RunnerDeps.models`/`roles` | role 解析不变 |
|
|
216
|
+
| skills | 本地=全文内联;远程=manifest + `fetchSkillContent(hash)` | 本地无 wire 跳,无需按 hash 回取 |
|
|
217
|
+
| mcp | `TaskSpec.mcp`(allowTools + principalHeader) | env/header NAME→值解析 |
|
|
218
|
+
| limits(0.19.0,cost/限流族 14 键;11.0.0 起五个限额键的唯一位) | 消费方的限额面(server:`stage-limits`) | `.optional()` 无默认;本包只判结构,业务域值(下限/0 哨兵/跨键不变量)由消费方响亮拒 |
|
|
219
|
+
| governance.autonomy / commandPolicy | 见 §6(小心区) | 经 tightenTaskSpec / combinePolicies |
|
|
220
|
+
| rosters | control-plane only | `/effective` wire 经 `resolveEffectiveForWorker` 清空/剥离 |
|
|
221
|
+
|
|
222
|
+
## 8. fleet(远程)模式 —— 从 sema-web-admin 拉
|
|
223
|
+
|
|
224
|
+
- `GET /effective[?worker=]` → `EffectiveWire`(skills=manifest,rosters 已剥);`fetchSkillContent(hash)` 取 body。
|
|
225
|
+
- `GET /api/config/export[?published]` → bundle,可 `writeBundle` 种到本地 `config.d`(fleet 配置 seed 单机)。
|
|
226
|
+
- 拉取 env-NAME:**`WEB_ADMIN_ENV.url` + `WEB_ADMIN_ENV.token`**(值 `SEMA_WEB_ADMIN_URL` / `SEMA_WEB_ADMIN_TOKEN`;export 端点
|
|
227
|
+
editor-gated → 要 editor 级 token)。引擎连 web-admin 读的 env 名都在 `WEB_ADMIN_ENV` 一张表里,**别手抄**。旧名已改名并退役
|
|
228
|
+
—— 在场即拒启(见 §2)。
|
|
229
|
+
- 凭证也可以声明成**文件**:`WEB_ADMIN_ENV.tokenFile`(每次取值重读,轮换不重启),与 `WEB_ADMIN_ENV.token`
|
|
230
|
+
**二选一**,两个都设 = 拒;断言令牌的一对(`assertionToken` / `assertionTokenFile`)同一条规则。这条规则**别手抄**:取
|
|
231
|
+
`judgeWebAdminCredentialDeclaration(env, "access" | "assertion")`(S-742);文件内容的读法取
|
|
232
|
+
`parseWebAdminCredentialFile(text)`(S-698:裸行 = 静态 bearer,`{` 开头 = `WebAdminCredentialEnvelope` 用户会话信封,
|
|
233
|
+
读不成 = `unreadable`,不回落)。
|
|
234
|
+
- **本地 `config.d` 的一次性迁移(引擎起服)**:调 `@sema-agent/settings-schema/node` 的 `migrateConfigDirectory(dir, { documents, io: nodeConfigDirectoryIo })`,`documents` 填你本地读腿会读的域名集
|
|
235
|
+
(覆盖面外的文件不读不判不写不删;要迁 retired 的 `runtime` 域就得把 `governance` 与 `limits` 一并放进来 —— 迁出的旧值要写那两份,缺了 `runtime` 按 `partial` 拒迁、一字不写);按返回的 `kind` 穷举处置(`applied` / `noop` / `pending` / `refused` / `no-documents` / `unreadable`)。**别按同语义自写**读目录、迁、整域终判、原子写、删退役域文件那一串 ——
|
|
236
|
+
命令行 `migrate` 与这只函数同一份实现;写盘用的就是本包那一只原子写器(契约文档新建 0o644、已有的沿用原模式、符号链接保留)。
|
|
237
|
+
- **会话网页链接**:要给用户一个 web-admin 里的会话链接,调 `webAdminSessionUrl(webBaseUrl, { worker, sessionId })`(`webBaseUrl` 取引擎 `/health` 自报的人用界面根 URL,
|
|
238
|
+
`worker` 取引擎向 web-admin 登记用的 worker 名),得到 `<webBaseUrl>/chat?agent=<worker>&session=<sessionId>`;**别按地址形手拼** —— 路由属于 web-admin,模板与读回的
|
|
239
|
+
`parseWebAdminSessionUrl(link)` 同在本包,路径与参数名只此一份。两位必填:只带 `session` 的链接会落到页面自己挑的 worker 上、可能找不到会话。
|
|
240
|
+
- **模型拒因(引擎 → web-admin,S-768,6.2.0)**:引擎逐台报「本机不服务目录里的哪些条目、为什么」,一处铸、两出口 ——
|
|
241
|
+
心跳 `InstanceAnnouncement.modelRefusals?` 与 `GET /health` 的 `modelRefusals`(`InstanceHealth` 不类型化它)。**两个出口
|
|
242
|
+
同一只读口**:`readModelRefusals(body)`,`body` 传 `/health` 体或已 parse 的公告都行(它只读 `modelRefusals` 键);别直接
|
|
243
|
+
读公告上的字段 —— 字段是 wire 形 `ModelRefusalsWire`(码是开放串),读口才按本包闭集收窄成 `ModelRefusalsSignal`。
|
|
244
|
+
形 = `{ entries: { <条目名>: <码> }, plane?: <组拒码> }`,**名 → 码就是全部**:不带上游 id / provider / 坏字段 / 拒因句
|
|
245
|
+
(那些留在引擎日志里)。码是本包的**闭集**(`MODEL_ENTRY_REFUSAL_CODES` 两码 / `MODEL_PLANE_REFUSAL_CODES` 四码,各有
|
|
246
|
+
`z.enum` 与型)—— 渲染按码穷举 `switch`,别手抄词表。**集外码 = 没说**:本包版本不认识的码,读口把那一条(或 `plane`)
|
|
247
|
+
丢掉,丢到空就回 `undefined`;心跳照收、存行照存 wire 形(web-admin 升级本包后同一行读出新码)—— 版本错位不会拖垮心跳。心跳上
|
|
248
|
+
`modelRefusals` 形坏(比如空信号)同样只降为缺席,不拒整条心跳。web-admin 若要给形坏留痕,自己对**请求原体**的 `modelRefusals`
|
|
249
|
+
字段(`InstanceAnnouncement.parse` 之前 —— parse 之后形坏的值已降为 `undefined`)`ModelRefusalsWire.safeParse`,读法口不替它记。`plane` 在场 = 引擎把最新交给它的那份目录的模型面整组拒了
|
|
250
|
+
(它继续服务上一代;一个候选恰一个组拒判决),`entries` 照旧逐条、说的是**同一份**(没落地的)候选 —— 组拒期间这台实例的
|
|
251
|
+
选择通道仍按上一代的拒因执行,所以 `entries` 此时是「这份新目录落不了地的逐条原因」,不是「此刻点这些名会被拒」。
|
|
252
|
+
**读版本,不读缺席**:键缺席本身不说「无拒」—— 更早的引擎从不发此键(缺席 = **不可知**)。读方看公告上已有的 `version`,
|
|
253
|
+
引擎版本 ≥ 首发此键的那一版(见 CHANGELOG 6.2.0 节)才把缺席读成「没有可报的拒」;`readModelRefusals` 只答键、不替你判版本。
|
|
254
|
+
别把老引擎渲染成「全部可用」。「可报」的边界:载不住 / 秘密形 / 不是标识符(6.3.0 起:不在 `DISCLOSABLE_IDENTITY_RE` 白名单闭形里,见 §2)的条目名不上报
|
|
255
|
+
(引擎按 `isReportableRefusalName` 筛,不进键、不计数),所以 `entries` 的条数可以少于真被拒的条数,只有这类拒时整个键缺席;`/health` 上读不出的值读口也回 `undefined`。
|
|
256
|
+
每拍整行重报 ⇒ 存行整替、不合并。
|
|
257
|
+
|
|
258
|
+
## 9. 契约演进纪律(你要提字段时)
|
|
259
|
+
|
|
260
|
+
- **加性 `.optional()` 无默认**:默认值会静默覆盖部署的 env 闸(service 在 `rateLimitPerMin` 上踩过)。
|
|
261
|
+
- secret 字段 = env-NAME(`ENV_NAME`)或 `file:`,**绝不是值字段**;URL 用 `NON_SECRET_URL`。
|
|
262
|
+
- **包版本即 schema 版本**(1.x semver,README「发版纪律」):新键/新导出/词表加词 = minor(提货时穷举表 / `Record<Word,…>` 可能编译红,属提货成本);rename/retype/删除/默认值或校验变更 = major + 迁移;文案/注释 = patch。
|
|
263
|
+
- 改 `RemoteExecSpec`/`EffectiveConfig` 契约走 web-admin,经 channel 协调(core 的 RunnerDeps/TaskSpec 注入面是另一侧,走 `给core-AI.md`)。
|
|
264
|
+
|
|
265
|
+
## 10. 验证
|
|
266
|
+
|
|
267
|
+
`examples/toc-e2e.mjs` 是可独立复跑的参考闭环:造 `config.d`(含 `file:` ref / autonomy / commandPolicy)+ `.env` → `loadLocalConfig` → 断言 effective+remoteExec+env+requiredEnv + secret 边界(盘上只有 env-NAME 无值)。照着镜像你的接入即可。`npm run e2e` 跑它(CI 已纳入)。
|
|
268
|
+
|
|
269
|
+
## 11. 跨仓一致性门 kit `@sema-agent/settings-schema/conformance`(3.0.0 新增)
|
|
270
|
+
|
|
271
|
+
`examples/toc-e2e.mjs`(§10)验的是「你的接入跑不跑得通」;`./conformance` 验的是「你的生产读腿,跟契约仓
|
|
272
|
+
自己、跟别的消费方,读出来的 `EffectiveConfig` 与告警多重集是不是逐字节一致」——两者不是一件事,新消费方
|
|
273
|
+
两个都要接。
|
|
274
|
+
|
|
275
|
+
- 契约仓自带**金样语料 + 比对器**;你把自己的生产入口(`entry{module,export}` 的绝对路径)包成一个
|
|
276
|
+
`ConformanceAdapter` 塞给 `runConformance(adapter, { mode: "release" })`。四条腿每条都要表态:实现,或
|
|
277
|
+
`{ declined: "no-production-path", why }`(§0.3)。
|
|
278
|
+
- `report.ok` 不绿就读 `report.violations`:effective 的 JSON patch diff、告警多重集差、元数据差三面分开。
|
|
279
|
+
- Node-only(语料落盘,`local-root` 腿要真的写文件)。
|
|
280
|
+
- 语料贡献形、四律(来源律 / 派生律 / 金样律 / 元数据律)、报告形的完整说明见 `docs/CONFORMANCE.md`,别在
|
|
281
|
+
这份档里重复展开。
|
|
282
|
+
- 今天真接了这道门的:server(`wire` + `localRoot` 两条读腿)、管理面(`domains` 往返腿,钉在 6.x);cli 显式拒接 kit 读腿
|
|
283
|
+
(壳不走上游 effective 读腿),client-core 零 import。8.0.0 起两家接线方都要对四条腿逐条表态(§0.3)。
|