@substrat-run/control-plane-api 0.133.0 → 0.135.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 (44) hide show
  1. package/dist/api.d.ts.map +1 -1
  2. package/dist/api.js +22 -2
  3. package/dist/api.js.map +1 -1
  4. package/dist/auth.d.ts +2 -13
  5. package/dist/auth.d.ts.map +1 -1
  6. package/dist/auth.js +4 -13
  7. package/dist/auth.js.map +1 -1
  8. package/dist/browser.d.ts +12 -0
  9. package/dist/browser.d.ts.map +1 -0
  10. package/dist/browser.js +13 -0
  11. package/dist/browser.js.map +1 -0
  12. package/dist/cf-observability.js +1 -1
  13. package/dist/cf-observability.js.map +1 -1
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +13 -18
  16. package/dist/errors.js.map +1 -1
  17. package/dist/failure-attribution.js +1 -1
  18. package/dist/failure-attribution.js.map +1 -1
  19. package/dist/index.d.ts +1 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -2
  22. package/dist/index.js.map +1 -1
  23. package/dist/platform-drain.d.ts.map +1 -1
  24. package/dist/platform-drain.js +4 -4
  25. package/dist/platform-drain.js.map +1 -1
  26. package/dist/preview-auth.js +1 -1
  27. package/dist/preview-auth.js.map +1 -1
  28. package/dist/staff-client.d.ts +399 -0
  29. package/dist/staff-client.d.ts.map +1 -0
  30. package/dist/staff-client.js +302 -0
  31. package/dist/staff-client.js.map +1 -0
  32. package/dist/vertical-client.d.ts +3 -1
  33. package/dist/vertical-client.d.ts.map +1 -1
  34. package/dist/vertical-client.js +8 -2
  35. package/dist/vertical-client.js.map +1 -1
  36. package/package.json +10 -5
  37. package/dist/client.d.ts +0 -137
  38. package/dist/client.d.ts.map +0 -1
  39. package/dist/client.js +0 -194
  40. package/dist/client.js.map +0 -1
  41. package/dist/identity-tenants.d.ts +0 -30
  42. package/dist/identity-tenants.d.ts.map +0 -1
  43. package/dist/identity-tenants.js +0 -23
  44. package/dist/identity-tenants.js.map +0 -1
