@substrat-run/adapter-cloudflare 0.162.0 → 0.164.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/control-plane-do.d.ts +74 -20
- package/dist/control-plane-do.d.ts.map +1 -1
- package/dist/control-plane-do.js +183 -41
- package/dist/control-plane-do.js.map +1 -1
- package/dist/host.d.ts +90 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +239 -97
- package/dist/host.js.map +1 -1
- package/dist/scope-do.d.ts +36 -0
- package/dist/scope-do.d.ts.map +1 -1
- package/dist/scope-do.js +101 -20
- package/dist/scope-do.js.map +1 -1
- package/package.json +16 -13
package/dist/scope-do.d.ts
CHANGED
|
@@ -59,6 +59,42 @@ type ProjectedRoleRow = {
|
|
|
59
59
|
source: string;
|
|
60
60
|
holder?: 'person' | 'system' | null;
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* #2555: the directory's current permission state for one scope, as a fan-out projects it
|
|
64
|
+
* (`tenantProjection` and the vertical's connection keys), written by a control-plane-full load in
|
|
65
|
+
* its own transaction after the dump's rows are in. The roles are judged there against the state
|
|
66
|
+
* the dump has just brought, with `judge`'s reservations joined with the class's.
|
|
67
|
+
*/
|
|
68
|
+
export type LoadProjection = {
|
|
69
|
+
tenantId: TenantId;
|
|
70
|
+
roles: ProjectedRoleRow[];
|
|
71
|
+
tuples: {
|
|
72
|
+
subject: string;
|
|
73
|
+
relation: string;
|
|
74
|
+
object: string;
|
|
75
|
+
expires_at: string | null;
|
|
76
|
+
revoked_at: string | null;
|
|
77
|
+
}[];
|
|
78
|
+
entitlements: {
|
|
79
|
+
entitlement_key: string;
|
|
80
|
+
expires_at: string | null;
|
|
81
|
+
quota: number | null;
|
|
82
|
+
plan: string | null;
|
|
83
|
+
}[];
|
|
84
|
+
identities: {
|
|
85
|
+
provider: string;
|
|
86
|
+
external_id: string;
|
|
87
|
+
principal_id: string;
|
|
88
|
+
scope_id: string | null;
|
|
89
|
+
}[];
|
|
90
|
+
connectionKeys: {
|
|
91
|
+
connection_id: string;
|
|
92
|
+
provider: string;
|
|
93
|
+
key_id: string;
|
|
94
|
+
public_key: string;
|
|
95
|
+
}[];
|
|
96
|
+
judge: RoleJudge;
|
|
97
|
+
};
|
|
62
98
|
/**
|
|
63
99
|
* #2531: a load's role repair, judged and written in the load's own transaction after the dump's
|
|
64
100
|
* rows are in: the vertical's role set for `tenantId`, projected as `projectRolesLocal` projects it.
|
package/dist/scope-do.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAmDL,KAAK,QAAQ,EAWd,MAAM,yBAAyB,CAAC;AACjC,OAAO,
|
|
1
|
+
{"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"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"}
|
package/dist/scope-do.js
CHANGED
|
@@ -2,7 +2,7 @@ import { REWIND_REFUSED } from './rewind-refusal.js';
|
|
|
2
2
|
import { SYSTEM_DOOR_MOVED } from './system-door.js';
|
|
3
3
|
import { DurableObject } from 'cloudflare:workers';
|
|
4
4
|
import { ATTACHMENT_ADDED, ATTACHMENT_REMOVED, attachmentRecord, domainEvent, domainEventInput, eventId, instant, objectRef, entityObjectRef, assertModuleEmittableType, assertKernelAuthoredType, toWireFailure, grantRefFromProof, principalId, principalBecomeCapabilityInput, scopeId as scopeIdOf, tenantId as tenantIdOf, platformRequestInput, platformRequestId, MAX_PENDING_PLATFORM_REQUESTS, MAX_PENDING_SWEEP_RUNS, SWEEP_RUNS_KIND, SCOPE_DUMP_PAGE_MAX_BYTES, SCOPE_TABLE_PAGE_MAX, SCOPE_QUERY_ROW_MAX, listLimitOf, pageOf, countedPageOf, requestFingerprint, substratError, errorCodeOf, assertReplayableDump, REDRAIN_BATCH, } from '@substrat-run/contracts';
|
|
5
|
-
import { ulid, DO_SQL_LIMITS, unknownRoleError, createUlid, assertAllowed, ConnectionSealingKeyUnavailableError, noSealingKeyMessage, sealTo, assertReadOnlyQuery, entitlementDenial, platformRequestHistoryQuery, platformRequestOf, PLATFORM_REQUEST_COLUMNS, PLATFORM_REQUEST_REDACTION_SQL, platformRequestRedactionParams, platformRequestRedactionQuery, intentPayloadCarriesSubject, redactSubjectJobRuns, eraseSubjectFromModules, moduleErasurePlan, redactSubjectScopeText, assertRowLimit, assertRowOffset, JOB_RUN_PATCH_SQL, JOB_RUN_CLAIM_SQL, JOB_RUN_RENEW_SQL, JOB_RUN_BEGIN_SQL, JOB_RUN_MISS_SQL, JOB_RUN_MISS_SETTLE_SQL, admissionMissOutcome, JOB_LEASE_EXPIRED_NOTE, JOB_STEP_RECORD_SQL, DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, seatScopeTuple, delegatedRevokeSql, writeExplicitTupleIn, grantEntityShapeIn, topUpEntityGrantShapes, applyScopeRoleChange, changeScopeRole, revokeScopeRoles, scopeRoleHolders, assertModuleDeclared, noteDeclaredKeys, noteReservedKeys, SCOPE_SEATED_ROLES_SQL, inUnitRoleRefusal, reservedRoleSetRefusedRole, reservedGrantRefusal, reservedRoleRefusal, reservedShapeRefusal, effectiveRoleGrantQuery, switchRecordedOff, switchSystemSchedules, peerGrantsStatus, systemGrantsStatus, systemScheduleState, systemSwitchedOff, denialListQuery, refusalListQuery, mapRefusalRow, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialSummaryBuckets, PermissionDenied, assertImpersonationWrites, assertModuleEnqueueableKind, impersonationStampOf, createAtomic, NotSearchable, isSearchIndexTable, searchIndexPlans, NotListable, listIndexPlans, moduleMigrations, MIGRATION_DIGEST_FENCE_DDL, MIGRATION_DIGEST_MARK_LEGACY, assertJournalDumpCoherent, assertMigrationSql, migrationDivergence, migrationFailedError, migrationSetDivergence, migrationSteps, planMigrations, listQuery, cursorOf, searchLimit, searchMatchExpression, searchQuery, ATTACHMENT_TEXT_DDL, ATTACHMENT_BYTES_SQL, ATTACHMENT_CONDEMNED_DDL, ATTACHMENT_SWEEP_LICENCE_DDL, ATTACHMENT_SWEEP_LICENCE_TABLE, clearOrphanSweepLicence, condemnLicensedOrphanAttachments, consumeOrphanSweepLicence, licensedOrphanIds, recordOrphanSweepLicence, refuseUnlicensedOrphanSweep, refuseCondemnedAttachment, refuseCondemnedInDump, rowlessAttachmentIds, attachmentRecordOfRow, enqueueAttachmentText, reconcileAttachmentText, queueAttachmentTextBackfill, startAttachmentTextBackfill, recordAttachmentText, searchAttachments, IDEMPOTENCY_DDL, REFUSALS_TABLE_DDL, REFUSALS_INDEX, refusalInsert, refusalOf, markGuardRefusal, REFUSALS_REBUILD, refusalsAdmitGuards, assertIdempotencyKey, assertPermissionKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, entityVersionQuery, entityVersionOf, assertIfMatch, OUTBOX_ENTITY_INDEX, SCHEDULE_STATE_DDL, SCHEDULE_STATE_REBUILD, scheduleStateHasKind, VERTICAL_EVENTS_DDL, EXPORT_HOPS_SQL, emptyImportResult, IMPORT_CURSORS_SQL, IMPORT_CURSOR_OF_SQL, OUTBOX_MARK_SQL, exportedSinceQuery, emittedSinceQuery, emittedReportOf, EMITTED_REPORT_CAP, IMPORT_CURSOR_ADVANCE_SQL, IMPORT_RECORD_SQL, moveImportCursor, CrossVerticalRegistry, exportReadPlan, exportReadQuery, planExportBatch, withheldNote, JOB_RUN_DDL, JOB_RUN_DUE_AT, jobRunListLimit, SYSTEM_DOOR_WAIT, ancestorsWithin, } from '@substrat-run/kernel';
|
|
5
|
+
import { ulid, DO_SQL_LIMITS, unknownRoleError, createUlid, assertAllowed, ConnectionSealingKeyUnavailableError, noSealingKeyMessage, sealTo, assertReadOnlyQuery, entitlementDenial, platformRequestHistoryQuery, platformRequestOf, PLATFORM_REQUEST_COLUMNS, PLATFORM_REQUEST_REDACTION_SQL, platformRequestRedactionParams, platformRequestRedactionQuery, intentPayloadCarriesSubject, redactSubjectJobRuns, eraseSubjectFromModules, moduleErasurePlan, redactSubjectScopeText, assertRowLimit, assertRowOffset, JOB_RUN_PATCH_SQL, JOB_RUN_CLAIM_SQL, JOB_RUN_RENEW_SQL, JOB_RUN_BEGIN_SQL, JOB_RUN_MISS_SQL, JOB_RUN_MISS_SETTLE_SQL, admissionMissOutcome, JOB_LEASE_EXPIRED_NOTE, JOB_STEP_RECORD_SQL, DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, seatScopeTuple, delegatedRevokeSql, writeExplicitTupleIn, grantEntityShapeIn, topUpEntityGrantShapes, applyScopeRoleChange, changeScopeRole, revokeScopeRoles, scopeRoleHolders, assertModuleDeclared, noteDeclaredKeys, noteReservedKeys, SCOPE_SEATED_ROLES_SQL, inUnitRoleRefusal, loadedRoleSetRefusedRole, reservedRoleSetRefusedRole, reservedGrantRefusal, reservedRoleRefusal, reservedShapeRefusal, effectiveRoleGrantQuery, switchRecordedOff, switchSystemSchedules, peerGrantsStatus, systemGrantsStatus, systemScheduleState, systemSwitchedOff, denialListQuery, refusalListQuery, mapRefusalRow, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialSummaryBuckets, PermissionDenied, assertImpersonationWrites, assertModuleEnqueueableKind, impersonationStampOf, createAtomic, NotSearchable, isSearchIndexTable, searchIndexPlans, NotListable, listIndexPlans, moduleMigrations, MIGRATION_DIGEST_FENCE_DDL, MIGRATION_DIGEST_MARK_LEGACY, assertJournalDumpCoherent, assertMigrationSql, migrationDivergence, migrationFailedError, migrationSetDivergence, migrationSteps, planMigrations, listQuery, cursorOf, searchLimit, searchMatchExpression, searchQuery, ATTACHMENT_TEXT_DDL, ATTACHMENT_BYTES_SQL, ATTACHMENT_CONDEMNED_DDL, ATTACHMENT_SWEEP_LICENCE_DDL, ATTACHMENT_SWEEP_LICENCE_TABLE, WIRE_NONCE_DDL, WIRE_NONCE_FULL, claimWireNonceIn, isWireNonceTable, isWireNonceWrite, clearOrphanSweepLicence, condemnLicensedOrphanAttachments, consumeOrphanSweepLicence, licensedOrphanIds, recordOrphanSweepLicence, refuseUnlicensedOrphanSweep, refuseCondemnedAttachment, refuseCondemnedInDump, rowlessAttachmentIds, attachmentRecordOfRow, enqueueAttachmentText, reconcileAttachmentText, queueAttachmentTextBackfill, startAttachmentTextBackfill, recordAttachmentText, searchAttachments, IDEMPOTENCY_DDL, REFUSALS_TABLE_DDL, REFUSALS_INDEX, refusalInsert, refusalOf, markGuardRefusal, REFUSALS_REBUILD, refusalsAdmitGuards, assertIdempotencyKey, assertPermissionKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, entityVersionQuery, entityVersionOf, assertIfMatch, OUTBOX_ENTITY_INDEX, SCHEDULE_STATE_DDL, SCHEDULE_STATE_REBUILD, scheduleStateHasKind, VERTICAL_EVENTS_DDL, EXPORT_HOPS_SQL, emptyImportResult, IMPORT_CURSORS_SQL, IMPORT_CURSOR_OF_SQL, OUTBOX_MARK_SQL, exportedSinceQuery, emittedSinceQuery, emittedReportOf, EMITTED_REPORT_CAP, IMPORT_CURSOR_ADVANCE_SQL, IMPORT_RECORD_SQL, moveImportCursor, CrossVerticalRegistry, exportReadPlan, exportReadQuery, planExportBatch, withheldNote, JOB_RUN_DDL, JOB_RUN_DUE_AT, jobRunListLimit, SYSTEM_DOOR_WAIT, ancestorsWithin, } from '@substrat-run/kernel';
|
|
6
6
|
import { isUpgradeRequest, readSubscription, LIVE_DECIDE_ATTEMPTS, LIVE_FANOUT_LIMIT, LIVE_PRINCIPAL_HEADER, LIVE_SCOPE_HEADER, LIVE_SUBSCRIBE_PATH, LIVE_TENANT_HEADER, LIVE_WITHIN_HEADER, LIVE_EXPIRES_HEADER, LIVE_CLOSE, LIVE_SOCKETS_PER_PRINCIPAL, decodeLiveWithin, liveInstant, liveRefusal, liveRefusalStatus, } from './live-reads.js';
|
|
7
7
|
import { replyOf } from './do-reply.js';
|
|
8
8
|
import { OperationQueue } from './serialization.js';
|
|
@@ -390,6 +390,10 @@ const KERNEL_DDL = `
|
|
|
390
390
|
-- #119: the kernel's authorization for one archive/trash move, read by the derived update
|
|
391
391
|
-- trigger. Shared with the other adapter from @substrat-run/kernel; the comments are there.
|
|
392
392
|
${ENTITY_STATE_MOVES_DDL}
|
|
393
|
+
-- RFC wire-signing §4: the nonces of signed calls this scope accepted, and their count.
|
|
394
|
+
-- Store-local: never dumped, written on the bookkeeping path. Shared with the pure adapter
|
|
395
|
+
-- from @substrat-run/kernel, where the column comments are.
|
|
396
|
+
${WIRE_NONCE_DDL}
|
|
393
397
|
`;
|
|
394
398
|
/**
|
|
395
399
|
* Workers RPC carries a plain `Error`'s MESSAGE faithfully and nothing else. A custom
|
|
@@ -425,6 +429,8 @@ function kernelEmit(ctx, event) {
|
|
|
425
429
|
throw new Error('kernelEmit: not an operation context this host built');
|
|
426
430
|
write(event);
|
|
427
431
|
}
|
|
432
|
+
/** #2555: the `_substrat_meta` keys a control-plane-full load keeps from the destination. */
|
|
433
|
+
const PERMISSION_FLAG_KEYS = ['permission_source', 'entitlements_enforced'];
|
|
428
434
|
/** #2450: a commit or a page naming a stage this store no longer holds. */
|
|
429
435
|
const STAGE_GONE = 'the staged load is not here any more (another carry replaced it, or it was already loaded); nothing was loaded';
|
|
430
436
|
/** A competing page zero must leave the stage a live copy owns intact. */
|
|
@@ -551,11 +557,11 @@ class WriteRevision {
|
|
|
551
557
|
if (write)
|
|
552
558
|
this.written++;
|
|
553
559
|
if (this.keeping && write) {
|
|
554
|
-
// Bookkeeping takes the copy-marker insert
|
|
555
|
-
// else: any other write here would be one the revision never saw, which is the hole this
|
|
560
|
+
// Bookkeeping takes the copy-marker insert, the lifecycle delivery (#1713) and the wire
|
|
561
|
+
// nonce claim's own statements (RFC wire-signing §4) and nothing else: any other write here would be one the revision never saw, which is the hole this
|
|
556
562
|
// class exists to close.
|
|
557
|
-
if (!isCopyMarkInsert(query) && !isLifecycleWrite(query)) {
|
|
558
|
-
throw substratError('internal', `the bookkeeping path takes only the copy-marker insert
|
|
563
|
+
if (!isCopyMarkInsert(query) && !isLifecycleWrite(query) && !isWireNonceWrite(query)) {
|
|
564
|
+
throw substratError('internal', `the bookkeeping path takes only the copy-marker insert, the lifecycle delivery and the wire nonce claim (#1722, #1713), not: ${query.slice(0, 80)}`);
|
|
559
565
|
}
|
|
560
566
|
return raw.exec(query, ...bindings);
|
|
561
567
|
}
|
|
@@ -582,8 +588,9 @@ class WriteRevision {
|
|
|
582
588
|
* r10). That only ever restricts what the store may run and changes no data, so it advances no
|
|
583
589
|
* write revision: a backfill that marks a carry's source between its export and its wipe must
|
|
584
590
|
* not read as a write the carry did not copy (that would keep the copy for nothing). Enforced,
|
|
585
|
-
* not trusted: any write in here but the marker insert (`isCopyMarkInsert`)
|
|
586
|
-
* delivery (`isLifecycleWrite`, #1713)
|
|
591
|
+
* not trusted: any write in here but the marker insert (`isCopyMarkInsert`), the lifecycle
|
|
592
|
+
* delivery (`isLifecycleWrite`, #1713) or a wire nonce claim (`isWireNonceWrite`, RFC
|
|
593
|
+
* wire-signing §4: a nonce describes a call this store accepted, not its data) throws. Clearing a
|
|
587
594
|
* marker is NOT bookkeeping (r11): it loosens the store, so it is a write a carry fences on.
|
|
588
595
|
*/
|
|
589
596
|
bookkeeping(run) {
|
|
@@ -3897,9 +3904,9 @@ options = {}) {
|
|
|
3897
3904
|
* The answer is a fixed sentence naming the role key only, whoever reads it. The rule's own
|
|
3898
3905
|
* wording is the pre-check's, which a caller still asks first for the common case.
|
|
3899
3906
|
*/
|
|
3900
|
-
refuseRoleSetInUnit(tenantId, roles, judge) {
|
|
3907
|
+
refuseRoleSetInUnit(tenantId, roles, { judge, loaded }) {
|
|
3901
3908
|
const state = this.roleStateSync(tenantId);
|
|
3902
|
-
const refused = reservedRoleSetRefusedRole(roles, { reserved: new Set([...judge.reserved, ...this.reservedKeys]), declared: new Set([...judge.declared, ...this.declaredKeys]) }, { roles: state.roles, seated: new Set(state.seated), ...(state.dropped ? { dropped: state.dropped } : {}) });
|
|
3909
|
+
const refused = (loaded ? loadedRoleSetRefusedRole : reservedRoleSetRefusedRole)(roles, { reserved: new Set([...judge.reserved, ...this.reservedKeys]), declared: new Set([...judge.declared, ...this.declaredKeys]) }, { roles: state.roles, seated: new Set(state.seated), ...(state.dropped ? { dropped: state.dropped } : {}) });
|
|
3903
3910
|
if (refused === null)
|
|
3904
3911
|
return;
|
|
3905
3912
|
const error = substratError('forbidden', inUnitRoleRefusal(refused.key));
|
|
@@ -4929,6 +4936,24 @@ options = {}) {
|
|
|
4929
4936
|
});
|
|
4930
4937
|
return marked;
|
|
4931
4938
|
}
|
|
4939
|
+
/**
|
|
4940
|
+
* RFC wire-signing §4: spend a signed call's nonce in this scope's own store, the claim the
|
|
4941
|
+
* platform's entry makes through the vertical's host (`claimWireNonceLocal`) after every other
|
|
4942
|
+
* check of the envelope has passed. `true` once per nonce; `false` for one already spent, or a
|
|
4943
|
+
* claim after `until`. A store at its cap throws, after its purge committed, which the entry
|
|
4944
|
+
* answers as the store's failure. In storage, so it survives an eviction. Bookkeeping, like the
|
|
4945
|
+
* copy marker: a nonce describes a call this store accepted, not its data, so it advances no
|
|
4946
|
+
* write revision a carry fences on, and no dump carries it.
|
|
4947
|
+
*/
|
|
4948
|
+
claimWireNonce(nonce, until) {
|
|
4949
|
+
let answer;
|
|
4950
|
+
this.revision.transactionSync(() => {
|
|
4951
|
+
answer = this.revision.bookkeeping(() => claimWireNonceIn(this.switchSql(), nonce, until, Math.floor(Date.now() / 1000)));
|
|
4952
|
+
});
|
|
4953
|
+
if (answer === 'full')
|
|
4954
|
+
throw new Error(WIRE_NONCE_FULL);
|
|
4955
|
+
return answer;
|
|
4956
|
+
}
|
|
4932
4957
|
/**
|
|
4933
4958
|
* Clear a mistaken copy classification (#2005, `clearCopyMarker`); a load's events mark stays,
|
|
4934
4959
|
* so copied work still never runs here (#2009). A
|
|
@@ -5328,7 +5353,8 @@ options = {}) {
|
|
|
5328
5353
|
* The tables a dump carries, by name. The derived search index (#827) is excluded: its shadow
|
|
5329
5354
|
* tables cannot be replayed — this host answers "object name reserved for internal use" — and
|
|
5330
5355
|
* it is recomputable, so `importDump` rebuilds it from the loaded rows. A dump carries the rows,
|
|
5331
|
-
* never the index over them. A staged load's own tables (#2450) are this store's, not its data
|
|
5356
|
+
* never the index over them. A staged load's own tables (#2450) are this store's, not its data,
|
|
5357
|
+
* and so is the wire nonce store (RFC wire-signing §4): a load builds it empty.
|
|
5332
5358
|
*/
|
|
5333
5359
|
dumpTableDefs() {
|
|
5334
5360
|
return this.sql
|
|
@@ -5336,7 +5362,7 @@ options = {}) {
|
|
|
5336
5362
|
WHERE type = 'table' AND name NOT GLOB 'sqlite_*' AND name NOT GLOB '_cf_*'
|
|
5337
5363
|
AND name NOT GLOB '${STAGE_GLOB}' AND sql IS NOT NULL
|
|
5338
5364
|
ORDER BY name`)
|
|
5339
|
-
.toArray().filter(({ name }) => !isSearchIndexTable(name));
|
|
5365
|
+
.toArray().filter(({ name }) => !isSearchIndexTable(name) && !isWireNonceTable(name));
|
|
5340
5366
|
}
|
|
5341
5367
|
/**
|
|
5342
5368
|
* One page of `exportDumpStamped` (#2450): the tables in the same order, each table's rows in
|
|
@@ -5609,7 +5635,7 @@ options = {}) {
|
|
|
5609
5635
|
* in memory at once. `stageId` names the staged load the rows come from: checked inside the
|
|
5610
5636
|
* load's transaction, and its tables dropped in it once the rows are in.
|
|
5611
5637
|
*/
|
|
5612
|
-
async loadTables(tables, destScopeId, { switchOff, sourceScopeId, exact, loadStamp, expect, resolveKept, markCopy, provisionedFor, fence, roleRepair, onRolesProjected, } = {}, stageId) {
|
|
5638
|
+
async loadTables(tables, destScopeId, { switchOff, sourceScopeId, exact, loadStamp, expect, resolveKept, markCopy, provisionedFor, fence, roleRepair, onRolesProjected, projection, keepPermissionFlags, } = {}, stageId) {
|
|
5613
5639
|
// The WHOLE drop-then-replay runs under deferred foreign keys, in one transaction.
|
|
5614
5640
|
//
|
|
5615
5641
|
// Two distinct FK hazards, and both need the deferral — the second is why it cannot
|
|
@@ -5632,8 +5658,10 @@ options = {}) {
|
|
|
5632
5658
|
// rather than failing a restore over data about to be recomputed.
|
|
5633
5659
|
// Matched without case, as SQLite resolves a table name (#1883 review).
|
|
5634
5660
|
// #1686: a copy into another scope id loads no capability rows; a same-scope restore keeps them.
|
|
5661
|
+
// The wire nonce store is this store's own (RFC wire-signing §4): never replayed, so a load
|
|
5662
|
+
// starts it empty.
|
|
5635
5663
|
const replayable = tables
|
|
5636
|
-
.filter((t) => !isSearchIndexTable(t.name.toLowerCase()))
|
|
5664
|
+
.filter((t) => !isSearchIndexTable(t.name.toLowerCase()) && !isWireNonceTable(t.name))
|
|
5637
5665
|
.map((t) => (loadDropsRowsOf(t.name, destScopeId, sourceScopeId) ? { ...t, rows: () => [] } : t));
|
|
5638
5666
|
// The dump is untrusted input (#1143). `SqlStorage.exec` runs every statement
|
|
5639
5667
|
// in the string it is given, so a `ddl` with anything appended to its CREATE
|
|
@@ -5649,7 +5677,10 @@ options = {}) {
|
|
|
5649
5677
|
// #2531: a reaped store takes no role repair, judged or not, as `projectInUnit` drops a
|
|
5650
5678
|
// projection there: the load comes back with no roles, no receipt and no enforcement flip,
|
|
5651
5679
|
// as it did before the repair rode the load. Read before the unit, as `projectInUnit` reads it.
|
|
5652
|
-
|
|
5680
|
+
// #2555: the directory's projection is dropped there too, for the same reason.
|
|
5681
|
+
const reaped = roleRepair || projection ? await this.isReaped() : false;
|
|
5682
|
+
const repair = roleRepair && !reaped ? roleRepair : undefined;
|
|
5683
|
+
const directory = projection && !reaped ? projection : undefined;
|
|
5653
5684
|
const switched = await this.revision.transaction(async () => {
|
|
5654
5685
|
// #1722: first, before any read: a store a reap has since emptied must refuse the late load
|
|
5655
5686
|
// too, and the check is against this store's clock, inside the load's own transaction.
|
|
@@ -5679,6 +5710,8 @@ options = {}) {
|
|
|
5679
5710
|
return { name: t.name, columns: ['id'], rows: at < 0 ? [] : Array.from(t.rows(), (row) => [row[at]]) };
|
|
5680
5711
|
}));
|
|
5681
5712
|
const receiptBefore = this.provisionedFor();
|
|
5713
|
+
// #2555: this store's own permission flags, read before the drops take them.
|
|
5714
|
+
const flagsBefore = keepPermissionFlags ? PERMISSION_FLAG_KEYS.map((key) => [key, this.metaValue(key)]) : [];
|
|
5682
5715
|
if (provisionedFor !== undefined && receiptBefore !== null && receiptBefore !== provisionedFor) {
|
|
5683
5716
|
const refused = substratError('conflict', tenantReceiptRefusal(receiptBefore, provisionedFor));
|
|
5684
5717
|
tenantRefusals.add(refused);
|
|
@@ -5776,6 +5809,15 @@ options = {}) {
|
|
|
5776
5809
|
if (receipt !== null) {
|
|
5777
5810
|
this.sql.exec(`INSERT INTO _substrat_meta (key, value) VALUES (?, ?)`, PROVISIONED_FOR_KEY, receipt);
|
|
5778
5811
|
}
|
|
5812
|
+
// #2555: which reader the checker uses, and whether entitlements are enforced, are this
|
|
5813
|
+
// store's, as its receipt is: the dump's values describe the scope it was captured from,
|
|
5814
|
+
// when it was. A flag this store did not hold stays unset. The projection below may then
|
|
5815
|
+
// move them as a fan-out would.
|
|
5816
|
+
for (const [key, value] of flagsBefore) {
|
|
5817
|
+
this.sql.exec(`DELETE FROM _substrat_meta WHERE key = ?`, key);
|
|
5818
|
+
if (value !== null)
|
|
5819
|
+
this.sql.exec(`INSERT INTO _substrat_meta (key, value) VALUES (?, ?)`, key, value);
|
|
5820
|
+
}
|
|
5779
5821
|
// #1722: whatever stamp an export read here no longer describes this store, so every load
|
|
5780
5822
|
// replaces it: with the carry's own, or with none. And the write revision moves on from
|
|
5781
5823
|
// where it stood before the drops, so it never goes back.
|
|
@@ -5829,9 +5871,19 @@ options = {}) {
|
|
|
5829
5871
|
// seats the dump has just brought (`refuseRoleSetInUnit`), then projected exactly as
|
|
5830
5872
|
// `projectRolesLocal` projects it. A refusal rolls the whole load back, so the scope keeps
|
|
5831
5873
|
// its rows and a staged load keeps its stage; an admitted load lands with its repair.
|
|
5832
|
-
|
|
5874
|
+
// #2555: a control-plane-full load writes the directory's current state the same way, in the
|
|
5875
|
+
// same place: the tenant's roles, tuples, entitlements, identity links and the vertical's
|
|
5876
|
+
// connection keys, each a full replace as a fan-out writes it, so nothing the directory has
|
|
5877
|
+
// since revoked comes back with the dump. Scope-level tuples (the seats) are not touched.
|
|
5878
|
+
// The roles are judged only where the loaded state has a part in them (`loaded`).
|
|
5879
|
+
if (repair || directory) {
|
|
5833
5880
|
try {
|
|
5834
|
-
|
|
5881
|
+
if (repair)
|
|
5882
|
+
this.projectionBody({ judge: repair.judge }, repair.tenantId, repair.roles, []);
|
|
5883
|
+
else if (directory) {
|
|
5884
|
+
const { judge, tenantId, roles, tuples, entitlements, identities, connectionKeys } = directory;
|
|
5885
|
+
this.projectionBody({ judge, loaded: true }, tenantId, roles, tuples, entitlements, undefined, identities, connectionKeys);
|
|
5886
|
+
}
|
|
5835
5887
|
}
|
|
5836
5888
|
catch (e) {
|
|
5837
5889
|
// The projection's own tenant refusal (its receipt check) is a tenant refusal of the
|
|
@@ -5914,6 +5966,35 @@ options = {}) {
|
|
|
5914
5966
|
async importDumpJudged(tables, destScopeId, opts, roleRepair) {
|
|
5915
5967
|
return this.judgedLoad((onRolesProjected) => this.checkedLoad(() => this.importDump(tables, destScopeId, { ...opts, roleRepair, onRolesProjected })));
|
|
5916
5968
|
}
|
|
5969
|
+
/**
|
|
5970
|
+
* #2555: a control-plane-full restore or import. `importDump`, with this store's permission flags
|
|
5971
|
+
* kept through it and, when `projection` is given (a host that projects permissions into its
|
|
5972
|
+
* scopes), the directory's current state judged and projected last in the load's transaction. A
|
|
5973
|
+
* refusal rolls the whole load back. The role judgement's own is answered as `refusedRoles`, so
|
|
5974
|
+
* the host can tell it from any other; every other is answered as data so its code survives the
|
|
5975
|
+
* hop. A method of its own, so a host meeting a ScopeDO from before it is refused by the
|
|
5976
|
+
* runtime, nothing loaded, rather than loading the dump's permission state as is.
|
|
5977
|
+
*/
|
|
5978
|
+
importDumpProjected(tables, destScopeId, opts, projection) {
|
|
5979
|
+
return replyOf(async () => {
|
|
5980
|
+
try {
|
|
5981
|
+
return {
|
|
5982
|
+
switchedOff: await this.importDump(tables, destScopeId, {
|
|
5983
|
+
switchOff: opts.switchOff,
|
|
5984
|
+
sourceScopeId: opts.sourceScopeId,
|
|
5985
|
+
exact: opts.exact,
|
|
5986
|
+
keepPermissionFlags: true,
|
|
5987
|
+
...(projection ? { projection } : {}),
|
|
5988
|
+
}),
|
|
5989
|
+
};
|
|
5990
|
+
}
|
|
5991
|
+
catch (e) {
|
|
5992
|
+
if (e instanceof Error && roleRefusals.has(e))
|
|
5993
|
+
return { refusedRoles: e.message };
|
|
5994
|
+
throw e;
|
|
5995
|
+
}
|
|
5996
|
+
});
|
|
5997
|
+
}
|
|
5917
5998
|
/** #2531: `commitStagedDumpChecked` with the role repair in the load's transaction (`importDumpJudged`). */
|
|
5918
5999
|
async commitStagedDumpJudged(stageId, destScopeId, opts, roleRepair) {
|
|
5919
6000
|
return this.judgedLoad((onRolesProjected) => this.commitStagedDumpChecked(stageId, destScopeId, { ...opts, roleRepair, onRolesProjected }));
|
|
@@ -7176,13 +7257,13 @@ options = {}) {
|
|
|
7176
7257
|
// The whole body is synchronous storage work, so it is ONE transaction (#1738): the queue
|
|
7177
7258
|
// serializes but does not roll back, and a role insert that throws part-way must not leave
|
|
7178
7259
|
// the receipt (or a half-replaced role set) behind for `servesTenant` to trust.
|
|
7179
|
-
return this.queue.enqueue(() => this.revision.transactionSync(() => this.projectionBody(judge, ...args)));
|
|
7260
|
+
return this.queue.enqueue(() => this.revision.transactionSync(() => this.projectionBody(judge && { judge }, ...args)));
|
|
7180
7261
|
}
|
|
7181
7262
|
/**
|
|
7182
7263
|
* The projection's writes, synchronous, for a unit the caller holds: `projectInUnit`'s, or a
|
|
7183
7264
|
* load's (`loadTables`, #2531), so a restore's role repair lands in the load's transaction.
|
|
7184
7265
|
*/
|
|
7185
|
-
projectionBody(
|
|
7266
|
+
projectionBody(judgement, ...[tenantId, roles, tuples, entitlements, scopeTuples, identities, connectionKeys, switchOff, serviceSubjects]) {
|
|
7186
7267
|
// #1738: the receipt `servesTenant` reads. First writer wins: absent or equal is written,
|
|
7187
7268
|
// a receipt for ANOTHER tenant refuses the whole projection (K-3), before a single row
|
|
7188
7269
|
// moves, so a misdirected projection can never re-point a scope or leave its roles behind.
|
|
@@ -7198,8 +7279,8 @@ options = {}) {
|
|
|
7198
7279
|
: `applyProjection ${foreignTenant(held, tenantId)}`);
|
|
7199
7280
|
}
|
|
7200
7281
|
// #2531: judged before the first write of the unit, on what the scope holds now.
|
|
7201
|
-
if (
|
|
7202
|
-
this.refuseRoleSetInUnit(tenantId, roles.map(roleShapeOfRow),
|
|
7282
|
+
if (judgement)
|
|
7283
|
+
this.refuseRoleSetInUnit(tenantId, roles.map(roleShapeOfRow), judgement);
|
|
7203
7284
|
// Written before any guard below can return early, so every projection leaves it.
|
|
7204
7285
|
this.sql.exec(`INSERT OR REPLACE INTO _substrat_meta (key, value) VALUES (?, ?)`, PROVISIONED_FOR_KEY, tenantId);
|
|
7205
7286
|
// #2531: a role the new set leaves out is TOMBSTONED, not deleted: it keeps the keys and the
|