@substrat-run/adapter-cloudflare 0.124.0 → 0.126.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.
@@ -39,8 +39,12 @@ export declare const LIVE_SCOPE_HEADER = "x-substrat-live-scope";
39
39
  * is how somebody reports "live updates are broken" two months later and nobody can
40
40
  * establish whether they ever worked on that hostname. One header makes the fallback a
41
41
  * fact the client can log, display, and report.
42
+ *
43
+ * Defined in the kernel (#1859), because the pure host's `501` names it too and a
44
+ * vertical's dev server should not import this adapter to spell it. Re-exported here so
45
+ * `host.ts` and `scope-do.ts` keep reading every wire name from this one file.
42
46
  */
43
- export declare const LIVE_MODE_HEADER = "x-substrat-live";
47
+ export { LIVE_MODE_HEADER, type LiveRefusal } from '@substrat-run/kernel';
44
48
  /**
45
49
  * Cloudflare's own per-request marker for orange-to-orange routing.
46
50
  *
@@ -58,12 +62,6 @@ export declare const LIVE_MODE_HEADER = "x-substrat-live";
58
62
  * cheaper than a matrix and correct on the request after the tenant changes their DNS.
59
63
  */
60
64
  export declare const O2O_HEADER = "cf-connecting-o2o";
61
- /** Why a subscription was refused — the value of `LIVE_MODE_HEADER` on a refusal. */
62
- export type LiveRefusal =
63
- /** This connection cannot carry a WebSocket; the client should keep polling. */
64
- 'poll'
65
- /** The request was not an upgrade at all — a programming error at the caller. */
66
- | 'not-an-upgrade';
67
65
  /**
68
66
  * What a subscribed socket remembers about itself across a hibernation.
69
67
  *
@@ -103,8 +101,8 @@ export declare function readSubscription(attachment: unknown): LiveSubscription
103
101
  * reaching it means something unusual happened rather than something normal being cut off.
104
102
  */
105
103
  export declare const LIVE_FANOUT_LIMIT = 200;
106
- /** Is this request asking to be upgraded to a WebSocket? */
107
- export declare function isUpgradeRequest(request: Request): boolean;
104
+ /** Is this request asking to be upgraded to a WebSocket? Defined in the kernel (#1859). */
105
+ export { isUpgradeRequest } from '@substrat-run/kernel';
108
106
  /**
109
107
  * Is this request arriving over an orange-to-orange hop?
110
108
  *
@@ -1 +1 @@
1
- {"version":3,"file":"live-reads.d.ts","sourceRoot":"","sources":["../src/live-reads.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAE9E;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,8BAA8B,CAAC;AACjE,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAC3D,eAAO,MAAM,iBAAiB,0BAA0B,CAAC;AAEzD;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,oBAAoB,CAAC;AAElD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU,sBAAsB,CAAC;AAE9C,qFAAqF;AACrF,MAAM,MAAM,WAAW;AACrB,gFAAgF;AAC9E,MAAM;AACR,iFAAiF;GAC/E,gBAAgB,CAAC;AAErB;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,yFAAyF;IACzF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAa7E;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAI1D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAE1D"}
1
+ {"version":3,"file":"live-reads.d.ts","sourceRoot":"","sources":["../src/live-reads.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAE9E;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,8BAA8B,CAAC;AACjE,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAC3D,eAAO,MAAM,iBAAiB,0BAA0B,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE1E;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU,sBAAsB,CAAC;AAE9C;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,yFAAyF;IACzF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAa7E;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,2FAA2F;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAE1D"}
@@ -27,8 +27,12 @@ export const LIVE_SCOPE_HEADER = 'x-substrat-live-scope';
27
27
  * is how somebody reports "live updates are broken" two months later and nobody can
28
28
  * establish whether they ever worked on that hostname. One header makes the fallback a
29
29
  * fact the client can log, display, and report.
30
+ *
31
+ * Defined in the kernel (#1859), because the pure host's `501` names it too and a
32
+ * vertical's dev server should not import this adapter to spell it. Re-exported here so
33
+ * `host.ts` and `scope-do.ts` keep reading every wire name from this one file.
30
34
  */
31
- export const LIVE_MODE_HEADER = 'x-substrat-live';
35
+ export { LIVE_MODE_HEADER } from '@substrat-run/kernel';
32
36
  /**
33
37
  * Cloudflare's own per-request marker for orange-to-orange routing.
34
38
  *
@@ -87,12 +91,8 @@ export function readSubscription(attachment) {
87
91
  * reaching it means something unusual happened rather than something normal being cut off.
88
92
  */
89
93
  export const LIVE_FANOUT_LIMIT = 200;
90
- /** Is this request asking to be upgraded to a WebSocket? */
91
- export function isUpgradeRequest(request) {
92
- // Case-insensitive: the header is `Upgrade: websocket` by the RFC, but the token is
93
- // compared case-insensitively there too, and browsers are not the only clients.
94
- return (request.headers.get('Upgrade') ?? '').toLowerCase() === 'websocket';
95
- }
94
+ /** Is this request asking to be upgraded to a WebSocket? Defined in the kernel (#1859). */
95
+ export { isUpgradeRequest } from '@substrat-run/kernel';
96
96
  /**
97
97
  * Is this request arriving over an orange-to-orange hop?
98
98
  *
@@ -1 +1 @@
1
- {"version":3,"file":"live-reads.js","sourceRoot":"","sources":["../src/live-reads.ts"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAErD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,2BAA2B,CAAC;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;AAC3D,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AAEzD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAElD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,mBAAmB,CAAC;AA0B9C;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAmB;IAClD,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACvE,MAAM,CAAC,GAAG,UAA8D,CAAC;IACzE,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACvE,IAAI,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACrE,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACnE,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO;QACL,SAAS,EAAE,CAAC,CAAC,SAAwB;QACrC,QAAQ,EAAE,CAAC,CAAC,QAAoB;QAChC,OAAO,EAAE,CAAC,CAAC,OAAkB;QAC7B,KAAK,EAAE,CAAC,CAAC,KAAK;KACf,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC,4DAA4D;AAC5D,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,oFAAoF;IACpF,gFAAgF;IAChF,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,WAAW,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,CAAC;AACjD,CAAC"}
1
+ {"version":3,"file":"live-reads.js","sourceRoot":"","sources":["../src/live-reads.ts"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAErD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,2BAA2B,CAAC;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;AAC3D,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,gBAAgB,EAAoB,MAAM,sBAAsB,CAAC;AAE1E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,mBAAmB,CAAC;AAmB9C;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAmB;IAClD,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACvE,MAAM,CAAC,GAAG,UAA8D,CAAC;IACzE,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACvE,IAAI,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACrE,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACnE,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO;QACL,SAAS,EAAE,CAAC,CAAC,SAAwB;QACrC,QAAQ,EAAE,CAAC,CAAC,QAAoB;QAChC,OAAO,EAAE,CAAC,CAAC,OAAkB;QAC7B,KAAK,EAAE,CAAC,CAAC,KAAK;KACf,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC,2FAA2F;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,CAAC;AACjD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAWL,KAAK,WAAW,EAqCjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAmCL,KAAK,SAAS,EAmBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAoEtB,MAAM,sBAAsB,CAAC;AAiF9B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAwaD;;;GAGG;AACH,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAgDxG;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,SAAS,CAOxD;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAwDxD;AAED,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,CAo5JjE"}
1
+ {"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAcL,KAAK,WAAW,EAqCjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAmCL,KAAK,SAAS,EAmBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAoEtB,MAAM,sBAAsB,CAAC;AAmF9B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAsbD;;;GAGG;AACH,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAgDxG;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,SAAS,CAOxD;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAwDxD;AAED,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,CAs7JjE"}
package/dist/scope-do.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { REWIND_REFUSED } from './rewind-refusal.js';
2
2
  import { DurableObject } from 'cloudflare:workers';
3
- import { ATTACHMENT_ADDED, ATTACHMENT_REMOVED, attachmentRecord, domainEvent, domainEventInput, eventId, instant, objectRef, toWireFailure, grantRefFromProof, principalId, scopeId as scopeIdOf, tenantId as tenantIdOf, platformRequestInput, platformRequestId, MAX_PENDING_PLATFORM_REQUESTS, MAX_PENDING_SWEEP_RUNS, SWEEP_RUNS_KIND, SCOPE_TABLE_PAGE_MAX, SCOPE_QUERY_ROW_MAX, listLimitOf, requestFingerprint, substratError, assertReplayableDump, REDRAIN_BATCH, } from '@substrat-run/contracts';
3
+ import { ATTACHMENT_ADDED, ATTACHMENT_REMOVED, attachmentRecord, domainEvent, domainEventInput, eventId, instant, objectRef, entityObjectRef, assertModuleEmittableType, assertKernelAuthoredType, toWireFailure, grantRefFromProof, principalId, scopeId as scopeIdOf, tenantId as tenantIdOf, platformRequestInput, platformRequestId, MAX_PENDING_PLATFORM_REQUESTS, MAX_PENDING_SWEEP_RUNS, SWEEP_RUNS_KIND, SCOPE_TABLE_PAGE_MAX, SCOPE_QUERY_ROW_MAX, listLimitOf, requestFingerprint, substratError, assertReplayableDump, REDRAIN_BATCH, } from '@substrat-run/contracts';
4
4
  import { ulid, DO_SQL_LIMITS, createUlid, assertAllowed, ConnectionSealingKeyUnavailableError, noSealingKeyMessage, sealTo, assertReadOnlyQuery, entitlementDenial, platformRequestHistoryQuery, platformRequestOf, PLATFORM_REQUEST_COLUMNS, PLATFORM_REQUEST_REDACTION_SQL, platformRequestRedactionParams, platformRequestRedactionQuery, intentPayloadCarriesSubject, redactSubjectJobRuns, JOB_RUN_PATCH_SQL, JOB_STEP_RECORD_SQL, DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, seatScopeTuple, effectiveRoleGrantQuery, switchRecordedOff, switchSystemSchedules, peerGrantsStatus, systemGrantsStatus, systemScheduleState, systemSwitchedOff, denialListQuery, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialSummaryBuckets, PermissionDenied, assertImpersonationWrites, assertModuleEnqueueableKind, impersonationStampOf, createAtomic, NotSearchable, isSearchIndexTable, searchIndexDdl, searchIndexPlans, NotListable, listIndexPlans, moduleMigrations, listQuery, cursorOf, searchLimit, searchMatchExpression, searchQuery, IDEMPOTENCY_DDL, 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, 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, jobRunListLimit, } from '@substrat-run/kernel';
5
5
  import { isUpgradeRequest, readSubscription, LIVE_FANOUT_LIMIT, LIVE_MODE_HEADER, LIVE_PRINCIPAL_HEADER, LIVE_SCOPE_HEADER, LIVE_SUBSCRIBE_PATH, LIVE_TENANT_HEADER, } from './live-reads.js';
6
6
  import { OperationQueue } from './serialization.js';
7
7
  import { doScopedSql, doSpineSql } from './sql.js';
8
- import { actorOf, admitPeer, collectPeers, switchPeer, assertNoSecret, CAPABILITY_DDL, CAPABILITY_EXCHANGE_OPERATION, capabilityAttachmentWriteRefused, createCapabilityVerbs, exchangeCapability, guardSecrets, mintBecomeCapability, redactSecrets, redactSecretText, moduleLog, resolveCapabilitySession, revokeCapabilityAsPlatform, domainEventOf, facetEvents, readDeadLetters, readHistory, readInvocation, readUndrainedOutbox, walkEventCause, walkEventEffects, } from '@substrat-run/kernel';
8
+ import { actorOf, admitPeer, collectPeers, switchPeer, assertNoSecret, CAPABILITY_DDL, CAPABILITY_EXCHANGE_OPERATION, capabilityAttachmentWriteRefused, createCapabilityVerbs, createEntityEdgeVerbs, exchangeCapability, guardSecrets, mintBecomeCapability, redactSecrets, redactSecretText, moduleLog, resolveCapabilitySession, revokeCapabilityAsPlatform, domainEventOf, facetEvents, readDeadLetters, readHistory, readInvocation, readUndrainedOutbox, walkEventCause, walkEventEffects, } from '@substrat-run/kernel';
9
9
  import { createDoTupleChecker, createLocalControlPlaneReader } from './checker.js';
10
+ import { repointScopeGrants } from '@substrat-run/kernel';
10
11
  /**
11
12
  * The key marking a scope DO whose storage was destroyed (`destroyStorage`).
12
13
  * Written after `deleteAll()` so it survives the wipe, and never cleared: `reaped`
@@ -341,6 +342,19 @@ const KERNEL_DDL = `
341
342
  * In-process — the SQLite adapter, a handler in the same isolate — the real class
342
343
  * arrives and `errorCodeOf` reads it directly.
343
344
  */
345
+ /**
346
+ * #1864: the kernel's own events (attachments, capability exchanges) written through the
347
+ * context an operation runs in, WITHOUT `ctx.emit`'s refusal of kernel-authored types. Keyed
348
+ * by the context object and never exported, so module code — which can reach `ctx` but not
349
+ * this map — cannot write one. Registered as each context is built.
350
+ */
351
+ const kernelEmitters = new WeakMap();
352
+ function kernelEmit(ctx, event) {
353
+ const write = kernelEmitters.get(ctx);
354
+ if (!write)
355
+ throw new Error('kernelEmit: not an operation context this host built');
356
+ write(event);
357
+ }
344
358
  function toRpcError(err) {
345
359
  if (err instanceof Error) {
346
360
  return err.constructor === Error ? err : new Error(err.message);
@@ -556,8 +570,20 @@ export function defineScopeDO(modules, bareOps) {
556
570
  lastFailure = null;
557
571
  /** Passes of `applyPendingMigrations` that had work to do — see `migrationAttemptsOnInstance`. */
558
572
  migrationRuns = 0;
573
+ /**
574
+ * TEST-ONLY (#1860): no production code reads this. An exact `'ping'` is answered
575
+ * by `setWebSocketAutoResponse` without reaching `webSocketMessage` at all, and
576
+ * this count is how a test proves that from outside the object — read through
577
+ * `runInDurableObject` — rather than assuming the runtime's documented behaviour.
578
+ */
579
+ webSocketMessagesHandled = 0;
559
580
  constructor(ctx, env) {
560
581
  super(ctx, env);
582
+ // #1860: the client's 45s keep-alive (demos/ticket0/app/src/feed.ts) answered at
583
+ // the runtime level, before this object wakes for it. Without this, every idle
584
+ // subscriber's ping still reaches `webSocketMessage` and pins the DO in memory —
585
+ // exactly the hibernation cost `acceptWebSocket` (below) exists to avoid.
586
+ ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair('ping', 'pong'));
561
587
  this.sql = ctx.storage.sql;
562
588
  for (const stmt of splitSqlStatements(KERNEL_DDL)) {
563
589
  this.sql.exec(stmt);
@@ -1233,16 +1259,17 @@ export function defineScopeDO(modules, bareOps) {
1233
1259
  // BOTH stores, because a scope check consults both: rule 2 inheritance makes a
1234
1260
  // tenant-level grant enforceable here exactly as a scope-level one is, and the
1235
1261
  // projected `_substrat_tenant_tuples` is where this DO holds them. A read-back
1236
- // that disagreed with enforcement would be worse than none.
1262
+ // that disagreed with enforcement would be worse than none. GLOB, not LIKE (#1869):
1263
+ // case-sensitive, as the checker's match is.
1237
1264
  const rows = [
1238
1265
  ...this.sql
1239
1266
  .exec(`SELECT subject, relation, expires_at FROM _substrat_tuples
1240
- WHERE subject LIKE 'connection:%' AND relation LIKE 'granted:%'
1267
+ WHERE subject GLOB 'connection:*' AND relation GLOB 'granted:*'
1241
1268
  AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)`, now)
1242
1269
  .toArray(),
1243
1270
  ...this.sql
1244
1271
  .exec(`SELECT subject, relation, expires_at FROM _substrat_tenant_tuples
1245
- WHERE subject LIKE 'connection:%' AND relation LIKE 'granted:%'
1272
+ WHERE subject GLOB 'connection:*' AND relation GLOB 'granted:*'
1246
1273
  AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at > ?)`, now)
1247
1274
  .toArray(),
1248
1275
  ];
@@ -1827,8 +1854,14 @@ export function defineScopeDO(modules, bareOps) {
1827
1854
  * Deliberately NOT a place to let a client narrow or widen what it receives: a
1828
1855
  * filter the client chooses is a filter the client can choose wrongly, and the
1829
1856
  * only filter that matters here is the one it does not control.
1857
+ *
1858
+ * #1860: an exact `'ping'` is now answered by `setWebSocketAutoResponse` in the
1859
+ * constructor, at the runtime level, without waking this object — so this branch no
1860
+ * longer runs for it. Kept as the documented fallback for a client on an older
1861
+ * build, or a hop where the runtime's auto-response is unavailable.
1830
1862
  */
1831
1863
  webSocketMessage(ws, message) {
1864
+ this.webSocketMessagesHandled++;
1832
1865
  if (message === 'ping')
1833
1866
  ws.send('pong');
1834
1867
  }
@@ -2071,7 +2104,7 @@ export function defineScopeDO(modules, bareOps) {
2071
2104
  (id, entity_type, entity_id, filename, content_type, size, sha256,
2072
2105
  visibility, created_by, created_at)
2073
2106
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, parsed.id, parsed.entity.entityType, parsed.entity.entityId, parsed.filename, parsed.contentType, parsed.size, parsed.sha256, parsed.visibility, parsed.createdBy, parsed.createdAt);
2074
- ctx.emit({
2107
+ kernelEmit(ctx, {
2075
2108
  type: ATTACHMENT_ADDED,
2076
2109
  schemaVersion: 1,
2077
2110
  entity: parsed.entity,
@@ -2167,7 +2200,7 @@ export function defineScopeDO(modules, bareOps) {
2167
2200
  const ctx = this.operationContext(principal, tenantId, scopeId, undefined, connectionId, undefined, undefined, undefined, 'attachments.remove');
2168
2201
  assertAllowed(await ctx.check(gate.write, record.entity));
2169
2202
  this.sql.exec('DELETE FROM _substrat_attachments WHERE id = ?', attachmentId);
2170
- ctx.emit({
2203
+ kernelEmit(ctx, {
2171
2204
  type: ATTACHMENT_REMOVED,
2172
2205
  schemaVersion: 1,
2173
2206
  entity: record.entity,
@@ -2574,7 +2607,7 @@ export function defineScopeDO(modules, bareOps) {
2574
2607
  // Stamped `{ capability }` by the ordinary emit path, under the exchange's own
2575
2608
  // pseudo-operation name — the actor is the capability, as on every event it
2576
2609
  // goes on to cause.
2577
- emit: (capability, event) => this.operationContext(principalId.parse(ulid()), tenantId, scopeId, undefined, undefined, undefined, undefined, undefined, CAPABILITY_EXCHANGE_OPERATION, capability, [], now).emit(event),
2610
+ emit: (capability, event) => kernelEmit(this.operationContext(principalId.parse(ulid()), tenantId, scopeId, undefined, undefined, undefined, undefined, undefined, CAPABILITY_EXCHANGE_OPERATION, capability, [], now), event),
2578
2611
  }, secret, mode);
2579
2612
  });
2580
2613
  if (outcome)
@@ -3386,11 +3419,7 @@ export function defineScopeDO(modules, bareOps) {
3386
3419
  this.sql.exec(stmt);
3387
3420
  });
3388
3421
  }
3389
- async importDump(tables, destScopeId,
3390
- /** The directory's recorded-off modules (#1742), switched off on `destScopeId` right after
3391
- * the replay re-points the grants, in the same event: a dump from before the switch was
3392
- * pulled carries the grants live and no marker. Needs `destScopeId`, the scope restored. */
3393
- switchOff) {
3422
+ async importDump(tables, destScopeId, { switchOff, sourceScopeId, exact, } = {}) {
3394
3423
  // The WHOLE drop-then-replay runs under deferred foreign keys, in one transaction.
3395
3424
  //
3396
3425
  // Two distinct FK hazards, and both need the deferral — the second is why it cannot
@@ -3419,6 +3448,10 @@ export function defineScopeDO(modules, bareOps) {
3419
3448
  // only the first statement, so the text itself has to be the one statement.
3420
3449
  // Pure input validation, so it runs before the first DROP: a refused dump touches nothing.
3421
3450
  assertReplayableDump(replayable, { maxColumns: DO_SQL_LIMITS.columns });
3451
+ // #1869: `exact` vouches for a named source; without one it would silently fall back.
3452
+ if (exact && !sourceScopeId) {
3453
+ throw substratError('validation_failed', 'restore refused: `exact` needs the scope the dump came from');
3454
+ }
3422
3455
  const switched = await this.ctx.storage.transaction(async () => {
3423
3456
  this.sql.exec('PRAGMA defer_foreign_keys = ON');
3424
3457
  // Real tables only; `sqlite_*` internals are auto-managed and un-droppable.
@@ -3469,7 +3502,7 @@ export function defineScopeDO(modules, bareOps) {
3469
3502
  // Re-point the restored grants at THIS scope (after the spine exists, so a dump
3470
3503
  // that carried no tuples table still finds one here).
3471
3504
  if (destScopeId)
3472
- this.rewriteScopeTuples(destScopeId);
3505
+ this.rewriteScopeTuples(destScopeId, sourceScopeId && { scopeId: sourceScopeId, exact });
3473
3506
  // #1742: the recorded-off modules go back off INSIDE the replay's transaction, with
3474
3507
  // the spine and the re-point. A switch that throws rolls the whole restore back, so
3475
3508
  // the dump's grants never commit live without the switch that should cover them.
@@ -3539,14 +3572,14 @@ export function defineScopeDO(modules, bareOps) {
3539
3572
  * `_substrat_tenant_tuples`, which this does not attempt — a cross-tenant restore is a
3540
3573
  * governed copy, not a repair.
3541
3574
  *
3575
+ * Which rows move is `repointScopeGrants`'s (#1869): the dump's source scope, exactly.
3576
+ *
3542
3577
  * `UPDATE OR REPLACE` because (subject, relation, object) is the primary key: if the
3543
3578
  * dump already held a tuple for the destination scope, the rewritten row collapses
3544
3579
  * onto it instead of failing the whole restore.
3545
3580
  */
3546
- rewriteScopeTuples(destScopeId) {
3547
- this.sql.exec(`UPDATE OR REPLACE _substrat_tuples
3548
- SET object = ?
3549
- WHERE object LIKE 'scope:%' AND object <> ?`, `scope:${destScopeId}`, `scope:${destScopeId}`);
3581
+ rewriteScopeTuples(destScopeId, source) {
3582
+ repointScopeGrants(this.switchSql(), destScopeId, source);
3550
3583
  }
3551
3584
  /**
3552
3585
  * Wipe this scope's storage — the reap half of deleteSnapshot (preview-and-
@@ -3918,6 +3951,51 @@ export function defineScopeDO(modules, bareOps) {
3918
3951
  }
3919
3952
  return checker.covers(subject, role.permissions, { tenantId, scopeId });
3920
3953
  };
3954
+ // ctx.emit's writer, and the kernel's own events' (#1864): one path to the outbox, with the
3955
+ // reserved-type refusal applied to module code only. Never handed to module code as-is.
3956
+ const writeEvent = (event, author) => {
3957
+ assertImpersonationWrites(impersonation, 'ctx.emit');
3958
+ const parsed = domainEventInput.parse(event);
3959
+ // #1864: a kernel-authored type is the kernel's to write — module code cannot forge one.
3960
+ if (author === 'module')
3961
+ assertModuleEmittableType(parsed.type);
3962
+ else
3963
+ assertKernelAuthoredType(parsed.type); // a new kernel event must join the reserved set
3964
+ // #1672: the COMPLETE parsed event — entity id, type and subject as well as payload.
3965
+ assertNoSecret('ctx.emit', parsed, minted);
3966
+ const full = domainEvent.parse({
3967
+ ...parsed,
3968
+ // #956: from the operation's instant, not a second reading of the clock.
3969
+ // `ORDER BY id` is how the outbox and every timeline page, so an id whose
3970
+ // timestamp disagreed with its own `occurredAt` sorted the log by a clock
3971
+ // nothing else in the operation used.
3972
+ id: eventId.parse(mintEventId(Date.parse(at))),
3973
+ occurredAt: at,
3974
+ tenantId,
3975
+ scopeId,
3976
+ actor: systemActor ?? derivedActor,
3977
+ ...(passed.length ? { authorization: passed.map((p) => ({ ...p })) } : {}),
3978
+ ...(stamp ? { impersonation: stamp } : {}),
3979
+ // #1231: kernel-stamped like the two above — `domainEventInput.parse`
3980
+ // already stripped anything module code tried to smuggle under this key.
3981
+ ...(operation ? { operation } : {}),
3982
+ });
3983
+ sql.exec(`INSERT INTO _substrat_outbox
3984
+ (id, type, schema_version, occurred_at, tenant_id, scope_id, actor,
3985
+ entity_type, entity_id, pii_class, subject_id, authorization,
3986
+ impersonation, operation, version, caused_by, invocation_id, payload)
3987
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, full.id, full.type, full.schemaVersion, full.occurredAt, full.tenantId, full.scopeId, JSON.stringify(full.actor), full.entity.entityType, full.entity.entityId, full.piiClass, full.subjectId ?? null, full.authorization ? JSON.stringify(full.authorization) : null, full.impersonation ? JSON.stringify(full.impersonation) : null, full.operation ?? null,
3988
+ // #1242: script configuration, not envelope data — the version is a fact
3989
+ // about the deploy, so it never rides `DomainEvent` for module code to
3990
+ // branch on; it exists for the observability joins the column serves.
3991
+ this.env.SUBSTRAT_VERSION_ID ?? null,
3992
+ // #1237: whatever delivery is in flight, if any — read off the DO the same
3993
+ // way the version is read off its env. A fact about the surrounding
3994
+ // dispatch, never envelope data module code could set or branch on.
3995
+ this.causedBy,
3996
+ // #1237: a fact about the surrounding CALL, like the version above.
3997
+ this.invocationId, full.payload === undefined ? null : JSON.stringify(full.payload));
3998
+ };
3921
3999
  const ctxRef = {
3922
4000
  tenantId,
3923
4001
  scopeId,
@@ -3939,44 +4017,7 @@ export function defineScopeDO(modules, bareOps) {
3939
4017
  // log's text is not JSON.
3940
4018
  redact: (text) => redactSecretText(text, minted),
3941
4019
  }),
3942
- emit: (event) => {
3943
- assertImpersonationWrites(impersonation, 'ctx.emit');
3944
- const parsed = domainEventInput.parse(event);
3945
- // #1672: the COMPLETE parsed event — entity id, type and subject as well as payload.
3946
- assertNoSecret('ctx.emit', parsed, minted);
3947
- const full = domainEvent.parse({
3948
- ...parsed,
3949
- // #956: from the operation's instant, not a second reading of the clock.
3950
- // `ORDER BY id` is how the outbox and every timeline page, so an id whose
3951
- // timestamp disagreed with its own `occurredAt` sorted the log by a clock
3952
- // nothing else in the operation used.
3953
- id: eventId.parse(mintEventId(Date.parse(at))),
3954
- occurredAt: at,
3955
- tenantId,
3956
- scopeId,
3957
- actor: systemActor ?? derivedActor,
3958
- ...(passed.length ? { authorization: passed.map((p) => ({ ...p })) } : {}),
3959
- ...(stamp ? { impersonation: stamp } : {}),
3960
- // #1231: kernel-stamped like the two above — `domainEventInput.parse`
3961
- // already stripped anything module code tried to smuggle under this key.
3962
- ...(operation ? { operation } : {}),
3963
- });
3964
- sql.exec(`INSERT INTO _substrat_outbox
3965
- (id, type, schema_version, occurred_at, tenant_id, scope_id, actor,
3966
- entity_type, entity_id, pii_class, subject_id, authorization,
3967
- impersonation, operation, version, caused_by, invocation_id, payload)
3968
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, full.id, full.type, full.schemaVersion, full.occurredAt, full.tenantId, full.scopeId, JSON.stringify(full.actor), full.entity.entityType, full.entity.entityId, full.piiClass, full.subjectId ?? null, full.authorization ? JSON.stringify(full.authorization) : null, full.impersonation ? JSON.stringify(full.impersonation) : null, full.operation ?? null,
3969
- // #1242: script configuration, not envelope data — the version is a fact
3970
- // about the deploy, so it never rides `DomainEvent` for module code to
3971
- // branch on; it exists for the observability joins the column serves.
3972
- this.env.SUBSTRAT_VERSION_ID ?? null,
3973
- // #1237: whatever delivery is in flight, if any — read off the DO the same
3974
- // way the version is read off its env. A fact about the surrounding
3975
- // dispatch, never envelope data module code could set or branch on.
3976
- this.causedBy,
3977
- // #1237: a fact about the surrounding CALL, like the version above.
3978
- this.invocationId, full.payload === undefined ? null : JSON.stringify(full.payload));
3979
- },
4020
+ emit: (event) => writeEvent(event, 'module'),
3980
4021
  requestPlatform: (request) => {
3981
4022
  assertImpersonationWrites(impersonation, 'ctx.requestPlatform');
3982
4023
  const input = platformRequestInput.parse(request);
@@ -4070,6 +4111,7 @@ export function defineScopeDO(modules, bareOps) {
4070
4111
  */
4071
4112
  grant: async (principal, permission, entity) => {
4072
4113
  assertImpersonationWrites(impersonation, 'ctx.grant');
4114
+ entityObjectRef(entity, 'ctx.grant'); // #1856: a tuple the walk can read back
4073
4115
  const held = await runCheck(permission, entity);
4074
4116
  if (!held.allowed) {
4075
4117
  throw new PermissionDenied(`cannot grant '${permission}' on ${entity.entityType}:${entity.entityId} — ` +
@@ -4077,6 +4119,11 @@ export function defineScopeDO(modules, bareOps) {
4077
4119
  }
4078
4120
  sql.exec(`INSERT OR IGNORE INTO _substrat_tuples (subject, relation, object) VALUES (?, ?, ?)`, `principal:${principal}`, `granted:${permission}`, `${entity.entityType}:${entity.entityId}`);
4079
4121
  },
4122
+ /**
4123
+ * Deliberately NOT the #1856 grammar check `grant` and `link` make: a revoke writes
4124
+ * nothing the walk must read back, and a grant stored before that check existed must
4125
+ * stay removable.
4126
+ */
4080
4127
  revoke: async (principal, permission, entity) => {
4081
4128
  assertImpersonationWrites(impersonation, 'ctx.revoke');
4082
4129
  const held = await runCheck(permission, entity);
@@ -4098,21 +4145,19 @@ export function defineScopeDO(modules, bareOps) {
4098
4145
  subject: systemActor ? { kind: 'system', id: systemActor.system } : subject,
4099
4146
  now: at,
4100
4147
  check: runCheck,
4101
- emit: (event) => ctxRef.emit(event),
4148
+ emit: (event) => writeEvent(event, 'kernel'),
4102
4149
  isOperation: (name) => this.operations.has(name),
4103
4150
  assertWrites: (verb) => assertImpersonationWrites(impersonation, verb),
4104
4151
  minted,
4105
4152
  }),
4106
- link: (child, parent) => {
4107
- assertImpersonationWrites(impersonation, 'ctx.link');
4108
- const allowed = relations.get(child.entityType);
4109
- if (!allowed?.has(parent.entityType)) {
4110
- throw new Error(`undeclared entity relation: ${child.entityType} → ${parent.entityType} ` +
4111
- `(declare it in a module manifest's entityRelations)`);
4112
- }
4113
- sql.exec(`INSERT OR IGNORE INTO _substrat_tuples (subject, relation, object)
4114
- VALUES (?, 'parent', ?)`, `${child.entityType}:${child.entityId}`, `${parent.entityType}:${parent.entityId}`);
4115
- },
4153
+ // K-16 / #1864: link and relink, written once in the kernel over the raw spine seam.
4154
+ ...createEntityEdgeVerbs({
4155
+ sql: doSpineSql(sql),
4156
+ relations,
4157
+ now: at,
4158
+ emit: (event) => writeEvent(event, 'kernel'),
4159
+ assertWrites: (verb) => assertImpersonationWrites(impersonation, verb),
4160
+ }),
4116
4161
  entitlement: async (key) => {
4117
4162
  const held = await entitlementReader().listEntitlements(tenantId);
4118
4163
  return held.find((e) => e.key === key) ?? null;
@@ -4136,6 +4181,7 @@ export function defineScopeDO(modules, bareOps) {
4136
4181
  return sealTo({ keyId: row.key_id, publicKey: row.public_key }, plaintext);
4137
4182
  },
4138
4183
  };
4184
+ kernelEmitters.set(ctxRef, (event) => writeEvent(event, 'kernel'));
4139
4185
  return ctxRef;
4140
4186
  }
4141
4187
  /** True once this scope has had entitlements projected at least once (#304) — the switch