@substrat-run/control-plane-api 0.141.0 → 0.143.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 (46) hide show
  1. package/dist/api.d.ts +8 -0
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +170 -82
  4. package/dist/api.js.map +1 -1
  5. package/dist/cf-observability.js +2 -2
  6. package/dist/index.d.ts +3 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +3 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/platform-drain.d.ts +17 -0
  11. package/dist/platform-drain.d.ts.map +1 -1
  12. package/dist/platform-drain.js +48 -27
  13. package/dist/platform-drain.js.map +1 -1
  14. package/dist/platform-entry.d.ts +7 -5
  15. package/dist/platform-entry.d.ts.map +1 -1
  16. package/dist/platform-entry.generated.d.ts +2 -2
  17. package/dist/platform-entry.generated.d.ts.map +1 -1
  18. package/dist/platform-entry.generated.js +5 -5
  19. package/dist/platform-entry.generated.js.map +1 -1
  20. package/dist/platform-entry.js +47 -13
  21. package/dist/platform-entry.js.map +1 -1
  22. package/dist/provider-error.d.ts +34 -5
  23. package/dist/provider-error.d.ts.map +1 -1
  24. package/dist/provider-error.js +60 -5
  25. package/dist/provider-error.js.map +1 -1
  26. package/dist/scope-copy-backfill.d.ts +106 -0
  27. package/dist/scope-copy-backfill.d.ts.map +1 -0
  28. package/dist/scope-copy-backfill.js +418 -0
  29. package/dist/scope-copy-backfill.js.map +1 -0
  30. package/dist/scope-copy-cleanup.d.ts +10 -0
  31. package/dist/scope-copy-cleanup.d.ts.map +1 -1
  32. package/dist/scope-copy-cleanup.js +12 -6
  33. package/dist/scope-copy-cleanup.js.map +1 -1
  34. package/dist/scope-deployment.d.ts +33 -0
  35. package/dist/scope-deployment.d.ts.map +1 -1
  36. package/dist/scope-deployment.js +38 -0
  37. package/dist/scope-deployment.js.map +1 -1
  38. package/dist/staff-client.d.ts +2 -1
  39. package/dist/staff-client.d.ts.map +1 -1
  40. package/dist/staff-client.js +3 -0
  41. package/dist/staff-client.js.map +1 -1
  42. package/dist/vertical-client.d.ts +16 -4
  43. package/dist/vertical-client.d.ts.map +1 -1
  44. package/dist/vertical-client.js +43 -6
  45. package/dist/vertical-client.js.map +1 -1
  46. package/package.json +7 -6
package/dist/api.d.ts CHANGED
@@ -339,6 +339,14 @@ type Vars = {
339
339
  host: ScopeHost;
340
340
  /** `host.admin` of the same request — the spelling every route writes through. */
341
341
  admin: HostAdmin;
342
+ /**
343
+ * The error this request answered with, when a route answered it itself instead of throwing
344
+ * (`failedWith`) or `onError` did. The 5xx backstop reads it to attribute the row, and to skip
345
+ * one a step already recorded.
346
+ */
347
+ failure?: unknown;
348
+ /** A step recorded this request's failure with no error object to mark (a literal 5xx). */
349
+ failureRecorded?: true;
342
350
  };
