@substrat-run/adapter-cloudflare 0.133.0 → 0.134.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.
@@ -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,EAcL,KAAK,WAAW,EAsCjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAoCL,KAAK,SAAS,EAmBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EAuEtB,MAAM,sBAAsB,CAAC;AAsF9B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AA0bD;;;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,CAslKjE"}
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,EAsCjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAuCL,KAAK,SAAS,EAmBd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EA+EtB,MAAM,sBAAsB,CAAC;AAsF9B;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AA6bD;;;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,CAupKjE"}
package/dist/scope-do.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { REWIND_REFUSED } from './rewind-refusal.js';
2
2
  import { DurableObject } from 'cloudflare:workers';
3
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
- 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, 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, REFUSALS_DDL, refusalInsert, refusedTransitionOf, 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';
4
+ 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, redactSubjectScopeText, assertRowLimit, assertRowOffset, 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, ATTACHMENT_TEXT_DDL, attachmentRecordOfRow, enqueueAttachmentText, reconcileAttachmentText, recordAttachmentText, searchAttachments, IDEMPOTENCY_DDL, REFUSALS_DDL, refusalInsert, refusedTransitionOf, 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
- import { doScopedSql, doBuiltColumnsOf, doSpineSql } from './sql.js';
7
+ import { doScopedSql, doBuiltColumnsOf, doRedactionSql, doSpineSql } from './sql.js';
8
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, readLifecycleFlow, readHistory, readInvocation, readUndrainedOutbox, walkEventCause, walkEventEffects, } from '@substrat-run/kernel';
9
9
  import { createDoTupleChecker, createLocalControlPlaneReader } from './checker.js';
10
10
  import { assertNoSpineReference, assertSpineTablesBuilt, dumpRowsInsert, isSpineTable, repointScopeGrants, spineColumnAdditions } from '@substrat-run/kernel';