package/dist/client.d.ts DELETED
@@ -1,137 +0,0 @@
1
- import type { ConnectionProbe, EntitlementGrant, EntitlementGrantInput, QueryScopeInput, ReadScopeTableInput, Scope, ScopeId, ScopeQueryResult, DenialFilter, DenialSummary, PermissionDenial, ScopeTable, ScopeTablePage, Tenant, TenantId } from '@substrat-run/contracts';
2
- import { type IdentityTenant } from './identity-tenants.js';
3
- /**
4
- * A typed HTTP client for the control-plane API — the vertical side of the
5
- * connect seam (first-flow.md slice 4).
6
- *
7
- * A vertical that runs against a *separately deployed* shared control plane uses
8
- * this to (a) register its tenant, entitlements, and scope on boot, and (b) gate
9
- * each request on the directory's authoritative lifecycle — `assertScopeActive`
10
- * fails closed exactly as the kernel's own `getScope` does, so a suspend in the
11
- * console bites the vertical's next operation even across process and deployment
12
- * boundaries.
13
- *
14
- * What it deliberately does NOT do: write roles or grants. Those are not on the
15
- * control-plane HTTP surface (api.ts §4.5 — permission writes are the human
16
- * checkpoint, D-22/D-29), so a connected vertical keeps its permission model
17
- * local and treats the shared plane as the authority for tenant/scope lifecycle
18
- * and entitlements only.
19
- *
20
- * `fetch` is injectable: pass a Worker service-binding's fetch, or the router's
21
- * own `app.fetch` for an in-process test, instead of the global.
22
- */
23
- export interface ControlPlaneClientOptions {
24
- /** Base URL of the control-plane API, e.g. `https://cp.example.com` or `http://127.0.0.1:8788`. */
25
- baseUrl: string;
26
- /**
27
- * The platform actor id stamped as the audit subject on every write — sent as the
28
- * dev-only `x-platform-actor` header, and ONLY when no `serviceToken` is set. With a
29
- * token the control plane resolves the subject from the token (its fixed service
30
- * actor), so the header is never consulted there and is not sent (#980).
31
- */
32
- actor: string;
33
- /**
34
- * A service credential (`x-service-token`) proving the caller is an authorized
35
- * vertical, not just anyone with an actor id. Required when the control plane
36
- * has real auth — the dev-actor header alone does not authenticate there. When
37
- * set, it is the request's only credential: `actor` is not sent.
38
- */
39
- serviceToken?: string;
40
- /** Defaults to the global `fetch`. */
41
- fetch?: typeof globalThis.fetch;
42
- }
43
- export interface ClientProvisionScopeInput {
44
- tenantId: TenantId;
45
- scopeId: ScopeId;
46
- slug?: string;
47
- kind?: string;
48
- name?: string;
49
- vertical?: string | null;
50
- jurisdiction?: 'eu' | 'us' | 'global';
51
- }
52
- /** A non-2xx (or unreachable) control-plane response. `status` is 0 on a transport error. */
53
- export declare class ControlPlaneError extends Error {
54
- readonly status: number;
55
- /**
56
- * The provider's own answer, when the plane refused a connect because the credential
57
- * was rejected upstream (#605, 422). Carried so a console can show WHY — "Scrive:
58
- * No valid access credentials were provided" — instead of a generic save failure.
59
- */
60
- readonly probe?: ConnectionProbe | undefined;
61
- constructor(status: number, message: string,
62
- /**
63
- * The provider's own answer, when the plane refused a connect because the credential
64
- * was rejected upstream (#605, 422). Carried so a console can show WHY — "Scrive:
65
- * No valid access credentials were provided" — instead of a generic save failure.
66
- */
67
- probe?: ConnectionProbe | undefined);
68
- }
69
- export declare class ControlPlaneClient {
70
- private readonly baseUrl;
71
- private readonly actor;
72
- private readonly serviceToken?;
73
- private readonly fetchImpl;
74
- constructor(options: ControlPlaneClientOptions);
75
- /**
76
- * One request, and the one place a failure is read: a transport error or a non-2xx answer
77
- * throws `ControlPlaneError`, so what comes back is always a successful `Response`. A 404
78
- * is handed back as-is when the caller allows it.
79
- */
80
- private send;
81
- private call;
82
- createTenant(input: {
83
- id: TenantId;
84
- slug: string;
85
- name: string;
86
- }): Promise<Tenant>;
87
- grantEntitlement(tenantId: TenantId, key: string, plan?: EntitlementGrantInput): Promise<EntitlementGrant[]>;
88
- provisionScope(input: ClientProvisionScopeInput): Promise<Scope>;
89
- /**
90
- * Confirm the scope exists here, moving the directory row provisioning → active.
91
- *
92
- * In this (push) direction the vertical has already built the scope locally, so
93
- * registering and confirming are the same moment — but they stay two calls so
94
- * `provisionScope` means one thing everywhere, and so the directory is never the
95
- * one deciding a scope is ready (K-31).
96
- */
97
- activateScope(tenantId: TenantId, scopeId: ScopeId): Promise<Scope>;
98
- getTenant(tenantId: TenantId): Promise<Tenant | undefined>;
99
- getScopeRecord(tenantId: TenantId, scopeId: ScopeId): Promise<Scope | undefined>;
100
- listEntitlements(tenantId: TenantId): Promise<EntitlementGrant[]>;
101
- /**
102
- * The tenants a login builds for, each flagged with whether it holds the `builder`
103
- * entitlement — the builder studio's membership read (builder-plane.md §4). `externalId`
104
- * is the login's OIDC subject. Service-token gated: an unset token is refused by the
105
- * plane, never bypassed.
106
- *
107
- * The answer is PARSED, not asserted: a plane that renamed `entitled`, or answered an
108
- * `{ error }` body with a 200, would hand a cast `entitled: undefined` — falsy, so the
109
- * studio would lock the tenant out with the ordinary "not enabled" page and nothing
110
- * anywhere would say the directory had changed shape. A refused parse throws instead.
111
- * The message names the shape that was wrong, never the body — the body is directory
112
- * facts about a person's tenants.
113
- */
114
- identityTenants(externalId: string): Promise<IdentityTenant[]>;
115
- /** Every table in the scope's own database, with row counts (Data view). */
116
- listScopeTables(tenantId: TenantId, scopeId: ScopeId): Promise<ScopeTable[]>;
117
- /** A bounded page of one table of the scope's database. */
118
- readScopeTable(tenantId: TenantId, scopeId: ScopeId, input: ReadScopeTableInput): Promise<ScopeTablePage>;
119
- /** One read-only SQL statement against the scope's database — the console (#219). */
120
- queryScope(tenantId: TenantId, scopeId: ScopeId, input: QueryScopeInput): Promise<ScopeQueryResult>;
121
- /**
122
- * The scope's recorded permission refusals, bucketed per (actor, permission) with the
123
- * window's own facts beside them — the view to open first, because the raw log's
124
- * volume is attacker-influenceable and a prober can flood a newest-first page.
125
- */
126
- summarizeDenials(tenantId: TenantId, scopeId: ScopeId, filter?: DenialFilter): Promise<DenialSummary>;
127
- /** The raw rows behind a bucket, newest first. */
128
- listDenials(tenantId: TenantId, scopeId: ScopeId, filter?: DenialFilter): Promise<PermissionDenial[]>;
129
- /**
130
- * The gate. Throws unless the tenant is active AND the scope exists and is
131
- * active — the same fail-closed logic the kernel's `validateScopeAccess`
132
- * applies locally, so a tenant-level cascade suspend bites too, not just a
133
- * per-scope one. Call it before handing a request to the local scope host.
134
- */
135
- assertScopeActive(tenantId: TenantId, scopeId: ScopeId): Promise<void>;
136
- }
137
- //# sourceMappingURL=client.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js DELETED
@@ -1,194 +0,0 @@
1
- import { denialQuery, problemDetail } from '@substrat-run/contracts';
2
- import { DEV_ACTOR_HEADER, SERVICE_TOKEN_HEADER } from './auth.js';
3
- import { identityTenantsResponse } from './identity-tenants.js';
4
- /** A non-2xx (or unreachable) control-plane response. `status` is 0 on a transport error. */
5
- export class ControlPlaneError extends Error {
6
- status;
7
- probe;
8
- constructor(status, message,
9
- /**
10
- * The provider's own answer, when the plane refused a connect because the credential
11
- * was rejected upstream (#605, 422). Carried so a console can show WHY — "Scrive:
12
- * No valid access credentials were provided" — instead of a generic save failure.
13
- */
14
- probe) {
15
- super(message);
16
- this.status = status;
17
- this.probe = probe;
18
- this.name = 'ControlPlaneError';
19
- }
20
- }
21
- export class ControlPlaneClient {
22
- baseUrl;
23
- actor;
24
- serviceToken;
25
- fetchImpl;
26
- constructor(options) {
27
- this.baseUrl = options.baseUrl.replace(/\/$/, '');
28
- this.actor = options.actor;
29
- this.serviceToken = options.serviceToken;
30
- // Bind to globalThis: workerd throws "Illegal invocation" if `fetch` is called
31
- // with a `this` other than the global scope (which `this.fetchImpl(...)` would
32
- // otherwise set to this instance). An injected fetch is used as-is.
33
- this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
34
- }
35
- /**
36
- * One request, and the one place a failure is read: a transport error or a non-2xx answer
37
- * throws `ControlPlaneError`, so what comes back is always a successful `Response`. A 404
38
- * is handed back as-is when the caller allows it.
39
- */
40
- async send(path, init, allow404 = false) {
41
- let res;
42
- try {
43
- res = await this.fetchImpl(`${this.baseUrl}${path}`, {
44
- ...init,
45
- headers: {
46
- // One credential per request (#980): a service token identifies the caller as
47
- // the plane's service actor and is checked BEFORE the dev-actor stub, so the
48
- // actor header would be ignored there — and a dev-only header has no business
49
- // leaving a production caller at all. Without a token, the actor header IS the
50
- // (local, UNSAFE) credential.
51
- ...(this.serviceToken
52
- ? { [SERVICE_TOKEN_HEADER]: this.serviceToken }
53
- : { [DEV_ACTOR_HEADER]: this.actor }),
54
- 'content-type': 'application/json',
55
- ...init?.headers,
56
- },
57
- });
58
- }
59
- catch (e) {
60
- // A transport failure (control plane down) must fail closed, not silently
61
- // pass — a vertical that cannot reach the authority does not get to run.
62
- throw new ControlPlaneError(0, `control plane unreachable: ${e.message}`);
63
- }
64
- if (res.status === 404 && allow404)
65
- return res;
66
- if (!res.ok) {
67
- // `problemDetail` is the one reading of a failed body (#971): the RFC 9457
68
- // `detail` first, the deprecated `error` duplicate second, both against the
69
- // published schema — so this works either way, and keeps working the day the
70
- // duplicate is deleted. The status line is the fallback only when the body
71
- // said nothing readable.
72
- const body = await res.json().catch(() => null);
73
- throw new ControlPlaneError(res.status, problemDetail(body) ?? `${res.status} ${res.statusText}`);
74
- }
75
- return res;
76
- }
77
- async call(path, init, allow404 = false) {
78
- const res = await this.send(path, init, allow404);
79
- if (res.status === 404 && allow404)
80
- return undefined;
81
- return res.status === 204 ? undefined : (await res.json());
82
- }
83
- // -- registration (idempotent, mirrors HostAdmin) --------------------------
84
- createTenant(input) {
85
- return this.call('/tenants', { method: 'POST', body: JSON.stringify(input) });
86
- }
87
- grantEntitlement(tenantId, key, plan) {
88
- return this.call(`/tenants/${tenantId}/entitlements/${key}`, {
89
- method: 'PUT',
90
- body: plan === undefined ? undefined : JSON.stringify(plan),
91
- });
92
- }
93
- provisionScope(input) {
94
- return this.call('/scopes', { method: 'POST', body: JSON.stringify(input) });
95
- }
96
- /**
97
- * Confirm the scope exists here, moving the directory row provisioning → active.
98
- *
99
- * In this (push) direction the vertical has already built the scope locally, so
100
- * registering and confirming are the same moment — but they stay two calls so
101
- * `provisionScope` means one thing everywhere, and so the directory is never the
102
- * one deciding a scope is ready (K-31).
103
- */
104
- activateScope(tenantId, scopeId) {
105
- return this.call(`/tenants/${tenantId}/scopes/${scopeId}/activate`, { method: 'POST' });
106
- }
107
- // -- reads used for gating -------------------------------------------------
108
- getTenant(tenantId) {
109
- return this.call(`/tenants/${tenantId}`, undefined, true);
110
- }
111
- getScopeRecord(tenantId, scopeId) {
112
- return this.call(`/tenants/${tenantId}/scopes/${scopeId}`, undefined, true);
113
- }
114
- listEntitlements(tenantId) {
115
- return this.call(`/tenants/${tenantId}/entitlements`);
116
- }
117
- /**
118
- * The tenants a login builds for, each flagged with whether it holds the `builder`
119
- * entitlement — the builder studio's membership read (builder-plane.md §4). `externalId`
120
- * is the login's OIDC subject. Service-token gated: an unset token is refused by the
121
- * plane, never bypassed.
122
- *
123
- * The answer is PARSED, not asserted: a plane that renamed `entitled`, or answered an
124
- * `{ error }` body with a 200, would hand a cast `entitled: undefined` — falsy, so the
125
- * studio would lock the tenant out with the ordinary "not enabled" page and nothing
126
- * anywhere would say the directory had changed shape. A refused parse throws instead.
127
- * The message names the shape that was wrong, never the body — the body is directory
128
- * facts about a person's tenants.
129
- */
130
- async identityTenants(externalId) {
131
- const res = await this.send('/internal/builder/identity-tenants', {
132
- method: 'POST',
133
- body: JSON.stringify({ externalId }),
134
- });
135
- const parsed = identityTenantsResponse.safeParse(await res.json().catch(() => null));
136
- if (!parsed.success) {
137
- throw new ControlPlaneError(res.status, `identity-tenants returned an unexpected shape: ${parsed.error.message}`);
138
- }
139
- return parsed.data.tenants;
140
- }
141
- // -- read-only scope-DB introspection (§5.4 admin-query RPC) ----------------
142
- /** Every table in the scope's own database, with row counts (Data view). */
143
- listScopeTables(tenantId, scopeId) {
144
- return this.call(`/tenants/${tenantId}/scopes/${scopeId}/tables`);
145
- }
146
- /** A bounded page of one table of the scope's database. */
147
- readScopeTable(tenantId, scopeId, input) {
148
- const q = new URLSearchParams({ limit: String(input.limit), offset: String(input.offset) });
149
- return this.call(`/tenants/${tenantId}/scopes/${scopeId}/tables/${encodeURIComponent(input.table)}?${q}`);
150
- }
151
- /** One read-only SQL statement against the scope's database — the console (#219). */
152
- queryScope(tenantId, scopeId, input) {
153
- return this.call(`/tenants/${tenantId}/scopes/${scopeId}/query`, {
154
- method: 'POST',
155
- body: JSON.stringify(input),
156
- });
157
- }
158
- // -- the denial log (K-35, #867) --------------------------------------------
159
- /**
160
- * The scope's recorded permission refusals, bucketed per (actor, permission) with the
161
- * window's own facts beside them — the view to open first, because the raw log's
162
- * volume is attacker-influenceable and a prober can flood a newest-first page.
163
- */
164
- summarizeDenials(tenantId, scopeId, filter) {
165
- return this.call(`/tenants/${tenantId}/scopes/${scopeId}/denials/summary${denialQuery(filter)}`);
166
- }
167
- /** The raw rows behind a bucket, newest first. */
168
- listDenials(tenantId, scopeId, filter) {
169
- return this.call(`/tenants/${tenantId}/scopes/${scopeId}/denials${denialQuery(filter)}`);
170
- }
171
- /**
172
- * The gate. Throws unless the tenant is active AND the scope exists and is
173
- * active — the same fail-closed logic the kernel's `validateScopeAccess`
174
- * applies locally, so a tenant-level cascade suspend bites too, not just a
175
- * per-scope one. Call it before handing a request to the local scope host.
176
- */
177
- async assertScopeActive(tenantId, scopeId) {
178
- const [tenant, scope] = await Promise.all([
179
- this.getTenant(tenantId),
180
- this.getScopeRecord(tenantId, scopeId),
181
- ]);
182
- if (!tenant)
183
- throw new ControlPlaneError(403, `unknown tenant: ${tenantId}`);
184
- if (tenant.status !== 'active') {
185
- throw new ControlPlaneError(403, `tenant not active (status: ${tenant.status}): ${tenantId}`);
186
- }
187
- if (!scope)
188
- throw new ControlPlaneError(403, `unknown scope for tenant: (${tenantId}, ${scopeId})`);
189
- if (scope.status !== 'active') {
190
- throw new ControlPlaneError(403, `scope not active (status: ${scope.status}): ${scopeId}`);
191
- }
192
- }
193
- }
194
- //# sourceMappingURL=client.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAmBrE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAuB,MAAM,uBAAuB,CAAC;AAsDrF,6FAA6F;AAC7F,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAE/B,MAAM;IAON,KAAK;IARhB,YACW,MAAc,EACvB,OAAe;IACf;;;;OAIG;IACM,KAAuB;QAEhC,KAAK,CAAC,OAAO,CAAC,CAAC;sBATN,MAAM;qBAON,KAAK;QAGd,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,MAAM,OAAO,kBAAkB;IACZ,OAAO,CAAS;IAChB,KAAK,CAAS;IACd,YAAY,CAAU;IACtB,SAAS,CAA0B;IAEpD,YAAY,OAAkC;QAC5C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QACzC,+EAA+E;QAC/E,+EAA+E;QAC/E,oEAAoE;QACpE,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACtE,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,IAAkB,EAAE,QAAQ,GAAG,KAAK;QACnE,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,EAAE;gBACnD,GAAG,IAAI;gBACP,OAAO,EAAE;oBACP,8EAA8E;oBAC9E,6EAA6E;oBAC7E,8EAA8E;oBAC9E,+EAA+E;oBAC/E,8BAA8B;oBAC9B,GAAG,CAAC,IAAI,CAAC,YAAY;wBACnB,CAAC,CAAC,EAAE,CAAC,oBAAoB,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE;wBAC/C,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;oBACvC,cAAc,EAAE,kBAAkB;oBAClC,GAAG,IAAI,EAAE,OAAO;iBACjB;aACF,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,0EAA0E;YAC1E,yEAAyE;YACzE,MAAM,IAAI,iBAAiB,CAAC,CAAC,EAAE,8BAA+B,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ;YAAE,OAAO,GAAG,CAAC;QAC/C,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,2EAA2E;YAC3E,4EAA4E;YAC5E,6EAA6E;YAC7E,2EAA2E;YAC3E,yBAAyB;YACzB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YAChD,MAAM,IAAI,iBAAiB,CACzB,GAAG,CAAC,MAAM,EACV,aAAa,CAAC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CACzD,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,KAAK,CAAC,IAAI,CAAI,IAAY,EAAE,IAAkB,EAAE,QAAQ,GAAG,KAAK;QACtE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAClD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ;YAAE,OAAO,SAAc,CAAC;QAC1D,OAAO,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAE,SAAe,CAAC,CAAC,CAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAO,CAAC;IAC3E,CAAC;IAED,6EAA6E;IAE7E,YAAY,CAAC,KAAmD;QAC9D,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;IAED,gBAAgB,CACd,QAAkB,EAClB,GAAW,EACX,IAA4B;QAE5B,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,QAAQ,iBAAiB,GAAG,EAAE,EAAE;YAC3D,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC5D,CAAC,CAAC;IACL,CAAC;IAED,cAAc,CAAC,KAAgC;QAC7C,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;IAED;;;;;;;OAOG;IACH,aAAa,CAAC,QAAkB,EAAE,OAAgB;QAChD,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,QAAQ,WAAW,OAAO,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,6EAA6E;IAE7E,SAAS,CAAC,QAAkB;QAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IAC5D,CAAC;IAED,cAAc,CAAC,QAAkB,EAAE,OAAgB;QACjD,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,QAAQ,WAAW,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IAC9E,CAAC;IAED,gBAAgB,CAAC,QAAkB;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,QAAQ,eAAe,CAAC,CAAC;IACxD,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,eAAe,CAAC,UAAkB;QACtC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,oCAAoC,EAAE;YAChE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,CAAC;SACrC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,uBAAuB,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QACrF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,iBAAiB,CACzB,GAAG,CAAC,MAAM,EACV,kDAAkD,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CACzE,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;IAC7B,CAAC;IAED,8EAA8E;IAE9E,4EAA4E;IAC5E,eAAe,CAAC,QAAkB,EAAE,OAAgB;QAClD,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,QAAQ,WAAW,OAAO,SAAS,CAAC,CAAC;IACpE,CAAC;IAED,2DAA2D;IAC3D,cAAc,CACZ,QAAkB,EAClB,OAAgB,EAChB,KAA0B;QAE1B,MAAM,CAAC,GAAG,IAAI,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC5F,OAAO,IAAI,CAAC,IAAI,CACd,YAAY,QAAQ,WAAW,OAAO,WAAW,kBAAkB,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CACxF,CAAC;IACJ,CAAC;IAED,qFAAqF;IACrF,UAAU,CACR,QAAkB,EAClB,OAAgB,EAChB,KAAsB;QAEtB,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,QAAQ,WAAW,OAAO,QAAQ,EAAE;YAC/D,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAE9E;;;;OAIG;IACH,gBAAgB,CACd,QAAkB,EAClB,OAAgB,EAChB,MAAqB;QAErB,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,QAAQ,WAAW,OAAO,mBAAmB,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACnG,CAAC;IAED,kDAAkD;IAClD,WAAW,CACT,QAAkB,EAClB,OAAgB,EAChB,MAAqB;QAErB,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,QAAQ,WAAW,OAAO,WAAW,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,iBAAiB,CAAC,QAAkB,EAAE,OAAgB;QAC1D,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACxC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;YACxB,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC;SACvC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,mBAAmB,QAAQ,EAAE,CAAC,CAAC;QAC7E,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,8BAA8B,MAAM,CAAC,MAAM,MAAM,QAAQ,EAAE,CAAC,CAAC;QAChG,CAAC;QACD,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,8BAA8B,QAAQ,KAAK,OAAO,GAAG,CAAC,CAAC;QACpG,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,6BAA6B,KAAK,CAAC,MAAM,MAAM,OAAO,EAAE,CAAC,CAAC;QAC7F,CAAC;IACH,CAAC;CACF"}
@@ -1,30 +0,0 @@
1
- import { z } from '@substrat-run/contracts';
2
- /**
3
- * A tenant a login builds for, as the control plane's studio membership read states it
4
- * (`POST /internal/builder/identity-tenants`, builder-plane.md §4).
5
- *
6
- * The three directory fields are parsed with the SAME schemas the `tenant` record
7
- * publishes (`contracts/src/tenancy.ts`), not with generic strings: the id is a ULID, the
8
- * slug is the constrained slug, the name is non-empty. That costs nothing in lockout risk
9
- * — `createTenantInput` picks those same three, so a tenant that exists satisfied them at
10
- * creation — and it buys the half a loose parse would miss. A non-empty but malformed id
11
- * passes `z.string()` and then becomes a Durable Object key and a team route, which is the
12
- * downstream use this parse exists to protect.
13
- */
14
- export declare const identityTenant: z.ZodObject<{
15
- id: z.core.$ZodBranded<z.ZodString, "TenantId", "out">;
16
- slug: z.ZodString;
17
- name: z.ZodString;
18
- entitled: z.ZodBoolean;
19
- }, z.core.$strip>;
20
- /** The `/internal/builder/identity-tenants` response body. */
21
- export declare const identityTenantsResponse: z.ZodObject<{
22
- tenants: z.ZodArray<z.ZodObject<{
23
- id: z.core.$ZodBranded<z.ZodString, "TenantId", "out">;
24
- slug: z.ZodString;
25
- name: z.ZodString;
26
- entitled: z.ZodBoolean;
27
- }, z.core.$strip>>;
28
- }, z.core.$strip>;
29
- export type IdentityTenant = z.infer<typeof identityTenant>;
30
- //# sourceMappingURL=identity-tenants.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"identity-tenants.d.ts","sourceRoot":"","sources":["../src/identity-tenants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,CAAC,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc;;;;;iBAMzB,CAAC;AAEH,8DAA8D;AAC9D,eAAO,MAAM,uBAAuB;;;;;;;iBAAiD,CAAC;AAEtF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
@@ -1,23 +0,0 @@
1
- import { slug, tenantId, z } from '@substrat-run/contracts';
2
- /**
3
- * A tenant a login builds for, as the control plane's studio membership read states it
4
- * (`POST /internal/builder/identity-tenants`, builder-plane.md §4).
5
- *
6
- * The three directory fields are parsed with the SAME schemas the `tenant` record
7
- * publishes (`contracts/src/tenancy.ts`), not with generic strings: the id is a ULID, the
8
- * slug is the constrained slug, the name is non-empty. That costs nothing in lockout risk
9
- * — `createTenantInput` picks those same three, so a tenant that exists satisfied them at
10
- * creation — and it buys the half a loose parse would miss. A non-empty but malformed id
11
- * passes `z.string()` and then becomes a Durable Object key and a team route, which is the
12
- * downstream use this parse exists to protect.
13
- */
14
- export const identityTenant = z.object({
15
- id: tenantId,
16
- slug,
17
- name: z.string().min(1),
18
- /** Whether the tenant holds the `builder` entitlement (the control plane applies expiry at read). */
19
- entitled: z.boolean(),
20
- });
21
- /** The `/internal/builder/identity-tenants` response body. */
22
- export const identityTenantsResponse = z.object({ tenants: z.array(identityTenant) });
23
- //# sourceMappingURL=identity-tenants.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"identity-tenants.js","sourceRoot":"","sources":["../src/identity-tenants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,EAAE,EAAE,QAAQ;IACZ,IAAI;IACJ,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,qGAAqG;IACrG,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;CACtB,CAAC,CAAC;AAEH,8DAA8D;AAC9D,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC"}