@substrat-run/control-plane-api 0.144.0 → 0.145.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 +57 -17
- 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/reconcile.d.ts +17 -1
- package/dist/reconcile.d.ts.map +1 -1
- package/dist/reconcile.js +20 -1
- package/dist/reconcile.js.map +1 -1
- package/dist/vertical-client.d.ts +16 -2
- package/dist/vertical-client.d.ts.map +1 -1
- package/dist/vertical-client.js +15 -2
- 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":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAkBL,KAAK,qBAAqB,
|
|
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, capabilityId as capabilityIdSchema, 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, FINDINGS_LIMIT_DEFAULT, FINDINGS_LIMIT_MAX, findingCursorOf, 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';
|
|
@@ -13,7 +13,7 @@ import { BACKFILL_PAGE_MAX, backfillScopeScriptCopies } from './scope-copy-backf
|
|
|
13
13
|
import { oidcCallbackUrl, retireClientsOfReapedScope, wirePreviewAuth } from './preview-auth.js';
|
|
14
14
|
import { scopeDeployment, versionReachedAt } from './scope-deployment.js';
|
|
15
15
|
import { reconcileConnectionGrants } from './connection-grants.js';
|
|
16
|
-
import { reconcileThenReassert, reviewedEntityGrants, switchCarryFor } from './reconcile.js';
|
|
16
|
+
import { reconcileOutcomeOf, reconcileThenReassert, reviewedEntityGrants, switchCarryFor } from './reconcile.js';
|
|
17
17
|
import { ConnectionRelayError, relayConnectionUpsert } from './connection-relay.js';
|
|
18
18
|
import { ControlPlaneError } from '@substrat-run/control-plane-client';
|
|
19
19
|
import { provisionSiblingScope } from './platform-drain.js';
|
|
@@ -482,13 +482,15 @@ const issueStatusUpdate = z.object({
|
|
|
482
482
|
fingerprint: z.string().min(1),
|
|
483
483
|
status: issueStatusInput,
|
|
484
484
|
});
|
|
485
|
-
// The findings read (#1748).
|
|
486
|
-
//
|
|
485
|
+
// The findings read (#1748), keyset-paged (#2198). `limit` stays optional with the kernel's own
|
|
486
|
+
// default (100) rather than `listPageQuery`'s 20, so a caller that sends nothing gets the page it
|
|
487
|
+
// always got. `tenantId` is forced for a confined principal.
|
|
487
488
|
const findingsQuery = z.object({
|
|
488
489
|
tenantId: tenantIdSchema.optional(),
|
|
489
490
|
status: findingStatus.optional(),
|
|
490
491
|
kind: findingKind.optional(),
|
|
491
|
-
limit: z.coerce.number().int().min(1).max(
|
|
492
|
+
limit: z.coerce.number().int().min(1).max(FINDINGS_LIMIT_MAX).optional(),
|
|
493
|
+
cursor: listPageQuery.shape.cursor,
|
|
492
494
|
});
|
|
493
495
|
const findingStatusUpdate = z.object({ status: findingStatusInput }).strict();
|
|
494
496
|
const findingRulesQuery = z.object({
|
|
@@ -2068,12 +2070,15 @@ export function createControlPlaneApi(options) {
|
|
|
2068
2070
|
* #1756: refuse, BEFORE any data moves, a move of this scope that would break apps in its
|
|
2069
2071
|
* tenant, unless acknowledged. The host refuses the same move whoever calls; this is what keeps
|
|
2070
2072
|
* a route that exports, restores or snapshots first from doing so for a move that is refused.
|
|
2071
|
-
* `servingRef` is the route the scope takes in the same act (an adopt).
|
|
2073
|
+
* `servingRef` is the route the scope takes in the same act (an adopt). The host's read also
|
|
2074
|
+
* refuses, whatever is acknowledged, a move that strands people a link bound (#2178).
|
|
2072
2075
|
*/
|
|
2073
2076
|
const refuseOnBreaks = async (actor, tenantId, scopeId, versionId, opts) => {
|
|
2077
|
+
// Asked even when acknowledged: the read also refuses a move that strands people a link
|
|
2078
|
+
// bound (#2178), which no acknowledgement passes.
|
|
2079
|
+
const breaks = await host.admin.bindingImpact(actor, tenantId, scopeId, versionId, opts.servingRef !== undefined ? { servingRef: opts.servingRef } : undefined);
|
|
2074
2080
|
if (opts.acknowledge?.exportBreak)
|
|
2075
2081
|
return;
|
|
2076
|
-
const breaks = await host.admin.bindingImpact(actor, tenantId, scopeId, versionId, opts.servingRef !== undefined ? { servingRef: opts.servingRef } : undefined);
|
|
2077
2082
|
if (breaks.length > 0)
|
|
2078
2083
|
throw new ExportBreakRefused(breaks);
|
|
2079
2084
|
};
|
|
@@ -3179,8 +3184,9 @@ export function createControlPlaneApi(options) {
|
|
|
3179
3184
|
// button and the automatic phase have to write the same receipt, or pressing the
|
|
3180
3185
|
// button means nothing to the thing that watches. Both name it with
|
|
3181
3186
|
// `versionReachedAt`, from the deployment each actually reached.
|
|
3187
|
+
// #2185: with whether it finished the version's retirements, from the deployment's answer.
|
|
3182
3188
|
if (ranAs) {
|
|
3183
|
-
await c.var.admin.markScopeProvisioned(actor, tenantId, scopeId, ranAs);
|
|
3189
|
+
await c.var.admin.markScopeProvisioned(actor, tenantId, scopeId, ranAs, reconcileOutcomeOf(entityGrants, result));
|
|
3184
3190
|
}
|
|
3185
3191
|
// The reconcile succeeded; a store that did not mint rides back as a diagnosis
|
|
3186
3192
|
// (`substrat scope provision` prints it, and `/health` keeps reporting the gap
|
|
@@ -4861,6 +4867,9 @@ export function createControlPlaneApi(options) {
|
|
|
4861
4867
|
const origin = { tenantId: tenantIdSchema.parse(dump.tenantId), scopeId: scopeIdSchema.parse(dump.scopeId) };
|
|
4862
4868
|
const tables = await openDump(dump.tables, sealerFor(c, origin.tenantId, origin.scopeId));
|
|
4863
4869
|
const landing = { ...dump, tables };
|
|
4870
|
+
// #2185: cleared before the load as well as after it (below): a step after the load can still
|
|
4871
|
+
// throw (the deployment's half, the re-assert), and the data that landed may predate a reconcile.
|
|
4872
|
+
await c.var.admin.markScopeProvisioned(actor, tenantId, scopeId, null);
|
|
4864
4873
|
await c.var.host.restoreScope(actor, tenantId, scopeId, landing, sourceHint ? { sourceScopeId: sourceHint } : undefined);
|
|
4865
4874
|
const vertical = await verticalForScope(c, scope);
|
|
4866
4875
|
// #1742: the recorded OFF positions ride the restore, so the deployment switches them
|
|
@@ -4875,9 +4884,19 @@ export function createControlPlaneApi(options) {
|
|
|
4875
4884
|
// directory's record puts it back off now, not at the next reconcile. Where the
|
|
4876
4885
|
// deployment already did (#1742), this finds it done and audits what it reported.
|
|
4877
4886
|
await c.var.admin.reassertSystemSwitches(actor, { tenantId, scopeId }, { appliedInUnit: restored?.switchedOff });
|
|
4887
|
+
// #2185: the dump may predate what a reconcile did here, a finished retirement included: its
|
|
4888
|
+
// tuples bring a retired key back and its spine has no record that the retirement ran. So the
|
|
4889
|
+
// receipt is cleared, as the rewind clears it, and with it the version whose retirements
|
|
4890
|
+
// finished: the install reads as not reconciled, and the sweep's next pass reconciles it.
|
|
4891
|
+
// Again here, after the load, for a sweep reconcile that wrote the receipt while it ran.
|
|
4892
|
+
await c.var.admin.markScopeProvisioned(actor, tenantId, scopeId, null);
|
|
4878
4893
|
return c.json({ restored: scopeId, tables: tables.length });
|
|
4879
4894
|
}
|
|
4880
4895
|
catch (e) {
|
|
4896
|
+
// #2185: and once more on the way out of a failure, for a sweep reconcile that wrote the
|
|
4897
|
+
// receipt between the clear above and the throw. Clearing is always safe (at worst one more
|
|
4898
|
+
// reconcile); a clear that fails here must not hide the restore's own error.
|
|
4899
|
+
await c.var.admin.markScopeProvisioned(actor, tenantId, scopeId, null).catch(() => undefined);
|
|
4881
4900
|
if (e instanceof ControlPlaneError) {
|
|
4882
4901
|
return c.json({ error: e.message }, failedWith(c, e));
|
|
4883
4902
|
}
|
|
@@ -5256,13 +5275,26 @@ export function createControlPlaneApi(options) {
|
|
|
5256
5275
|
return c.json({ error: `the store scope ${scopeId} routes to in '${liveScript}' is not a live store (wiped or kept); nothing was resolved` }, 409);
|
|
5257
5276
|
}
|
|
5258
5277
|
const dump = await retryTransient(() => holder.exportScope(scopeId));
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5278
|
+
// #2185: before the load as well as after it, so a throw once the kept copy has landed (the
|
|
5279
|
+
// re-assert) cannot leave the receipt set over older data.
|
|
5280
|
+
await c.var.admin.markScopeProvisioned(actor, tenantId, scopeId, null);
|
|
5281
|
+
try {
|
|
5282
|
+
const restored = await restoreCarryingSwitches(actor, live, tenantId, scopeId, dump, {
|
|
5283
|
+
scopeId,
|
|
5284
|
+
exact: true,
|
|
5285
|
+
loadStamp: ulid(),
|
|
5286
|
+
expect: liveMarker,
|
|
5287
|
+
});
|
|
5288
|
+
await c.var.admin.reassertSystemSwitches(actor, { tenantId, scopeId }, { appliedInUnit: restored.switchedOff });
|
|
5289
|
+
}
|
|
5290
|
+
catch (e) {
|
|
5291
|
+
// And on a failure, for a sweep reconcile that wrote the receipt after the clear above.
|
|
5292
|
+
await c.var.admin.markScopeProvisioned(actor, tenantId, scopeId, null).catch(() => undefined);
|
|
5293
|
+
throw e;
|
|
5294
|
+
}
|
|
5295
|
+
// #2185: the kept copy is an older store's data, which may predate a reconcile the live store
|
|
5296
|
+
// ran (a finished retirement included), so the receipt is cleared as after a restore.
|
|
5297
|
+
await c.var.admin.markScopeProvisioned(actor, tenantId, scopeId, null);
|
|
5266
5298
|
// Still routed there once the data landed? If not, the move that re-pointed the scope keeps
|
|
5267
5299
|
// what this restore put in that store (its source wipe protects a copy changed since its
|
|
5268
5300
|
// export), and the kept copy here stays kept too: nothing is lost, nothing is discarded.
|
|
@@ -5511,7 +5543,13 @@ export function createControlPlaneApi(options) {
|
|
|
5511
5543
|
await c.var.admin.reassertSystemSwitches(c.get('actor'), { tenantId: input.tenantId, scopeId: input.scopeId }, { appliedInUnit: instance.switchedOff });
|
|
5512
5544
|
}
|
|
5513
5545
|
if (provisioned?.verticalVersionId) {
|
|
5514
|
-
|
|
5546
|
+
// #2185: a provision carries no shapes and reconciles none, so its retirements count as
|
|
5547
|
+
// finished only when the version retires nothing; otherwise the sweep reconciles it. A
|
|
5548
|
+
// registry that cannot be read is not finished, never a failed provision.
|
|
5549
|
+
const outcome = provisioned.vertical
|
|
5550
|
+
? await reviewedEntityGrants(c.var.admin, c.get('actor'), provisioned.vertical, provisioned.verticalVersionId).then((shapes) => reconcileOutcomeOf(shapes, null), () => ({ retirementsFinished: false }))
|
|
5551
|
+
: reconcileOutcomeOf(undefined, null);
|
|
5552
|
+
await c.var.admin.markScopeProvisioned(c.get('actor'), input.tenantId, input.scopeId, provisioned.verticalVersionId, outcome);
|
|
5515
5553
|
}
|
|
5516
5554
|
return c.json(instance, 201);
|
|
5517
5555
|
}
|
|
@@ -8453,8 +8491,10 @@ export function createControlPlaneApi(options) {
|
|
|
8453
8491
|
status: c.req.query('status'),
|
|
8454
8492
|
kind: c.req.query('kind'),
|
|
8455
8493
|
limit: c.req.query('limit'),
|
|
8494
|
+
cursor: c.req.query('cursor'),
|
|
8456
8495
|
});
|
|
8457
|
-
|
|
8496
|
+
const entries = await c.var.admin.listFindings(c.get('actor'), filter);
|
|
8497
|
+
return c.json(pageOf(entries, filter.limit ?? FINDINGS_LIMIT_DEFAULT, findingCursorOf));
|
|
8458
8498
|
});
|
|
8459
8499
|
// A verdict: acknowledge, resolve or reopen. Keyed on the tenant in the path as well as the id,
|
|
8460
8500
|
// so another tenant's finding is a 404, never a write.
|