@substrat-run/control-plane-api 0.133.0 → 0.134.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.
@@ -0,0 +1,299 @@
1
+ import { denialQuery } from '@substrat-run/contracts';
2
+ import { ControlPlaneTransport } from './transport.js';
3
+ /** Repeatable params (status, action) are appended once per value — the API reads them
4
+ * with `c.req.queries()`. `undefined` is dropped. */
5
+ function query(params) {
6
+ const q = new URLSearchParams();
7
+ for (const [k, v] of Object.entries(params)) {
8
+ if (v === undefined)
9
+ continue;
10
+ if (Array.isArray(v))
11
+ for (const one of v)
12
+ q.append(k, one);
13
+ else
14
+ q.append(k, String(v));
15
+ }
16
+ const s = q.toString();
17
+ return s ? `?${s}` : '';
18
+ }
19
+ const seg = encodeURIComponent;
20
+ export class ControlPlaneStaffClient extends ControlPlaneTransport {
21
+ json(method, path, body) {
22
+ return this.call(path, {
23
+ method,
24
+ body: body === undefined ? undefined : JSON.stringify(body),
25
+ });
26
+ }
27
+ post = (path, body) => this.json('POST', path, body);
28
+ // -- tenants ---------------------------------------------------------------
29
+ listTenants = (page = {}) => this.call(`/tenants${query({ ...page })}`);
30
+ getTenant = (id) => this.call(`/tenants/${id}`);
31
+ createTenant = (input) => this.post('/tenants', input);
32
+ setTenantStatus = (id, status) => this.json('PATCH', `/tenants/${id}/status`, { status });
33
+ // Reap a deleting tenant NOW (§4.8) — skips the grace window: every scope reaped,
34
+ // PII/config directory rows cleared, the tenant row kept as a `reaped` tombstone.
35
+ reapTenant = (id) => this.post(`/tenants/${id}/reap`);
36
+ // The per-tenant store ledgers as inventory (#301 D1, #473 R2): which database and
37
+ // which bucket hold this tenant's bytes. Read-only — stores are minted by
38
+ // provisioning, never from here.
39
+ tenantStores = (id) => this.call(`/tenants/${id}/stores`);
40
+ listEntitlements = (id) => this.call(`/tenants/${id}/entitlements`);
41
+ // The key rides in the PATH, so it is encoded (#691). `entitlementGrant` accepts any
42
+ // non-empty string — looser than the manifest's `/^[a-z0-9-]+$/` — precisely so a
43
+ // legacy grant like `t-0wv2mwk4j5/crm-eff` round-trips. Unencoded, its slash forked the
44
+ // route and the console could neither grant nor REVOKE the very rows that needed
45
+ // cleaning up, which is what forced the hand-curl.
46
+ grantEntitlement = (id, key, plan) => this.json('PUT', `/tenants/${id}/entitlements/${seg(key)}`, plan);
47
+ revokeEntitlement = (id, key) => this.json('DELETE', `/tenants/${id}/entitlements/${seg(key)}`);
48
+ // -- platform --------------------------------------------------------------
49
+ // Where the platform's compute and stores live — the account + dispatch namespace the
50
+ // refs this console renders resolve in, so a scope's script or a tenant's database can
51
+ // be a LINK into the Cloudflare dashboard rather than an id to search for. Answers
52
+ // null on a control plane with no runtime configured (self-host): the views then show
53
+ // the same identifiers, unlinked.
54
+ platformRuntime = () => this.call('/platform/runtime');
55
+ // The Durable Object namespaces one script defines, scope-class first — the ids the
56
+ // dashboard addresses a namespace by. 501s when the control plane has no lookup
57
+ // configured, which callers treat as "link to the list instead".
58
+ doNamespaces = (script) => this.call(`/platform/do-namespaces${query({ script })}`);
59
+ // §5's meters 1 and 2 (#38) — tenants + effective-active scopes, and the entitlement
60
+ // store grouped by SKU and tier. Computed platform-side because the billable rule
61
+ // (a scope is billable only if its tenant is active; expiry decided at `readAt`)
62
+ // belongs to one definition, not to whichever surface renders it. Omit `tenantId`
63
+ // for the fleet reading.
64
+ readMeters = (tenantId) => this.call(`/meters${query({ tenantId })}`);
65
+ // Storage (#1524): one PAGE of a tenant's scope-database sizes, read on demand. Each
66
+ // scope read wakes its Durable Object, so the card calls this only when a person asks,
67
+ // and walks further pages with `cursor` one press at a time.
68
+ readStorage = (tenantId, cursor) => this.call(`/meters/storage${query({ tenantId, cursor })}`);
69
+ // Meter 3 (#1054) — model usage, the one D-30 could not compute: the lines the
70
+ // `model-usage` intents drained into the directory, folded per (tenant, vertical,
71
+ // model) with the platform's margin applied at read time. Defaults to the current
72
+ // calendar month so far; omit `tenantId` for the fleet.
73
+ readModelUsage = (q = {}) => this.call(`/model-usage/summary${query({ ...q })}`);
74
+ // -- scopes ----------------------------------------------------------------
75
+ listScopes = (filter) => this.call(`/scopes${query({ ...filter })}`);
76
+ // Fleet migration progress (kernel-design §5.3, #49): "release N: X/Y
77
+ // migrated, P pending, F failed" against the deployment's frontier.
78
+ migrationProgress = (vertical) => this.call(`/fleet/migrations${query({ vertical })}`);
79
+ getScope = (tenantId, scopeId) => this.call(`/tenants/${tenantId}/scopes/${scopeId}`);
80
+ // Scope health (#321): an ACTIVE scope with a resolvable serving script but an empty
81
+ // role projection serves traffic every check denies — surfaced as a platform condition
82
+ // here instead of only as a per-app 403. `missingStores` (#825) is the same kind of
83
+ // condition one layer down: a per-tenant store the vertical declares that this tenant
84
+ // was never minted, invisible until the code touches it.
85
+ scopeHealth = (tenantId, scopeId) => this.call(`/tenants/${tenantId}/scopes/${scopeId}/health`);
86
+ // The K-35 denial log (#867) — the scope's own record of every ENFORCED permission
87
+ // refusal. Summary first: the raw log's volume is attacker-influenceable (a probing
88
+ // client mints rows), so bucketing is what keeps a quiet actor visible next to a loud
89
+ // one. The rows are the drill-down behind a bucket.
90
+ denialSummary = (t, s, filter) => this.call(`/tenants/${t}/scopes/${s}/denials/summary${denialQuery(filter)}`);
91
+ listDenials = (t, s, filter) => this.call(`/tenants/${t}/scopes/${s}/denials${denialQuery(filter)}`);
92
+ // The #1666 schedule kill switch, read and moved from the console (#1674/#1675). No
93
+ // new permission surface here — the route is already staff-only server-side
94
+ // (`confinedTenant`), and the console is a staff session. `moduleId` is the module's
95
+ // manifest id (e.g. '@substrat-run/engine-absence'), always read off a status entry
96
+ // rather than typed by hand. A deployment predating the route answers 501, which
97
+ // `ControlPlaneError` relays verbatim (never a wrong `on`).
98
+ systemGrantsStatus = (t, s) => this.call(`/tenants/${t}/scopes/${s}/system-grants`);
99
+ // DELETE on the switch route (#1676) — `reason` is required server-side
100
+ // (`z.string().trim().min(1).max(500)`) and lands on the admin log beside the actor.
101
+ switchScheduleOff = (t, s, moduleId, reason) => this.json('DELETE', `/tenants/${t}/scopes/${s}/system-grants`, {
102
+ moduleId,
103
+ reason,
104
+ });
105
+ // POST on the same route — the only way back on; restores exactly what the DELETE took.
106
+ switchScheduleOn = (t, s, moduleId, reason) => this.post(`/tenants/${t}/scopes/${s}/system-grants`, { moduleId, reason });
107
+ // The #1706 peer kill switch, read and moved from the console. The route admits a
108
+ // tenant's own credential as well as staff (unlike the schedule switch above), because
109
+ // it answers a question about the tenant's own two apps; the console is a staff session
110
+ // either way. `vertical` is the CALLING vertical's registry slug, always read off a
111
+ // status entry rather than typed by hand. A deployment predating the route answers 501,
112
+ // which `ControlPlaneError` relays verbatim (never a wrong `on`).
113
+ peerGrantsStatus = (t, s) => this.call(`/tenants/${t}/scopes/${s}/peer-grants`);
114
+ // DELETE on the switch route — `reason` is required server-side and lands on the admin
115
+ // log beside the actor, exactly as the schedule switch's does.
116
+ switchPeerOff = (t, s, vertical, reason) => this.json('DELETE', `/tenants/${t}/scopes/${s}/peer-grants`, {
117
+ vertical,
118
+ reason,
119
+ });
120
+ // POST on the same route — the only way back on; restores exactly what the DELETE took.
121
+ switchPeerOn = (t, s, vertical, reason) => this.post(`/tenants/${t}/scopes/${s}/peer-grants`, { vertical, reason });
122
+ // #1705 PR 3: where each cross-vertical edge of a tenant stands, read live through the
123
+ // sweep's own reach, and the replay lever on a consumer scope. The lever's body carries its
124
+ // acknowledgement literal. Without it the plane refuses in the words it stands for.
125
+ // `focus` narrows the read to one scope's edges, into it and out of it.
126
+ crossVerticalEdges = (t, focus) => this.call(`/tenants/${t}/cross-vertical/edges${focus ? `?scopeId=${seg(focus)}` : ''}`);
127
+ moveImportCursor = (t, s, move) => this.post(`/tenants/${t}/scopes/${s}/import-cursor`, move);
128
+ provisionScope = (input) => this.post('/scopes', input);
129
+ // One method per audited transition, mirroring the API and HostAdmin. The
130
+ // console renders only legal transitions; the graph is enforced below.
131
+ // provisioning → active: the vertical has confirmed the scope exists (K-31).
132
+ activateScope = (t, s) => this.post(`/tenants/${t}/scopes/${s}/activate`);
133
+ suspendScope = (t, s) => this.post(`/tenants/${t}/scopes/${s}/suspend`);
134
+ unsuspendScope = (t, s) => this.post(`/tenants/${t}/scopes/${s}/unsuspend`);
135
+ /**
136
+ * Re-run the vertical's provision for a scope that already has one — NOT a status
137
+ * transition, which is why it sits outside the ladder above and outside
138
+ * `availableActions`. Nothing about the scope's state changes; what happens is that
139
+ * everything provisioning delivers is delivered again: entitlements, identity links,
140
+ * connection grants, and the vertical's OWN `onProvision` hook.
141
+ *
142
+ * Idempotent at the far end (K-31), so it is safe to press twice. It is the lever
143
+ * for two shapes of stuck install: the #332 lockout (roles projected, zero tuples,
144
+ * every login denied), and an install that predates something its vertical now mints
145
+ * for itself at provision — a new service principal, say — which no other path can
146
+ * ever deliver, because provision runs at install and never again.
147
+ */
148
+ reprovisionScope = (t, s) => this.post(`/tenants/${t}/scopes/${s}/provision`);
149
+ archiveScope = (t, s) => this.post(`/tenants/${t}/scopes/${s}/archive`);
150
+ unarchiveScope = (t, s) => this.post(`/tenants/${t}/scopes/${s}/unarchive`);
151
+ // archived → reaped (§4.4): irreversibly wipe the scope's DO storage, keeping the
152
+ // directory row as a tombstone. Staff-only server-side; the console arms it behind a
153
+ // type-to-confirm dialog because, unlike archive, there is no restore.
154
+ //
155
+ // `backup: true` is sent ALWAYS, never omitted (#493): the control plane treats an
156
+ // explicit ask as "back up or refuse", so a console reap can never quietly wipe a
157
+ // scope because the backup bucket went unbound. The returned `backup` names the copy
158
+ // that landed — the operator's proof it exists, and the address to restore from.
159
+ reapScope = (t, s, opts = { backup: true }) => this.post(`/tenants/${t}/scopes/${s}/reap`, opts);
160
+ // Move ONE scope onto a DIFFERENT vertical lineage's serving script (#389) — the
161
+ // update-rebind behind retiring a lineage in favour of another. Data-first with the
162
+ // source script kept as the backout. `ackMigrations` is the digest gate's override:
163
+ // the control plane refuses the crossing when the two lineages' migration digests
164
+ // differ unless the operator acknowledges having read both surfaces. `abandonData`
165
+ // is deliberately NOT exposed — it exists for pre-#236 relic scripts only, and a
166
+ // console that offers it invites moving an install while leaving its data behind.
167
+ rebindScopeVertical = (t, s, vertical, opts = {}) => this.post(`/tenants/${t}/scopes/${s}/rebind-vertical`, { vertical, ...opts });
168
+ /** The copies held for a scope, newest first — readable after the reap (the tombstone survives). */
169
+ listScopeBackups = (t, s) => this.call(`/tenants/${t}/scopes/${s}/backups`);
170
+ /** Take a copy without reaping — a pre-migration checkpoint, or an export to keep. */
171
+ backupScope = (t, s) => this.post(`/tenants/${t}/scopes/${s}/backups`);
172
+ // The PLATFORM's own copies (#40) — the directory, not a tenant's scope. Read and
173
+ // take-now only: `POST /directory/restore` is deliberately NOT on this client. A
174
+ // one-click replace-the-whole-directory control is well past what a type-to-confirm
175
+ // dialog can guard (its blast radius is every tenant at once), and the scenario it
176
+ // exists for is one where the directory is GONE — a recovery path that assumes a
177
+ // healthy console is a recovery path that is not there when it is needed. Restore is
178
+ // a deliberate API call from the runbook (control-plane.md §4.9).
179
+ //
180
+ // A 501 here is meaningful, not an error to swallow: it means no backup store is
181
+ // bound, i.e. this control plane keeps NO platform copy. The view renders that as
182
+ // the alarm it is.
183
+ listDirectoryBackups = () => this.call('/directory/backups');
184
+ backupDirectory = () => this.post('/directory/backups');
185
+ // Hard-delete a SNAPSHOT fork (forkedFrom set): wipes its storage, hostnames, and the
186
+ // row (unlike reap, which keeps a tombstone). Refused (409) on a non-fork primary scope.
187
+ deleteScope = (t, s) => this.json('DELETE', `/tenants/${t}/scopes/${s}`);
188
+ // Read only — there is no route that writes a role, by design.
189
+ // Cursor is the composite `${tenantId}|${roleKey}` sort key (scope-host.ts listRoles).
190
+ listRoles = (filter) => this.call(`/roles${query({ ...filter })}`);
191
+ // The hostname map (§4.7). `resolveHostname` is absent on purpose — that is the
192
+ // router's per-request path, not a staff action, and it is not on this surface.
193
+ listHostnames = (filter) => this.call(`/hostnames${query({ ...filter })}`);
194
+ bindHostname = (input) => this.post('/hostnames', input);
195
+ setHostnameStatus = (hostname, status, note) => this.json('PATCH', `/hostnames/${seg(hostname)}/status`, { status, note });
196
+ // Release a bound name — what a retire pass runs before reaping a scope, so the
197
+ // reap's bound-hostname guard is satisfied and the router stops resolving it.
198
+ unbindHostname = (hostname) => this.json('DELETE', `/hostnames/${seg(hostname)}`);
199
+ /**
200
+ * Create one instance of a vertical (K-31). The control plane calls the
201
+ * vertical, because only it can create a usable scope DO.
202
+ *
203
+ * Call this BEFORE `provisionScope`: the directory row should only exist once
204
+ * the vertical is ready, so a failure leaves an invisible orphan rather than a
205
+ * directory row promising a scope that is not there.
206
+ */
207
+ provisionInstance = (verticalSlug, input) => this.post(`/verticals/${seg(verticalSlug)}/instances`, input);
208
+ // -- logs and fleet reads --------------------------------------------------
209
+ adminLog = (q = {}) => this.call(`/admin-log${query({ ...q })}`);
210
+ // Operational failures (#559) — what the platform could NOT do, durable and
211
+ // queryable, distinct from the admin log's successful mutations. Newest first
212
+ // by default; `reference` finds the row a `reference = <id>` CI error names.
213
+ listOpsFailures = (q = {}) => this.call(`/ops-failures${query({ ...q })}`);
214
+ // The fleet's sweep record (#1232) — connections polled, schedules fired or
215
+ // skipped, freshness verdicts. Newest first; 14-day retention.
216
+ listSweepRuns = (q = {}) => this.call(`/sweep-runs${query({ ...q })}`);
217
+ // The schedule kill-switch fleet read (#1674) — every (tenant, scope, module) the
218
+ // directory records a switch position for. `position` defaults to `off` server-side:
219
+ // "what is switched off across the fleet" is the question this read exists to answer.
220
+ listSystemSwitches = (q = {}) => this.call(`/system-switches${query({ ...q })}`);
221
+ // Fleet-wide connection health (#1690) — every tenant's connections with their
222
+ // derived health, refresh-expiry warning, and connector dead letters per provider.
223
+ listConnectionHealth = (q = {}) => this.call(`/connections/health${query({ ...q })}`);
224
+ // Connector calls per provider over a window (#1691) — the trend behind the health
225
+ // line. 501s where the control plane names no connector-call dataset.
226
+ connectorCalls = (q) => this.call(`/connections/calls${query({ ...q })}`);
227
+ // Failures grouped by fingerprint (#1233) — counted defects with a lifecycle.
228
+ // `{ entries }` alone: the server sends no cursor, deliberately.
229
+ listIssues = (q = {}) => this.call(`/issues${query({ ...q })}`);
230
+ // The staff verdict: resolve / ignore / reopen. The fingerprint rides in the
231
+ // body — it embeds U+001F, and a path segment would demand percent-encoding
232
+ // every caller can get subtly wrong.
233
+ setIssueStatus = (fingerprint, status) => this.json('PUT', '/issues/status', { fingerprint, status });
234
+ // -- vertical + version registry (orchestration.md §5.6) ----------------
235
+ // The staff surface for the two human checkpoints: admit/reject a version,
236
+ // and promote a channel — which refuses a changed permission/migration digest
237
+ // unless acknowledged. Register is a producer action; publishing a version
238
+ // (with digests from a build) is CI/CLI, not hand-entry.
239
+ listVerticals = (page = {}) => this.call(`/verticals${query({ ...page })}`);
240
+ registerVertical = (input) => this.post('/verticals', input);
241
+ listVersions = (slug, page = {}) => this.call(`/verticals/${seg(slug)}/versions${query({ ...page })}`);
242
+ admitVersion = (slug, id) => this.post(`/verticals/${seg(slug)}/versions/${id}/admit`);
243
+ rejectVersion = (slug, id, note) => this.post(`/verticals/${seg(slug)}/versions/${id}/reject`, { note });
244
+ listChannels = (slug, page = {}) => this.call(`/verticals/${seg(slug)}/channels${query({ ...page })}`);
245
+ // Publish/unpublish to the PUBLIC marketplace (marketplace-publish.md §5) — the staff
246
+ // admission of a builder's publish request. The API refuses `listed: true` while prod
247
+ // points at an auto-admitted version; that refusal is surfaced verbatim, not pre-checked.
248
+ setVerticalListed = (slug, listed) => this.post(`/verticals/${seg(slug)}/listing`, { listed });
249
+ // The install kill-switch: block/unblock NEW installs (existing scopes keep serving).
250
+ setInstallsBlocked = (slug, blocked) => this.post(`/verticals/${seg(slug)}/install-block`, {
251
+ blocked,
252
+ });
253
+ // Grant/revoke the tenant-provisioner capability (#412): whether this vertical's scopes
254
+ // may enqueue provision-tenant / set-entitlements intents the platform executes.
255
+ setTenantProvisioner = (slug, granted) => this.post(`/verticals/${seg(slug)}/tenant-provisioner`, { granted });
256
+ // Grant/revoke the email-sender capability (#303): whether this vertical's scopes may POST
257
+ // to the /internal/email/send relay and have transactional mail sent on their behalf.
258
+ setEmailSender = (slug, granted) => this.post(`/verticals/${seg(slug)}/email-sender`, {
259
+ granted,
260
+ });
261
+ // Delete a vertical + its versions/channels. Refused (4xx) while any scope is bound.
262
+ deleteVertical = (slug) => this.json('DELETE', `/verticals/${seg(slug)}`);
263
+ promoteVersion = (slug, channel, versionId, acknowledge) => this.post(`/verticals/${seg(slug)}/channels/${channel}/promote`, {
264
+ versionId,
265
+ acknowledge,
266
+ });
267
+ // #1705 PR 3: which installed apps promoting `versionId` would break, read before promoting
268
+ // so the dialog can ask for the export-break acknowledgement up front. Staff see every app.
269
+ promotionImpact = (slug, channel, versionId) => this.call(`/verticals/${seg(slug)}/channels/${channel}/promote-impact?versionId=${seg(versionId)}`);
270
+ // #1677: the two diffs a promote acknowledges. `registry` is null for a version that declared
271
+ // none ("cannot diff"), `migrations` null for one that carries no SQL ("not available").
272
+ versionRegistry = (slug, versionId) => this.call(`/verticals/${seg(slug)}/versions/${seg(versionId)}/registry`);
273
+ versionMigrations = (slug, versionId, base) => this.call(`/verticals/${seg(slug)}/versions/${seg(versionId)}/migrations${base === undefined ? '' : `?base=${seg(base)}`}`);
274
+ // Pin a scope to a version — what the router dispatches on (orchestration.md §5.4).
275
+ // Refuses a non-admitted version below the seam.
276
+ bindScopeVersion = (tenantId, scopeId, versionId) => this.post(`/tenants/${tenantId}/scopes/${scopeId}/version`, { versionId });
277
+ // -- members (console → Members) ----------------------------------------
278
+ // Who may act on the control plane (staff_actor) — the CP worker's own D1,
279
+ // not the directory, so the routes live beside /api/auth/* rather than in
280
+ // control-plane-api. Every mutation returns the fresh reading: one round
281
+ // trip, no refetch. (Builder-studio access is NOT managed here — it is the
282
+ // `builder` entitlement on the tenant, granted like any SKU.)
283
+ listMembers = () => this.call('/members');
284
+ grantStaffAccess = (email, name) => this.post('/members/staff', { email, name });
285
+ revokeStaffAccess = (email) => this.post('/members/staff/revoke', { email });
286
+ // -- observability (design/observability.md §4.1) -----------------------
287
+ // Proxied reads over the control plane's observability seam; 501 when no
288
+ // backend is configured. Tier-3 numbers: sampled, approximate, never money
289
+ // (master-plan §5.3).
290
+ serviceMetrics = (hours) => this.call(`/observability/metrics${query({ hours })}`);
291
+ recentLogs = (q = {}) => this.call(`/observability/logs${query({ ...q })}`);
292
+ /** Observed-vs-declared outbound egress for a vertical's deployed versions (#859). */
293
+ verticalEgress = (slug, q = {}) => this.call(`/verticals/${seg(slug)}/egress${query({ ...q })}`);
294
+ // Platform-request drain backlog, fleet-wide (#1690 §2) — terminal give-ups over a
295
+ // window, plus the pending count as of the sweep's last drain pass (#1840). See
296
+ // `PlatformRequestBacklog`'s own doc.
297
+ platformRequestBacklog = () => this.call('/platform-requests/backlog');
298
+ }
299
+ //# sourceMappingURL=staff-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staff-client.js","sourceRoot":"","sources":["../src/staff-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import type { ConnectionProbe } from '@substrat-run/contracts';
2
+ /**
3
+ * The one HTTP seam every control-plane caller shares (#971): how a request is
4
+ * authenticated, how a failure is read, and what an empty answer is. The callers
5
+ * differ in what they ask — `ControlPlaneClient` is the vertical's connect seam,
6
+ * `ControlPlaneStaffClient` the console's staff surface — not in how they ask it.
7
+ *
8
+ * Browser-safe: nothing here reaches past `fetch` and `@substrat-run/contracts`, which is
9
+ * why the console can import it (`@substrat-run/control-plane-api/client`).
10
+ */
11
+ export interface ControlPlaneTransportOptions {
12
+ /** Base URL of the control-plane API; a path such as `/api` is fine in a browser. */
13
+ baseUrl: string;
14
+ /**
15
+ * The dev-only `x-platform-actor` header, sent only when no `serviceToken` is set. `null`
16
+ * sends none: the console's session mode, where the staff cookie authenticates instead.
17
+ */
18
+ actor: string | null;
19
+ serviceToken?: string;
20
+ fetch?: typeof globalThis.fetch;
21
+ /** Passed to `fetch` only when set — `'include'` carries the console's staff session cookie. */
22
+ credentials?: RequestCredentials;
23
+ }
24
+ /** A non-2xx (or unreachable) control-plane response. `status` is 0 on a transport error. */
25
+ export declare class ControlPlaneError extends Error {
26
+ readonly status: number;
27
+ /**
28
+ * The provider's own answer, when the plane refused a connect because the credential
29
+ * was rejected upstream (#605, 422). Carried so a console can show WHY — "Scrive:
30
+ * No valid access credentials were provided" — instead of a generic save failure.
31
+ */
32
+ readonly probe?: ConnectionProbe | undefined;
33
+ constructor(status: number, message: string,
34
+ /**
35
+ * The provider's own answer, when the plane refused a connect because the credential
36
+ * was rejected upstream (#605, 422). Carried so a console can show WHY — "Scrive:
37
+ * No valid access credentials were provided" — instead of a generic save failure.
38
+ */
39
+ probe?: ConnectionProbe | undefined);
40
+ }
41
+ export declare class ControlPlaneTransport {
42
+ private readonly baseUrl;
43
+ private readonly actor;
44
+ private readonly serviceToken?;
45
+ private readonly fetchImpl;
46
+ private readonly credentials?;
47
+ constructor(options: ControlPlaneTransportOptions);
48
+ /**
49
+ * One request, and the one place a failure is read: a transport error or a non-2xx answer
50
+ * throws `ControlPlaneError`, so what comes back is always a successful `Response`. A 404
51
+ * is handed back as-is when the caller allows it.
52
+ */
53
+ protected send(path: string, init?: RequestInit, allow404?: boolean): Promise<Response>;
54
+ protected call<T>(path: string, init?: RequestInit, allow404?: boolean): Promise<T>;
55
+ }
56
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI/D;;;;;;;;GAQG;AACH,MAAM,WAAW,4BAA4B;IAC3C,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,gGAAgG;IAChG,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED,6FAA6F;AAC7F,qBAAa,iBAAkB,SAAQ,KAAK;IAExC,QAAQ,CAAC,MAAM,EAAE,MAAM;IAEvB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe;IARlC,YACW,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM;IACf;;;;OAIG;IACM,KAAK,CAAC,EAAE,eAAe,YAAA,EAIjC;CACF;AAED,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAqB;IAElD,YAAY,OAAO,EAAE,4BAA4B,EAShD;IAED;;;;OAIG;IACH,UAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwC1F;IAED,UAAgB,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAItF;CACF"}
@@ -0,0 +1,88 @@
1
+ import { problemDetail } from '@substrat-run/contracts';
2
+ import { DEV_ACTOR_HEADER, SERVICE_TOKEN_HEADER } from './auth.js';
3
+ /** A non-2xx (or unreachable) control-plane response. `status` is 0 on a transport error. */
4
+ export class ControlPlaneError extends Error {
5
+ status;
6
+ probe;
7
+ constructor(status, message,
8
+ /**
9
+ * The provider's own answer, when the plane refused a connect because the credential
10
+ * was rejected upstream (#605, 422). Carried so a console can show WHY — "Scrive:
11
+ * No valid access credentials were provided" — instead of a generic save failure.
12
+ */
13
+ probe) {
14
+ super(message);
15
+ this.status = status;
16
+ this.probe = probe;
17
+ this.name = 'ControlPlaneError';
18
+ }
19
+ }
20
+ export class ControlPlaneTransport {
21
+ baseUrl;
22
+ actor;
23
+ serviceToken;
24
+ fetchImpl;
25
+ credentials;
26
+ constructor(options) {
27
+ this.baseUrl = options.baseUrl.replace(/\/$/, '');
28
+ this.actor = options.actor;
29
+ this.serviceToken = options.serviceToken;
30
+ this.credentials = options.credentials;
31
+ // Bind to globalThis: workerd throws "Illegal invocation" if `fetch` is called
32
+ // with a `this` other than the global scope (which `this.fetchImpl(...)` would
33
+ // otherwise set to this instance). An injected fetch is used as-is.
34
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
35
+ }
36
+ /**
37
+ * One request, and the one place a failure is read: a transport error or a non-2xx answer
38
+ * throws `ControlPlaneError`, so what comes back is always a successful `Response`. A 404
39
+ * is handed back as-is when the caller allows it.
40
+ */
41
+ async send(path, init, allow404 = false) {
42
+ let res;
43
+ try {
44
+ res = await this.fetchImpl(`${this.baseUrl}${path}`, {
45
+ ...init,
46
+ ...(this.credentials ? { credentials: this.credentials } : {}),
47
+ headers: {
48
+ // One credential per request (#980): a service token identifies the caller as
49
+ // the plane's service actor and is checked BEFORE the dev-actor stub, so the
50
+ // actor header would be ignored there — and a dev-only header has no business
51
+ // leaving a production caller at all. Without a token, the actor header IS the
52
+ // (local, UNSAFE) credential.
53
+ ...(this.serviceToken
54
+ ? { [SERVICE_TOKEN_HEADER]: this.serviceToken }
55
+ : this.actor !== null
56
+ ? { [DEV_ACTOR_HEADER]: this.actor }
57
+ : {}),
58
+ 'content-type': 'application/json',
59
+ ...init?.headers,
60
+ },
61
+ });
62
+ }
63
+ catch (e) {
64
+ // A transport failure (control plane down) must fail closed, not silently
65
+ // pass — a vertical that cannot reach the authority does not get to run.
66
+ throw new ControlPlaneError(0, `control plane unreachable: ${e.message}`);
67
+ }
68
+ if (res.status === 404 && allow404)
69
+ return res;
70
+ if (!res.ok) {
71
+ // `problemDetail` is the one reading of a failed body (#971): the RFC 9457
72
+ // `detail` first, the deprecated `error` duplicate second, both against the
73
+ // published schema — so this works either way, and keeps working the day the
74
+ // duplicate is deleted. The status line is the fallback only when the body
75
+ // said nothing readable.
76
+ const body = await res.json().catch(() => null);
77
+ throw new ControlPlaneError(res.status, problemDetail(body) ?? `${res.status} ${res.statusText}`);
78
+ }
79
+ return res;
80
+ }
81
+ async call(path, init, allow404 = false) {
82
+ const res = await this.send(path, init, allow404);
83
+ if (res.status === 404 && allow404)
84
+ return undefined;
85
+ return res.status === 204 ? undefined : (await res.json());
86
+ }
87
+ }
88
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAGxD,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAyBnE,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,qBAAqB;IACf,OAAO,CAAS;IAChB,KAAK,CAAgB;IACrB,YAAY,CAAU;IACtB,SAAS,CAA0B;IACnC,WAAW,CAAsB;IAElD,YAAY,OAAqC;QAC/C,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,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QACvC,+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;IACO,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,IAAkB,EAAE,QAAQ,GAAG,KAAK;QACrE,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,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9D,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,IAAI,CAAC,KAAK,KAAK,IAAI;4BACnB,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE;4BACpC,CAAC,CAAC,EAAE,CAAC;oBACT,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;IAES,KAAK,CAAC,IAAI,CAAI,IAAY,EAAE,IAAkB,EAAE,QAAQ,GAAG,KAAK;QACxE,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;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@substrat-run/control-plane-api",
3
- "version": "0.133.0",
3
+ "version": "0.134.0",
4
4
  "description": "HTTP surface over HostAdmin — the audited control-plane transport (control-plane.md §4.5)",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -16,6 +16,10 @@
