@substrat-run/control-plane-api 0.57.0 → 0.58.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.d.ts CHANGED
@@ -2,7 +2,7 @@ import { Hono } from 'hono';
2
2
  import type { PlatformActorId } from '@substrat-run/contracts';
3
3
  import type { ScopeHost } from '@substrat-run/kernel';
4
4
  import type { PlatformActorAuth, BuilderAuth, Principal } from './auth.js';
5
- import type { VerticalClient } from './vertical-client.js';
5
+ import { type VerticalClient } from './vertical-client.js';
6
6
  import type { DeployVerticalFn, FetchVerticalAssetFn, FetchVerticalModulesFn } from './deploy.js';
7
7
  import type { PatchScriptBindingsFn } from './wfp.js';
8
8
  import type { ObservabilityReader } from './observability.js';
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/api.js CHANGED
@@ -2,6 +2,7 @@ import { Hono } from 'hono';
2
2
  import { adminAction, ASSET_PART_PREFIX, assetHash, channelName, createTenantInput, entitlementGrantInput, hostname as hostnameSchema, hostnameRegion, hostnameStatus, identityLink, listPageQuery, pageOf, principalId as principalIdSchema, promotionAcknowledgement, provisionableJurisdiction, publishVersionInput, queryScopeInput, readScopeTableInput, registerVerticalInput, scopeDump, dataSubjectId as dataSubjectIdSchema, scopeId as scopeIdSchema, scopeStatus, storageShape, surfaceName, tenantId as tenantIdSchema, tenantStatus, versionOrigin, z, } from '@substrat-run/contracts';
3
3
  import { migrationProgress, ulid } from '@substrat-run/kernel';
4
4
  import { TENANT_HEADER } from './auth.js';
5
+ import { connectionGrantsForScope } from './vertical-client.js';
5
6
  import { ControlPlaneError } from './client.js';
6
7
  import { provisionSiblingScope } from './platform-drain.js';
7
8
  import { mapError } from './errors.js';
@@ -610,6 +611,12 @@ export function createControlPlaneApi(options) {
610
611
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
611
612
  return c.json(await admin.listIdentityLinks(c.get('actor'), tenantId));
612
613
  });
614
+ // The tenant's live connection grants (#592) — the readable "what may this connection
615
+ // invoke" (connections.md §6.2.4 Q2), and the rows provision/reconcile deliver from.
616
+ app.get('/tenants/:tenantId/connection-grants', async (c) => {
617
+ const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
618
+ return c.json(await admin.listConnectionGrants(c.get('actor'), tenantId));
619
+ });
613
620
  // -- the scope directory (§3.2/§4.2) ---------------------------------------
