@substrat-run/adapter-sqlite 0.89.0 → 0.90.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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type BlobStoreHandle, type ConnectionId, type ModuleId, type DomainEvent, type PlatformRequestId, type PlatformRequest, type PlatformRequestFilter, type PlatformRequestStatus, type PlatformRequestFailure, type ProjectedConnectionGrant, type PlatformActorId, type PrincipalId, type ScopeDump, type ScopeId, type TenantId, type TenantStoreHandle } from '@substrat-run/contracts';
1
+ import { type BlobStoreHandle, type ConnectionId, type ModuleId, type DomainEvent, type PlatformRequestId, type PlatformRequest, type PlatformRequestFilter, type PlatformRequestStatus, type PlatformRequestFailure, type ProjectedConnectionGrant, type PlatformActorId, type PrincipalId, type ScopeDump, type ScopeId, type ImpersonationSessionId, type TenantId, type TenantStoreHandle } from '@substrat-run/contracts';
2
2
  import { type BlobStoreProvisionInput, type ScopeAttachments, type ExecutorDeadLetter, type ExecutorDrainReport, type MigrateScopeOutcome, type MigrationFrontier, type ExecutorHandler, type ExecutorRetryPolicy, type ConnectorHandler, type ConnectorOptions, type Clock, type FetchLike, type SecretBox, type HostAdmin, type ModuleRegistration, type OperationHandler, type PermissionChecker, type ProvisionScopeInput, type ScheduleRegistration, type ScheduleRunReport, type ScopeHost, type ScopeStub, type ScopeStubOptions, type TenantRelationalStore, type TenantStoreProvisionInput } from '@substrat-run/kernel';