343
351
  /**
344
352
  * The K-35 denial-log filter (#867). Bounded by default like every other HTTP read
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/api.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { resolveObservabilityWindow } from './observability-window.js';
2
2
  import { Hono } from 'hono';
3
- import { parseHostname, withLabel, RESERVED_LABEL_SEPARATOR, adminAction, ownerTransferInput, memberInviteInput, memberRoleInput, ASSET_PART_PREFIX, assetHash, channelName, connectLinkId, connectionActivity, connectionActivitySource, connectionCredential, connectionCertificate, connectionFilter, connectionHealthState, connectionProvider, connectorDispatchKind, PROVISION_SIBLING_KIND, ARCHIVE_SCOPE_KIND, PROVISION_TENANT_KIND, SET_ENTITLEMENTS_KIND, MODEL_USAGE_KIND, SWEEP_RUNS_KIND, toConnectionHealthEntry, CONNECTION_EXPIRY_WARNING_DAYS, CONNECTION_STALE_DAYS, connectionProbe, createTenantInput, entitlementGrantInput, hostname as hostnameSchema, hostnameRegion, hostnameStatus, identityLink, errorCode, issueStatus, issueStatusInput, findingKind, findingRuleInput, findingStatus, findingStatusInput, listPageQuery, matchesOutboundHost, pageOf, platformRequestFilter, principalId as principalIdSchema, impersonationStamp, promotionAcknowledgement, bindAcknowledgement, provisionableJurisdiction, publishVersionInput, queryScopeInput, readScopeTableInput, entityHistoryInput, eventFacetInput, eventCauseInput, eventEffectsInput, invocationEventsInput, deadLettersInput, lifecycleFlowInput, operationSeriesCount, operationSeriesInput, delegatedReadParams, createOrgInput, roleKey as roleKeySchema, peerSwitch, verticalSlug, systemSwitch, DEFAULT_DENIAL_LIMIT, DENIAL_LIMIT_MAX, denialGroupBy, registerVerticalInput, platformScriptCollision, platformScriptRefusal, PLATFORM_SCRIPT_NAMES, serviceDimensions, sweepRunKind, sweepRunOutcome, scopeDump, dataSubjectId as dataSubjectIdSchema, scopeId as scopeIdSchema, scopeStatus, storageShape, surfaceName, tenantId as tenantIdSchema, tenantStatus, versionOrigin, z, PROBLEM_CONTENT_TYPE, toProblem, redrainEventsInput, importCursorMove, importCursorAcknowledgementMissing, REDRAIN_BATCH, migrationsOnTop, errorCodeOf, capabilityFilterQuery, PLATFORM_FEATURES_HEADER, PLATFORM_FEATURE_SCOPE_SWEEPER, } from '@substrat-run/contracts';
3
+ import { parseHostname, withLabel, RESERVED_LABEL_SEPARATOR, adminAction, ownerTransferInput, memberInviteInput, memberRoleInput, ASSET_PART_PREFIX, assetHash, channelName, connectLinkId, connectionActivity, connectionActivitySource, connectionCredential, connectionCertificate, connectionFilter, connectionHealthState, connectionProvider, connectorDispatchKind, PROVISION_SIBLING_KIND, ARCHIVE_SCOPE_KIND, PROVISION_TENANT_KIND, SET_ENTITLEMENTS_KIND, MODEL_USAGE_KIND, SWEEP_RUNS_KIND, toConnectionHealthEntry, CONNECTION_EXPIRY_WARNING_DAYS, CONNECTION_STALE_DAYS, connectionProbe, createTenantInput, entitlementGrantInput, hostname as hostnameSchema, hostnameRegion, hostnameStatus, identityLink, errorCode, issueStatus, issueStatusInput, findingKind, findingRuleInput, findingStatus, findingStatusInput, listPageQuery, matchesOutboundHost, pageOf, platformRequestFilter, principalId as principalIdSchema, impersonationStamp, promotionAcknowledgement, bindAcknowledgement, provisionableJurisdiction, publishVersionInput, queryScopeInput, readScopeTableInput, entityHistoryInput, eventFacetInput, eventCauseInput, eventEffectsInput, invocationEventsInput, deadLettersInput, lifecycleFlowInput, operationSeriesCount, operationSeriesInput, delegatedReadParams, createOrgInput, roleKey as roleKeySchema, peerSwitch, verticalSlug, systemSwitch, DEFAULT_DENIAL_LIMIT, DENIAL_LIMIT_MAX, denialGroupBy, registerVerticalInput, platformScriptCollision, platformScriptRefusal, PLATFORM_SCRIPT_NAMES, serviceDimensions, sweepRunKind, sweepRunOutcome, scopeDump, dataSubjectId as dataSubjectIdSchema, scopeId as scopeIdSchema, scopeStatus, storageShape, surfaceName, tenantId as tenantIdSchema, tenantStatus, versionOrigin, z, PROBLEM_CONTENT_TYPE, toProblem, redrainEventsInput, importCursorMove, importCursorAcknowledgementMissing, REDRAIN_BATCH, migrationsOnTop, errorCodeOf, capabilityFilterQuery, capabilityId as capabilityIdSchema, PLATFORM_FEATURES_HEADER, PLATFORM_FEATURE_SCOPE_SWEEPER, } from '@substrat-run/contracts';
4
4
  import { SCOPE_COPY_LEASE_MS, copyRestoreFence } from '@substrat-run/kernel';
5
5
  import { attributeFailure } from './failure-attribution.js';
6
6
  import { auditedChange, withAuditedOutcomes } from './audited-change.js';
@@ -9,8 +9,9 @@ import { TENANT_HEADER, confinedTenant } from './auth.js';
9
9
  import { mintTenantToken } from './tenant-token.js';
10
10
  import { connectionGrantsForScope } from './vertical-client.js';
11
11
  import { assertNoUnreachableScopeCopies, listAllScopeScriptCopies, reapScopeScriptCopies, recordStrandedStorage } from './scope-copy-cleanup.js';
12
+ import { BACKFILL_PAGE_MAX, backfillScopeScriptCopies } from './scope-copy-backfill.js';
12
13
  import { oidcCallbackUrl, retireClientsOfReapedScope, wirePreviewAuth } from './preview-auth.js';
13
- import { versionReachedAt } from './scope-deployment.js';
14
+ import { scopeDeployment, versionReachedAt } from './scope-deployment.js';
14
15
  import { reconcileConnectionGrants } from './connection-grants.js';
15
16
  import { reconcileThenReassert, reviewedEntityGrants, switchCarryFor } from './reconcile.js';
16
17
  import { ConnectionRelayError, relayConnectionUpsert } from './connection-relay.js';
@@ -772,6 +773,18 @@ const namedTenants = (c) => {
772
773
  ];
773
774
  return named.filter((v) => typeof v === 'string' && v.length > 0);
774
775
  };
776
+ function messageOfThrow(err) {
777
+ return err instanceof Error ? err.message : String(err);
778
+ }
779
+ /** What a 5xx answered with no error object said: the JSON body's `error` (and `detail`), else the status. */
780
+ async function answeredMessage(res) {
781
+ const body = (await res.clone().json().catch(() => null));
782
+ const error = typeof body?.['error'] === 'string' ? body['error'] : typeof body?.['title'] === 'string' ? body['title'] : null;
783
+ const detail = typeof body?.['detail'] === 'string' ? body['detail'] : null;
784
+ if (error && detail)
785
+ return `${error}: ${detail}`;
786
+ return error ?? detail ?? `HTTP ${res.status}`;
787
+ }
775
788
  /**
776
789
  * The audited HTTP surface over `HostAdmin` (control-plane.md §4.5).
777
790
  *
@@ -803,6 +816,48 @@ export function createControlPlaneApi(options) {
803
816
  const { host, authenticate, authenticateBuilder, authenticateTenantService } = options;
804
817
  const copyLeaseMs = options.copyLeaseMs ?? SCOPE_COPY_LEASE_MS;
805
818
  const app = new Hono();
819
+ // The 5xx backstop: every 5xx this API answers lands an ops-failure row, however it was
820
+ // answered. A 5xx is the PLATFORM failing (an unmapped throw, or a downstream vertical's own
821
+ // 5xx passing through, #559), so the console must be able to list it and a `reference = <id>`
822
+ // search must find it. It used to be recorded only in `onError`, which never sees the routes
823
+ // that catch a ControlPlaneError and answer it themselves: a preview re-push whose bind failed
824
+ // after a clean carry answered 502 with a reference and left no row behind.
825
+ //
826
+ // Registered first, so it wraps every other middleware and route. 501 stays out: an honest
827
+ // not-implemented is a capability statement, not a failure. 4xx stay out too: they are
828
+ // refusals the caller can already read. Actor is unset only when the request failed before
829
+ // authentication, where nothing worth recording refuses. A throw a step already recorded,
830
+ // with its stage and scope, is not recorded again.
831
+ app.use('*', async (c, next) => {
832
+ await next();
833
+ const status = c.res.status;
834
+ const actor = c.get('actor');
835
+ if (status >= 500 && status !== 501 && actor && !c.get('failureRecorded')) {
836
+ const cause = c.get('failure');
837
+ const marked = typeof cause === 'object' && cause !== null && recordedCauses.has(cause);
838
+ if (!marked) {
839
+ const { vertical, tenantId } = await failureSubject(c);
840
+ recordFailure({
841
+ actor,
842
+ operation: `${c.req.method} ${c.req.routePath}`,
843
+ vertical,
844
+ tenantId,
845
+ scopeId: c.req.param('scopeId') ?? null,
846
+ status,
847
+ message: cause !== undefined ? messageOfThrow(cause) : await answeredMessage(c.res),
848
+ }, cause === undefined ? NO_CAUSE : cause);
849
+ }
850
+ }
851
+ if (inflightRecords.size) {
852
+ // `executionCtx` throws outside a Worker (tests, node); the write then simply runs on.
853
+ try {
854
+ c.executionCtx.waitUntil(Promise.allSettled([...inflightRecords]));
855
+ }
856
+ catch {
857
+ // no execution context
858
+ }
859
+ }
860
+ });
806
861
  // The CLI version advisory (#971), stamped on EVERY response — including the 401 the
807
862
  // auth middleware below answers with, since a CLI too old to authenticate is exactly
808
863
  // the one that needs to hear it. Registered first so nothing can return around it.
@@ -1026,20 +1081,62 @@ export function createControlPlaneApi(options) {
1026
1081
  // `undefined` (`throw undefined`, a bare rejection), and that throw deserves an
1027
1082
  // `unknown` attribution, not a skipped one. Only true omission skips.
1028
1083
  const NO_CAUSE = Symbol('no-cause');
1084
+ // The throws a step already recorded, so the 5xx backstop below does not record them twice.
1085
+ const recordedCauses = new WeakSet();
1086
+ // The writes still in flight. A Worker may end once its response is sent, and a write nobody
1087
+ // awaits goes with it, so the backstop hands these to `waitUntil`.
1088
+ const inflightRecords = new Set();
1029
1089
  const recordFailure = (entry, cause = NO_CAUSE) => {
1090
+ if (typeof cause === 'object' && cause !== null)
1091
+ recordedCauses.add(cause);
1030
1092
  // The error SHAPE rides beside the prose (#1233): attributed here — one place,
1031
1093
  // from the throw itself — so a fingerprint groups on a column and never has to
1032
1094
  // regex a message. A caller with no throw in hand leaves the columns null,
1033
1095
  // which reads as "nobody classified this" rather than a guess nobody made.
1034
1096
  const attributed = cause === NO_CAUSE ? undefined : attributeFailure(cause);
1035
- void host.admin
1097
+ const write = host.admin
1036
1098
  .recordOpsFailure({
1037
1099
  ...entry,
1038
1100
  origin: entry.origin ?? attributed?.origin ?? null,
1039
1101
  code: entry.code ?? attributed?.code ?? null,
1040
1102
  reference: entry.reference ?? UPSTREAM_REFERENCE.exec(entry.message)?.[1] ?? null,
1041
1103
  })
1042
- .catch(() => undefined);
1104
+ .catch(() => undefined)
1105
+ .finally(() => inflightRecords.delete(write));
1106
+ inflightRecords.add(write);
1107
+ };
1108
+ /**
1109
+ * Whose failure the backstop is recording, so the dashboard's tenant-pinned failure view lists
1110
+ * it. The path's tenant when it names one; otherwise the credential's own (a CI push token, a
1111
+ * builder); otherwise, on a vertical route, the vertical's owner. The slug is resolved the way
1112
+ * the route resolves it, so a CI push's bare `todo` is recorded as the full slug the
1113
+ * dashboard filters on. Best effort: a lookup that fails still records the row, unattributed.
1114
+ */
1115
+ const failureSubject = async (c) => {
1116
+ // Already decoded by Hono, and handed on as-is the way the routes hand it to
1117
+ // `resolveVerticalId`. Decoding again would throw on a slug carrying `%25`, and a throw here
1118
+ // replaces the 5xx being recorded with onError's answer.
1119
+ const raw = c.req.param('slug') ?? null;
1120
+ const pathTenant = c.req.param('tenantId') ?? null;
1121
+ const principal = c.get('principal');
1122
+ const confined = principal ? confinedTenant(principal) : null;
1123
+ if (!raw)
1124
+ return { vertical: null, tenantId: pathTenant ?? confined };
1125
+ try {
1126
+ const vertical = await resolveVerticalId(c, raw);
1127
+ const owner = (pathTenant ?? confined)
1128
+ ? null
1129
+ : ((await verticalOf(c.get('actor'), vertical))?.ownerTenant ?? null);
1130
+ return { vertical, tenantId: pathTenant ?? confined ?? owner };
1131
+ }
1132
+ catch {
1133
+ return { vertical: raw, tenantId: pathTenant ?? confined };
1134
+ }
1135
+ };
1136
+ /** The status of a ControlPlaneError a route answers itself, with the error marked for the backstop. */
1137
+ const failedWith = (c, e) => {
1138
+ c.set('failure', e);
1139
+ return e.status;
1043
1140
  };
