@substrat-run/adapter-cloudflare 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/checker.d.ts.map +1 -1
- package/dist/checker.js +6 -1
- package/dist/checker.js.map +1 -1
- package/dist/control-plane-do.d.ts +8 -1
- package/dist/control-plane-do.d.ts.map +1 -1
- package/dist/control-plane-do.js +14 -4
- package/dist/control-plane-do.js.map +1 -1
- package/dist/host.d.ts +24 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +110 -29
- package/dist/host.js.map +1 -1
- package/dist/scope-do.d.ts.map +1 -1
- package/dist/scope-do.js +67 -12
- package/dist/scope-do.js.map +1 -1
- package/package.json +9 -9
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;AA4DnD,OAAO,EA2DL,KAAK,SAAS,EAsBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAqGtB,MAAM,sBAAsB,CAAC;AAoD9B,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAkBL,kBAAkB,
|
|
1
|
+
{"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AA4DnD,OAAO,EA2DL,KAAK,SAAS,EAsBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAqGtB,MAAM,sBAAsB,CAAC;AAoD9B,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAkBL,kBAAkB,EA4EnB,MAAM,sBAAsB,CAAC;AA0B9B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAoFD,oFAAoF;AACpF,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AA+jB5E;;;GAGG;AACH,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;AAmBtD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,SAAS,CAOxD;AAED,gHAAgH;AAChH,OAAO,EAAE,kBAAkB,EAAE,CAAC;AA2B9B,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAAE,EAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,GACxD,KAAK,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,UAAU,KAAK,aAAa,CAAC,UAAU,CAAC,CA2jO7E"}
|
package/dist/scope-do.js
CHANGED
|
@@ -7,7 +7,7 @@ import { isUpgradeRequest, readSubscription, LIVE_DECIDE_ATTEMPTS, LIVE_FANOUT_L
|
|
|
7
7
|
import { replyOf } from './do-reply.js';
|
|
8
8
|
import { OperationQueue } from './serialization.js';
|
|
9
9
|
import { assertNoWorkerdStorage, doScopedSql, doBuiltColumnsOf, doRedactionSql, doSpineSql } from './sql.js';
|
|
10
|
-
import { actorOf, admitPeer, collectPeers, switchPeer, peerSubjectRef, subjectGrantState, assertNoSecret, CAPABILITY_DDL, COPY_ORIGIN_DDL, ENTITY_STATE_MOVES_DDL, SWITCH_FENCES_DDL, TABLE_OWNERS_DDL, assertMigrationLeavesLedgerAlone, recordOwnershipSteps, runMigrationStatements, splitSqlStatements, executableSqlStatements, blankSqlComments, CAPABILITY_EXCHANGE_OPERATION, CAPABILITY_BECOME_MINT_OPERATION, becomeMintCheck, mintBecomeCapabilityAsPrincipal, revokeBecomeCapabilityAsPrincipal, readBecomeLinkStates, ENTITY_UNBIND_OPERATION, allowedFor, unbindEntityHolderBounded, capabilityAttachmentWriteRefused, createCapabilityVerbs, createEntityEdgeVerbs, createEntityStateVerbs, createTrashedReads, CONTINUATION_POSITION_CAP, visibleContinuation, searchStateWhere, uncheckedView, addStatePlans, assertNoCallerPurge, isUnreachableParent, PURGE_BATCH, heldPurgePass, lifecycleRefusal, purgeHeldBy, purgeDueOf, purgeOnlyKeysOf, runPurgePass, refuseTrashedTarget, assertBoundOperations, registerTrashTargets, withheldKeysFor, statefulTablesOf, afterMigration, afterRuntimeDdl, derivesAnything, repairDerivedObjects, StateColumnLost, exchangeCapability, guardSecrets, mintBecomeCapability, redactSecrets, redactSecretText, moduleLog, asyncInvocationId, asyncLinePass, resolveCapabilitySession, revokeCapabilityAsPlatform, readCapabilityPage, domainEventOf, facetEvents, readDeadLetters, readLifecycleFlow, readOperationSeries, readHistory, readInvocation, readUndrainedOutbox, walkEventCause, walkEventEffects, } from '@substrat-run/kernel';
|
|
10
|
+
import { actorOf, admitPeer, collectPeers, switchPeer, peerSubjectRef, subjectGrantState, assertNoSecret, CAPABILITY_DDL, COPY_ORIGIN_DDL, ENTITY_STATE_MOVES_DDL, SWITCH_FENCES_DDL, TABLE_OWNERS_DDL, assertMigrationLeavesLedgerAlone, recordOwnershipSteps, runMigrationStatements, splitSqlStatements, executableSqlStatements, blankSqlComments, CAPABILITY_EXCHANGE_OPERATION, CAPABILITY_BECOME_MINT_OPERATION, becomeMintCheck, mintBecomeCapabilityAsPrincipal, revokeBecomeCapabilityAsPrincipal, readBecomeLinkStates, ENTITY_UNBIND_OPERATION, allowedFor, unbindEntityHolderBounded, capabilityAttachmentWriteRefused, createCapabilityVerbs, createEntityEdgeVerbs, createEntityStateVerbs, createTrashedReads, CONTINUATION_POSITION_CAP, visibleContinuation, searchStateWhere, uncheckedView, addStatePlans, addUpdatedStampPlans, watchUpdatedStamps, assertNoCallerPurge, isUnreachableParent, PURGE_BATCH, heldPurgePass, lifecycleRefusal, purgeHeldBy, purgeDueOf, purgeOnlyKeysOf, runPurgePass, refuseTrashedTarget, assertBoundOperations, registerTrashTargets, withheldKeysFor, statefulTablesOf, afterMigration, afterRuntimeDdl, derivesAnything, repairDerivedObjects, StateColumnLost, exchangeCapability, guardSecrets, mintBecomeCapability, redactSecrets, redactSecretText, moduleLog, asyncInvocationId, asyncLinePass, resolveCapabilitySession, revokeCapabilityAsPlatform, readCapabilityPage, domainEventOf, facetEvents, readDeadLetters, readLifecycleFlow, readOperationSeries, readHistory, readInvocation, readUndrainedOutbox, walkEventCause, walkEventEffects, } from '@substrat-run/kernel';
|
|
11
11
|
import { COPY_RESTORE_FENCE_LAPSED, copyRestoreFenceLapsed } from '@substrat-run/kernel';
|
|
12
12
|
import { createDoTupleChecker, createLocalControlPlaneReader, scopeTupleReader } from './checker.js';
|
|
13
13
|
import { settlePlatformRequestIn } from '@substrat-run/kernel';
|
|
@@ -352,9 +352,9 @@ const KERNEL_DDL = `
|
|
|
352
352
|
-- #1575: attachment text and its FTS5 index, shared from @substrat-run/kernel. After
|
|
353
353
|
-- _substrat_attachments, whose delete trigger it adds.
|
|
354
354
|
${ATTACHMENT_TEXT_DDL}
|
|
355
|
-
-- #901: an entity's version is the ULID of the last event about it
|
|
356
|
-
--
|
|
357
|
-
--
|
|
355
|
+
-- #901: an entity's version is the ULID of the last event about it (a state move
|
|
356
|
+
-- excepted, #2191), so the newest id per (entity_type, entity_id) is the read. The id
|
|
357
|
+
-- column sits last so SQLite walks the matched range backwards instead of aggregating.
|
|
358
358
|
${OUTBOX_ENTITY_INDEX}
|
|
359
359
|
-- #116: the request-dedupe table, kernel-owned so no vertical migrates for it.
|
|
360
360
|
${IDEMPOTENCY_DDL}
|
|
@@ -746,6 +746,8 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
746
746
|
listPlans = new Map();
|
|
747
747
|
/** #119: entity type → its archive/trash plan, from every registered module. */
|
|
748
748
|
statePlans = new Map();
|
|
749
|
+
/** #2166: entity type → the updated stamps the host holds, from every registered module. */
|
|
750
|
+
updatedStampPlans = new Map();
|
|
749
751
|
/** #2068: each registered module's erasure, in registration order — what `redactSubject` runs. */
|
|
750
752
|
erasurePlans = [];
|
|
751
753
|
/** entityType → the declared attachment gate (#473): read key + write key (default: read). */
|
|
@@ -869,6 +871,7 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
869
871
|
}
|
|
870
872
|
// #119: the archive/trash plans, refusing a second owner of one entity type.
|
|
871
873
|
addStatePlans(this.statePlans, manifest.id, manifest.entityStates, manifest.permissions);
|
|
874
|
+
addUpdatedStampPlans(this.updatedStampPlans, manifest.id, manifest.updatedStamps);
|
|
872
875
|
// #811: the list indexes `lists` declares, same placement and same reason.
|
|
873
876
|
for (const plan of listIndexPlans(manifest.id, manifest.lists, manifest.entityStates)) {
|
|
874
877
|
const existing = this.listPlans.get(plan.entityType);
|
|
@@ -1367,7 +1370,10 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
1367
1370
|
// As the producer's principal: real checks against the grants this vertical's
|
|
1368
1371
|
// `peers` gave it; its emits carry `{ vertical, scope }` and what they passed.
|
|
1369
1372
|
// What it emits was emitted BECAUSE of the producer's event (#1237).
|
|
1370
|
-
|
|
1373
|
+
const ctx = this.importContext(tenantId, scopeId, peerSubject, { causedBy: e.id, invocationId: unit.invocationId });
|
|
1374
|
+
const stamps = watchUpdatedStamps(doSpineSql(this.sql), this.updatedStampPlans);
|
|
1375
|
+
await imp.handler(ctx, event);
|
|
1376
|
+
stamps?.assertHeld(ctx, `${imp.moduleId} consuming ${e.type}`);
|
|
1371
1377
|
this.sql.exec(`INSERT INTO _substrat_deliveries (event_id, consumer_module, delivered_at, invocation_id)
|
|
1372
1378
|
VALUES (?, ?, ?, NULL)`, e.id, imp.moduleId, new Date().toISOString());
|
|
1373
1379
|
});
|
|
@@ -1684,14 +1690,16 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
1684
1690
|
return { ...one, done: one.next === null };
|
|
1685
1691
|
}
|
|
1686
1692
|
let [toppedUp, retired, retirementsLeftOpen, from] = [0, 0, 0, null];
|
|
1693
|
+
let boundEver;
|
|
1687
1694
|
do {
|
|
1688
1695
|
const one = await pass(from);
|
|
1689
1696
|
toppedUp += one.toppedUp;
|
|
1690
1697
|
retired += one.retired;
|
|
1691
1698
|
retirementsLeftOpen += one.retirementsLeftOpen;
|
|
1692
1699
|
from = one.next;
|
|
1700
|
+
boundEver = one.boundEver;
|
|
1693
1701
|
} while (from);
|
|
1694
|
-
return { toppedUp, retired, retirementsLeftOpen, next: null, done: true };
|
|
1702
|
+
return { toppedUp, retired, retirementsLeftOpen, next: null, done: true, ...(boundEver ? { boundEver } : {}) };
|
|
1695
1703
|
}
|
|
1696
1704
|
/**
|
|
1697
1705
|
* The dispatch capability's admission (#726 remedy B): this delivery may read the
|
|
@@ -2061,9 +2069,12 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
2061
2069
|
: undefined;
|
|
2062
2070
|
// #119: the host's trash refusal, before the guards and the handler — the pure
|
|
2063
2071
|
// adapter's, through the same kernel function.
|
|
2064
|
-
|
|
2072
|
+
// #2166: what the guards and the handler emit about a stamped entity, held below.
|
|
2073
|
+
const stamps = watchUpdatedStamps(doSpineSql(this.sql), this.updatedStampPlans);
|
|
2074
|
+
await refuseTrashedTarget({ sql: doSpineSql(this.sql), plans: this.statePlans, check: ctx.check, now: ctx.now() }, operation, target, parsed, purging ? { gate: this.purgeGateFacts(systemDoor.moduleId, tenantId) } : undefined);
|
|
2065
2075
|
await this.runGuards(operation, ctx, parsed);
|
|
2066
2076
|
result = await handler(ctx, parsed);
|
|
2077
|
+
stamps?.assertHeld(ctx, operation);
|
|
2067
2078
|
if (guardedRef && invokeOptions?.ifMatch !== undefined) {
|
|
2068
2079
|
assertIfMatch(guardedRef, invokeOptions.ifMatch, seen ?? null);
|
|
2069
2080
|
}
|
|
@@ -2564,6 +2575,12 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
2564
2575
|
* principal holding that root in several tabs is one check, fanned out to each socket.
|
|
2565
2576
|
*/
|
|
2566
2577
|
const rootChecks = new Map();
|
|
2578
|
+
/**
|
|
2579
|
+
* A row beneath a checked root that the key reaches only through a record whose shape
|
|
2580
|
+
* protects it (#2177): decided by the subscriber's own check of the key on the row, asked
|
|
2581
|
+
* once per (principal, key, row) per pass, under the same epoch rules as `rootChecks`.
|
|
2582
|
+
*/
|
|
2583
|
+
const rowChecks = new Map();
|
|
2567
2584
|
const ancestors = new Map();
|
|
2568
2585
|
const openEpoch = (now) => ({
|
|
2569
2586
|
writes: this.revision.statementsWritten,
|
|
@@ -2577,17 +2594,26 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
2577
2594
|
if (!holds(epoch, now)) {
|
|
2578
2595
|
epoch = openEpoch(now);
|
|
2579
2596
|
rootChecks.clear();
|
|
2597
|
+
rowChecks.clear();
|
|
2580
2598
|
ancestors.clear();
|
|
2581
2599
|
}
|
|
2582
2600
|
return epoch;
|
|
2583
2601
|
};
|
|
2584
|
-
|
|
2585
|
-
|
|
2602
|
+
/**
|
|
2603
|
+
* Is the row beneath the root? With a `key` (#2177), only along a path the key's own check
|
|
2604
|
+
* would climb: it stops at a record whose shape protects the key. Memoised per (row, key),
|
|
2605
|
+
* since the stop depends on it.
|
|
2606
|
+
*/
|
|
2607
|
+
const reaches = async (row, root, now, key) => {
|
|
2608
|
+
const memo = `${row.id}\n${key ?? ''}`;
|
|
2609
|
+
let up = ancestors.get(memo);
|
|
2586
2610
|
if (!up) {
|
|
2587
2611
|
// A walk that cannot answer is a frame not sent — the same fail-closed rule as the check.
|
|
2588
2612
|
// Walked at the decision's own instant; remembered only while its epoch holds.
|
|
2589
|
-
up =
|
|
2590
|
-
|
|
2613
|
+
up = Promise.resolve()
|
|
2614
|
+
.then(() => ancestorsWithin(parents, { entityType: row.entity_type, entityId: row.entity_id }, now, key))
|
|
2615
|
+
.catch(() => new Set());
|
|
2616
|
+
ancestors.set(memo, up);
|
|
2591
2617
|
}
|
|
2592
2618
|
return (await up).has(`${root.entityType}:${root.entityId}`);
|
|
2593
2619
|
};
|
|
@@ -2655,7 +2681,13 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
2655
2681
|
rootVerdict = undefined;
|
|
2656
2682
|
// Narrowing first: it is memoised across sockets, and a row outside the root
|
|
2657
2683
|
// is out whatever the principal holds.
|
|
2658
|
-
|
|
2684
|
+
// A checked root walks with its key first (#2177). The keyed walk is the plain walk with
|
|
2685
|
+
// stops, so whatever it reaches the plain walk reaches too: a hit settles "beneath the
|
|
2686
|
+
// root" in one walk, and only a miss needs the plain walk to tell a row outside the root
|
|
2687
|
+
// from one the key reaches only through a record whose shape protects it.
|
|
2688
|
+
const key = within?.checked;
|
|
2689
|
+
const keyed = within && key !== undefined ? await reaches(row, within, now, key) : false;
|
|
2690
|
+
if (within && !keyed && !(await reaches(row, within, now)))
|
|
2659
2691
|
return 'skip';
|
|
2660
2692
|
if (within?.checked !== undefined) {
|
|
2661
2693
|
if (rootVerdict === undefined) {
|
|
@@ -2673,6 +2705,27 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
2673
2705
|
}
|
|
2674
2706
|
if (!rootVerdict.allowed)
|
|
2675
2707
|
return 'revoked';
|
|
2708
|
+
// The root check speaks for a row the key reaches through the root. A row it reaches
|
|
2709
|
+
// only through a record whose shape protects the key (#2177) is the subscriber's own
|
|
2710
|
+
// check on the row: a holder by role or by the record's shape is nudged, a holder by a
|
|
2711
|
+
// grant above the record is not — the answer `ctx.check` gives, by asking it.
|
|
2712
|
+
if (!keyed) {
|
|
2713
|
+
const ask = () => context()
|
|
2714
|
+
.check(within.checked, { entityType: row.entity_type, entityId: row.entity_id })
|
|
2715
|
+
.then((d) => d.allowed, () => false);
|
|
2716
|
+
let allowed;
|
|
2717
|
+
if (e.remote) {
|
|
2718
|
+
allowed = ask();
|
|
2719
|
+
}
|
|
2720
|
+
else {
|
|
2721
|
+
const memo = `${subscriber.principal}\n${key}\n${row.id}`;
|
|
2722
|
+
allowed = rowChecks.get(memo);
|
|
2723
|
+
if (!allowed)
|
|
2724
|
+
rowChecks.set(memo, (allowed = ask()));
|
|
2725
|
+
}
|
|
2726
|
+
if (!(await allowed))
|
|
2727
|
+
return 'skip';
|
|
2728
|
+
}
|
|
2676
2729
|
}
|
|
2677
2730
|
if (within?.vouched !== undefined || within?.checked !== undefined) {
|
|
2678
2731
|
// The root was vouched for or checked, so the walk above was the row filter —
|
|
@@ -5541,7 +5594,9 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
5541
5594
|
causedBy: event.id,
|
|
5542
5595
|
invocationId: unit.invocationId,
|
|
5543
5596
|
});
|
|
5597
|
+
const stamps = watchUpdatedStamps(doSpineSql(this.sql), this.updatedStampPlans);
|
|
5544
5598
|
await consumer.handler(ctx, event);
|
|
5599
|
+
stamps?.assertHeld(ctx, `${mod.id} consuming ${event.type}`);
|
|
5545
5600
|
this.sql.exec(`INSERT INTO _substrat_deliveries
|
|
5546
5601
|
(event_id, consumer_module, delivered_at, invocation_id)
|
|
5547
5602
|
VALUES (?, ?, ?, ?)`, event.id, mod.id, new Date().toISOString(), invocationId);
|