614
621
  app.get('/scopes', async (c) => {
615
622
  const p = c.get('principal');
@@ -1021,8 +1028,18 @@ export function createControlPlaneApi(options) {
1021
1028
  // #406: re-gathered and re-delivered like entitlements, so a reconcile also repairs a
1022
1029
  // dropped identity-link delivery — and is the channel a link/unlink after provision rides.
1023
1030
  const identityLinks = (await admin.listIdentityLinks(actor, tenantId)).map(({ tenantId: _tenantId, ...link }) => link);
1031
+ // #592: connection grants ride the same authoritative gather — the back-fill for a
1032
+ // scope provisioned before `grantToConnection` ran, and how a revoked connection's
1033
+ // grants stop being delivered (they are absent from the directory's live rows).
1034
+ const connectionGrants = connectionGrantsForScope(await admin.listConnectionGrants(actor, tenantId), scope.vertical, scopeId);
1024
1035
  try {
1025
- const result = await vertical.reconcileInstance({ tenantId, scopeId, entitlements, identityLinks });
1036
+ const result = await vertical.reconcileInstance({
1037
+ tenantId,
1038
+ scopeId,
1039
+ entitlements,
1040
+ identityLinks,
1041
+ connectionGrants,
1042
+ });
1026
1043
  return c.json(result);
1027
1044
  }
1028
1045
  catch (e) {
@@ -1180,6 +1197,30 @@ export function createControlPlaneApi(options) {
1180
1197
  // the vertical must never even be asked to wipe a primary scope — and re-checked
1181
1198
  // below the seam by deleteSnapshot, which also wipes the co-located storage,
1182
1199
  // removes hostnames + the directory row, and writes the audit entry.
1200
+ /**
1201
+ * Wipe a scope's co-located bytes via the vertical's `/internal/delete-scope`,
1202
+ * tolerating the ONE refusal that must never pin a directory row forever: a script
1203
+ * that never implemented the verb (answers 501 — the standalone-app shape; the
1204
+ * retired auth-server lineage is the canonical case). Those bytes are unreachable
1205
+ * through every platform verb — export and delete alike — so they stay in the
1206
+ * script's own storage and die with the script at orphan cleanup (#248): the same
1207
+ * stranded-not-deleted posture as rebind's `abandonData`. Anything else (a real
1208
+ * 5xx, a timeout) still aborts — an implemented wipe that failed is a retry, not a
1209
+ * shrug. Returns true when the storage was stranded.
1210
+ */
1211
+ const deleteScopeStorageOrStrand = async (vertical, scopeId) => {
1212
+ if (!vertical)
1213
+ return false;
1214
+ try {
1215
+ await vertical.deleteScope({ scopeId });
1216
+ return false;
1217
+ }
1218
+ catch (e) {
1219
+ if (e instanceof ControlPlaneError && e.status === 501)
1220
+ return true;
1221
+ throw e;
1222
+ }
1223
+ };
1183
1224
  app.delete('/tenants/:tenantId/scopes/:scopeId', async (c) => {
1184
1225
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
1185
1226
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
@@ -1196,10 +1237,9 @@ export function createControlPlaneApi(options) {
1196
1237
  // storage-before-row ordering deleteSnapshot itself keeps, so a crash
1197
1238
  // between the two converges on retry.
1198
1239
  const vertical = await verticalForScope(c, scope);
1199
- if (vertical)
1200
- await vertical.deleteScope({ scopeId });
1240
+ const storageStranded = await deleteScopeStorageOrStrand(vertical, scopeId);
1201
1241
  await options.host.deleteSnapshot(actor, tenantId, scopeId);
1202
- return c.json({ deleted: scopeId });
1242
+ return c.json({ deleted: scopeId, ...(storageStranded ? { storageStranded: true } : {}) });
1203
1243
  }
1204
1244
  catch (e) {
1205
1245
  if (e instanceof ControlPlaneError) {
@@ -1457,13 +1497,14 @@ export function createControlPlaneApi(options) {
1457
1497
  }
1458
1498
  try {
1459
1499
  const vertical = await verticalForScope(c, scope);
1460
- if (vertical)
1461
- await vertical.deleteScope({ scopeId });
1500
+ // By this point the backup contract has resolved (a copy landed, or the caller
1501
+ // explicitly declined one), so stranding is a bookkeeping fact, not data loss.
1502
+ const storageStranded = await deleteScopeStorageOrStrand(vertical, scopeId);
1462
1503
  await admin.reapScope(actor, tenantId, scopeId, {
1463
1504
  ...(backup ? { backupRef: backupRefOf(backup) } : {}),
1464
1505
  });
1465
1506
  const reaped = await admin.getScopeRecord(actor, tenantId, scopeId);
1466
- return c.json({ ...reaped, backup });
1507
+ return c.json({ ...reaped, backup, ...(storageStranded ? { storageStranded: true } : {}) });
1467
1508
  }
1468
1509
  catch (e) {
1469
1510
  if (e instanceof ControlPlaneError) {
@@ -1882,6 +1923,11 @@ export function createControlPlaneApi(options) {
1882
1923
  // so its auth adapter resolves logins from local storage, and offboarding becomes an
1883
1924
  // unlink + re-deliver instead of a source edit + deploy.
1884
1925
  const identityLinks = (await admin.listIdentityLinks(c.get('actor'), input.tenantId)).map(({ tenantId: _tenantId, ...link }) => link);
1926
+ // #592: connection grants too — tenant-wide rows materialize for the NEW scope, so an
1927
+ // install provisioned after `grantToConnection` holds the same `connection:<id>` tuple
1928
+ // as one provisioned before it, and the connector return path works without a human
1929
+ // replaying grants per install.
1930
+ const connectionGrants = connectionGrantsForScope(await admin.listConnectionGrants(c.get('actor'), input.tenantId), slug, input.scopeId);
1885
1931
  // #301 PR-2: per-tenant relational stores the vertical DECLARED, minted here (before
1886
1932
  // the callback — the vertical migrates the store inside the K-31 ready-gate, so it
1887
1933
  // must exist and be bound first). Idempotent like the endpoint: a retried provision
@@ -1913,6 +1959,7 @@ export function createControlPlaneApi(options) {
1913
1959
  ...input,
1914
1960
  entitlements,
1915
1961
  identityLinks,
1962
+ connectionGrants,
1916
1963
  ...(tenantStores.length ? { tenantStores } : {}),
1917
1964
  }));
1918
1965
  return c.json(instance, 201);