@sema-agent/settings-schema
v1.4.1
Published
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-c
Maintainers
Readme
@sema-agent/settings-schema
改名声明(1.0.0):本包自
1.0.0起名为@sema-agent/settings-schema;在此之前名为@sema-agent/registry-core(最后一版0.19.0),更早还曾是@sema-ai/registry-core。 这是一次纯改名,不加料:0.19.0→1.0.0之间零语义变更——没有字段增删、没有校验收紧 (无.strict()化)、没有默认值改动、没有行为变化;exports的 21 个 subpath 一个不动 (././types/./node/./api/auth/./api/wire…… 逐条 1:1 平移,只换包名前缀)。 版号跳到1.0.0不是「契约进入稳定期」的语义信号——改名需要一个不与旧0.x段混淆的新起点而已 (但自1.0.0起本包按标准 semver 发版:破坏性 = major,见下文「发版纪律」)。消费方迁移(两步,注意版号段也要动):
package.json依赖:"@sema-agent/registry-core": "^0.19.0"→"@sema-agent/settings-schema": "^1.0.0"。 ⚠️ 只换名不换段会装不上 —— 新名的最低版本就是1.0.0,^0.19.0在新名下没有任何匹配版本,npm install直接报No matching version found。改完重生成 lockfile(npm install/npm i --package-lock-only)。- 代码 import 说明符:
@sema-agent/registry-core…→@sema-agent/settings-schema…(纯前缀替换, 21 个 subpath 的后缀一个不动)。这两步之外什么都不用改:没有 API 改名、没有类型改形、没有行为差异(实证见 CHANGELOG
1.0.0段的 发布物 diff)。旧名
@sema-agent/registry-core(≤0.19.0)保持已发布状态、冻结不再出新版,后续会打 npm deprecate 标指向新名。
Engine-agnostic, zero-dependency contract layer for the sema stack — one source of truth for the
config domain across the center (the config-center product — today sema-web — the fleet admin), the
server (the workers), and the client (desktop/CLI) seams. No two copies of the schema.
命名说明:config-center 这个产品曾叫
sema-registry,现在是sema-web(后续还会再改名)。 本包名与产品名解耦,不随产品改名而动(1.0.0 这次改名是本包自身的正名,与产品名无关)—— 下文凡提「center / config-center 产品」即指该产品的当期实现。
- Source of truth:
github.com/sema-agent/sema-settings-schema, branchmain(private org repo; renamed fromsema-registry-coreon 2026-08-26, old URLs redirect). 仓名本步不动(只改包名)。 - npm: published as
@sema-agent/settings-schemaon the public npm registry (≤0.19.0发在旧名@sema-agent/registry-core下,冻结)。
The package is pure: zero next/react/server imports, zero DB drivers. Its only runtime dependency is
zod (a dependency pinned ^3.24.0 — the same range the host repos (config-center 产品 / sema-server / CLI) use, so
package managers dedup to a single shared zod instance, keeping cross-instance schema validation / instanceof
sound). npm ls --prod therefore shows only zod. Node built-ins only (node:crypto, node:fs).
What's in it
- The zod contract (
types.ts):DOMAIN_SCHEMAS(17 domains — 0.10.0 addsprojects, the project identity ledger; 0.10.18 addsprompts; 0.19.0 addslimits, the cost/rate-limit publication slot),EffectiveConfig,EffectiveWire,ENV_NAME/K8S_QUANTITYboundaries, every domain's entry/config type. - Pure config fns (
config-fns.ts):buildEffective(deterministic —updatedAtis a required param),parseDomain,emptyEffective(epoch-seeded),statusRowsToPrune, role helpers (roleAtLeast,domainWriteRole, …),resolveActiveTiers(0.9.0 档位组→引擎RunnerDeps.tiers组存models.tierGroups+models.activeTierGroup,组切换=换表下发), and the pure data shapes (User,PublishedSnapshot, …). - Roster resolution (
resolve-roster.ts):resolveEffectiveForWorker→ the per-worker wire payload. - Ref integrity (
cross-domain.ts):refIntegrityIssues+siblingResolver(the pure half — fleet auto-placement stays in the config-center 产品). - Hashing (
hash.ts):stableHash(FNV-1a 64) for ETags / version detection. - Skills manifest (
skills-manifest.ts): content-addressing for the lazy-pull skill bodies. - Secret inventory (
secret-refs.ts):collectEnvRefs— every referenced env-NAME. - The narrow reader (
reader.ts):ConfigReader(3 methods) — the config-center 产品's fatConfigStoreextendsit; a local file source / test fixture implements it without stubbing ~32 methods. remoteExeccontract (remote-exec.ts): the execution-substrate schema mirroring the service's actual providers (e2b/k8s/ssh/adb), discriminated onprovider; every secret an env-NAME ref.- Safety merge-spec (
safety-merge-spec.ts, subpath./safety-merge-spec):SAFETY_MERGE_SPEC, the ONE per-category merge/enforcement table every settings resolver reads (mergeShape/minTrust/tighten/enforcement/fail) — no consumer re-implements the merge law. 1.1.0 adds member-level admission: a category may declaremembers, each with its ownminTrustfloor anddirection(loosen= a privilege, needs trust;tighten= only narrows, any settings-file layer may add it). Today onlypermissionsdoes —allow(loosen @global) /deny/ask(tighten @local), a closed set; read it viamembersOf/memberMinTrust/memberAdmission(never a hardcoded allow/deny/ask table). The category-levelminTruststays the default floor, so consumers that predatemembersbehave exactly as before. This package publishes the contract only: it performs no admission check and no "degrade a below-floor allow to a pending suggestion" step — those live in the consumers (engine import door, shell resolver), each on its own release schedule. 1.2.0 adds the trust ordering primitives:trustRank(layer)(v2-design §1 verbatim,managed 6 > global 5 > project 4 > local 3 > session 2 > task 1) andisAtLeast(layer, minTrust)— the ladder every resolver is to compare floors against; consumers retire their own rank tables onto it (the CLI'sTRUST_RANKmigration is pending on its side, board [5807] ③ — exporting the primitive does not by itself make it the single source).MinTrustgains thetaskword (the runtime task overlay, the loosest floor; appended, declaration order otherwise unchanged — the declaration order is not the ladder,trustRankis). The published table does not usetask; the widened vocabulary is visible to consumers that hold aRecord<MinTrust, …>/ exhaustive switch (compile error on upgrade — migrate totrustRank) or that validate external spec values with these schemas (they now accepttaskand must rank it — againtrustRank, never a five-word table of their own). - Auth wire contract (
api/auth.ts, subpath./api/auth): the/api/v1/auth/*face — RFC 8628 device flow (code / token poll / approve consent) + rotating refresh + logout, the OAuth error envelope, and the pure client helpersnormalizeUserCode/nextPollInterval. Shipped M1 shapes frozen. OpenAPI mirror:docs/openapi-auth.yaml(auth face only). - Auth bridge (
api/auth-bridge.ts, subpath./api/auth-bridge): the 鉴权桥 contract — how a worker verifies a registry-minted user JWT: RS256-only, JWKS from/api/auth/sso/jwkswith kid-miss forced refresh + ≥60s throttle, exp/nbf clock skew (60s), 24h offline grace then fail-closed;VerifierConfig/VerifiedIdentity/AuthBridgeErrorCode+ pure decision fns (shouldForceJwksRefresh,jwksWithinOfflineGrace,classifyTimeClaims,toVerifiedIdentity). The normative rules R1–R10 in its header are the service verifier's implementation spec.VerifiedIdentitycarries an optionalscope(the token's active tenant scope — seeapi/scopes). - Multi-tenant scopes (
api/scopes.ts, subpath./api/scopes): the scope contract — theScopeentity ({id,name,createdAt}, reserved default"global"always exists), per-scopeScopeMemberrows ({scopeId,principal,role}on the existing viewer<editor<publisher<admin ladder), the optional JWTscopeclaim (absent ="global", fully backward compatible; refresh tokens are never scope-bound), and the wire shapes forGET/POST /api/v1/scopes,POST /api/v1/auth/scope(scope-switch mint, OAuth-style errors) andPUT/DELETE /api/v1/scopes/{id}/members. The DECISION SEMANTICS live here as pure functions the registry imports (never re-implements):resolveTokenScope(mint-time scope binding: 1 membership → it; several incl. global → global; several without global → lexicographically first; none →{scope:"global", role:instanceRole}),canSwitchScope(member or instance admin),effectiveScopeRole(instance admin is"admin"in every scope) andtokenScopeOf(missing claim ="global"). OpenAPI mirror:docs/openapi-scopes.yaml. - Local file source (
file-store.ts,file-edit.ts):FileConfigStorereadsconfig.d/*.json→EffectiveConfig;validateDomain/writeDomainFile/listRequiredEnvNamesare the TOC's read/write/validate helpers.getEffective({tolerant:true})(0.10.12) isolates a malformed catalog-domain file (that domain falls to its schema default, the error is listed indomainErrors) instead of failing the whole read; gate domains (governance/rosters/entitlement/execution/runtime/ limits/workers) stay fail-loud even in tolerant mode. 0.19.0 adds a FAIL-LOUD sweep on the same read: aconfig.d/*.jsonthe local set will never read (a name that is not a domain, or a non-portable one) and any top-level key a domain schema strips are both NAMED throughonWarning(unread-config-file/unknown-keys-dropped) instead of vanishing silently.
Secret boundary
Every secret field is an env-NAME, never a value. The ENV_NAME regex (/^[A-Z_][A-Z0-9_]*$/) rejects a
pasted credential. Values are resolved by the consumer from its own env (service env / k8s secret / the TOC
local .env). This package never carries a credential.
Local file layout (TOC)
<root>/
config.d/
models.json rosters.json skills.json mcp.json scenarios.json plugins.json
collab.json runtime.json governance.json execution.json prompts.json limits.json
remote-exec.json # NOT a domain (the exec backend; read by loadRemoteExec)
.env # secret VALUES only (KEY=value); gitignored; never read into any *.jsonFleet-only domains (workers, hosts, systems) are omitted on a single machine — they resolve to schema
defaults. Each config.d/<domain>.json is exactly that domain's zod object.
发版纪律(semver,1.x)
自 1.0.0(改名)起本包按标准 semver 发版;0.x 时代的「破坏性变更必 bump minor」规矩已撤
(末条仅作历史注记)。判档看消费方可见性,不看 diff 大小:
- 破坏性变更必 bump major:字段/导出改名、改形(retype)、删除、默认值变更、校验收紧或放宽
(例:
.passthrough()→ 默认 strip、宽读 → 严判)、枚举重排——凡是能让一个按上一版契约写的 消费方在运行期变红、或让同一份配置解析出不同结果的改动;编译期同判,唯一裁定例外 = 下一条的 additive 词表尾插(穷举表消费方的编译红按「提货成本」处理,不构成 major,见下条与其留档异议)。^1.x脱字号跨 minor,任何塞进 minor/patch 的破坏都会被未 pin 精确版的下游npm install静默拿到。 - 新键 / 新导出 / 词表加词 = minor(additive):可选键(无默认)、新 subpath、枚举尾插新词。
⚠️ 「表不动」≠「消费方零感知」:持
Record<Word, …>/ 穷举switch的消费方在提货时会编译红—— 那是提货成本,按能力显式表态制 @点名消费方并附迁移句,不因此升 major(1.2.0 / 1.3.0 口径; codex 复审持「严格 semver 应升 major」异议,原样留档候裁)。 - 文案 / 注释 / 测试 / bug 修复 = patch:不改任何 wire 形、类型面、默认值与校验结果。行为面的 「修 bug」若改变了合法输入的解析结果,按上两档判,不按 patch。
- 契约破坏面动之前先盘 center / server / CLI 三方消费点;commit message 的
feat!/BREAKING标记照旧, 但标记不豁免版号义务。每版 CHANGELOG 一节,git tagv<version>同名。 - 历史注记(0.x 时代):0.x 下
^0.7.0这类 range 只自动收 patch,故当年规矩是「破坏性必 bump minor,patch 只装 additive/修 bug/文档」。0.7.1(2026-07-10)曾携带两件预协调 BREAKING(撤 governance effective 双写镜像、撤 QuotaLease 0.5tokens别名 shim),名义上违反该条;当时下游(center/service 1.150.0)均已显式迁移、实际破坏面为零,PM 拍板不重发 0.8.0(纯扰动)。该例外随 1.x 政策一并归档。 - 发布(单腿 npmjs,内网腿已废止):
npm run publish:npm;发后必须 curl 直打 npmjs 复验 shasum。
贡献指引
- 真源 =
sema-agent/sema-settings-schemaorg 仓main(2026-08-26 由 sema-registry-core 改名),所有变更直接落 org main(或 PR 进 main); 不再向任何个人位/镜像位 push。 - 提交门 =
npm run build+npx vitest run;改域/契约后必跑npm run e2e(examples/toc-e2e.mjs)。 - 契约破坏面动之前先盘 center / server / CLI 三方消费点;破坏性变更必 bump major(见上节)。