16
16
  ".": {
17
17
  "types": "./dist/index.d.ts",
18
18
  "default": "./dist/index.js"
19
+ },
20
+ "./browser": {
21
+ "types": "./dist/browser.d.ts",
22
+ "default": "./dist/browser.js"
19
23
  }
20
24
  },
21
25
  "files": [
@@ -23,9 +27,9 @@
23
27
  ],
24
28
  "dependencies": {
25
29
  "hono": "^4.13.9",
26
- "@substrat-run/kernel": "^0.133.0",
27
- "@substrat-run/psl": "^0.2.5",
28
- "@substrat-run/contracts": "^0.133.0"
30
+ "@substrat-run/contracts": "^0.134.0",
31
+ "@substrat-run/kernel": "^0.134.0",
32
+ "@substrat-run/psl": "^0.2.5"
29
33
  },
30
34
  "devDependencies": {
31
35
  "@hono/node-server": "^1.13.0",
@@ -36,8 +40,8 @@
36
40
  "tsx": "^4.19.0",
37
41
  "typescript": "^7.0.0",
38
42
  "vitest": "^3.2.7",
39
- "@substrat-run/adapter-sqlite": "^0.133.0",
40
- "@substrat-run/vertical-host": "^0.133.0"
43
+ "@substrat-run/adapter-sqlite": "^0.134.0",
44
+ "@substrat-run/vertical-host": "^0.134.0"
41
45
  },
42
46
  "publishConfig": {
43
47
  "access": "public"