@substrat-run/adapter-cloudflare 0.159.0 → 0.161.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 +131 -30
- package/dist/control-plane-do.d.ts.map +1 -1
- package/dist/control-plane-do.js +358 -94
- package/dist/control-plane-do.js.map +1 -1
- package/dist/host.d.ts +885 -7
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +476 -200
- package/dist/host.js.map +1 -1
- package/dist/platform-sweeper-do.d.ts +3 -2
- package/dist/platform-sweeper-do.d.ts.map +1 -1
- package/dist/platform-sweeper-do.js +3 -2
- package/dist/platform-sweeper-do.js.map +1 -1
- package/dist/r2.d.ts +7 -1
- package/dist/r2.d.ts.map +1 -1
- package/dist/r2.js +23 -2
- package/dist/r2.js.map +1 -1
- package/dist/scope-do.d.ts.map +1 -1
- package/dist/scope-do.js +207 -42
- package/dist/scope-do.js.map +1 -1
- package/dist/scope-sweeper-do.d.ts +4 -2
- package/dist/scope-sweeper-do.d.ts.map +1 -1
- package/dist/scope-sweeper-do.js +7 -1
- package/dist/scope-sweeper-do.js.map +1 -1
- package/package.json +9 -9
|
@@ -87,8 +87,9 @@ export interface PlatformSweeperDo {
|
|
|
87
87
|
* sweep: (env) =>
|
|
88
88
|
* runPlatformSweep(hostFor(env), {
|
|
89
89
|
* actor: SWEEP_ACTOR,
|
|
90
|
-
* //
|
|
91
|
-
*
|
|
90
|
+
* // the kernel's: called on its global (workerd refuses another receiver), and a sized
|
|
91
|
+
* // stream body is sent at its length rather than as "[object Object]" (#2498)
|
|
92
|
+
* fetch: globalFetch,
|
|
92
93
|
* sweepers: { scrive: sweepScriveReconciliations },
|
|
93
94
|
* }),
|
|
94
95
|
* });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform-sweeper-do.d.ts","sourceRoot":"","sources":["../src/platform-sweeper-do.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAExD,2EAA2E;AAC3E,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,MAAM,WAAW,uBAAuB,CAAC,GAAG;IAC1C;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC9C,kFAAkF;IAClF,MAAM,CAAC,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC;CACxD;AAED,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,WAAW,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,gEAAgE;IAChE,QAAQ,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAC3C;AAED
|
|
1
|
+
{"version":3,"file":"platform-sweeper-do.d.ts","sourceRoot":"","sources":["../src/platform-sweeper-do.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAExD,2EAA2E;AAC3E,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,MAAM,WAAW,uBAAuB,CAAC,GAAG;IAC1C;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC9C,kFAAkF;IAClF,MAAM,CAAC,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC;CACxD;AAED,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,WAAW,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,gEAAgE;IAChE,QAAQ,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EACzC,MAAM,EAAE,uBAAuB,CAAC,GAAG,CAAC,GACnC,KAAK,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,GAAG,KAAK,aAAa,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAqDnF"}
|
|
@@ -50,8 +50,9 @@ export const PLATFORM_SWEEPER_NAME = 'platform-sweeper';
|
|
|
50
50
|
* sweep: (env) =>
|
|
51
51
|
* runPlatformSweep(hostFor(env), {
|
|
52
52
|
* actor: SWEEP_ACTOR,
|
|
53
|
-
* //
|
|
54
|
-
*
|
|
53
|
+
* // the kernel's: called on its global (workerd refuses another receiver), and a sized
|
|
54
|
+
* // stream body is sent at its length rather than as "[object Object]" (#2498)
|
|
55
|
+
* fetch: globalFetch,
|
|
55
56
|
* sweepers: { scrive: sweepScriveReconciliations },
|
|
56
57
|
* }),
|
|
57
58
|
* });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform-sweeper-do.js","sourceRoot":"","sources":["../src/platform-sweeper-do.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;AAoCxD
|
|
1
|
+
{"version":3,"file":"platform-sweeper-do.js","sourceRoot":"","sources":["../src/platform-sweeper-do.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;AAoCxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,MAAM,UAAU,uBAAuB,CACrC,MAAoC;IAEpC,OAAO,MAAM,iBAAkB,SAAQ,aAAkB;QACvD,mEAAmE;QACnE,KAAK,GAAyC,IAAI,CAAC;QAEnD;;;WAGG;QACH,KAAK,CAAC,WAAW;YACf,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACnD,IAAI,QAAQ,KAAK,IAAI;gBAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;YAClE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC;YAC/C,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACzC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAClC,CAAC;QAED;;;;WAIG;QACH,KAAK,CAAC,QAAQ;YACZ,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;QACrB,CAAC;QAED,yEAAyE;QACzE,KAAK,CAAC,KAAK;YACT,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACpB,CAAC;QAED,KAAK,CAAC,IAAI;YACR,IAAI,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC;YAClC,IAAI,CAAC,KAAK,GAAG,CAAC,KAAK,IAAmC,EAAE;gBACtD,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;oBAC5C,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;oBAClC,OAAO,MAAM,CAAC;gBAChB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC5E,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;oBACnC,OAAO,OAAO,CAAC;gBACjB,CAAC;YACH,CAAC,CAAC,EAAE,CAAC;YACL,IAAI,CAAC;gBACH,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC;YAC1B,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;gBAClB,yEAAyE;gBACzE,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/r2.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type FetchLike, type TenantBlobStore } from '@substrat-run/kernel';
|
|
1
|
+
import { type FetchLike, type IncrementalSha256, type TenantBlobStore } from '@substrat-run/kernel';
|
|
2
2
|
/**
|
|
3
3
|
* The live R2 side of per-tenant blob stores (#473): a minimal client over Cloudflare's
|
|
4
4
|
* R2 REST API — create and delete buckets. Pure web-standard `fetch`, injected for tests,
|
|
@@ -40,6 +40,12 @@ export interface R2BlobStoresOptions {
|
|
|
40
40
|
*/
|
|
41
41
|
export declare function blobStoreBucketName(tenantId: string, vertical: string, binding: string): Promise<string>;
|
|
42
42
|
export declare function createR2BlobStores(opts: R2BlobStoresOptions): R2BlobStores;
|
|
43
|
+
/**
|
|
44
|
+
* workerd's running SHA-256 (#2498): `crypto.DigestStream`, fed chunk by chunk as a streamed
|
|
45
|
+
* upload passes, so its sha256 is known when the last byte lands without the body ever being
|
|
46
|
+
* held. The kernel's `putAttachmentBytes` takes it.
|
|
47
|
+
*/
|
|
48
|
+
export declare function workerdSha256(): IncrementalSha256;
|
|
43
49
|
/**
|
|
44
50
|
* The WORKER-side open (#473): wrap the tenant's real `r2_bucket` binding — looked up as
|
|
45
51
|
* `env[blobStoreBindingName(binding, tenantId)]` — in the kernel's `TenantBlobStore`
|
package/dist/r2.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"r2.d.ts","sourceRoot":"","sources":["../src/r2.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"r2.d.ts","sourceRoot":"","sources":["../src/r2.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,8EAA8E;IAC9E,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,0FAA0F;IAC1F,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAaD;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,GAAG,YAAY,CA2D1E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,iBAAiB,CAIjD;AAyBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAoClE"}
|
package/dist/r2.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { globalFetch } from '@substrat-run/kernel';
|
|
1
|
+
import { globalFetch, isSizedByteStream, knownLengthStream, runtimeSha256, } from '@substrat-run/kernel';
|
|
2
2
|
/** Bound an untrusted upstream body for an error message, marking the cut (#307). */
|
|
3
3
|
function bounded(body, max = 2000) {
|
|
4
4
|
return body.length <= max ? body : `${body.slice(0, max)} … [truncated, ${body.length - max} chars omitted]`;
|
|
@@ -84,6 +84,17 @@ export function createR2BlobStores(opts) {
|
|
|
84
84
|
},
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* workerd's running SHA-256 (#2498): `crypto.DigestStream`, fed chunk by chunk as a streamed
|
|
89
|
+
* upload passes, so its sha256 is known when the last byte lands without the body ever being
|
|
90
|
+
* held. The kernel's `putAttachmentBytes` takes it.
|
|
91
|
+
*/
|
|
92
|
+
export function workerdSha256() {
|
|
93
|
+
const make = runtimeSha256();
|
|
94
|
+
if (!make)
|
|
95
|
+
throw new Error('this runtime has no crypto.DigestStream; the Cloudflare adapter runs on workerd');
|
|
96
|
+
return make();
|
|
97
|
+
}
|
|
87
98
|
/**
|
|
88
99
|
* The WORKER-side open (#473): wrap the tenant's real `r2_bucket` binding — looked up as
|
|
89
100
|
* `env[blobStoreBindingName(binding, tenantId)]` — in the kernel's `TenantBlobStore`
|
|
@@ -94,7 +105,9 @@ export function r2TenantBlobStore(bucket) {
|
|
|
94
105
|
const r2 = bucket;
|
|
95
106
|
return {
|
|
96
107
|
put: async (key, body, opts) => {
|
|
97
|
-
|
|
108
|
+
// #2498: a stream goes in as it is read, at the length it declared.
|
|
109
|
+
const value = isSizedByteStream(body) ? knownLengthStream(body) : body;
|
|
110
|
+
await r2.put(key, value, opts?.contentType ? { httpMetadata: { contentType: opts.contentType } } : undefined);
|
|
98
111
|
},
|
|
99
112
|
get: async (key) => {
|
|
100
113
|
const obj = await r2.get(key);
|
|
@@ -104,6 +117,14 @@ export function r2TenantBlobStore(bucket) {
|
|
|
104
117
|
const contentType = obj.httpMetadata?.contentType;
|
|
105
118
|
return contentType !== undefined ? { body, contentType } : { body };
|
|
106
119
|
},
|
|
120
|
+
// #2514: the object's own body stream, with the size R2 holds for it, so an open reads it as it goes.
|
|
121
|
+
getStream: async (key) => {
|
|
122
|
+
const obj = await r2.get(key);
|
|
123
|
+
if (!obj)
|
|
124
|
+
return null;
|
|
125
|
+
const contentType = obj.httpMetadata?.contentType;
|
|
126
|
+
return { stream: obj.body, size: obj.size, ...(contentType !== undefined ? { contentType } : {}) };
|
|
127
|
+
},
|
|
107
128
|
delete: async (key) => {
|
|
108
129
|
await r2.delete(key);
|
|
109
130
|
},
|
package/dist/r2.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"r2.js","sourceRoot":"","sources":["../src/r2.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"r2.js","sourceRoot":"","sources":["../src/r2.ts"],"names":[],"mappings":"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"}
|
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;AAgEnD,OAAO,
|
|
1
|
+
{"version":3,"file":"scope-do.d.ts","sourceRoot":"","sources":["../src/scope-do.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAgEnD,OAAO,EA2DL,KAAK,mBAAmB,EASxB,KAAK,SAAS,EAsBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAmHtB,MAAM,sBAAsB,CAAC;AAoD9B,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAwBL,kBAAkB,EAiFnB,MAAM,sBAAsB,CAAC;AA2B9B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAoFD,oFAAoF;AACpF,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AA2rB5E;;;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;AACzD;;;;GAIG;AACH,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,mBAAmB,CAAA;CAAO,GAClD,KAAK,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,UAAU,KAAK,aAAa,CAAC,UAAU,CAAC,CA00P7E"}
|
package/dist/scope-do.js
CHANGED
|
@@ -2,12 +2,12 @@ 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, noteReservedKeys, 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_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, 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';
|
|
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';
|
|
9
9
|
import { assertNoWorkerdStorage, doScopedSql, doBuiltColumnsOf, doRedactionSql, doSpineSql } from './sql.js';
|
|
10
|
-
import { actorOf, admitPeer, boundToPeerDeclarations, collectPeers, switchPeer, peerSubjectRef, subjectGrantState, assertNoSecret, CAPABILITY_DDL, UPLOAD_LINK_DDL, createAttachmentVerbs, inspectUploadLink, redeemUploadLinkInTransaction, 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, checkHeldOperationsOf, countChecks, noteUncheckedCompletion, 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, boundToPeerDeclarations, collectPeers, switchPeer, peerSubjectRef, subjectGrantState, assertNoSecret, CAPABILITY_DDL, UPLOAD_LINK_DDL, createAttachmentVerbs, inspectUploadLink, redeemUploadLinkInTransaction, 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, assertPlatformRequestRoom, purgeDueOf, purgeOnlyKeysOf, runPurgePass, refuseTrashedTarget, assertBoundOperations, checkHeldOperationsOf, countChecks, noteUncheckedCompletion, 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, orphanSweepVersionRefusal } from '@substrat-run/kernel';
|
|
12
12
|
import { createDoTupleChecker, createLocalControlPlaneReader, scopeTupleReader } from './checker.js';
|
|
13
13
|
import { settlePlatformRequestIn } from '@substrat-run/kernel';
|
|
@@ -762,8 +762,11 @@ options = {}) {
|
|
|
762
762
|
assertModuleDeclared(options.permissions, registration.manifest.id);
|
|
763
763
|
// What the declared surface reserves holds in every instance too, whatever registers under its ids.
|
|
764
764
|
const declaredReserved = new Set();
|
|
765
|
-
|
|
765
|
+
const declaredListed = new Set();
|
|
766
|
+
for (const m of options.permissions?.modules ?? []) {
|
|
766
767
|
noteReservedKeys(declaredReserved, m.manifest);
|
|
768
|
+
noteDeclaredKeys(declaredListed, m.manifest);
|
|
769
|
+
}
|
|
767
770
|
return class ScopeDO extends DurableObject {
|
|
768
771
|
sql;
|
|
769
772
|
continuationOrder = 0;
|
|
@@ -825,6 +828,8 @@ options = {}) {
|
|
|
825
828
|
* `holder: 'system'` — the role and grant doors' refusals read it.
|
|
826
829
|
*/
|
|
827
830
|
reservedKeys = new Set(declaredReserved);
|
|
831
|
+
/** #2447: every key those modules declare, marked or not — a role definition projected here reads it. */
|
|
832
|
+
declaredKeys = new Set(declaredListed);
|
|
828
833
|
/** #1705: what this deployment exports to other verticals and imports from them. */
|
|
829
834
|
crossVertical = new CrossVerticalRegistry();
|
|
830
835
|
/**
|
|
@@ -1004,6 +1009,7 @@ options = {}) {
|
|
|
1004
1009
|
})),
|
|
1005
1010
|
});
|
|
1006
1011
|
noteReservedKeys(this.reservedKeys, manifest);
|
|
1012
|
+
noteDeclaredKeys(this.declaredKeys, manifest);
|
|
1007
1013
|
if (erasure)
|
|
1008
1014
|
this.erasurePlans.push(erasure);
|
|
1009
1015
|
for (const [name, handler] of Object.entries(registration.predicates ?? {})) {
|
|
@@ -1198,6 +1204,14 @@ options = {}) {
|
|
|
1198
1204
|
databaseSize() {
|
|
1199
1205
|
return this.sql.databaseSize;
|
|
1200
1206
|
}
|
|
1207
|
+
/**
|
|
1208
|
+
* This scope's attachment bytes (#2501): what its attachment rows sum to
|
|
1209
|
+
* (`ATTACHMENT_BYTES_SQL`). Read without migrating, as `databaseSize` is. The constructor lays
|
|
1210
|
+
* the spine, so the table is always there, and a copy wiped of its rows reads 0.
|
|
1211
|
+
*/
|
|
1212
|
+
attachmentBytes() {
|
|
1213
|
+
return this.sql.exec(ATTACHMENT_BYTES_SQL).toArray()[0].bytes;
|
|
1214
|
+
}
|
|
1201
1215
|
/**
|
|
1202
1216
|
* The PITR bookmarks this scope recorded before migration passes (#286),
|
|
1203
1217
|
* newest first — what a backout UI offers as rewind points. Rows taken after
|
|
@@ -1209,11 +1223,21 @@ options = {}) {
|
|
|
1209
1223
|
* written since this table shipped and selected by nobody — the frontier
|
|
1210
1224
|
* readers want `(module_id, version)` only — so "when did this scope's
|
|
1211
1225
|
* schema change" had no answer. A pre-column row reads null, a fact.
|
|
1226
|
+
*
|
|
1227
|
+
* Every row, never a page (#2553): the caller treats the list as the scope's whole
|
|
1228
|
+
* migration history, and a cap truncated it silently once a scope passed 100 rows.
|
|
1229
|
+
* A row is a few short columns (~150 B on the wire) and every row is an authored
|
|
1230
|
+
* migration a push shipped, so the set is bounded by authored migrations × pushes:
|
|
1231
|
+
* hundreds of rows, tens of KB, far under the RPC and response limits.
|
|
1232
|
+
*
|
|
1233
|
+
* `rowid DESC` breaks a tie: each migration reads its own clock, so neighbours can
|
|
1234
|
+
* share a millisecond, and the rowid is the order they were written in. `module_id,
|
|
1235
|
+
* version` ascending would read those oldest-first.
|
|
1212
1236
|
*/
|
|
1213
|
-
appliedMigrations(
|
|
1237
|
+
appliedMigrations() {
|
|
1214
1238
|
return this.sql
|
|
1215
1239
|
.exec(`SELECT module_id, version, applied_at, duration_ms, rows_changed FROM _substrat_migrations
|
|
1216
|
-
ORDER BY applied_at DESC,
|
|
1240
|
+
ORDER BY applied_at DESC, rowid DESC`)
|
|
1217
1241
|
.toArray()
|
|
1218
1242
|
.map((r) => ({
|
|
1219
1243
|
moduleId: r.module_id,
|
|
@@ -1348,6 +1372,93 @@ options = {}) {
|
|
|
1348
1372
|
const { verdict } = this.tenantVerdict(tenantId);
|
|
1349
1373
|
return verdict === 'recorded' || verdict === 'inferred';
|
|
1350
1374
|
}
|
|
1375
|
+
/**
|
|
1376
|
+
* #2541: this scope's database size, or null when the deployment does not hold it. The held
|
|
1377
|
+
* check and the read are ONE call: a reap or wipe cannot land between them, so a scope that
|
|
1378
|
+
* stopped being held is answered "not held", never with a number.
|
|
1379
|
+
*/
|
|
1380
|
+
async databaseSizeIfHeld() {
|
|
1381
|
+
return (await this.holdsScope()) ? this.databaseSize() : null;
|
|
1382
|
+
}
|
|
1383
|
+
/** #2541: this scope's attachment bytes, or null when the deployment does not hold it; one call, as the size is. */
|
|
1384
|
+
async attachmentBytesIfHeld() {
|
|
1385
|
+
return (await this.holdsScope()) ? this.attachmentBytes() : null;
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* #2544: the introspection reads that name only a scope id, each as the held check and the read
|
|
1389
|
+
* in ONE call (as the size reads above are): `null` says the deployment does not hold this scope,
|
|
1390
|
+
* never an empty answer. The read itself is the one the unguarded method makes.
|
|
1391
|
+
*/
|
|
1392
|
+
async ifHeld(read) {
|
|
1393
|
+
return (await this.holdsScope()) ? read() : null;
|
|
1394
|
+
}
|
|
1395
|
+
facetEventsIfHeld(input) {
|
|
1396
|
+
return this.ifHeld(() => this.facetEvents(input));
|
|
1397
|
+
}
|
|
1398
|
+
eventEffectsIfHeld(input) {
|
|
1399
|
+
return this.ifHeld(() => this.eventEffects(input));
|
|
1400
|
+
}
|
|
1401
|
+
invocationEventsIfHeld(input) {
|
|
1402
|
+
return this.ifHeld(() => this.invocationEvents(input));
|
|
1403
|
+
}
|
|
1404
|
+
deadLettersIfHeld(input) {
|
|
1405
|
+
return this.ifHeld(() => this.deadLetters(input));
|
|
1406
|
+
}
|
|
1407
|
+
lifecycleFlowIfHeld(input) {
|
|
1408
|
+
return this.ifHeld(() => this.lifecycleFlow(input));
|
|
1409
|
+
}
|
|
1410
|
+
operationSeriesIfHeld(input) {
|
|
1411
|
+
return this.ifHeld(() => this.operationSeries(input));
|
|
1412
|
+
}
|
|
1413
|
+
eventCauseIfHeld(input) {
|
|
1414
|
+
return this.ifHeld(() => this.eventCause(input));
|
|
1415
|
+
}
|
|
1416
|
+
entityHistoryIfHeld(input) {
|
|
1417
|
+
return this.ifHeld(() => this.entityHistory(input));
|
|
1418
|
+
}
|
|
1419
|
+
appliedMigrationsIfHeld() {
|
|
1420
|
+
return this.ifHeld(() => this.appliedMigrations());
|
|
1421
|
+
}
|
|
1422
|
+
migrationBookmarksIfHeld() {
|
|
1423
|
+
return this.ifHeld(() => this.migrationBookmarks());
|
|
1424
|
+
}
|
|
1425
|
+
introspectTablesIfHeld() {
|
|
1426
|
+
return this.ifHeld(() => this.introspectTables());
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* #2563: the table page and the SQL console, each as the held check and the read in ONE call.
|
|
1430
|
+
* They answer as a `DoReply` (a refusal keeps its code across the hop, #113), `null` outside it
|
|
1431
|
+
* for a scope the deployment does not hold. The carry's reader of a wiped copy's tombstone does
|
|
1432
|
+
* not come through here: that copy is not held, by design, and it asks `introspectTableReply`.
|
|
1433
|
+
*/
|
|
1434
|
+
introspectTableIfHeld(table, limit, offset) {
|
|
1435
|
+
return this.ifHeld(() => this.introspectTableReply(table, limit, offset));
|
|
1436
|
+
}
|
|
1437
|
+
introspectQueryIfHeld(sql) {
|
|
1438
|
+
return this.ifHeld(() => this.introspectQueryReply(sql));
|
|
1439
|
+
}
|
|
1440
|
+
undrainedEventsReadIfHeld(limit) {
|
|
1441
|
+
return this.ifHeld(() => this.undrainedEventsRead(limit));
|
|
1442
|
+
}
|
|
1443
|
+
redrainCountIfHeld(drainedBefore) {
|
|
1444
|
+
return this.ifHeld(() => this.redrainCount(drainedBefore));
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* #2541: whether this object is a scope the deployment holds, asked by a read that names only a
|
|
1448
|
+
* scope id. False for a DO nothing provisioned or loaded here (never provisioned), for a reaped
|
|
1449
|
+
* scope's name addressed again (its tombstone), and for a copy a carry wiped, whose rows went
|
|
1450
|
+
* with the wipe. Held otherwise: a provision receipt or role rows, or state (`holdsData`). Read
|
|
1451
|
+
* without migrating, so a refusal leaves the DO as it found it.
|
|
1452
|
+
*/
|
|
1453
|
+
async holdsScope() {
|
|
1454
|
+
if (this.carriedAwayCopy || (await this.isReaped()))
|
|
1455
|
+
return false;
|
|
1456
|
+
return this.provisionedFor() !== null || this.holdsRoles() || this.holdsData();
|
|
1457
|
+
}
|
|
1458
|
+
/** Whether the scope holds any role row, of whichever tenant; read without migrating. */
|
|
1459
|
+
holdsRoles() {
|
|
1460
|
+
return this.hasTable('_substrat_roles') && this.sql.exec('SELECT 1 FROM _substrat_roles LIMIT 1').toArray().length > 0;
|
|
1461
|
+
}
|
|
1351
1462
|
/**
|
|
1352
1463
|
* #2016: what one door into this scope needs from its storage on a CP-less host, in ONE call —
|
|
1353
1464
|
* how `tenantId` reads against the scope (`tenantVerdict`), and the lifecycle the platform
|
|
@@ -3170,6 +3281,29 @@ options = {}) {
|
|
|
3170
3281
|
return parsed;
|
|
3171
3282
|
});
|
|
3172
3283
|
}
|
|
3284
|
+
/**
|
|
3285
|
+
* The upload's write gate and nothing else (#2516): asked by the host before a streamed body is
|
|
3286
|
+
* read, so a connection that may not write costs no object. Checked and recorded exactly as
|
|
3287
|
+
* `addAttachment` does, which still checks again when the row is written.
|
|
3288
|
+
*/
|
|
3289
|
+
async checkAttachmentWrite(entity, principal, tenantId, scopeId, connectionId) {
|
|
3290
|
+
await this.ensureMigrations();
|
|
3291
|
+
const gate = this.attachmentGate(entity.entityType);
|
|
3292
|
+
try {
|
|
3293
|
+
const ctx = this.operationContext(principal, tenantId, scopeId, undefined, connectionId, undefined, undefined, undefined, 'attachments.upload');
|
|
3294
|
+
assertAllowed(await ctx.check(gate.write, entity));
|
|
3295
|
+
}
|
|
3296
|
+
catch (err) {
|
|
3297
|
+
if (err instanceof PermissionDenied) {
|
|
3298
|
+
this.recordDenial(attachSubject(principal, connectionId), tenantId, 'attachments.upload', err, null);
|
|
3299
|
+
}
|
|
3300
|
+
throw err;
|
|
3301
|
+
}
|
|
3302
|
+
return null;
|
|
3303
|
+
}
|
|
3304
|
+
attachmentCheckWriteReply(...args) {
|
|
3305
|
+
return replyOf(() => this.checkAttachmentWrite(...args));
|
|
3306
|
+
}
|
|
3173
3307
|
/** Records for one entity, newest first — read gate first. */
|
|
3174
3308
|
async listAttachments(entity, principal, tenantId, scopeId, connectionId) {
|
|
3175
3309
|
await this.ensureMigrations();
|
|
@@ -3664,10 +3798,15 @@ options = {}) {
|
|
|
3664
3798
|
/**
|
|
3665
3799
|
* #2430: why `roleKey` may not be conferred through `door` here, or null — the tenant's role as
|
|
3666
3800
|
* the checker expands it (`controlPlaneReader`), against the keys this class's modules reserve.
|
|
3667
|
-
* A role the tenant does not define is left to the door's own answer for it.
|
|
3801
|
+
* A role the tenant does not define is left to the door's own answer for it. A person's door reads
|
|
3802
|
+
* the role even when this class reserves nothing (#2447): a role stored `holder: 'system'` is
|
|
3803
|
+
* never a person's, whatever this class knows. Accepted cost: on a class that reserves nothing, an
|
|
3804
|
+
* invite, a role change and a `canAssign` read the role here and again for the bound, which on a
|
|
3805
|
+
* scope still reading the shared directory is one more ControlPlaneDO round trip per call. These
|
|
3806
|
+
* doors are a person acting, not a hot path, so one read is not threaded through both.
|
|
3668
3807
|
*/
|
|
3669
3808
|
async reservedRoleRefusalFor(tenantId, roleKey, door) {
|
|
3670
|
-
if (this.reservedKeys.size === 0)
|
|
3809
|
+
if (door === 'platform' && this.reservedKeys.size === 0)
|
|
3671
3810
|
return null;
|
|
3672
3811
|
const role = await this.controlPlaneReader().getRole(tenantId, roleKey);
|
|
3673
3812
|
return role ? reservedRoleRefusal(role, this.reservedKeys, door) : null;
|
|
@@ -3686,7 +3825,7 @@ options = {}) {
|
|
|
3686
3825
|
* door cannot know, so it refuses.
|
|
3687
3826
|
*/
|
|
3688
3827
|
async reservedRoleRefusalHere(roleKey, door) {
|
|
3689
|
-
if (this.reservedKeys.size === 0)
|
|
3828
|
+
if (door === 'platform' && this.reservedKeys.size === 0)
|
|
3690
3829
|
return null;
|
|
3691
3830
|
const tenantId = this.provisionedFor();
|
|
3692
3831
|
if (tenantId)
|
|
@@ -3699,7 +3838,8 @@ options = {}) {
|
|
|
3699
3838
|
if (refusal)
|
|
3700
3839
|
return refusal;
|
|
3701
3840
|
}
|
|
3702
|
-
|
|
3841
|
+
// A class that reserves nothing has nothing to rule out here, at either door (#2447 keeps that).
|
|
3842
|
+
if (tenants.length === 0 && this.reservedKeys.size > 0 && this.permissionSourceIsRemote()) {
|
|
3703
3843
|
return (`role '${roleKey}' cannot be judged here: this scope has no provisioning receipt and no projected role, and its ` +
|
|
3704
3844
|
"checks read roles from the control plane, so which tenant's definition a check expands is unknown and a key " +
|
|
3705
3845
|
'reserved to a system principal cannot be ruled out. Provision the scope first.');
|
|
@@ -3714,6 +3854,31 @@ options = {}) {
|
|
|
3714
3854
|
async reservedShapeRefusalFor(shapes) {
|
|
3715
3855
|
return reservedShapeRefusal(shapes, this.reservedKeys);
|
|
3716
3856
|
}
|
|
3857
|
+
/**
|
|
3858
|
+
* #2447: the tenant's roles as this scope stores them, and the role keys anyone but a system
|
|
3859
|
+
* principal holds a live scope-level seat in here — what a role
|
|
3860
|
+
* set defined on this scope, or a role definition fanned out to it, is judged against.
|
|
3861
|
+
*/
|
|
3862
|
+
async roleStateHere(tenantId) {
|
|
3863
|
+
// Spine tables only (built in the constructor), so no module migration runs ahead of a provision's.
|
|
3864
|
+
const now = new Date().toISOString();
|
|
3865
|
+
const rows = this.hasTable('_substrat_roles')
|
|
3866
|
+
? this.sql.exec('SELECT role_key, permissions, holder, revoked_at FROM _substrat_roles WHERE tenant_id = ?', tenantId).toArray()
|
|
3867
|
+
: [];
|
|
3868
|
+
const shapeOf = (r) => ({ key: r.role_key, permissions: JSON.parse(r.permissions), ...(r.holder ? { holder: r.holder } : {}) });
|
|
3869
|
+
const roles = rows.filter((r) => r.revoked_at === null).map(shapeOf);
|
|
3870
|
+
// #2531: the tombstones `applyProjection` leaves for the roles a projection dropped.
|
|
3871
|
+
const dropped = rows.filter((r) => r.revoked_at !== null).map(shapeOf);
|
|
3872
|
+
// Scope-level seats only. The tenant-level ones are the directory's (the ControlPlaneDO reads
|
|
3873
|
+
// them in its own unit), and this scope's projected copy can be stale: a fan-out skips an
|
|
3874
|
+
// archived scope, and a CP-less projection carries none.
|
|
3875
|
+
const seated = new Set(this.sql.exec(SCOPE_SEATED_ROLES_SQL, now).toArray().map((r) => r.role_key));
|
|
3876
|
+
return { roles, seated: [...seated], dropped };
|
|
3877
|
+
}
|
|
3878
|
+
/** #2447: what this class's modules reserve and declare — a coordinator judging a role definition joins its own. */
|
|
3879
|
+
async reservationHere() {
|
|
3880
|
+
return { reserved: [...this.reservedKeys], declared: [...this.declaredKeys] };
|
|
3881
|
+
}
|
|
3717
3882
|
/** #2430: which of `permissions` this class's modules reserve to a system principal. */
|
|
3718
3883
|
async reservedAmong(permissions) {
|
|
3719
3884
|
return permissions.filter((p) => this.reservedKeys.has(p));
|
|
@@ -4100,7 +4265,12 @@ options = {}) {
|
|
|
4100
4265
|
* positional-RPC reason `recordExecutorAttempt` gives; a coordinator without it gets a fresh
|
|
4101
4266
|
* id per purge, as before.
|
|
4102
4267
|
*/
|
|
4103
|
-
runInvocationId
|
|
4268
|
+
runInvocationId,
|
|
4269
|
+
/**
|
|
4270
|
+
* #2517: platform requests the purge runs earlier in the same tick enqueued. LAST and optional
|
|
4271
|
+
* for the same reason; absent means none, so a refused head is a failure as before.
|
|
4272
|
+
*/
|
|
4273
|
+
earlierPurgeRequests) {
|
|
4104
4274
|
await this.ensureMigrations();
|
|
4105
4275
|
const schedule = this.purgeSchedules.get(operation);
|
|
4106
4276
|
if (!schedule)
|
|
@@ -4120,9 +4290,14 @@ options = {}) {
|
|
|
4120
4290
|
if (held !== null)
|
|
4121
4291
|
return heldPurgePass(held);
|
|
4122
4292
|
const due = purgeDueOf(doSpineSql(this.sql), this.statePlans, this.operationTarget, operation, schedule.entityType, new Date().toISOString(), PURGE_BATCH);
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4293
|
+
// #2440: what each committed purge enqueued, from the same envelope an ordinary invoke's
|
|
4294
|
+
// caller reads. A purge that threw rolled back, and its intents with it.
|
|
4295
|
+
let platformRequests = 0;
|
|
4296
|
+
const pass = await runPurgePass(operation, due, async (entityId) => {
|
|
4297
|
+
const envelope = await this.#invokeOrThrow(operation, { [due.idFrom]: entityId }, schedule.moduleId, tenantId, scopeId, undefined, undefined, schedule.moduleId, { invocationId: runInvocationId ?? ulid() }, undefined, undefined, undefined, systemDoorInstance, true);
|
|
4298
|
+
platformRequests += envelope.platformRequests;
|
|
4299
|
+
}, (write) => write(doSpineSql(this.sql)), earlierPurgeRequests ?? 0);
|
|
4300
|
+
return platformRequests > 0 ? { ...pass, platformRequests } : pass;
|
|
4126
4301
|
}
|
|
4127
4302
|
/**
|
|
4128
4303
|
* #119: the purge gate's facts (`PurgeGateFacts`), from this scope's own storage: the module's
|
|
@@ -4911,9 +5086,7 @@ options = {}) {
|
|
|
4911
5086
|
const pending = Number(this.sql
|
|
4912
5087
|
.exec(`SELECT COUNT(*) AS c FROM _substrat_platform_requests WHERE status = 'pending'`)
|
|
4913
5088
|
.toArray()[0].c);
|
|
4914
|
-
|
|
4915
|
-
throw new Error(`too many pending platform requests (${pending}); the delivery retries once some have drained`);
|
|
4916
|
-
}
|
|
5089
|
+
assertPlatformRequestRoom(pending, 'the delivery retries once some have drained');
|
|
4917
5090
|
const id = platformRequestId.parse(ulid());
|
|
4918
5091
|
// K-42: the intent inherits the SOURCE EVENT's stamp rather than being written
|
|
4919
5092
|
// unstamped. Nobody is impersonating at this moment — the drain runs long after
|
|
@@ -4982,10 +5155,16 @@ options = {}) {
|
|
|
4982
5155
|
const names = this.sql
|
|
4983
5156
|
.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT GLOB '_cf_*' ORDER BY name`)
|
|
4984
5157
|
.toArray().map((r) => r.name);
|
|
5158
|
+
const count = (sql) => Number(this.sql.exec(sql).toArray()[0].c);
|
|
4985
5159
|
return names.map((name) => ({
|
|
4986
5160
|
name,
|
|
4987
|
-
rowCount:
|
|
5161
|
+
rowCount: count(`SELECT COUNT(*) AS c FROM "${name}"`),
|
|
4988
5162
|
system: isSystemTable(name),
|
|
5163
|
+
// #2531: rows whose `revoked_at` is null, for any table with that column (`ScopeTable.liveRowCount`).
|
|
5164
|
+
// Scope health reads it for `_substrat_roles`, where a dropped role is kept revoked.
|
|
5165
|
+
...(doBuiltColumnsOf(this.sql, name)?.includes('revoked_at')
|
|
5166
|
+
? { liveRowCount: count(`SELECT COUNT(*) AS c FROM "${name}" WHERE revoked_at IS NULL`) }
|
|
5167
|
+
: {}),
|
|
4989
5168
|
}));
|
|
4990
5169
|
}
|
|
4991
5170
|
/** A bounded page of one table. Unknown table names throw — never queried blind. Kept for a
|
|
@@ -6551,9 +6730,7 @@ options = {}) {
|
|
|
6551
6730
|
const pending = Number(sql
|
|
6552
6731
|
.exec(`SELECT COUNT(*) AS c FROM _substrat_platform_requests WHERE status = 'pending'`)
|
|
6553
6732
|
.toArray()[0].c);
|
|
6554
|
-
|
|
6555
|
-
throw new Error(`too many pending platform requests (${pending}); retry once some have drained`);
|
|
6556
|
-
}
|
|
6733
|
+
assertPlatformRequestRoom(pending);
|
|
6557
6734
|
const id = platformRequestId.parse(ulid());
|
|
6558
6735
|
const requestedBy = systemActor ?? derivedActor;
|
|
6559
6736
|
sql.exec(`INSERT INTO _substrat_platform_requests
|
|
@@ -6838,27 +7015,9 @@ options = {}) {
|
|
|
6838
7015
|
// The write side of the local reader: the coordinator fans role/tuple changes
|
|
6839
7016
|
// into a scope's own storage (Phase 2), then flips the source to 'local'. Public
|
|
6840
7017
|
// RPC methods so `CloudflareScopeHost` (and the projection sweep) can call them.
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
this.sql.exec(`INSERT OR REPLACE INTO _substrat_roles (tenant_id, role_key, permissions, source, holder, revoked_at)
|
|
6845
|
-
VALUES (?, ?, ?, ?, ?, NULL)`, tenantId, role.key, JSON.stringify(role.permissions), role.source, role.holder ?? null);
|
|
6846
|
-
});
|
|
6847
|
-
}
|
|
6848
|
-
/** Tombstone a projected role — it stops granting but stays as evidence (K-21). */
|
|
6849
|
-
async revokeProjectedRole(tenantId, key, revokedAt) {
|
|
6850
|
-
await this.queue.enqueue(() => {
|
|
6851
|
-
this.sql.exec(`UPDATE _substrat_roles SET revoked_at = ? WHERE tenant_id = ? AND role_key = ?`, revokedAt, tenantId, key);
|
|
6852
|
-
});
|
|
6853
|
-
}
|
|
6854
|
-
/** Project (upsert) a tenant-level tuple into this scope. `revokedAt` tombstones it. */
|
|
6855
|
-
async projectTenantTuple(tenantId, subject, relation, object, expiresAt, revokedAt = null) {
|
|
6856
|
-
await this.queue.enqueue(() => {
|
|
6857
|
-
this.sql.exec(`INSERT OR REPLACE INTO _substrat_tenant_tuples
|
|
6858
|
-
(tenant_id, subject, relation, object, expires_at, revoked_at)
|
|
6859
|
-
VALUES (?, ?, ?, ?, ?, ?)`, tenantId, subject, relation, object, expiresAt, revokedAt);
|
|
6860
|
-
});
|
|
6861
|
-
}
|
|
7018
|
+
// #2531: no per-row role or tenant-tuple projection RPC. Every projected row arrives through
|
|
7019
|
+
// `applyProjection`, whose callers judge what they project (or fan out definitions already
|
|
7020
|
+
// judged), so no RPC writes a role row unjudged.
|
|
6862
7021
|
/** Flip which reader the checker uses. 'local' makes projections authoritative. */
|
|
6863
7022
|
async setPermissionSource(source) {
|
|
6864
7023
|
await this.queue.enqueue(() => {
|
|
@@ -6940,7 +7099,13 @@ options = {}) {
|
|
|
6940
7099
|
}
|
|
6941
7100
|
// Written before any guard below can return early, so every projection leaves it.
|
|
6942
7101
|
this.sql.exec(`INSERT OR REPLACE INTO _substrat_meta (key, value) VALUES (?, ?)`, PROVISIONED_FOR_KEY, tenantId);
|
|
6943
|
-
|
|
7102
|
+
// #2531: a role the new set leaves out is TOMBSTONED, not deleted: it keeps the keys and the
|
|
7103
|
+
// holder it had, and grants nothing (every reader skips a revoked row). Kept for good, as
|
|
7104
|
+
// evidence (K-21): the reserved-key rule reads a system-held tombstone as the stored
|
|
7105
|
+
// definition it was, so a release that drops a role and a later one that adds it back
|
|
7106
|
+
// are judged as one that kept it. A row already tombstoned keeps its first `revoked_at`;
|
|
7107
|
+
// a role the set names is written live again below, which clears it.
|
|
7108
|
+
this.sql.exec(`UPDATE _substrat_roles SET revoked_at = ? WHERE tenant_id = ? AND revoked_at IS NULL`, new Date().toISOString(), tenantId);
|
|
6944
7109
|
for (const r of roles) {
|
|
6945
7110
|
this.sql.exec(`INSERT OR REPLACE INTO _substrat_roles (tenant_id, role_key, permissions, source, holder, revoked_at)
|
|
6946
7111
|
VALUES (?, ?, ?, ?, ?, NULL)`, tenantId, r.role_key, r.permissions, r.source, r.holder === 'system' ? 'system' : r.holder == null ? null : 'person');
|