@substrat-run/control-plane-api 0.166.0 → 0.167.1
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 +16 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +120 -51
- package/dist/api.js.map +1 -1
- package/dist/connection-binding-options.d.ts +13 -0
- package/dist/connection-binding-options.d.ts.map +1 -1
- package/dist/connection-binding-options.js +17 -0
- package/dist/connection-binding-options.js.map +1 -1
- package/dist/deploy.d.ts +2 -4
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +0 -2
- package/dist/deploy.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/staff-client.d.ts +2 -2
- package/dist/staff-client.d.ts.map +1 -1
- package/dist/staff-client.js.map +1 -1
- package/dist/store-rebuild.d.ts +7 -2
- package/dist/store-rebuild.d.ts.map +1 -1
- package/dist/store-rebuild.js +6 -3
- package/dist/store-rebuild.js.map +1 -1
- package/dist/store-releases.d.ts +16 -6
- package/dist/store-releases.d.ts.map +1 -1
- package/dist/store-releases.js +51 -11
- package/dist/store-releases.js.map +1 -1
- package/dist/store-script.d.ts +31 -6
- package/dist/store-script.d.ts.map +1 -1
- package/dist/store-script.js +36 -16
- package/dist/store-script.js.map +1 -1
- package/package.json +9 -9
package/dist/api.d.ts
CHANGED
|
@@ -3,12 +3,12 @@ import type { ConnectionScopeBinding, ConnectionScopeBindingInput } from '@subst
|
|
|
3
3
|
import { Hono } from 'hono';
|
|
4
4
|
import { type ConnectionCertificate, z, type ScopeStoreNeed, type StoreDriverRegistration } from '@substrat-run/contracts';
|
|
5
5
|
import type { Connection, ConnectionActivity, ConnectionActivitySource, ConnectionCredential, ConnectionProbe, PlatformActorId, ScopeId, TenantId } from '@substrat-run/contracts';
|
|
6
|
-
import type { CrossVerticalOptions, HostAdmin, OpsFailureInput, ScopeHost } from '@substrat-run/kernel';
|
|
6
|
+
import type { CrossVerticalOptions, HostAdmin, MailSender, OpsFailureInput, ScopeHost } from '@substrat-run/kernel';
|
|
7
7
|
import type { PlatformActorAuth, BuilderAuth, Principal, TenantServiceAuth } from './auth.js';
|
|
8
8
|
import { type VerticalClient } from './vertical-client.js';
|
|
9
9
|
import { type CopyJobs } from './copy-job.js';
|
|
10
10
|
import type { PlatformScopeStores } from './platform-scope-stores.js';
|
|
11
|
-
import type
|
|
11
|
+
import { type ScopeStoreEnsurerOptions, type StoreCanaryTarget, type StoreDriverApprovals } from './store-script.js';
|
|
12
12
|
import { type CanaryCall, type StoreReleaseBytes } from './store-releases.js';
|
|
13
13
|
import { type ConnectionCandidatePrepare, type VerticalConnectorDeclarationOf } from './connection-relay.js';
|
|
14
14
|
import type { DeployVerticalFn, FetchVerticalAssetFn, FetchVerticalModulesFn } from './deploy.js';
|
|
@@ -209,6 +209,8 @@ export interface ControlPlaneApiOptions {
|
|
|
209
209
|
/** The fleet rebuild (#2309): rebuild a script that exists, and create none. */
|
|
210
210
|
existingOnly?: boolean;
|
|
211
211
|
loadRelease: ScopeStoreEnsurerOptions['loadRelease'];
|
|
212
|
+
/** The driver's approvals, read by the caller under its hold on the driver (D-65). */
|
|
213
|
+
approvals: StoreDriverApprovals;
|
|
212
214
|
}) => Promise<void | {
|
|
213
215
|
uploaded: number;
|
|
214
216
|
}>;
|
|
@@ -221,6 +223,7 @@ export interface ControlPlaneApiOptions {
|
|
|
221
223
|
bytes: StoreReleaseBytes;
|
|
222
224
|
ensureCanary(input: StoreCanaryTarget & {
|
|
223
225
|
loadRelease: ScopeStoreEnsurerOptions['loadRelease'];
|
|
226
|
+
approvals: StoreDriverApprovals;
|
|
224
227
|
}): Promise<{
|
|
225
228
|
scriptName: string;
|
|
226
229
|
uploaded: boolean;
|
|
@@ -363,6 +366,12 @@ export interface ControlPlaneApiOptions {
|
|
|
363
366
|
* verify a Fortnox key yet" is a true statement; "your Fortnox key is fine" is not.
|
|
364
367
|
*/
|
|
365
368
|
connectionInspectors?: Record<string, ConnectionInspector>;
|
|
369
|
+
/**
|
|
370
|
+
* The mail sender each provider that sends as a tenant's own addresses registers (#2098), by
|
|
371
|
+
* provider — the same `{ provider → sender }` the email relay routes through. Behind
|
|
372
|
+
* `POST …/connections/:id/send-test` (#2343); a provider absent here 501s there.
|
|
373
|
+
*/
|
|
374
|
+
mailSenders?: Readonly<Record<string, MailSender>>;
|
|
366
375
|
/**
|
|
367
376
|
* The inspector for a vertical-defined provider (`<namespace>.<name>`, vertical-connectors.md
|
|
368
377
|
* §6.4), which `connectionInspectors` never names: built per (tenant, vertical), since its code
|
|
@@ -499,6 +508,11 @@ type Vars = {
|
|
|
499
508
|
vertical: string | null;
|
|
500
509
|
tenantId: OpsFailureInput['tenantId'];
|
|
501
510
|
};
|
|
511
|
+
/**
|
|
512
|
+
* #113: the id the host worker minted for this request and answers in `requestId` and the
|
|
513
|
+
* `x-substrat-request-id` header. A failure's reference reuses it, so the caller quotes one id.
|
|
514
|
+
*/
|
|
515
|
+
substratInvocationId?: string;
|
|
502
516
|
};
|
|
503
517
|
/** #2446: the most scopes one promote reads the role state of (`roleStateScopeCap`). */
|
|
504
518
|
export declare const ROLE_STATE_SCOPE_CAP = 500;
|
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,EAAsB,KAAK,kBAAkB,EAAoB,MAAM,yBAAyB,CAAC;AAGxG,OAAO,KAAK,EAAE,sBAAsB,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AAE9G,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/api.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { sourceBuildRequest } from '@substrat-run/contracts';
|
|
2
2
|
import { PREVIEW_CONNECTIONS_REFUSAL } from './connection-relay.js';
|
|
3
|
-
import { connectionBindingOptions } from './connection-binding-options.js';
|
|
3
|
+
import { bindingChangeRefusal, connectionBindingOptions, refusesBindingChange } from './connection-binding-options.js';
|
|
4
4
|
import { resolveObservabilityWindow } from './observability-window.js';
|
|
5
5
|
import { Hono } from 'hono';
|
|
6
|
-
import { permissionKey, parseHostname, withLabel, RESERVED_LABEL_SEPARATOR, adminAction, lakeEventsInput, ownerTransferInput, memberInviteInput, memberRoleInput, ASSET_PART_PREFIX, assetHash, STORE_DRIVERS, storeCatalogOperation, 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, rebindAcknowledgement, provisionableJurisdiction, publishVersionInput, queryScopeInput, readScopeTableInput, entityHistoryInput, eventFacetInput, eventCauseInput, eventEffectsInput, invocationEventsInput, deadLettersInput, lifecycleFlowInput, operationSeriesCount, operationSeriesInput, delegatedReadParams, createOrgInput, roleKey as roleKeySchema, peerSwitch, verticalSlug, scopeText, 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, requestRollupReading, meterDaysReading, verticalOwnersReading, utcDay, storageShape, surfaceName, tenantId as tenantIdSchema, tenantStatus, versionOrigin, z, PROBLEM_CONTENT_TYPE, toProblem, validationIssuesFrom, redrainEventsInput, importCursorMove, importCursorAcknowledgementMissing, REDRAIN_BATCH, migrationsOnTop, substratError, errorCodeOf, envVarSpec, capabilityFilterQuery, capabilityId as capabilityIdSchema, PLATFORM_FEATURES_HEADER, PLATFORM_FEATURE_SCOPE_SWEEPER, PLATFORM_FEATURE_SOURCE_BUILDS, SOCIAL_RELAY_VERTICALS, isVerticalConnectorProvider, } from '@substrat-run/contracts';
|
|
6
|
+
import { permissionKey, parseHostname, withLabel, RESERVED_LABEL_SEPARATOR, adminAction, lakeEventsInput, ownerTransferInput, memberInviteInput, memberRoleInput, ASSET_PART_PREFIX, assetHash, STORE_DRIVERS, storeCatalogOperation, storeDriverBasisAdmits, 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, connectionSendTest, connectionSendTestResult, connectionSenders, 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, rebindAcknowledgement, provisionableJurisdiction, publishVersionInput, queryScopeInput, readScopeTableInput, entityHistoryInput, eventFacetInput, eventCauseInput, eventEffectsInput, invocationEventsInput, deadLettersInput, lifecycleFlowInput, operationSeriesCount, operationSeriesInput, delegatedReadParams, createOrgInput, roleKey as roleKeySchema, peerSwitch, verticalSlug, scopeText, 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, requestRollupReading, meterDaysReading, verticalOwnersReading, utcDay, storageShape, surfaceName, tenantId as tenantIdSchema, tenantStatus, versionOrigin, z, PROBLEM_CONTENT_TYPE, toProblem, validationIssuesFrom, redrainEventsInput, importCursorMove, importCursorAcknowledgementMissing, REDRAIN_BATCH, migrationsOnTop, substratError, errorCodeOf, envVarSpec, capabilityFilterQuery, capabilityId as capabilityIdSchema, PLATFORM_FEATURES_HEADER, PLATFORM_FEATURE_SCOPE_SWEEPER, PLATFORM_FEATURE_SOURCE_BUILDS, SOCIAL_RELAY_VERTICALS, isVerticalConnectorProvider, } from '@substrat-run/contracts';
|
|
7
7
|
import { upstreamHandleIn, upstreamReferenceOf, withUpstreamReference } from '@substrat-run/contracts';
|
|
8
8
|
import { ENGINE_ENTITLEMENT_KEYS, bareSlug, declarableEntitlementKey, entitlementNamespaceRefusal, grantableEntitlements } from '@substrat-run/contracts';
|
|
9
9
|
import { SCOPE_COPY_LEASE_MS, copyRestoreFence, publishedMigrationDigest, replayStoreErasures } from '@substrat-run/kernel';
|
|
@@ -15,8 +15,9 @@ import { mintTenantToken } from './tenant-token.js';
|
|
|
15
15
|
import { beginTransfer, connectionGrantsForScope, transferScope, isScopeNotHeld, notHeldIsBenign } from './vertical-client.js';
|
|
16
16
|
import { COPY_JOB_MAX_MS, copyJobAt, copyJobHeld, copyJobId, copyJobLive, copyTagId, versionIdOfOrder, versionOrderOf, } from './copy-job.js';
|
|
17
17
|
import { runningVersionOf, runningVersionStoresOf, scopeStoreReachOf, scopeStoresOf, unrouteScope } from './platform-scope-stores.js';
|
|
18
|
+
import { liveApprovalOf } from './store-script.js';
|
|
18
19
|
import { STORE_REBUILD_REQUEST_SUBREQUESTS, runStoreFleetRebuild, storeRebuildBudget, storeRebuildStatus } from './store-rebuild.js';
|
|
19
|
-
import { PUSH_HOLD_TTL_MS, STORE_RELEASE_STORED_MAX_BYTES, approveStoredDriverRelease, runStoreDriverRevoke, runStoreDriverRollout, storeDriverRelease, storedReleaseLoader } from './store-releases.js';
|
|
20
|
+
import { PUSH_HOLD_TTL_MS, STORE_RELEASE_STORED_MAX_BYTES, approveStoredDriverRelease, readStoreDriverApprovals, runStoreDriverRevoke, runStoreDriverRollout, storeDriverRelease, storedReleaseLoader } from './store-releases.js';
|
|
20
21
|
import { assertNoUnreachableScopeCopies, claimScopeScriptReap, drainScopeScriptCopies, listAllScopeScriptCopies, recordStrandedStorage } from './scope-copy-cleanup.js';
|
|
21
22
|
import { BACKFILL_PAGE_MAX, backfillScopeScriptCopies } from './scope-copy-backfill.js';
|
|
22
23
|
import { oidcCallbackUrl, retireClientsOfReapedScope, wirePreviewAuth } from './preview-auth.js';
|
|
@@ -77,6 +78,8 @@ import { queryLakeEvents } from './lake-gateway.js';
|
|
|
77
78
|
import { LakeQueryError } from './r2-sql-engine.js';
|
|
78
79
|
import { isCustomHostname, validateBindableHostname, } from './custom-hostnames.js';
|
|
79
80
|
import { isSocialRelayVertical, reconcileScopeSocialRelay, relayHostnames, } from './social-relay.js';
|
|
81
|
+
/** The body of a connection's test message (#2343), plain enough to be the same in HTML and text. */
|
|
82
|
+
const SEND_TEST_TEXT = 'This is a test message from a mail connection. It reached you, so its sender address can send.';
|
|
80
83
|
/**
|
|
81
84
|
* Response headers carrying the CLI version advisory (`ControlPlaneApiOptions.cliAdvisory`).
|
|
82
85
|
* The CLI reads these by the same spelling (`packages/cli/src/version.ts`); exported so a
|
|
@@ -84,6 +87,8 @@ import { isSocialRelayVertical, reconcileScopeSocialRelay, relayHostnames, } fro
|
|
|
84
87
|
*/
|
|
85
88
|
export const CLI_MIN_VERSION_HEADER = 'x-substrat-cli-min-version';
|
|
86
89
|
export const CLI_LATEST_VERSION_HEADER = 'x-substrat-cli-latest-version';
|
|
90
|
+
/** A failure's reference: the request's own id where the host minted one, else a fresh ULID. */
|
|
91
|
+
const referenceOf = (c) => c.get('substratInvocationId') ?? ulid();
|
|
87
92
|
/**
|
|
88
93
|
* The directory's classification of a scope that is not primary (#2005), which a verb sends so
|
|
89
94
|
* the store it writes is marked a copy whatever its own marker says; undefined for a primary one.
|
|
@@ -701,6 +706,9 @@ const TENANT_ROUTES = [
|
|
|
701
706
|
// #2100: the PUBLIC certificate a connection signs in with — same pin as the credential view.
|
|
702
707
|
{ method: 'GET', re: /\/tenants\/[^/]+\/connections\/[^/]+\/certificate$/, pin: 'path' },
|
|
703
708
|
{ method: 'POST', re: /\/tenants\/[^/]+\/connections\/[^/]+\/verify$/, pin: 'path' },
|
|
709
|
+
// #2343: a mail connection's test send. The dashboard sends it only to the signed-in person.
|
|
710
|
+
{ method: 'POST', re: /\/tenants\/[^/]+\/connections\/[^/]+\/send-test$/, pin: 'path' },
|
|
711
|
+
{ method: 'GET', re: /\/tenants\/[^/]+\/connections\/[^/]+\/senders$/, pin: 'path' },
|
|
704
712
|
// A vertical's mailed connect links (connections.md §3.5.4): the consent landing's liveness
|
|
705
713
|
// read, the callback's spend and its undo, and the integrations card's list and revoke.
|
|
706
714
|
{ method: 'GET', re: /\/tenants\/[^/]+\/connect-links$/, pin: 'path' },
|
|
@@ -1108,7 +1116,7 @@ export function createControlPlaneApi(options) {
|
|
|
1108
1116
|
// row with no tenant, which only staff read, and to the log line, under the same reference.
|
|
1109
1117
|
let reference = c.get('failureReference');
|
|
1110
1118
|
if (!reference) {
|
|
1111
|
-
reference =
|
|
1119
|
+
reference = referenceOf(c);
|
|
1112
1120
|
console.error('control-plane.unhandled', { method: c.req.method, path: c.req.path, reference, tenantId, scopeId, detail: raw });
|
|
1113
1121
|
}
|
|
1114
1122
|
recordWithheld({ ...base, tenantId, reference }, raw, answered, cause);
|
|
@@ -1440,7 +1448,7 @@ export function createControlPlaneApi(options) {
|
|
|
1440
1448
|
* second row for it; a route that answers the failure as a 5xx of its own also sets `failureRecorded`.
|
|
1441
1449
|
*/
|
|
1442
1450
|
const withheldFailure = (c, entry, cause, answered) => {
|
|
1443
|
-
const reference =
|
|
1451
|
+
const reference = referenceOf(c);
|
|
1444
1452
|
console.error('control-plane.unhandled', {
|
|
1445
1453
|
method: c.req.method,
|
|
1446
1454
|
path: c.req.path,
|
|
@@ -1571,7 +1579,7 @@ export function createControlPlaneApi(options) {
|
|
|
1571
1579
|
// says what is wrong, and the tail line is what ties it to the request that hit it.
|
|
1572
1580
|
if (status >= 500) {
|
|
1573
1581
|
const subject = await subjectOf();
|
|
1574
|
-
const reference =
|
|
1582
|
+
const reference = referenceOf(c);
|
|
1575
1583
|
c.set('failureReference', reference);
|
|
1576
1584
|
console.error('control-plane.unhandled', {
|
|
1577
1585
|
method: c.req.method,
|
|
@@ -2528,6 +2536,13 @@ export function createControlPlaneApi(options) {
|
|
|
2528
2536
|
if (!grants.some((g) => g.connectionId === row.id && g.permission === input.permission)) {
|
|
2529
2537
|
return refuseStatus(c, 409, `connection does not hold '${input.permission}' on scope ${scopeId}; grant it through the tenant's authorized connection flow, then bind`);
|
|
2530
2538
|
}
|
|
2539
|
+
// A change the held binding cannot take in place is the operator's to act on, not a failed write.
|
|
2540
|
+
const held = refusesBindingChange(row.provider)
|
|
2541
|
+
? (await inspectorOf(row)?.bindings?.(c.var.host, row))?.find((b) => b.scopeId === scopeId)
|
|
2542
|
+
: undefined;
|
|
2543
|
+
const changeRefused = bindingChangeRefusal(row.provider, held, input);
|
|
2544
|
+
if (changeRefused)
|
|
2545
|
+
return refuseStatus(c, 409, changeRefused);
|
|
2531
2546
|
const operationId = ulid();
|
|
2532
2547
|
const done = await auditedChange({
|
|
2533
2548
|
flow: 'connection-binding', operationId,
|
|
@@ -2567,6 +2582,70 @@ export function createControlPlaneApi(options) {
|
|
|
2567
2582
|
}
|
|
2568
2583
|
return c.json(connectionProbe.parse(await probe(c.var.host, row)));
|
|
2569
2584
|
});
|
|
2585
|
+
/** The addresses a mail connection may send as (#2343) — the send test's picker, whole and uncut. */
|
|
2586
|
+
app.get('/tenants/:tenantId/connections/:id/senders', async (c) => {
|
|
2587
|
+
const row = await inspectableConnection(c);
|
|
2588
|
+
if (!row)
|
|
2589
|
+
return refuse(c, 'not_found', 'unknown connection');
|
|
2590
|
+
const sender = options.mailSenders?.[row.provider];
|
|
2591
|
+
if (!sender)
|
|
2592
|
+
return refuseStatus(c, 501, `no mail sender registered for provider '${row.provider}'`);
|
|
2593
|
+
return c.json(connectionSenders.parse({ senders: await sender.senders(c.var.host, row) }));
|
|
2594
|
+
});
|
|
2595
|
+
/**
|
|
2596
|
+
* **Send a test message through a connection** (#2343) — the probe for mail, which cannot be
|
|
2597
|
+
* probed without sending. From one of the connection's own senders, to one recipient; the
|
|
2598
|
+
* dashboard's door sends only to the signed-in person's own address.
|
|
2599
|
+
*
|
|
2600
|
+
* Like `/verify`, a refusal is `200 { ok: false, error }` in the connector's own words (outside
|
|
2601
|
+
* the granted scope, throttled, sign-in failed), and the send rides the connector's recorded
|
|
2602
|
+
* egress, so health is written as for any send. A success is recorded by the connector as that
|
|
2603
|
+
* sender's verification. Not an audited admin-log mutation, the same class as a probe; who
|
|
2604
|
+
* asked is logged on the request record instead, never the recipient.
|
|
2605
|
+
*/
|
|
2606
|
+
app.post('/tenants/:tenantId/connections/:id/send-test', async (c) => {
|
|
2607
|
+
const row = await inspectableConnection(c);
|
|
2608
|
+
if (!row)
|
|
2609
|
+
return refuse(c, 'not_found', 'unknown connection');
|
|
2610
|
+
const sender = options.mailSenders?.[row.provider];
|
|
2611
|
+
if (!sender)
|
|
2612
|
+
return refuseStatus(c, 501, `no mail sender registered for provider '${row.provider}'`);
|
|
2613
|
+
const body = connectionSendTest.safeParse(await c.req.json().catch(() => null));
|
|
2614
|
+
if (!body.success)
|
|
2615
|
+
return refuse(c, 'validation_failed', 'from and to must each be one email address');
|
|
2616
|
+
const { from, to } = body.data;
|
|
2617
|
+
const senders = await sender.senders(c.var.host, row);
|
|
2618
|
+
if (!senders.some((s) => s.toLowerCase() === from.toLowerCase())) {
|
|
2619
|
+
return refuse(c, 'validation_failed', `'${from}' is not one of this connection's sender addresses`);
|
|
2620
|
+
}
|
|
2621
|
+
const principal = c.get('principal');
|
|
2622
|
+
console.log('connection.send-test', {
|
|
2623
|
+
tenantId: row.tenantId,
|
|
2624
|
+
connectionId: row.id,
|
|
2625
|
+
provider: row.provider,
|
|
2626
|
+
actor: c.get('actor'),
|
|
2627
|
+
onBehalfOf: principal.kind === 'tenant' ? (principal.onBehalfOf?.principal ?? null) : null,
|
|
2628
|
+
});
|
|
2629
|
+
try {
|
|
2630
|
+
await sender.send(c.var.host, row, {
|
|
2631
|
+
from: { email: from },
|
|
2632
|
+
to: [{ email: to }],
|
|
2633
|
+
subject: 'Substrat test message',
|
|
2634
|
+
html: `<p>${SEND_TEST_TEXT}</p>`,
|
|
2635
|
+
text: SEND_TEST_TEXT,
|
|
2636
|
+
attachments: [],
|
|
2637
|
+
});
|
|
2638
|
+
return c.json(connectionSendTestResult.parse({ ok: true, status: null, error: null }));
|
|
2639
|
+
}
|
|
2640
|
+
catch (e) {
|
|
2641
|
+
const status = e?.status;
|
|
2642
|
+
return c.json(connectionSendTestResult.parse({
|
|
2643
|
+
ok: false,
|
|
2644
|
+
status: typeof status === 'number' ? status : null,
|
|
2645
|
+
error: (e instanceof Error ? e.message : String(e)).slice(0, 600),
|
|
2646
|
+
}));
|
|
2647
|
+
}
|
|
2648
|
+
});
|
|
2570
2649
|
/**
|
|
2571
2650
|
* **What the connection has done** (#605) — the connector's dispatch ledger, projected.
|
|
2572
2651
|
*
|
|
@@ -4296,7 +4375,7 @@ export function createControlPlaneApi(options) {
|
|
|
4296
4375
|
// backstop would file is filed here as a pair, the tenant's row with the sentences only.
|
|
4297
4376
|
const tenantStores = storeSentences.length ? ` (stores: ${storeSentences.join('; ')})` : '';
|
|
4298
4377
|
if (e.status >= 500 && e.status !== 501 && tenantStores !== stores) {
|
|
4299
|
-
const reference =
|
|
4378
|
+
const reference = referenceOf(c);
|
|
4300
4379
|
recordWithheld({
|
|
4301
4380
|
actor,
|
|
4302
4381
|
operation: `${c.req.method} ${c.req.routePath}`,
|
|
@@ -4317,7 +4396,7 @@ export function createControlPlaneApi(options) {
|
|
|
4317
4396
|
const sentence = (reference) => `the reconcile of this scope failed (${e.status})${stores} (reference ${reference})`;
|
|
4318
4397
|
if (e.status < 500) {
|
|
4319
4398
|
// A refusal, which the 5xx backstop leaves unrecorded for staff too: logged, no row.
|
|
4320
|
-
const reference =
|
|
4399
|
+
const reference = referenceOf(c);
|
|
4321
4400
|
console.warn('control-plane.withheld', {
|
|
4322
4401
|
method: c.req.method,
|
|
4323
4402
|
path: c.req.path,
|
|
@@ -7577,7 +7656,13 @@ export function createControlPlaneApi(options) {
|
|
|
7577
7656
|
throw new ControlPlaneError(409, `${operation} refused: the hold on scope store driver '${each}' lapsed during the upload; try again`);
|
|
7578
7657
|
}
|
|
7579
7658
|
}
|
|
7580
|
-
|
|
7659
|
+
const registration = (options.storeDrivers ?? STORE_DRIVERS).find((k) => k.driver === driver);
|
|
7660
|
+
if (!registration)
|
|
7661
|
+
throw new ControlPlaneError(403, `scope store driver '${driver}' is not registered`);
|
|
7662
|
+
// Read under the shared hold (D-65): a revoke takes the driver exclusively, so one written
|
|
7663
|
+
// before this read is seen, and none is written until this upload is done.
|
|
7664
|
+
const approvals = await readStoreDriverApprovals(host.admin, actor, registration);
|
|
7665
|
+
await options.ensureScopeStores({ vertical: slug, stores: stores.filter((store) => store.driver === driver), loadRelease, approvals });
|
|
7581
7666
|
}
|
|
7582
7667
|
}
|
|
7583
7668
|
catch (e) {
|
|
@@ -10988,20 +11073,8 @@ export function createControlPlaneApi(options) {
|
|
|
10988
11073
|
throw new ControlPlaneError(422, `not a store driver revoke: ${parsed.error.issues.map(safeIssue).join('; ')}`);
|
|
10989
11074
|
return parsed.data;
|
|
10990
11075
|
};
|
|
10991
|
-
|
|
10992
|
-
|
|
10993
|
-
const stored = await c.var.admin.getStoreDriverRelease(c.get('actor'), registration.driver, sha256);
|
|
10994
|
-
if (!stored)
|
|
10995
|
-
throw new ControlPlaneError(404, `release ${sha256} of '${registration.driver}' is not stored`);
|
|
10996
|
-
const pin = registration.releases.find((p) => p.sha256 === sha256);
|
|
10997
|
-
if (!pin) {
|
|
10998
|
-
throw new ControlPlaneError(422, `release ${sha256} of '${registration.driver}' is not pinned in its registration; it runs once a reviewed change pins it and the control plane is deployed`);
|
|
10999
|
-
}
|
|
11000
|
-
if (pin.sourceCommit !== stored.sourceCommit) {
|
|
11001
|
-
throw new ControlPlaneError(422, `release ${sha256} is pinned at ${pin.sourceCommit} and was stored as built from ${stored.sourceCommit}`);
|
|
11002
|
-
}
|
|
11003
|
-
return pin;
|
|
11004
|
-
};
|
|
11076
|
+
// Whether the release is stored, approved and keeps the schema is judged inside
|
|
11077
|
+
// `runStoreDriverRollout`, under its exclusive hold (D-65), never here before it.
|
|
11005
11078
|
const rollOut = async (c, registration, sha256, action) => {
|
|
11006
11079
|
const { flow, ensureScopeStores } = releaseFlow();
|
|
11007
11080
|
const actor = c.get('actor');
|
|
@@ -11016,22 +11089,11 @@ export function createControlPlaneApi(options) {
|
|
|
11016
11089
|
const result = await ensureScopeStores({ ...input, loadRelease });
|
|
11017
11090
|
return result ? result.uploaded > 0 : true;
|
|
11018
11091
|
},
|
|
11019
|
-
ensureCanary: (to) => flow.ensureCanary({ driver: registration.driver, to, loadRelease }),
|
|
11092
|
+
ensureCanary: (to, approvals) => flow.ensureCanary({ driver: registration.driver, to, loadRelease, approvals }),
|
|
11020
11093
|
callCanary: flow.callCanary,
|
|
11021
11094
|
});
|
|
11022
11095
|
return c.json(result, result.complete ? 200 : 202);
|
|
11023
11096
|
};
|
|
11024
|
-
/** Neither a rollout nor a rollback lowers the stored schema: a driver migrates forward only. */
|
|
11025
|
-
const assertSchemaKept = (registration, from, to) => {
|
|
11026
|
-
if (from === null || from === to.sha256)
|
|
11027
|
-
return;
|
|
11028
|
-
const leaving = registration.releases.find((p) => p.sha256 === from);
|
|
11029
|
-
if (!leaving)
|
|
11030
|
-
throw new ControlPlaneError(409, `the current release ${from} of '${registration.driver}' is not pinned, so its schema cannot be judged`);
|
|
11031
|
-
if (to.schema < leaving.schema) {
|
|
11032
|
-
throw new ControlPlaneError(409, `release ${to.sha256} has schema ${to.schema}, below the current release's ${leaving.schema}: objects may already have migrated`);
|
|
11033
|
-
}
|
|
11034
|
-
};
|
|
11035
11097
|
/** Where the platform's rebuild of every script of the driver stands (#2309), if it rebuilds them. */
|
|
11036
11098
|
const rebuildView = async (admin, actor, driver, known) => {
|
|
11037
11099
|
const fingerprint = known ?? (await options.storeReleases?.fingerprint?.());
|
|
@@ -11046,19 +11108,26 @@ export function createControlPlaneApi(options) {
|
|
|
11046
11108
|
const registration = registeredDriver(c.req.param('driver'));
|
|
11047
11109
|
const [state, approvals] = await Promise.all([
|
|
11048
11110
|
c.var.admin.storeDriverState(c.get('actor'), registration.driver),
|
|
11049
|
-
c.var.admin
|
|
11111
|
+
readStoreDriverApprovals(c.var.admin, c.get('actor'), registration),
|
|
11050
11112
|
]);
|
|
11051
11113
|
const rebuild = await rebuildView(c.var.admin, c.get('actor'), registration.driver);
|
|
11052
|
-
|
|
11114
|
+
// `pins` stays for released CLIs (RFC §5 step 3): the releases a move may go to now, one per live approval.
|
|
11115
|
+
const pins = approvals.records
|
|
11116
|
+
.filter((a) => liveApprovalOf(approvals, a.sha256) === a)
|
|
11117
|
+
.map((a) => ({ sha256: a.sha256, sourceCommit: a.sourceCommit, schema: a.schema }));
|
|
11118
|
+
return c.json({ driver: registration.driver, pins, approvals: approvals.records, ...state, ...(rebuild ? { rebuild } : {}) });
|
|
11053
11119
|
});
|
|
11054
|
-
// Store a release's bytes, before or after
|
|
11120
|
+
// Store a release's bytes, before or after a person approves them (D-65). Idempotent per bundle.
|
|
11055
11121
|
app.put('/store-drivers/:driver/releases', async (c) => {
|
|
11056
11122
|
const registration = registeredDriver(c.req.param('driver'));
|
|
11057
11123
|
const actor = await byAPerson(c);
|
|
11058
11124
|
const { flow } = releaseFlow();
|
|
11059
11125
|
const body = await readCappedJson(c.req.raw, STORE_RELEASE_REQUEST_MAX_BYTES);
|
|
11060
11126
|
const release = await storeDriverRelease({ admin: c.var.admin, actor, bytes: flow.bytes, driver: registration.driver, body });
|
|
11061
|
-
|
|
11127
|
+
const live = liveApprovalOf(await readStoreDriverApprovals(c.var.admin, actor, registration), release.sha256);
|
|
11128
|
+
const approved = live !== undefined && live.sourceCommit === release.sourceCommit;
|
|
11129
|
+
// `pinned` stays beside `approved` for released CLIs, meaning the same (RFC §5 step 3).
|
|
11130
|
+
return c.json({ release, pinned: approved, approved }, 201);
|
|
11062
11131
|
});
|
|
11063
11132
|
// The approval is a second, person-only write over bytes the platform already named by digest.
|
|
11064
11133
|
app.post('/store-drivers/:driver/releases/:sha256/approvals', async (c) => {
|
|
@@ -11066,17 +11135,20 @@ export function createControlPlaneApi(options) {
|
|
|
11066
11135
|
const actor = await byAPerson(c, approvalWords);
|
|
11067
11136
|
const sha256 = releaseSha(c.req.param('sha256'));
|
|
11068
11137
|
const body = parseApprovalBody(await readCappedJson(c.req.raw, 64 * 1024));
|
|
11138
|
+
// Refused here, not only by the readers (D-65 §3): a row its basis does not admit could never
|
|
11139
|
+
// run, yet as a live approval it would hold the one slot a corrected approval needs. Fixed text,
|
|
11140
|
+
// no input echoed.
|
|
11141
|
+
if (!storeDriverBasisAdmits(registration.isolationBasis, body.sourceReferences)) {
|
|
11142
|
+
throw new ControlPlaneError(422, 'not a store driver approval: sourceReferences: outside_isolation_basis');
|
|
11143
|
+
}
|
|
11069
11144
|
const flow = options.storeReleases;
|
|
11070
11145
|
if (!flow)
|
|
11071
11146
|
throw new ControlPlaneError(501, 'this control plane cannot read stored release bytes for approval');
|
|
11072
11147
|
const approval = await approveStoredDriverRelease({ admin: c.var.admin, actor, bytes: flow.bytes, driver: registration.driver, sha256, approval: body });
|
|
11073
11148
|
const state = await c.var.admin.storeDriverState(actor, registration.driver);
|
|
11074
11149
|
const approvals = state.current ? await c.var.admin.storeDriverApprovals(actor, registration.driver) : [];
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
?? registration.releases.find((p) => p.sha256 === state.current.sha256)?.schema
|
|
11078
|
-
?? null
|
|
11079
|
-
: null;
|
|
11150
|
+
// The current release's approval facts, revoked or not: they are fixed at its first approval.
|
|
11151
|
+
const currentSchema = state.current ? approvals.find((a) => a.sha256 === state.current.sha256)?.schema ?? null : null;
|
|
11080
11152
|
return c.json({ approval, currentSchema }, 201);
|
|
11081
11153
|
});
|
|
11082
11154
|
app.post('/store-drivers/:driver/releases/:sha256/revoke', async (c) => {
|
|
@@ -11100,20 +11172,19 @@ export function createControlPlaneApi(options) {
|
|
|
11100
11172
|
if (!flow)
|
|
11101
11173
|
throw new ControlPlaneError(501, 'this control plane cannot move the store canary');
|
|
11102
11174
|
const loadRelease = storedReleaseLoader({ admin: c.var.admin, actor, bytes: flow.bytes });
|
|
11103
|
-
|
|
11175
|
+
// Called under the revoke's exclusive hold, so the approvals read here are the ones it judges.
|
|
11176
|
+
const approvals = await readStoreDriverApprovals(c.var.admin, actor, registration);
|
|
11177
|
+
await flow.ensureCanary({ driver: registration.driver, to, loadRelease, approvals });
|
|
11104
11178
|
},
|
|
11105
11179
|
});
|
|
11106
11180
|
return c.json(result, result.complete ? 200 : 202);
|
|
11107
11181
|
});
|
|
11108
|
-
// Roll a stored,
|
|
11182
|
+
// Roll a stored, approved release out: the canary, then every vertical that ever declared the
|
|
11109
11183
|
// driver. Posting the same release again converges a rollout that stopped.
|
|
11110
11184
|
app.post('/store-drivers/:driver/rollouts', async (c) => {
|
|
11111
11185
|
const registration = registeredDriver(c.req.param('driver'));
|
|
11112
11186
|
await byAPerson(c);
|
|
11113
11187
|
const { sha256 } = z.object({ sha256: z.string().regex(/^[0-9a-f]{64}$/) }).strict().parse(await c.req.json());
|
|
11114
|
-
const pin = await pinOf(c, registration, sha256);
|
|
11115
|
-
const { current } = await c.var.admin.storeDriverState(c.get('actor'), registration.driver);
|
|
11116
|
-
assertSchemaKept(registration, current?.sha256 ?? null, pin);
|
|
11117
11188
|
return rollOut(c, registration, sha256, 'rollout');
|
|
11118
11189
|
});
|
|
11119
11190
|
// Return to the immediately previous good release (D-63), never further and never a guess. After
|
|
@@ -11126,8 +11197,6 @@ export function createControlPlaneApi(options) {
|
|
|
11126
11197
|
const { current, rollbackTarget } = await c.var.admin.storeDriverState(c.get('actor'), registration.driver);
|
|
11127
11198
|
if (!current || !rollbackTarget)
|
|
11128
11199
|
throw new ControlPlaneError(409, `driver '${registration.driver}' has no previous good release to roll back to`);
|
|
11129
|
-
const pin = await pinOf(c, registration, rollbackTarget);
|
|
11130
|
-
assertSchemaKept(registration, current.sha256, pin);
|
|
11131
11200
|
return rollOut(c, registration, rollbackTarget, 'rollback');
|
|
11132
11201
|
});
|
|
11133
11202
|
// Run a pass of the platform's rebuild of every script of the driver now (#2309): the scheduled
|