@@ -312,6 +312,9 @@ const KERNEL_DDL = `
312
312
  );
313
313
  CREATE INDEX IF NOT EXISTS _substrat_attachments_entity
314
314
  ON _substrat_attachments (entity_type, entity_id);
315
+ -- #1575: attachment text and its FTS5 index, shared from @substrat-run/kernel. After
316
+ -- _substrat_attachments, whose delete trigger it adds.
317
+ ${ATTACHMENT_TEXT_DDL}
315
318
  -- #901: an entity's version is the ULID of the last event about it, so
316
319
  -- MAX(id) per (entity_type, entity_id) is the read. The id column sits last so
317
320
  -- SQLite walks to the end of the matched range instead of aggregating over it.
@@ -2089,19 +2092,7 @@ export function defineScopeDO(modules, bareOps) {
2089
2092
  const row = this.sql
2090
2093
  .exec('SELECT * FROM _substrat_attachments WHERE id = ?', attachmentId)
2091
2094
  .toArray()[0];
2092
- if (!row)
2093
- return null;
2094
- return attachmentRecord.parse({
2095
- id: row.id,
2096
- entity: { entityType: row.entity_type, entityId: row.entity_id },
2097
- filename: row.filename,
2098
- contentType: row.content_type,
2099
- size: Number(row.size),
2100
- sha256: row.sha256,
2101
- visibility: row.visibility,
2102
- createdBy: row.created_by,
2103
- createdAt: row.created_at,
2104
- });
2095
+ return row ? attachmentRecordOfRow(row) : null;
2105
2096
  }
2106
2097
  /** Record an uploaded attachment: write gate + row + `attachment.added`, one txn. */
2107
2098
  async attachmentAdd(record, principal, tenantId, scopeId,
@@ -2131,6 +2122,9 @@ export function defineScopeDO(modules, bareOps) {
2131
2122
  piiClass: 'none',
2132
2123
  payload: { attachment: parsed },
2133
2124
  });
2125
+ // #1575: queue its text extraction in the same transaction — a `pending` row
2126
+ // and a job run. Nothing is extracted here, so nothing here can fail the upload.
2127
+ enqueueAttachmentText(doSpineSql(this.sql), parsed.id, ulid(), new Date().toISOString());
2134
2128
  });
2135
2129
  }
2136
2130
  catch (err) {
@@ -2263,6 +2257,49 @@ export function defineScopeDO(modules, bareOps) {
2263
2257
  return record;
2264
2258
  });
2265
2259
  }
2260
+ // -- attachment text (#1575) ---------------------------------------------------
2261
+ /**
2262
+ * Extracted-text search as `ctx`'s subject: authorized first — the check `open` makes,
2263
+ * the target's read key on the owning entity — then matched over readable owners only
2264
+ * (`searchAttachments`). The term and the limit are judged here again, never trusted
2265
+ * from the coordinator.
2266
+ */
2267
+ searchAttachmentsAs(ctx, term, limit) {
2268
+ return searchAttachments(doSpineSql(this.sql), { targets: this.attachmentTargets, check: (permission, entity) => ctx.check(permission, entity) }, term, limit);
2269
+ }
2270
+ /**
2271
+ * #1575: `ScopeAttachments.search` for a principal or a connection. Its failure travels as
2272
+ * DATA, the capability verbs' envelope, because a throw across this RPC keeps only its
2273
+ * message — and the too-many-owners refusal's `reason` is what a UI explains it by.
2274
+ */
2275
+ async attachmentSearch(term, limit, principal, tenantId, scopeId, connectionId) {
2276
+ await this.ensureMigrations();
2277
+ try {
2278
+ const ctx = this.operationContext(principal, tenantId, scopeId, undefined, connectionId, undefined, undefined, undefined, 'attachments.search');
2279
+ return { value: await this.searchAttachmentsAs(ctx, term, limit) };
2280
+ }
2281
+ catch (err) {
2282
+ return { failure: toWireFailure(err) };
2283
+ }
2284
+ }
2285
+ /**
2286
+ * #1575: the extraction job's read of one record. No gate, deliberately — extraction
2287
+ * is the kernel's own derivation and the coordinator is its only caller; what a person
2288
+ * learns is gated at search.
2289
+ */
2290
+ async attachmentTextSource(attachmentId) {
2291
+ await this.ensureMigrations();
2292
+ return this.attachmentRow(attachmentId);
2293
+ }
2294
+ /**
2295
+ * #1575: write one extraction outcome, guarded on the attachment still existing.
2296
+ * Through the queue, so it can never land inside an invoke's or an upload's open
2297
+ * transaction and roll back with it.
2298
+ */
2299
+ async attachmentTextRecord(attachmentId, outcome) {
2300
+ await this.ensureMigrations();
2301
+ return this.queue.enqueue(async () => this.ctx.storage.transactionSync(() => recordAttachmentText(doSpineSql(this.sql), attachmentId, outcome, new Date().toISOString())));
2302
+ }
2266
2303
  // -- attachments through a capability (#1686) -------------------------------
2267
2304
  // The coordinator hashed the session token; only the hash arrives. Each verb resolves it
2268
2305
  // INSIDE the queue, as `invoke` does, so nothing can revoke between the resolution and
@@ -2332,6 +2369,11 @@ export function defineScopeDO(modules, bareOps) {
2332
2369
  return record;
2333
2370
  });
2334
2371
  }
2372
+ /** #1575: extracted-text search as the capability — its keys, its subtree, its minter now. */
2373
+ async capabilityAttachmentSearch(term, limit, sessionHash, tenantId, scopeId) {
2374
+ await this.ensureMigrations();
2375
+ return this.asCapability(sessionHash, tenantId, scopeId, 'attachments.search', (ctx) => this.searchAttachmentsAs(ctx, term, limit));
2376
+ }
2335
2377
  /**
2336
2378
  * Refuse an upload or a remove through a capability, and record it. Throws for an
2337
2379
  * unknown attachment id too — the same answer either way — but only a known target has
@@ -3284,8 +3326,10 @@ export function defineScopeDO(modules, bareOps) {
3284
3326
  .toArray().map((r) => r.name));
3285
3327
  if (!known.has(table))
3286
3328
  throw new Error(`unknown table '${table}'`);
3287
- const l = Math.min(Math.max(1, limit), SCOPE_TABLE_PAGE_MAX);
3288
- const o = Math.max(0, offset);
3329
+ // The ceiling clamps; a bound SQLite would misread (NaN, non-finite, fractional,
3330
+ // negative) is refused instead of reaching LIMIT / OFFSET (#1632).
3331
+ const l = Math.min(assertRowLimit('limit', limit), SCOPE_TABLE_PAGE_MAX);
3332
+ const o = assertRowOffset('offset', offset);
3289
3333
  const cursor = this.sql.exec(`SELECT * FROM "${table}" LIMIT ? OFFSET ?`, l, o);
3290
3334
  const columns = cursor.columnNames;
3291
3335
  const rows = Array.from(cursor.raw(), (row) => row.map(cellToJson));
@@ -3601,6 +3645,10 @@ export function defineScopeDO(modules, bareOps) {
3601
3645
  // not this one. Dropped, so a restore never carries a receipt over; the repair
3602
3646
  // projection that follows writes this scope's own.
3603
3647
  this.sql.exec(`DELETE FROM _substrat_meta WHERE key = 'provisioned_for'`);
3648
+ // #1575: attachment text is not in a dump, so the load left it as it was. Drop the
3649
+ // text of attachments the dump did not bring back, and queue extraction for those
3650
+ // it brought back without text — the bytes decide what that run finds.
3651
+ reconcileAttachmentText(doSpineSql(this.sql), ulid, new Date().toISOString());
3604
3652
  // Re-point the restored grants at THIS scope (after the spine exists, so a dump
3605
3653
  // that carried no tuples table still finds one here).
3606
3654
  if (destScopeId) {
@@ -3747,12 +3795,16 @@ export function defineScopeDO(modules, bareOps) {
3747
3795
  // A failed delivery's error text about one of those events (#1632) — keyed by event
3748
3796
  // id, so the outbox predicate names it. Not a copy of the event; not counted.
3749
3797
  this.sql.exec(DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, REDACTED_DELIVERY_NOTE, subjectId);
3798
+ const sql = doRedactionSql(this.sql);
3750
3799
  return {
3751
3800
  events: doomed.length,
3752
3801
  intents: this.redactSubjectIntents(subjectId, at),
3753
3802
  // The job-run tables (#1632) — the kernel's walk, so the pure host runs the
3754
3803
  // identical SQL.
3755
- jobRuns: redactSubjectJobRuns((sql, params) => this.sql.exec(sql, ...params).toArray(), subjectId, at),
3804
+ jobRuns: redactSubjectJobRuns(sql, subjectId, at),
3805
+ // The free-text copies (#1632), and the tombstoned intents the coordinator hands to
3806
+ // the directory half. Last, so those ids include every intent tombstoned above.
3807
+ ...redactSubjectScopeText(sql, subjectId, at),
3756
3808
  };
3757
3809
  }
3758
3810
  /**