@substrat-run/control-plane-api 0.130.1 → 0.131.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.
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, OWNER_TRANSFER_AUDIT_ERROR_MAX, ASSET_PART_PREFIX, assetHash, channelName, connectionActivity, connectionActivitySource, connectionCredential, 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, listPageQuery, matchesOutboundHost, pageOf, platformRequestFilter, principalId as principalIdSchema, promotionAcknowledgement, bindAcknowledgement, provisionableJurisdiction, publishVersionInput, queryScopeInput, readScopeTableInput, entityHistoryInput, eventFacetInput, eventCauseInput, eventEffectsInput, invocationEventsInput, deadLettersInput, lifecycleFlowInput, delegatedReadParams, createOrgInput, roleKey as roleKeySchema, peerSwitch, systemSwitch, DEFAULT_DENIAL_LIMIT, DENIAL_LIMIT_MAX, denialGroupBy, registerVerticalInput, 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, } from '@substrat-run/contracts';
3
+ import { parseHostname, withLabel, RESERVED_LABEL_SEPARATOR, adminAction, ownerTransferInput, OWNER_TRANSFER_AUDIT_ERROR_MAX, ASSET_PART_PREFIX, assetHash, channelName, connectionActivity, connectionActivitySource, connectionCredential, 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, listPageQuery, matchesOutboundHost, pageOf, platformRequestFilter, principalId as principalIdSchema, impersonationStamp, promotionAcknowledgement, bindAcknowledgement, provisionableJurisdiction, publishVersionInput, queryScopeInput, readScopeTableInput, entityHistoryInput, eventFacetInput, eventCauseInput, eventEffectsInput, invocationEventsInput, deadLettersInput, lifecycleFlowInput, delegatedReadParams, createOrgInput, roleKey as roleKeySchema, peerSwitch, systemSwitch, DEFAULT_DENIAL_LIMIT, DENIAL_LIMIT_MAX, denialGroupBy, registerVerticalInput, 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, } from '@substrat-run/contracts';
4
4
  import { attributeFailure } from './failure-attribution.js';
5
5
  import { BIND_EXPORT_BREAK_REFUSAL, bindExportBreakRefusal, crossVerticalHealth, exportBreakRefusal, isBindExportBreakRefusal, isExportBreakRefusal, migrationProgress, ulid, } from '@substrat-run/kernel';
6
6
  import { TENANT_HEADER, confinedTenant } from './auth.js';
@@ -492,15 +492,78 @@ export const DEFAULT_MODEL_MARGIN_PERCENT = 20;
492
492
  * Cloudflare's `internal error; reference = <id>` shape (#559). */
493
493
  const UPSTREAM_REFERENCE = /\breference\s*=\s*([a-z0-9]+)/i;
494
494
  const TENANT_ROUTES = [
495
- // Everything under one tenant's own path. One entry rather than sixty, because
496
- // the confinement is carried by the URL shape and not by a list anybody has to
497
- // keep current: `/tenants/<pin>/…` cannot address another tenant's row whatever
498
- // the sub-route does. `/tenants` itself (the fleet list) does not match.
499
- { method: 'GET', re: /\/tenants\/[^/]+(\/.*)?$/, pin: 'path' },
500
- { method: 'POST', re: /\/tenants\/[^/]+(\/.*)?$/, pin: 'path' },
501
- { method: 'PUT', re: /\/tenants\/[^/]+(\/.*)?$/, pin: 'path' },
502
- { method: 'PATCH', re: /\/tenants\/[^/]+(\/.*)?$/, pin: 'path' },
503
- { method: 'DELETE', re: /\/tenants\/[^/]+(\/.*)?$/, pin: 'path' },
495
+ // One tenant's own path, route by route (#977). It used to be ONE catch-all entry per
496
+ // method — `/tenants/<pin>/…` cannot address another tenant's row, so it looked safe —
497
+ // but a path that is confined is not a route that is harmless: it reached entitlement
498
+ // self-grant, the tenant's own status and admission, and the staff-only redrain doors,
499
+ // none of which the dashboard calls. The pin says WHOSE rows; this list says WHICH
500
+ // acts, and a route the dashboard does not issue is reach nobody asked for. The
501
+ // dashboard's `credential-reach` test holds the other direction — every route its
502
+ // authority seam issues is on this list — so a new call site fails in its own PR.
503
+ //
504
+ // The tenant row: read it, rename it. Not `/status` — suspending or admitting a
505
+ // tenant is the platform's act, not the tenant's.
506
+ { method: 'GET', re: /\/tenants\/[^/]+$/, pin: 'path' },
507
+ { method: 'PATCH', re: /\/tenants\/[^/]+$/, pin: 'path' },
508
+ // Integrations (#605/#726): connections, their grants, the consent round's verify.
509
+ { method: 'GET', re: /\/tenants\/[^/]+\/connection-grants$/, pin: 'path' },
510
+ { method: 'GET', re: /\/tenants\/[^/]+\/connections$/, pin: 'path' },
511
+ { method: 'POST', re: /\/tenants\/[^/]+\/connections$/, pin: 'path' },
512
+ { method: 'DELETE', re: /\/tenants\/[^/]+\/connections\/[^/]+$/, pin: 'path' },
513
+ { method: 'GET', re: /\/tenants\/[^/]+\/connections\/[^/]+\/activity$/, pin: 'path' },
514
+ { method: 'GET', re: /\/tenants\/[^/]+\/connections\/[^/]+\/credential$/, pin: 'path' },
515
+ { method: 'POST', re: /\/tenants\/[^/]+\/connections\/[^/]+\/verify$/, pin: 'path' },
516
+ // Members: identity links mirrored from the dashboard, orgs, role assignments.
517
+ { method: 'GET', re: /\/tenants\/[^/]+\/identities$/, pin: 'path' },
518
+ { method: 'PUT', re: /\/tenants\/[^/]+\/identities$/, pin: 'path' },
519
+ { method: 'DELETE', re: /\/tenants\/[^/]+\/identities\/[^/]+$/, pin: 'path' },
520
+ { method: 'GET', re: /\/tenants\/[^/]+\/orgs$/, pin: 'path' },
521
+ { method: 'POST', re: /\/tenants\/[^/]+\/orgs$/, pin: 'path' },
522
+ { method: 'POST', re: /\/tenants\/[^/]+\/role-assignments$/, pin: 'path' },
523
+ { method: 'DELETE', re: /\/tenants\/[^/]+\/role-assignments$/, pin: 'path' },
524
+ // An install's entitlement. The handler narrows WHICH key a tenant credential may
525
+ // grant (`tenantGrantableEntitlement`): the pin alone would let it grant itself any.
526
+ { method: 'PUT', re: /\/tenants\/[^/]+\/entitlements\/[^/]+$/, pin: 'path' },
527
+ { method: 'GET', re: /\/tenants\/[^/]+\/cross-vertical\/edges$/, pin: 'path' },
528
+ // Scopes: provision, read, and the lifecycle acts the Data and Deployments tabs offer.
529
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes$/, pin: 'path' },
530
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+$/, pin: 'path' },
531
+ { method: 'DELETE', re: /\/tenants\/[^/]+\/scopes\/[^/]+$/, pin: 'path' },
532
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/binding-impact$/, pin: 'path' },
533
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/bookmarks$/, pin: 'path' },
534
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/cause$/, pin: 'path' },
535
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/dead-letters$/, pin: 'path' },
536
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/denials$/, pin: 'path' },
537
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/denials\/summary$/, pin: 'path' },
538
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/effects$/, pin: 'path' },
539
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/export$/, pin: 'path' },
540
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/facets$/, pin: 'path' },
541
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/history$/, pin: 'path' },
542
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/intents$/, pin: 'path' },
543
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/invocation$/, pin: 'path' },
544
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/migrations$/, pin: 'path' },
545
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/owner-seat$/, pin: 'path' },
546
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/peer-grants$/, pin: 'path' },
547
+ // The peer switch (#1706): on is POST, off is DELETE, on the tenant's own scope.
548
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/peer-grants$/, pin: 'path' },
549
+ { method: 'DELETE', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/peer-grants$/, pin: 'path' },
550
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/snapshots$/, pin: 'path' },
551
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/tables$/, pin: 'path' },
552
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/tables\/[^/]+$/, pin: 'path' },
553
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/activate$/, pin: 'path' },
554
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/archive$/, pin: 'path' },
555
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/configure$/, pin: 'path' },
556
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/import-cursor$/, pin: 'path' },
557
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/lifecycle-flow$/, pin: 'path' },
558
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/owner-claim$/, pin: 'path' },
559
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/query$/, pin: 'path' },
560
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/reap$/, pin: 'path' },
561
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/rebind-vertical$/, pin: 'path' },
562
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/restore$/, pin: 'path' },
563
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/rewind$/, pin: 'path' },
564
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/snapshots$/, pin: 'path' },
565
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/suspend$/, pin: 'path' },
566
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/version$/, pin: 'path' },
504
567
  // Create the tenant's own directory row (idempotent) — `id` is the pin.
505
568
  { method: 'POST', re: /\/tenants$/, pin: 'body' },
506
569
  // Provision an app + materialize its instance.
