@substrat-run/control-plane-api 0.138.0 → 0.139.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 CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/api.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { resolveObservabilityWindow } from './observability-window.js';
2
2
  import { Hono } from 'hono';
3
- import { parseHostname, withLabel, RESERVED_LABEL_SEPARATOR, adminAction, ownerTransferInput, 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, operationSeriesCount, operationSeriesInput, delegatedReadParams, createOrgInput, roleKey as roleKeySchema, peerSwitch, systemSwitch, DEFAULT_DENIAL_LIMIT, DENIAL_LIMIT_MAX, denialGroupBy, registerVerticalInput, platformScriptCollision, platformScriptRefusal, PLATFORM_SCRIPT_NAMES, serviceDimensions, sweepRunKind, sweepRunOutcome, scopeDump, dataSubjectId as dataSubjectIdSchema, scopeId as scopeIdSchema, scopeStatus, storageShape, surfaceName, tenantId as tenantIdSchema, tenantStatus, versionOrigin, z, PROBLEM_CONTENT_TYPE, toProblem, redrainEventsInput, importCursorMove, importCursorAcknowledgementMissing, REDRAIN_BATCH, migrationsOnTop, errorCodeOf, capabilityFilterQuery, PLATFORM_FEATURES_HEADER, PLATFORM_FEATURE_SCOPE_SWEEPER, } from '@substrat-run/contracts';
3
+ import { parseHostname, withLabel, RESERVED_LABEL_SEPARATOR, adminAction, ownerTransferInput, OWNER_TRANSFER_AUDIT_ERROR_MAX, memberInviteInput, memberRoleInput, 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, findingKind, findingRuleInput, findingStatus, findingStatusInput, listPageQuery, matchesOutboundHost, pageOf, platformRequestFilter, principalId as principalIdSchema, impersonationStamp, promotionAcknowledgement, bindAcknowledgement, provisionableJurisdiction, publishVersionInput, queryScopeInput, readScopeTableInput, entityHistoryInput, eventFacetInput, eventCauseInput, eventEffectsInput, invocationEventsInput, deadLettersInput, lifecycleFlowInput, operationSeriesCount, operationSeriesInput, delegatedReadParams, createOrgInput, roleKey as roleKeySchema, peerSwitch, systemSwitch, DEFAULT_DENIAL_LIMIT, DENIAL_LIMIT_MAX, denialGroupBy, registerVerticalInput, platformScriptCollision, platformScriptRefusal, PLATFORM_SCRIPT_NAMES, serviceDimensions, sweepRunKind, sweepRunOutcome, scopeDump, dataSubjectId as dataSubjectIdSchema, scopeId as scopeIdSchema, scopeStatus, storageShape, surfaceName, tenantId as tenantIdSchema, tenantStatus, versionOrigin, z, PROBLEM_CONTENT_TYPE, toProblem, redrainEventsInput, importCursorMove, importCursorAcknowledgementMissing, REDRAIN_BATCH, migrationsOnTop, errorCodeOf, capabilityFilterQuery, PLATFORM_FEATURES_HEADER, PLATFORM_FEATURE_SCOPE_SWEEPER, } from '@substrat-run/contracts';
4
4
  import { attributeFailure } from './failure-attribution.js';
5
5
  import { BIND_EXPORT_BREAK_REFUSAL, CARRIED_AWAY_KEY, bindExportBreakRefusal, carriedAwayDump, dumpMetaValue, metaValueIn, crossVerticalHealth, exportBreakRefusal, isBindExportBreakRefusal, isExportBreakRefusal, isPrimaryScope, migrationProgress, ulid, } from '@substrat-run/kernel';
6
6
  import { TENANT_HEADER, confinedTenant } from './auth.js';
@@ -478,6 +478,22 @@ const issueStatusUpdate = z.object({
478
478
  fingerprint: z.string().min(1),
479
479
  status: issueStatusInput,
480
480
  });
