@substrat-run/adapter-cloudflare 0.132.1 → 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.
- package/dist/control-plane-do.d.ts +10 -2
- package/dist/control-plane-do.d.ts.map +1 -1
- package/dist/control-plane-do.js +60 -15
- package/dist/control-plane-do.js.map +1 -1
- package/dist/host.d.ts +24 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +82 -19
- package/dist/host.js.map +1 -1
- package/dist/scope-do.d.ts.map +1 -1
- package/dist/scope-do.js +85 -29
- package/dist/scope-do.js.map +1 -1
- package/dist/sql.d.ts +3 -1
- package/dist/sql.d.ts.map +1 -1
- package/dist/sql.js +4 -0
- package/dist/sql.js.map +1 -1
- package/package.json +8 -7
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":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAcL,KAAK,WAAW,EAsCjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,
|
|
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
|
-
|
|
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
|
|
@@ -2822,8 +2864,8 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
2822
2864
|
this.ctx.storage.transactionSync(() => {
|
|
2823
2865
|
this.sql.exec(`INSERT INTO _substrat_job_runs
|
|
2824
2866
|
(id, module_id, job, instance, payload, status, cursor, counters, attempts,
|
|
2825
|
-
last_error, started_at, updated_at, next_attempt_at, ended_at)
|
|
2826
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, row.id, row.module_id, row.job, row.instance, row.payload, row.status, row.cursor, row.counters, row.attempts, row.last_error, row.started_at, row.updated_at, row.next_attempt_at, row.ended_at);
|
|
2867
|
+
last_error, started_at, updated_at, next_attempt_at, ended_at, subject_id)
|
|
2868
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, row.id, row.module_id, row.job, row.instance, row.payload, row.status, row.cursor, row.counters, row.attempts, row.last_error, row.started_at, row.updated_at, row.next_attempt_at, row.ended_at, row.subject_id ?? null);
|
|
2827
2869
|
});
|
|
2828
2870
|
return row;
|
|
2829
2871
|
}
|
|
@@ -2835,8 +2877,8 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
2835
2877
|
async jobRunInsert(row) {
|
|
2836
2878
|
this.sql.exec(`INSERT INTO _substrat_job_runs
|
|
2837
2879
|
(id, module_id, job, instance, payload, status, cursor, counters, attempts,
|
|
2838
|
-
last_error, started_at, updated_at, next_attempt_at, ended_at)
|
|
2839
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, row.id, row.module_id, row.job, row.instance, row.payload, row.status, row.cursor, row.counters, row.attempts, row.last_error, row.started_at, row.updated_at, row.next_attempt_at, row.ended_at);
|
|
2880
|
+
last_error, started_at, updated_at, next_attempt_at, ended_at, subject_id)
|
|
2881
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, row.id, row.module_id, row.job, row.instance, row.payload, row.status, row.cursor, row.counters, row.attempts, row.last_error, row.started_at, row.updated_at, row.next_attempt_at, row.ended_at, row.subject_id ?? null);
|
|
2840
2882
|
}
|
|
2841
2883
|
/**
|
|
2842
2884
|
* `running` runs whose backoff has elapsed, oldest first, after `afterId`.
|
|
@@ -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
|
-
|
|
3288
|
-
|
|
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));
|
|
@@ -3416,6 +3460,8 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3416
3460
|
'ALTER TABLE _substrat_migrations ADD COLUMN duration_ms INTEGER',
|
|
3417
3461
|
'ALTER TABLE _substrat_migrations ADD COLUMN rows_changed INTEGER',
|
|
3418
3462
|
'ALTER TABLE _substrat_tuples ADD COLUMN revoked_at TEXT',
|
|
3463
|
+
// #1632: legacy runs retain an unknown subject; no content-based backfill.
|
|
3464
|
+
'ALTER TABLE _substrat_job_runs ADD COLUMN subject_id TEXT',
|
|
3419
3465
|
// Executor retry state (#100). The defaults read as "terminal", which is
|
|
3420
3466
|
// right for every row already there: each is a completed delivery or a
|
|
3421
3467
|
// consumer dead-letter.
|
|
@@ -3599,6 +3645,10 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3599
3645
|
// not this one. Dropped, so a restore never carries a receipt over; the repair
|
|
3600
3646
|
// projection that follows writes this scope's own.
|
|
3601
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());
|
|
3602
3652
|
// Re-point the restored grants at THIS scope (after the spine exists, so a dump
|
|
3603
3653
|
// that carried no tuples table still finds one here).
|
|
3604
3654
|
if (destScopeId) {
|
|
@@ -3745,12 +3795,16 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
3745
3795
|
// A failed delivery's error text about one of those events (#1632) — keyed by event
|
|
3746
3796
|
// id, so the outbox predicate names it. Not a copy of the event; not counted.
|
|
3747
3797
|
this.sql.exec(DELIVERY_ERROR_REDACTION_SQL, REDACTED_DELIVERY_NOTE, REDACTED_DELIVERY_NOTE, subjectId);
|
|
3798
|
+
const sql = doRedactionSql(this.sql);
|
|
3748
3799
|
return {
|
|
3749
3800
|
events: doomed.length,
|
|
3750
3801
|
intents: this.redactSubjectIntents(subjectId, at),
|
|
3751
3802
|
// The job-run tables (#1632) — the kernel's walk, so the pure host runs the
|
|
3752
3803
|
// identical SQL.
|
|
3753
|
-
jobRuns: redactSubjectJobRuns(
|
|
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),
|
|
3754
3808
|
};
|
|
3755
3809
|
}
|
|
3756
3810
|
/**
|
|
@@ -4425,7 +4479,9 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
4425
4479
|
/** The directory's recorded-off modules (#1742), switched off in THIS unit right after the
|
|
4426
4480
|
* seat, so no sweep can run the grants the seat just re-created. `scopeId` is the scope
|
|
4427
4481
|
* this projection provisions (the object of `scopeTuples`), never another. */
|
|
4428
|
-
switchOff
|
|
4482
|
+
switchOff,
|
|
4483
|
+
/** Declared service subjects excluded only from human lockout repair (#1896). */
|
|
4484
|
+
serviceSubjects) {
|
|
4429
4485
|
if (await this.isReaped())
|
|
4430
4486
|
return [];
|
|
4431
4487
|
// A projection that arrives after this scope was reaped is dropped, not
|
|
@@ -4510,14 +4566,14 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
4510
4566
|
this.sql.exec(seat.sql, ...seat.params);
|
|
4511
4567
|
}
|
|
4512
4568
|
// #1659's one exception: the owner-of-record's seat comes back over a revoke when
|
|
4513
|
-
//
|
|
4514
|
-
// That is the #332 lockout this path exists to repair
|
|
4515
|
-
//
|
|
4569
|
+
// No HUMAN holder remains — declared service subjects cannot prevent lockout repair.
|
|
4570
|
+
// That is the #332 lockout this path exists to repair. The flip guard below still
|
|
4571
|
+
// counts service authority; only the repair excludes the declared service subjects.
|
|
4516
4572
|
// With any other effective holder, the revoke stands: a hand-over that seats a
|
|
4517
4573
|
// successor before unseating the owner is not undone by the next promote. A holder of
|
|
4518
4574
|
// a role the vertical no longer defines is NOT one — it passes no check, so it must
|
|
4519
4575
|
// not stand in for the holder this repair exists to restore.
|
|
4520
|
-
if (roles.length > 0 && !this.hasEffectiveRoleGrant(tenantId)) {
|
|
4576
|
+
if (roles.length > 0 && !this.hasEffectiveRoleGrant(tenantId, serviceSubjects)) {
|
|
4521
4577
|
for (const st of scopeTuples ?? []) {
|
|
4522
4578
|
if (!st.lockout_reseat)
|
|
4523
4579
|
continue;
|
|
@@ -4566,8 +4622,8 @@ export function defineScopeDO(modules, bareOps) {
|
|
|
4566
4622
|
* kernel's `effectiveRoleGrantQuery`, where it is tested against a real SQLite. A tuple
|
|
4567
4623
|
* for a role the vertical no longer defines counts for nothing, exactly as in the local
|
|
4568
4624
|
* checker, which expands a role only through its definition. */
|
|
4569
|
-
hasEffectiveRoleGrant(tenantId) {
|
|
4570
|
-
const q = effectiveRoleGrantQuery(tenantId, new Date().toISOString());
|
|
4625
|
+
hasEffectiveRoleGrant(tenantId, excludedSubjects) {
|
|
4626
|
+
const q = effectiveRoleGrantQuery(tenantId, new Date().toISOString(), undefined, excludedSubjects);
|
|
4571
4627
|
const row = this.sql.exec(q.sql, ...q.params).toArray()[0];
|
|
4572
4628
|
return row?.effective === 1;
|
|
4573
4629
|
}
|