@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
package/dist/api/auth.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `api/auth` — the /api/v1/auth/* WIRE CONTRACT (REGISTRY-REBUILD-DESIGN §2.1/§2.2, M1 shipped in
|
|
3
|
+
* sema-registry; this file freezes the shipped shapes so registry, orchestrator, service and the sema
|
|
4
|
+
* shell import ONE source of truth — design §3 "契约先行").
|
|
5
|
+
*
|
|
6
|
+
* The flow is RFC 8628 (OAuth 2.0 Device Authorization Grant) + a rotating opaque refresh token:
|
|
7
|
+
*
|
|
8
|
+
* 1. client POST /api/v1/auth/device/code → DeviceCodeResponse (device_code + user_code)
|
|
9
|
+
* 2. user opens verification_uri (/activate) in ANY logged-in browser, approves the user_code
|
|
10
|
+
* (GET/POST /api/v1/auth/device/approve — session-cookie-gated, consent screen)
|
|
11
|
+
* 3. client POLLS POST /api/v1/auth/device/token every `interval` seconds
|
|
12
|
+
* → 400 OAuthErrorResponse (authorization_pending / slow_down / expired_token / access_denied)
|
|
13
|
+
* → 200 TokenGrantResponse exactly ONCE (the handshake row is consumed atomically)
|
|
14
|
+
* 4. client silently renews via POST /api/v1/auth/token/refresh (refresh rotation: the presented
|
|
15
|
+
* refresh token is single-use; a replay answers invalid_grant)
|
|
16
|
+
* 5. client POST /api/v1/auth/logout revokes the refresh grant (RFC 7009 discipline: always 200 ok,
|
|
17
|
+
* no token-existence disclosure)
|
|
18
|
+
*
|
|
19
|
+
* WIRE CASING: these are OAuth-standard endpoints — fields are snake_case ON THE WIRE (access_token,
|
|
20
|
+
* device_code, …), deliberately breaking this package's camelCase habit. Do not "fix" it.
|
|
21
|
+
*
|
|
22
|
+
* SECRET DISCIPLINE (registry side, informational for consumers): device_code and refresh_token are
|
|
23
|
+
* ≥256-bit opaque strings persisted ONLY as SHA-256 hashes; access_token is the standard issueSsoToken
|
|
24
|
+
* RS256 JWT (1h) — see `api/auth-bridge` for how a worker verifies it.
|
|
25
|
+
*
|
|
26
|
+
* Pure zod contract — zero IO, browser-safe, same rules as the rest of this package.
|
|
27
|
+
*/
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
// ── endpoint paths + protocol constants (shipped values) ─────────────────────────────────────────────────
|
|
30
|
+
/** The five auth endpoints, relative to the registry origin. */
|
|
31
|
+
export const AUTH_V1_PATHS = {
|
|
32
|
+
deviceCode: "/api/v1/auth/device/code",
|
|
33
|
+
deviceToken: "/api/v1/auth/device/token",
|
|
34
|
+
deviceApprove: "/api/v1/auth/device/approve",
|
|
35
|
+
tokenRefresh: "/api/v1/auth/token/refresh",
|
|
36
|
+
logout: "/api/v1/auth/logout",
|
|
37
|
+
};
|
|
38
|
+
/** The RFC 8628 grant_type the device token endpoint requires. */
|
|
39
|
+
export const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
40
|
+
/** Minimum seconds between token-endpoint polls (RFC 8628 `interval`); polling faster → `slow_down`. */
|
|
41
|
+
export const DEVICE_POLL_INTERVAL_SECONDS = 5;
|
|
42
|
+
/** Device-code handshake lifetime (seconds) — the user has 15 minutes to open /activate and approve. */
|
|
43
|
+
export const DEVICE_CODE_TTL_SECONDS = 15 * 60;
|
|
44
|
+
/** Access-token (RS256 JWT) lifetime in seconds — 1h, re-minted via refresh. */
|
|
45
|
+
export const ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
|
|
46
|
+
/** Refresh-grant lifetime (seconds) — 30d SLIDING: every rotation opens a fresh 30d window. */
|
|
47
|
+
export const REFRESH_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
48
|
+
// ── OAuth error envelope (RFC 6749 §5.2 / RFC 8628 §3.5) ─────────────────────────────────────────────────
|
|
49
|
+
/** Every error code the /api/v1/auth/* endpoints answer (HTTP 400 unless noted). */
|
|
50
|
+
export const OAUTH_ERROR_CODES = [
|
|
51
|
+
/** device/token: the user has not approved (or denied) the handshake yet — keep polling. */
|
|
52
|
+
"authorization_pending",
|
|
53
|
+
/** device/token: polling faster than `interval` — RFC 8628 §3.5: ADD 5 seconds to the interval. */
|
|
54
|
+
"slow_down",
|
|
55
|
+
/** device/token: handshake expired, unknown, or already exchanged — restart the flow.
|
|
56
|
+
* (Deliberately covers "never existed": the server does not disclose handshake existence.) */
|
|
57
|
+
"expired_token",
|
|
58
|
+
/** device/token: the user explicitly denied the handshake — stop polling, do not restart silently. */
|
|
59
|
+
"access_denied",
|
|
60
|
+
/** any endpoint: malformed body / missing required field / wrong content type. */
|
|
61
|
+
"invalid_request",
|
|
62
|
+
/** device/token: grant_type is not DEVICE_GRANT_TYPE. */
|
|
63
|
+
"unsupported_grant_type",
|
|
64
|
+
/** token/refresh + device/approve: unknown / expired / revoked / replayed token or code — no detail. */
|
|
65
|
+
"invalid_grant",
|
|
66
|
+
/** SSO not configured on the registry (HTTP 503) — a deployment problem, not a client one. */
|
|
67
|
+
"server_error",
|
|
68
|
+
];
|
|
69
|
+
/** The uniform error body. `error_description` is HUMAN diagnostic text — clients branch on `error` only. */
|
|
70
|
+
export const OAuthErrorResponse = z
|
|
71
|
+
.object({
|
|
72
|
+
error: z.enum(OAUTH_ERROR_CODES),
|
|
73
|
+
error_description: z.string().optional(),
|
|
74
|
+
})
|
|
75
|
+
.passthrough();
|
|
76
|
+
// ── POST /api/v1/auth/device/code ────────────────────────────────────────────────────────────────────────
|
|
77
|
+
/** Unauthenticated by design (the client has no credentials yet). `client_name` is a NON-secret display
|
|
78
|
+
* hint for the consent screen ("sema CLI on macOS"); absent → the server falls back to the User-Agent.
|
|
79
|
+
* Body may be JSON or form-encoded; an empty body is legal. */
|
|
80
|
+
export const DeviceCodeRequest = z
|
|
81
|
+
.object({
|
|
82
|
+
client_name: z.string().max(200).optional(),
|
|
83
|
+
})
|
|
84
|
+
.passthrough();
|
|
85
|
+
/** RFC 8628 §3.2 device-authorization response. `device_code` is the client's polling secret (keep it in
|
|
86
|
+
* memory, never log it); `user_code` is the short human code ("XXXX-XXXX") the user approves. */
|
|
87
|
+
export const DeviceCodeResponse = z
|
|
88
|
+
.object({
|
|
89
|
+
/** ≥256-bit opaque polling secret — returned ONCE, hash-persisted server-side. */
|
|
90
|
+
device_code: z.string().min(1),
|
|
91
|
+
/** Human approval code, canonical shape "XXXX-XXXX" (base-20 alphabet: no vowels, no 0/O/1/I). */
|
|
92
|
+
user_code: z.string().min(1),
|
|
93
|
+
/** The /activate page the user opens (any device, any logged-in browser). */
|
|
94
|
+
verification_uri: z.string().min(1),
|
|
95
|
+
/** verification_uri with ?user_code= pre-filled — one-click / QR path. */
|
|
96
|
+
verification_uri_complete: z.string().min(1),
|
|
97
|
+
/** Seconds until the handshake expires (DEVICE_CODE_TTL_SECONDS). */
|
|
98
|
+
expires_in: z.number().int().positive(),
|
|
99
|
+
/** Minimum seconds between polls (DEVICE_POLL_INTERVAL_SECONDS). */
|
|
100
|
+
interval: z.number().int().positive(),
|
|
101
|
+
})
|
|
102
|
+
.passthrough();
|
|
103
|
+
// ── POST /api/v1/auth/device/token (the poll) ────────────────────────────────────────────────────────────
|
|
104
|
+
/** RFC 8628 §3.4 token request. Form or JSON. */
|
|
105
|
+
export const DeviceTokenRequest = z
|
|
106
|
+
.object({
|
|
107
|
+
grant_type: z.literal(DEVICE_GRANT_TYPE),
|
|
108
|
+
device_code: z.string().min(1),
|
|
109
|
+
})
|
|
110
|
+
.passthrough();
|
|
111
|
+
/** The ONE token success shape — shared by device/token AND token/refresh (both mint through the same
|
|
112
|
+
* exit: access = issueSsoToken RS256 JWT, refresh = fresh opaque rotating grant). */
|
|
113
|
+
export const TokenGrantResponse = z
|
|
114
|
+
.object({
|
|
115
|
+
/** RS256 JWT — claims: sub (principal), iss, iat, exp, role; optional aud. Verify per api/auth-bridge. */
|
|
116
|
+
access_token: z.string().min(1),
|
|
117
|
+
/** NEW opaque rotating refresh token — replaces any previous one the client held. Store 0600/keychain. */
|
|
118
|
+
refresh_token: z.string().min(1),
|
|
119
|
+
token_type: z.literal("Bearer"),
|
|
120
|
+
/** Access-token lifetime in seconds (ACCESS_TOKEN_TTL_SECONDS). */
|
|
121
|
+
expires_in: z.number().int().positive(),
|
|
122
|
+
})
|
|
123
|
+
.passthrough();
|
|
124
|
+
// ── POST /api/v1/auth/token/refresh ──────────────────────────────────────────────────────────────────────
|
|
125
|
+
/** Rotation is STRICT single-use: success revokes the presented grant and returns a NEW refresh_token;
|
|
126
|
+
* a replayed (or raced) old token answers `invalid_grant`. Clients MUST persist the new token before
|
|
127
|
+
* discarding the old request state. */
|
|
128
|
+
export const TokenRefreshRequest = z
|
|
129
|
+
.object({
|
|
130
|
+
refresh_token: z.string().min(1),
|
|
131
|
+
})
|
|
132
|
+
.passthrough();
|
|
133
|
+
// success = TokenGrantResponse; failure = OAuthErrorResponse (invalid_grant | invalid_request)
|
|
134
|
+
// ── POST /api/v1/auth/logout ─────────────────────────────────────────────────────────────────────────────
|
|
135
|
+
/** Revoke the refresh grant. The already-minted access JWT stays valid until its 1h exp — revocation kills
|
|
136
|
+
* the RENEWAL path, which is the durable one. */
|
|
137
|
+
export const LogoutRequest = z
|
|
138
|
+
.object({
|
|
139
|
+
refresh_token: z.string().min(1),
|
|
140
|
+
})
|
|
141
|
+
.passthrough();
|
|
142
|
+
/** Always `{ok:true}` with HTTP 200 whether or not the token matched a live grant (RFC 7009: a revocation
|
|
143
|
+
* endpoint never discloses token existence). 400 only for a malformed request. */
|
|
144
|
+
export const LogoutResponse = z.object({ ok: z.literal(true) }).passthrough();
|
|
145
|
+
// ── GET/POST /api/v1/auth/device/approve (the /activate consent action — session-cookie-gated) ───────────
|
|
146
|
+
/** Handshake states a consent-screen lookup can see. (`approved` rows vanish once exchanged/consumed.)
|
|
147
|
+
* `denied` is contract-reserved for an explicit deny button (pairs with the `access_denied` poll error);
|
|
148
|
+
* the shipped M1 store carries pending|approved only — adding deny later is implementation, not schema. */
|
|
149
|
+
export const DEVICE_AUTH_STATUSES = ["pending", "approved", "denied"];
|
|
150
|
+
/** GET ?user_code=XXXX-XXXX — the consent screen's lookup, WITHOUT approving. 401 when not signed in;
|
|
151
|
+
* invalid_grant for an unknown/expired code. */
|
|
152
|
+
export const DeviceApproveLookupResponse = z
|
|
153
|
+
.object({
|
|
154
|
+
user_code: z.string().min(1),
|
|
155
|
+
status: z.enum(DEVICE_AUTH_STATUSES),
|
|
156
|
+
/** The client_name/User-Agent display hint from device/code — null when the client sent none. */
|
|
157
|
+
client: z.string().nullable(),
|
|
158
|
+
/** Handshake expiry, epoch MILLISECONDS. */
|
|
159
|
+
expires_at: z.number(),
|
|
160
|
+
})
|
|
161
|
+
.passthrough();
|
|
162
|
+
/** POST body (JSON ONLY — the JSON content type is part of the cross-site-request defence; a cross-site
|
|
163
|
+
* form cannot send application/json without a CORS preflight). The approving identity comes from the
|
|
164
|
+
* session cookie exclusively — NEVER from this body. */
|
|
165
|
+
export const DeviceApproveRequest = z
|
|
166
|
+
.object({
|
|
167
|
+
/** Case-insensitive; dashes/spaces optional — see normalizeUserCode. */
|
|
168
|
+
user_code: z.string().min(1),
|
|
169
|
+
})
|
|
170
|
+
.passthrough();
|
|
171
|
+
export const DeviceApproveResponse = z
|
|
172
|
+
.object({
|
|
173
|
+
ok: z.literal(true),
|
|
174
|
+
user_code: z.string().min(1),
|
|
175
|
+
})
|
|
176
|
+
.passthrough();
|
|
177
|
+
// ── pure client-side helpers ─────────────────────────────────────────────────────────────────────────────
|
|
178
|
+
/**
|
|
179
|
+
* Canonicalize a human-typed user code to the wire shape "XXXX-XXXX": uppercase, dashes/spaces optional.
|
|
180
|
+
* null = not a plausible code (wrong length/charset after stripping separators). Same normalization the
|
|
181
|
+
* server applies — run it client-side for instant feedback.
|
|
182
|
+
*/
|
|
183
|
+
export function normalizeUserCode(input) {
|
|
184
|
+
if (typeof input !== "string")
|
|
185
|
+
return null;
|
|
186
|
+
const raw = input.toUpperCase().replace(/[\s-]/g, "");
|
|
187
|
+
if (!/^[A-Z0-9]{8}$/.test(raw))
|
|
188
|
+
return null;
|
|
189
|
+
return `${raw.slice(0, 4)}-${raw.slice(4)}`;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The RFC 8628 §3.5 poll-loop step: what interval to poll with next, and whether to keep going.
|
|
193
|
+
* - authorization_pending → keep polling at the current interval
|
|
194
|
+
* - slow_down → keep polling, interval + 5s (the RFC-mandated increment)
|
|
195
|
+
* - anything else → stop (expired_token/access_denied = terminal; invalid_* = client bug)
|
|
196
|
+
*/
|
|
197
|
+
export function nextPollInterval(currentIntervalSeconds, error) {
|
|
198
|
+
if (error === "authorization_pending")
|
|
199
|
+
return { continue: true, intervalSeconds: currentIntervalSeconds };
|
|
200
|
+
if (error === "slow_down")
|
|
201
|
+
return { continue: true, intervalSeconds: currentIntervalSeconds + 5 };
|
|
202
|
+
return { continue: false, intervalSeconds: currentIntervalSeconds };
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,353 @@
|
|
|
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 { type UserRole } from "../config-fns.js";
|
|
48
|
+
/** The reserved default scope. Always exists; every token without a `scope` claim lives here. */
|
|
49
|
+
export declare const GLOBAL_SCOPE = "global";
|
|
50
|
+
/** Scope ids that can never be created (or deleted) through the API. */
|
|
51
|
+
export declare const RESERVED_SCOPE_IDS: readonly ["global"];
|
|
52
|
+
/** Slug shape for scope ids: lowercase alphanumeric + inner dashes, 1–64 chars, no leading/trailing dash. */
|
|
53
|
+
export declare const SCOPE_ID_REGEX: RegExp;
|
|
54
|
+
/** The scope endpoints, relative to the registry origin. `members` needs the scope id — use scopeMembersPath. */
|
|
55
|
+
export declare const SCOPES_V1_PATHS: {
|
|
56
|
+
/** GET (list) + POST (create). */
|
|
57
|
+
readonly scopes: "/api/v1/scopes";
|
|
58
|
+
/** POST — mint a new access token bound to another scope. */
|
|
59
|
+
readonly authScope: "/api/v1/auth/scope";
|
|
60
|
+
};
|
|
61
|
+
/** The JWT claim carrying the token's active scope. OPTIONAL — absent means GLOBAL_SCOPE (tokenScopeOf). */
|
|
62
|
+
export declare const SCOPE_CLAIM = "scope";
|
|
63
|
+
/** `/api/v1/scopes/{id}/members` for PUT (upsert) and DELETE (remove). */
|
|
64
|
+
export declare function scopeMembersPath(scopeId: string): string;
|
|
65
|
+
/** True for ids the API refuses to create/delete ("global"). */
|
|
66
|
+
export declare function isReservedScopeId(id: string): boolean;
|
|
67
|
+
/** A scope id on the wire — slug-validated. (The reserved "global" IS a valid ScopeId: it exists as an
|
|
68
|
+
* entity everywhere except the create endpoint.) */
|
|
69
|
+
export declare const ScopeId: z.ZodString;
|
|
70
|
+
export type ScopeId = z.infer<typeof ScopeId>;
|
|
71
|
+
/** The per-scope role ladder = the existing instance RBAC ladder (viewer < editor < publisher < admin). */
|
|
72
|
+
export declare const ScopeRole: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
73
|
+
export type ScopeRole = UserRole;
|
|
74
|
+
/** The scope entity. */
|
|
75
|
+
export declare const Scope: z.ZodObject<{
|
|
76
|
+
id: z.ZodString;
|
|
77
|
+
/** Human display name (non-unique, non-secret). */
|
|
78
|
+
name: z.ZodString;
|
|
79
|
+
/** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
|
|
80
|
+
createdAt: z.ZodNumber;
|
|
81
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
82
|
+
id: z.ZodString;
|
|
83
|
+
/** Human display name (non-unique, non-secret). */
|
|
84
|
+
name: z.ZodString;
|
|
85
|
+
/** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
|
|
86
|
+
createdAt: z.ZodNumber;
|
|
87
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
88
|
+
id: z.ZodString;
|
|
89
|
+
/** Human display name (non-unique, non-secret). */
|
|
90
|
+
name: z.ZodString;
|
|
91
|
+
/** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
|
|
92
|
+
createdAt: z.ZodNumber;
|
|
93
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
94
|
+
export type Scope = z.infer<typeof Scope>;
|
|
95
|
+
/** One membership row: `principal` holds `role` inside `scopeId`. Per-scope — the same principal may
|
|
96
|
+
* hold different roles in different scopes. */
|
|
97
|
+
export declare const ScopeMember: z.ZodObject<{
|
|
98
|
+
scopeId: z.ZodString;
|
|
99
|
+
/** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
|
|
100
|
+
principal: z.ZodString;
|
|
101
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
102
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
103
|
+
scopeId: z.ZodString;
|
|
104
|
+
/** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
|
|
105
|
+
principal: z.ZodString;
|
|
106
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
107
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
108
|
+
scopeId: z.ZodString;
|
|
109
|
+
/** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
|
|
110
|
+
principal: z.ZodString;
|
|
111
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
112
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
113
|
+
export type ScopeMember = z.infer<typeof ScopeMember>;
|
|
114
|
+
/** One list entry: a scope the caller can act in, with the caller's EFFECTIVE role there
|
|
115
|
+
* (effectiveScopeRole — instance admins list every scope with role "admin"). */
|
|
116
|
+
export declare const ScopeListEntry: z.ZodObject<{
|
|
117
|
+
id: z.ZodString;
|
|
118
|
+
name: z.ZodString;
|
|
119
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
120
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
121
|
+
id: z.ZodString;
|
|
122
|
+
name: z.ZodString;
|
|
123
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
124
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
125
|
+
id: z.ZodString;
|
|
126
|
+
name: z.ZodString;
|
|
127
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
128
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
129
|
+
export type ScopeListEntry = z.infer<typeof ScopeListEntry>;
|
|
130
|
+
export declare const ScopesListResponse: z.ZodObject<{
|
|
131
|
+
scopes: z.ZodArray<z.ZodObject<{
|
|
132
|
+
id: z.ZodString;
|
|
133
|
+
name: z.ZodString;
|
|
134
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
135
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
136
|
+
id: z.ZodString;
|
|
137
|
+
name: z.ZodString;
|
|
138
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
139
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
140
|
+
id: z.ZodString;
|
|
141
|
+
name: z.ZodString;
|
|
142
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
143
|
+
}, z.ZodTypeAny, "passthrough">>, "many">;
|
|
144
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
145
|
+
scopes: z.ZodArray<z.ZodObject<{
|
|
146
|
+
id: z.ZodString;
|
|
147
|
+
name: z.ZodString;
|
|
148
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
149
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
150
|
+
id: z.ZodString;
|
|
151
|
+
name: z.ZodString;
|
|
152
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
153
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
154
|
+
id: z.ZodString;
|
|
155
|
+
name: z.ZodString;
|
|
156
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
157
|
+
}, z.ZodTypeAny, "passthrough">>, "many">;
|
|
158
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
159
|
+
scopes: z.ZodArray<z.ZodObject<{
|
|
160
|
+
id: z.ZodString;
|
|
161
|
+
name: z.ZodString;
|
|
162
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
163
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
164
|
+
id: z.ZodString;
|
|
165
|
+
name: z.ZodString;
|
|
166
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
167
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
168
|
+
id: z.ZodString;
|
|
169
|
+
name: z.ZodString;
|
|
170
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
171
|
+
}, z.ZodTypeAny, "passthrough">>, "many">;
|
|
172
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
173
|
+
export type ScopesListResponse = z.infer<typeof ScopesListResponse>;
|
|
174
|
+
/** Request: the target scope. Errors: `invalid_request` (malformed), `access_denied` (not a member and
|
|
175
|
+
* not an instance admin — canSwitchScope false). */
|
|
176
|
+
export declare const ScopeSwitchRequest: z.ZodObject<{
|
|
177
|
+
scope: z.ZodString;
|
|
178
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
179
|
+
scope: z.ZodString;
|
|
180
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
181
|
+
scope: z.ZodString;
|
|
182
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
183
|
+
export type ScopeSwitchRequest = z.infer<typeof ScopeSwitchRequest>;
|
|
184
|
+
/** A fresh access token bound to the requested scope (`scope` claim = `scope` field below). The refresh
|
|
185
|
+
* grant is untouched — this endpoint never returns a refresh_token. `role` = the caller's effective role
|
|
186
|
+
* inside the new scope (effectiveScopeRole). */
|
|
187
|
+
export declare const ScopeSwitchResponse: z.ZodObject<{
|
|
188
|
+
/** RS256 JWT with the `scope` claim set — verify per api/auth-bridge; VerifiedIdentity.scope carries it. */
|
|
189
|
+
access_token: z.ZodString;
|
|
190
|
+
token_type: z.ZodLiteral<"Bearer">;
|
|
191
|
+
/** Access-token lifetime in seconds (ACCESS_TOKEN_TTL_SECONDS — same mint as api/auth). */
|
|
192
|
+
expires_in: z.ZodNumber;
|
|
193
|
+
scope: z.ZodString;
|
|
194
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
195
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
196
|
+
/** RS256 JWT with the `scope` claim set — verify per api/auth-bridge; VerifiedIdentity.scope carries it. */
|
|
197
|
+
access_token: z.ZodString;
|
|
198
|
+
token_type: z.ZodLiteral<"Bearer">;
|
|
199
|
+
/** Access-token lifetime in seconds (ACCESS_TOKEN_TTL_SECONDS — same mint as api/auth). */
|
|
200
|
+
expires_in: z.ZodNumber;
|
|
201
|
+
scope: z.ZodString;
|
|
202
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
203
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
204
|
+
/** RS256 JWT with the `scope` claim set — verify per api/auth-bridge; VerifiedIdentity.scope carries it. */
|
|
205
|
+
access_token: z.ZodString;
|
|
206
|
+
token_type: z.ZodLiteral<"Bearer">;
|
|
207
|
+
/** Access-token lifetime in seconds (ACCESS_TOKEN_TTL_SECONDS — same mint as api/auth). */
|
|
208
|
+
expires_in: z.ZodNumber;
|
|
209
|
+
scope: z.ZodString;
|
|
210
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
211
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
212
|
+
export type ScopeSwitchResponse = z.infer<typeof ScopeSwitchResponse>;
|
|
213
|
+
/** Re-exported convenience: the switch endpoint mints through the SAME exit as api/auth (1h JWT). */
|
|
214
|
+
export declare const SCOPE_SWITCH_TOKEN_TTL_SECONDS: number;
|
|
215
|
+
/** Reserved ids ("global") are rejected AT THE SCHEMA — the default scope pre-exists and is not creatable. */
|
|
216
|
+
export declare const ScopeCreateRequest: z.ZodObject<{
|
|
217
|
+
id: z.ZodEffects<z.ZodString, string, string>;
|
|
218
|
+
name: z.ZodString;
|
|
219
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
220
|
+
id: z.ZodEffects<z.ZodString, string, string>;
|
|
221
|
+
name: z.ZodString;
|
|
222
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
223
|
+
id: z.ZodEffects<z.ZodString, string, string>;
|
|
224
|
+
name: z.ZodString;
|
|
225
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
226
|
+
export type ScopeCreateRequest = z.infer<typeof ScopeCreateRequest>;
|
|
227
|
+
/** The created scope, as stored. */
|
|
228
|
+
export declare const ScopeCreateResponse: z.ZodObject<{
|
|
229
|
+
id: z.ZodString;
|
|
230
|
+
/** Human display name (non-unique, non-secret). */
|
|
231
|
+
name: z.ZodString;
|
|
232
|
+
/** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
|
|
233
|
+
createdAt: z.ZodNumber;
|
|
234
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
235
|
+
id: z.ZodString;
|
|
236
|
+
/** Human display name (non-unique, non-secret). */
|
|
237
|
+
name: z.ZodString;
|
|
238
|
+
/** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
|
|
239
|
+
createdAt: z.ZodNumber;
|
|
240
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
241
|
+
id: z.ZodString;
|
|
242
|
+
/** Human display name (non-unique, non-secret). */
|
|
243
|
+
name: z.ZodString;
|
|
244
|
+
/** Creation time, epoch MILLISECONDS. The reserved "global" scope reports its provisioning time. */
|
|
245
|
+
createdAt: z.ZodNumber;
|
|
246
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
247
|
+
export type ScopeCreateResponse = Scope;
|
|
248
|
+
/** PUT body — upsert: sets `principal`'s role in the path scope, creating or replacing the row. */
|
|
249
|
+
export declare const ScopeMemberUpsertRequest: z.ZodObject<{
|
|
250
|
+
principal: z.ZodString;
|
|
251
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
252
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
253
|
+
principal: z.ZodString;
|
|
254
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
255
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
256
|
+
principal: z.ZodString;
|
|
257
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
258
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
259
|
+
export type ScopeMemberUpsertRequest = z.infer<typeof ScopeMemberUpsertRequest>;
|
|
260
|
+
/** The stored membership row (scopeId echoes the path). */
|
|
261
|
+
export declare const ScopeMemberUpsertResponse: z.ZodObject<{
|
|
262
|
+
scopeId: z.ZodString;
|
|
263
|
+
/** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
|
|
264
|
+
principal: z.ZodString;
|
|
265
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
266
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
267
|
+
scopeId: z.ZodString;
|
|
268
|
+
/** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
|
|
269
|
+
principal: z.ZodString;
|
|
270
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
271
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
272
|
+
scopeId: z.ZodString;
|
|
273
|
+
/** The user identity — same principal string the JWT `sub` / x-agent-principal pipeline uses. */
|
|
274
|
+
principal: z.ZodString;
|
|
275
|
+
role: z.ZodEnum<[UserRole, ...UserRole[]]>;
|
|
276
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
277
|
+
export type ScopeMemberUpsertResponse = ScopeMember;
|
|
278
|
+
/** DELETE body — remove `principal` from the path scope. */
|
|
279
|
+
export declare const ScopeMemberDeleteRequest: z.ZodObject<{
|
|
280
|
+
principal: z.ZodString;
|
|
281
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
282
|
+
principal: z.ZodString;
|
|
283
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
284
|
+
principal: z.ZodString;
|
|
285
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
286
|
+
export type ScopeMemberDeleteRequest = z.infer<typeof ScopeMemberDeleteRequest>;
|
|
287
|
+
/** Idempotent: `{ok:true}` whether or not the principal was a member (no membership disclosure beyond
|
|
288
|
+
* what the admin caller already sees). */
|
|
289
|
+
export declare const ScopeMemberDeleteResponse: z.ZodObject<{
|
|
290
|
+
ok: z.ZodLiteral<true>;
|
|
291
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
292
|
+
ok: z.ZodLiteral<true>;
|
|
293
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
294
|
+
ok: z.ZodLiteral<true>;
|
|
295
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
296
|
+
export type ScopeMemberDeleteResponse = z.infer<typeof ScopeMemberDeleteResponse>;
|
|
297
|
+
/** A membership as the decision functions see it (a projection of ScopeMember). */
|
|
298
|
+
export interface ScopeMembershipInput {
|
|
299
|
+
scopeId: string;
|
|
300
|
+
role: UserRole;
|
|
301
|
+
}
|
|
302
|
+
/** What token minting resolved: the scope to stamp into the `scope` claim + the `role` claim value. */
|
|
303
|
+
export interface ResolvedTokenScope {
|
|
304
|
+
scope: string;
|
|
305
|
+
role: string;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* THE instance-admin override rule, in one place:
|
|
309
|
+
*
|
|
310
|
+
* **An instance `admin`'s effective role inside ANY scope is always `"admin"`** — regardless of whether
|
|
311
|
+
* they hold a membership there or what that membership says. For everyone else the effective role is
|
|
312
|
+
* exactly their membership role (`membershipRole`), and `undefined` when they have no membership.
|
|
313
|
+
*
|
|
314
|
+
* `instanceRole` is the instance-level users.role string; only the exact value `"admin"` triggers the
|
|
315
|
+
* override (publisher/editor/viewer at instance level grant NO cross-scope power).
|
|
316
|
+
*/
|
|
317
|
+
export declare function effectiveScopeRole(membershipRole: UserRole | undefined, instanceRole: string): UserRole | undefined;
|
|
318
|
+
/**
|
|
319
|
+
* THE token-minting scope decision — which scope (and role claim) a fresh access token binds to, given
|
|
320
|
+
* the principal's scope memberships and instance role. Deterministic; the registry (Node/Next) imports
|
|
321
|
+
* and calls this at every mint (device-flow grant AND refresh). NORMATIVE RULES (verbatim, 任务书拍定):
|
|
322
|
+
*
|
|
323
|
+
* 1. **唯一成员 scope** — exactly one membership → bind that scope, role = that membership's effective
|
|
324
|
+
* role (effectiveScopeRole: instance admin ⇒ "admin").
|
|
325
|
+
* 2. **多个含 global** — multiple memberships, one of them "global" → bind "global", role = the global
|
|
326
|
+
* membership's effective role.
|
|
327
|
+
* 3. **多个不含 global** — multiple memberships, none "global" → bind the scope whose id is FIRST in
|
|
328
|
+
* lexicographic (UTF-16 code-unit) order — deterministic, no wall-clock/insertion-order dependence;
|
|
329
|
+
* role = that membership's effective role.
|
|
330
|
+
* 4. **零成员** — no memberships → `{scope: "global", role: instanceRole}` (the instance role IS the
|
|
331
|
+
* role claim, exactly as pre-scope tokens behaved — full backward compatibility).
|
|
332
|
+
*
|
|
333
|
+
* Duplicate scopeIds in the input: the entry that sorts first (by scopeId, then by role string) wins —
|
|
334
|
+
* callers should not pass duplicates, but the function stays deterministic if they do.
|
|
335
|
+
*/
|
|
336
|
+
export declare function resolveTokenScope(memberships: readonly ScopeMembershipInput[], instanceRole: string): ResolvedTokenScope;
|
|
337
|
+
/**
|
|
338
|
+
* THE scope-switch authorization decision (POST /api/v1/auth/scope): allowed ⇔ the caller is a MEMBER of
|
|
339
|
+
* `targetScope` (any role) OR an instance `admin` (who may enter any scope, including ones they are not
|
|
340
|
+
* a member of — their effective role there is "admin" per effectiveScopeRole). Everyone else →
|
|
341
|
+
* `access_denied`. Note: "global" gets NO special-case here — a multi-scope user without a global
|
|
342
|
+
* membership cannot switch to global (their default mint never lands there either, rule 3 above).
|
|
343
|
+
*/
|
|
344
|
+
export declare function canSwitchScope(memberships: readonly ScopeMembershipInput[], instanceRole: string, targetScope: string): boolean;
|
|
345
|
+
/**
|
|
346
|
+
* Read the active scope off verified JWT claims: a non-empty string `scope` claim, else GLOBAL_SCOPE.
|
|
347
|
+
* This is the ONE place the "missing claim = global" backward-compat rule lives — workers and the
|
|
348
|
+
* registry both call this instead of defaulting ad hoc.
|
|
349
|
+
*/
|
|
350
|
+
export declare function tokenScopeOf(claims: {
|
|
351
|
+
scope?: unknown;
|
|
352
|
+
}): string;
|
|
353
|
+
//# sourceMappingURL=scopes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../src/api/scopes.ts"],"names":[],"mappings":"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"}
|