@sema-ai/registry-core 0.10.3
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 +131 -0
- package/README.md +84 -0
- package/dist/api/auth-bridge.d.ts +332 -0
- package/dist/api/auth-bridge.d.ts.map +1 -0
- package/dist/api/auth-bridge.js +211 -0
- package/dist/api/auth-bridge.js.map +1 -0
- package/dist/api/auth.d.ts +258 -0
- package/dist/api/auth.d.ts.map +1 -0
- package/dist/api/auth.js +204 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/scopes.d.ts +353 -0
- package/dist/api/scopes.d.ts.map +1 -0
- package/dist/api/scopes.js +229 -0
- package/dist/api/scopes.js.map +1 -0
- package/dist/bundle.d.ts +14 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +68 -0
- package/dist/bundle.js.map +1 -0
- package/dist/config-fns.d.ts +236 -0
- package/dist/config-fns.d.ts.map +1 -0
- package/dist/config-fns.js +272 -0
- package/dist/config-fns.js.map +1 -0
- package/dist/cross-domain.d.ts +35 -0
- package/dist/cross-domain.d.ts.map +1 -0
- package/dist/cross-domain.js +119 -0
- package/dist/cross-domain.js.map +1 -0
- package/dist/file-edit.d.ts +37 -0
- package/dist/file-edit.d.ts.map +1 -0
- package/dist/file-edit.js +126 -0
- package/dist/file-edit.js.map +1 -0
- package/dist/file-store.d.ts +39 -0
- package/dist/file-store.d.ts.map +1 -0
- package/dist/file-store.js +142 -0
- package/dist/file-store.js.map +1 -0
- package/dist/fleet.d.ts +479 -0
- package/dist/fleet.d.ts.map +1 -0
- package/dist/fleet.js +303 -0
- package/dist/fleet.js.map +1 -0
- package/dist/hash.d.ts +33 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +60 -0
- package/dist/hash.js.map +1 -0
- package/dist/hooks.d.ts +5478 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +628 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/local-load.d.ts +33 -0
- package/dist/local-load.d.ts.map +1 -0
- package/dist/local-load.js +164 -0
- package/dist/local-load.js.map +1 -0
- package/dist/migrate.d.ts +245 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +543 -0
- package/dist/migrate.js.map +1 -0
- package/dist/node.d.ts +12 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +12 -0
- package/dist/node.js.map +1 -0
- package/dist/reader.d.ts +19 -0
- package/dist/reader.d.ts.map +1 -0
- package/dist/reader.js +2 -0
- package/dist/reader.js.map +1 -0
- package/dist/remote-exec.d.ts +275 -0
- package/dist/remote-exec.d.ts.map +1 -0
- package/dist/remote-exec.js +183 -0
- package/dist/remote-exec.js.map +1 -0
- package/dist/resolve-roster.d.ts +29 -0
- package/dist/resolve-roster.d.ts.map +1 -0
- package/dist/resolve-roster.js +105 -0
- package/dist/resolve-roster.js.map +1 -0
- package/dist/safety-merge-spec.d.ts +328 -0
- package/dist/safety-merge-spec.d.ts.map +1 -0
- package/dist/safety-merge-spec.js +65 -0
- package/dist/safety-merge-spec.js.map +1 -0
- package/dist/scheduler-store-node.d.ts +15 -0
- package/dist/scheduler-store-node.d.ts.map +1 -0
- package/dist/scheduler-store-node.js +43 -0
- package/dist/scheduler-store-node.js.map +1 -0
- package/dist/scheduler-store.d.ts +65 -0
- package/dist/scheduler-store.d.ts.map +1 -0
- package/dist/scheduler-store.js +59 -0
- package/dist/scheduler-store.js.map +1 -0
- package/dist/secret-refs.d.ts +34 -0
- package/dist/secret-refs.d.ts.map +1 -0
- package/dist/secret-refs.js +49 -0
- package/dist/secret-refs.js.map +1 -0
- package/dist/sha256.d.ts +17 -0
- package/dist/sha256.d.ts.map +1 -0
- package/dist/sha256.js +115 -0
- package/dist/sha256.js.map +1 -0
- package/dist/skills-manifest.d.ts +13 -0
- package/dist/skills-manifest.d.ts.map +1 -0
- package/dist/skills-manifest.js +55 -0
- package/dist/skills-manifest.js.map +1 -0
- package/dist/types.d.ts +9911 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1727 -0
- package/dist/types.js.map +1 -0
- package/package.json +130 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `api/scopes` — the MULTI-TENANT SCOPE wire contract for the registry (/api/v1/scopes* +
|
|
3
|
+
* /api/v1/auth/scope). Frozen here so sema-registry (the implementation), the orchestrator, workers and
|
|
4
|
+
* the sema shell import ONE source of truth — same "契约先行" discipline as `api/auth`.
|
|
5
|
+
*
|
|
6
|
+
* THE SCOPE MODEL (normative — the registry implements EXACTLY this):
|
|
7
|
+
*
|
|
8
|
+
* - A **scope** is a tenant boundary: `{id, name, createdAt}`. `id` is a slug (SCOPE_ID_REGEX).
|
|
9
|
+
* The reserved id `"global"` (GLOBAL_SCOPE) is the DEFAULT scope: it always exists, is never
|
|
10
|
+
* created via the API (ScopeCreateRequest rejects it) and is never deleted.
|
|
11
|
+
* - **Membership** is per-scope: `{scopeId, principal, role}` where role is the existing 4-level
|
|
12
|
+
* RBAC ladder (viewer < editor < publisher < admin). The SAME principal may be `editor` in scope A
|
|
13
|
+
* and `viewer` in scope B.
|
|
14
|
+
* - The **instance-level** `users.role` is unchanged and orthogonal: an instance `admin` administers
|
|
15
|
+
* ALL scopes — inside any scope their effective role is always `"admin"` (see effectiveScopeRole).
|
|
16
|
+
* - The **access JWT** gains an OPTIONAL `scope` claim (SCOPE_CLAIM) binding the token to its active
|
|
17
|
+
* scope. A token WITHOUT the claim means `"global"` (tokenScopeOf) — full backward compatibility:
|
|
18
|
+
* every pre-scope token keeps working, scoped to global. The refresh token is NOT scope-bound;
|
|
19
|
+
* refresh re-mints per resolveTokenScope, and the client re-switches if it wants another scope.
|
|
20
|
+
*
|
|
21
|
+
* ENDPOINTS (paths in SCOPES_V1_PATHS / scopeMembersPath):
|
|
22
|
+
*
|
|
23
|
+
* GET /api/v1/scopes → ScopesListResponse {scopes:[{id,name,role}]} — the CALLER's
|
|
24
|
+
* scopes with their per-scope effective role (instance admin sees ALL scopes, role "admin").
|
|
25
|
+
* POST /api/v1/auth/scope → switch active scope: ScopeSwitchRequest {scope} →
|
|
26
|
+
* ScopeSwitchResponse {access_token, token_type:"Bearer", expires_in, scope, role}; errors are
|
|
27
|
+
* OAuth-style OAuthErrorResponse (`invalid_request` = malformed / unknown scope shape,
|
|
28
|
+
* `access_denied` = caller is not a member and not an instance admin — canSwitchScope).
|
|
29
|
+
* Auth: a valid access token (any scope). Mints a NEW access token bound to `scope`; the
|
|
30
|
+
* caller's refresh grant is untouched.
|
|
31
|
+
* POST /api/v1/scopes → create a scope (instance admin only): ScopeCreateRequest
|
|
32
|
+
* {id,name} → ScopeCreateResponse (the created Scope).
|
|
33
|
+
* PUT /api/v1/scopes/{id}/members → upsert a member (instance admin only): ScopeMemberUpsertRequest
|
|
34
|
+
* {principal, role} → ScopeMemberUpsertResponse (the stored membership row).
|
|
35
|
+
* DELETE /api/v1/scopes/{id}/members → remove a member (instance admin only): ScopeMemberDeleteRequest
|
|
36
|
+
* {principal} → ScopeMemberDeleteResponse {ok:true} (idempotent — removing a non-member is 200).
|
|
37
|
+
*
|
|
38
|
+
* WIRE CASING: /api/v1/auth/scope is part of the OAuth face — snake_case on the wire (access_token,
|
|
39
|
+
* expires_in), matching `api/auth`. The /api/v1/scopes management face uses this package's camelCase
|
|
40
|
+
* (createdAt, scopeId) — it is a registry management API, not an OAuth endpoint.
|
|
41
|
+
*
|
|
42
|
+
* Pure zod contract + pure decision functions — zero IO, browser-safe. The decision functions
|
|
43
|
+
* (resolveTokenScope / canSwitchScope / effectiveScopeRole) are THE semantics: the registry
|
|
44
|
+
* implementation imports and calls these — it must not re-implement them.
|
|
45
|
+
*/
|
|
46
|
+
import { z } from "zod";
|
|
47
|
+
import { USER_ROLES } from "../config-fns.js";
|
|
48
|
+
import { ACCESS_TOKEN_TTL_SECONDS } from "./auth.js";
|
|
49
|
+
// ── constants ────────────────────────────────────────────────────────────────────────────────────────────
|
|
50
|
+
/** The reserved default scope. Always exists; every token without a `scope` claim lives here. */
|
|
51
|
+
export const GLOBAL_SCOPE = "global";
|
|
52
|
+
/** Scope ids that can never be created (or deleted) through the API. */
|
|
53
|
+
export const RESERVED_SCOPE_IDS = [GLOBAL_SCOPE];
|
|
54
|
+
/** Slug shape for scope ids: lowercase alphanumeric + inner dashes, 1–64 chars, no leading/trailing dash. */
|
|
55
|
+
export const SCOPE_ID_REGEX = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
|
|
56
|
+
/** The scope endpoints, relative to the registry origin. `members` needs the scope id — use scopeMembersPath. */
|
|
57
|
+
export const SCOPES_V1_PATHS = {
|
|
58
|
+
/** GET (list) + POST (create). */
|
|
59
|
+
scopes: "/api/v1/scopes",
|
|
60
|
+
/** POST — mint a new access token bound to another scope. */
|
|
61
|
+
authScope: "/api/v1/auth/scope",
|
|
62
|
+
};
|
|
63
|
+
/** The JWT claim carrying the token's active scope. OPTIONAL — absent means GLOBAL_SCOPE (tokenScopeOf). */
|
|
64
|
+
export const SCOPE_CLAIM = "scope";
|
|
65
|
+
/** `/api/v1/scopes/{id}/members` for PUT (upsert) and DELETE (remove). */
|
|
66
|
+
export function scopeMembersPath(scopeId) {
|
|
67
|
+
return `${SCOPES_V1_PATHS.scopes}/${encodeURIComponent(scopeId)}/members`;
|
|
68
|
+
}
|
|
69
|
+
/** True for ids the API refuses to create/delete ("global"). */
|
|
70
|
+
export function isReservedScopeId(id) {
|
|
71
|
+
return RESERVED_SCOPE_IDS.includes(id);
|
|
72
|
+
}
|
|
73
|
+
// ── core shapes ──────────────────────────────────────────────────────────────────────────────────────────
|
|
74
|
+
/** A scope id on the wire — slug-validated. (The reserved "global" IS a valid ScopeId: it exists as an
|
|
75
|
+
* entity everywhere except the create endpoint.) */
|
|
76
|
+
export const ScopeId = z.string().regex(SCOPE_ID_REGEX, "scope id must be a slug: [a-z0-9-], 1-64 chars, no edge dash");
|
|
77
|
+
/** The per-scope role ladder = the existing instance RBAC ladder (viewer < editor < publisher < admin). */
|
|
78
|
+
export const ScopeRole = z.enum(USER_ROLES);
|
|
79
|
+
/** The scope entity. */
|
|
80
|
+
export const Scope = z
|
|
81
|
+
.object({
|
|
82
|
+
id: ScopeId,
|
|
83
|
+
/** Human display name (non-unique, non-secret). */
|
|
84
|
+
name: z.string().min(1).max(200),
|
|
85
|
+
/** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
|
|
86
|
+
createdAt: z.number(),
|
|
87
|
+
})
|
|
88
|
+
.passthrough();
|
|
89
|
+
/** One membership row: `principal` holds `role` inside `scopeId`. Per-scope — the same principal may
|
|
90
|
+
* hold different roles in different scopes. */
|
|
91
|
+
export const ScopeMember = z
|
|
92
|
+
.object({
|
|
93
|
+
scopeId: ScopeId,
|
|
94
|
+
/** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
|
|
95
|
+
principal: z.string().min(1),
|
|
96
|
+
role: ScopeRole,
|
|
97
|
+
})
|
|
98
|
+
.passthrough();
|
|
99
|
+
// ── GET /api/v1/scopes ───────────────────────────────────────────────────────────────────────────────────
|
|
100
|
+
/** One list entry: a scope the caller can act in, with the caller's EFFECTIVE role there
|
|
101
|
+
* (effectiveScopeRole — instance admins list every scope with role "admin"). */
|
|
102
|
+
export const ScopeListEntry = z
|
|
103
|
+
.object({
|
|
104
|
+
id: ScopeId,
|
|
105
|
+
name: z.string().min(1),
|
|
106
|
+
role: ScopeRole,
|
|
107
|
+
})
|
|
108
|
+
.passthrough();
|
|
109
|
+
export const ScopesListResponse = z.object({ scopes: z.array(ScopeListEntry) }).passthrough();
|
|
110
|
+
// ── POST /api/v1/auth/scope (switch active scope — OAuth face, snake_case) ──────────────────────────────
|
|
111
|
+
/** Request: the target scope. Errors: `invalid_request` (malformed), `access_denied` (not a member and
|
|
112
|
+
* not an instance admin — canSwitchScope false). */
|
|
113
|
+
export const ScopeSwitchRequest = z
|
|
114
|
+
.object({
|
|
115
|
+
scope: ScopeId,
|
|
116
|
+
})
|
|
117
|
+
.passthrough();
|
|
118
|
+
/** A fresh access token bound to the requested scope (`scope` claim = `scope` field below). The refresh
|
|
119
|
+
* grant is untouched — this endpoint never returns a refresh_token. `role` = the caller's effective role
|
|
120
|
+
* inside the new scope (effectiveScopeRole). */
|
|
121
|
+
export const ScopeSwitchResponse = z
|
|
122
|
+
.object({
|
|
123
|
+
/** RS256 JWT with the `scope` claim set — verify per api/auth-bridge; VerifiedIdentity.scope carries it. */
|
|
124
|
+
access_token: z.string().min(1),
|
|
125
|
+
token_type: z.literal("Bearer"),
|
|
126
|
+
/** Access-token lifetime in seconds (ACCESS_TOKEN_TTL_SECONDS — same mint as api/auth). */
|
|
127
|
+
expires_in: z.number().int().positive(),
|
|
128
|
+
scope: ScopeId,
|
|
129
|
+
role: ScopeRole,
|
|
130
|
+
})
|
|
131
|
+
.passthrough();
|
|
132
|
+
/** Re-exported convenience: the switch endpoint mints through the SAME exit as api/auth (1h JWT). */
|
|
133
|
+
export const SCOPE_SWITCH_TOKEN_TTL_SECONDS = ACCESS_TOKEN_TTL_SECONDS;
|
|
134
|
+
// ── POST /api/v1/scopes (create — instance admin only) ──────────────────────────────────────────────────
|
|
135
|
+
/** Reserved ids ("global") are rejected AT THE SCHEMA — the default scope pre-exists and is not creatable. */
|
|
136
|
+
export const ScopeCreateRequest = z
|
|
137
|
+
.object({
|
|
138
|
+
id: ScopeId.refine((id) => !isReservedScopeId(id), { message: `"${GLOBAL_SCOPE}" is reserved` }),
|
|
139
|
+
name: z.string().min(1).max(200),
|
|
140
|
+
})
|
|
141
|
+
.passthrough();
|
|
142
|
+
/** The created scope, as stored. */
|
|
143
|
+
export const ScopeCreateResponse = Scope;
|
|
144
|
+
// ── PUT/DELETE /api/v1/scopes/{id}/members (membership writes — instance admin only) ────────────────────
|
|
145
|
+
/** PUT body — upsert: sets `principal`'s role in the path scope, creating or replacing the row. */
|
|
146
|
+
export const ScopeMemberUpsertRequest = z
|
|
147
|
+
.object({
|
|
148
|
+
principal: z.string().min(1),
|
|
149
|
+
role: ScopeRole,
|
|
150
|
+
})
|
|
151
|
+
.passthrough();
|
|
152
|
+
/** The stored membership row (scopeId echoes the path). */
|
|
153
|
+
export const ScopeMemberUpsertResponse = ScopeMember;
|
|
154
|
+
/** DELETE body — remove `principal` from the path scope. */
|
|
155
|
+
export const ScopeMemberDeleteRequest = z
|
|
156
|
+
.object({
|
|
157
|
+
principal: z.string().min(1),
|
|
158
|
+
})
|
|
159
|
+
.passthrough();
|
|
160
|
+
/** Idempotent: `{ok:true}` whether or not the principal was a member (no membership disclosure beyond
|
|
161
|
+
* what the admin caller already sees). */
|
|
162
|
+
export const ScopeMemberDeleteResponse = z.object({ ok: z.literal(true) }).passthrough();
|
|
163
|
+
/**
|
|
164
|
+
* THE instance-admin override rule, in one place:
|
|
165
|
+
*
|
|
166
|
+
* **An instance `admin`'s effective role inside ANY scope is always `"admin"`** — regardless of whether
|
|
167
|
+
* they hold a membership there or what that membership says. For everyone else the effective role is
|
|
168
|
+
* exactly their membership role (`membershipRole`), and `undefined` when they have no membership.
|
|
169
|
+
*
|
|
170
|
+
* `instanceRole` is the instance-level users.role string; only the exact value `"admin"` triggers the
|
|
171
|
+
* override (publisher/editor/viewer at instance level grant NO cross-scope power).
|
|
172
|
+
*/
|
|
173
|
+
export function effectiveScopeRole(membershipRole, instanceRole) {
|
|
174
|
+
if (instanceRole === "admin")
|
|
175
|
+
return "admin";
|
|
176
|
+
return membershipRole;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* THE token-minting scope decision — which scope (and role claim) a fresh access token binds to, given
|
|
180
|
+
* the principal's scope memberships and instance role. Deterministic; the registry (Node/Next) imports
|
|
181
|
+
* and calls this at every mint (device-flow grant AND refresh). NORMATIVE RULES (verbatim, 任务书拍定):
|
|
182
|
+
*
|
|
183
|
+
* 1. **唯一成员 scope** — exactly one membership → bind that scope, role = that membership's effective
|
|
184
|
+
* role (effectiveScopeRole: instance admin ⇒ "admin").
|
|
185
|
+
* 2. **多个含 global** — multiple memberships, one of them "global" → bind "global", role = the global
|
|
186
|
+
* membership's effective role.
|
|
187
|
+
* 3. **多个不含 global** — multiple memberships, none "global" → bind the scope whose id is FIRST in
|
|
188
|
+
* lexicographic (UTF-16 code-unit) order — deterministic, no wall-clock/insertion-order dependence;
|
|
189
|
+
* role = that membership's effective role.
|
|
190
|
+
* 4. **零成员** — no memberships → `{scope: "global", role: instanceRole}` (the instance role IS the
|
|
191
|
+
* role claim, exactly as pre-scope tokens behaved — full backward compatibility).
|
|
192
|
+
*
|
|
193
|
+
* Duplicate scopeIds in the input: the entry that sorts first (by scopeId, then by role string) wins —
|
|
194
|
+
* callers should not pass duplicates, but the function stays deterministic if they do.
|
|
195
|
+
*/
|
|
196
|
+
export function resolveTokenScope(memberships, instanceRole) {
|
|
197
|
+
if (memberships.length === 0) {
|
|
198
|
+
return { scope: GLOBAL_SCOPE, role: instanceRole };
|
|
199
|
+
}
|
|
200
|
+
const sorted = [...memberships].sort((a, b) => a.scopeId < b.scopeId ? -1 : a.scopeId > b.scopeId ? 1 : a.role < b.role ? -1 : a.role > b.role ? 1 : 0);
|
|
201
|
+
if (sorted.length === 1) {
|
|
202
|
+
const only = sorted[0];
|
|
203
|
+
return { scope: only.scopeId, role: effectiveScopeRole(only.role, instanceRole) ?? only.role };
|
|
204
|
+
}
|
|
205
|
+
const global = sorted.find((m) => m.scopeId === GLOBAL_SCOPE);
|
|
206
|
+
const chosen = global ?? sorted[0];
|
|
207
|
+
return { scope: chosen.scopeId, role: effectiveScopeRole(chosen.role, instanceRole) ?? chosen.role };
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* THE scope-switch authorization decision (POST /api/v1/auth/scope): allowed ⇔ the caller is a MEMBER of
|
|
211
|
+
* `targetScope` (any role) OR an instance `admin` (who may enter any scope, including ones they are not
|
|
212
|
+
* a member of — their effective role there is "admin" per effectiveScopeRole). Everyone else →
|
|
213
|
+
* `access_denied`. Note: "global" gets NO special-case here — a multi-scope user without a global
|
|
214
|
+
* membership cannot switch to global (their default mint never lands there either, rule 3 above).
|
|
215
|
+
*/
|
|
216
|
+
export function canSwitchScope(memberships, instanceRole, targetScope) {
|
|
217
|
+
if (instanceRole === "admin")
|
|
218
|
+
return true;
|
|
219
|
+
return memberships.some((m) => m.scopeId === targetScope);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Read the active scope off verified JWT claims: a non-empty string `scope` claim, else GLOBAL_SCOPE.
|
|
223
|
+
* This is the ONE place the "missing claim = global" backward-compat rule lives — workers and the
|
|
224
|
+
* registry both call this instead of defaulting ad hoc.
|
|
225
|
+
*/
|
|
226
|
+
export function tokenScopeOf(claims) {
|
|
227
|
+
return typeof claims.scope === "string" && claims.scope.length > 0 ? claims.scope : GLOBAL_SCOPE;
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=scopes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/api/scopes.ts"],"names":[],"mappings":"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"}
|
package/dist/bundle.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ConfigReader } from "./reader.js";
|
|
2
|
+
import { type DomainName } from "./types.js";
|
|
3
|
+
export type ConfigBundle = Partial<Record<DomainName, unknown>>;
|
|
4
|
+
/** Read the (portable, by default) domains out of any ConfigReader → a bundle object. Absent domains are
|
|
5
|
+
* omitted (not defaulted) so a re-import doesn't fabricate domains the source never set. */
|
|
6
|
+
export declare function exportBundle(reader: ConfigReader, domains?: readonly DomainName[]): Promise<ConfigBundle>;
|
|
7
|
+
/** Write a bundle to `<root>/config.d/<domain>.json` — each file atomic + VALIDATED (writeDomainFile parses
|
|
8
|
+
* against the domain zod, so a malformed source domain fails loudly rather than writing garbage). Returns the
|
|
9
|
+
* domains written. (The TOC owns `<root>`; secrets stay in `<root>/.env`, never in these files.) */
|
|
10
|
+
export declare function writeBundle(root: string, bundle: ConfigBundle): Promise<DomainName[]>;
|
|
11
|
+
/** Read a `<root>/config.d/*.json` bundle → raw domain configs (missing/empty file → omitted). The reverse of
|
|
12
|
+
* writeBundle: feed into sema-registry's per-domain PUT to import, or into the resolver to run locally. */
|
|
13
|
+
export declare function readBundle(root: string, domains?: readonly DomainName[]): Promise<ConfigBundle>;
|
|
14
|
+
//# sourceMappingURL=bundle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAE3D,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;AAEhE;6FAC6F;AAC7F,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,GAAE,SAAS,UAAU,EAAqB,GAAG,OAAO,CAAC,YAAY,CAAC,CAOjI;AAED;;qGAEqG;AACrG,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQ3F;AAED;4GAC4G;AAC5G,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,SAAS,UAAU,EAAqB,GAAG,OAAO,CAAC,YAAY,CAAC,CAsBvH"}
|
package/dist/bundle.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Round-trip between a config SOURCE (any {@link ConfigReader} — sema-registry's store OR a FileConfigStore)
|
|
3
|
+
* and a local `config.d/<domain>.json` BUNDLE. This is how a FLEET config seeds a TOC desktop (export → copy
|
|
4
|
+
* the bundle + set .env → run locally) and how a local bundle seeds sema-registry (readBundle → PUT each domain
|
|
5
|
+
* via the existing domain API). The bundle = the RAW authorable domain configs; `skills` carry their full
|
|
6
|
+
* bodies (SkillSpec.content is stored in full — NOT the /effective manifest), so there is no separate
|
|
7
|
+
* body-bundling step and the round-trip is symmetric.
|
|
8
|
+
*
|
|
9
|
+
* Node-only (writeBundle/readBundle touch the filesystem) → imported via `@sema-ai/registry-core/node`.
|
|
10
|
+
*/
|
|
11
|
+
import { readFile } from "node:fs/promises";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { writeDomainFile } from "./file-edit.js";
|
|
14
|
+
import { PORTABLE_DOMAINS } from "./file-store.js";
|
|
15
|
+
import { isDomainName } from "./types.js";
|
|
16
|
+
/** Read the (portable, by default) domains out of any ConfigReader → a bundle object. Absent domains are
|
|
17
|
+
* omitted (not defaulted) so a re-import doesn't fabricate domains the source never set. */
|
|
18
|
+
export async function exportBundle(reader, domains = PORTABLE_DOMAINS) {
|
|
19
|
+
const out = {};
|
|
20
|
+
for (const d of domains) {
|
|
21
|
+
const v = await reader.getDomain(d);
|
|
22
|
+
if (v !== undefined)
|
|
23
|
+
out[d] = v;
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
/** Write a bundle to `<root>/config.d/<domain>.json` — each file atomic + VALIDATED (writeDomainFile parses
|
|
28
|
+
* against the domain zod, so a malformed source domain fails loudly rather than writing garbage). Returns the
|
|
29
|
+
* domains written. (The TOC owns `<root>`; secrets stay in `<root>/.env`, never in these files.) */
|
|
30
|
+
export async function writeBundle(root, bundle) {
|
|
31
|
+
const written = [];
|
|
32
|
+
for (const [d, v] of Object.entries(bundle)) {
|
|
33
|
+
if (v === undefined)
|
|
34
|
+
continue;
|
|
35
|
+
await writeDomainFile(root, d, v);
|
|
36
|
+
written.push(d);
|
|
37
|
+
}
|
|
38
|
+
return written;
|
|
39
|
+
}
|
|
40
|
+
/** Read a `<root>/config.d/*.json` bundle → raw domain configs (missing/empty file → omitted). The reverse of
|
|
41
|
+
* writeBundle: feed into sema-registry's per-domain PUT to import, or into the resolver to run locally. */
|
|
42
|
+
export async function readBundle(root, domains = PORTABLE_DOMAINS) {
|
|
43
|
+
const out = {};
|
|
44
|
+
await Promise.all(domains.map(async (d) => {
|
|
45
|
+
if (!isDomainName(d))
|
|
46
|
+
throw new Error(`"${d}" is not a config domain — refusing to build a config.d path from it (no traversal)`);
|
|
47
|
+
let text;
|
|
48
|
+
try {
|
|
49
|
+
text = await readFile(path.join(root, "config.d", `${d}.json`), "utf8");
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
if (e.code === "ENOENT")
|
|
53
|
+
return; // absent file → omit (not fabricated)
|
|
54
|
+
throw e;
|
|
55
|
+
}
|
|
56
|
+
const trimmed = text.trim();
|
|
57
|
+
if (!trimmed)
|
|
58
|
+
return;
|
|
59
|
+
try {
|
|
60
|
+
out[d] = JSON.parse(trimmed);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new Error(`invalid JSON in config.d/${d}.json`); // sanitized: never echo the raw file text
|
|
64
|
+
}
|
|
65
|
+
}));
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAE,YAAY,EAAmB,MAAM,YAAY,CAAC;AAI3D;6FAC6F;AAC7F,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAoB,EAAE,UAAiC,gBAAgB;IACxG,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;qGAEqG;AACrG,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,MAAoB;IAClE,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC,KAAK,SAAS;YAAE,SAAS;QAC9B,MAAM,eAAe,CAAC,IAAI,EAAE,CAAe,EAAE,CAAC,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,CAAe,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;4GAC4G;AAC5G,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,UAAiC,gBAAgB;IAC9F,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,MAAM,OAAO,CAAC,GAAG,CACf,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QACtB,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,qFAAqF,CAAC,CAAC;QAClI,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;QAC1E,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,CAAC,sCAAsC;YAClG,MAAM,CAAC,CAAC;QACV,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,IAAI,CAAC;YACH,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,OAAO,CAAC,CAAC,CAAC,0CAA0C;QACnG,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IACF,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure config functions + data shapes — LIFTED verbatim out of sema-registry's `store.ts` (the half that
|
|
3
|
+
* depends only on `./types`, zero app/DB/next coupling). sema-registry re-exports these so its existing
|
|
4
|
+
* `@/lib/config` importers keep working unchanged (ONE copy of the code, here).
|
|
5
|
+
*
|
|
6
|
+
* Two DETERMINISM refactors vs. the original store.ts (so the package is wall-clock-free and a local file
|
|
7
|
+
* source can produce byte-identical effective config for identical inputs):
|
|
8
|
+
* 1. `buildEffective(version, getRaw, updatedAt)` — `updatedAt` is now a REQUIRED param (was a
|
|
9
|
+
* `new Date().toISOString()` default). Every caller supplies it explicitly.
|
|
10
|
+
* 2. `emptyEffective()` seeds the epoch (`new Date(0).toISOString()`) instead of the wall clock, matching
|
|
11
|
+
* the stores' existing "epoch sentinel" reduce seed.
|
|
12
|
+
*
|
|
13
|
+
* The fat `ConfigStore` interface itself STAYS in sema-registry (app-only, ~32 publish/RBAC/worker-status
|
|
14
|
+
* methods); only the narrow {@link ConfigReader} (reader.ts) is carried here.
|
|
15
|
+
*/
|
|
16
|
+
import type { z } from "zod";
|
|
17
|
+
import { type DomainConfig, type DomainName, type EffectiveConfig } from "./types.js";
|
|
18
|
+
/** Redact secret-shaped tokens from a validation MESSAGE — a zod error echoes the offending value (e.g.
|
|
19
|
+
* `received 'sk-live-…'`), so a secret pasted into the WRONG field would leak via the error string. Redacts
|
|
20
|
+
* space-spanning auth credentials (`Bearer <token>`) FIRST (the token pass can't see across the space), then
|
|
21
|
+
* any secret-shaped run (`%` kept in the run so a percent-encoded secret stays whole for the percent-aware
|
|
22
|
+
* containsSecretToken). invariant ①: a secret VALUE never rides an error. R13/R14 (codex). */
|
|
23
|
+
export declare function redactSecrets(text: string): string;
|
|
24
|
+
/** Sanitize a ZodError IN PLACE so no surfaced error (message / `received` / nested `unionErrors`) echoes a
|
|
25
|
+
* pasted secret. RECURSES into `unionErrors` (a `z.union` failure nests a ZodError per member — e.g. RoleTarget
|
|
26
|
+
* `{model}|{select}`) and redacts string path segments too. The ONE sanitizer every validation entry point
|
|
27
|
+
* uses (parseDomain / validateDomain / validateRemoteExec / write*). R14 (codex). */
|
|
28
|
+
export declare function sanitizeZodError(err: z.ZodError): z.ZodError;
|
|
29
|
+
/** Result of a CAS write (sema-registry's `ConfigStore.setDomainsIfUnchanged`): applied (new `version`) or
|
|
30
|
+
* rejected because the version moved (`currentVersion` = what it is now, so the caller can re-read + retry). */
|
|
31
|
+
export type CasWriteResult = {
|
|
32
|
+
ok: true;
|
|
33
|
+
version: number;
|
|
34
|
+
} | {
|
|
35
|
+
ok: false;
|
|
36
|
+
currentVersion: number;
|
|
37
|
+
};
|
|
38
|
+
/** sema-registry's lifecycle bookkeeping for one worker (its OWN clock — design/25 决策3). */
|
|
39
|
+
export interface WorkerLifecycleRecord {
|
|
40
|
+
name: string;
|
|
41
|
+
/** when sema-registry FIRST saw this worker — TTL is measured from here. */
|
|
42
|
+
firstSeenAt: string;
|
|
43
|
+
/** last time the reverse-proxy saw traffic to it (idle detection). */
|
|
44
|
+
lastActiveAt?: string;
|
|
45
|
+
}
|
|
46
|
+
/** RBAC roles, hierarchical: viewer < editor < publisher < admin. */
|
|
47
|
+
export type UserRole = "viewer" | "editor" | "publisher" | "admin";
|
|
48
|
+
export declare const ROLE_RANK: Record<UserRole, number>;
|
|
49
|
+
export declare const USER_ROLES: readonly UserRole[];
|
|
50
|
+
/** True if `role` is at least `min` in the hierarchy (e.g. publisher satisfies editor). */
|
|
51
|
+
export declare function roleAtLeast(role: UserRole, min: UserRole): boolean;
|
|
52
|
+
export declare function isRole(v: unknown): v is UserRole;
|
|
53
|
+
/**
|
|
54
|
+
* Minimum role to WRITE a domain. Default = `editor`; `skills` and `mcp` are raised to `publisher`
|
|
55
|
+
* because their content goes straight into the agent's system prompt / tool surface — one edit retargets
|
|
56
|
+
* the prompt of every worker × scenario that loads it (docs/MCP-SKILLS.md §3.3, decision §6 Q3 从严).
|
|
57
|
+
* Looked up by the [domain] PUT route. Any domain absent here uses {@link DEFAULT_DOMAIN_WRITE_ROLE}.
|
|
58
|
+
*/
|
|
59
|
+
export declare const DEFAULT_DOMAIN_WRITE_ROLE: UserRole;
|
|
60
|
+
export declare const DOMAIN_WRITE_ROLE: Partial<Record<DomainName, UserRole>>;
|
|
61
|
+
/** The minimum role required to write `domain` (the raised gate for skills/mcp, else editor). */
|
|
62
|
+
export declare function domainWriteRole(domain: DomainName): UserRole;
|
|
63
|
+
/** A console user (no secret). */
|
|
64
|
+
export interface User {
|
|
65
|
+
username: string;
|
|
66
|
+
role: UserRole;
|
|
67
|
+
/** True = an SSO user (empty password hash) auto-provisioned from Gitea OAuth — signs in via Gitea only,
|
|
68
|
+
* no password. Derived on list (we expose the boolean, never the hash) so /users can distinguish them. */
|
|
69
|
+
sso?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** A user with its password hash (login only — never sent to the client). */
|
|
72
|
+
export interface UserAuth extends User {
|
|
73
|
+
passwordHash: string;
|
|
74
|
+
/** scrypt hash of the user's API token (NOT the plaintext), or undefined/"" if none. A `Bearer cc_<user>_<rand>`
|
|
75
|
+
* is authenticated by scrypt-verifying `<rand>` against this. Set/revoked via `ConfigStore.setUserApiToken`. */
|
|
76
|
+
apiTokenHash?: string;
|
|
77
|
+
}
|
|
78
|
+
/** Outcome of an atomic `ConfigStore.approvePublish`. */
|
|
79
|
+
export type ApprovePublishResult = {
|
|
80
|
+
status: "no-pending";
|
|
81
|
+
} | {
|
|
82
|
+
status: "is-requester";
|
|
83
|
+
} | {
|
|
84
|
+
status: "approved";
|
|
85
|
+
approvals: number;
|
|
86
|
+
required: number;
|
|
87
|
+
} | {
|
|
88
|
+
status: "published";
|
|
89
|
+
approvals: number;
|
|
90
|
+
version: number;
|
|
91
|
+
}
|
|
92
|
+
/** The draft changed since the request was opened — publishing it would release content the approvers
|
|
93
|
+
* never reviewed. The pending is cleared; re-request against the current draft. */
|
|
94
|
+
| {
|
|
95
|
+
status: "draft-moved";
|
|
96
|
+
expectedVersion: number;
|
|
97
|
+
currentVersion: number;
|
|
98
|
+
};
|
|
99
|
+
/** A pending publish awaiting approvals (publish approval workflow). Singleton — the current draft. */
|
|
100
|
+
export interface PendingPublish {
|
|
101
|
+
requestedBy: string;
|
|
102
|
+
requestedAt: string;
|
|
103
|
+
summary?: string;
|
|
104
|
+
/** The config version this request asks to release — approvals sign off on EXACTLY this draft; the final
|
|
105
|
+
* approval refuses (draft-moved) if the version has advanced since. Optional only for legacy rows
|
|
106
|
+
* persisted before the field existed (treated as moved — never published). */
|
|
107
|
+
draftVersion?: number;
|
|
108
|
+
/** approvers who've signed off (deduped; the requester may not approve their own). */
|
|
109
|
+
approvals: {
|
|
110
|
+
user: string;
|
|
111
|
+
at: string;
|
|
112
|
+
}[];
|
|
113
|
+
}
|
|
114
|
+
/** A frozen, deliberately-published config release (draft→publish gate, E). */
|
|
115
|
+
export interface PublishedSnapshot {
|
|
116
|
+
/** the frozen merged config consumers receive in publish mode (carries the version at publish time). */
|
|
117
|
+
config: EffectiveConfig;
|
|
118
|
+
/** when it was promoted to published. */
|
|
119
|
+
publishedAt: string;
|
|
120
|
+
/** who published it. */
|
|
121
|
+
publishedBy: string;
|
|
122
|
+
/** optional release note. */
|
|
123
|
+
summary?: string;
|
|
124
|
+
/** MONOTONIC publish counter — increments on EVERY publish INCLUDING a rollback-republish, so it never goes
|
|
125
|
+
* backward (unlike `config.version`, which a rollback re-freezes to an OLDER value). The sole ordering/
|
|
126
|
+
* freshness authority consumers compare across published snapshots (design CENTER-CONTROL-PLANE R2-S #9 /
|
|
127
|
+
* R2-D4). `config.version` stays the CAS/contentHash anchor WITHIN a revision; revision orders ACROSS them. */
|
|
128
|
+
globalRevision: number;
|
|
129
|
+
}
|
|
130
|
+
/** The empty/defaults effective config at version 0 — what publish mode serves before the first publish.
|
|
131
|
+
* Seeds the epoch (`new Date(0)`) so it's deterministic (the wall-clock default was the original store.ts
|
|
132
|
+
* non-determinism — see the file header). */
|
|
133
|
+
export declare function emptyEffective(): EffectiveConfig;
|
|
134
|
+
/** Validate + fill defaults for a domain payload (or the schema's empty default when absent/invalid). */
|
|
135
|
+
export declare function parseDomain<K extends DomainName>(domain: K, raw: unknown): DomainConfig[K];
|
|
136
|
+
/**
|
|
137
|
+
* Shared GC predicate for worker-status rows (F10) — keeps memory/sqlite/tidb identical. Given the rows'
|
|
138
|
+
* `{name, updatedAt}` and the GC options, returns the names to prune (A: not in `keepNames`; B: older than
|
|
139
|
+
* `ttlMs`). Pass neither selector → prunes nothing (guards against a wipe-all). See `ConfigStore.gcWorkerStatuses`.
|
|
140
|
+
*/
|
|
141
|
+
export declare function statusRowsToPrune(rows: {
|
|
142
|
+
name: string;
|
|
143
|
+
updatedAt: string;
|
|
144
|
+
}[], opts: {
|
|
145
|
+
keepNames?: string[];
|
|
146
|
+
ttlMs?: number;
|
|
147
|
+
now?: Date;
|
|
148
|
+
}): string[];
|
|
149
|
+
/**
|
|
150
|
+
* Merge every domain into the effective config a consumer pulls. Shared by all store impls.
|
|
151
|
+
* `updatedAt` is REQUIRED (the determinism refactor — the original store.ts defaulted it to the wall clock,
|
|
152
|
+
* which made `emptyEffective`/the memory store non-deterministic). Every caller now supplies it explicitly.
|
|
153
|
+
*/
|
|
154
|
+
/** Lift the governance trio out of a LEGACY runtime doc (pre-0.6 stored runtime carried autonomy/commandPolicy/
|
|
155
|
+
* approvalRequire). Used as the governance domain's read fallback when governance was NEVER set — so a 0.5
|
|
156
|
+
* store upgrades with zero data migration and zero gate loss. An explicitly-set governance domain WINS
|
|
157
|
+
* (its absence-of-a-key then means "center not managing", never "fall back to legacy runtime"). */
|
|
158
|
+
export declare function legacyGovernanceFromRuntime(rawRuntime: unknown): unknown;
|
|
159
|
+
/** A warning emitted by {@link buildEffective} when it clamps/degrades instead of throwing (0.8.1). Callers
|
|
160
|
+
* route it to their audit stream; omitting the callback keeps the resilience but drops the telemetry. */
|
|
161
|
+
export type EffectiveReadWarning = {
|
|
162
|
+
domain: "hosts";
|
|
163
|
+
kind: "hosts-grandfathered";
|
|
164
|
+
droppedNames: string[];
|
|
165
|
+
truncatedFrom?: number;
|
|
166
|
+
} | {
|
|
167
|
+
domain: DomainName;
|
|
168
|
+
kind: "domain-defaulted";
|
|
169
|
+
error: unknown;
|
|
170
|
+
};
|
|
171
|
+
/** Per-domain READ-corruption policy (0.8.1) — what buildEffective does when a stored domain no longer parses.
|
|
172
|
+
* `"default"` = degrade to the schema default + warn (safe: the default only REMOVES capability, fail-closed).
|
|
173
|
+
* `"throw"` = keep fail-loud (defaulting would LOOSEN gates or destructively apply — worse than an outage,
|
|
174
|
+
* consumers fail-static on last-good). A `satisfies Record<DomainName,…>` forces every FUTURE domain to make
|
|
175
|
+
* this call explicitly (compile error until it's classified). Per-domain rationale:
|
|
176
|
+
* - models/skills/mcp/plugins/scenarios/systems/collab: additive capability catalogs — empty = the
|
|
177
|
+
* capability disappears (fail-closed), no gate is lost.
|
|
178
|
+
* - hosts: empty registry = no auto-placement targets (availability-only); self-registration re-lands
|
|
179
|
+
* entries as approved:false = 待批 (fail-closed). Plus the precise grandfather clamp runs FIRST.
|
|
180
|
+
* - rosters: THROW — a lost roster makes resolveEffectiveForWorker fall back to the FULL models catalog
|
|
181
|
+
* ("global(fallback)"), silently WIDENING every restricted worker's model access. 放权,不许.
|
|
182
|
+
* - workers: THROW — an empty fleet spec would make the reconciler RETIRE every managed worker
|
|
183
|
+
* (destructive apply — far worse than a read outage).
|
|
184
|
+
* - runtime: THROW — defaulting drops centrally-published rate/cost ceilings = unbounded spend (fail-open
|
|
185
|
+
* on the budget axis).
|
|
186
|
+
* - governance: THROW — defaulting drops autonomy/commandPolicy/approvalRequire = the consumer falls back
|
|
187
|
+
* to env/defaults with the operator's guardrails silently gone. 放权,不许.
|
|
188
|
+
* - entitlement: THROW — defaulting drops the org kill-switch/budget ceilings/tier restrictions for every
|
|
189
|
+
* principal. 放权,不许.
|
|
190
|
+
* - execution: THROW — defaulting flips required:true (sandbox mandate) back to false. 放权,不许.
|
|
191
|
+
* (0.10.1: sessionMirror rides the SAME stance — defaulting would evaporate an org's required:true
|
|
192
|
+
* mirror mandate = 审计面静默放权; the shared rationale is exactly WHY it was收编 here, not a new domain.)
|
|
193
|
+
* - projects: THROW — defaulting evaporates the whole identity ledger: once the S4 worker gate ships,
|
|
194
|
+
* every `proj:*` derivation fails-fast (全租户记忆面停摆 — consumers fail-static on last-good is
|
|
195
|
+
* strictly better); WORSE, the claim write face is read-modify-write — reading a defaulted-empty
|
|
196
|
+
* ledger and writing it back would CLOBBER existing registrations down to one entry (data loss,
|
|
197
|
+
* one layer beyond the rosters "放权" rationale). */
|
|
198
|
+
export declare const DOMAIN_READ_FALLBACK: {
|
|
199
|
+
readonly models: "default";
|
|
200
|
+
readonly rosters: "throw";
|
|
201
|
+
readonly skills: "default";
|
|
202
|
+
readonly mcp: "default";
|
|
203
|
+
readonly plugins: "default";
|
|
204
|
+
readonly scenarios: "default";
|
|
205
|
+
readonly systems: "default";
|
|
206
|
+
readonly collab: "default";
|
|
207
|
+
readonly workers: "throw";
|
|
208
|
+
readonly hosts: "default";
|
|
209
|
+
readonly runtime: "throw";
|
|
210
|
+
readonly governance: "throw";
|
|
211
|
+
readonly entitlement: "throw";
|
|
212
|
+
readonly execution: "throw";
|
|
213
|
+
readonly projects: "throw";
|
|
214
|
+
};
|
|
215
|
+
/** Result of {@link grandfatherHostsRead}. `raw` is the (possibly clamped) document to parse; when NOTHING
|
|
216
|
+
* was clamped it is the ORIGINAL reference — well-formed data stays byte-identical through the read path. */
|
|
217
|
+
export interface GrandfatherHostsReadResult {
|
|
218
|
+
raw: unknown;
|
|
219
|
+
/** Host names DROPPED because they exceed HOST_NAME_MAX (63) — truncating would forge a NEW identity that
|
|
220
|
+
* matches no ORCH_HOST_ID (and could collide into the duplicate-name refine), so the entry is skipped whole. */
|
|
221
|
+
droppedNames: string[];
|
|
222
|
+
/** Set when the hosts array (after drops) exceeded HOSTS_ARRAY_MAX (1024) and was sliced — the pre-slice length. */
|
|
223
|
+
truncatedFrom?: number;
|
|
224
|
+
}
|
|
225
|
+
/** READ-path grandfather clamp for the 0.8.0 hosts tightening (0.8.1, S3 M-1). Clamps ONLY the two bounds
|
|
226
|
+
* 0.8.0 added (name > 63 → drop the entry; array > 1024 → slice) so 0.7-era legal stored data keeps reading;
|
|
227
|
+
* everything else (regex, types, duplicates) is left for parseDomain — this is a targeted grandfather, not a
|
|
228
|
+
* lenient parser. PURE + idempotent; returns the ORIGINAL reference when nothing exceeds the bounds. */
|
|
229
|
+
export declare function grandfatherHostsRead(raw: unknown): GrandfatherHostsReadResult;
|
|
230
|
+
export declare function buildEffective(version: number, getRaw: (d: DomainName) => unknown, updatedAt: string, onWarning?: (w: EffectiveReadWarning) => void): EffectiveConfig;
|
|
231
|
+
/** [599]③ 档位组→引擎单表的唯一解析点(0.9.0)。返回 ACTIVE 档位组的绑定表(剔除未绑档),即 core
|
|
232
|
+
* `RunnerDeps.tiers` 要吃的那张单表——组切换=center 换这张表下发,零新引擎机制。无 active 组/未命中/
|
|
233
|
+
* 组内一档未绑 → `undefined`(引擎档位层恒惰性,与「未配 tiers」逐字同义)。消费方(center /effective
|
|
234
|
+
* 投影、service applyEffective 接线)都走这一个函数,不要各自解析。 */
|
|
235
|
+
export declare function resolveActiveTiers(models: Pick<DomainConfig["models"], "tierGroups" | "activeTierGroup">): Record<string, string> | undefined;
|
|
236
|
+
//# sourceMappingURL=config-fns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-fns.d.ts","sourceRoot":"","sources":["../src/config-fns.ts"],"names":[],"mappings":"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"}
|