1044
1141
  // Rides out a transient downstream window: the install path's binding-attach →
1045
1142
  // script-settings propagation race (#424 case 2), and a one-shot DO storage blip
@@ -1083,25 +1180,8 @@ export function createControlPlaneApi(options) {
1083
1180
  return c.json({ error: err.message, carrySourceChanged: { scopeId: err.scopeId, from: err.from, to: err.to, attempts: err.attempts } }, 409);
1084
1181
  }
1085
1182
  const { status, body } = mapError(err);
1086
- // A 5xx is the PLATFORM failing — an unmapped throw, or a downstream vertical's
1087
- // own 5xx passing through (a DO storage fault during a preview restore is the
1088
- // founding case, #559) — so it lands a durable ops-failure row the console can
1089
- // list and a `reference = <id>` search can find. 501 stays out: an honest
1090
- // not-implemented is a capability statement, not a failure. 4xx stay out too:
1091
- // they are refusals the caller can already read. Actor is unset only when the
1092
- // throw happened before authentication — nothing worth recording refuses there.
1093
- const actor = c.get('actor');
1094
- if (status >= 500 && status !== 501 && actor) {
1095
- recordFailure({
1096
- actor,
1097
- operation: `${c.req.method} ${c.req.routePath}`,
1098
- vertical: c.req.param('slug') ? decodeURIComponent(c.req.param('slug')) : null,
1099
- tenantId: c.req.param('tenantId') ?? null,
1100
- scopeId: c.req.param('scopeId') ?? null,
1101
- status,
1102
- message: err instanceof Error ? err.message : String(err),
1103
- }, err);
1104
- }
1183
+ // The 5xx backstop (registered first, above) records it, with this throw as the cause.
1184
+ c.set('failure', err);
1105
1185
  // A 500 is, by definition, a throw whose message `mapError` did not recognise — so the