3
3
  export interface SqliteScopeHostOptions {
4
4
  /** Directory holding one SQLite file per scope plus the directory database. */
@@ -171,10 +171,21 @@ export declare class SqliteScopeHost implements ScopeHost {
171
171
  */
172
172
  getConnectorScope(connectionId: ConnectionId, scopeId: ScopeId): Promise<ScopeStub>;
173
173
  getConnectorAttachments(connectionId: ConnectionId, scopeId: ScopeId): Promise<ScopeAttachments>;
174
+ /** Read one session, or say so. The door and every invoke through it come here. */
175
+ private readImpersonation;
176
+ /**
177
+ * The impersonation door (K-42) — mirror of `getConnectorScope`/`getSystemScope`.
178
+ *
179
+ * The session is the authority and it is read from the directory, never from
180
+ * the caller: a stub cannot be minted by describing a session, only by naming
181
+ * one somebody opened and the admin log recorded. Everything after that is an
182
+ * ordinary invoke against the impersonated principal.
183
+ */
184
+ getImpersonatedScope(session: ImpersonationSessionId, tenantId: TenantId, scopeId: ScopeId, options?: ScopeStubOptions): Promise<ScopeStub>;
174
185
  getSystemScope(moduleId: ModuleId, tenantId: TenantId, scopeId: ScopeId): Promise<ScopeStub>;
175
186
  registeredSchedules(): ScheduleRegistration[];
176
187
  runDueSchedules(moduleId: ModuleId, tenantId: TenantId, scopeId: ScopeId): Promise<ScheduleRunReport>;
177
- /** The stub body, shared by the principal and connection doors. */
188
+ /** The stub body, shared by the principal, connection, system and impersonation doors. */
178
189
  private buildStub;
179
190
  /**
180
191
  * The entity a guarded operation's precondition is about (#129).
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync
2
2
  import { dirname, join } from 'node:path';
3
3
  import Database from 'better-sqlite3';
4
4
  import { accessLogEntry, adminLogEntry, opsFailureEntry, ATTACHMENT_ADDED, ATTACHMENT_REMOVED, attachmentRecord, createTenantInput, domainEvent, domainEventInput, entitlementGrant, entitlementGrantInput, eventId, meterReading, platformRequestInput, platformRequestId, platformRequest, MAX_PENDING_PLATFORM_REQUESTS, identityLink, identityPool, instant, connection, connectionGrant, connectionGrantRecord, connectionSecret, systemGrant, subjectRef, requestFingerprint, createConnectionInput, projectedConnectionGrant, projectedConnectionKey, moduleManifest, createOrgInput, promotionAcknowledgement, bindHostnameInput, channelHistoryEntry, hostnameBinding, publishVersionInput, AUTO_ADMISSION_NOTE, routeTarget, registerVerticalInput, vertical as verticalSchema, verticalChannel, verticalVersion, objectRef, grantRefFromProof, org as orgSchema, orgMembership, principalId, resolvedIdentity, roleDefinition, scope as scopeSchema, tenant as tenantSchema, tenantRole, subjectShredReceipt, SCOPE_TABLE_PAGE_DEFAULT, SCOPE_TABLE_PAGE_MAX, SCOPE_QUERY_ROW_MAX, verticalServingState, outboundOfManifestJson, listLimitOf, substratError, } from '@substrat-run/contracts';
5
- import { asPrincipal, assertAllowed, assertReadOnlyQuery, attachmentBlobKey, entitlementDenial, foldMeterReading, parseValidationRecords, resolveScopeRecord, ulid, OPS_FAILURE_RETENTION_DAYS, backoffAt, platformRequestHistoryQuery, PLATFORM_REQUEST_COLUMNS, denialListQuery, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialBucketRow, resolveRetryPolicy, isSecretBoxConfigured, unconfiguredSecretBox, createSubjectKeys, generateSealingKeyPair, noSealingKeyMessage, openSealed, sealTo, ConnectionSealingKeyUnavailableError, PermissionDenied, createAtomic, NotSearchable, NotListable, listIndexMigrations, listIndexPlans, listQuery, cursorOf, isSearchIndexTable, searchIndexDdl, searchIndexMigrations, searchIndexPlans, searchLimit, searchMatchExpression, searchQuery, entityVersionQuery, entityVersionOf, assertIfMatch, OUTBOX_ENTITY_INDEX, IDEMPOTENCY_DDL, assertIdempotencyKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, } from '@substrat-run/kernel';
5
+ import { asPrincipal, assertAllowed, assertReadOnlyQuery, attachmentBlobKey, entitlementDenial, foldMeterReading, parseValidationRecords, resolveScopeRecord, ulid, OPS_FAILURE_RETENTION_DAYS, backoffAt, platformRequestHistoryQuery, PLATFORM_REQUEST_COLUMNS, denialListQuery, denialSummaryQuery, denialTotalsQuery, DENIAL_WINDOW_QUERY, mapDenialRow, mapDenialBucketRow, IMPERSONATION_COLUMNS, IMPERSONATION_DDL, ImpersonationRefused, assertImpersonationWrites, assertSessionUsable, impersonationByIdQuery, impersonationListQuery, impersonationRowValues, impersonationStampOf, mapImpersonationRow, newImpersonationSession, resolveRetryPolicy, isSecretBoxConfigured, unconfiguredSecretBox, createSubjectKeys, generateSealingKeyPair, noSealingKeyMessage, openSealed, sealTo, ConnectionSealingKeyUnavailableError, PermissionDenied, createAtomic, NotSearchable, NotListable, listIndexMigrations, listIndexPlans, listQuery, cursorOf, isSearchIndexTable, searchIndexDdl, searchIndexMigrations, searchIndexPlans, searchLimit, searchMatchExpression, searchQuery, entityVersionQuery, entityVersionOf, assertIfMatch, OUTBOX_ENTITY_INDEX, IDEMPOTENCY_DDL, assertIdempotencyKey, idempotencyLookupQuery, idempotencyPruneStatement, idempotencyRecordStatement, idempotencyOptedOutMessage, replayFor, } from '@substrat-run/kernel';
6
6
  import { ScopeActor } from './actor.js';
7
7
  import { createTupleChecker } from './checker.js';
8
8
  const KERNEL_DDL = `
@@ -22,6 +22,10 @@ const KERNEL_DDL = `
22
22
  -- K-34: the checks the emitting operation passed (JSON [{permission, grant?}]).
23
23
  -- NULL on rows written before the field existed — honestly unrecorded, not empty.
24
24
  authorization TEXT,
25
+ -- K-42: the staff actor + session an impersonated operation ran under (JSON
26
+ -- session/by). NULL is the ordinary case — nobody was impersonating — and the
27
+ -- actor column above stays the principal the permission model answered about.
28
+ impersonation TEXT,
25
29
  drained_at TEXT
26
30
  );
27
31
  -- platform-intents.md: durable intents a vertical enqueues (ctx.requestPlatform) for the platform
@@ -32,6 +36,8 @@ const KERNEL_DDL = `
32
36
  kind TEXT NOT NULL,
33
37
  payload TEXT NOT NULL,
34
38
  requested_by TEXT NOT NULL,
39
+ -- K-42: the staff actor + session, when the intent was raised under one.
40
+ impersonation TEXT,
35
41
  status TEXT NOT NULL DEFAULT 'pending',
36
42
  attempts INTEGER NOT NULL DEFAULT 0,
37
43
  last_error TEXT,
@@ -57,6 +63,8 @@ const KERNEL_DDL = `
57
63
  tenant_id TEXT NOT NULL,
58
64
  scope_id TEXT,
59
65
  operation TEXT,
66
+ -- K-42: WHICH of the two actors was refused is exactly what this log is for.
67
+ impersonation TEXT,
60
68
  at TEXT NOT NULL,
61
69
  drained_at TEXT
62
70
  );
@@ -195,6 +203,7 @@ function rowToPlatformRequest(r) {
195
203
  kind: r.kind,
196
204
  payload: JSON.parse(r.payload),
197
205
  requestedBy: JSON.parse(r.requested_by),
206
+ impersonation: r.impersonation == null ? null : JSON.parse(r.impersonation),
198
207
  status: r.status,
199
208
  attempts: r.attempts,
200
209
  lastError: r.last_error,
@@ -746,6 +755,7 @@ export class SqliteScopeHost {
746
755
  shredded_at TEXT,
747
756
  PRIMARY KEY (scope_id, subject_id)
748
757
  );
758
+ ${IMPERSONATION_DDL}
749
759
  CREATE TABLE IF NOT EXISTS _substrat_admin_log (
750
760
  id TEXT PRIMARY KEY,
751
761
  actor TEXT NOT NULL,
@@ -1821,6 +1831,40 @@ export class SqliteScopeHost {
1821
1831
  const store = this.attachmentStore(conn.tenant_id, scope.vertical);
1822
1832
  return this.buildAttachments(rt, { kind: 'connection', id: connectionId }, store);
1823
1833
  }
1834
+ /** Read one session, or say so. The door and every invoke through it come here. */
1835
+ readImpersonation(session) {
1836
+ const q = impersonationByIdQuery(session);
1837
+ const row = this.directory.prepare(q.sql).get(...q.params);
1838
+ if (!row)
1839
+ throw new ImpersonationRefused(`unknown impersonation session: ${session}`);
1840
+ return mapImpersonationRow(row);
1841
+ }
1842
+ /**
1843
+ * The impersonation door (K-42) — mirror of `getConnectorScope`/`getSystemScope`.
1844
+ *
1845
+ * The session is the authority and it is read from the directory, never from
1846
+ * the caller: a stub cannot be minted by describing a session, only by naming
1847
+ * one somebody opened and the admin log recorded. Everything after that is an
1848
+ * ordinary invoke against the impersonated principal.
1849
+ */
1850
+ async getImpersonatedScope(session, tenantId, scopeId, options) {
1851
+ const record = this.readImpersonation(session);
1852
+ assertSessionUsable(record, this.clock(), { tenantId, scopeId });
1853
+ // Same lifecycle gates as the principal door, and reached the same way: a
1854
+ // suspended tenant refuses a support session exactly as it refuses a user.
1855
+ const scope = this.directory
1856
+ .prepare('SELECT tenant_id, status FROM scopes WHERE scope_id = ?')
1857
+ .get(scopeId);
1858
+ if (!scope || scope.tenant_id !== tenantId) {
1859
+ throw new Error(`unknown scope for tenant: (${tenantId}, ${scopeId})`);
1860
+ }
1861
+ if (scope.status !== 'active') {
1862
+ throw new Error(`scope not active (status: ${scope.status}): ${scopeId}`);
1863
+ }
1864
+ const rt = this.runtime(tenantId, scopeId);
1865
+ await this.applyPendingMigrations(rt);
1866
+ return this.buildStub(tenantId, scopeId, rt, asPrincipal(record.principal), options, record.id);
1867
+ }
1824
1868
  async getSystemScope(moduleId, tenantId, scopeId) {
1825
1869
  // The scheduler's door (#383) — mirror of getConnectorScope. The module must be
1826
1870
  // registered on this host (a schedule can only invoke its own vertical's ops),
@@ -1922,8 +1966,15 @@ export class SqliteScopeHost {
1922
1966
  }
1923
1967
  return report;
1924
1968
  }
1925
- /** The stub body, shared by the principal and connection doors. */
1926
- buildStub(tenantId, scopeId, rt, subject, options) {
1969
+ /** The stub body, shared by the principal, connection, system and impersonation doors. */
1970
+ buildStub(tenantId, scopeId, rt, subject, options,
1971
+ /**
1972
+ * K-42: the session this stub acts under. `subject` is already the
1973
+ * IMPERSONATED principal — that is what makes the permission model answer
1974
+ * about them with no override branch anywhere — and this is what every
1975
+ * record additionally keeps, plus the bound a read-only session is held to.
1976
+ */
1977
+ sessionId) {
1927
1978
  const operations = this.operations;
1928
1979
  return {
1929
1980
  tenantId,
@@ -1934,6 +1985,20 @@ export class SqliteScopeHost {
1934
1985
  // gives it, so a demo and a hosted vertical answer 404 for the same reason.
1935
1986
  if (!handler)
1936
1987
  return Promise.reject(substratError('not_found', `unknown operation: ${operation}`));
1988
+ // K-42: re-read the session on EVERY invoke, not once when the stub was
1989
+ // minted. A stub is a capability and nothing takes it away, so a session
1990
+ // checked only at the door would be a time box the one caller holding it
1991
+ // never runs out of — and an `endImpersonation` would stop nothing.
1992
+ let session;
1993
+ if (sessionId !== undefined) {
1994
+ try {
1995
+ session = this.readImpersonation(sessionId);
1996
+ assertSessionUsable(session, this.clock(), { tenantId, scopeId });
1997
+ }
1998
+ catch (err) {
1999
+ return Promise.reject(err);
2000
+ }
2001
+ }
1937
2002
  // #129. A precondition the operation never declared is REFUSED, not
1938
2003
  // ignored. A caller sending `If-Match` believes its write is serialised,
1939
2004
  // and quietly dropping the header leaves that belief intact while the
@@ -1979,7 +2044,7 @@ export class SqliteScopeHost {
1979
2044
  const invoked = await rt.actor.enqueue(async () => {
1980
2045
  // Fresh per operation: the context carries the K-34 authorization accumulator,
1981
2046
  // which must not leak across operations (invokes are serialized per scope).
1982
- const ctx = this.operationContext(rt, subject, undefined, signals);
2047
+ const ctx = this.operationContext(rt, subject, undefined, signals, session);
1983
2048
  const clonedInput = structuredClone(input);
1984
2049
  // #893: parse, don't trust — at the scope door, from the operation's own
1985
2050
  // declaration. BEFORE `BEGIN`, so a malformed call never opens a
@@ -2070,22 +2135,36 @@ export class SqliteScopeHost {
2070
2135
  const prune = idempotencyPruneStatement(at);
2071
2136
  rt.db.prepare(prune.sql).run(...prune.params);
2072
2137
  }
2073
- rt.db.exec('COMMIT');
2138
+ // K-42: a read-only session's transaction is ROLLED BACK, never
2139
+ // committed — the half of read-only that is a mechanism rather than a
2140
+ // promise. `ctx.emit` and the other effecting verbs refuse outright,
2141
+ // which is what a support engineer sees; this is what holds when a
2142
+ // handler writes a row with plain `ctx.sql.exec` and calls none of
2143
+ // them. Same shape as the scope console's read-only query on the DO.
2144
+ if (session?.mode === 'read-only')
2145
+ rt.db.exec('ROLLBACK');
2146
+ else
2147
+ rt.db.exec('COMMIT');
2074
2148
  }
2075
2149
  catch (err) {
2076
2150
  rt.db.exec('ROLLBACK');
2077
2151
  // K-35: a refused check rolled the operation back. Record it now — a fresh
2078
2152
  // statement in autocommit, AFTER the rollback, so the denial survives it.
2079
2153
  if (err instanceof PermissionDenied)
2080
- this.recordDenial(rt, subject, operation, err);
2154
+ this.recordDenial(rt, subject, operation, err, session);
2081
2155
  throw err;
2082
2156
  }
2083
2157
  // Post-commit, still inside the actor task: drain outbox → consumers,
2084
2158
  // then → executors. Prompt dispatch (K-22 §4.2): the common case
2085
2159
  // completes inside this request, with the outbox as the retry backstop
2086
2160
  // if it does not.
2087
- await this.dispatch(rt);
2088
- await this.dispatchExecutors(rt);
2161
+ // A read-only session committed nothing, so there is nothing of its to
2162
+ // drain — and draining anyway would run consumers as a side effect of a
2163
+ // session that may not have side effects.
2164
+ if (session?.mode !== 'read-only') {
2165
+ await this.dispatch(rt);
2166
+ await this.dispatchExecutors(rt);
2167
+ }
2089
2168
  return structuredClone(result);
2090
2169
  });
2091
2170
  if (signals.platformRequests > 0)
@@ -2471,7 +2550,9 @@ export class SqliteScopeHost {
2471
2550
  * catch AFTER `ROLLBACK`, so this INSERT runs in autocommit and survives — the whole
2472
2551
  * point, since the denial is exactly the write the rolled-back operation could not make.
2473
2552
  */
2474
- recordDenial(rt, subject, operation, err) {
2553
+ recordDenial(rt, subject, operation, err,
2554
+ /** K-42: the session the refused call ran under, when it ran under one. */
2555
+ impersonation) {
2475
2556
  // Only an ENFORCED denial (assertAllowed, which attaches the checked permission +
2476
2557
  // node) is recorded. A module's own hand-thrown `new PermissionDenied('…')` is its
2477
2558
  // policy, carries no permission key, and is left to the module.
@@ -2484,9 +2565,9 @@ export class SqliteScopeHost {
2484
2565
  : subject.id;
2485
2566
  rt.db
2486
2567
  .prepare(`INSERT INTO _substrat_denials
2487
- (id, actor, permission, tenant_id, scope_id, operation, at)
2488
- VALUES (?, ?, ?, ?, ?, ?, ?)`)
2489
- .run(ulid(), JSON.stringify(actor), err.permission, err.node.tenantId, err.node.scopeId ?? null, operation, new Date().toISOString());
2568
+ (id, actor, permission, tenant_id, scope_id, operation, impersonation, at)
2569
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
2570
+ .run(ulid(), JSON.stringify(actor), err.permission, err.node.tenantId, err.node.scopeId ?? null, operation, impersonation ? JSON.stringify(impersonationStampOf(impersonation)) : null, new Date().toISOString());
2490
2571
  }
2491
2572
  parseOutboxRow(row) {
2492
2573
  return domainEvent.parse({
@@ -2501,6 +2582,11 @@ export class SqliteScopeHost {
2501
2582
  piiClass: row.pii_class,
2502
2583
  ...(row.subject_id ? { subjectId: row.subject_id } : {}),
2503
2584
  ...(row.authorization ? { authorization: JSON.parse(row.authorization) } : {}),
2585
+ // K-42: the stamp survives the read, so a consumer's event and an executor's
2586
+ // are the same fact the stored row is. Absent rather than null when nobody
2587
+ // was impersonating, because `DomainEvent.impersonation` is optional — the
2588
+ // shape module code never sees is also the shape it cannot branch on.
2589
+ ...(row.impersonation ? { impersonation: JSON.parse(row.impersonation) } : {}),
2504
2590
  payload: row.payload === null ? undefined : JSON.parse(row.payload),
2505
2591
  });
2506
2592
  }
@@ -4242,6 +4328,48 @@ export class SqliteScopeHost {
4242
4328
  this.recordAccess(actor, 'shredSubject', { tenantId, scopeId }, { subjectId }, redacted.changes);
4243
4329
  return receipt;
4244
4330
  },
4331
+ // -- impersonation (K-42, #868) ----------------------------------------
4332
+ beginImpersonation: async (actor, input) => {
4333
+ // Fail closed on the scope before minting anything, exactly as the
4334
+ // principal door does: a session naming a scope that is not there is a
4335
+ // credential for nothing, and issuing one anyway means the admin log
4336
+ // records an access that could never have happened.
4337
+ this.assertScope(input.tenantId, input.scopeId);
4338
+ const session = newImpersonationSession(ulid(), actor, input, this.clock());
4339
+ this.directory
4340
+ .prepare(`INSERT INTO _substrat_impersonations
4341
+ (${IMPERSONATION_COLUMNS}) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
4342
+ .run(...impersonationRowValues(session));
4343
+ // BEFORE the session is returned, so the log entry precedes every row it
4344
+ // will ever touch. The reason rides in `after` — it is the field a review
4345
+ // reads first, and a log saying a session opened but not why is half a
4346
+ // record.
4347
+ this.recordAdmin(actor, 'beginImpersonation', { tenantId: session.tenantId, scopeId: session.scopeId }, null, session);
4348
+ return session;
4349
+ },
4350
+ endImpersonation: async (actor, id) => {
4351
+ const before = this.readImpersonation(id);
4352
+ // Idempotent: "stop that session" must not fail because somebody else
4353
+ // already stopped it, and it must not move the moment it stopped.
4354
+ if (before.endedAt !== null)
4355
+ return before;
4356
+ this.directory
4357
+ .prepare('UPDATE _substrat_impersonations SET ended_at = ? WHERE id = ?')
4358
+ .run(this.clock(), id);
4359
+ const after = this.readImpersonation(id);
4360
+ this.recordAdmin(actor, 'endImpersonation', { tenantId: after.tenantId, scopeId: after.scopeId }, before, after);
4361
+ return after;
4362
+ },
4363
+ listImpersonations: async (actor, filter) => {
4364
+ const q = impersonationListQuery(filter, this.clock());
4365
+ const rows = this.directory.prepare(q.sql).all(...q.params);
4366
+ const sessions = rows.map(mapImpersonationRow);
4367
+ // A staff READ, logged like every other one (K-24) — including the
4368
+ // `result_count`, which is what separates "checked one session" from
4369
+ // "enumerated every support session on the platform".
4370
+ this.recordAccess(actor, 'listImpersonations', { tenantId: (filter?.tenantId ?? null) }, filter ?? null, sessions.length);
4371
+ return sessions;
4372
+ },
4245
4373
  reapTenant: async (actor, tenantId) => {
4246
4374
  // The terminal tenant reap (§4.8), the tenant analogue of reapScope. The
4247
4375
  // caller (the reap route / grace-window sweep) has already reaped every scope's
@@ -5113,7 +5241,14 @@ export class SqliteScopeHost {
5113
5241
  */
5114
5242
  operationContext(rt, subject, overrideActor,
5115
5243
  /** #458: per-invoke tally of `ctx.requestPlatform` calls; absent for consumer dispatch. */
5116
- signals) {
5244
+ signals,
5245
+ /**
5246
+ * K-42: the session this operation is running under, if any. Read here and
5247
+ * nowhere module code can reach — a vertical that could see it could branch
5248
+ * on it, and "hide this row when support is looking" is the one behaviour
5249
+ * this whole feature exists to make impossible.
5250
+ */
5251
+ impersonation) {
5117
5252
  const principal = subject.id;
5118
5253
  const checker = this.checker;
5119
5254
  const relations = this.relations;
@@ -5124,6 +5259,10 @@ export class SqliteScopeHost {
5124
5259
  // whatever has passed up to that point. A system/override actor is unconditionally
5125
5260
  // allowed, so its checks are not authorizations and are not recorded.
5126
5261
  const passed = [];
5262
+ // K-42: the two-actor stamp, computed once per operation. Every record this
5263
+ // operation writes about who did what carries it; nothing module code passes
5264
+ // can add it, change it or drop it.
5265
+ const stamp = impersonation ? impersonationStampOf(impersonation) : undefined;
5127
5266
  /**
5128
5267
  * The operation's instant (#812), read ONCE when the context is built.
5129
5268
  *
@@ -5200,6 +5339,7 @@ export class SqliteScopeHost {
5200
5339
  sql: scopedSql(rt.db),
5201
5340
  now: () => at,
5202
5341
  emit: (event) => {
5342
+ assertImpersonationWrites(impersonation, 'ctx.emit');
5203
5343
  const input = domainEventInput.parse(event);
5204
5344
  const full = domainEvent.parse({
5205
5345
  ...input,
@@ -5214,15 +5354,18 @@ export class SqliteScopeHost {
5214
5354
  ? { connection: subject.id }
5215
5355
  : principal),
5216
5356
  ...(passed.length ? { authorization: passed.map((p) => ({ ...p })) } : {}),
5357
+ ...(stamp ? { impersonation: stamp } : {}),
5217
5358
  });
5218
5359
  rt.db
5219
5360
  .prepare(`INSERT INTO _substrat_outbox
5220
5361
  (id, type, schema_version, occurred_at, tenant_id, scope_id, actor,
5221
- entity_type, entity_id, pii_class, subject_id, authorization, payload)
5222
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
5223
- .run(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.payload === undefined ? null : JSON.stringify(full.payload));
5362
+ entity_type, entity_id, pii_class, subject_id, authorization,
5363
+ impersonation, payload)
5364
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
5365
+ .run(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.payload === undefined ? null : JSON.stringify(full.payload));
5224
5366
  },
5225
5367
  requestPlatform: (request) => {
5368
+ assertImpersonationWrites(impersonation, 'ctx.requestPlatform');
5226
5369
  const input = platformRequestInput.parse(request);
5227
5370
  // Backpressure (platform-intents.md): refuse when the scope already holds too many pending
5228
5371
  // intents, so a stuck or runaway vertical cannot flood the platform drain.
@@ -5241,9 +5384,9 @@ export class SqliteScopeHost {
5241
5384
  : principal);
5242
5385
  rt.db
5243
5386
  .prepare(`INSERT INTO _substrat_platform_requests
5244
- (id, kind, payload, requested_by, status, attempts, requested_at)
5245
- VALUES (?, ?, ?, ?, 'pending', 0, ?)`)
5246
- .run(id, input.kind, JSON.stringify(input.payload ?? null), JSON.stringify(requestedBy), at);
5387
+ (id, kind, payload, requested_by, impersonation, status, attempts, requested_at)
5388
+ VALUES (?, ?, ?, ?, ?, 'pending', 0, ?)`)
5389
+ .run(id, input.kind, JSON.stringify(input.payload ?? null), JSON.stringify(requestedBy), stamp ? JSON.stringify(stamp) : null, at);
5247
5390
  if (signals)
5248
5391
  signals.platformRequests += 1;
5249
5392
  return id;
@@ -5327,6 +5470,7 @@ export class SqliteScopeHost {
5327
5470
  * guardrail 2 the same way its `check` does.
5328
5471
  */
5329
5472
  grant: async (principal, permission, entity) => {
5473
+ assertImpersonationWrites(impersonation, 'ctx.grant');
5330
5474
  const held = await runCheck(permission, entity);
5331
5475
  if (!held.allowed) {
5332
5476
  throw new PermissionDenied(`cannot grant '${permission}' on ${entity.entityType}:${entity.entityId} — ` +
@@ -5339,6 +5483,7 @@ export class SqliteScopeHost {
5339
5483
  },
5340
5484
  /** Withdraw a grant this caller could have made. Same guardrails, same reason. */
5341
5485
  revoke: async (principal, permission, entity) => {
5486
+ assertImpersonationWrites(impersonation, 'ctx.revoke');
5342
5487
  const held = await runCheck(permission, entity);
5343
5488
  if (!held.allowed) {
5344
5489
  throw new PermissionDenied(`cannot revoke '${permission}' on ${entity.entityType}:${entity.entityId} — ` +
@@ -5350,6 +5495,7 @@ export class SqliteScopeHost {
5350
5495
  },
5351
5496
  atomic: createAtomic(runSub, { passed, signals }),
5352
5497
  link: (child, parent) => {
5498
+ assertImpersonationWrites(impersonation, 'ctx.link');
5353
5499
  const allowed = relations.get(child.entityType);
5354
5500
  if (!allowed?.has(parent.entityType)) {
5355
5501
  throw new Error(`undeclared entity relation: ${child.entityType} → ${parent.entityType} ` +
@@ -5526,6 +5672,13 @@ export class SqliteScopeHost {
5526
5672
  // already settled reads as "nobody classified this" rather than claiming an origin
5527
5673
  // the drain never actually decided.
5528
5674
  this.ensureColumn(db, '_substrat_platform_requests', 'last_failure', 'last_failure TEXT');
5675
+ // K-42: the two-actor stamp on the three spine tables that record who did what,
5676
+ // for a scope DB created before impersonation existed. Nullable everywhere, and
5677
+ // the null means "nobody was impersonating" rather than "unrecorded" — every one
5678
+ // of those rows was written when there was no other possibility.
5679
+ this.ensureColumn(db, '_substrat_outbox', 'impersonation', 'impersonation TEXT');
5680
+ this.ensureColumn(db, '_substrat_platform_requests', 'impersonation', 'impersonation TEXT');
5681
+ this.ensureColumn(db, '_substrat_denials', 'impersonation', 'impersonation TEXT');
5529
5682
  const appliedMigrations = new Set(db.prepare('SELECT module_id, version FROM _substrat_migrations').all().map((r) => `${r.module_id}@${r.version}`));
5530
5683
  const created = { tenantId, scopeId, db, actor: new ScopeActor(), appliedMigrations };
5531
5684
  this.scopes.set(key, created);