@@ -627,7 +690,6 @@ const namedTenants = (c) => {
627
690
  */
628
691
  export function createControlPlaneApi(options) {
629
692
  const { host, authenticate, authenticateBuilder, authenticateTenantService } = options;
630
- const admin = host.admin;
631
693
  const app = new Hono();
632
694
  // The CLI version advisory (#971), stamped on EVERY response — including the 401 the
633
695
  // auth middleware below answers with, since a CLI too old to authenticate is exactly
@@ -659,7 +721,12 @@ export function createControlPlaneApi(options) {
659
721
  if (authenticateTenantService) {
660
722
  const tenant = await authenticateTenantService(c.req.raw);
661
723
  if (tenant) {
662
- principal = { kind: 'tenant', actor: tenant.actor, tenantId: tenant.tenantId };
724
+ principal = {
725
+ kind: 'tenant',
726
+ actor: tenant.actor,
727
+ tenantId: tenant.tenantId,
728
+ ...(tenant.onBehalfOf ? { onBehalfOf: tenant.onBehalfOf } : {}),
729
+ };
663
730
  }
664
731
  }
665
732
  const staff = principal ? null : await authenticate(c.req.raw);
@@ -680,6 +747,14 @@ export function createControlPlaneApi(options) {
680
747
  return c.json({ error: 'unauthenticated' }, 401);
681
748
  c.set('principal', principal);
682
749
  c.set('actor', principal.actor);
750
+ // #977: a tenant credential minted for a person writes through a view that names
751
+ // them on every admin row. Only the credential's own claim can supply the person —
752
+ // never a header or a body — and only the staff mint writes that claim.
753
+ const view = principal.kind === 'tenant' && principal.onBehalfOf && host.attributed
754
+ ? host.attributed(principal.onBehalfOf)
755
+ : host;
756
+ c.set('host', view);
757
+ c.set('admin', view.admin);
683
758
  await next();
684
759
  });
685
760
  // Confine a BUILDER to the vertical-management surface, fail-CLOSED (builder-plane.md
@@ -839,7 +914,7 @@ export function createControlPlaneApi(options) {
839
914
  // regex a message. A caller with no throw in hand leaves the columns null,
840
915
  // which reads as "nobody classified this" rather than a guess nobody made.
841
916
  const attributed = cause === NO_CAUSE ? undefined : attributeFailure(cause);
842
- void admin
917
+ void host.admin
843
918
  .recordOpsFailure({
844
919
  ...entry,
845
920
  origin: entry.origin ?? attributed?.origin ?? null,
@@ -927,7 +1002,7 @@ export function createControlPlaneApi(options) {
927
1002
  // -- tenant registry (§4.1) ------------------------------------------------
928
1003
  app.get('/tenants', async (c) => {
929
1004
  const page = pageParams(c);
930
- const entries = await admin.listTenants(c.get('actor'), page);
1005
+ const entries = await c.var.admin.listTenants(c.get('actor'), page);
931
1006
  return c.json(pageOf(entries, page.limit, (t) => t.id));
932
1007
  });
933
1008
  app.post('/tenants', async (c) => {
@@ -936,14 +1011,14 @@ export function createControlPlaneApi(options) {
936
1011
  // (from the manager scope's directory row) — never caller-supplied. A direct staff
937
1012
  // create is first-class by definition, so force it null even if a body carries it,
938
1013
  // so the field can't be forged into a false ownership relationship.
939
- await admin.createTenant(c.get('actor'), { ...input, provisionedByTenant: null });
1014
+ await c.var.admin.createTenant(c.get('actor'), { ...input, provisionedByTenant: null });
940
1015
  // Idempotent (§4.1): re-creating an existing tenant is a no-op, not an error,
941
1016
  // so this reads back rather than reporting a create that may not have happened.
942
- return c.json(await admin.getTenant(c.get('actor'), input.id), 201);
1017
+ return c.json(await c.var.admin.getTenant(c.get('actor'), input.id), 201);
943
1018
  });
944
1019
  app.get('/tenants/:tenantId', async (c) => {
945
1020
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
946
- const tenant = await admin.getTenant(c.get('actor'), tenantId);
1021
+ const tenant = await c.var.admin.getTenant(c.get('actor'), tenantId);
947
1022
  if (!tenant)
948
1023
  return c.json({ error: `unknown tenant: ${tenantId}` }, 404);
949
1024
  return c.json(tenant);
@@ -955,8 +1030,8 @@ export function createControlPlaneApi(options) {
955
1030
  app.patch('/tenants/:tenantId', async (c) => {
956
1031
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
957
1032
  const { name } = z.object({ name: z.string().trim().min(1).max(100) }).parse(await c.req.json());
958
- await admin.setTenantName(c.get('actor'), tenantId, name);
959
- return c.json(await admin.getTenant(c.get('actor'), tenantId));
1033
+ await c.var.admin.setTenantName(c.get('actor'), tenantId, name);
1034
+ return c.json(await c.var.admin.getTenant(c.get('actor'), tenantId));
960
1035
  });
961
1036
  app.patch('/tenants/:tenantId/status', async (c) => {
962
1037
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
@@ -964,8 +1039,8 @@ export function createControlPlaneApi(options) {
964
1039
  // The live weapon (§7): `suspended` fails getScope closed for EVERY scope
965
1040
  // under the tenant. The blast radius is the console's to show; the audit row
966
1041
  // is this layer's to guarantee.
967
- await admin.setTenantStatus(c.get('actor'), tenantId, status);
968
- return c.json(await admin.getTenant(c.get('actor'), tenantId));
1042
+ await c.var.admin.setTenantStatus(c.get('actor'), tenantId, status);
1043
+ return c.json(await c.var.admin.getTenant(c.get('actor'), tenantId));
969
1044
  });
970
1045
  // Reap a DELETING tenant now (control-plane.md §4.8) — the staff "reap now" that skips
971
1046
  // the grace window, the tenant analogue of the scope reap route below. Refuses a tenant
@@ -980,7 +1055,7 @@ export function createControlPlaneApi(options) {
980
1055
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
981
1056
  const actor = c.get('actor');
982
1057
  const { backup: wantsBackup } = reapScopeBody.parse(await c.req.json().catch(() => ({})));
983
- const tenant = await admin.getTenant(actor, tenantId);
1058
+ const tenant = await c.var.admin.getTenant(actor, tenantId);
984
1059
  if (!tenant)
985
1060
  return c.json({ error: `unknown tenant: ${tenantId}` }, 404);
986
1061
  if (tenant.status !== 'deleting') {
@@ -989,11 +1064,11 @@ export function createControlPlaneApi(options) {
989
1064
  }, 409);
990
1065
  }
991
1066
  try {
992
- for (const scope of await admin.listScopes(actor, { tenantId })) {
1067
+ for (const scope of await c.var.admin.listScopes(actor, { tenantId })) {
993
1068
  if (scope.status === 'reaped')
994
1069
  continue;
995
1070
  if (scope.status !== 'archived')
996
- await admin.archiveScope(actor, tenantId, scope.id);
1071
+ await c.var.admin.archiveScope(actor, tenantId, scope.id);
997
1072
  // A backup here is OPT-IN, the inverse of the per-scope reap's default (#493).
998
1073
  // A scope reap is operational cleanup, so leaving a copy is the safe default; a
999
1074
  // TENANT reap is the deletion of a customer, and §4.8 exists partly to serve an
@@ -1006,13 +1081,13 @@ export function createControlPlaneApi(options) {
1006
1081
  await vertical.deleteScope({ scopeId: scope.id });
1007
1082
  // Tenant teardown reaps every scope and releases every name by design — force past
1008
1083
  // the bound-hostname guard (which fences the interactive per-scope reap route below).
1009
- await admin.reapScope(actor, tenantId, scope.id, {
1084
+ await c.var.admin.reapScope(actor, tenantId, scope.id, {
1010
1085
  force: true,
1011
1086
  ...(backup ? { backupRef: backupRefOf(backup) } : {}),
1012
1087
  });
1013
1088
  }
1014
- await admin.reapTenant(actor, tenantId);
1015
- return c.json(await admin.getTenant(actor, tenantId));
1089
+ await c.var.admin.reapTenant(actor, tenantId);
1090
+ return c.json(await c.var.admin.getTenant(actor, tenantId));
1016
1091
  }
1017
1092
  catch (e) {
1018
1093
  if (e instanceof ControlPlaneError) {
@@ -1022,20 +1097,35 @@ export function createControlPlaneApi(options) {
1022
1097
  }
1023
1098
  });
1024
1099
  // -- entitlements (§4.3) ---------------------------------------------------
1025
- app.get('/tenants/:tenantId/entitlements', async (c) => c.json(await admin.listEntitlements(c.get('actor'), tenantIdSchema.parse(c.req.param('tenantId')))));
1100
+ app.get('/tenants/:tenantId/entitlements', async (c) => c.json(await c.var.admin.listEntitlements(c.get('actor'), tenantIdSchema.parse(c.req.param('tenantId')))));
1026
1101
  app.put('/tenants/:tenantId/entitlements/:key', async (c) => {
1027
1102
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
1028
1103
  // The body is the plan half (#33) and optional — a bodyless PUT is the
1029
1104
  // pre-widening bare flag grant, and PATCH semantics in the store mean it
1030
1105
  // preserves whatever plan fields the grant already carries.
1031
1106
  const plan = entitlementGrantInput.parse(await c.req.json().catch(() => ({})));
1032
- await admin.grantEntitlement(c.get('actor'), tenantId, c.req.param('key'), plan);
1033
- return c.json(await admin.listEntitlements(c.get('actor'), tenantId));
1107
+ const key = c.req.param('key');
1108
+ const p = c.get('principal');
1109
+ if (p.kind === 'tenant') {
1110
+ // #977: a tenant credential may grant its own tenant only what an INSTALL grants —
1111
+ // a key some vertical it can see declares (or that vertical's bare slug, the key a
1112
+ // vertical declaring none gates on). Anything else is the platform's to grant:
1113
+ // `builder`, a plan-gated feature, a key no vertical asked for. And never the plan
1114
+ // half: an expiry, a quota or a plan name is pricing, which is not the tenant's.
1115
+ if (plan.expiresAt != null || plan.quota != null || plan.plan != null) {
1116
+ return c.json({ error: 'a tenant credential grants entitlements without plan fields' }, 403);
1117
+ }
1118
+ if (!(await tenantGrantable(p, key))) {
1119
+ return c.json({ error: `entitlement '${key}' is not declared by a vertical this tenant can install` }, 403);
1120
+ }
1121
+ }
1122
+ await c.var.admin.grantEntitlement(c.get('actor'), tenantId, key, plan);
1123
+ return c.json(await c.var.admin.listEntitlements(c.get('actor'), tenantId));
1034
1124
  });
1035
1125
  app.delete('/tenants/:tenantId/entitlements/:key', async (c) => {
1036
1126
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
1037
- await admin.revokeEntitlement(c.get('actor'), tenantId, c.req.param('key'));
1038
- return c.json(await admin.listEntitlements(c.get('actor'), tenantId));
1127
+ await c.var.admin.revokeEntitlement(c.get('actor'), tenantId, c.req.param('key'));
1128
+ return c.json(await c.var.admin.listEntitlements(c.get('actor'), tenantId));
1039
1129
  });
1040
1130
  // -- the meters (§5, #38) --------------------------------------------------
1041
1131
  // Meters 1 and 2, read fleet-wide or narrowed with `?tenantId=`. Meters 3 and 4 have
@@ -1049,7 +1139,7 @@ export function createControlPlaneApi(options) {
1049
1139
  app.get('/meters', async (c) => {
1050
1140
  const raw = c.req.query('tenantId');
1051
1141
  const tenantId = raw ? tenantIdSchema.parse(raw) : undefined;
1052
- return c.json(await admin.readMeters(c.get('actor'), tenantId ? { tenantId } : undefined));
1142
+ return c.json(await c.var.admin.readMeters(c.get('actor'), tenantId ? { tenantId } : undefined));
1053
1143
  });
1054
1144
  // Storage, read on demand (#1524): one tenant's scope-database sizes and their sum.
1055
1145
  // Staff-only like `/meters` (on neither allowlist, so a builder or a tenant token is
@@ -1076,7 +1166,7 @@ export function createControlPlaneApi(options) {
1076
1166
  limit: c.req.query('limit') || undefined,
1077
1167
  });
1078
1168
  const actor = c.get('actor');
1079
- const scopes = await admin.listScopes(actor, { tenantId: q.tenantId });
1169
+ const scopes = await c.var.admin.listScopes(actor, { tenantId: q.tenantId });
1080
1170
  return c.json(await readStoragePage({
1081
1171
  tenantId: q.tenantId,
1082
1172
  readAt: new Date().toISOString(),
@@ -1085,13 +1175,13 @@ export function createControlPlaneApi(options) {
1085
1175
  limit: q.limit,
1086
1176
  read: async (scope) => {
1087
1177
  if (!scope.vertical)
1088
- return admin.scopeDatabaseSize(actor, q.tenantId, scope.id);
1178
+ return c.var.admin.scopeDatabaseSize(actor, q.tenantId, scope.id);
1089
1179
  const vertical = await verticalForScope(c, scope);
1090
1180
  if (vertical)
1091
1181
  return vertical.databaseSize(scope.id);
1092
1182
  if (delegatesToVerticals)
1093
1183
  throw new Error(`no deployment resolves for vertical ${scope.vertical}`);
1094
- return admin.scopeDatabaseSize(actor, q.tenantId, scope.id);
1184
+ return c.var.admin.scopeDatabaseSize(actor, q.tenantId, scope.id);
1095
1185
  },
1096
1186
  }));
1097
1187
  });
@@ -1106,8 +1196,8 @@ export function createControlPlaneApi(options) {
1106
1196
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
1107
1197
  const actor = c.get('actor');
1108
1198
  const [tenantStores, blobStores] = await Promise.all([
1109
- admin.listTenantStores(actor, { tenantId }),
1110
- admin.listBlobStores(actor, { tenantId }),
1199
+ c.var.admin.listTenantStores(actor, { tenantId }),
1200
+ c.var.admin.listBlobStores(actor, { tenantId }),
1111
1201
  ]);
1112
1202
  return c.json({ tenantStores, blobStores });
1113
1203
  });
@@ -1124,26 +1214,26 @@ export function createControlPlaneApi(options) {
1124
1214
  const link = mirrorIdentityBody.parse(await c.req.json());
1125
1215
  // The pool must exist before a link can land in it (central topology, K-23);
1126
1216
  // registering an existing pool is a no-op.
1127
- await admin.registerIdentityPool(c.get('actor'), { provider: link.provider, topology: 'central', tenantId: null });
1128
- await admin.linkIdentity(c.get('actor'), { ...link, tenantId });
1217
+ await c.var.admin.registerIdentityPool(c.get('actor'), { provider: link.provider, topology: 'central', tenantId: null });
1218
+ await c.var.admin.linkIdentity(c.get('actor'), { ...link, tenantId });
1129
1219
  return c.body(null, 204);
1130
1220
  });
1131
1221
  app.delete('/tenants/:tenantId/identities/:principal', async (c) => {
1132
1222
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
1133
- await admin.unlinkIdentity(c.get('actor'), tenantId, principalIdSchema.parse(c.req.param('principal')));
1223
+ await c.var.admin.unlinkIdentity(c.get('actor'), tenantId, principalIdSchema.parse(c.req.param('principal')));
1134
1224
  return c.body(null, 204);
1135
1225
  });
1136
1226
  // The read half of the mirror seam (#406): what the directory currently links for a
1137
1227
  // tenant — the console's offboarding view, and the way to verify an unlink landed.
1138
1228
  app.get('/tenants/:tenantId/identities', async (c) => {
1139
1229
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
1140
- return c.json(await admin.listIdentityLinks(c.get('actor'), tenantId));
1230
+ return c.json(await c.var.admin.listIdentityLinks(c.get('actor'), tenantId));
1141
1231
  });
1142
1232
  // The tenant's live connection grants (#592) — the readable "what may this connection
1143
1233
  // invoke" (connections.md §6.2.4 Q2), and the rows provision/reconcile deliver from.
1144
1234
  app.get('/tenants/:tenantId/connection-grants', async (c) => {
1145
1235
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
1146
- return c.json(await admin.listConnectionGrants(c.get('actor'), tenantId));
1236
+ return c.json(await c.var.admin.listConnectionGrants(c.get('actor'), tenantId));
1147
1237
  });
1148
1238
  // -- the connection store, tenant-scoped (connections.md §3.5) -------------
1149
1239
  //
@@ -1162,7 +1252,7 @@ export function createControlPlaneApi(options) {
1162
1252
  provider: c.req.query('provider'),
1163
1253
  includeRevoked: c.req.query('includeRevoked') === '1' ? true : undefined,
1164
1254
  });
1165
- return c.json(await admin.listConnections(c.get('actor'), filter));
1255
+ return c.json(await c.var.admin.listConnections(c.get('actor'), filter));
1166
1256
  });
1167
1257
  // -- connection health, fleet-wide (#1690) -----------------------------------
1168
1258
  //
@@ -1198,7 +1288,7 @@ export function createControlPlaneApi(options) {
1198
1288
  order: c.req.query('order'),
1199
1289
  });
1200
1290
  const now = new Date();
1201
- const rows = await admin.listConnections(c.get('actor'), {
1291
+ const rows = await c.var.admin.listConnections(c.get('actor'), {
1202
1292
  ...(q.tenantId ? { tenantId: q.tenantId } : {}),
1203
1293
  ...(q.provider ? { provider: q.provider } : {}),
1204
1294
  });
@@ -1243,7 +1333,7 @@ export function createControlPlaneApi(options) {
1243
1333
  const since = new Date(now.getTime() - CONNECTOR_DEAD_LETTER_WINDOW_DAYS * 86_400_000).toISOString();
1244
1334
  const providers = q.provider ? [q.provider] : [...new Set(rows.map((r) => r.provider))].sort();
1245
1335
  const deadLetters = await Promise.all(providers.map(async (provider) => {
1246
- const found = await admin.listOpsFailures(c.get('actor'), {
1336
+ const found = await c.var.admin.listOpsFailures(c.get('actor'), {
1247
1337
  operation: `intent.${connectorDispatchKind(provider)}`,
1248
1338
  ...(q.tenantId ? { tenantId: q.tenantId } : {}),
1249
1339
  since,
@@ -1312,7 +1402,7 @@ export function createControlPlaneApi(options) {
1312
1402
  // per provider that has (or had) a live connection anywhere in the fleet — the same
1313
1403
  // derivation the dead-letter count above uses, since that kind only exists for a
1314
1404
  // provider some connection actually names.
1315
- const providers = [...new Set((await admin.listConnections(c.get('actor'), { includeRevoked: true })).map((r) => r.provider))];
1405
+ const providers = [...new Set((await c.var.admin.listConnections(c.get('actor'), { includeRevoked: true })).map((r) => r.provider))];
1316
1406
  const kinds = [
1317
1407
  PROVISION_SIBLING_KIND,
1318
1408
  ARCHIVE_SCOPE_KIND,
@@ -1325,7 +1415,7 @@ export function createControlPlaneApi(options) {
1325
1415
  // The drain's newest pass is independent of the failure counts, so it rides alongside them.
1326
1416
  const [counts, [lastPass]] = await Promise.all([
1327
1417
  Promise.all(kinds.map(async (kind) => {
1328
- const found = await admin.listOpsFailures(c.get('actor'), {
1418
+ const found = await c.var.admin.listOpsFailures(c.get('actor'), {
1329
1419
  operation: `intent.${kind}`,
1330
1420
  since,
1331
1421
  limit: PLATFORM_REQUEST_BACKLOG_CAP + 1,
@@ -1335,7 +1425,7 @@ export function createControlPlaneApi(options) {
1335
1425
  capped: found.length > PLATFORM_REQUEST_BACKLOG_CAP,
1336
1426
  };
1337
1427
  })),
1338
- admin.listSweepRuns(c.get('actor'), { kind: 'platform-request', unit: 'fleet', limit: 1 }),
1428
+ c.var.admin.listSweepRuns(c.get('actor'), { kind: 'platform-request', unit: 'fleet', limit: 1 }),
1339
1429
  ]);
1340
1430
  const body = {
1341
1431
  total: counts.reduce((sum, k) => sum + k.count, 0),
@@ -1363,7 +1453,7 @@ export function createControlPlaneApi(options) {
1363
1453
  return c.json({ error: 'body tenantId disagrees with the route tenant' }, 400);
1364
1454
  }
1365
1455
  try {
1366
- return c.json(await relayConnectionUpsert(host, c.get('actor'), { ...body, tenantId }, { probeCandidate }));
1456
+ return c.json(await relayConnectionUpsert(c.var.host, c.get('actor'), { ...body, tenantId }, { probeCandidate }));
1367
1457
  }
1368
1458
  catch (err) {
1369
1459
  if (err instanceof ConnectionRelayError && err.status < 500) {
@@ -1385,12 +1475,12 @@ export function createControlPlaneApi(options) {
1385
1475
  app.delete('/tenants/:tenantId/connections/:id', async (c) => {
1386
1476
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
1387
1477
  const id = c.req.param('id');
1388
- const rows = await admin.listConnections(c.get('actor'), { tenantId, includeRevoked: true });
1478
+ const rows = await c.var.admin.listConnections(c.get('actor'), { tenantId, includeRevoked: true });
1389
1479
  const row = rows.find((r) => r.id === id);
1390
1480
  if (!row)
1391
1481
  return c.json({ error: 'unknown connection' }, 404);
1392
1482
  if (row.status !== 'revoked')
1393
- await admin.revokeConnection(c.get('actor'), row.id);
1483
+ await c.var.admin.revokeConnection(c.get('actor'), row.id);
1394
1484
  return c.body(null, 204);
1395
1485
  });
1396
1486
  /**
@@ -1408,7 +1498,7 @@ export function createControlPlaneApi(options) {
1408
1498
  const inspectableConnection = async (c) => {
1409
1499
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
1410
1500
  const id = c.req.param('id');
1411
- const rows = await admin.listConnections(c.get('actor'), { tenantId });
1501
+ const rows = await c.var.admin.listConnections(c.get('actor'), { tenantId });
1412
1502
  return rows.find((r) => r.id === id);
1413
1503
  };
1414
1504
  /**
@@ -1433,7 +1523,7 @@ export function createControlPlaneApi(options) {
1433
1523
  if (!probe) {
1434
1524
  return c.json({ error: `no probe registered for provider '${row.provider}'` }, 501);
1435
1525
  }
1436
- return c.json(connectionProbe.parse(await probe(host, row)));
1526
+ return c.json(connectionProbe.parse(await probe(c.var.host, row)));
1437
1527
  });
1438
1528
  /**
1439
1529
  * **What the connection has done** (#605) — the connector's dispatch ledger, projected.
@@ -1457,7 +1547,7 @@ export function createControlPlaneApi(options) {
1457
1547
  }
1458
1548
  const source = connectionActivitySource.catch('ledger').parse(c.req.query('source'));
1459
1549
  const live = c.req.query('live') === '1';
1460
- return c.json(connectionActivity.parse(await activity(host, row, { live, source })));
1550
+ return c.json(connectionActivity.parse(await activity(c.var.host, row, { live, source })));
1461
1551
  });
1462
1552
  /**
1463
1553
  * **The stored credential, reduced** (#605) — identifiers whole, secrets masked.
@@ -1476,7 +1566,7 @@ export function createControlPlaneApi(options) {
1476
1566
  if (!credential) {
1477
1567
  return c.json({ error: `no credential view registered for provider '${row.provider}'` }, 501);
1478
1568
  }
1479
- return c.json(connectionCredential.parse(await credential(host, row)));
1569
+ return c.json(connectionCredential.parse(await credential(c.var.host, row)));
1480
1570
  });
1481
1571
  // -- the scope directory (§3.2/§4.2) ---------------------------------------
1482
1572
  app.get('/scopes', async (c) => {
@@ -1489,7 +1579,7 @@ export function createControlPlaneApi(options) {
1489
1579
  vertical: c.req.query('vertical'),
1490
1580
  });
1491
1581
  const page = pageParams(c);
1492
- const entries = await admin.listScopes(c.get('actor'), { ...filter, ...page });
1582
+ const entries = await c.var.admin.listScopes(c.get('actor'), { ...filter, ...page });
1493
1583
  return c.json(pageOf(entries, page.limit, (s) => s.id));
1494
1584
  });
1495
1585
  // -- fleet migration progress (kernel-design §5.3, #49) ---------------------
@@ -1501,7 +1591,7 @@ export function createControlPlaneApi(options) {
1501
1591
  // an unfiltered read is meaningful only where one deployment runs everything.
1502
1592
  app.get('/fleet/migrations', async (c) => {
1503
1593
  const filter = fleetMigrationsQuery.parse({ vertical: c.req.query('vertical') });
1504
- const scopes = await admin.listScopes(c.get('actor'), {
1594
+ const scopes = await c.var.admin.listScopes(c.get('actor'), {
1505
1595
  status: ['active', 'provisioning'],
1506
1596
  ...(filter.vertical ? { vertical: filter.vertical } : {}),
1507
1597
  });
@@ -1509,8 +1599,8 @@ export function createControlPlaneApi(options) {
1509
1599
  });
1510
1600
  app.post('/scopes', async (c) => {
1511
1601
  const input = provisionScopeBody.parse(await c.req.json());
1512
- await host.provisionScope(c.get('actor'), input);
1513
- const record = await admin.getScopeRecord(c.get('actor'), input.tenantId, input.scopeId);
1602
+ await c.var.host.provisionScope(c.get('actor'), input);
1603
+ const record = await c.var.admin.getScopeRecord(c.get('actor'), input.tenantId, input.scopeId);
1514
1604
  return c.json(record, 201);
1515
1605
  });
1516
1606
  // Add a SIBLING scope — a new "site" — to an app the tenant already runs (multi-scope
@@ -1538,7 +1628,7 @@ export function createControlPlaneApi(options) {
1538
1628
  // The provisioning sequence lives in one place — `provisionSiblingScope` — shared with the
1539
1629
  // platform-intent drain's `provision-sibling` handler (platform-intents.md B2).
1540
1630
  try {
1541
- const result = await provisionSiblingScope({ host, actor, resolveVerticalForScope: (scope) => verticalForScope({ get: () => actor }, scope) }, {
1631
+ const result = await provisionSiblingScope({ host: c.var.host, actor, resolveVerticalForScope: (scope) => verticalForScope(c, scope) }, {
1542
1632
  tenantId,
1543
1633
  parentScopeId: input.parentScopeId,
1544
1634
  scopeId: input.scopeId,
@@ -1548,7 +1638,7 @@ export function createControlPlaneApi(options) {
1548
1638
  });
1549
1639
  if (!result.ok)
1550
1640
  return c.json({ error: result.error }, result.status);
1551
- return c.json(await admin.getScopeRecord(actor, tenantId, input.scopeId), 201);
1641
+ return c.json(await c.var.admin.getScopeRecord(actor, tenantId, input.scopeId), 201);
1552
1642
  }
1553
1643
  catch (e) {
1554
1644
  if (e instanceof ControlPlaneError)
@@ -1564,7 +1654,7 @@ export function createControlPlaneApi(options) {
1564
1654
  if (outsideTenant(p, tenantId)) {
1565
1655
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
1566
1656
  }
1567
- const record = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
1657
+ const record = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
1568
1658
  // Absent, or present under another tenant — indistinguishable on purpose (K-3).
1569
1659
  if (!record)
1570
1660
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
@@ -1594,8 +1684,8 @@ export function createControlPlaneApi(options) {
1594
1684
  * protect a subject's data is itself a thing an incident asks about.
1595
1685
  */
1596
1686
  const sealerFor = (c, tenantId, scopeId) => ({
1597
- seal: (items) => admin.sealSubjectPayloads(c.get('actor'), tenantId, scopeId, items),
1598
- open: (items) => admin.openSubjectPayloads(c.get('actor'), tenantId, scopeId, items),
1687
+ seal: (items) => c.var.admin.sealSubjectPayloads(c.get('actor'), tenantId, scopeId, items),
1688
+ open: (items) => c.var.admin.openSubjectPayloads(c.get('actor'), tenantId, scopeId, items),
1599
1689
  });
1600
1690
  const verticalForScope = async (c, scope) => (await deploymentForScope(c, scope))?.client;
1601
1691
  /**
@@ -1637,7 +1727,7 @@ export function createControlPlaneApi(options) {
1637
1727
  // under its bare spelling. Retry once under the registry id; anything else still
1638
1728
  // misses and surfaces through `diagnoseUnboundScope` as before.
1639
1729
  if (scope.tenantId && !scope.vertical.includes('/') && (await ownerOf(actor, scope.vertical)) === undefined) {
1640
- const tenant = await admin.getTenant(actor, scope.tenantId).catch(() => null);
1730
+ const tenant = await c.var.admin.getTenant(actor, scope.tenantId).catch(() => null);
1641
1731
  const prefixed = tenant ? `${tenant.slug}/${scope.vertical}` : null;
1642
1732
  if (prefixed && (await ownerOf(actor, prefixed)) !== undefined)
1643
1733
  return bySlug(prefixed);
@@ -1660,7 +1750,7 @@ export function createControlPlaneApi(options) {
1660
1750
  const slug = scope.vertical;
1661
1751
  if (!slug)
1662
1752
  return 'scope has no vertical bound — nothing to deliver to';
1663
- const versions = await admin.listVersions(actor, slug).catch(() => []);
1753
+ const versions = await host.admin.listVersions(actor, slug).catch(() => []);
1664
1754
  const boundId = scope.verticalVersionId;
1665
1755
  if (boundId && !versions.some((v) => v.id === boundId)) {
1666
1756
  return (`scope is bound to version '${boundId}', which is not among vertical '${slug}'’s ${versions.length} ` +
@@ -1673,7 +1763,7 @@ export function createControlPlaneApi(options) {
1673
1763
  `pushes are on different slugs (a lineage fork). Push under '${slug}' by setting \`substrat.slug\`, ` +
1674
1764
  `or rebind the install to the slug the versions live under.`);
1675
1765
  }
1676
- const serving = await admin.verticalServing(actor, slug).catch(() => null);
1766
+ const serving = await host.admin.verticalServing(actor, slug).catch(() => null);
1677
1767
  if (!serving) {
1678
1768
  return `vertical '${slug}' has ${versions.length} version(s) but none is promoted to a serving channel — promote one to prod.`;
1679
1769
  }
@@ -1689,7 +1779,7 @@ export function createControlPlaneApi(options) {
1689
1779
  const refuseOnBreaks = async (actor, tenantId, scopeId, versionId, opts) => {
1690
1780
  if (opts.acknowledge?.exportBreak)
1691
1781
  return;
1692
- const breaks = await admin.bindingImpact(actor, tenantId, scopeId, versionId, opts.servingRef !== undefined ? { servingRef: opts.servingRef } : undefined);
1782
+ const breaks = await host.admin.bindingImpact(actor, tenantId, scopeId, versionId, opts.servingRef !== undefined ? { servingRef: opts.servingRef } : undefined);
1693
1783
  if (breaks.length > 0)
1694
1784
  throw new ExportBreakRefused(breaks);
1695
1785
  };
@@ -1724,14 +1814,14 @@ export function createControlPlaneApi(options) {
1724
1814
  * and `exact` when the platform exported them itself rather than a caller supplying them. */
1725
1815
  source) => {
1726
1816
  return retryTransient(async () => dest.restoreScope(tenantId, scopeId, tables, {
1727
- ...(await switchCarryFor(admin, actor, { tenantId, scopeId })),
1817
+ ...(await switchCarryFor(host.admin, actor, { tenantId, scopeId })),
1728
1818
  sourceScopeId: source.scopeId,
1729
1819
  exact: source.exact,
1730
1820
  }));
1731
1821
  };
1732
1822
  const adoptScopeOntoServing = async (c, tenantId, scopeId, opts = {}) => {
1733
1823
  const actor = c.get('actor');
1734
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
1824
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
1735
1825
  if (!scope) {
1736
1826
  throw new ControlPlaneError(404, `unknown scope for tenant: (${tenantId}, ${scopeId})`);
1737
1827
  }
@@ -1742,13 +1832,13 @@ export function createControlPlaneApi(options) {
1742
1832
  // #1674 (Copilot review): an adopt that flipped routing and then failed to re-assert is
1743
1833
  // retried HERE, so the retry re-asserts too — else a store that lost its OFF marker in
1744
1834
  // the copy would stay on for good. Cheap and idempotent when nothing is owed.
1745
- await admin.reassertSystemSwitches(actor, { tenantId, scopeId });
1835
+ await c.var.admin.reassertSystemSwitches(actor, { tenantId, scopeId });
1746
1836
  return { servingRef: scope.servingRef, alreadyAdopted: true };
1747
1837
  }
1748
1838
  if (!scope.vertical) {
1749
1839
  throw new ControlPlaneError(409, 'scope has no vertical — nothing to adopt onto');
1750
1840
  }
1751
- const serving = await admin.verticalServing(actor, scope.vertical);
1841
+ const serving = await c.var.admin.verticalServing(actor, scope.vertical);
1752
1842
  if (!serving) {
1753
1843
  throw new ControlPlaneError(409, `vertical '${scope.vertical}' has no serving script yet — promote a version to prod first`);
1754
1844
  }
@@ -1767,15 +1857,15 @@ export function createControlPlaneApi(options) {
1767
1857
  // #1742: the recorded OFF positions ride the restore, applied in the replay's own event.
1768
1858
  const restored = await restoreCarryingSwitches(actor, dest, tenantId, scopeId, dump, { scopeId, exact: true });
1769
1859
  // Data landed — only now flip routing and move the version pointer.
1770
- await admin
1860
+ await c.var.admin
1771
1861
  .setScopeServingRef(actor, tenantId, scopeId, serving.ref, { acknowledge: opts.acknowledge })
1772
1862
  .catch(relayHostRefusal);
1773
- await admin
1863
+ await c.var.admin
1774
1864
  .bindScopeVersion(actor, tenantId, scopeId, serving.versionId, { acknowledge: opts.acknowledge })
1775
1865
  .catch(relayHostRefusal);
1776
1866
  // #1674: the scope now routes to a different store, so put the directory's recorded
1777
1867
  // OFF positions back there — cheap and idempotent when the dump or the restore did.
1778
- await admin.reassertSystemSwitches(actor, { tenantId, scopeId }, { appliedInUnit: restored.switchedOff });
1868
+ await c.var.admin.reassertSystemSwitches(actor, { tenantId, scopeId }, { appliedInUnit: restored.switchedOff });
1779
1869
  return { servingRef: serving.ref, tables: restored.tables };
1780
1870
  };
1781
1871
  /**
@@ -1798,13 +1888,13 @@ export function createControlPlaneApi(options) {
1798
1888
  acknowledge) => {
1799
1889
  const actor = c.get('actor');
1800
1890
  const none = { refused: [] };
1801
- const serving = await admin.verticalServing(actor, slug);
1891
+ const serving = await c.var.admin.verticalServing(actor, slug);
1802
1892
  if (!serving)
1803
1893
  return none; // embedded / not dispatch-backed — the host cascade handled rebinds
1804
1894
  const v = await verticalOf(actor, slug);
1805
1895
  if (!v || v.ownerTenant === null || v.listed)
1806
1896
  return none; // private only, like the host cascade
1807
- const owned = (await admin.listScopes(actor, { tenantId: v.ownerTenant, vertical: slug, status: ['active'] })).filter((s) => !s.forkedFrom && s.kind !== 'preview');
1897
+ const owned = (await c.var.admin.listScopes(actor, { tenantId: v.ownerTenant, vertical: slug, status: ['active'] })).filter((s) => !s.forkedFrom && s.kind !== 'preview');
1808
1898
  // An install refused for what it would break is left where it is, and the rest still move:
1809
1899
  // one lagging app must not keep every other install's pointer, or the store backfill after
1810
1900
  // this, from happening. The caller answers with what was refused.
@@ -1822,7 +1912,7 @@ export function createControlPlaneApi(options) {
1822
1912
  // Already on the serving script (born there, or adopted earlier): routing is
1823
1913
  // pinned to servingRef, so advancing the version pointer only affects Update
1824
1914
  // offers. Snapshot on a migration-digest crossing (fork-before-promote, §4).
1825
- await admin.bindScopeVersion(actor, s.tenantId, s.id, versionId, { snapshot: true, acknowledge });
1915
+ await c.var.admin.bindScopeVersion(actor, s.tenantId, s.id, versionId, { snapshot: true, acknowledge });
1826
1916
  }
1827
1917
  }
1828
1918
  catch (e) {
@@ -1854,7 +1944,7 @@ export function createControlPlaneApi(options) {
1854
1944
  */
1855
1945
  const rebindScopeOntoVertical = async (c, tenantId, scopeId, target, opts) => {
1856
1946
  const actor = c.get('actor');
1857
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
1947
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
1858
1948
  if (!scope) {
1859
1949
  throw new ControlPlaneError(404, `unknown scope for tenant: (${tenantId}, ${scopeId})`);
1860
1950
  }
@@ -1864,13 +1954,13 @@ export function createControlPlaneApi(options) {
1864
1954
  if (!scope.vertical) {
1865
1955
  throw new ControlPlaneError(409, 'scope has no vertical — nothing to rebind');
1866
1956
  }
1867
- const serving = await admin.verticalServing(actor, target);
1957
+ const serving = await c.var.admin.verticalServing(actor, target);
1868
1958
  if (!serving) {
1869
1959
  throw new ControlPlaneError(409, `vertical '${target}' has no serving script yet — promote a version to prod first`);
1870
1960
  }
1871
1961
  if (scope.vertical === target && scope.servingRef === serving.ref) {
1872
1962
  // #1674: a rebind retried after its re-assert failed lands here; re-assert, as adopt does.
1873
- await admin.reassertSystemSwitches(actor, { tenantId, scopeId });
1963
+ await c.var.admin.reassertSystemSwitches(actor, { tenantId, scopeId });
1874
1964
  return { servingRef: serving.ref, versionId: serving.versionId, alreadyBound: true };
1875
1965
  }
1876
1966
  // #1756: within one lineage this is an adopt, and judged as one before anything moves. A
@@ -1885,8 +1975,8 @@ export function createControlPlaneApi(options) {
1885
1975
  // protect — the target provisions its own schema from scratch. The source
1886
1976
  // script's copy is untouched and remains the backout, same as a carried rebind.
1887
1977
  // The scope serves nothing until `/verticals/:slug/instances` re-provisions it.
1888
- await admin.setScopeServingRef(actor, tenantId, scopeId, serving.ref, move).catch(relayHostRefusal);
1889
- await admin.bindScopeVersion(actor, tenantId, scopeId, serving.versionId, move).catch(relayHostRefusal);
1978
+ await c.var.admin.setScopeServingRef(actor, tenantId, scopeId, serving.ref, move).catch(relayHostRefusal);
1979
+ await c.var.admin.bindScopeVersion(actor, tenantId, scopeId, serving.versionId, move).catch(relayHostRefusal);
1890
1980
  return { servingRef: serving.ref, versionId: serving.versionId, dataAbandoned: true };
1891
1981
  }
1892
1982
  // The frontier gate. Digest equality proves the target's migration set is exactly
@@ -1894,9 +1984,9 @@ export function createControlPlaneApi(options) {
1894
1984
  // brought — the crossing is then no riskier than an ordinary version bind. Anything
1895
1985
  // else (differing digests, or a scope with no bound version to compare) needs the
1896
1986
  // operator's explicit acknowledgement.
1897
- const targetV = await admin.getVersion(actor, serving.versionId, target);
1987
+ const targetV = await c.var.admin.getVersion(actor, serving.versionId, target);
1898
1988
  const currentV = scope.verticalVersionId
1899
- ? await admin.getVersion(actor, scope.verticalVersionId, scope.vertical)
1989
+ ? await c.var.admin.getVersion(actor, scope.verticalVersionId, scope.vertical)
1900
1990
  : undefined;
1901
1991
  const digestsMatch = Boolean(currentV && targetV && currentV.migrationDigest === targetV.migrationDigest);
1902
1992
  if (!digestsMatch && !opts.ackMigrations) {
@@ -1914,10 +2004,10 @@ export function createControlPlaneApi(options) {
1914
2004
  // `bindScopeVersion` rewrites `scopes.vertical` from the version row, audited. No
1915
2005
  // extra snapshot here (adopt-serving's precedent): the source script's copy is the
1916
2006
  // pre-migration state, and it is never deleted — that copy is the backout.
1917
- await admin.setScopeServingRef(actor, tenantId, scopeId, serving.ref, move).catch(relayHostRefusal);
1918
- await admin.bindScopeVersion(actor, tenantId, scopeId, serving.versionId, move).catch(relayHostRefusal);
2007
+ await c.var.admin.setScopeServingRef(actor, tenantId, scopeId, serving.ref, move).catch(relayHostRefusal);
2008
+ await c.var.admin.bindScopeVersion(actor, tenantId, scopeId, serving.versionId, move).catch(relayHostRefusal);
1919
2009
  // #1674: re-assert the recorded OFF positions in the store the scope now routes to.
1920
- await admin.reassertSystemSwitches(actor, { tenantId, scopeId }, { appliedInUnit: restored.switchedOff });
2010
+ await c.var.admin.reassertSystemSwitches(actor, { tenantId, scopeId }, { appliedInUnit: restored.switchedOff });
1921
2011
  return { servingRef: serving.ref, versionId: serving.versionId, tables: restored.tables };
1922
2012
  };
1923
2013
  /**
@@ -1960,7 +2050,7 @@ export function createControlPlaneApi(options) {
1960
2050
  if (!scope.vertical)
1961
2051
  return null;
1962
2052
  const actor = c.get('actor');
1963
- const incoming = await admin.getVersion(actor, versionId, scope.vertical);
2053
+ const incoming = await c.var.admin.getVersion(actor, versionId, scope.vertical);
1964
2054
  // The bind refuses a version that is not admitted, except onto a preview. Check that
1965
2055
  // BEFORE the copy. Otherwise unreviewed code would receive the scope's data through its
1966
2056
  // own restore handler, only for the bind to refuse afterwards.
@@ -1974,7 +2064,7 @@ export function createControlPlaneApi(options) {
1974
2064
  return null;
1975
2065
  const to = incoming.deploymentRef ?? null;
1976
2066
  const bound = scope.verticalVersionId
1977
- ? await admin.getVersion(actor, scope.verticalVersionId, scope.vertical)
2067
+ ? await c.var.admin.getVersion(actor, scope.verticalVersionId, scope.vertical)
1978
2068
  : undefined;
1979
2069
  const from = scope.servingRef ?? bound?.deploymentRef ?? null;
1980
2070
  if (!to || !from || from === to)
@@ -2005,12 +2095,12 @@ export function createControlPlaneApi(options) {
2005
2095
  app.get('/tenants/:tenantId/scopes/:scopeId/tables', async (c) => {
2006
2096
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
2007
2097
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
2008
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2098
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2009
2099
  if (!scope)
2010
2100
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2011
2101
  return c.json(await delegatedRead(c, tenantId, scopeId, scope, 'listScopeTables', null, {
2012
2102
  viaVertical: (v) => v.listScopeTables(scopeId),
2013
- colocated: () => admin.listScopeTables(c.get('actor'), tenantId, scopeId),
2103
+ colocated: () => c.var.admin.listScopeTables(c.get('actor'), tenantId, scopeId),
2014
2104
  }, (r) => r.length));
2015
2105
  });
2016
2106
  // Scope health (#321, criterion #3). The silent failure the field report chased was an
@@ -2039,21 +2129,21 @@ export function createControlPlaneApi(options) {
2039
2129
  if (outsideTenant(principal, tenantId)) {
2040
2130
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2041
2131
  }
2042
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2132
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2043
2133
  if (!scope)
2044
2134
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2045
2135
  // Health reads the scope the same way the Data view does, so it leaves the same
2046
2136
  // row: an auditor should not be able to tell which route reached the tables.
2047
2137
  const tables = await delegatedRead(c, tenantId, scopeId, scope, 'listScopeTables', null, {
2048
2138
  viaVertical: (v) => v.listScopeTables(scopeId),
2049
- colocated: () => admin.listScopeTables(c.get('actor'), tenantId, scopeId),
2139
+ colocated: () => c.var.admin.listScopeTables(c.get('actor'), tenantId, scopeId),
2050
2140
  }, (r) => r.length);
2051
2141
  const roles = tables.find((t) => t.name === '_substrat_roles');
2052
2142
  const roleCount = roles ? roles.rowCount : null;
2053
2143
  const roleProjectionEmpty = scope.status === 'active' && roleCount === 0;
2054
2144
  const missingStores = scope.vertical
2055
2145
  ? await missingStoresForTenant({
2056
- host: options.host,
2146
+ host: c.var.host,
2057
2147
  actor: c.get('actor'),
2058
2148
  slug: scope.vertical,
2059
2149
  tenantId,
@@ -2096,7 +2186,7 @@ export function createControlPlaneApi(options) {
2096
2186
  status: c.req.query('status'),
2097
2187
  limit: c.req.query('limit') ? Number(c.req.query('limit')) : undefined,
2098
2188
  });
2099
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2189
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2100
2190
  if (!scope)
2101
2191
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2102
2192
  // Same delegation ladder as the table reads: the intents live in the DO of the scope's BOUND
@@ -2104,7 +2194,7 @@ export function createControlPlaneApi(options) {
2104
2194
  const vertical = await verticalForScope(c, scope);
2105
2195
  return c.json(vertical
2106
2196
  ? await vertical.listPlatformRequestHistory(tenantId, scopeId, filter)
2107
- : await host.listPlatformRequestHistory(tenantId, scopeId, filter));
2197
+ : await c.var.host.listPlatformRequestHistory(tenantId, scopeId, filter));
2108
2198
  });
2109
2199
  // #332: re-provision a scope stuck at "roles projected, zero tuples" — the enforcement flip
2110
2200
  // switched on against an empty tuple table, so every login denies and the owner is locked out
@@ -2117,7 +2207,7 @@ export function createControlPlaneApi(options) {
2117
2207
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
2118
2208
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
2119
2209
  const actor = c.get('actor');
2120
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
2210
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
2121
2211
  if (!scope)
2122
2212
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2123
2213
  const p = c.get('principal');
@@ -2131,10 +2221,10 @@ export function createControlPlaneApi(options) {
2131
2221
  if (!reached)
2132
2222
  return c.json({ error: await diagnoseUnboundScope(c.get('actor'), scope) }, 501);
2133
2223
  const vertical = reached.client;
2134
- const entitlements = await admin.listEntitlements(actor, tenantId);
2224
+ const entitlements = await c.var.admin.listEntitlements(actor, tenantId);
2135
2225
  // #406: re-gathered and re-delivered like entitlements, so a reconcile also repairs a
2136
2226
  // dropped identity-link delivery — and is the channel a link/unlink after provision rides.
2137
- const identityLinks = (await admin.listIdentityLinks(actor, tenantId)).map(({ tenantId: _tenantId, ...link }) => link);
2227
+ const identityLinks = (await c.var.admin.listIdentityLinks(actor, tenantId)).map(({ tenantId: _tenantId, ...link }) => link);
2138
2228
  // #592: connection grants ride the same authoritative gather — the back-fill for a
2139
2229
  // scope provisioned before `grantToConnection` ran, and how a revoked connection's
2140
2230
  // grants stop being delivered (they are absent from the directory's live rows).
@@ -2148,18 +2238,18 @@ export function createControlPlaneApi(options) {
2148
2238
  // Best-effort: healing reaches the directory and must never take the reconcile down
2149
2239
  // with it. A failure here leaves exactly the behaviour that shipped before it existed.
2150
2240
  try {
2151
- await reconcileConnectionGrants({ admin, actor, declared: options.connectorGrants ?? {} }, tenantId, scope.vertical);
2241
+ await reconcileConnectionGrants({ admin: c.var.admin, actor, declared: options.connectorGrants ?? {} }, tenantId, scope.vertical);
2152
2242
  }
2153
2243
  catch {
2154
2244
  // The next reconcile tries again; the read-back still shows what is missing.
2155
2245
  }
2156
- const connectionGrants = connectionGrantsForScope(await admin.listConnectionGrants(actor, tenantId), scope.vertical, scopeId);
2246
+ const connectionGrants = connectionGrantsForScope(await c.var.admin.listConnectionGrants(actor, tenantId), scope.vertical, scopeId);
2157
2247
  // #687: connection sealing keys ride the same gather — the back-fill for a scope
2158
2248
  // provisioned before its connection existed, and the channel a connection made AFTER
2159
2249
  // provision rides. Minted on first ask, so this is also how a connection older than
2160
2250
  // the feature acquires a keypair without being reconnected.
2161
2251
  const connectionKeys = scope.vertical
2162
- ? await admin.connectionSealingKeys(tenantId, scope.vertical)
2252
+ ? await c.var.admin.connectionSealingKeys(tenantId, scope.vertical)
2163
2253
  : [];
2164
2254
  // #825: the BACKFILL seam for per-tenant stores. Minting lived only in the
2165
2255
  // tenant-creation lifecycle, so a tenant created before its vertical declared a
@@ -2213,7 +2303,7 @@ export function createControlPlaneApi(options) {
2213
2303
  // need a second run to discover the other.
2214
2304
  const tenantStores = scope.vertical
2215
2305
  ? ((await mintBestEffort('tenant-stores', () => collectTenantStoreHandles({
2216
- host: options.host,
2306
+ host: c.var.host,
2217
2307
  actor,
2218
2308
  slug: scope.vertical,
2219
2309
  tenantId,
@@ -2222,7 +2312,7 @@ export function createControlPlaneApi(options) {
2222
2312
  : [];
2223
2313
  if (scope.vertical) {
2224
2314
  await mintBestEffort('blob-stores', () => collectBlobStoreHandles({
2225
- host: options.host,
2315
+ host: c.var.host,
2226
2316
  actor,
2227
2317
  slug: scope.vertical,
2228
2318
  tenantId,
@@ -2237,12 +2327,12 @@ export function createControlPlaneApi(options) {
2237
2327
  // one before. Best-effort: without it the receipt falls back to the bound version, the
2238
2328
  // pre-#1653 answer, which costs at most one more reconcile.
2239
2329
  const serving = reached.via === 'serving-script' && scope.vertical
2240
- ? await admin.verticalServing(actor, scope.vertical).catch(() => null)
2330
+ ? await c.var.admin.verticalServing(actor, scope.vertical).catch(() => null)
2241
2331
  : null;
2242
2332
  const ranAs = versionReachedAt(reached.via, scope, serving);
2243
2333
  try {
2244
2334
  // #1674: the recorded OFF positions go back after the reconcile, before the receipt.
2245
- const result = await reconcileThenReassert(admin, actor, { tenantId, scopeId }, (carry) => vertical.reconcileInstance({
2335
+ const result = await reconcileThenReassert(c.var.admin, actor, { tenantId, scopeId }, (carry) => vertical.reconcileInstance({
2246
2336
  tenantId,
2247
2337
  scopeId,
2248
2338
  entitlements,
@@ -2261,7 +2351,7 @@ export function createControlPlaneApi(options) {
2261
2351
  // button means nothing to the thing that watches. Both name it with
2262
2352
  // `versionReachedAt`, from the deployment each actually reached.
2263
2353
  if (ranAs) {
2264
- await admin.markScopeProvisioned(actor, tenantId, scopeId, ranAs);
2354
+ await c.var.admin.markScopeProvisioned(actor, tenantId, scopeId, ranAs);
2265
2355
  }
2266
2356
  // The reconcile succeeded; a store that did not mint rides back as a diagnosis
2267
2357
  // (`substrat scope provision` prints it, and `/health` keeps reporting the gap
@@ -2295,14 +2385,14 @@ export function createControlPlaneApi(options) {
2295
2385
  until: c.req.query('until') ?? undefined,
2296
2386
  limit: c.req.query('limit') ? Number(c.req.query('limit')) : undefined,
2297
2387
  });
2298
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2388
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2299
2389
  if (!scope)
2300
2390
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2301
2391
  return c.json(await delegatedRead(c, tenantId, scopeId, scope, 'facetEvents', input, {
2302
2392
  // #1762: only the vertical's answer needs the guard. The co-located read runs
2303
2393
  // this platform's own kernel, which withholds by construction.
2304
2394
  viaVertical: async (v) => withholdIfPredatesRule(input, await v.facetEvents(scopeId, input)),
2305
- colocated: () => admin.facetEvents(c.get('actor'), tenantId, scopeId, input),
2395
+ colocated: () => c.var.admin.facetEvents(c.get('actor'), tenantId, scopeId, input),
2306
2396
  },
2307
2397
  // The buckets RETURNED, not `total`: the row says how much this read handed
2308
2398
  // back, and a capped facet hands back fewer than it counted.
@@ -2343,7 +2433,7 @@ export function createControlPlaneApi(options) {
2343
2433
  if (!vertical)
2344
2434
  return run.colocated();
2345
2435
  const answer = await run.viaVertical(vertical);
2346
- await admin.recordDelegatedRead(c.get('actor'), {
2436
+ await c.var.admin.recordDelegatedRead(c.get('actor'), {
2347
2437
  method,
2348
2438
  tenantId,
2349
2439
  scopeId,
@@ -2364,12 +2454,12 @@ export function createControlPlaneApi(options) {
2364
2454
  eventId: c.req.query('eventId'),
2365
2455
  maxDepth: c.req.query('maxDepth') ? Number(c.req.query('maxDepth')) : undefined,
2366
2456
  });
2367
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2457
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2368
2458
  if (!scope)
2369
2459
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2370
2460
  return c.json(await delegatedRead(c, tenantId, scopeId, scope, 'eventCause', input, {
2371
2461
  viaVertical: (v) => v.eventCause(scopeId, input),
2372
- colocated: () => admin.eventCause(c.get('actor'), tenantId, scopeId, input),
2462
+ colocated: () => c.var.admin.eventCause(c.get('actor'), tenantId, scopeId, input),
2373
2463
  }, (r) => r.chain.length));
2374
2464
  });
2375
2465
  // #1237 forward: what one event set off. Delegated like its backwards twin.
@@ -2380,12 +2470,12 @@ export function createControlPlaneApi(options) {
2380
2470
  eventId: c.req.query('eventId'),
2381
2471
  maxNodes: c.req.query('maxNodes') ? Number(c.req.query('maxNodes')) : undefined,
2382
2472
  });
2383
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2473
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2384
2474
  if (!scope)
2385
2475
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2386
2476
  return c.json(await delegatedRead(c, tenantId, scopeId, scope, 'eventEffects', input, {
2387
2477
  viaVertical: (v) => v.eventEffects(scopeId, input),
2388
- colocated: () => admin.eventEffects(c.get('actor'), tenantId, scopeId, input),
2478
+ colocated: () => c.var.admin.eventEffects(c.get('actor'), tenantId, scopeId, input),
2389
2479
  }, (r) => r.count));
2390
2480
  });
2391
2481
  // #1237: everything one call emitted — the siblings neither walk reaches. Delegated
@@ -2397,12 +2487,12 @@ export function createControlPlaneApi(options) {
2397
2487
  invocationId: c.req.query('invocationId'),
2398
2488
  limit: c.req.query('limit') ? Number(c.req.query('limit')) : undefined,
2399
2489
  });
2400
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2490
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2401
2491
  if (!scope)
2402
2492
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2403
2493
  return c.json(await delegatedRead(c, tenantId, scopeId, scope, 'invocationEvents', input, {
2404
2494
  viaVertical: (v) => v.invocationEvents(scopeId, input),
2405
- colocated: () => admin.invocationEvents(c.get('actor'), tenantId, scopeId, input),
2495
+ colocated: () => c.var.admin.invocationEvents(c.get('actor'), tenantId, scopeId, input),
2406
2496
  }, (r) => r.events.length));
2407
2497
  });
2408
2498
  // #1525: every delivery in the scope that gave up — the list the walks can only reach
@@ -2414,12 +2504,12 @@ export function createControlPlaneApi(options) {
2414
2504
  limit: c.req.query('limit') ? Number(c.req.query('limit')) : undefined,
2415
2505
  cursor: c.req.query('cursor') ?? undefined,
2416
2506
  });
2417
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2507
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2418
2508
  if (!scope)
2419
2509
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2420
2510
  return c.json(await delegatedRead(c, tenantId, scopeId, scope, 'deadLetters', input, {
2421
2511
  viaVertical: (v) => v.deadLetters(scopeId, input),
2422
- colocated: () => admin.deadLetters(c.get('actor'), tenantId, scopeId, input),
2512
+ colocated: () => c.var.admin.deadLetters(c.get('actor'), tenantId, scopeId, input),
2423
2513
  }, (r) => r.entries.length));
2424
2514
  });
2425
2515
  // #1744: one entity's declared lifecycle replayed over the scope's outbox — the process
@@ -2429,12 +2519,12 @@ export function createControlPlaneApi(options) {
2429
2519
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
2430
2520
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
2431
2521
  const input = lifecycleFlowInput.parse(await c.req.json());
2432
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2522
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2433
2523
  if (!scope)
2434
2524
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2435
2525
  return c.json(await delegatedRead(c, tenantId, scopeId, scope, 'lifecycleFlow', input, {
2436
2526
  viaVertical: (v) => v.lifecycleFlow(scopeId, input),
2437
- colocated: () => admin.lifecycleFlow(c.get('actor'), tenantId, scopeId, input),
2527
+ colocated: () => c.var.admin.lifecycleFlow(c.get('actor'), tenantId, scopeId, input),
2438
2528
  }, (r) => r.observation.events));
2439
2529
  });
2440
2530
  // #1235: one record's event history — the per-entity pivot of the audit spine.
@@ -2449,12 +2539,12 @@ export function createControlPlaneApi(options) {
2449
2539
  limit: c.req.query('limit') ? Number(c.req.query('limit')) : undefined,
2450
2540
  cursor: c.req.query('cursor') ?? undefined,
2451
2541
  });
2452
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2542
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2453
2543
  if (!scope)
2454
2544
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2455
2545
  return c.json(await delegatedRead(c, tenantId, scopeId, scope, 'entityHistory', input, {
2456
2546
  viaVertical: (v) => v.entityHistory(scopeId, input),
2457
- colocated: () => admin.entityHistory(c.get('actor'), tenantId, scopeId, input),
2547
+ colocated: () => c.var.admin.entityHistory(c.get('actor'), tenantId, scopeId, input),
2458
2548
  }, (r) => r.entries.length));
2459
2549
  });
2460
2550
  app.get('/tenants/:tenantId/scopes/:scopeId/tables/:table', async (c) => {
@@ -2465,12 +2555,12 @@ export function createControlPlaneApi(options) {
2465
2555
  limit: c.req.query('limit') ? Number(c.req.query('limit')) : undefined,
2466
2556
  offset: c.req.query('offset') ? Number(c.req.query('offset')) : undefined,
2467
2557
  });
2468
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2558
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2469
2559
  if (!scope)
2470
2560
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2471
2561
  return c.json(await delegatedRead(c, tenantId, scopeId, scope, 'readScopeTable', input, {
2472
2562
  viaVertical: (v) => v.readScopeTable(scopeId, input),
2473
- colocated: () => admin.readScopeTable(c.get('actor'), tenantId, scopeId, input),
2563
+ colocated: () => c.var.admin.readScopeTable(c.get('actor'), tenantId, scopeId, input),
2474
2564
  }, (r) => r.rows.length));
2475
2565
  });
2476
2566
  // The SQL console (#219): one read-only statement, POSTed because SQL does not
@@ -2482,7 +2572,7 @@ export function createControlPlaneApi(options) {
2482
2572
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
2483
2573
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
2484
2574
  const input = queryScopeInput.parse(await c.req.json());
2485
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2575
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2486
2576
  if (!scope)
2487
2577
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2488
2578
  return c.json(await delegatedRead(
@@ -2491,7 +2581,7 @@ export function createControlPlaneApi(options) {
2491
2581
  // statement would answer "someone queried" and nothing a reader needs.
2492
2582
  c, tenantId, scopeId, scope, 'queryScope', input, {
2493
2583
  viaVertical: (v) => v.queryScope(scopeId, input),
2494
- colocated: () => admin.queryScope(c.get('actor'), tenantId, scopeId, input),
2584
+ colocated: () => c.var.admin.queryScope(c.get('actor'), tenantId, scopeId, input),
2495
2585
  }, (r) => r.rows.length));
2496
2586
  });
2497
2587
  // The K-35 denial log (#867) — the third of the platform's three logs and the last to
@@ -2513,12 +2603,12 @@ export function createControlPlaneApi(options) {
2513
2603
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2514
2604
  }
2515
2605
  const filter = denialLogQuery.parse(c.req.query());
2516
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2606
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2517
2607
  if (!scope)
2518
2608
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2519
2609
  return c.json(await delegatedRead(c, tenantId, scopeId, scope, 'listDenials', filter, {
2520
2610
  viaVertical: (v) => v.listDenials(scopeId, filter),
2521
- colocated: () => admin.listDenials(c.get('actor'), tenantId, scopeId, filter),
2611
+ colocated: () => c.var.admin.listDenials(c.get('actor'), tenantId, scopeId, filter),
2522
2612
  }, (r) => r.length));
2523
2613
  });
2524
2614
  app.get('/tenants/:tenantId/scopes/:scopeId/denials/summary', async (c) => {
@@ -2529,12 +2619,12 @@ export function createControlPlaneApi(options) {
2529
2619
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2530
2620
  }
2531
2621
  const filter = denialLogQuery.parse(c.req.query());
2532
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2622
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2533
2623
  if (!scope)
2534
2624
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2535
2625
  return c.json(await delegatedRead(c, tenantId, scopeId, scope, 'summarizeDenials', filter, {
2536
2626
  viaVertical: (v) => v.summarizeDenials(scopeId, filter),
2537
- colocated: () => admin.summarizeDenials(c.get('actor'), tenantId, scopeId, filter),
2627
+ colocated: () => c.var.admin.summarizeDenials(c.get('actor'), tenantId, scopeId, filter),
2538
2628
  }, (r) => r.buckets.length));
2539
2629
  });
2540
2630
  // The owner seat (#925). Both reads go to the VERTICAL — the seat lives in its identity
@@ -2551,7 +2641,7 @@ export function createControlPlaneApi(options) {
2551
2641
  if (outsideTenant(principal, tenantId)) {
2552
2642
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2553
2643
  }
2554
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2644
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2555
2645
  if (!scope)
2556
2646
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2557
2647
  const vertical = await verticalForScope(c, scope);
@@ -2574,7 +2664,7 @@ export function createControlPlaneApi(options) {
2574
2664
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2575
2665
  }
2576
2666
  const actor = c.get('actor');
2577
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
2667
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
2578
2668
  if (!scope)
2579
2669
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2580
2670
  const vertical = await verticalForScope(c, scope);
@@ -2584,7 +2674,7 @@ export function createControlPlaneApi(options) {
2584
2674
  // the platform has activated but not requiring it — a platform hostname is bound
2585
2675
  // `pending` and activated a step later, and that step can fail while the app is fully
2586
2676
  // reachable (#294). `failed` is the one state that never routes, so only it is skipped.
2587
- const bound = (await admin.listHostnames(actor, { scopeId })).filter((h) => h.status !== 'failed');
2677
+ const bound = (await c.var.admin.listHostnames(actor, { scopeId })).filter((h) => h.status !== 'failed');
2588
2678
  const pick = (hs) => hs.find((h) => h.surface === 'app' && h.canonical) ?? hs.find((h) => h.surface === 'app') ?? hs[0];
2589
2679
  const host = pick(bound.filter((h) => h.status === 'active')) ?? pick(bound);
2590
2680
  if (!host) {
@@ -2618,7 +2708,7 @@ export function createControlPlaneApi(options) {
2618
2708
  const body = ownerTransferInput.parse(await c.req.json());
2619
2709
  const actor = c.get('actor');
2620
2710
  // K-3 first, so a scope of another tenant reads as absent before anything is reached.
2621
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
2711
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
2622
2712
  if (!scope)
2623
2713
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2624
2714
  const vertical = await verticalForScope(c, scope);
@@ -2628,7 +2718,7 @@ export function createControlPlaneApi(options) {
2628
2718
  // An abandon (`abandon: true`) is audited the same way, every row marked, so the log tells
2629
2719
  // a hand-over closed without finishing from one that finished.
2630
2720
  const base = { tenantId, scopeId, operationId, from: body.from, to: body.to, ...(body.abandon ? { abandon: true } : {}) };
2631
- await admin.recordOwnerTransfer(actor, { ...base, phase: 'intent' });
2721
+ await c.var.admin.recordOwnerTransfer(actor, { ...base, phase: 'intent' });
2632
2722
  let moved;
2633
2723
  try {
2634
2724
  moved = await vertical.transferOwner({ tenantId, scopeId, ...body });
@@ -2636,7 +2726,7 @@ export function createControlPlaneApi(options) {
2636
2726
  catch (e) {
2637
2727
  const refused = e instanceof ControlPlaneError && e.status === 409;
2638
2728
  const error = (e instanceof Error ? e.message : String(e)).slice(0, OWNER_TRANSFER_AUDIT_ERROR_MAX);
2639
- await admin
2729
+ await c.var.admin
2640
2730
  .recordOwnerTransfer(actor, { ...base, phase: refused ? 'refused' : 'failed', error })
2641
2731
  .catch(() => undefined);
2642
2732
  if (e instanceof ControlPlaneError) {
@@ -2645,7 +2735,7 @@ export function createControlPlaneApi(options) {
2645
2735
  throw e;
2646
2736
  }
2647
2737
  try {
2648
- await admin.recordOwnerTransfer(actor, {
2738
+ await c.var.admin.recordOwnerTransfer(actor, {
2649
2739
  ...base,
2650
2740
  phase: 'applied',
2651
2741
  outcome: moved.outcome,
@@ -2676,7 +2766,7 @@ export function createControlPlaneApi(options) {
2676
2766
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
2677
2767
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
2678
2768
  const input = configureInstanceBody.parse(await c.req.json());
2679
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2769
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2680
2770
  if (!scope)
2681
2771
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2682
2772
  const vertical = await verticalForScope(c, scope);
@@ -2699,18 +2789,18 @@ export function createControlPlaneApi(options) {
2699
2789
  // status the transition graph forbids. The graph is enforced below the seam;
2700
2790
  // an illegal transition surfaces as a 409.
2701
2791
  const transitions = {
2702
- activate: (a, t, s) => admin.activateScope(a, t, s),
2703
- suspend: (a, t, s) => admin.suspendScope(a, t, s),
2704
- unsuspend: (a, t, s) => admin.unsuspendScope(a, t, s),
2705
- archive: (a, t, s) => admin.archiveScope(a, t, s),
2706
- unarchive: (a, t, s) => admin.unarchiveScope(a, t, s),
2792
+ activate: (admin, a, t, s) => admin.activateScope(a, t, s),
2793
+ suspend: (admin, a, t, s) => admin.suspendScope(a, t, s),
2794
+ unsuspend: (admin, a, t, s) => admin.unsuspendScope(a, t, s),
2795
+ archive: (admin, a, t, s) => admin.archiveScope(a, t, s),
2796
+ unarchive: (admin, a, t, s) => admin.unarchiveScope(a, t, s),
2707
2797
  };
2708
2798
  for (const [action, run] of Object.entries(transitions)) {
2709
2799
  app.post(`/tenants/:tenantId/scopes/:scopeId/${action}`, async (c) => {
2710
2800
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
2711
2801
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
2712
- await run(c.get('actor'), tenantId, scopeId);
2713
- return c.json(await admin.getScopeRecord(c.get('actor'), tenantId, scopeId));
2802
+ await run(c.var.admin, c.get('actor'), tenantId, scopeId);
2803
+ return c.json(await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId));
2714
2804
  });
2715
2805
  }
2716
2806
  // -- snapshots (preview-and-snapshots.md §3/§9) -----------------------------
@@ -2723,13 +2813,13 @@ export function createControlPlaneApi(options) {
2723
2813
  const actor = c.get('actor');
2724
2814
  const vertical = await verticalForScope(c, scope);
2725
2815
  if (!vertical)
2726
- return options.host.snapshotScope(actor, tenantId, scope.id, opts);
2816
+ return c.var.host.snapshotScope(actor, tenantId, scope.id, opts);
2727
2817
  const snapId = scopeIdSchema.parse(ulid());
2728
2818
  // Directory row FIRST, as `provisioning` (K-31's two-phase shape, used as
2729
2819
  // intended): a crash between the row and the data copy leaves an inert
2730
2820
  // provisioning row — which, carrying provenance and an expiry, the GC sweep
2731
2821
  // eventually reaps — never copied data with no record.
2732
- await options.host.provisionScope(actor, {
2822
+ await c.var.host.provisionScope(actor, {
2733
2823
  tenantId,
2734
2824
  scopeId: snapId,
2735
2825
  kind: opts.kind ?? 'archive',
@@ -2740,11 +2830,11 @@ export function createControlPlaneApi(options) {
2740
2830
  expiresAt: opts.expiresAt,
2741
2831
  });
2742
2832
  await retryTransient(() => vertical.snapshotScope({ sourceScopeId: scope.id, newScopeId: snapId }));
2743
- await admin.activateScope(actor, tenantId, snapId);
2833
+ await c.var.admin.activateScope(actor, tenantId, snapId);
2744
2834
  // Bound to the SOURCE's current version: source and fork share a deployment, so
2745
2835
  // the fork resolves to the DO namespace its bytes actually live in.
2746
2836
  if (scope.verticalVersionId) {
2747
- await admin.bindScopeVersion(actor, tenantId, snapId, scope.verticalVersionId);
2837
+ await c.var.admin.bindScopeVersion(actor, tenantId, snapId, scope.verticalVersionId);
2748
2838
  }
2749
2839
  return snapId;
2750
2840
  };
@@ -2753,7 +2843,7 @@ export function createControlPlaneApi(options) {
2753
2843
  app.get('/tenants/:tenantId/scopes/:scopeId/snapshots', async (c) => {
2754
2844
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
2755
2845
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
2756
- const scopes = await admin.listScopes(c.get('actor'), { tenantId });
2846
+ const scopes = await c.var.admin.listScopes(c.get('actor'), { tenantId });
2757
2847
  return c.json(scopes
2758
2848
  .filter((s) => s.forkedFrom === scopeId)
2759
2849
  .sort((a, b) => (a.forkedAt < b.forkedAt ? 1 : -1)));
@@ -2762,12 +2852,12 @@ export function createControlPlaneApi(options) {
2762
2852
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
2763
2853
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
2764
2854
  const body = snapshotScopeBody.parse(await c.req.json().catch(() => ({})));
2765
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2855
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2766
2856
  if (!scope)
2767
2857
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2768
2858
  try {
2769
2859
  const snapId = await orchestratedSnapshot(c, tenantId, scope, body);
2770
- return c.json(await admin.getScopeRecord(c.get('actor'), tenantId, snapId), 201);
2860
+ return c.json(await c.var.admin.getScopeRecord(c.get('actor'), tenantId, snapId), 201);
2771
2861
  }
2772
2862
  catch (e) {
2773
2863
  if (e instanceof ControlPlaneError) {
@@ -2808,7 +2898,7 @@ export function createControlPlaneApi(options) {
2808
2898
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
2809
2899
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
2810
2900
  const actor = c.get('actor');
2811
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
2901
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
2812
2902
  if (!scope)
2813
2903
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2814
2904
  if (!scope.forkedFrom) {
@@ -2821,7 +2911,7 @@ export function createControlPlaneApi(options) {
2821
2911
  // between the two converges on retry.
2822
2912
  const vertical = await verticalForScope(c, scope);
2823
2913
  const storageStranded = await deleteScopeStorageOrStrand(vertical, scopeId);
2824
- await options.host.deleteSnapshot(actor, tenantId, scopeId);
2914
+ await c.var.host.deleteSnapshot(actor, tenantId, scopeId);
2825
2915
  return c.json({ deleted: scopeId, ...(storageStranded ? { storageStranded: true } : {}) });
2826
2916
  }
2827
2917
  catch (e) {
@@ -2852,7 +2942,7 @@ export function createControlPlaneApi(options) {
2852
2942
  `store is global, so a backup would move its data out of that jurisdiction ` +
2853
2943
  `(K-32); refused until a per-jurisdiction store exists`);
2854
2944
  }
2855
- const dump = await admin.exportScope(c.get('actor'), tenantId, scope.id);
2945
+ const dump = await c.var.admin.exportScope(c.get('actor'), tenantId, scope.id);
2856
2946
  const vertical = await verticalForScope(c, scope);
2857
2947
  const tables = vertical ? await vertical.exportScope(scope.id) : dump.tables;
2858
2948
  // Seal classified payloads per subject on the way in (#37). This copy is full-fidelity
@@ -2875,7 +2965,7 @@ export function createControlPlaneApi(options) {
2875
2965
  return c.json({ error: 'no backup target configured' }, 501);
2876
2966
  // Reads the directory, not the store, for the tenant cross-check: the store is keyed
2877
2967
  // by (tenant, scope) but nothing there proves the caller's tenant owns the scope.
2878
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2968
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2879
2969
  if (!scope)
2880
2970
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2881
2971
  return c.json(await options.scopeBackups.list({ tenantId, scopeId }));
@@ -2889,7 +2979,7 @@ export function createControlPlaneApi(options) {
2889
2979
  const capturedAt = c.req.param('capturedAt');
2890
2980
  if (!options.scopeBackups)
2891
2981
  return c.json({ error: 'no backup target configured' }, 501);
2892
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2982
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2893
2983
  if (!scope)
2894
2984
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2895
2985
  const dump = await options.scopeBackups.get({ tenantId, scopeId, capturedAt });
@@ -2909,7 +2999,7 @@ export function createControlPlaneApi(options) {
2909
2999
  app.post('/tenants/:tenantId/scopes/:scopeId/backups', async (c) => {
2910
3000
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
2911
3001
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
2912
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
3002
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2913
3003
  if (!scope)
2914
3004
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2915
3005
  try {
@@ -2943,10 +3033,10 @@ export function createControlPlaneApi(options) {
2943
3033
  // A ULID, like every other subject id the spine carries — parsed rather than trusted,
2944
3034
  // so a wildcard or an injection attempt is a 400 and never reaches the UPDATE.
2945
3035
  const subjectId = dataSubjectIdSchema.parse(c.req.param('subjectId'));
2946
- const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
3036
+ const scope = await c.var.admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
2947
3037
  if (!scope)
2948
3038
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
2949
- return c.json(await admin.shredSubject(c.get('actor'), tenantId, scopeId, subjectId));
3039
+ return c.json(await c.var.admin.shredSubject(c.get('actor'), tenantId, scopeId, subjectId));
2950
3040
  });
2951
3041
  // -- directory backups (#40) -----------------------------------------------
2952
3042
  // The platform's own disaster recovery, on the same store posture as the scope
@@ -2971,7 +3061,7 @@ export function createControlPlaneApi(options) {
2971
3061
  if (!options.directoryBackups)
2972
3062
  return c.json({ error: 'no directory backup target configured' }, 501);
2973
3063
  const result = await backupDirectoryIfDue({
2974
- admin,
3064
+ admin: c.var.admin,
2975
3065
  store: options.directoryBackups,
2976
3066
  actor: c.get('actor'),
2977
3067
  force: true,
@@ -3008,14 +3098,14 @@ export function createControlPlaneApi(options) {
3008
3098
  if (!dump)
3009
3099
  return c.json({ error: `no directory backup at ${body.capturedAt}` }, 404);
3010
3100
  const actor = c.get('actor');
3011
- const live = await admin.listTenants(actor, { limit: 1 });
3101
+ const live = await c.var.admin.listTenants(actor, { limit: 1 });
3012
3102
  if (live.length > 0 && !body.overwrite) {
3013
3103
  return c.json({
3014
3104
  error: 'the directory is not empty — a restore REPLACES it, so anything created ' +
3015
3105
  'since this copy was taken would be lost; pass overwrite=true to confirm',
3016
3106
  }, 409);
3017
3107
  }
3018
- await admin.restoreDirectory(actor, dump);
3108
+ await c.var.admin.restoreDirectory(actor, dump);
3019
3109
  return c.json({ capturedAt: dump.capturedAt, tables: dump.tables.length });
3020
3110
  });
3021
3111
  // Reap an ARCHIVED primary scope (control-plane.md §4.4): free its DO storage —
@@ -3059,12 +3149,12 @@ export function createControlPlaneApi(options) {
3059
3149
  if (parsed.data.countOnly !== undefined) {
3060
3150
  return c.json({ error: 'countOnly is not honoured here — POST the same body to …/redrain-count for a read-only count' }, 400);
3061
3151
  }
3062
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
3152
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
3063
3153
  if (!scope)
3064
3154
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
3065
3155
  // The instant alone, never `parsed.data`, so the field cannot reach the adapter from
3066
3156
  // this door even if the refusal above is ever relaxed.
3067
- const redrained = await admin.redrainEvents(actor, tenantId, scopeId, {
3157
+ const redrained = await c.var.admin.redrainEvents(actor, tenantId, scopeId, {
3068
3158
  drainedBefore: parsed.data.drainedBefore,
3069
3159
  });
3070
3160
  // `more` rather than silence: the verb reopens a bounded batch, so a caller that posts
@@ -3103,10 +3193,10 @@ export function createControlPlaneApi(options) {
3103
3193
  'already shipped to the rebuilt table. Pass the instant the old table stopped receiving.',
3104
3194
  }, 400);
3105
3195
  }
3106
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
3196
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
3107
3197
  if (!scope)
3108
3198
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
3109
- const redrainable = await admin.redrainEvents(actor, tenantId, scopeId, {
3199
+ const redrainable = await c.var.admin.redrainEvents(actor, tenantId, scopeId, {
3110
3200
  drainedBefore: parsed.data.drainedBefore,
3111
3201
  countOnly: true,
3112
3202
  });
@@ -3119,7 +3209,7 @@ export function createControlPlaneApi(options) {
3119
3209
  // Body is optional so the bare `POST …/reap` every existing caller sends still
3120
3210
  // parses; `backup` tri-states on purpose (see the ordering comment below).
3121
3211
  const { backup: wantsBackup } = reapScopeBody.parse(await c.req.json().catch(() => ({})));
3122
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
3212
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
3123
3213
  if (!scope)
3124
3214
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
3125
3215
  if (scope.status !== 'archived') {
@@ -3130,7 +3220,7 @@ export function createControlPlaneApi(options) {
3130
3220
  // only in `reapScope` after. A serving app always holds a bound name; unbind it first.
3131
3221
  // (This route is the interactive per-scope reap; the tenant-teardown route forces past
3132
3222
  // the same guard because releasing every name is the point there.)
3133
- const boundNames = await admin.listHostnames(actor, { scopeId, limit: 1 });
3223
+ const boundNames = await c.var.admin.listHostnames(actor, { scopeId, limit: 1 });
3134
3224
  if (boundNames.length > 0) {
3135
3225
  return c.json({
3136
3226
  error: `scope ${scopeId} still resolves hostname '${boundNames[0].hostname}' — ` +
@@ -3168,10 +3258,10 @@ export function createControlPlaneApi(options) {
3168
3258
  // By this point the backup contract has resolved (a copy landed, or the caller
3169
3259
  // explicitly declined one), so stranding is a bookkeeping fact, not data loss.
3170
3260
  const storageStranded = await deleteScopeStorageOrStrand(vertical, scopeId);
3171
- await admin.reapScope(actor, tenantId, scopeId, {
3261
+ await c.var.admin.reapScope(actor, tenantId, scopeId, {
3172
3262
  ...(backup ? { backupRef: backupRefOf(backup) } : {}),
3173
3263
  });
3174
- const reaped = await admin.getScopeRecord(actor, tenantId, scopeId);
3264
+ const reaped = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
3175
3265
  return c.json({ ...reaped, backup, ...(storageStranded ? { storageStranded: true } : {}) });
3176
3266
  }
3177
3267
  catch (e) {
@@ -3191,7 +3281,7 @@ export function createControlPlaneApi(options) {
3191
3281
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
3192
3282
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
3193
3283
  const actor = c.get('actor');
3194
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
3284
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
3195
3285
  if (!scope)
3196
3286
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
3197
3287
  // Residency (K-7/K-32): jurisdiction pins EXECUTION, not just storage. A pull
@@ -3209,7 +3299,7 @@ export function createControlPlaneApi(options) {
3209
3299
  // bytes when the host is co-located. When the scope's data lives in a vertical
3210
3300
  // deployment, its dump OVERLAYS the (placeholder) tables — audit stays on the
3211
3301
  // one canonical path either way.
3212
- const dump = await admin.exportScope(actor, tenantId, scopeId);
3302
+ const dump = await c.var.admin.exportScope(actor, tenantId, scopeId);
3213
3303
  const vertical = await verticalForScope(c, scope);
3214
3304
  const tables = vertical ? await vertical.exportScope(scopeId) : dump.tables;
3215
3305
  if (full)
@@ -3248,13 +3338,13 @@ export function createControlPlaneApi(options) {
3248
3338
  app.get('/tenants/:tenantId/export', async (c) => {
3249
3339
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
3250
3340
  const actor = c.get('actor');
3251
- const tenant = await admin.getTenant(actor, tenantId);
3341
+ const tenant = await c.var.admin.getTenant(actor, tenantId);
3252
3342
  if (!tenant)
3253
3343
  return c.json({ error: `unknown tenant: ${tenantId}` }, 404);
3254
3344
  // Every scope, tombstones included: an archived or reaped scope is part of the
3255
3345
  // tenant's history, and an export that quietly dropped them would misrepresent what
3256
3346
  // the tenant was. Their DATA is a different question, handled below.
3257
- const scopes = await admin.listScopes(actor, { tenantId });
3347
+ const scopes = await c.var.admin.listScopes(actor, { tenantId });
3258
3348
  // Residency (K-7/K-32), checked across the WHOLE tenant before anything is read: an
3259
3349
  // export lands on a machine outside the platform's control, so one pinned scope
3260
3350
  // taints the file. Refused as a unit rather than silently exporting the global
@@ -3271,18 +3361,18 @@ export function createControlPlaneApi(options) {
3271
3361
  }
3272
3362
  const full = c.req.query('full') === 'true';
3273
3363
  try {
3274
- const orgs = await admin.listOrgs(actor, tenantId);
3364
+ const orgs = await c.var.admin.listOrgs(actor, tenantId);
3275
3365
  // Revoked memberships included: K-21 makes a removal a tombstone precisely because
3276
3366
  // "was a member until March" is the fact an audit asks for.
3277
- const members = (await Promise.all(orgs.map((o) => admin.listMembers(actor, tenantId, o.id, { includeRevoked: true })))).flat();
3367
+ const members = (await Promise.all(orgs.map((o) => c.var.admin.listMembers(actor, tenantId, o.id, { includeRevoked: true })))).flat();
3278
3368
  const [roles, entitlements, identityLinks, hostnames, stores, blobStores, connections] = await Promise.all([
3279
- admin.listRoles(actor, { tenantId }),
3280
- admin.listEntitlements(actor, tenantId),
3281
- admin.listIdentityLinks(actor, tenantId),
3282
- admin.listHostnames(actor, { tenantId }),
3283
- admin.listTenantStores(actor, { tenantId }),
3284
- admin.listBlobStores(actor, { tenantId }),
3285
- admin.listConnections(actor, { tenantId }),
3369
+ c.var.admin.listRoles(actor, { tenantId }),
3370
+ c.var.admin.listEntitlements(actor, tenantId),
3371
+ c.var.admin.listIdentityLinks(actor, tenantId),
3372
+ c.var.admin.listHostnames(actor, { tenantId }),
3373
+ c.var.admin.listTenantStores(actor, { tenantId }),
3374
+ c.var.admin.listBlobStores(actor, { tenantId }),
3375
+ c.var.admin.listConnections(actor, { tenantId }),
3286
3376
  ]);
3287
3377
  // Scope DATA, from the same delegation the per-scope export route uses: the
3288
3378
  // canonical `exportScope` writes the audit entry and is the bytes when co-located;
@@ -3296,7 +3386,7 @@ export function createControlPlaneApi(options) {
3296
3386
  const live = scopes.filter((s) => s.status !== 'reaped');
3297
3387
  const data = [];
3298
3388
  for (const scope of live) {
3299
- const dump = await admin.exportScope(actor, tenantId, scope.id);
3389
+ const dump = await c.var.admin.exportScope(actor, tenantId, scope.id);
3300
3390
  const vertical = await verticalForScope(c, scope);
3301
3391
  const tables = vertical ? await vertical.exportScope(scope.id) : dump.tables;
3302
3392
  data.push({ ...dump, tables: mask ? await maskDump(tables, mask) : tables });
@@ -3305,7 +3395,7 @@ export function createControlPlaneApi(options) {
3305
3395
  // customer's Art. 20 data, and it carries staff actor ids and internal action
3306
3396
  // names. An escrow or a dispute needs it, which is why break-glass reaches it
3307
3397
  // rather than nothing reaching it.
3308
- const adminLog = full ? await admin.auditLog(actor, { tenantId }) : null;
3398
+ const adminLog = full ? await c.var.admin.auditLog(actor, { tenantId }) : null;
3309
3399
  const body = {
3310
3400
  tenantId,
3311
3401
  capturedAt: new Date().toISOString(),
@@ -3358,7 +3448,7 @@ export function createControlPlaneApi(options) {
3358
3448
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
3359
3449
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
3360
3450
  const actor = c.get('actor');
3361
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
3451
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
3362
3452
  if (!scope)
3363
3453
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
3364
3454
  const raw = await c.req.json();
@@ -3386,7 +3476,7 @@ export function createControlPlaneApi(options) {
3386
3476
  const origin = { tenantId: tenantIdSchema.parse(dump.tenantId), scopeId: scopeIdSchema.parse(dump.scopeId) };
3387
3477
  const tables = await openDump(dump.tables, sealerFor(c, origin.tenantId, origin.scopeId));
3388
3478
  const landing = { ...dump, tables };
3389
- await host.restoreScope(actor, tenantId, scopeId, landing, sourceHint ? { sourceScopeId: sourceHint } : undefined);
3479
+ await c.var.host.restoreScope(actor, tenantId, scopeId, landing, sourceHint ? { sourceScopeId: sourceHint } : undefined);
3390
3480
  const vertical = await verticalForScope(c, scope);
3391
3481
  // #1742: the recorded OFF positions ride the restore, so the deployment switches them
3392
3482
  // off in the replay's own event — its own sweeper cannot land in between.
@@ -3399,7 +3489,7 @@ export function createControlPlaneApi(options) {
3399
3489
  // #1674: a backup from before a module was switched off brings it back on; the
3400
3490
  // directory's record puts it back off now, not at the next reconcile. Where the
3401
3491
  // deployment already did (#1742), this finds it done and audits what it reported.
3402
- await admin.reassertSystemSwitches(actor, { tenantId, scopeId }, { appliedInUnit: restored?.switchedOff });
3492
+ await c.var.admin.reassertSystemSwitches(actor, { tenantId, scopeId }, { appliedInUnit: restored?.switchedOff });
3403
3493
  return c.json({ restored: scopeId, tables: tables.length });
3404
3494
  }
3405
3495
  catch (e) {
@@ -3423,13 +3513,13 @@ export function createControlPlaneApi(options) {
3423
3513
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
3424
3514
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
3425
3515
  const actor = c.get('actor');
3426
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
3516
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
3427
3517
  if (!scope)
3428
3518
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
3429
3519
  const vertical = await verticalForScope(c, scope);
3430
3520
  return c.json(vertical
3431
3521
  ? await vertical.appliedMigrations(scopeId)
3432
- : await admin.scopeAppliedMigrations(actor, tenantId, scopeId));
3522
+ : await c.var.admin.scopeAppliedMigrations(actor, tenantId, scopeId));
3433
3523
  });
3434
3524
  // #286: the PITR bookmarks a scope recorded before its migration passes — the
3435
3525
  // rewind points the deployments UI offers for a backout. Read through the
@@ -3439,13 +3529,13 @@ export function createControlPlaneApi(options) {
3439
3529
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
3440
3530
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
3441
3531
  const actor = c.get('actor');
3442
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
3532
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
3443
3533
  if (!scope)
3444
3534
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
3445
3535
  const vertical = await verticalForScope(c, scope);
3446
3536
  return c.json(vertical
3447
3537
  ? await vertical.migrationBookmarks(scopeId)
3448
- : await admin.scopeMigrationBookmarks(actor, tenantId, scopeId));
3538
+ : await c.var.admin.scopeMigrationBookmarks(actor, tenantId, scopeId));
3449
3539
  });
3450
3540
  // #286's backout: rewind a scope to a pre-migration bookmark — schema AND data,
3451
3541
  // discarding every write since. Audited below the seam BEFORE the rewind runs;
@@ -3459,12 +3549,12 @@ export function createControlPlaneApi(options) {
3459
3549
  .object({ bookmark: z.string().min(1), force: z.boolean().optional() })
3460
3550
  .parse(await c.req.json());
3461
3551
  const actor = c.get('actor');
3462
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
3552
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
3463
3553
  if (!scope)
3464
3554
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
3465
3555
  try {
3466
3556
  const vertical = await verticalForScope(c, scope);
3467
- const result = await admin.rewindScope(actor, tenantId, scopeId, bookmark, {
3557
+ const result = await c.var.admin.rewindScope(actor, tenantId, scopeId, bookmark, {
3468
3558
  force,
3469
3559
  localApply: !vertical,
3470
3560
  });
@@ -3478,7 +3568,7 @@ export function createControlPlaneApi(options) {
3478
3568
  // Meanwhile, the deployment that rewound it holds those modules off (#1819). A hold is
3479
3569
  // released by that reconcile's re-assert, or earlier by a switch move known to land on the
3480
3570
  // rewound storage, such as an operator's ON.
3481
- await admin.markScopeProvisioned(actor, tenantId, scopeId, null);
3571
+ await c.var.admin.markScopeProvisioned(actor, tenantId, scopeId, null);
3482
3572
  return c.json(answer);
3483
3573
  }
3484
3574
  catch (e) {
@@ -3529,7 +3619,7 @@ export function createControlPlaneApi(options) {
3529
3619
  if (v.ownerTenant === null) {
3530
3620
  return c.json({ error: 'adopt-serving is a private-vertical operation' }, 409);
3531
3621
  }
3532
- const owned = (await admin.listScopes(actor, { tenantId: v.ownerTenant, vertical: slug })).filter((s) => !s.forkedFrom && s.kind !== 'preview' && s.status === 'active');
3622
+ const owned = (await c.var.admin.listScopes(actor, { tenantId: v.ownerTenant, vertical: slug })).filter((s) => !s.forkedFrom && s.kind !== 'preview' && s.status === 'active');
3533
3623
  const { acknowledge } = adoptServingBody.parse(await c.req.json().catch(() => ({})));
3534
3624
  const adopted = [];
3535
3625
  const alreadyAdopted = [];
@@ -3556,6 +3646,13 @@ export function createControlPlaneApi(options) {
3556
3646
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
3557
3647
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
3558
3648
  const { vertical, ackMigrations, abandonData, acknowledge } = rebindScopeVerticalBody.parse(await c.req.json());
3649
+ // #977: the TARGET must be a vertical this principal can read — its own tenant's, or a
3650
+ // listed one. The path pin confines whose scope moves; without this, it could move onto
3651
+ // another tenant's private vertical, which a confined caller must not even learn exists
3652
+ // (404, as `GET /verticals/:slug` answers it).
3653
+ if (await notReadable(c.get('principal'), vertical)) {
3654
+ return c.json({ error: `unknown vertical: ${vertical}` }, 404);
3655
+ }
3559
3656
  try {
3560
3657
  const r = await rebindScopeOntoVertical(c, tenantId, scopeId, vertical, {
3561
3658
  ackMigrations,
@@ -3581,7 +3678,7 @@ export function createControlPlaneApi(options) {
3581
3678
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
3582
3679
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
3583
3680
  const versionId = z.string().min(1).parse(c.req.query('versionId'));
3584
- return c.json({ affected: await admin.bindingImpact(c.get('actor'), tenantId, scopeId, versionId) });
3681
+ return c.json({ affected: await c.var.admin.bindingImpact(c.get('actor'), tenantId, scopeId, versionId) });
3585
3682
  });
3586
3683
  // Pin a scope to a vertical version (#31; orchestration.md §4). Refuses a
3587
3684
  // non-admitted version below the seam — that refusal is the registry's reason to
@@ -3600,7 +3697,7 @@ export function createControlPlaneApi(options) {
3600
3697
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
3601
3698
  const { versionId, snapshot, acknowledge } = bindScopeVersionBody.parse(await c.req.json());
3602
3699
  const actor = c.get('actor');
3603
- const scope = await admin.getScopeRecord(actor, tenantId, scopeId);
3700
+ const scope = await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
3604
3701
  // #1756: the apps in this tenant the bind would break, asked BEFORE the carry and the
3605
3702
  // snapshot below, which move data. The host refuses too, whoever calls, but by then a
3606
3703
  // refusal would land after the bytes had moved. It refuses what the bind would refuse
@@ -3621,12 +3718,12 @@ export function createControlPlaneApi(options) {
3621
3718
  // already did so in the restore's own event; what it reports is audited here.
3622
3719
  const reassert = async () => {
3623
3720
  if (scope) {
3624
- await admin.reassertSystemSwitches(actor, { tenantId, scopeId }, { appliedInUnit: carried?.switchedOff });
3721
+ await c.var.admin.reassertSystemSwitches(actor, { tenantId, scopeId }, { appliedInUnit: carried?.switchedOff });
3625
3722
  }
3626
3723
  };
3627
3724
  // The host's own refusal, should the impact have changed since it was read above (a
3628
3725
  // promote landing in between): relayed as the same 409, never as a 500.
3629
- const bind = (opts) => admin.bindScopeVersion(actor, tenantId, scopeId, versionId, opts).catch(relayHostRefusal);
3726
+ const bind = (opts) => c.var.admin.bindScopeVersion(actor, tenantId, scopeId, versionId, opts).catch(relayHostRefusal);
3630
3727
  if (snapshot) {
3631
3728
  if (!scope) {
3632
3729
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
@@ -3638,8 +3735,8 @@ export function createControlPlaneApi(options) {
3638
3735
  let migrationCrossing = false;
3639
3736
  if (scope.vertical && scope.verticalVersionId) {
3640
3737
  const [current, incoming] = await Promise.all([
3641
- admin.getVersion(actor, scope.verticalVersionId, scope.vertical),
3642
- admin.getVersion(actor, versionId, scope.vertical),
3738
+ c.var.admin.getVersion(actor, scope.verticalVersionId, scope.vertical),
3739
+ c.var.admin.getVersion(actor, versionId, scope.vertical),
3643
3740
  ]);
3644
3741
  migrationCrossing = Boolean(current && incoming && current.migrationDigest !== incoming.migrationDigest);
3645
3742
  }
@@ -3657,12 +3754,12 @@ export function createControlPlaneApi(options) {
3657
3754
  await bind({ acknowledge, snapshot: true });
3658
3755
  }
3659
3756
  await reassert();
3660
- return c.json(await admin.getScopeRecord(actor, tenantId, scopeId));
3757
+ return c.json(await c.var.admin.getScopeRecord(actor, tenantId, scopeId));
3661
3758
  }
3662
3759
  await carry();
3663
3760
  await bind({ acknowledge });
3664
3761
  await reassert();
3665
- return c.json(await admin.getScopeRecord(actor, tenantId, scopeId));
3762
+ return c.json(await c.var.admin.getScopeRecord(actor, tenantId, scopeId));
3666
3763
  });
3667
3764
  // -- instances (K-31) -------------------------------------------------------
3668
3765
  // The one place this surface calls OUT rather than sitting over `HostAdmin`, and
@@ -3679,7 +3776,7 @@ export function createControlPlaneApi(options) {
3679
3776
  // The install kill-switch: a blocked vertical takes no NEW instances, for anyone
3680
3777
  // including its owner. Refused before deployment resolution so the answer is
3681
3778
  // uniform whether or not anything is deployed. Existing scopes keep serving.
3682
- const registered = (await admin.listVerticals(c.get('actor'))).find((v) => v.slug === slug);
3779
+ const registered = (await c.var.admin.listVerticals(c.get('actor'))).find((v) => v.slug === slug);
3683
3780
  if (registered?.installsBlocked) {
3684
3781
  return c.json({ error: `new installs of vertical '${slug}' are blocked` }, 403);
3685
3782
  }
@@ -3696,11 +3793,11 @@ export function createControlPlaneApi(options) {
3696
3793
  // (#304). A vertical predating the field ignores it; grant/revoke AFTER provision ride
3697
3794
  // a re-provision (this endpoint is idempotent, K-31), meanwhile expiry still enforces
3698
3795
  // locally because the projected row carries it.
3699
- const entitlements = await admin.listEntitlements(c.get('actor'), input.tenantId);
3796
+ const entitlements = await c.var.admin.listEntitlements(c.get('actor'), input.tenantId);
3700
3797
  // #406: identity links ride the same authoritative gather — the vertical projects them
3701
3798
  // so its auth adapter resolves logins from local storage, and offboarding becomes an
3702
3799
  // unlink + re-deliver instead of a source edit + deploy.
3703
- const identityLinks = (await admin.listIdentityLinks(c.get('actor'), input.tenantId)).map(({ tenantId: _tenantId, ...link }) => link);
3800
+ const identityLinks = (await c.var.admin.listIdentityLinks(c.get('actor'), input.tenantId)).map(({ tenantId: _tenantId, ...link }) => link);
3704
3801
  // #592: connection grants too — tenant-wide rows materialize for the NEW scope, so an
3705
3802
  // install provisioned after `grantToConnection` holds the same `connection:<id>` tuple
3706
3803
  // as one provisioned before it, and the connector return path works without a human
@@ -3709,24 +3806,24 @@ export function createControlPlaneApi(options) {
3709
3806
  // connector has since declared another grant receives it — rather than inheriting the
3710
3807
  // gap the tenant's first install was created with.
3711
3808
  try {
3712
- await reconcileConnectionGrants({ admin, actor: c.get('actor'), declared: options.connectorGrants ?? {} }, input.tenantId, slug);
3809
+ await reconcileConnectionGrants({ admin: c.var.admin, actor: c.get('actor'), declared: options.connectorGrants ?? {} }, input.tenantId, slug);
3713
3810
  }
3714
3811
  catch {
3715
3812
  // Best-effort, exactly as on the reconcile route.
3716
3813
  }
3717
- const connectionGrants = connectionGrantsForScope(await admin.listConnectionGrants(c.get('actor'), input.tenantId), slug, input.scopeId);
3814
+ const connectionGrants = connectionGrantsForScope(await c.var.admin.listConnectionGrants(c.get('actor'), input.tenantId), slug, input.scopeId);
3718
3815
  // #687: and the public sealing keys of this tenant's live connections for the same
3719
3816
  // vertical, so the new install can seal a value TO a connector from its first
3720
3817
  // operation — before this, a scope could only receive a connector's answer, never
3721
3818
  // hand it something the spine must not carry in the clear.
3722
- const connectionKeys = await admin.connectionSealingKeys(input.tenantId, slug);
3819
+ const connectionKeys = await c.var.admin.connectionSealingKeys(input.tenantId, slug);
3723
3820
  // #301 PR-2: per-tenant relational stores the vertical DECLARED, minted here (before
3724
3821
  // the callback — the vertical migrates the store inside the K-31 ready-gate, so it
3725
3822
  // must exist and be bound first). Idempotent like the endpoint: a retried provision
3726
3823
  // re-resolves the same handles and the binding attach no-ops once present. `[]` for
3727
3824
  // every vertical that declares none — the payload is unchanged for them.
3728
3825
  const tenantStores = await collectTenantStoreHandles({
3729
- host: options.host,
3826
+ host: c.var.host,
3730
3827
  actor: c.get('actor'),
3731
3828
  slug,
3732
3829
  tenantId: input.tenantId,
@@ -3736,7 +3833,7 @@ export function createControlPlaneApi(options) {
3736
3833
  // No handle rides the provision callback (a blob store has no schema to migrate), so
3737
3834
  // the effect is purely the ledger row + the attached r2_bucket binding.
3738
3835
  await collectBlobStoreHandles({
3739
- host: options.host,
3836
+ host: c.var.host,
3740
3837
  actor: c.get('actor'),
3741
3838
  slug,
3742
3839
  tenantId: input.tenantId,
@@ -3745,7 +3842,7 @@ export function createControlPlaneApi(options) {
3745
3842
  // #1742: the recorded OFF positions ride the provision into the seat's own unit. Read
3746
3843
  // from the directory whether or not the row exists yet: a brand-new install has no
3747
3844
  // record, so nothing is carried and the body is unchanged.
3748
- const switches = await switchCarryFor(admin, c.get('actor'), { tenantId: input.tenantId, scopeId: input.scopeId });
3845
+ const switches = await switchCarryFor(c.var.admin, c.get('actor'), { tenantId: input.tenantId, scopeId: input.scopeId });
3749
3846
  try {
3750
3847
  // #424 case 2: the binding attach above races Cloudflare script-settings
3751
3848
  // propagation, so the vertical's FIRST answer can be a transient 5xx that a
@@ -3767,17 +3864,17 @@ export function createControlPlaneApi(options) {
3767
3864
  // is the right trade — a receipt written before a bind would name no version, and
3768
3865
  // one guessed from the serving pointer would claim a hook ran against code this
3769
3866
  // scope was not running.
3770
- const provisioned = await admin.getScopeRecord(c.get('actor'), input.tenantId, input.scopeId);
3867
+ const provisioned = await c.var.admin.getScopeRecord(c.get('actor'), input.tenantId, input.scopeId);
3771
3868
  // #1674: this route is idempotent (K-31), so an already-bound scope that was wiped can
3772
3869
  // come back through it, re-seated live by the deployment — and the receipt below means
3773
3870
  // no sweep follows. Re-assert the recorded OFF positions before it. Only once the
3774
3871
  // directory row exists: a brand-new install's is written after this call (see above),
3775
3872
  // has no record to re-assert, and `reassertSystemSwitches` refuses an unknown scope.
3776
3873
  if (provisioned) {
3777
- await admin.reassertSystemSwitches(c.get('actor'), { tenantId: input.tenantId, scopeId: input.scopeId }, { appliedInUnit: instance.switchedOff });
3874
+ await c.var.admin.reassertSystemSwitches(c.get('actor'), { tenantId: input.tenantId, scopeId: input.scopeId }, { appliedInUnit: instance.switchedOff });
3778
3875
  }
3779
3876
  if (provisioned?.verticalVersionId) {
3780
- await admin.markScopeProvisioned(c.get('actor'), input.tenantId, input.scopeId, provisioned.verticalVersionId);
3877
+ await c.var.admin.markScopeProvisioned(c.get('actor'), input.tenantId, input.scopeId, provisioned.verticalVersionId);
3781
3878
  }
3782
3879
  return c.json(instance, 201);
3783
3880
  }
@@ -3816,13 +3913,13 @@ export function createControlPlaneApi(options) {
3816
3913
  // `listVerticals` returns every row (the adapter does not narrow); the transport is
3817
3914
  // where per-principal filtering happens — here and on GET /verticals.
3818
3915
  const ownerOf = async (actor, slug) => {
3819
- const v = (await admin.listVerticals(actor)).find((x) => x.slug === slug);
3916
+ const v = (await host.admin.listVerticals(actor)).find((x) => x.slug === slug);
3820
3917
  return v ? v.ownerTenant : undefined;
3821
3918
  };
3822
3919
  // The full registry row, for the checks that need more than the owner — whether the
3823
3920
  // vertical is PRIVATE (owned + not listed), which is what scopes a builder's prod
3824
3921
  // self-serve below.
3825
- const verticalOf = async (actor, slug) => (await admin.listVerticals(actor)).find((x) => x.slug === slug);
3922
+ const verticalOf = async (actor, slug) => (await host.admin.listVerticals(actor)).find((x) => x.slug === slug);
3826
3923
  /**
3827
3924
  * "This principal is confined, and this vertical is not its tenant's." The one
3828
3925
  * predicate every slug-addressed route asks.
@@ -3846,6 +3943,22 @@ export function createControlPlaneApi(options) {
3846
3943
  * vertical stays owner-only, and so do the reads narrowed on purpose (migration SQL,
3847
3944
  * the prod history).
3848
3945
  */
3946
+ /**
3947
+ * Whether a CONFINED principal may grant its own tenant `key` (#977): some vertical it
3948
+ * can read — its own, or a listed one — declares the key, or is the vertical whose
3949
+ * bare slug it is. The same derivation the dashboard's install makes
3950
+ * (`installEntitlements`), checked here so a credential cannot grant what no install
3951
+ * would. Read through the configured host: a check audits nothing.
3952
+ */
3953
+ const tenantGrantable = async (p, key) => {
3954
+ const pin = confinedTenant(p);
3955
+ if (pin === null)
3956
+ return true;
3957
+ const visible = (await host.admin.listVerticals(p.actor)).filter((v) => v.ownerTenant === pin || v.listed);
3958
+ return visible.some((v) => v.entitlements && v.entitlements.length > 0
3959
+ ? v.entitlements.includes(key)
3960
+ : (v.slug.split('/').pop() || v.slug) === key);
3961
+ };
3849
3962
  const notReadable = async (p, slug) => {
3850
3963
  const pin = confinedTenant(p);
3851
3964
  if (pin === null)
@@ -3883,7 +3996,7 @@ export function createControlPlaneApi(options) {
3883
3996
  if (!pin)
3884
3997
  return raw;
3885
3998
  const actor = c.get('actor');
3886
- const workspace = (await admin.listTenants(actor)).find((t) => t.slug === pin || t.id === pin);
3999
+ const workspace = (await c.var.admin.listTenants(actor)).find((t) => t.slug === pin || t.id === pin);
3887
4000
  if (!workspace || raw.startsWith(`${workspace.slug}/`))
3888
4001
  return raw;
3889
4002
  if ((await ownerOf(actor, raw)) === workspace.id)
@@ -3893,7 +4006,7 @@ export function createControlPlaneApi(options) {
3893
4006
  };
3894
4007
  app.get('/verticals', async (c) => {
3895
4008
  const page = pageParams(c);
3896
- const all = await admin.listVerticals(c.get('actor'));
4009
+ const all = await c.var.admin.listVerticals(c.get('actor'));
3897
4010
  const p = c.get('principal');
3898
4011
  // A builder sees only what it owns; staff see the whole registry. The narrowing
3899
4012
  // runs above the adapter, so the page is sliced here — over the narrowed list.
@@ -3932,12 +4045,12 @@ export function createControlPlaneApi(options) {
3932
4045
  * it that can drift when a third ref scheme appears.
3933
4046
  */
3934
4047
  const tenantServiceRefs = async (actor, tenantId, slug) => {
3935
- const all = (await admin.listVerticals(actor)).filter((v) => v.ownerTenant === tenantId);
4048
+ const all = (await host.admin.listVerticals(actor)).filter((v) => v.ownerTenant === tenantId);
3936
4049
  const considered = slug === undefined ? all : all.filter((v) => v.slug === slug);
3937
4050
  const verticalsTruncated = considered.length > MAX_SERVICE_REF_VERTICALS;
3938
4051
  const entries = [];
3939
4052
  for (const v of considered.slice(0, MAX_SERVICE_REF_VERTICALS)) {
3940
- const versions = await admin.listVersions(actor, v.slug);
4053
+ const versions = await host.admin.listVersions(actor, v.slug);
3941
4054
  const labelOf = new Map(versions.map((ver) => [ver.id, ver.version]));
3942
4055
  // The stable serving script — where real traffic lands. Its version is the
3943
4056
  // registry row's own servingVersionId: authoritative, not the scope-derived
@@ -4016,10 +4129,10 @@ export function createControlPlaneApi(options) {
4016
4129
  return c.json({ error: 'forbidden' }, 403);
4017
4130
  input.ownerTenant = p.tenantId;
4018
4131
  }
4019
- await admin.registerVertical(c.get('actor'), input);
4132
+ await c.var.admin.registerVertical(c.get('actor'), input);
4020
4133
  // Idempotent on the slug (a conflicting re-register throws below the seam), so
4021
4134
  // read back rather than echo the request.
4022
- const registered = (await admin.listVerticals(c.get('actor'))).find((v) => v.slug === input.slug);
4135
+ const registered = (await c.var.admin.listVerticals(c.get('actor'))).find((v) => v.slug === input.slug);
4023
4136
  return c.json(registered, 201);
4024
4137
  });
4025
4138
  app.get('/verticals/:slug/versions', async (c) => {
@@ -4031,7 +4144,7 @@ export function createControlPlaneApi(options) {
4031
4144
  return c.json({ error: 'not found' }, 404);
4032
4145
  }
4033
4146
  const page = pageParams(c);
4034
- const entries = await admin.listVersions(c.get('actor'), slug, page);
4147
+ const entries = await c.var.admin.listVersions(c.get('actor'), slug, page);
4035
4148
  return c.json(pageOf(entries, page.limit, (v) => v.id));
4036
4149
  });
4037
4150
  app.post('/verticals/:slug/versions', async (c) => {
@@ -4077,8 +4190,8 @@ export function createControlPlaneApi(options) {
4077
4190
  return refuse(registry.error.issues[0], ['registry']);
4078
4191
  }
4079
4192
  }
4080
- await admin.publishVersion(c.get('actor'), input);
4081
- const version = await admin.getVersion(c.get('actor'), input.id, slug);
4193
+ await c.var.admin.publishVersion(c.get('actor'), input);
4194
+ const version = await c.var.admin.getVersion(c.get('actor'), input.id, slug);
4082
4195
  return c.json(version, 201);
4083
4196
  });
4084
4197
  // The declared permission registry (D-39, #336) that ships inside one version's manifest:
@@ -4093,7 +4206,7 @@ export function createControlPlaneApi(options) {
4093
4206
  if (await notReadable(p, slug)) {
4094
4207
  return c.json({ error: 'not found' }, 404);
4095
4208
  }
4096
- const json = await admin.versionManifest(c.get('actor'), slug, c.req.param('id'));
4209
+ const json = await c.var.admin.versionManifest(c.get('actor'), slug, c.req.param('id'));
4097
4210
  const registry = json ? (storedDeployManifest.parse(JSON.parse(json)).registry ?? null) : null;
4098
4211
  return c.json({ registry });
4099
4212
  });
@@ -4112,7 +4225,7 @@ export function createControlPlaneApi(options) {
4112
4225
  return c.json({ error: 'not found' }, 404);
4113
4226
  }
4114
4227
  // `versionMigrations` refuses a version of another vertical, so `base` cannot reach one.
4115
- const migrationsOf = (id) => admin.versionMigrations(c.get('actor'), slug, id);
4228
+ const migrationsOf = (id) => c.var.admin.versionMigrations(c.get('actor'), slug, id);
4116
4229
  const base = c.req.query('base');
4117
4230
  const [incoming, baseline] = await Promise.all([
4118
4231
  migrationsOf(c.req.param('id')),
@@ -4133,7 +4246,7 @@ export function createControlPlaneApi(options) {
4133
4246
  if (await notReadable(p, slug)) {
4134
4247
  return c.json({ error: 'not found' }, 404);
4135
4248
  }
4136
- const json = await admin.versionManifest(c.get('actor'), slug, c.req.param('id'));
4249
+ const json = await c.var.admin.versionManifest(c.get('actor'), slug, c.req.param('id'));
4137
4250
  const parsed = json ? storedDeployManifest.parse(JSON.parse(json)) : null;
4138
4251
  // #1232: freshness rides the same read — the manifest is already in hand, and a
4139
4252
  // sibling route would cost a second full parse for a field sitting beside this one.
@@ -4156,7 +4269,7 @@ export function createControlPlaneApi(options) {
4156
4269
  if (await notReadable(p, slug)) {
4157
4270
  return c.json({ error: 'not found' }, 404);
4158
4271
  }
4159
- const json = await admin.versionManifest(c.get('actor'), slug, c.req.param('id'));
4272
+ const json = await c.var.admin.versionManifest(c.get('actor'), slug, c.req.param('id'));
4160
4273
  const parsed = json ? storedDeployManifest.parse(JSON.parse(json)) : null;
4161
4274
  return c.json({
4162
4275
  declaredEvents: parsed?.declaredEvents ?? null,
@@ -4184,7 +4297,7 @@ export function createControlPlaneApi(options) {
4184
4297
  if (await notReadable(p, slug)) {
4185
4298
  return c.json({ error: 'not found' }, 404);
4186
4299
  }
4187
- const json = await admin.versionManifest(c.get('actor'), slug, c.req.param('id'));
4300
+ const json = await c.var.admin.versionManifest(c.get('actor'), slug, c.req.param('id'));
4188
4301
  const parsed = json ? storedDeployManifest.parse(JSON.parse(json)) : null;
4189
4302
  // #1321 rides this read for the reason the freshness field rides the schedules
4190
4303
  // one: the manifest is already parsed, and the field sits beside the model the
@@ -4203,22 +4316,22 @@ export function createControlPlaneApi(options) {
4203
4316
  if (await notReadable(p, slug)) {
4204
4317
  return c.json({ error: 'not found' }, 404);
4205
4318
  }
4206
- const json = await admin.versionManifest(c.get('actor'), slug, c.req.param('id'));
4319
+ const json = await c.var.admin.versionManifest(c.get('actor'), slug, c.req.param('id'));
4207
4320
  const assets = json ? (storedDeployManifest.parse(JSON.parse(json)).assets ?? null) : null;
4208
4321
  return c.json({ assets });
4209
4322
  });
4210
4323
  app.post('/verticals/:slug/versions/:id/admit', async (c) => {
4211
4324
  const slug = c.req.param('slug');
4212
4325
  const id = c.req.param('id');
4213
- await admin.admitVersion(c.get('actor'), id);
4214
- return c.json(await admin.getVersion(c.get('actor'), id, slug));
4326
+ await c.var.admin.admitVersion(c.get('actor'), id);
4327
+ return c.json(await c.var.admin.getVersion(c.get('actor'), id, slug));
4215
4328
  });
4216
4329
  app.post('/verticals/:slug/versions/:id/reject', async (c) => {
4217
4330
  const slug = c.req.param('slug');
4218
4331
  const id = c.req.param('id');
4219
4332
  const { note } = rejectVersionBody.parse(await c.req.json());
4220
- await admin.rejectVersion(c.get('actor'), id, note);
4221
- return c.json(await admin.getVersion(c.get('actor'), id, slug));
4333
+ await c.var.admin.rejectVersion(c.get('actor'), id, note);
4334
+ return c.json(await c.var.admin.getVersion(c.get('actor'), id, slug));
4222
4335
  });
4223
4336
  // A builder REQUESTS publication of a vertical it owns (marketplace-publish.md §5) — any owner
4224
4337
  // may ask; the staff `listing` flip below is the review gate. Owner-checked (like promote).
@@ -4228,7 +4341,7 @@ export function createControlPlaneApi(options) {
4228
4341
  if (await notOwned(p, slug)) {
4229
4342
  return c.json({ error: 'not found' }, 404);
4230
4343
  }
4231
- await admin.requestPublish(c.get('actor'), slug);
4344
+ await c.var.admin.requestPublish(c.get('actor'), slug);
4232
4345
  return c.json({ slug, requested: true });
4233
4346
  });
4234
4347
  // Publish/unpublish a vertical to the PUBLIC marketplace (marketplace-publish.md §5). Staff
@@ -4237,7 +4350,7 @@ export function createControlPlaneApi(options) {
4237
4350
  app.post('/verticals/:slug/listing', async (c) => {
4238
4351
  const slug = c.req.param('slug');
4239
4352
  const { listed } = z.object({ listed: z.boolean() }).parse(await c.req.json());
4240
- await admin.setVerticalListed(c.get('actor'), slug, listed);
4353
+ await c.var.admin.setVerticalListed(c.get('actor'), slug, listed);
4241
4354
  return c.json({ slug, listed });
4242
4355
  });
4243
4356
  // The install kill-switch (staff-only — not in BUILDER_ROUTES, so a builder is
@@ -4246,7 +4359,7 @@ export function createControlPlaneApi(options) {
4246
4359
  app.post('/verticals/:slug/install-block', async (c) => {
4247
4360
  const slug = c.req.param('slug');
4248
4361
  const { blocked } = z.object({ blocked: z.boolean() }).parse(await c.req.json());
4249
- await admin.setVerticalInstallsBlocked(c.get('actor'), slug, blocked);
4362
+ await c.var.admin.setVerticalInstallsBlocked(c.get('actor'), slug, blocked);
4250
4363
  return c.json({ slug, installsBlocked: blocked });
4251
4364
  });
4252
4365
  // Grant/revoke the TENANT-PROVISIONER capability (#412) — whether this vertical's scopes
@@ -4256,7 +4369,7 @@ export function createControlPlaneApi(options) {
4256
4369
  app.post('/verticals/:slug/tenant-provisioner', async (c) => {
4257
4370
  const slug = c.req.param('slug');
4258
4371
  const { granted } = z.object({ granted: z.boolean() }).parse(await c.req.json());
4259
- await admin.setVerticalTenantProvisioner(c.get('actor'), slug, granted);
4372
+ await c.var.admin.setVerticalTenantProvisioner(c.get('actor'), slug, granted);
4260
4373
  return c.json({ slug, tenantProvisioner: granted });
4261
4374
  });
4262
4375
  // Grant/revoke the EMAIL-SENDER capability (#303) — whether this vertical's scopes may POST to
@@ -4266,7 +4379,7 @@ export function createControlPlaneApi(options) {
4266
4379
  app.post('/verticals/:slug/email-sender', async (c) => {
4267
4380
  const slug = c.req.param('slug');
4268
4381
  const { granted } = z.object({ granted: z.boolean() }).parse(await c.req.json());
4269
- await admin.setVerticalEmailSender(c.get('actor'), slug, granted);
4382
+ await c.var.admin.setVerticalEmailSender(c.get('actor'), slug, granted);
4270
4383
  return c.json({ slug, emailSender: granted });
4271
4384
  });
4272
4385
  // Delete a vertical + its versions and channels. Refused below the seam while any
@@ -4283,7 +4396,7 @@ export function createControlPlaneApi(options) {
4283
4396
  // existence-hiding reflex the registry's other per-slug reads use (K-3).
4284
4397
  if (await notOwned(c.get('principal'), slug))
4285
4398
  return c.json({ error: 'not found' }, 404);
4286
- await admin.deleteVertical(c.get('actor'), slug);
4399
+ await c.var.admin.deleteVertical(c.get('actor'), slug);
4287
4400
  return c.json({ slug, deleted: true });
4288
4401
  });
4289
4402
  app.get('/verticals/:slug/channels', async (c) => {
@@ -4293,7 +4406,7 @@ export function createControlPlaneApi(options) {
4293
4406
  return c.json({ error: 'not found' }, 404);
4294
4407
  }
4295
4408
  const page = pageParams(c);
4296
- const entries = await admin.listChannels(c.get('actor'), slug, page);
4409
+ const entries = await c.var.admin.listChannels(c.get('actor'), slug, page);
4297
4410
  return c.json(pageOf(entries, page.limit, (ch) => ch.channel));
4298
4411
  });
4299
4412
  // The promotion timeline (newest first) — what a rollback UI picks a target from.
@@ -4311,7 +4424,7 @@ export function createControlPlaneApi(options) {
4311
4424
  return c.json({ error: 'not found' }, 404);
4312
4425
  }
4313
4426
  const page = pageParams(c);
4314
- const entries = await admin.listChannelHistory(c.get('actor'), slug, channel, page);
4427
+ const entries = await c.var.admin.listChannelHistory(c.get('actor'), slug, channel, page);
4315
4428
  return c.json(pageOf(entries, page.limit, (e) => e.id));
4316
4429
  });
4317
4430
  /**
@@ -4326,20 +4439,20 @@ export function createControlPlaneApi(options) {
4326
4439
  * the caller surfaces it — and leaves `servingVersionId` trailing the channel:
4327
4440
  * visible, and retried by promoting again.
4328
4441
  */
4329
- const serveVersionInPlace = async (actor, slug, versionId) => {
4442
+ const serveVersionInPlace = async (admin, actor, slug, versionId) => {
4330
4443
  if (!options.deployVertical || !options.fetchVerticalModules)
4331
4444
  return; // not configured: pre-#286 behavior
4332
- const version = await admin.getVersion(actor, versionId, slug);
4445
+ const version = await host.admin.getVersion(actor, versionId, slug);
4333
4446
  if (!version?.deploymentRef) {
4334
4447
  throw new ControlPlaneError(502, `version ${versionId} has no archive script to serve from`);
4335
4448
  }
4336
4449
  const archiveRef = version.deploymentRef;
4337
- const manifestJson = await admin.versionManifest(actor, slug, versionId);
4450
+ const manifestJson = await host.admin.versionManifest(actor, slug, versionId);
4338
4451
  if (!manifestJson) {
4339
4452
  throw new ControlPlaneError(502, `version ${versionId} retained no manifest — pushed pre-#286; push it again to serve in place`);
4340
4453
  }
4341
4454
  const manifest = storedDeployManifest.parse(JSON.parse(manifestJson));
4342
- const serving = await admin.verticalServing(actor, slug);
4455
+ const serving = await host.admin.verticalServing(actor, slug);
4343
4456
  const ref = serving?.ref ?? stableDeploymentRefFor(slug);
4344
4457
  const modules = await options.fetchVerticalModules(version.deploymentRef);
4345
4458
  // #301 PR-2: every serving upload re-derives the per-tenant store D1 bindings from
@@ -4349,11 +4462,11 @@ export function createControlPlaneApi(options) {
4349
4462
  // tenant's store. Platform-granted after the §4 sandbox check, like injectSecrets:
4350
4463
  // the builder never declared these and never named the ids.
4351
4464
  const storeBindings = [
4352
- ...tenantStoreBindings(await admin.listTenantStores(actor, { vertical: slug })),
4465
+ ...tenantStoreBindings(await host.admin.listTenantStores(actor, { vertical: slug })),
4353
4466
  // #473: the per-tenant blob-store r2_bucket bindings, re-derived from the ledger on
4354
4467
  // every serving upload for the same reason — an upload replaces the script's binding
4355
4468
  // set, so a re-deploy must never drop a tenant's attachment bucket.
4356
- ...blobStoreBindings(await admin.listBlobStores(actor, { vertical: slug })),
4469
+ ...blobStoreBindings(await host.admin.listBlobStores(actor, { vertical: slug })),
4357
4470
  ].map((b) => b.type === 'd1'
4358
4471
  ? { type: 'd1', name: b.name, id: b.id }
4359
4472
  : { type: 'r2_bucket', name: b.name, bucket_name: b.bucketName });
@@ -4438,7 +4551,7 @@ export function createControlPlaneApi(options) {
4438
4551
  try {
4439
4552
  // Every tenant with an install that can still serve. Archived/reaped scopes are
4440
4553
  // excluded — minting a bucket for storage that is gone is pure waste.
4441
- const scopes = await admin.listScopes(actor, {
4554
+ const scopes = await c.var.admin.listScopes(actor, {
4442
4555
  vertical: slug,
4443
4556
  status: ['provisioning', 'active', 'suspended'],
4444
4557
  });
@@ -4446,7 +4559,7 @@ export function createControlPlaneApi(options) {
4446
4559
  if (tenantIds.length === 0)
4447
4560
  return { minted: [] };
4448
4561
  const minted = await backfillDeclaredStores({
4449
- host: options.host,
4562
+ host: c.var.host,
4450
4563
  actor,
4451
4564
  slug,
4452
4565
  tenantIds,
@@ -4474,22 +4587,22 @@ export function createControlPlaneApi(options) {
4474
4587
  * costs a read, a missing one a silent stall.
4475
4588
  */
4476
4589
  const promoteImpactOf = async (actor, slug, channel, versionId) => {
4477
- const gate = await admin.promotionImpact(actor, slug, channel, versionId);
4590
+ const gate = await host.admin.promotionImpact(actor, slug, channel, versionId);
4478
4591
  if (channel !== 'prod')
4479
4592
  return gate;
4480
4593
  const v = await verticalOf(actor, slug);
4481
4594
  if (!v || v.ownerTenant === null || v.listed)
4482
4595
  return gate;
4483
- const incoming = await admin.getVersion(actor, versionId, slug);
4596
+ const incoming = await host.admin.getVersion(actor, versionId, slug);
4484
4597
  if (!incoming?.deploymentRef || incoming.admission !== 'admitted')
4485
4598
  return gate;
4486
- const lagging = (await admin.listScopes(actor, { tenantId: v.ownerTenant, vertical: slug, status: ['active'] })).filter((s) => !s.forkedFrom && s.kind !== 'preview' && !s.servingRef && s.verticalVersionId !== versionId);
4599
+ const lagging = (await host.admin.listScopes(actor, { tenantId: v.ownerTenant, vertical: slug, status: ['active'] })).filter((s) => !s.forkedFrom && s.kind !== 'preview' && !s.servingRef && s.verticalVersionId !== versionId);
4487
4600
  const seen = new Set(gate.map((b) => `${b.scopeId}|${b.type}`));
4488
4601
  const out = [...gate];
4489
4602
  for (const s of lagging) {
4490
4603
  // After the adopt it runs the promoted version, served in place: that pointer move, from
4491
4604
  // what it runs now, is the move to judge.
4492
- for (const b of await admin.bindingImpact(actor, s.tenantId, s.id, versionId)) {
4605
+ for (const b of await host.admin.bindingImpact(actor, s.tenantId, s.id, versionId)) {
4493
4606
  const key = `${b.scopeId}|${b.type}`;
4494
4607
  if (!seen.has(key))
4495
4608
  (seen.add(key), out.push(b));
@@ -4575,7 +4688,7 @@ export function createControlPlaneApi(options) {
4575
4688
  // and refuses a non-admitted version. Both are enforced below the seam and
4576
4689
  // surface as a 4xx through mapError, not a 500.
4577
4690
  try {
4578
- await admin.promoteVersion(c.get('actor'), slug, channel, versionId, acknowledge);
4691
+ await c.var.admin.promoteVersion(c.get('actor'), slug, channel, versionId, acknowledge);
4579
4692
  }
4580
4693
  catch (err) {
4581
4694
  if (!isExportBreakRefusal(err))
@@ -4596,7 +4709,7 @@ export function createControlPlaneApi(options) {
4596
4709
  let adopted = { refused: [] };
4597
4710
  if (channel === 'prod') {
4598
4711
  try {
4599
- await serveVersionInPlace(c.get('actor'), slug, versionId);
4712
+ await serveVersionInPlace(c.var.admin, c.get('actor'), slug, versionId);
4600
4713
  // Adopt any still-legacy owned scope onto the serving script and advance every
4601
4714
  // owned scope's version — the rebind the host cascade delegated to us for a
4602
4715
  // dispatch-backed vertical (#321), in the correct order (serve → adopt → rebind),
@@ -4643,7 +4756,7 @@ export function createControlPlaneApi(options) {
4643
4756
  : {}),
4644
4757
  }, 409);
4645
4758
  }
4646
- const promoted = (await admin.listChannels(c.get('actor'), slug)).find((ch) => ch.channel === channel);
4759
+ const promoted = (await c.var.admin.listChannels(c.get('actor'), slug)).find((ch) => ch.channel === channel);
4647
4760
  // The store backfill rides the response only when it has something to say (#825), so
4648
4761
  // nothing changes for the overwhelming majority of promotes — and when a store WAS
4649
4762
  // minted, or could not be, the builder reads it at the terminal rather than finding out
@@ -4733,7 +4846,7 @@ export function createControlPlaneApi(options) {
4733
4846
  ownerTenant = p.tenantId;
4734
4847
  }
4735
4848
  else if (pin) {
4736
- const workspace = (await admin.listTenants(c.get('actor'))).find((t) => t.slug === pin || t.id === pin);
4849
+ const workspace = (await c.var.admin.listTenants(c.get('actor'))).find((t) => t.slug === pin || t.id === pin);
4737
4850
  if (!workspace)
4738
4851
  return c.json({ error: `unknown workspace '${pin}'` }, 404);
4739
4852
  // Back-compat: a BARE slug already registered as the pinned tenant's (a staff
@@ -4772,7 +4885,7 @@ export function createControlPlaneApi(options) {
4772
4885
  // scoping is also what keeps existence-hiding intact — a builder is never told about
4773
4886
  // a foreign private slug.
4774
4887
  if (form.get('allowFork') !== '1') {
4775
- const registry = await admin.listVerticals(c.get('actor'));
4888
+ const registry = await c.var.admin.listVerticals(c.get('actor'));
4776
4889
  const tail = slug.split('/').pop();
4777
4890
  const siblings = registry.some((v) => v.slug === slug)
4778
4891
  ? []
@@ -4920,7 +5033,7 @@ export function createControlPlaneApi(options) {
4920
5033
  // (or admitted, for a PRIVATE vertical — the registry's self-admit rule). The owner
4921
5034
  // was resolved with the slug above: the builder's tenant, the pinned workspace, or
4922
5035
  // the preserved existing owner for an unpinned staff push.
4923
- await admin.registerVertical(c.get('actor'), {
5036
+ await c.var.admin.registerVertical(c.get('actor'), {
4924
5037
  slug,
4925
5038
  name: manifest.name ?? slug,
4926
5039
  source: 'cli',
@@ -4946,7 +5059,7 @@ export function createControlPlaneApi(options) {
4946
5059
  // hostname-binding picker, never behavior. Not part of any admission digest.
4947
5060
  ...(manifest.surfaces ? { surfaces: manifest.surfaces } : {}),
4948
5061
  });
4949
- await admin.publishVersion(c.get('actor'), {
5062
+ await c.var.admin.publishVersion(c.get('actor'), {
4950
5063
  id,
4951
5064
  verticalSlug: slug,
4952
5065
  version: manifest.version,
@@ -4972,14 +5085,14 @@ export function createControlPlaneApi(options) {
4972
5085
  // one push depend on every other tenant's routing rows parsing cleanly — a
4973
5086
  // malformed cert-validation blob on an unrelated domain took the whole deploy down
4974
5087
  // with a blank 500, after the version had already been published.
4975
- const bound = await admin.listHostnames(c.get('actor'), { verticalSlug: slug });
5088
+ const bound = await c.var.admin.listHostnames(c.get('actor'), { verticalSlug: slug });
4976
5089
  for (const h of bound) {
4977
5090
  if (!declared.has(h.surface)) {
4978
5091
  warnings.push(`hostname '${h.hostname}' is bound to surface '${h.surface}', which this version no longer declares`);
4979
5092
  }
4980
5093
  }
4981
5094
  }
4982
- const version = await admin.getVersion(c.get('actor'), id, slug);
5095
+ const version = await c.var.admin.getVersion(c.get('actor'), id, slug);
4983
5096
  return c.json({ ...version, ...(warnings.length ? { warnings } : {}) }, 201);
4984
5097
  });
4985
5098
  // -- where this platform runs (ops ergonomics) -----------------------------
@@ -5258,7 +5371,7 @@ export function createControlPlaneApi(options) {
5258
5371
  * nothing. `listScopes`, not `getScopeRecord`: a dashboard read must not write an access
5259
5372
  * row per page view.
5260
5373
  */
5261
- const appServices = async (actor, tenantId, scopeId, vertical) => scriptFamiliesOfScopes(await admin.listScopes(actor, { tenantId: tenantIdSchema.parse(tenantId) }), { scopeId, vertical });
5374
+ const appServices = async (actor, tenantId, scopeId, vertical) => scriptFamiliesOfScopes(await host.admin.listScopes(actor, { tenantId: tenantIdSchema.parse(tenantId) }), { scopeId, vertical });
5262
5375
  app.get('/observability/tenant-request-volume', async (c) => {
5263
5376
  if (!options.observability?.tenantRequestVolume) {
5264
5377
  return c.json({ error: 'request aggregation is not configured on this control plane' }, 501);
@@ -5359,7 +5472,7 @@ export function createControlPlaneApi(options) {
5359
5472
  .parse({ hours: c.req.query('hours'), limit: c.req.query('limit') });
5360
5473
  // Deployed versions only: a version with no `deploymentRef` has never run, so there is
5361
5474
  // nothing it could have been observed reaching.
5362
- const versions = (await admin.listVersions(c.get('actor'), slug)).filter((v) => v.deploymentRef);
5475
+ const versions = (await c.var.admin.listVersions(c.get('actor'), slug)).filter((v) => v.deploymentRef);
5363
5476
  // Cap the fan-out — each service is another backend query. Truncating the SERVICE list
5364
5477
  // is exactly the silent-drop #859 calls a defect, so it is reported, not swallowed.
5365
5478
  const MAX_SERVICES = 20;
@@ -5422,7 +5535,7 @@ export function createControlPlaneApi(options) {
5422
5535
  return c.json({ error: 'push tokens are not configured on this control plane' }, 501);
5423
5536
  }
5424
5537
  const { tenantId } = z.object({ tenantId: tenantIdSchema }).parse(await c.req.json());
5425
- const tenant = await admin.getTenant(c.get('actor'), tenantId);
5538
+ const tenant = await c.var.admin.getTenant(c.get('actor'), tenantId);
5426
5539
  if (!tenant)
5427
5540
  return c.json({ error: `unknown tenant: ${tenantId}` }, 404);
5428
5541
  const token = await mintPushToken(options.pushTokenSecret, {
@@ -5443,12 +5556,22 @@ export function createControlPlaneApi(options) {
5443
5556
  if (!options.tenantTokenSecret) {
5444
5557
  return c.json({ error: 'tenant tokens are not configured on this control plane' }, 501);
5445
5558
  }
5446
- const { tenantId } = z.object({ tenantId: tenantIdSchema }).parse(await c.req.json());
5559
+ const { tenantId, principal, impersonation } = z
5560
+ .object({
5561
+ tenantId: tenantIdSchema,
5562
+ // #977: the signed-in person the token will act for — attribution on every admin
5563
+ // row it writes, never reach. Optional: a token for background work names nobody.
5564
+ principal: principalIdSchema.optional(),
5565
+ // K-42: the staff member acting as `principal`. Meaningless without the person.
5566
+ impersonation: impersonationStamp.optional(),
5567
+ })
5568
+ .refine((b) => !b.impersonation || b.principal, { message: 'impersonation names no principal' })
5569
+ .parse(await c.req.json());
5447
5570
  // Unlike the push-token mint, this does NOT require the tenant to exist: a token
5448
5571
  // for a tenant with no directory row reaches nothing except creating that row
5449
5572
  // (`POST /tenants`, body-pinned to the same id), which is the sign-up bootstrap.
5450
5573
  // See the mint's docblock in tenant-token.ts.
5451
- const token = await mintTenantToken(options.tenantTokenSecret, { tenantId });
5574
+ const token = await mintTenantToken(options.tenantTokenSecret, { tenantId, principal, impersonation });
5452
5575
  return c.json({ token }, 201);
5453
5576
  });
5454
5577
  // -- the hostname map (§4.7, K-26) -----------------------------------------
@@ -5466,7 +5589,7 @@ export function createControlPlaneApi(options) {
5466
5589
  const p = c.get('principal');
5467
5590
  const pin = confinedTenant(p);
5468
5591
  const filter = pin ? { tenantId: pin } : {};
5469
- return (await admin.listHostnames(c.get('actor'), filter)).find((h) => h.hostname === name.toLowerCase());
5592
+ return (await c.var.admin.listHostnames(c.get('actor'), filter)).find((h) => h.hostname === name.toLowerCase());
5470
5593
  };
5471
5594
  app.get('/hostnames', async (c) => {
5472
5595
  const p = c.get('principal');
@@ -5483,7 +5606,7 @@ export function createControlPlaneApi(options) {
5483
5606
  if (hostnamePin)
5484
5607
  filter.tenantId = hostnamePin;
5485
5608
  const page = pageParams(c);
5486
- const entries = await admin.listHostnames(c.get('actor'), { ...filter, ...page });
5609
+ const entries = await c.var.admin.listHostnames(c.get('actor'), { ...filter, ...page });
5487
5610
  return c.json(pageOf(entries, page.limit, (h) => h.hostname));
5488
5611
  });
5489
5612
  // Which base domains ride the platform wildcard cert (go straight to `active`);
@@ -5501,7 +5624,7 @@ export function createControlPlaneApi(options) {
5501
5624
  * Failures are swallowed to a `failed`/`pending` note rather than thrown: a bind that
5502
5625
  * recorded the row must still return 201, and the reconcile sweep retries.
5503
5626
  */
5504
- const runIssuance = async (actor, row) => {
5627
+ const runIssuance = async (admin, actor, row) => {
5505
5628
  if (!custom(row.hostname)) {
5506
5629
  // Platform mint: rides *.<base> — no per-hostname CF object, immediately servable.
5507
5630
  // A mint carrying issuance relics (a CF id, publish-these-records rows) was born
@@ -5549,7 +5672,7 @@ export function createControlPlaneApi(options) {
5549
5672
  });
5550
5673
  }
5551
5674
  };
5552
- const hostnameRow = async (c, name) => (await admin.listHostnames(c.get('actor'), {})).find((h) => h.hostname === name.toLowerCase());
5675
+ const hostnameRow = async (c, name) => (await c.var.admin.listHostnames(c.get('actor'), {})).find((h) => h.hostname === name.toLowerCase());
5553
5676
  app.post('/hostnames', async (c) => {
5554
5677
  const p = c.get('principal');
5555
5678
  const input = bindHostnameBody.parse(await c.req.json());
@@ -5578,13 +5701,13 @@ export function createControlPlaneApi(options) {
5578
5701
  if (bad)
5579
5702
  return c.json({ error: bad }, 422);
5580
5703
  }
5581
- await admin.bindHostname(c.get('actor'), input);
5582
- const bound = (await admin.listHostnames(c.get('actor'), { scopeId: input.scopeId })).find((h) => h.hostname === input.hostname);
5704
+ await c.var.admin.bindHostname(c.get('actor'), input);
5705
+ const bound = (await c.var.admin.listHostnames(c.get('actor'), { scopeId: input.scopeId })).find((h) => h.hostname === input.hostname);
5583
5706
  if (bound)
5584
- await runIssuance(c.get('actor'), bound);
5707
+ await runIssuance(c.var.admin, c.get('actor'), bound);
5585
5708
  // Re-read so the response carries the post-issuance status + DNS records the caller
5586
5709
  // (dashboard / CLI) renders — a custom bind comes back `verifying` with records.
5587
- const out = (await admin.listHostnames(c.get('actor'), { scopeId: input.scopeId })).find((h) => h.hostname === input.hostname);
5710
+ const out = (await c.var.admin.listHostnames(c.get('actor'), { scopeId: input.scopeId })).find((h) => h.hostname === input.hostname);
5588
5711
  return c.json(out ?? bound, 201);
5589
5712
  });
5590
5713
  // Re-poll a custom hostname's issuance ("check again" in the dashboard). Tenant-narrowed
@@ -5598,7 +5721,7 @@ export function createControlPlaneApi(options) {
5598
5721
  const row = await hostnameRow(c, name);
5599
5722
  if (!row)
5600
5723
  return c.json({ error: `unknown hostname: ${name.toLowerCase()}` }, 404);
5601
- await runIssuance(c.get('actor'), row);
5724
+ await runIssuance(c.var.admin, c.get('actor'), row);
5602
5725
  return c.json(await hostnameRow(c, name));
5603
5726
  });
5604
5727
  app.patch('/hostnames/:hostname/status', async (c) => {
@@ -5609,8 +5732,8 @@ export function createControlPlaneApi(options) {
5609
5732
  if (confinedTenant(c.get('principal')) !== null && !(await tenantHostname(c, name))) {
5610
5733
  return c.json({ error: `unknown hostname: ${name.toLowerCase()}` }, 404);
5611
5734
  }
5612
- await admin.setHostnameStatus(c.get('actor'), name, status, note);
5613
- const row = (await admin.listHostnames(c.get('actor'), {})).find((h) => h.hostname === name.toLowerCase());
5735
+ await c.var.admin.setHostnameStatus(c.get('actor'), name, status, note);
5736
+ const row = (await c.var.admin.listHostnames(c.get('actor'), {})).find((h) => h.hostname === name.toLowerCase());
5614
5737
  return c.json(row);
5615
5738
  });
5616
5739
  // Unbind (hard-delete) a hostname row — what the orphan cleanup uses on rows
@@ -5631,7 +5754,7 @@ export function createControlPlaneApi(options) {
5631
5754
  if (row?.customHostnameId && options.provisionHostname) {
5632
5755
  await options.provisionHostname.remove(row.customHostnameId).catch(() => { });
5633
5756
  }
5634
- await admin.unbindHostname(c.get('actor'), name);
5757
+ await c.var.admin.unbindHostname(c.get('actor'), name);
5635
5758
  return c.json({ deleted: name.toLowerCase() });
5636
5759
  });
5637
5760
  // -- per-PR previews (preview-and-snapshots.md §2/§9) ----------------------
@@ -5677,23 +5800,23 @@ export function createControlPlaneApi(options) {
5677
5800
  const vertical = await verticalForScope(c, preview);
5678
5801
  if (vertical)
5679
5802
  await vertical.deleteScope({ scopeId: preview.id });
5680
- await options.host.deleteSnapshot(c.get('actor'), preview.tenantId, preview.id);
5803
+ await c.var.host.deleteSnapshot(c.get('actor'), preview.tenantId, preview.id);
5681
5804
  };
5682
5805
  /** The preview-login seam (#1704, `preview-auth.ts`): the directory, and each issuer's deployment. */
5683
5806
  const previewAuthDeps = (c) => ({
5684
- admin,
5807
+ admin: c.var.admin,
5685
5808
  actor: c.get('actor'),
5686
5809
  issuerClient: (scope) => verticalForScope(c, scope),
5687
5810
  });
5688
5811
  /** Given a base hostname `<label>.<domain>`, mint (or find) the preview's `--<tag>`
5689
5812
  * hostname `<label>--<tag>.<domain>` bound to `previewId`. Non-canonical, so it never
5690
5813
  * demotes the prod surface. Shared by the fork and clean-room paths. */
5691
- const bindPreviewHostname = async (actor, baseHostname, tenantId, previewId, tag, surface) => {
5814
+ const bindPreviewHostname = async (admin, actor, baseHostname, tenantId, previewId, tag, surface) => {
5692
5815
  const parsed = parseHostname(baseHostname);
5693
5816
  if (!parsed)
5694
5817
  throw new ControlPlaneError(400, `'${baseHostname}' is not a hostname a preview can be minted beside`);
5695
5818
  const hostname = withLabel(parsed, `${parsed.label}${RESERVED_LABEL_SEPARATOR}${tag}`);
5696
- const existing = (await admin.listHostnames(actor, { scopeId: previewId })).find((h) => h.hostname === hostname);
5819
+ const existing = (await host.admin.listHostnames(actor, { scopeId: previewId })).find((h) => h.hostname === hostname);
5697
5820
  if (!existing) {
5698
5821
  await admin.bindHostname(actor, {
5699
5822
  hostname,
@@ -5704,9 +5827,9 @@ export function createControlPlaneApi(options) {
5704
5827
  canonical: false,
5705
5828
  });
5706
5829
  }
5707
- const bound = (await admin.listHostnames(actor, { scopeId: previewId })).find((h) => h.hostname === hostname);
5830
+ const bound = (await host.admin.listHostnames(actor, { scopeId: previewId })).find((h) => h.hostname === hostname);
5708
5831
  if (bound)
5709
- await runIssuance(actor, bound); // platform mint rides the wildcard cert → active
5832
+ await runIssuance(admin, actor, bound); // platform mint rides the wildcard cert → active
5710
5833
  return hostname;
5711
5834
  };
5712
5835
  /** The base hostname a preview's `--<tag>` URL is derived from. A FORK inherits the
@@ -5715,7 +5838,7 @@ export function createControlPlaneApi(options) {
5715
5838
  * the same scheme provisioning mints (`callout-sesamy.global.substrat.run`). */
5716
5839
  const previewBaseHostname = async (actor, source, tenantId, slug, surface) => {
5717
5840
  if (source) {
5718
- const own = await admin.listHostnames(actor, { scopeId: source.id });
5841
+ const own = await host.admin.listHostnames(actor, { scopeId: source.id });
5719
5842
  const src = own.find((h) => h.canonical && h.surface === surface) ??
5720
5843
  own.find((h) => h.canonical) ??
5721
5844
  own[0];
@@ -5737,7 +5860,7 @@ export function createControlPlaneApi(options) {
5737
5860
  // bare apex and stranded clean-room previews on a hostname that never resolves.
5738
5861
  const baseDomain = [...platformBaseDomains].sort((a, b) => a.length - b.length)[0];
5739
5862
  const jurisdiction = 'global';
5740
- const tenant = await admin.getTenant(actor, tenantId);
5863
+ const tenant = await host.admin.getTenant(actor, tenantId);
5741
5864
  const handle = tenant?.slug ?? tenantId;
5742
5865
  return `${slug.split('/').at(-1)}-${handle}.${jurisdiction}.${baseDomain}`;
5743
5866
  };
@@ -5753,12 +5876,12 @@ export function createControlPlaneApi(options) {
5753
5876
  // prod build) while we report success. That is worse than a failure: it sends a reviewer
5754
5877
  // to redo correct work. So refuse to return success for a URL that would serve other code.
5755
5878
  const assertServesBoundVersion = async (previewId) => {
5756
- const bound = await admin.getVersion(actor, opts.versionId, slug);
5879
+ const bound = await c.var.admin.getVersion(actor, opts.versionId, slug);
5757
5880
  // A co-located / embedded vertical has no per-version dispatch script (deploymentRef
5758
5881
  // null) and routes via the static fallback — nothing to compare, so nothing to guard.
5759
5882
  if (!bound?.deploymentRef)
5760
5883
  return;
5761
- const rec = await admin.getScopeRecord(actor, tenantId, previewId);
5884
+ const rec = await c.var.admin.getScopeRecord(actor, tenantId, previewId);
5762
5885
  const effectiveRef = rec?.servingRef ?? bound.deploymentRef;
5763
5886
  if (effectiveRef !== bound.deploymentRef) {
5764
5887
  throw new ControlPlaneError(500, `preview ${previewId} bound version ${opts.versionId} but would route to '${effectiveRef}' ` +
@@ -5784,7 +5907,7 @@ export function createControlPlaneApi(options) {
5784
5907
  // every push is its own script, so the rebind first CARRIES the preview's data into
5785
5908
  // the new version's script (#1710). The preview's data then follows it from push to
5786
5909
  // push, and each push's migrations run forward over it (§4's rehearsal case).
5787
- const existing = (await admin.listScopes(actor, { tenantId, vertical: slug })).find((s) => s.kind === 'preview' && s.slug === previewSlug(slug, opts.tag));
5910
+ const existing = (await c.var.admin.listScopes(actor, { tenantId, vertical: slug })).find((s) => s.kind === 'preview' && s.slug === previewSlug(slug, opts.tag));
5788
5911
  // A preview only HAS data once its two-phase create finished: the directory row lands
5789
5912
  // first as `provisioning` (K-31), the fork's export→restore runs, and `activateScope`
5790
5913
  // is the last step. So a row still at `provisioning` is a create that DIED mid-fork —
@@ -5898,20 +6021,20 @@ export function createControlPlaneApi(options) {
5898
6021
  // Heal a preview provisioned before #527: clear any inherited serving_ref so routing
5899
6022
  // follows the bound version (its per-version script), not the prod serving script.
5900
6023
  if (existing.servingRef)
5901
- await admin.setScopeServingRef(actor, tenantId, existing.id, null);
5902
- await admin.bindScopeVersion(actor, tenantId, existing.id, opts.versionId);
6024
+ await c.var.admin.setScopeServingRef(actor, tenantId, existing.id, null);
6025
+ await c.var.admin.bindScopeVersion(actor, tenantId, existing.id, opts.versionId);
5903
6026
  // #1674: re-assert any recorded OFF in the script the preview now routes to.
5904
- await admin.reassertSystemSwitches(actor, { tenantId, scopeId: existing.id }, { appliedInUnit: carried?.switchedOff });
6027
+ await c.var.admin.reassertSystemSwitches(actor, { tenantId, scopeId: existing.id }, { appliedInUnit: carried?.switchedOff });
5905
6028
  // Renew (or clear) the preview's GC deadline so a reused preview does not silently die.
5906
- await admin.setScopeExpiresAt(actor, tenantId, existing.id, expiresAt);
5907
- const hostname = await bindPreviewHostname(actor, baseHostname, tenantId, existing.id, opts.tag, surface);
6029
+ await c.var.admin.setScopeExpiresAt(actor, tenantId, existing.id, expiresAt);
6030
+ const hostname = await bindPreviewHostname(c.var.admin, actor, baseHostname, tenantId, existing.id, opts.tag, surface);
5908
6031
  await assertServesBoundVersion(existing.id);
5909
6032
  // The fork it was made from, not whichever prod scope this call picked: a reuse is the
5910
6033
  // same preview. Every push is a new version with an EMPTY config store, and the carry
5911
6034
  // above moves the scope's data but not the vertical's config store, so the login is
5912
6035
  // wired again on every reuse, never assumed to have survived.
5913
6036
  const parent = existing.forkedFrom
5914
- ? ((await admin.getScopeRecord(actor, tenantId, existing.forkedFrom)) ?? null)
6037
+ ? ((await c.var.admin.getScopeRecord(actor, tenantId, existing.forkedFrom)) ?? null)
5915
6038
  : null;
5916
6039
  const auth = await wireLogin(existing.id, hostname, parent);
5917
6040
  return {
@@ -5981,7 +6104,7 @@ export function createControlPlaneApi(options) {
5981
6104
  if (!target) {
5982
6105
  throw new ControlPlaneError(501, 'preview needs dispatch resolution for the PR version');
5983
6106
  }
5984
- const canonical = await admin.exportScope(actor, tenantId, source.id);
6107
+ const canonical = await c.var.admin.exportScope(actor, tenantId, source.id);
5985
6108
  const tables = sourceClient ? await sourceClient.exportScope(source.id) : canonical.tables;
5986
6109
  // Directory row FIRST as `provisioning` (K-31 two-phase): a crash before the data
5987
6110
  // copy leaves an inert row that — carrying `forkedFrom` + `expiresAt` — the GC sweep
@@ -6028,10 +6151,10 @@ export function createControlPlaneApi(options) {
6028
6151
  // seats, so a provision that throws can leave a `provisioning` row too. A provision
6029
6152
  // that never wrote one (the tag's slug taken by a racing create) marks nothing: the
6030
6153
  // write below targets this create's own id, which does not exist, and is swallowed.
6031
- await options.host.provisionScope(actor, row);
6154
+ await c.var.host.provisionScope(actor, row);
6032
6155
  if (restore)
6033
6156
  await restoreOrRecord(restore);
6034
- await admin.activateScope(actor, tenantId, previewId);
6157
+ await c.var.admin.activateScope(actor, tenantId, previewId);
6035
6158
  }
6036
6159
  catch (e) {
6037
6160
  // Expire the row this create left behind, so the next create reaps it at once rather
@@ -6039,7 +6162,7 @@ export function createControlPlaneApi(options) {
6039
6162
  // CLI's retry after a known-dead attempt (#1918). The row stays until then, or until
6040
6163
  // the GC sweep's next pass reaps it as expired; the #559 ops-failure row outlives both.
6041
6164
  // Best-effort: if this write fails too, the age bound still frees the tag.
6042
- await admin
6165
+ await c.var.admin
6043
6166
  .setScopeExpiresAt(actor, tenantId, previewId, new Date().toISOString())
6044
6167
  .catch(() => undefined);
6045
6168
  throw e;
@@ -6047,8 +6170,8 @@ export function createControlPlaneApi(options) {
6047
6170
  // Bind the PR version. A private vertical's push self-admitted, so this is accepted; a
6048
6171
  // preview scope also admits a pending version (#513), which is what a clean-room rehearsal
6049
6172
  // of not-yet-admitted code needs.
6050
- await admin.bindScopeVersion(actor, tenantId, previewId, opts.versionId);
6051
- const hostname = await bindPreviewHostname(actor, baseHostname, tenantId, previewId, opts.tag, surface);
6173
+ await c.var.admin.bindScopeVersion(actor, tenantId, previewId, opts.versionId);
6174
+ const hostname = await bindPreviewHostname(c.var.admin, actor, baseHostname, tenantId, previewId, opts.tag, surface);
6052
6175
  await assertServesBoundVersion(previewId);
6053
6176
  const auth = await wireLogin(previewId, hostname, source);
6054
6177
  return {
@@ -6095,7 +6218,7 @@ export function createControlPlaneApi(options) {
6095
6218
  // Everything else forks the tenant's own active, non-fork scope for this vertical.
6096
6219
  let source = null;
6097
6220
  if (!body.empty) {
6098
- const owned = (await admin.listScopes(actor, { tenantId, vertical: slug, status: ['active'] })).filter((s) => !s.forkedFrom && s.kind !== 'preview');
6221
+ const owned = (await c.var.admin.listScopes(actor, { tenantId, vertical: slug, status: ['active'] })).filter((s) => !s.forkedFrom && s.kind !== 'preview');
6099
6222
  source = body.sourceScopeId
6100
6223
  ? (owned.find((s) => s.id === body.sourceScopeId) ?? null)
6101
6224
  : owned.length === 1
@@ -6127,12 +6250,12 @@ export function createControlPlaneApi(options) {
6127
6250
  return c.json({ error: gate.error }, gate.status);
6128
6251
  const { tenantId, slug } = gate;
6129
6252
  const actor = c.get('actor');
6130
- const previews = (await admin.listScopes(actor, { tenantId, vertical: slug }))
6253
+ const previews = (await c.var.admin.listScopes(actor, { tenantId, vertical: slug }))
6131
6254
  .filter((s) => s.kind === 'preview')
6132
6255
  .sort((a, b) => ((a.forkedAt ?? '') < (b.forkedAt ?? '') ? 1 : -1));
6133
6256
  const out = [];
6134
6257
  for (const s of previews) {
6135
- const hosts = await admin.listHostnames(actor, { scopeId: s.id });
6258
+ const hosts = await c.var.admin.listHostnames(actor, { scopeId: s.id });
6136
6259
  out.push({
6137
6260
  scopeId: s.id,
6138
6261
  tag: s.slug?.split('--').at(-1) ?? null,
@@ -6154,7 +6277,7 @@ export function createControlPlaneApi(options) {
6154
6277
  const { tenantId, slug } = gate;
6155
6278
  const tag = c.req.param('tag');
6156
6279
  const actor = c.get('actor');
6157
- const preview = (await admin.listScopes(actor, { tenantId, vertical: slug })).find((s) => s.kind === 'preview' && s.slug === previewSlug(slug, tag));
6280
+ const preview = (await c.var.admin.listScopes(actor, { tenantId, vertical: slug })).find((s) => s.kind === 'preview' && s.slug === previewSlug(slug, tag));
6158
6281
  // Idempotent: an already-reaped (or never-created) preview is a no-op success, so a
6159
6282
  // PR-close job never fails because the preview was already gone.
6160
6283
  if (!preview)
@@ -6183,7 +6306,7 @@ export function createControlPlaneApi(options) {
6183
6306
  source: c.req.query('source'),
6184
6307
  });
6185
6308
  const page = pageParams(c);
6186
- const entries = await admin.listRoles(c.get('actor'), { ...filter, ...page });
6309
+ const entries = await c.var.admin.listRoles(c.get('actor'), { ...filter, ...page });
6187
6310
  // Composite sort key (tenant_id, role_key) — the `|` join is the documented
6188
6311
  // cursor shape (scope-host.ts listRoles).
6189
6312
  return c.json(pageOf(entries, page.limit, (r) => `${r.tenantId}|${r.key}`));
@@ -6209,7 +6332,7 @@ export function createControlPlaneApi(options) {
6209
6332
  // The whole node comes from the path. Pinning only the tenant would pin
6210
6333
  // nothing — see the schema's note: a node carrying a scope is written to that
6211
6334
  // scope's DO directly, with no tenant cross-check below.
6212
- await admin.assignRole(c.get('actor'), {
6335
+ await c.var.admin.assignRole(c.get('actor'), {
6213
6336
  principalId: body.principalId,
6214
6337
  roleKey: body.roleKey,
6215
6338
  node: { tenantId, scopeId: null },
@@ -6222,7 +6345,7 @@ export function createControlPlaneApi(options) {
6222
6345
  // Tombstones rather than deletes (K-21) and is idempotent — unassigning what
6223
6346
  // was never assigned is a silent no-op, so a retry is safe. Node from the
6224
6347
  // path, for the same reason as the assign above.
6225
- await admin.unassignRole(c.get('actor'), {
6348
+ await c.var.admin.unassignRole(c.get('actor'), {
6226
6349
  principalId: body.principalId,
6227
6350
  roleKey: body.roleKey,
6228
6351
  node: { tenantId, scopeId: null },
@@ -6262,11 +6385,11 @@ export function createControlPlaneApi(options) {
6262
6385
  const body = systemSwitchBody.parse(await c.req.json());
6263
6386
  const actor = c.get('actor');
6264
6387
  // K-3 first, so a scope of another tenant reads as absent before anything is reached.
6265
- if (!(await admin.getScopeRecord(actor, tenantId, scopeId))) {
6388
+ if (!(await c.var.admin.getScopeRecord(actor, tenantId, scopeId))) {
6266
6389
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
6267
6390
  }
6268
6391
  const input = { moduleId: body.moduleId, node: { tenantId, scopeId }, reason: body.reason };
6269
- return c.json(to === 'off' ? await admin.revokeFromSystem(actor, input) : await admin.restoreToSystem(actor, input));
6392
+ return c.json(to === 'off' ? await c.var.admin.revokeFromSystem(actor, input) : await c.var.admin.restoreToSystem(actor, input));
6270
6393
  };
6271
6394
  app.delete('/tenants/:tenantId/scopes/:scopeId/system-grants', switchScheduleRoute('off'));
6272
6395
  app.post('/tenants/:tenantId/scopes/:scopeId/system-grants', switchScheduleRoute('on'));
@@ -6283,10 +6406,10 @@ export function createControlPlaneApi(options) {
6283
6406
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
6284
6407
  const actor = c.get('actor');
6285
6408
  // K-3 first, so a scope of another tenant reads as absent before anything is reached.
6286
- if (!(await admin.getScopeRecord(actor, tenantId, scopeId))) {
6409
+ if (!(await c.var.admin.getScopeRecord(actor, tenantId, scopeId))) {
6287
6410
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
6288
6411
  }
6289
- return c.json(await admin.systemGrantsStatus(actor, { tenantId, scopeId }));
6412
+ return c.json(await c.var.admin.systemGrantsStatus(actor, { tenantId, scopeId }));
6290
6413
  });
6291
6414
  // The fleet read (#1674): every scope with a module switched off, from the directory's
6292
6415
  // record of the switch, so it needs no walk of every scope's store. The record is not the
@@ -6312,7 +6435,7 @@ export function createControlPlaneApi(options) {
6312
6435
  order: c.req.query('order'),
6313
6436
  });
6314
6437
  const { position, ...rest } = q;
6315
- const entries = await admin.listSystemSwitches(c.get('actor'), {
6438
+ const entries = await c.var.admin.listSystemSwitches(c.get('actor'), {
6316
6439
  ...rest,
6317
6440
  ...(position === 'all' ? {} : { position }),
6318
6441
  });
@@ -6341,11 +6464,11 @@ export function createControlPlaneApi(options) {
6341
6464
  const body = peerSwitchBody.parse(await c.req.json());
6342
6465
  const actor = c.get('actor');
6343
6466
  // K-3 first, so a scope of another tenant reads as absent before anything is reached.
6344
- if (!(await admin.getScopeRecord(actor, tenantId, scopeId))) {
6467
+ if (!(await c.var.admin.getScopeRecord(actor, tenantId, scopeId))) {
6345
6468
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
6346
6469
  }
6347
6470
  const input = { vertical: body.vertical, node: { tenantId, scopeId }, reason: body.reason };
6348
- return c.json(to === 'off' ? await admin.revokeFromPeer(actor, input) : await admin.restoreToPeer(actor, input));
6471
+ return c.json(to === 'off' ? await c.var.admin.revokeFromPeer(actor, input) : await c.var.admin.restoreToPeer(actor, input));
6349
6472
  };
6350
6473
  app.delete('/tenants/:tenantId/scopes/:scopeId/peer-grants', switchPeerRoute('off'));
6351
6474
  app.post('/tenants/:tenantId/scopes/:scopeId/peer-grants', switchPeerRoute('on'));
@@ -6359,22 +6482,22 @@ export function createControlPlaneApi(options) {
6359
6482
  if (pin !== null && pin !== tenantId)
6360
6483
  return c.json({ error: 'forbidden' }, 403);
6361
6484
  const actor = c.get('actor');
6362
- if (!(await admin.getScopeRecord(actor, tenantId, scopeId))) {
6485
+ if (!(await c.var.admin.getScopeRecord(actor, tenantId, scopeId))) {
6363
6486
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
6364
6487
  }
6365
- return c.json(await admin.peerGrantsStatus(actor, { tenantId, scopeId }));
6488
+ return c.json(await c.var.admin.peerGrantsStatus(actor, { tenantId, scopeId }));
6366
6489
  });
6367
6490
  // Orgs: the portal-customer grouping (§4.1). Creating one mints no permission —
6368
6491
  // members reach what the org was GRANTED, and granting stays off this surface.
6369
6492
  app.post('/tenants/:tenantId/orgs', async (c) => {
6370
6493
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
6371
6494
  const body = tenantOrgBody.parse(await c.req.json());
6372
- await admin.createOrg(c.get('actor'), { ...body, tenantId });
6495
+ await c.var.admin.createOrg(c.get('actor'), { ...body, tenantId });
6373
6496
  return c.body(null, 201);
6374
6497
  });
6375
6498
  app.get('/tenants/:tenantId/orgs', async (c) => {
6376
6499
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
6377
- return c.json(await admin.listOrgs(c.get('actor'), tenantId));
6500
+ return c.json(await c.var.admin.listOrgs(c.get('actor'), tenantId));
6378
6501
  });
6379
6502
  // -- the admin log (§4.4/§4.5) ---------------------------------------------
6380
6503
  app.get('/admin-log', async (c) => {
@@ -6389,7 +6512,7 @@ export function createControlPlaneApi(options) {
6389
6512
  cursor: c.req.query('cursor'),
6390
6513
  order: c.req.query('order'),
6391
6514
  });
6392
- const entries = await admin.auditLog(c.get('actor'), filter);
6515
+ const entries = await c.var.admin.auditLog(c.get('actor'), filter);
6393
6516
  // The cursor IS the last entry's id (ULID order is chronological), so the
6394
6517
  // page carries its own continuation and the console never assembles one.
6395
6518
  return c.json(pageOf(entries, filter.limit, (e) => e.id));
@@ -6415,7 +6538,7 @@ export function createControlPlaneApi(options) {
6415
6538
  cursor: c.req.query('cursor'),
6416
6539
  order: c.req.query('order'),
6417
6540
  });
6418
- const entries = await admin.listOpsFailures(c.get('actor'), filter);
6541
+ const entries = await c.var.admin.listOpsFailures(c.get('actor'), filter);
6419
6542
  return c.json(pageOf(entries, filter.limit, (e) => e.id));
6420
6543
  });
6421
6544
  // -- sweep runs (#1232): the durable record of what a sweep pass touched -------
@@ -6437,7 +6560,7 @@ export function createControlPlaneApi(options) {
6437
6560
  cursor: c.req.query('cursor'),
6438
6561
  order: c.req.query('order'),
6439
6562
  });
6440
- const entries = await admin.listSweepRuns(c.get('actor'), filter);
6563
+ const entries = await c.var.admin.listSweepRuns(c.get('actor'), filter);
6441
6564
  return c.json(pageOf(entries, filter.limit, (e) => e.id));
6442
6565
  });
6443
6566
  // -- the replay lever (#1705 PR 3): move a consumer's watermark on one edge ----
@@ -6462,10 +6585,10 @@ export function createControlPlaneApi(options) {
6462
6585
  const move = importCursorMove.parse(raw);
6463
6586
  const actor = c.get('actor');
6464
6587
  // K-3 first, so a scope of another tenant reads as absent before anything is reached.
6465
- if (!(await admin.getScopeRecord(actor, tenantId, scopeId))) {
6588
+ if (!(await c.var.admin.getScopeRecord(actor, tenantId, scopeId))) {
6466
6589
  return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
6467
6590
  }
6468
- return c.json(await admin.moveImportCursor(actor, tenantId, scopeId, move));
6591
+ return c.json(await c.var.admin.moveImportCursor(actor, tenantId, scopeId, move));
6469
6592
  });
6470
6593
  // -- edge health (#1705 PR 3): where each cross-vertical edge of one tenant stands -
6471
6594
  // Read live, through the phase's own reach, so it cannot disagree with the next pass about
@@ -6478,7 +6601,7 @@ export function createControlPlaneApi(options) {
6478
6601
  if (pin !== null && pin !== tenantId)
6479
6602
  return c.json({ error: 'forbidden' }, 403);
6480
6603
  const focus = c.req.query('scopeId');
6481
- return c.json(await crossVerticalHealth(host, {
6604
+ return c.json(await crossVerticalHealth(c.var.host, {
6482
6605
  actor: c.get('actor'),
6483
6606
  tenantId,
6484
6607
  ...(focus ? { focus: scopeIdSchema.parse(focus) } : {}),
@@ -6496,7 +6619,7 @@ export function createControlPlaneApi(options) {
6496
6619
  code: c.req.query('code'),
6497
6620
  limit: c.req.query('limit'),
6498
6621
  });
6499
- const entries = await admin.listIssues(c.get('actor'), filter);
6622
+ const entries = await c.var.admin.listIssues(c.get('actor'), filter);
6500
6623
  // No cursor, deliberately — see issuesQuery. `entries` alone, so a client
6501
6624
  // never walks a continuation that cannot exist.
6502
6625
  return c.json({ entries });
@@ -6506,7 +6629,7 @@ export function createControlPlaneApi(options) {
6506
6629
  // rather than an invented row.
6507
6630
  app.put('/issues/status', async (c) => {
6508
6631
  const input = issueStatusUpdate.parse(await c.req.json());
6509
- const updated = await admin.setIssueStatus(c.get('actor'), input.fingerprint, input.status);
6632
+ const updated = await c.var.admin.setIssueStatus(c.get('actor'), input.fingerprint, input.status);
6510
6633
  if (!updated)
6511
6634
  return c.json({ error: 'unknown issue fingerprint' }, 404);
6512
6635
  return c.json(updated);
@@ -6527,7 +6650,7 @@ export function createControlPlaneApi(options) {
6527
6650
  cursor: c.req.query('cursor'),
6528
6651
  order: c.req.query('order'),
6529
6652
  });
6530
- const entries = await admin.listModelUsage(c.get('actor'), filter);
6653
+ const entries = await c.var.admin.listModelUsage(c.get('actor'), filter);
6531
6654
  return c.json(pageOf(entries, filter.limit, (e) => e.id));
6532
6655
  });
6533
6656
  // Folded per (tenant, vertical, model) with the platform's margin applied at read
@@ -6541,7 +6664,7 @@ export function createControlPlaneApi(options) {
6541
6664
  const now = new Date();
6542
6665
  const since = q.since ?? new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)).toISOString();
6543
6666
  const until = q.until ?? now.toISOString();
6544
- const summary = await admin.summarizeModelUsage(c.get('actor'), { ...(q.tenantId ? { tenantId: q.tenantId } : {}), since, until }, options.modelMarginPercent ?? DEFAULT_MODEL_MARGIN_PERCENT);
6667
+ const summary = await c.var.admin.summarizeModelUsage(c.get('actor'), { ...(q.tenantId ? { tenantId: q.tenantId } : {}), since, until }, options.modelMarginPercent ?? DEFAULT_MODEL_MARGIN_PERCENT);
6545
6668
  return c.json(summary);
6546
6669
  });
6547
6670
  return app;