@substrat-run/control-plane-api 0.107.0 → 0.108.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.map +1 -1
- package/dist/api.js +101 -2
- package/dist/api.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mask.d.ts.map +1 -1
- package/dist/mask.js +20 -6
- package/dist/mask.js.map +1 -1
- package/dist/pseudonymize.d.ts +9 -1
- package/dist/pseudonymize.d.ts.map +1 -1
- package/dist/pseudonymize.js +53 -1
- package/dist/pseudonymize.js.map +1 -1
- package/dist/vertical-client.d.ts +3 -1
- package/dist/vertical-client.d.ts.map +1 -1
- package/dist/vertical-client.js +13 -0
- package/dist/vertical-client.js.map +1 -1
- package/package.json +6 -6
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/api.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { parseHostname, withLabel, RESERVED_LABEL_SEPARATOR, adminAction, ASSET_PART_PREFIX, assetHash, channelName, connectionActivity, connectionActivitySource, connectionCredential, connectionFilter, connectionProbe, createTenantInput, entitlementGrantInput, hostname as hostnameSchema, hostnameRegion, hostnameStatus, identityLink, errorCode, issueStatus, issueStatusInput, listPageQuery, matchesOutboundHost, pageOf, platformRequestFilter, principalId as principalIdSchema, promotionAcknowledgement, provisionableJurisdiction, publishVersionInput, queryScopeInput, readScopeTableInput, DEFAULT_DENIAL_LIMIT, DENIAL_LIMIT_MAX, registerVerticalInput, serviceDimensions, sweepRunKind, sweepRunOutcome, scopeDump, dataSubjectId as dataSubjectIdSchema, scopeId as scopeIdSchema, scopeStatus, storageShape, surfaceName, tenantId as tenantIdSchema, tenantStatus, versionOrigin, z, PROBLEM_CONTENT_TYPE, toProblem, } from '@substrat-run/contracts';
|
|
2
|
+
import { parseHostname, withLabel, RESERVED_LABEL_SEPARATOR, adminAction, ASSET_PART_PREFIX, assetHash, channelName, connectionActivity, connectionActivitySource, connectionCredential, connectionFilter, connectionProbe, createTenantInput, entitlementGrantInput, hostname as hostnameSchema, hostnameRegion, hostnameStatus, identityLink, errorCode, issueStatus, issueStatusInput, listPageQuery, matchesOutboundHost, pageOf, platformRequestFilter, principalId as principalIdSchema, promotionAcknowledgement, provisionableJurisdiction, publishVersionInput, queryScopeInput, readScopeTableInput, entityHistoryInput, createOrgInput, roleKey as roleKeySchema, DEFAULT_DENIAL_LIMIT, DENIAL_LIMIT_MAX, registerVerticalInput, serviceDimensions, sweepRunKind, sweepRunOutcome, scopeDump, dataSubjectId as dataSubjectIdSchema, scopeId as scopeIdSchema, scopeStatus, storageShape, surfaceName, tenantId as tenantIdSchema, tenantStatus, versionOrigin, z, PROBLEM_CONTENT_TYPE, toProblem, } from '@substrat-run/contracts';
|
|
3
3
|
import { attributeFailure } from './failure-attribution.js';
|
|
4
4
|
import { migrationProgress, ulid } from '@substrat-run/kernel';
|
|
5
5
|
import { TENANT_HEADER } from './auth.js';
|
|
@@ -288,6 +288,32 @@ const issueStatusUpdate = z.object({
|
|
|
288
288
|
fingerprint: z.string().min(1),
|
|
289
289
|
status: issueStatusInput,
|
|
290
290
|
});
|
|
291
|
+
/**
|
|
292
|
+
* A TENANT-LEVEL role assignment under an addressed tenant (#1343) — which is
|
|
293
|
+
* what a team membership is, and all this surface does.
|
|
294
|
+
*
|
|
295
|
+
* `scopeId` is deliberately absent, and `.strict()` REFUSES one rather than
|
|
296
|
+
* stripping it. An earlier draft accepted a body `scopeId` and pinned only the
|
|
297
|
+
* tenant from the path, which pins nothing: `assignRole` addresses a scope DO
|
|
298
|
+
* directly when the node carries a scope (`writeScopeTuple` → `scopeStub(scopeId)`,
|
|
299
|
+
* no tenant cross-check anywhere below it), so a body naming another tenant's
|
|
300
|
+
* scope would have written a role tuple into it. Silently stripping the field
|
|
301
|
+
* would be almost as bad — a caller who believes they scoped an assignment and
|
|
302
|
+
* silently got a tenant-wide one is worse off than one who got a 400.
|
|
303
|
+
*
|
|
304
|
+
* If scope-level assignment is ever needed it wants its own route under
|
|
305
|
+
* `/tenants/:tenantId/scopes/:scopeId/role-assignments`, deriving the node from
|
|
306
|
+
* the PATH after `getScopeRecord(actor, tenantId, scopeId)` returns — the K-3
|
|
307
|
+
* cross-check every other scope-addressed route here already performs.
|
|
308
|
+
*/
|
|
309
|
+
const tenantRoleAssignmentBody = z
|
|
310
|
+
.object({
|
|
311
|
+
principalId: principalIdSchema,
|
|
312
|
+
roleKey: roleKeySchema,
|
|
313
|
+
})
|
|
314
|
+
.strict();
|
|
315
|
+
/** A new org under an addressed tenant (#1343) — `tenantId` comes from the path. */
|
|
316
|
+
const tenantOrgBody = createOrgInput.omit({ tenantId: true });
|
|
291
317
|
const sweepRunsQuery = z.object({
|
|
292
318
|
kind: sweepRunKind.optional(),
|
|
293
319
|
unit: z.string().optional(),
|
|
@@ -1493,6 +1519,26 @@ export function createControlPlaneApi(options) {
|
|
|
1493
1519
|
throw e;
|
|
1494
1520
|
}
|
|
1495
1521
|
});
|
|
1522
|
+
// #1235: one record's event history — the per-entity pivot of the audit spine.
|
|
1523
|
+
// Delegated exactly like the table reads: through the vertical that holds the
|
|
1524
|
+
// scope's data when one resolves, the co-located host otherwise.
|
|
1525
|
+
app.get('/tenants/:tenantId/scopes/:scopeId/history', async (c) => {
|
|
1526
|
+
const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
|
|
1527
|
+
const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
|
|
1528
|
+
const input = entityHistoryInput.parse({
|
|
1529
|
+
entityType: c.req.query('entityType'),
|
|
1530
|
+
entityId: c.req.query('entityId'),
|
|
1531
|
+
limit: c.req.query('limit') ? Number(c.req.query('limit')) : undefined,
|
|
1532
|
+
cursor: c.req.query('cursor') ?? undefined,
|
|
1533
|
+
});
|
|
1534
|
+
const scope = await admin.getScopeRecord(c.get('actor'), tenantId, scopeId);
|
|
1535
|
+
if (!scope)
|
|
1536
|
+
return c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404);
|
|
1537
|
+
const vertical = await verticalForScope(c, scope);
|
|
1538
|
+
return c.json(vertical
|
|
1539
|
+
? await vertical.entityHistory(scopeId, input)
|
|
1540
|
+
: await admin.entityHistory(c.get('actor'), tenantId, scopeId, input));
|
|
1541
|
+
});
|
|
1496
1542
|
app.get('/tenants/:tenantId/scopes/:scopeId/tables/:table', async (c) => {
|
|
1497
1543
|
const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
|
|
1498
1544
|
const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
|
|
@@ -3901,7 +3947,7 @@ export function createControlPlaneApi(options) {
|
|
|
3901
3947
|
throw new ControlPlaneError(409, `no platform base domain configured — a clean-room preview has no source URL to derive from`);
|
|
3902
3948
|
}
|
|
3903
3949
|
// Mint under `<label>.<jurisdiction>.<baseDomain>`, exactly as provisioning does
|
|
3904
|
-
// (provision.ts `bindDefaultHostname` → `
|
|
3950
|
+
// (provision.ts `bindDefaultHostname` → `acme.global.substrat.run`). A clean-room
|
|
3905
3951
|
// preview scope is provisioned `global` by construction (see the caller), and the
|
|
3906
3952
|
// wildcard DNS/cert lives on `*.global.substrat.run` — NOT the certless apex
|
|
3907
3953
|
// `*.substrat.run`. `platformBaseDomains` lists every platform suffix for custom-hostname
|
|
@@ -4204,6 +4250,59 @@ export function createControlPlaneApi(options) {
|
|
|
4204
4250
|
// cursor shape (scope-host.ts listRoles).
|
|
4205
4251
|
return c.json(pageOf(entries, page.limit, (r) => `${r.tenantId}|${r.key}`));
|
|
4206
4252
|
});
|
|
4253
|
+
// -- role ASSIGNMENTS, and orgs (#1343) ------------------------------------
|
|
4254
|
+
//
|
|
4255
|
+
// The line these sit on the far side of, and why. `defineRole` says what a role
|
|
4256
|
+
// MEANS — a permission change, and the permission diff is a human checkpoint
|
|
4257
|
+
// (D-22/D-29). It still gets no route, for the reason stated above the read.
|
|
4258
|
+
// ASSIGNING an already-defined role is a different act: per-principal, runtime,
|
|
4259
|
+
// and the same shape as the entity grants control-plane.md §4.5 already calls a
|
|
4260
|
+
// runtime console concern rather than a checkpoint artifact. `unassignRole`'s own
|
|
4261
|
+
// contract anticipates this caller — "decided above the kernel (e.g. the
|
|
4262
|
+
// dashboard's manage-members check)".
|
|
4263
|
+
//
|
|
4264
|
+
// Service/staff only, absent from BUILDER_ROUTES: a builder must no more be able
|
|
4265
|
+
// to assign itself a role than to write the directory that authenticates it.
|
|
4266
|
+
// Fail-closed, same law as the identity mirror. TENANT-LEVEL only — see the
|
|
4267
|
+
// body schema for why a scope cannot be named here.
|
|
4268
|
+
app.post('/tenants/:tenantId/role-assignments', async (c) => {
|
|
4269
|
+
const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
|
|
4270
|
+
const body = tenantRoleAssignmentBody.parse(await c.req.json());
|
|
4271
|
+
// The whole node comes from the path. Pinning only the tenant would pin
|
|
4272
|
+
// nothing — see the schema's note: a node carrying a scope is written to that
|
|
4273
|
+
// scope's DO directly, with no tenant cross-check below.
|
|
4274
|
+
await admin.assignRole(c.get('actor'), {
|
|
4275
|
+
principalId: body.principalId,
|
|
4276
|
+
roleKey: body.roleKey,
|
|
4277
|
+
node: { tenantId, scopeId: null },
|
|
4278
|
+
});
|
|
4279
|
+
return c.body(null, 204);
|
|
4280
|
+
});
|
|
4281
|
+
app.delete('/tenants/:tenantId/role-assignments', async (c) => {
|
|
4282
|
+
const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
|
|
4283
|
+
const body = tenantRoleAssignmentBody.parse(await c.req.json());
|
|
4284
|
+
// Tombstones rather than deletes (K-21) and is idempotent — unassigning what
|
|
4285
|
+
// was never assigned is a silent no-op, so a retry is safe. Node from the
|
|
4286
|
+
// path, for the same reason as the assign above.
|
|
4287
|
+
await admin.unassignRole(c.get('actor'), {
|
|
4288
|
+
principalId: body.principalId,
|
|
4289
|
+
roleKey: body.roleKey,
|
|
4290
|
+
node: { tenantId, scopeId: null },
|
|
4291
|
+
});
|
|
4292
|
+
return c.body(null, 204);
|
|
4293
|
+
});
|
|
4294
|
+
// Orgs: the portal-customer grouping (§4.1). Creating one mints no permission —
|
|
4295
|
+
// members reach what the org was GRANTED, and granting stays off this surface.
|
|
4296
|
+
app.post('/tenants/:tenantId/orgs', async (c) => {
|
|
4297
|
+
const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
|
|
4298
|
+
const body = tenantOrgBody.parse(await c.req.json());
|
|
4299
|
+
await admin.createOrg(c.get('actor'), { ...body, tenantId });
|
|
4300
|
+
return c.body(null, 201);
|
|
4301
|
+
});
|
|
4302
|
+
app.get('/tenants/:tenantId/orgs', async (c) => {
|
|
4303
|
+
const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
|
|
4304
|
+
return c.json(await admin.listOrgs(c.get('actor'), tenantId));
|
|
4305
|
+
});
|
|
4207
4306
|
// -- the admin log (§4.4/§4.5) ---------------------------------------------
|
|
4208
4307
|
app.get('/admin-log', async (c) => {
|
|
4209
4308
|
const filter = auditLogQuery.parse({
|