1106
1186
  // client gets a GENERIC body that discloses nothing, and until now nothing recorded WHAT
1107
1187
  // threw either. That left every unmapped failure (e.g. a raw SQLite constraint from a
@@ -1212,7 +1292,7 @@ export function createControlPlaneApi(options) {
1212
1292
  }
1213
1293
  catch (e) {
1214
1294
  if (e instanceof ControlPlaneError) {
1215
- return c.json({ error: e.message }, e.status);
1295
+ return c.json({ error: e.message }, failedWith(c, e));
1216
1296
  }
1217
1297
  throw e;
1218
1298
  }
@@ -1884,7 +1964,7 @@ export function createControlPlaneApi(options) {
1884
1964
  }
1885
1965
  catch (e) {
1886
1966
  if (e instanceof ControlPlaneError)
1887
- return c.json({ error: e.message }, e.status);
1967
+ return c.json({ error: e.message }, failedWith(c, e));
1888
1968
  throw e;
1889
1969
  }
1890
1970
  });
@@ -1940,42 +2020,14 @@ export function createControlPlaneApi(options) {
1940
2020
  * served version's hook ran when the bound one's did. `versionReachedAt(via, …)` turns
1941
2021
  * this answer into the version, so the choice and the receipt have one source.
1942
2022
  */
1943
- const deploymentForScope = async (c, scope) => {
1944
- if (!scope.vertical)
1945
- return undefined;
1946
- const actor = c.get('actor');
1947
- // A scope on the stable serving script (#286) is reached THERE — that script holds
1948
- // its DOs regardless of what the bound version or the prod channel say.
1949
- if (scope.servingRef && options.resolveVerticalRef) {
1950
- const serving = await options.resolveVerticalRef(scope.servingRef);
1951
- if (serving)
1952
- return { client: serving, via: 'serving-script' };
1953
- }
1954
- const bySlug = async (slug) => {
1955
- if (scope.verticalVersionId && options.resolveVerticalVersion) {
1956
- const bound = await options.resolveVerticalVersion(slug, scope.verticalVersionId, actor);
1957
- if (bound)
1958
- return { client: bound, via: 'bound-version' };
1959
- }
1960
- const bySlugClient = options.verticals?.[slug] ?? (await options.resolveVertical?.(slug, actor));
1961
- return bySlugClient ? { client: bySlugClient, via: 'slug' } : undefined;
1962
- };
1963
- const direct = await bySlug(scope.vertical);
1964
- if (direct)
1965
- return direct;
1966
- // Miss path only (#417) — a resolved scope never pays for these registry reads: a
1967
- // scope bound to a BARE slug that is not registered, while the owning tenant's
1968
- // prefixed registration of the same name exists, is addressing the prefixed lineage
1969
- // under its bare spelling. Retry once under the registry id; anything else still
1970
- // misses and surfaces through `diagnoseUnboundScope` as before.
1971
- if (scope.tenantId && !scope.vertical.includes('/') && (await ownerOf(actor, scope.vertical)) === undefined) {
1972
- const tenant = await c.var.admin.getTenant(actor, scope.tenantId).catch(() => null);
1973
- const prefixed = tenant ? `${tenant.slug}/${scope.vertical}` : null;
1974
- if (prefixed && (await ownerOf(actor, prefixed)) !== undefined)
1975
- return bySlug(prefixed);
1976
- }
1977
- return undefined;
1978
- };
2023
+ const deploymentForScope = (c, scope) => scopeDeployment({
2024
+ verticals: options.verticals,
2025
+ resolveVertical: options.resolveVertical,
2026
+ resolveVerticalVersion: options.resolveVerticalVersion,
2027
+ resolveVerticalRef: options.resolveVerticalRef,
2028
+ ownerOf,
2029
+ tenantSlugOf: async (actor, tenantId) => (await c.var.admin.getTenant(actor, tenantId).catch(() => null))?.slug ?? null,
2030
+ }, c.get('actor'), scope);
1979
2031
  /**
1980
2032
  * Why a scope's vertical did not resolve to a running deployment — an ACTIONABLE 501
1981
2033
  * body, computed only on the miss path, in place of the bare "no deployment is bound"
@@ -3142,7 +3194,7 @@ export function createControlPlaneApi(options) {
3142
3194
  // unbound store is exactly what makes a vertical refuse its own reconcile), so
3143
3195
  // it must not be the half that gets dropped.
3144
3196
  const detail = storeErrors.length ? `${e.message} (stores: ${storeErrors.join('; ')})` : e.message;
3145
- return c.json({ error: detail }, e.status);
3197
+ return c.json({ error: detail }, failedWith(c, e));
3146
3198
  }
3147
3199
  throw e;
3148
3200
  }
@@ -3439,6 +3491,23 @@ export function createControlPlaneApi(options) {
3439
3491
  colocated: () => c.var.admin.listCapabilities(c.get('actor'), tenantId, scopeId, filter),
3440
3492
  }, (r) => r.entries.length));
3441
3493
  });
3494
+ // The operator's revoke of one capability (#1686): the lever for a leaked link, beside the
3495
+ // read above and gated the same way, in the handler as well as by the builder default-deny.
3496
+ // `HostAdmin.revokeCapability` reaches the deployment serving a hosted scope (its
3497
+ // `capabilityDelegation`) and audits it on this side, intent then outcome; the record names the
3498
+ // actor as its revoker. Idempotent: revoking a revoked capability answers 204 again. A capability
3499
+ // the scope does not hold is 404; a deployment built before the far end answers 501 "redeploy".
3500
+ app.post('/tenants/:tenantId/scopes/:scopeId/capabilities/:capabilityId/revoke', async (c) => {
3501
+ if (confinedTenant(c.get('principal')) !== null) {
3502
+ return c.json({ error: 'forbidden: revoking a capability is staff-only' }, 403);
3503
+ }
3504
+ const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
3505
+ const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
3506
+ const capability = capabilityIdSchema.parse(c.req.param('capabilityId'));
3507
+ // An unknown scope, another tenant's included, is the verb's own `not_found`: a 404.
3508
+ await c.var.admin.revokeCapability(c.get('actor'), tenantId, scopeId, capability);
3509
+ return c.body(null, 204);
3510
+ });
3442
3511
  // The owner seat (#925). Both reads go to the VERTICAL — the seat lives in its identity
3443
3512
  // directory, in its deployment — after the same K-3 cross-check every scope-addressed read
3444
3513
  // here makes. A co-located scope (contract-test host, self-host running no vertical code)
@@ -3464,7 +3533,7 @@ export function createControlPlaneApi(options) {
3464
3533
  }
3465
3534
  catch (e) {
3466
3535
  if (e instanceof ControlPlaneError)
3467
- return c.json({ error: e.message }, e.status);
3536
+ return c.json({ error: e.message }, failedWith(c, e));
3468
3537
  throw e;
3469
3538
  }
3470
3539
  });
@@ -3504,7 +3573,7 @@ export function createControlPlaneApi(options) {
3504
3573
  }
3505
3574
  catch (e) {
3506
3575
  if (e instanceof ControlPlaneError)
3507
- return c.json({ error: e.message }, e.status);
3576
+ return c.json({ error: e.message }, failedWith(c, e));
3508
3577
  throw e;
3509
3578
  }
3510
3579
  });
@@ -3550,7 +3619,7 @@ export function createControlPlaneApi(options) {
3550
3619
  const { operationId } = done;
3551
3620
  if ('error' in done) {
3552
3621
  if (done.error instanceof ControlPlaneError) {
3553
- return c.json({ error: done.error.message, operationId }, done.error.status);
3622
+ return c.json({ error: done.error.message, operationId }, failedWith(c, done.error));
3554
3623
  }
3555
3624
  throw done.error;
3556
3625
  }
@@ -3589,7 +3658,7 @@ export function createControlPlaneApi(options) {
3589
3658
  }
3590
3659
  catch (e) {
3591
3660
  if (e instanceof ControlPlaneError)
3592
- return c.json({ error: e.message }, e.status);
3661
+ return c.json({ error: e.message }, failedWith(c, e));
3593
3662
  throw e;
3594
3663
  }
3595
3664
  });
@@ -3698,7 +3767,7 @@ export function createControlPlaneApi(options) {
3698
3767
  }
3699
3768
  catch (e) {
3700
3769
  if (e instanceof ControlPlaneError) {
3701
- return c.json({ error: e.message }, e.status);
3770
+ return c.json({ error: e.message }, failedWith(c, e));
3702
3771
  }
3703
3772
  throw e;
3704
3773
  }
@@ -3754,6 +3823,7 @@ export function createControlPlaneApi(options) {
3754
3823
  status: 503,
3755
3824
  message: error,
3756
3825
  }, failure.cause);
3826
+ c.set('failureRecorded', true);
3757
3827
  return c.json({ error }, 503);
3758
3828
  }
3759
3829
  }
@@ -3883,7 +3953,7 @@ export function createControlPlaneApi(options) {
3883
3953
  }
3884
3954
  catch (e) {
3885
3955
  if (e instanceof ControlPlaneError) {
3886
- return c.json({ error: e.message }, e.status);
3956
+ return c.json({ error: e.message }, failedWith(c, e));
3887
3957
  }
3888
3958
  throw e;
3889
3959
  }
@@ -3968,7 +4038,7 @@ export function createControlPlaneApi(options) {
3968
4038
  }
3969
4039
  catch (e) {
3970
4040
  if (e instanceof ControlPlaneError) {
3971
- return c.json({ error: e.message }, e.status);
4041
+ return c.json({ error: e.message }, failedWith(c, e));
3972
4042
  }
3973
4043
  throw e;
3974
4044
  }
@@ -4059,7 +4129,7 @@ export function createControlPlaneApi(options) {
4059
4129
  }
4060
4130
  catch (e) {
4061
4131
  if (e instanceof ControlPlaneError) {
4062
- return c.json({ error: e.message }, e.status);
4132
+ return c.json({ error: e.message }, failedWith(c, e));
4063
4133
  }
4064
4134
  throw e;
4065
4135
  }
@@ -4560,7 +4630,7 @@ export function createControlPlaneApi(options) {
4560
4630
  }
4561
4631
  catch (e) {
4562
4632
  if (e instanceof ControlPlaneError) {
4563
- return c.json({ error: e.message }, e.status);
4633
+ return c.json({ error: e.message }, failedWith(c, e));
4564
4634
  }
4565
4635
  return c.json({
4566
4636
  error: 'backup failed — the scope was NOT reaped and its data is intact; ' +
@@ -4581,7 +4651,7 @@ export function createControlPlaneApi(options) {
4581
4651
  }
4582
4652
  catch (e) {
4583
4653
  if (e instanceof ControlPlaneError) {
4584
- return c.json({ error: e.message }, e.status);
4654
+ return c.json({ error: e.message }, failedWith(c, e));
4585
4655
  }
4586
4656
  throw e;
4587
4657
  }
@@ -4628,7 +4698,7 @@ export function createControlPlaneApi(options) {
4628
4698
  }
4629
4699
  catch (e) {
4630
4700
  if (e instanceof ControlPlaneError) {
4631
- return c.json({ error: e.message }, e.status);
4701
+ return c.json({ error: e.message }, failedWith(c, e));
4632
4702
  }
4633
4703
  throw e;
4634
4704
  }
@@ -4743,7 +4813,7 @@ export function createControlPlaneApi(options) {
4743
4813
  }
4744
4814
  catch (e) {
4745
4815
  if (e instanceof ControlPlaneError) {
4746
- return c.json({ error: e.message }, e.status);
4816
+ return c.json({ error: e.message }, failedWith(c, e));
4747
4817
  }
4748
4818
  throw e;
4749
4819
  }
@@ -4809,7 +4879,7 @@ export function createControlPlaneApi(options) {
4809
4879
  }
4810
4880
  catch (e) {
4811
4881
  if (e instanceof ControlPlaneError) {
4812
- return c.json({ error: e.message }, e.status);
4882
+ return c.json({ error: e.message }, failedWith(c, e));
4813
4883
  }
4814
4884
  // A restore throw is driven by the caller-supplied dump (a shape the target cannot
4815
4885
  // load, a DDL the engine rejects), so DISCLOSE it as an actionable 422 rather than
@@ -4889,7 +4959,7 @@ export function createControlPlaneApi(options) {
4889
4959
  }
4890
4960
  catch (e) {
4891
4961
  if (e instanceof ControlPlaneError) {
4892
- return c.json({ error: e.message }, e.status);
4962
+ return c.json({ error: e.message }, failedWith(c, e));
4893
4963
  }
4894
4964
  throw e;
4895
4965
  }
@@ -4909,7 +4979,7 @@ export function createControlPlaneApi(options) {
4909
4979
  }
4910
4980
  catch (e) {
4911
4981
  if (e instanceof ControlPlaneError) {
4912
- return c.json(exportBreakBody(e), e.status);
4982
+ return c.json(exportBreakBody(e), failedWith(c, e));
4913
4983
  }
4914
4984
  throw e;
4915
4985
  }
@@ -4947,7 +5017,7 @@ export function createControlPlaneApi(options) {
4947
5017
  }
4948
5018
  catch (e) {
4949
5019
  if (e instanceof ControlPlaneError) {
4950
- return c.json({ ...exportBreakBody(e), adopted, alreadyAdopted }, e.status);
5020
+ return c.json({ ...exportBreakBody(e), adopted, alreadyAdopted }, failedWith(c, e));
4951
5021
  }
4952
5022
  throw e;
4953
5023
  }
@@ -4979,7 +5049,7 @@ export function createControlPlaneApi(options) {
4979
5049
  }
4980
5050
  catch (e) {
4981
5051
  if (e instanceof ControlPlaneError) {
4982
- return c.json(exportBreakBody(e), e.status);
5052
+ return c.json(exportBreakBody(e), failedWith(c, e));
4983
5053
  }
4984
5054
  throw e;
4985
5055
  }
@@ -5311,6 +5381,23 @@ export function createControlPlaneApi(options) {
5311
5381
  await bind({ acknowledge });
5312
5382
  return c.json(await c.var.admin.getScopeRecord(actor, tenantId, scopeId));
5313
5383
  });
5384
+ // -- copy-ledger backfill (#1722) ------------------------------------------------------------
5385
+ // Records the scripts a scope was copied into before the ledger existed, read from the admin
5386
+ // log, as `retained`: reached by reap and erasure from then on, never wiped from here
5387
+ // (`scope-copy-backfill.ts`). A dry run unless the body says otherwise, one page per call,
5388
+ // resumed with the `nextCursor` it answers. Staff only, by default-deny: on neither
5389
+ // BUILDER_ROUTES nor TENANT_ROUTES.
5390
+ const copyBackfillBody = z.object({
5391
+ dryRun: z.boolean().default(true),
5392
+ cursor: z.string().min(1).optional(),
5393
+ limit: z.number().int().min(1).max(BACKFILL_PAGE_MAX).optional(),
5394
+ }).strict();
5395
+ app.post('/scope-copies/backfill', async (c) => {
5396
+ const body = await readJsonBody(c, copyBackfillBody);
5397
+ if (!options.resolveVerticalRef)
5398
+ throw new ControlPlaneError(501, 'the copy backfill needs dispatch resolution by script');
5399
+ return c.json(await backfillScopeScriptCopies({ admin: c.var.admin, actor: c.get('actor'), resolveRef: options.resolveVerticalRef }, body));
5400
+ });
5314
5401
  // -- instances (K-31) -------------------------------------------------------
5315
5402
  // The one place this surface calls OUT rather than sitting over `HostAdmin`, and
5316
5403
  // it is unavoidable: only the vertical can create a usable scope DO, because the
@@ -5447,7 +5534,7 @@ export function createControlPlaneApi(options) {
5447
5534
  message: e.message,
5448
5535
  }, e);
5449
5536
  }
5450
- return c.json({ error: e.message }, e.status);
5537
+ return c.json({ error: e.message }, failedWith(c, e));
5451
5538
  }
5452
5539
  throw e;
5453
5540
  }
@@ -6598,6 +6685,7 @@ export function createControlPlaneApi(options) {
6598
6685
  status: rejected ? 422 : 502,
6599
6686
  message: detail,
6600
6687
  }, e);
6688
+ c.set('failureRecorded', true);
6601
6689
  return rejected
6602
6690
  ? c.json({ error: 'deploy rejected', detail }, 422)
6603
6691
  : c.json({ error: 'deploy upload failed', detail }, 502);
@@ -7908,7 +7996,7 @@ export function createControlPlaneApi(options) {
7908
7996
  }
7909
7997
  catch (e) {
7910
7998
  if (e instanceof ControlPlaneError)
7911
- return c.json({ error: e.message }, e.status);
7999
+ return c.json({ error: e.message }, failedWith(c, e));
7912
8000
  throw e;
7913
8001
  }
7914
8002
  });
@@ -7959,7 +8047,7 @@ export function createControlPlaneApi(options) {
7959
8047
  }
7960
8048
  catch (e) {
7961
8049
  if (e instanceof ControlPlaneError)
7962
- return c.json({ error: e.message }, e.status);
8050
+ return c.json({ error: e.message }, failedWith(c, e));
7963
8051
  throw e;
7964
8052
  }
7965
8053
  });