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