@sema-agent/settings-schema 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +728 -0
  2. package/LICENSE +103 -0
  3. package/README.md +129 -0
  4. package/dist/api/auth-bridge.d.ts +331 -0
  5. package/dist/api/auth-bridge.js +210 -0
  6. package/dist/api/auth.d.ts +216 -0
  7. package/dist/api/auth.js +138 -0
  8. package/dist/api/scopes.d.ts +344 -0
  9. package/dist/api/scopes.js +222 -0
  10. package/dist/api/wire.d.ts +60 -0
  11. package/dist/api/wire.js +89 -0
  12. package/dist/bundle.d.ts +13 -0
  13. package/dist/bundle.js +67 -0
  14. package/dist/config-fns.d.ts +318 -0
  15. package/dist/config-fns.js +472 -0
  16. package/dist/cross-domain.d.ts +34 -0
  17. package/dist/cross-domain.js +118 -0
  18. package/dist/file-edit.d.ts +36 -0
  19. package/dist/file-edit.js +125 -0
  20. package/dist/file-store.d.ts +89 -0
  21. package/dist/file-store.js +238 -0
  22. package/dist/fleet.d.ts +498 -0
  23. package/dist/fleet.js +317 -0
  24. package/dist/hash.d.ts +32 -0
  25. package/dist/hash.js +59 -0
  26. package/dist/hooks.d.ts +5477 -0
  27. package/dist/hooks.js +627 -0
  28. package/dist/index.d.ts +22 -0
  29. package/dist/index.js +26 -0
  30. package/dist/local-load.d.ts +42 -0
  31. package/dist/local-load.js +172 -0
  32. package/dist/migrate.d.ts +255 -0
  33. package/dist/migrate.js +542 -0
  34. package/dist/node.d.ts +11 -0
  35. package/dist/node.js +11 -0
  36. package/dist/reader.d.ts +18 -0
  37. package/dist/reader.js +1 -0
  38. package/dist/remote-exec.d.ts +274 -0
  39. package/dist/remote-exec.js +182 -0
  40. package/dist/resolve-roster.d.ts +28 -0
  41. package/dist/resolve-roster.js +108 -0
  42. package/dist/safety-merge-spec.d.ts +327 -0
  43. package/dist/safety-merge-spec.js +70 -0
  44. package/dist/scheduler-store-node.d.ts +72 -0
  45. package/dist/scheduler-store-node.js +119 -0
  46. package/dist/scheduler-store.d.ts +67 -0
  47. package/dist/scheduler-store.js +89 -0
  48. package/dist/secret-refs.d.ts +33 -0
  49. package/dist/secret-refs.js +48 -0
  50. package/dist/sha256.d.ts +16 -0
  51. package/dist/sha256.js +114 -0
  52. package/dist/skills-manifest.d.ts +12 -0
  53. package/dist/skills-manifest.js +54 -0
  54. package/dist/types.d.ts +13560 -0
  55. package/dist/types.js +2118 -0
  56. package/package.json +138 -0