481
+ // The findings read (#1748). No cursor, for the issues read's reason: the cardinality is the
482
+ // number of distinct shapes a tenant has. `tenantId` is forced for a confined principal.
483
+ const findingsQuery = z.object({
484
+ tenantId: tenantIdSchema.optional(),
485
+ status: findingStatus.optional(),
486
+ kind: findingKind.optional(),
487
+ limit: z.coerce.number().int().min(1).max(500).optional(),
488
+ });
489
+ const findingStatusUpdate = z.object({ status: findingStatusInput }).strict();
490
+ const findingRulesQuery = z.object({
491
+ active: z
492
+ .enum(['true', 'false'])
493
+ .optional()
494
+ .transform((v) => v === 'true'),
495
+ limit: z.coerce.number().int().min(1).max(500).optional(),
496
+ });
481
497
  /**
482
498
  * A TENANT-LEVEL role assignment under an addressed tenant (#1343) — which is
483
499
  * what a team membership is, and all this surface does.
@@ -592,6 +608,12 @@ const TENANT_ROUTES = [
592
608
  // grant (`tenantGrantableEntitlement`): the pin alone would let it grant itself any.
593
609
  { method: 'PUT', re: /\/tenants\/[^/]+\/entitlements\/[^/]+$/, pin: 'path' },
594
610
  { method: 'GET', re: /\/tenants\/[^/]+\/cross-vertical\/edges$/, pin: 'path' },
611
+ // Findings (#1748): the tenant's inbox, its verdicts and its suppress rules. The dashboard
612
+ // checks WHO inside the tenant may read or act (its findings permissions) before calling.
613
+ { method: 'PUT', re: /\/tenants\/[^/]+\/findings\/[^/]+\/status$/, pin: 'path' },
614
+ { method: 'GET', re: /\/tenants\/[^/]+\/finding-rules$/, pin: 'path' },
615
+ { method: 'POST', re: /\/tenants\/[^/]+\/finding-rules$/, pin: 'path' },
616
+ { method: 'DELETE', re: /\/tenants\/[^/]+\/finding-rules\/[^/]+$/, pin: 'path' },
595
617
  // Scopes: provision, read, and the lifecycle acts the Data and Deployments tabs offer.
596
618
  { method: 'POST', re: /\/tenants\/[^/]+\/scopes$/, pin: 'path' },
597
619
  { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+$/, pin: 'path' },
@@ -624,6 +646,11 @@ const TENANT_ROUTES = [
624
646
  { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/lifecycle-flow$/, pin: 'path' },
625
647
  { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/operation-series$/, pin: 'path' },
626
648
  { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/owner-claim$/, pin: 'path' },
649
+ // An installed vertical's members (#1150): the roster, an invite, a role move, a removal.
650
+ { method: 'GET', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/members$/, pin: 'path' },
651
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/members$/, pin: 'path' },
652
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/members\/[^/]+\/role$/, pin: 'path' },
653
+ { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/members\/[^/]+\/remove$/, pin: 'path' },
627
654
  { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/query$/, pin: 'path' },
628
655
  { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/reap$/, pin: 'path' },
629
656
  { method: 'POST', re: /\/tenants\/[^/]+\/scopes\/[^/]+\/rebind-vertical$/, pin: 'path' },
@@ -652,6 +679,7 @@ const TENANT_ROUTES = [
652
679
  { method: 'GET', re: /\/admin-log$/, pin: 'query' },
653
680
  { method: 'GET', re: /\/ops-failures$/, pin: 'query' },
654
681
  { method: 'GET', re: /\/sweep-runs$/, pin: 'query' },
682
+ { method: 'GET', re: /\/findings$/, pin: 'query' },
655
683
  { method: 'GET', re: /\/service-refs$/, pin: 'query' },
656
684
  { method: 'GET', re: /\/observability\/tenant-metrics$/, pin: 'query' },
657
685
  { method: 'GET', re: /\/observability\/tenant-metrics-series$/, pin: 'query' },
@@ -3158,6 +3186,19 @@ export function createControlPlaneApi(options) {
3158
3186
  throw e;
3159
3187
  }
3160
3188
  });
3189
+ /**
3190
+ * The address a link into an instance opens on: the scope's canonical `app` hostname,
3191
+ * preferring one the platform has activated but not requiring it — a platform hostname is
3192
+ * bound `pending` and activated a step later, and that step can fail while the app is fully
3193
+ * reachable (#294). `failed` is the one state that never routes, so only it is skipped. The
3194
+ * platform owns the hostname directory, so the origin is never taken from a body.
3195
+ */
3196
+ const instanceOrigin = async (admin, actor, scopeId) => {
3197
+ const bound = (await admin.listHostnames(actor, { scopeId })).filter((h) => h.status !== 'failed');
3198
+ const pick = (hs) => hs.find((h) => h.surface === 'app' && h.canonical) ?? hs.find((h) => h.surface === 'app') ?? hs[0];
3199
+ const host = pick(bound.filter((h) => h.status === 'active')) ?? pick(bound);
3200
+ return host ? `https://${host.hostname}` : null;
3201
+ };
3161
3202
  app.post('/tenants/:tenantId/scopes/:scopeId/owner-claim', async (c) => {
3162
3203
  const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
3163
3204
  const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
@@ -3172,18 +3213,12 @@ export function createControlPlaneApi(options) {
3172
3213
  const vertical = await verticalForScope(c, scope);
3173
3214
  if (!vertical)
3174
3215
  return c.json({ error: await diagnoseUnboundScope(actor, scope) }, 501);
3175
- // The address the link opens on: the scope's canonical `app` hostname, preferring one
3176
- // the platform has activated but not requiring it — a platform hostname is bound
3177
- // `pending` and activated a step later, and that step can fail while the app is fully
3178
- // reachable (#294). `failed` is the one state that never routes, so only it is skipped.
3179
- const bound = (await c.var.admin.listHostnames(actor, { scopeId })).filter((h) => h.status !== 'failed');
3180
- const pick = (hs) => hs.find((h) => h.surface === 'app' && h.canonical) ?? hs.find((h) => h.surface === 'app') ?? hs[0];
3181
- const host = pick(bound.filter((h) => h.status === 'active')) ?? pick(bound);
3182
- if (!host) {
3216
+ const origin = await instanceOrigin(c.var.admin, actor, scopeId);
3217
+ if (!origin) {
3183
3218
  return c.json({ error: `scope ${scopeId} has no hostname bound — a claim link needs an address to open on` }, 409);
3184
3219
  }
3185
3220
  try {
3186
- return c.json(await vertical.mintOwnerClaim({ tenantId, scopeId, origin: `https://${host.hostname}`, actor }), 201);
3221
+ return c.json(await vertical.mintOwnerClaim({ tenantId, scopeId, origin, actor }), 201);
3187
3222
  }
3188
3223
  catch (e) {
3189
3224
  if (e instanceof ControlPlaneError)
@@ -3191,6 +3226,111 @@ export function createControlPlaneApi(options) {
3191
3226
  throw e;
3192
3227
  }
3193
3228
  });
3229
+ // An installed vertical's MEMBERS (#1150) — the dashboard's Members section. The roster and
3230
+ // every change live in the VERTICAL (its scope's role tuples, its identity directory), so each
3231
+ // route makes the same K-3 cross-check as the owner seat and then asks the deployment that
3232
+ // holds the scope. One scope per call: no walk across scopes (#1581).
3233
+ //
3234
+ // **Who is acting is the credential's person, and only that.** Every change is bounded in the
3235
+ // vertical by the K-21 set comparison at the vertical's scope, asked about a principal — and
3236
+ // the one principal this plane can vouch for is the `onBehalfOf` the dashboard's tenant token
3237
+ // carries (#977), which only the staff mint writes. A builder session, a staff token or a
3238
+ // tenant token minted for nobody names no person, so the writes refuse it rather than
3239
+ // borrowing an authority it does not have. Being a tenant (dashboard) admin confers nothing in
3240
+ // the vertical's scope: what the person holds THERE is what bounds them.
3241
+ //
3242
+ // Every change is audited intent-first then outcome (`manageScopeMember`), like the hand-over.
3243
+ const memberPerson = (p) => p.kind === 'tenant' && p.onBehalfOf ? p.onBehalfOf.principal : null;
3244
+ /** The scope, its vertical and the acting person, or the answer that refuses the request. */
3245
+ const memberTarget = async (c) => {
3246
+ const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
3247
+ const scopeId = scopeIdSchema.parse(c.req.param('scopeId'));
3248
+ const principal = c.get('principal');
3249
+ const actor = c.get('actor');
3250
+ // K-3: another tenant's pair, or no such scope, reads the same — absent.
3251
+ const scope = outsideTenant(principal, tenantId) ? null : await c.var.admin.getScopeRecord(actor, tenantId, scopeId);
3252
+ if (!scope)
3253
+ return { refusal: c.json({ error: `unknown scope for tenant: (${tenantId}, ${scopeId})` }, 404) };
3254
+ const vertical = await verticalForScope(c, scope);
3255
+ if (!vertical)
3256
+ return { refusal: c.json({ error: await diagnoseUnboundScope(actor, scope) }, 501) };
3257
+ return { tenantId, scopeId, actor, vertical, person: memberPerson(principal) };
3258
+ };
3259
+ /** Run one audited member change: intent, the vertical's call, then the outcome row. */
3260
+ const auditedMemberChange = async (c, entry, run, applied) => {
3261
+ const actor = c.get('actor');
3262
+ const base = { ...entry, operationId: ulid() };
3263
+ await c.var.admin.recordMemberChange(actor, { ...base, phase: 'intent' });
3264
+ let result;
3265
+ try {
3266
+ result = await run();
3267
+ }
3268
+ catch (e) {
3269
+ const status = e instanceof ControlPlaneError ? e.status : 500;
3270
+ const error = (e instanceof Error ? e.message : String(e)).slice(0, OWNER_TRANSFER_AUDIT_ERROR_MAX);
3271
+ await c.var.admin
3272
+ .recordMemberChange(actor, { ...base, phase: status < 500 ? 'refused' : 'failed', error })
3273
+ .catch(() => undefined);
3274
+ if (e instanceof ControlPlaneError) {
3275
+ return { ok: false, response: c.json({ error: e.message }, e.status) };
3276
+ }
3277
+ throw e;
3278
+ }
3279
+ await c.var.admin.recordMemberChange(actor, { ...base, phase: 'applied', ...applied(result) });
3280
+ return { ok: true, result };
3281
+ };
3282
+ const NO_PERSON = 'a member change needs the signed-in person it is made for — this credential names nobody';
3283
+ app.get('/tenants/:tenantId/scopes/:scopeId/members', async (c) => {
3284
+ const target = await memberTarget(c);
3285
+ if ('refusal' in target)
3286
+ return target.refusal;
3287
+ try {
3288
+ return c.json(await target.vertical.listMembers(target.tenantId, target.scopeId));
3289
+ }
3290
+ catch (e) {
3291
+ if (e instanceof ControlPlaneError)
3292
+ return c.json({ error: e.message }, e.status);
3293
+ throw e;
3294
+ }
3295
+ });
3296
+ app.post('/tenants/:tenantId/scopes/:scopeId/members', async (c) => {
3297
+ const target = await memberTarget(c);
3298
+ if ('refusal' in target)
3299
+ return target.refusal;
3300
+ if (!target.person)
3301
+ return c.json({ error: NO_PERSON }, 403);
3302
+ const body = memberInviteInput.parse(await c.req.json());
3303
+ const origin = await instanceOrigin(c.var.admin, target.actor, target.scopeId);
3304
+ if (!origin) {
3305
+ return c.json({ error: `scope ${target.scopeId} has no hostname bound — an invite link needs an address to open on` }, 409);
3306
+ }
3307
+ const { tenantId, scopeId, person } = target;
3308
+ const done = await auditedMemberChange(c, { tenantId, scopeId, change: 'invite', caller: person, roleKey: body.roleKey }, () => target.vertical.inviteMember({ tenantId, scopeId, caller: person, origin, roleKey: body.roleKey, email: body.email ?? null }), (link) => ({ principal: link.principal }));
3309
+ return done.ok ? c.json(done.result, 201) : done.response;
3310
+ });
3311
+ app.post('/tenants/:tenantId/scopes/:scopeId/members/:principal/role', async (c) => {
3312
+ const target = await memberTarget(c);
3313
+ if ('refusal' in target)
3314
+ return target.refusal;
3315
+ if (!target.person)
3316
+ return c.json({ error: NO_PERSON }, 403);
3317
+ const member = principalIdSchema.parse(c.req.param('principal'));
3318
+ const body = memberRoleInput.parse(await c.req.json());
3319
+ const { tenantId, scopeId, person } = target;
3320
+ const done = await auditedMemberChange(c, { tenantId, scopeId, change: 'role', caller: person, principal: member, from: body.from, to: body.to }, () => target.vertical.changeMemberRole({ tenantId, scopeId, caller: person, principal: member, from: body.from, to: body.to }), () => ({}));
3321
+ return done.ok ? c.json({ principal: member, from: body.from, to: body.to }) : done.response;
3322
+ });
3323
+ app.post('/tenants/:tenantId/scopes/:scopeId/members/:principal/remove', async (c) => {
3324
+ const target = await memberTarget(c);
3325
+ if ('refusal' in target)
3326
+ return target.refusal;
3327
+ if (!target.person)
3328
+ return c.json({ error: NO_PERSON }, 403);
3329
+ const member = principalIdSchema.parse(c.req.param('principal'));
3330
+ const { tenantId, scopeId, person } = target;
3331
+ const done = await auditedMemberChange(c, { tenantId, scopeId, change: 'remove', caller: person, principal: member }, () => target.vertical.removeMember({ tenantId, scopeId, caller: person, principal: member }), (removal) => ({ revoked: removal.revoked }));
3332
+ return done.ok ? c.json(done.result) : done.response;
3333
+ });
3194
3334
  // The owner HAND-OVER (#1665). The move runs in the VERTICAL, behind `/internal/owner-transfer`
3195
3335
  // (vertical-host says in what order and why). This route decides who may ask, and audits every
3196
3336
  // attempt: an `intent` row before the vertical is reached, so a row the log cannot take means
@@ -7674,6 +7814,57 @@ export function createControlPlaneApi(options) {
7674
7814
  return c.json({ error: 'unknown issue fingerprint' }, 404);
7675
7815
  return c.json(updated);
7676
7816
  });
7817
+ // -- findings (#1748): the tenant inbox, with a lifecycle and suppress rules ----
7818
+ // Staff read the fleet; a tenant credential's tenant is forced from the principal, the
7819
+ // /ops-failures posture. Builders never reach any of these (absent from BUILDER_ROUTES): a
7820
+ // finding is the tenant's, not the vertical author's.
7821
+ app.get('/findings', async (c) => {
7822
+ const filter = findingsQuery.parse({
7823
+ tenantId: confinedTenant(c.get('principal')) ?? c.req.query('tenantId'),
7824
+ status: c.req.query('status'),
7825
+ kind: c.req.query('kind'),
7826
+ limit: c.req.query('limit'),
7827
+ });
7828
+ return c.json({ entries: await c.var.admin.listFindings(c.get('actor'), filter) });
7829
+ });
7830
+ // A verdict: acknowledge, resolve or reopen. Keyed on the tenant in the path as well as the id,
7831
+ // so another tenant's finding is a 404, never a write.
7832
+ app.put('/tenants/:tenantId/findings/:id/status', async (c) => {
7833
+ const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
7834
+ if (outsideTenant(c.get('principal'), tenantId))
7835
+ return c.json({ error: 'forbidden' }, 403);
7836
+ const { status } = findingStatusUpdate.parse(await c.req.json());
7837
+ const updated = await c.var.admin.setFindingStatus(c.get('actor'), tenantId, c.req.param('id'), status);
7838
+ if (!updated)
7839
+ return c.json({ error: 'unknown finding' }, 404);
7840
+ return c.json(updated);
7841
+ });
7842
+ app.get('/tenants/:tenantId/finding-rules', async (c) => {
7843
+ const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
7844
+ if (outsideTenant(c.get('principal'), tenantId))
7845
+ return c.json({ error: 'forbidden' }, 403);
7846
+ const filter = findingRulesQuery.parse({ active: c.req.query('active'), limit: c.req.query('limit') });
7847
+ const entries = await c.var.admin.listFindingRules(c.get('actor'), tenantId, filter);
7848
+ return c.json({ entries });
7849
+ });
7850
+ // Suppress with a rule: a scope plus an expiry, audited. 201 with the rule and how many
7851
+ // findings it suppressed at once.
7852
+ app.post('/tenants/:tenantId/finding-rules', async (c) => {
7853
+ const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
7854
+ if (outsideTenant(c.get('principal'), tenantId))
7855
+ return c.json({ error: 'forbidden' }, 403);
7856
+ const input = findingRuleInput.parse(await c.req.json());
7857
+ return c.json(await c.var.admin.createFindingRule(c.get('actor'), tenantId, input), 201);
7858
+ });
7859
+ app.delete('/tenants/:tenantId/finding-rules/:ruleId', async (c) => {
7860
+ const tenantId = tenantIdSchema.parse(c.req.param('tenantId'));
7861
+ if (outsideTenant(c.get('principal'), tenantId))
7862
+ return c.json({ error: 'forbidden' }, 403);
7863
+ const revoked = await c.var.admin.revokeFindingRule(c.get('actor'), tenantId, c.req.param('ruleId'));
7864
+ if (!revoked)
7865
+ return c.json({ error: 'unknown finding rule' }, 404);
7866
+ return c.json(revoked);
7867
+ });
7677
7868
  // -- model usage (#1054): meter 3, the one D-30 could not compute -------------
7678
7869
  // The ledger the `model-usage` intents drain into. Staff-only: the tenant-facing
7679
7870
  // read is the vertical's own usage screen over its metering engine; this is the