package/LICENSE ADDED
@@ -0,0 +1,103 @@
1
+ Business Source License 1.1
2
+
3
+ Parameters
4
+
5
+ Licensor: clay (github.com/clayboby)
6
+ Licensed Work: @sema-agent/settings-schema (sema-registry-core)
7
+ The Licensed Work is (c) 2026 clay.
8
+ Additional Use Grant: You may make production use of the Licensed Work for
9
+ personal, educational, research, or other
10
+ non-commercial purposes. Commercial production use
11
+ requires a commercial license from the Licensor.
12
+ Change Date: 2030-07-13
13
+ Change License: Apache License, Version 2.0
14
+
15
+ For information about alternative licensing arrangements for the Licensed
16
+ Work, please contact the Licensor.
17
+
18
+ Notice
19
+
20
+ The Business Source License (this document, or the "License") is not an Open
21
+ Source license. However, the Licensed Work will eventually be made available
22
+ under an Open Source License, as stated in this License.
23
+
24
+ License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
25
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
26
+
27
+ -----------------------------------------------------------------------------
28
+
29
+ Terms
30
+
31
+ The Licensor hereby grants you the right to copy, modify, create derivative
32
+ works, redistribute, and make non-production use of the Licensed Work. The
33
+ Licensor may make an Additional Use Grant, above, permitting limited
34
+ production use.
35
+
36
+ Effective on the Change Date, or the fourth anniversary of the first publicly
37
+ available distribution of a specific version of the Licensed Work under this
38
+ License, whichever comes first, the Licensor hereby grants you rights under
39
+ the terms of the Change License, and the rights granted in the paragraph
40
+ above terminate.
41
+
42
+ If your use of the Licensed Work does not comply with the requirements
43
+ currently in effect as described in this License, you must purchase a
44
+ commercial license from the Licensor, its affiliated entities, or authorized
45
+ resellers, or you must refrain from using the Licensed Work.
46
+
47
+ All copies of the original and modified Licensed Work, and derivative works
48
+ of the Licensed Work, are subject to this License. This License applies
49
+ separately for each version of the Licensed Work and the Change Date may vary
50
+ for each version of the Licensed Work released by Licensor.
51
+
52
+ You must conspicuously display this License on each original or modified copy
53
+ of the Licensed Work. If you receive the Licensed Work in original or
54
+ modified form from a third party, the terms and conditions set forth in this
55
+ License apply to your use of that work.
56
+
57
+ Any use of the Licensed Work in violation of this License will automatically
58
+ terminate your rights under this License for the current and all other
59
+ versions of the Licensed Work.
60
+
61
+ This License does not grant you any right in any trademark or logo of
62
+ Licensor or its affiliates (provided that you may use a trademark or logo of
63
+ Licensor as expressly required by this License).
64
+
65
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
66
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
67
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
68
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
69
+ TITLE.
70
+
71
+ MariaDB hereby grants you permission to use this License's text to license
72
+ your works, and to refer to it using the trademark "Business Source License",
73
+ as long as you comply with the Covenants of Licensor below.
74
+
75
+ Covenants of Licensor
76
+
77
+ In consideration of the right to use this License's text and the "Business
78
+ Source License" name and trademark, Licensor covenants to MariaDB, and to all
79
+ other recipients of the licensed work to be provided by Licensor:
80
+
81
+ 1. To specify as the Change License the GPL Version 2.0 or any later version,
82
+ or a license that is compatible with GPL Version 2.0 or a later version,
83
+ where "compatible" means that software provided under the Change License can
84
+ be included in a program with software provided under GPL Version 2.0 or a
85
+ later version. Licensor may specify additional Change Licenses without
86
+ limitation.
87
+
88
+ 2. To either: (a) specify an additional grant of rights to use that does not
89
+ impose any additional restriction on the right granted in this License, as
90
+ the Additional Use Grant; or (b) insert the text "None".
91
+
92
+ 3. To specify a Change Date.
93
+
94
+ 4. Not to modify this License in any other way.
95
+
96
+ -----------------------------------------------------------------------------
97
+
98
+ Third-party notices
99
+
100
+ Versions of this package up to and including 1.277.1 were published under the
101
+ MIT License; those published copies remain governed by the license they were
102
+ distributed under. This package incorporates design and (where noted) prompt
103
+ content adapted from MIT-licensed projects — see NOTICE.md for attributions.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # @sema-agent/settings-schema
2
+
3
+ > **改名声明(1.0.0)**:本包自 **`1.0.0`** 起名为 `@sema-agent/settings-schema`;在此之前名为
4
+ > **`@sema-agent/registry-core`(最后一版 `0.19.0`)**,更早还曾是 `@sema-ai/registry-core`。
5
+ > **这是一次纯改名,不加料**:`0.19.0` → `1.0.0` 之间**零语义变更**——没有字段增删、没有校验收紧
6
+ > (无 `.strict()` 化)、没有默认值改动、没有行为变化;`exports` 的 **21 个 subpath 一个不动**
7
+ > (`.` / `./types` / `./node` / `./api/auth` / `./api/wire` …… 逐条 1:1 平移,只换包名前缀)。
8
+ > 版号跳到 `1.0.0` **不是**「契约进入稳定期」的语义信号——改名需要一个不与旧 `0.x` 段混淆的新起点而已。
9
+ >
10
+ > **消费方迁移(两步,注意版号段也要动)**:
11
+ > 1. `package.json` 依赖:`"@sema-agent/registry-core": "^0.19.0"` → `"@sema-agent/settings-schema": "^1.0.0"`。
12
+ > ⚠️ **只换名不换段会装不上** —— 新名的最低版本就是 `1.0.0`,`^0.19.0` 在新名下**没有任何匹配版本**,
13
+ > `npm install` 直接报 `No matching version found`。改完重生成 lockfile(`npm install` / `npm i --package-lock-only`)。
14
+ > 2. 代码 import 说明符:`@sema-agent/registry-core…` → `@sema-agent/settings-schema…`(**纯前缀替换**,
15
+ > 21 个 subpath 的后缀一个不动)。
16
+ >
17
+ > 这两步之外**什么都不用改**:没有 API 改名、没有类型改形、没有行为差异(实证见 CHANGELOG `1.0.0` 段的
18
+ > 发布物 diff)。
19
+ >
20
+ > 旧名 `@sema-agent/registry-core`(≤`0.19.0`)保持已发布状态、**冻结不再出新版**,后续会打 npm
21
+ > deprecate 标指向新名。
22
+
23
+ Engine-agnostic, zero-dependency contract layer for the sema stack — **one source of truth** for the
24
+ config domain across the center (the config-center product — today `sema-web` — the fleet admin), the
25
+ server (the workers), and the client (desktop/CLI) seams. No two copies of the schema.
26
+
27
+ > 命名说明:config-center 这个**产品**曾叫 `sema-registry`,现在是 `sema-web`(后续还会再改名)。
28
+ > 本**包**名与产品名解耦,**不随产品改名而动**(1.0.0 这次改名是本包自身的正名,与产品名无关)——
29
+ > 下文凡提「center / config-center 产品」即指该产品的当期实现。
30
+
31
+ - **Source of truth**: `github.com/sema-agent/sema-registry-core`, branch `main` (private org repo).
32
+ 仓名本步不动(只改包名)。
33
+ - **npm**: published as `@sema-agent/settings-schema` on the public npm registry
34
+ (≤`0.19.0` 发在旧名 `@sema-agent/registry-core` 下,冻结)。
35
+
36
+ The package is **pure**: zero `next`/`react`/server imports, zero DB drivers. Its only runtime dependency is
37
+ `zod` (a `dependency` pinned `^3.24.0` — the same range the host repos (config-center 产品 / `sema-server` / CLI) use, so
38
+ package managers dedup to a **single shared zod instance**, keeping cross-instance schema validation / `instanceof`
39
+ sound). `npm ls --prod` therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
40
+
41
+ ## What's in it
42
+
43
+ - **The zod contract** (`types.ts`): `DOMAIN_SCHEMAS` (17 domains — 0.10.0 adds `projects`, the project
44
+ identity ledger; 0.10.18 adds `prompts`; 0.19.0 adds `limits`, the cost/rate-limit publication slot),
45
+ `EffectiveConfig`, `EffectiveWire`,
46
+ `ENV_NAME`/`K8S_QUANTITY` boundaries, every domain's entry/config type.
47
+ - **Pure config fns** (`config-fns.ts`): `buildEffective` (deterministic — `updatedAt` is a required param),
48
+ `parseDomain`, `emptyEffective` (epoch-seeded), `statusRowsToPrune`, role helpers (`roleAtLeast`,
49
+ `domainWriteRole`, …), `resolveActiveTiers`(0.9.0 档位组→引擎 `RunnerDeps.tiers` 组存 `models.tierGroups`+`models.activeTierGroup`,组切换=换表下发), and the pure data
50
+ shapes (`User`, `PublishedSnapshot`, …).
51
+ - **Roster resolution** (`resolve-roster.ts`): `resolveEffectiveForWorker` → the per-worker wire payload.
52
+ - **Ref integrity** (`cross-domain.ts`): `refIntegrityIssues` + `siblingResolver` (the pure half — fleet
53
+ auto-placement stays in the config-center 产品).
54
+ - **Hashing** (`hash.ts`): `stableHash` (FNV-1a 64) for ETags / version detection.
55
+ - **Skills manifest** (`skills-manifest.ts`): content-addressing for the lazy-pull skill bodies.
56
+ - **Secret inventory** (`secret-refs.ts`): `collectEnvRefs` — every referenced env-NAME.
57
+ - **The narrow reader** (`reader.ts`): `ConfigReader` (3 methods) — the config-center 产品's fat `ConfigStore`
58
+ `extends` it; a local file source / test fixture implements it without stubbing ~32 methods.
59
+ - **`remoteExec` contract** (`remote-exec.ts`): the execution-substrate schema mirroring the service's
60
+ actual providers (`e2b` / `k8s` / `ssh` / `adb`), discriminated on `provider`; every secret an env-NAME ref.
61
+ - **Auth wire contract** (`api/auth.ts`, subpath `./api/auth`): the `/api/v1/auth/*` face — RFC 8628
62
+ device flow (code / token poll / approve consent) + rotating refresh + logout, the OAuth error envelope,
63
+ and the pure client helpers `normalizeUserCode` / `nextPollInterval`. Shipped M1 shapes frozen.
64
+ OpenAPI mirror: `docs/openapi-auth.yaml` (auth face only).
65
+ - **Auth bridge** (`api/auth-bridge.ts`, subpath `./api/auth-bridge`): the 鉴权桥 contract —
66
+ how a worker verifies a registry-minted user JWT: RS256-only, JWKS from `/api/auth/sso/jwks` with
67
+ kid-miss forced refresh + ≥60s throttle, exp/nbf clock skew (60s), 24h offline grace then fail-closed;
68
+ `VerifierConfig` / `VerifiedIdentity` / `AuthBridgeErrorCode` + pure decision fns
69
+ (`shouldForceJwksRefresh`, `jwksWithinOfflineGrace`, `classifyTimeClaims`, `toVerifiedIdentity`).
70
+ The normative rules R1–R10 in its header are the service verifier's implementation spec.
71
+ `VerifiedIdentity` carries an optional `scope` (the token's active tenant scope — see `api/scopes`).
72
+ - **Multi-tenant scopes** (`api/scopes.ts`, subpath `./api/scopes`): the scope contract — the `Scope`
73
+ entity (`{id,name,createdAt}`, reserved default `"global"` always exists), per-scope `ScopeMember`
74
+ rows (`{scopeId,principal,role}` on the existing viewer<editor<publisher<admin ladder), the optional
75
+ JWT `scope` claim (absent = `"global"`, fully backward compatible; refresh tokens are never
76
+ scope-bound), and the wire shapes for `GET/POST /api/v1/scopes`, `POST /api/v1/auth/scope`
77
+ (scope-switch mint, OAuth-style errors) and `PUT/DELETE /api/v1/scopes/{id}/members`. The DECISION
78
+ SEMANTICS live here as pure functions the registry imports (never re-implements):
79
+ `resolveTokenScope` (mint-time scope binding: 1 membership → it; several incl. global → global;
80
+ several without global → lexicographically first; none → `{scope:"global", role:instanceRole}`),
81
+ `canSwitchScope` (member or instance admin), `effectiveScopeRole` (instance admin is `"admin"` in
82
+ every scope) and `tokenScopeOf` (missing claim = `"global"`). OpenAPI mirror: `docs/openapi-scopes.yaml`.
83
+ - **Local file source** (`file-store.ts`, `file-edit.ts`): `FileConfigStore` reads `config.d/*.json` →
84
+ `EffectiveConfig`; `validateDomain` / `writeDomainFile` / `listRequiredEnvNames` are the TOC's
85
+ read/write/validate helpers. `getEffective({tolerant:true})` (0.10.12) isolates a malformed
86
+ catalog-domain file (that domain falls to its schema default, the error is listed in `domainErrors`)
87
+ instead of failing the whole read; gate domains (governance/rosters/entitlement/execution/runtime/
88
+ limits/workers) stay fail-loud even in tolerant mode. 0.19.0 adds a FAIL-LOUD sweep on the same read:
89
+ a `config.d/*.json` the local set will never read (a name that is not a domain, or a non-portable one)
90
+ and any top-level key a domain schema strips are both NAMED through `onWarning`
91
+ (`unread-config-file` / `unknown-keys-dropped`) instead of vanishing silently.
92
+
93
+ ## Secret boundary
94
+
95
+ Every secret field is an **env-NAME**, never a value. The `ENV_NAME` regex (`/^[A-Z_][A-Z0-9_]*$/`) rejects a
96
+ pasted credential. Values are resolved by the consumer from its own env (service env / k8s secret / the TOC
97
+ local `.env`). This package never carries a credential.
98
+
99
+ ## Local file layout (TOC)
100
+
101
+ ```
102
+ <root>/
103
+ config.d/
104
+ models.json rosters.json skills.json mcp.json scenarios.json plugins.json
105
+ collab.json runtime.json governance.json execution.json prompts.json limits.json
106
+ remote-exec.json # NOT a domain (the exec backend; read by loadRemoteExec)
107
+ .env # secret VALUES only (KEY=value); gitignored; never read into any *.json
108
+ ```
109
+
110
+ Fleet-only domains (`workers`, `hosts`, `systems`) are omitted on a single machine — they resolve to schema
111
+ defaults. Each `config.d/<domain>.json` is exactly that domain's zod object.
112
+
113
+ ## 发版纪律(semver)
114
+
115
+ - **0.x 破坏性变更必 bump minor**(0.7 → 0.8),不许塞 patch——`^0.7.0` 这类脱字号 range 在 0.x 下
116
+ 会自动收 patch,把 BREAKING 塞进 patch = 未 pin 精确版的下游 `npm install` 静默拿到破坏。
117
+ - patch 只装:additive 字段、修 bug、文档/测试。commit message 的 `feat!`/`BREAKING` 标记照旧,
118
+ 但标记不豁免版号义务。
119
+ - **历史注记(0.7.1,2026-07-10)**:0.7.1 携带了两件预协调 BREAKING(撤 governance effective 双写
120
+ 镜像、撤 QuotaLease 0.5 `tokens` 别名 shim)——名义上违反本条。当时下游(center/service 1.150.0)
121
+ 均已显式迁移、实际破坏面为零,PM 拍板不重发 0.8.0(纯扰动);本段即为该例外的收账与今后的规矩。
122
+ - 发布(单腿 npmjs,内网腿已废止):`npm run publish:npm`;发后必须 curl 直打 npmjs 复验 shasum。
123
+
124
+ ## 贡献指引
125
+
126
+ - 真源 = `sema-agent/sema-registry-core` org 仓 `main`,所有变更直接落 org main(或 PR 进 main);
127
+ 不再向任何个人位/镜像位 push。
128
+ - 提交门 = `npm run build` + `npx vitest run`;改域/契约后必跑 `npm run e2e`(examples/toc-e2e.mjs)。
129
+ - 契约破坏面动之前先盘 center / server / CLI 三方消费点;0.x 破坏性变更必 bump minor(见上节)。
@@ -0,0 +1,331 @@
1
+ /**
2
+ * `api/auth-bridge` — the 鉴权桥 contract: how a WORKER (sema-server / any data-plane service) verifies a
3
+ * registry-minted USER access JWT and maps it into its existing `x-agent-principal` identity system.
4
+ * (REGISTRY-REBUILD-DESIGN §5, M3 — registry half = JWKS already shipped; service half = ONE verifier
5
+ * middleware implementing the rules below. This file is the schema + normative spec the service copies.)
6
+ *
7
+ * TRUST MODEL (single trust root, zero new worker config): the worker already trusts the registry for its
8
+ * effective config; it extends the SAME trust to the registry's JWKS. A bearer token that is a well-formed
9
+ * JWT and verifies against that JWKS with the checks below IS the user — `sub` feeds x-agent-principal
10
+ * directly (owner semantics / costQuota / entitlement are already principal-keyed). Existing
11
+ * SERVICE_AUTH_TOKENS / wpt_ bearer branches are untouched; this is an ADDITIONAL branch tried when the
12
+ * bearer LOOKS like a JWT (three dot-separated base64url segments).
13
+ *
14
+ * ════════════════════════════════════════════════════════════════════════════════════════════════════════
15
+ * NORMATIVE VERIFICATION RULES — the service verifier MUST implement ALL of these (copy as its spec):
16
+ *
17
+ * R1 ALGORITHM WHITELIST. Accept `alg: RS256` ONLY (AUTH_BRIDGE_ALGS). Reject `none`, every HS* (an
18
+ * HS-signed token verified against a PUBLIC key = classic alg-confusion forgery), the ES and PS families, and any
19
+ * token whose header alg is absent. Pin the list in the JWT library call — never read alg from the
20
+ * token to decide how to verify.
21
+ *
22
+ * R2 KEY SOURCE. Verification keys come from the registry JWKS endpoint `GET {registryBaseUrl}` +
23
+ * REGISTRY_JWKS_PATH ("/api/auth/sso/jwks"; response shape = `Jwks` below, public members only:
24
+ * kty/n/e/kid/use/alg). Match the token header `kid` against cached keys.
25
+ *
26
+ * R3 KID-MISS FORCED REFRESH + THROTTLE. kid found in cache → use the cached key (no fetch on the hot
27
+ * path). kid NOT in cache → force ONE JWKS refetch, then retry the match (this is the key-ROTATION
28
+ * window: a freshly rotated registry would otherwise reject every new token until the cache expired).
29
+ * Forced refreshes are throttled to at most one per `jwksRefreshMinIntervalSec` (default 60s) —
30
+ * otherwise an attacker spraying random-kid tokens turns the verifier into a JWKS-fetch DoS cannon.
31
+ * Within the throttle window an unknown kid fails as `invalid_signature` WITHOUT fetching.
32
+ * (Decision function: `shouldForceJwksRefresh`.)
33
+ *
34
+ * R4 ISSUER PIN. `iss` MUST equal the CONFIGURED issuer exactly (VerifierConfig.issuer = the registry's
35
+ * SSO_ISSUER, trailing slash stripped on both sides). A missing or different iss → `unknown_issuer`.
36
+ * Never derive the expected issuer from the token or from request headers.
37
+ *
38
+ * R5 TIME WINDOW WITH SKEW. `exp` is REQUIRED: reject when nowSec > exp + clockSkewSec. `nbf`, when
39
+ * present: reject when nowSec < nbf - clockSkewSec. clockSkewSec default 60 (DEFAULT_CLOCK_SKEW_SEC).
40
+ * Both failures classify as `expired`. (Decision function: `classifyTimeClaims`.)
41
+ *
42
+ * R6 PRINCIPAL. `sub` MUST be a non-empty string — it becomes `VerifiedIdentity.principal` and feeds the
43
+ * existing x-agent-principal pipeline verbatim. Empty/missing sub → `malformed`.
44
+ *
45
+ * R7 AUDIENCE (optional). When VerifierConfig.audience is set, `aud` must contain it (string equal or
46
+ * array member); mismatch → `invalid_signature`-class rejection is WRONG — classify as `malformed`
47
+ * (the token is valid but not FOR this worker). When audience is unset, `aud` is ignored.
48
+ *
49
+ * R8 OFFLINE GRACE / FAIL-CLOSED. JWKS fetch fails (network/5xx/malformed body): keys already cached
50
+ * remain USABLE for up to `offlineGraceSec` (default 24h, DEFAULT_OFFLINE_GRACE_SEC) measured from the
51
+ * last SUCCESSFUL fetch — a registry blip must not take the whole data plane down. Past the grace, or
52
+ * with no cache at all, verification fails CLOSED as `jwks_unavailable` (never fail-open, never skip
53
+ * signature checks). (Decision function: `jwksWithinOfflineGrace`.)
54
+ *
55
+ * R9 OUTPUT. Success → `VerifiedIdentity { principal: sub, role: token.role, grantId: token.jti,
56
+ * scope: token.scope }` (`scope` = the multi-tenant active scope claim, `api/scopes`; absent = "global").
57
+ * `role` is the registry RBAC role claim (string, informational for the worker's own authz mapping);
58
+ * `grantId` is the jti when the registry stamps one (absent today — optional forever). Failure → one
59
+ * of AUTH_BRIDGE_ERROR_CODES; the HTTP surface answers 401 with a GENERIC message (no error detail
60
+ * disclosure to the caller; the code goes to logs/metrics only).
61
+ *
62
+ * R10 NO CREDENTIAL LOGGING. Never log the token, any signature segment, or JWKS key material. Log
63
+ * kid + error code + iss at most.
64
+ * ════════════════════════════════════════════════════════════════════════════════════════════════════════
65
+ *
66
+ * Pure zod contract + pure decision functions — zero IO, zero crypto here (the service brings its own JWT
67
+ * library; THESE shapes pin down its configuration), browser-safe.
68
+ */
69
+ import { z } from "zod";
70
+ /** The registry JWKS endpoint, relative to the registry origin (shipped; public by design, RFC 7517 §5). */
71
+ export declare const REGISTRY_JWKS_PATH = "/api/auth/sso/jwks";
72
+ /** R1 — the ONLY acceptable JWS algorithms. */
73
+ export declare const AUTH_BRIDGE_ALGS: readonly ["RS256"];
74
+ export type AuthBridgeAlg = (typeof AUTH_BRIDGE_ALGS)[number];
75
+ /** R5 — default tolerated clock skew (seconds) on exp/nbf. */
76
+ export declare const DEFAULT_CLOCK_SKEW_SEC = 60;
77
+ /** R3 — default minimum seconds between FORCED (kid-miss) JWKS refetches. */
78
+ export declare const DEFAULT_JWKS_REFRESH_MIN_INTERVAL_SEC = 60;
79
+ /** R8 — default seconds a cached JWKS stays usable after fetches start failing (24h), then fail-closed. */
80
+ export declare const DEFAULT_OFFLINE_GRACE_SEC: number;
81
+ /** One RSA public JWK as the registry publishes it (public members ONLY — a `d`/`p`/`q` here is a registry
82
+ * bug, but the verifier MUST still never use/echo private members). */
83
+ export declare const RegistryJwk: z.ZodObject<{
84
+ kty: z.ZodLiteral<"RSA">;
85
+ /** modulus, base64url */
86
+ n: z.ZodString;
87
+ /** exponent, base64url */
88
+ e: z.ZodString;
89
+ /** RFC 7638 thumbprint of the public JWK — the rotation-stable key id tokens carry in their header. */
90
+ kid: z.ZodString;
91
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
92
+ alg: z.ZodOptional<z.ZodEnum<["RS256"]>>;
93
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
94
+ kty: z.ZodLiteral<"RSA">;
95
+ /** modulus, base64url */
96
+ n: z.ZodString;
97
+ /** exponent, base64url */
98
+ e: z.ZodString;
99
+ /** RFC 7638 thumbprint of the public JWK — the rotation-stable key id tokens carry in their header. */
100
+ kid: z.ZodString;
101
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
102
+ alg: z.ZodOptional<z.ZodEnum<["RS256"]>>;
103
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
104
+ kty: z.ZodLiteral<"RSA">;
105
+ /** modulus, base64url */
106
+ n: z.ZodString;
107
+ /** exponent, base64url */
108
+ e: z.ZodString;
109
+ /** RFC 7638 thumbprint of the public JWK — the rotation-stable key id tokens carry in their header. */
110
+ kid: z.ZodString;
111
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
112
+ alg: z.ZodOptional<z.ZodEnum<["RS256"]>>;
113
+ }, z.ZodTypeAny, "passthrough">>;
114
+ export type RegistryJwk = z.infer<typeof RegistryJwk>;
115
+ /** The JWKS document. `{keys: []}` is VALID (registry with SSO disabled) — every token then fails as
116
+ * `invalid_signature` (there is a registry, it just mints nothing). */
117
+ export declare const RegistryJwks: z.ZodObject<{
118
+ keys: z.ZodArray<z.ZodObject<{
119
+ kty: z.ZodLiteral<"RSA">;
120
+ /** modulus, base64url */
121
+ n: z.ZodString;
122
+ /** exponent, base64url */
123
+ e: z.ZodString;
124
+ /** RFC 7638 thumbprint of the public JWK — the rotation-stable key id tokens carry in their header. */
125
+ kid: z.ZodString;
126
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
127
+ alg: z.ZodOptional<z.ZodEnum<["RS256"]>>;
128
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
129
+ kty: z.ZodLiteral<"RSA">;
130
+ /** modulus, base64url */
131
+ n: z.ZodString;
132
+ /** exponent, base64url */
133
+ e: z.ZodString;
134
+ /** RFC 7638 thumbprint of the public JWK — the rotation-stable key id tokens carry in their header. */
135
+ kid: z.ZodString;
136
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
137
+ alg: z.ZodOptional<z.ZodEnum<["RS256"]>>;
138
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
139
+ kty: z.ZodLiteral<"RSA">;
140
+ /** modulus, base64url */
141
+ n: z.ZodString;
142
+ /** exponent, base64url */
143
+ e: z.ZodString;
144
+ /** RFC 7638 thumbprint of the public JWK — the rotation-stable key id tokens carry in their header. */
145
+ kid: z.ZodString;
146
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
147
+ alg: z.ZodOptional<z.ZodEnum<["RS256"]>>;
148
+ }, z.ZodTypeAny, "passthrough">>, "many">;
149
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
150
+ keys: z.ZodArray<z.ZodObject<{
151
+ kty: z.ZodLiteral<"RSA">;
152
+ /** modulus, base64url */
153
+ n: z.ZodString;
154
+ /** exponent, base64url */
155
+ e: z.ZodString;
156
+ /** RFC 7638 thumbprint of the public JWK — the rotation-stable key id tokens carry in their header. */
157
+ kid: z.ZodString;
158
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
159
+ alg: z.ZodOptional<z.ZodEnum<["RS256"]>>;
160
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
161
+ kty: z.ZodLiteral<"RSA">;
162
+ /** modulus, base64url */
163
+ n: z.ZodString;
164
+ /** exponent, base64url */
165
+ e: z.ZodString;
166
+ /** RFC 7638 thumbprint of the public JWK — the rotation-stable key id tokens carry in their header. */
167
+ kid: z.ZodString;
168
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
169
+ alg: z.ZodOptional<z.ZodEnum<["RS256"]>>;
170
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
171
+ kty: z.ZodLiteral<"RSA">;
172
+ /** modulus, base64url */
173
+ n: z.ZodString;
174
+ /** exponent, base64url */
175
+ e: z.ZodString;
176
+ /** RFC 7638 thumbprint of the public JWK — the rotation-stable key id tokens carry in their header. */
177
+ kid: z.ZodString;
178
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
179
+ alg: z.ZodOptional<z.ZodEnum<["RS256"]>>;
180
+ }, z.ZodTypeAny, "passthrough">>, "many">;
181
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
182
+ keys: z.ZodArray<z.ZodObject<{
183
+ kty: z.ZodLiteral<"RSA">;
184
+ /** modulus, base64url */
185
+ n: z.ZodString;
186
+ /** exponent, base64url */
187
+ e: z.ZodString;
188
+ /** RFC 7638 thumbprint of the public JWK — the rotation-stable key id tokens carry in their header. */
189
+ kid: z.ZodString;
190
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
191
+ alg: z.ZodOptional<z.ZodEnum<["RS256"]>>;
192
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
193
+ kty: z.ZodLiteral<"RSA">;
194
+ /** modulus, base64url */
195
+ n: z.ZodString;
196
+ /** exponent, base64url */
197
+ e: z.ZodString;
198
+ /** RFC 7638 thumbprint of the public JWK — the rotation-stable key id tokens carry in their header. */
199
+ kid: z.ZodString;
200
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
201
+ alg: z.ZodOptional<z.ZodEnum<["RS256"]>>;
202
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
203
+ kty: z.ZodLiteral<"RSA">;
204
+ /** modulus, base64url */
205
+ n: z.ZodString;
206
+ /** exponent, base64url */
207
+ e: z.ZodString;
208
+ /** RFC 7638 thumbprint of the public JWK — the rotation-stable key id tokens carry in their header. */
209
+ kid: z.ZodString;
210
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
211
+ alg: z.ZodOptional<z.ZodEnum<["RS256"]>>;
212
+ }, z.ZodTypeAny, "passthrough">>, "many">;
213
+ }, z.ZodTypeAny, "passthrough">>;
214
+ export type RegistryJwks = z.infer<typeof RegistryJwks>;
215
+ /** Everything the service verifier is allowed to be configured with. In the zero-new-config deployment the
216
+ * worker derives `jwksUrl` from the registry base URL it already has (config pull) + REGISTRY_JWKS_PATH,
217
+ * and `issuer` from the same registry identity — but both stay explicit here so a split deployment
218
+ * (issuer behind a proxy ≠ fetch URL) is expressible. */
219
+ export declare const VerifierConfig: z.ZodObject<{
220
+ /** R4 — the EXACT expected `iss` (the registry's SSO_ISSUER). Compare with trailing slash stripped. */
221
+ issuer: z.ZodString;
222
+ /** R2 — absolute JWKS URL. Default: issuer (or the worker's registry base URL) + REGISTRY_JWKS_PATH. */
223
+ jwksUrl: z.ZodOptional<z.ZodString>;
224
+ /** R7 — when set, `aud` must contain this value; unset = aud ignored. */
225
+ audience: z.ZodOptional<z.ZodString>;
226
+ /** R5 — seconds of exp/nbf tolerance. Default DEFAULT_CLOCK_SKEW_SEC (60). */
227
+ clockSkewSec: z.ZodDefault<z.ZodNumber>;
228
+ /** R3 — min seconds between forced kid-miss refetches. Default 60. */
229
+ jwksRefreshMinIntervalSec: z.ZodDefault<z.ZodNumber>;
230
+ /** R8 — seconds a stale cache survives fetch failures before fail-closed. Default 86400 (24h). */
231
+ offlineGraceSec: z.ZodDefault<z.ZodNumber>;
232
+ }, "strip", z.ZodTypeAny, {
233
+ issuer: string;
234
+ clockSkewSec: number;
235
+ jwksRefreshMinIntervalSec: number;
236
+ offlineGraceSec: number;
237
+ jwksUrl?: string | undefined;
238
+ audience?: string | undefined;
239
+ }, {
240
+ issuer: string;
241
+ jwksUrl?: string | undefined;
242
+ audience?: string | undefined;
243
+ clockSkewSec?: number | undefined;
244
+ jwksRefreshMinIntervalSec?: number | undefined;
245
+ offlineGraceSec?: number | undefined;
246
+ }>;
247
+ export type VerifierConfig = z.infer<typeof VerifierConfig>;
248
+ /** The pre-parse (all-optional-defaults) input shape, for consumers assembling config by hand. */
249
+ export type VerifierConfigInput = z.input<typeof VerifierConfig>;
250
+ /** R9 failure classification — for logs/metrics/tests; the HTTP surface stays a generic 401. */
251
+ export declare const AUTH_BRIDGE_ERROR_CODES: readonly ["expired", "invalid_signature", "unknown_issuer", "jwks_unavailable", "malformed"];
252
+ export type AuthBridgeErrorCode = (typeof AUTH_BRIDGE_ERROR_CODES)[number];
253
+ /** R9 success output — what the verifier middleware hands the existing principal pipeline. */
254
+ export declare const VerifiedIdentity: z.ZodObject<{
255
+ /** = token `sub`. Feeds x-agent-principal / owner semantics / costQuota / entitlement keying verbatim. */
256
+ principal: z.ZodString;
257
+ /** = token `role` claim (registry RBAC: admin/editor/viewer/…) — informational; the worker maps it to
258
+ * its own authz, it does NOT replace worker-side entitlement checks. */
259
+ role: z.ZodOptional<z.ZodString>;
260
+ /** = token `jti` when present (the auth_grants grant id — audit correlation). Absent today; optional forever. */
261
+ grantId: z.ZodOptional<z.ZodString>;
262
+ /** = token `scope` claim when present (the active tenant scope, `api/scopes`) — transparently forwarded
263
+ * to the worker's principal pipeline. ABSENT means the pre-scope/default token: semantically "global"
264
+ * (use `tokenScopeOf` from api/scopes to normalize). Optional forever — backward compatible. */
265
+ scope: z.ZodOptional<z.ZodString>;
266
+ }, "strip", z.ZodTypeAny, {
267
+ principal: string;
268
+ role?: string | undefined;
269
+ scope?: string | undefined;
270
+ grantId?: string | undefined;
271
+ }, {
272
+ principal: string;
273
+ role?: string | undefined;
274
+ scope?: string | undefined;
275
+ grantId?: string | undefined;
276
+ }>;
277
+ export type VerifiedIdentity = z.infer<typeof VerifiedIdentity>;
278
+ /** The verifier's total result — exactly one of ok/error. */
279
+ export type AuthBridgeResult = {
280
+ ok: true;
281
+ identity: VerifiedIdentity;
282
+ } | {
283
+ ok: false;
284
+ error: AuthBridgeErrorCode;
285
+ };
286
+ /** What the R3 cache logic needs to know — all times epoch ms supplied by the caller (pure, no clock). */
287
+ export interface JwksCacheSnapshot {
288
+ /** kids currently in the cache (empty = never fetched / fetched empty). */
289
+ cachedKids: readonly string[];
290
+ /** epoch ms of the last fetch ATTEMPT (success or failure); undefined = never attempted. */
291
+ lastFetchAttemptMs?: number;
292
+ /** epoch ms of the last SUCCESSFUL fetch; undefined = never succeeded. */
293
+ lastFetchSuccessMs?: number;
294
+ nowMs: number;
295
+ }
296
+ /**
297
+ * R3 — should a token bearing `kid` trigger a forced JWKS refetch?
298
+ * true ⇔ the kid is not cached AND we are outside the refresh throttle window.
299
+ * false ⇒ verify against the cache as-is (cached kid hit), or fail `invalid_signature` (unknown kid,
300
+ * throttled) without fetching.
301
+ */
302
+ export declare function shouldForceJwksRefresh(kid: string, cache: JwksCacheSnapshot, cfg: Pick<VerifierConfig, "jwksRefreshMinIntervalSec">): boolean;
303
+ /**
304
+ * R8 — with the LATEST fetch having failed, may the cached keys still be used?
305
+ * true ⇔ there WAS a successful fetch and it is at most offlineGraceSec old.
306
+ * false ⇒ fail closed: `jwks_unavailable`.
307
+ */
308
+ export declare function jwksWithinOfflineGrace(cache: Pick<JwksCacheSnapshot, "lastFetchSuccessMs" | "nowMs">, cfg: Pick<VerifierConfig, "offlineGraceSec">): boolean;
309
+ /**
310
+ * R5 — classify the token's time window. `exp` REQUIRED (a JWT without exp is not acceptable here —
311
+ * registry tokens always carry one; classify a missing exp as "expired" so it can never live forever).
312
+ * All values in SECONDS (JWT NumericDate).
313
+ */
314
+ export declare function classifyTimeClaims(claims: {
315
+ exp?: number;
316
+ nbf?: number;
317
+ }, nowSec: number, clockSkewSec: number): "ok" | "expired";
318
+ /**
319
+ * R6/R7/R9 — map a signature-verified payload to the identity, or reject. Pure: call AFTER the JWT
320
+ * library has verified signature (R1/R2/R3), issuer (R4) and time (R5).
321
+ */
322
+ export declare function toVerifiedIdentity(payload: {
323
+ sub?: unknown;
324
+ role?: unknown;
325
+ jti?: unknown;
326
+ aud?: unknown;
327
+ scope?: unknown;
328
+ }, cfg?: Pick<VerifierConfig, "audience">): AuthBridgeResult;
329
+ /** Cheap pre-filter: does this bearer LOOK like a JWT (three non-empty dot-separated segments)? Use it to
330
+ * route between the existing static-token branch and this verifier — NOT as any kind of validation. */
331
+ export declare function looksLikeJwt(bearer: string): boolean;