@oimlsmart/platform-server 0.2.1 → 0.2.3

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.
@@ -0,0 +1,24 @@
1
+ -- The 2026-09-06 performance audit's audit-chain read: the OP console's
2
+ -- last-sign-in-per-account read (lastAccountSignIns) string-matched
3
+ -- EVERY auditEvents row's data blob (two data LIKE '%…%' clauses —
4
+ -- O(journal) per call, degrading as the journal grows). The auditEvents
5
+ -- rows are entities rows whose data JSON carries the typed legs
6
+ -- (action, entity_id, timestamp), so the read compiles to json_extract
7
+ -- equality — and this expression index makes it a walk over the
8
+ -- (store, action) slice instead of the journal: only the sign-in rows
9
+ -- are ever visited.
10
+ --
11
+ -- The json_valid GUARD is load-bearing: an unguarded json_extract index
12
+ -- expression RAISES 'malformed JSON' on any corrupt entities row —
13
+ -- the corrupt row would become unwritable, and the unguarded query
14
+ -- would throw where the retired LIKE fold skipped. The CASE guard
15
+ -- lands the corrupt row's legs at NULL (indexed, never matched); the
16
+ -- query spells the identical expression so the planner proves the
17
+ -- index applies. Expand-only per the migration contract; schema.sql's
18
+ -- mirror lands in the same commit.
19
+ CREATE INDEX IF NOT EXISTS idx_entities_store_action ON entities (
20
+ store,
21
+ json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.action'),
22
+ json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.entity_id'),
23
+ json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.timestamp')
24
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oimlsmart/platform-server",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "The OIML SMART platform server kernel: the store seam (ServerStore + the D1 and SQLite implementations), the canonical D1 migration set both deployments apply, the instance profile, the mailer, the RBAC map, the OIDC/OAuth client cones, and the shared role/permission vocabulary. Consumed by the smart monorepo (browser/) and the identity service.",
5
5
  "type": "module",
6
6
  "repository": {
package/src/store/d1.ts CHANGED
@@ -31,6 +31,7 @@
31
31
  import type { D1Database, D1PreparedStatement } from '@cloudflare/workers-types'
32
32
  import {
33
33
  DEMO_PASSWORD,
34
+ EVENTS_BULK_KEY_CHUNK,
34
35
  StoreUnavailable,
35
36
  type AccountEmail,
36
37
  type AddAccountEmailResult,
@@ -42,6 +43,7 @@ import {
42
43
  type EnrollmentToken,
43
44
  type EntityChange,
44
45
  type EntityRow,
46
+ type EventEntityKey,
45
47
  type EventKeyFilter,
46
48
  type FederationPeer,
47
49
  type IdentityApproval,
@@ -1931,22 +1933,30 @@ export class D1ServerStore implements ServerStore {
1931
1933
 
1932
1934
  /** The last OP-side sign-in per account, FROM THE AUDIT CHAIN: the
1933
1935
  * newest auditEvents row whose action is a sign-in
1934
- * ('account.sign_in' / 'upstream_sign_in') per entity_id. */
1936
+ * ('account.sign_in' / 'upstream_sign_in') per entity_id. The typed
1937
+ * read (the 2026-09-06 audit): the legs come out of the data JSON by
1938
+ * json_extract against idx_entities_store_action (migration 0023) —
1939
+ * the sign-in slice is an index walk, never the O(journal) data-LIKE
1940
+ * scan. The json_valid guard spells the index's expression exactly
1941
+ * (a corrupt entities row answers NULL legs, never a raised
1942
+ * 'malformed JSON'). The action match stays exact (the retired
1943
+ * LIKE's closing quote made it so too) — and now spelling-proof:
1944
+ * the legs parse the JSON, so a serialized-with-spaces row answers
1945
+ * and an embedded lookalike substring never does. */
1935
1946
  async lastAccountSignIns(): Promise<Record<string, string>> {
1936
1947
  const res = await this.stmt(
1937
- `SELECT data FROM entities
1948
+ `SELECT json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.entity_id') AS entity_id,
1949
+ MAX(json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.timestamp')) AS ts
1950
+ FROM entities
1938
1951
  WHERE store = 'auditEvents'
1939
- AND (data LIKE '%"action":"account.sign_in"%' OR data LIKE '%"action":"upstream_sign_in"%')`,
1940
- ).all<{ data: string }>()
1952
+ AND json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.action') IN ('account.sign_in', 'upstream_sign_in')
1953
+ GROUP BY json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.entity_id')`,
1954
+ ).all<{ entity_id: string | null; ts: string | null }>()
1941
1955
  const out: Record<string, string> = {}
1942
- for (const { data } of res.results) {
1943
- try {
1944
- const event = JSON.parse(data) as { entity_id?: string; timestamp?: string }
1945
- if (typeof event.entity_id !== 'string' || typeof event.timestamp !== 'string') continue
1946
- if (!out[event.entity_id] || event.timestamp > out[event.entity_id]!) {
1947
- out[event.entity_id] = event.timestamp
1948
- }
1949
- } catch { /* a malformed audit row is skipped, never trusted */ }
1956
+ for (const { entity_id, ts } of res.results) {
1957
+ // A row missing either leg (the malformed audit row) is skipped,
1958
+ // never trusted the same posture the JSON fold held.
1959
+ if (typeof entity_id === 'string' && typeof ts === 'string') out[entity_id] = ts
1950
1960
  }
1951
1961
  return out
1952
1962
  }
@@ -3225,8 +3235,16 @@ export class D1ServerStore implements ServerStore {
3225
3235
  // ── the workflow entity store + change journal ───────────────────
3226
3236
 
3227
3237
  async listEntities(store: string): Promise<EntityRow[]> {
3238
+ // The ORDER BY is the seam's contract (the 0.2.3 pin): the answer
3239
+ // arrives in (org_id, rowid) order — the read's observable order
3240
+ // since migration 0001, when the planner walked
3241
+ // idx_entities_store_org. Migration 0023's expression index offered
3242
+ // a second store-prefixed walk and the unnamed order flipped to
3243
+ // insertion (the smart app's render-baseline red, oimlsmart/smart
3244
+ // PR #264) — a list read's order is a consumer-visible contract,
3245
+ // never the planner's pick.
3228
3246
  const res = await this.stmt(
3229
- 'SELECT store, id, org_id, data, updated_at FROM entities WHERE store = ?', store,
3247
+ 'SELECT store, id, org_id, data, updated_at FROM entities WHERE store = ? ORDER BY org_id, rowid', store,
3230
3248
  ).all<EntityRow>()
3231
3249
  return res.results
3232
3250
  }
@@ -3296,11 +3314,14 @@ export class D1ServerStore implements ServerStore {
3296
3314
  action: string
3297
3315
  payload: string
3298
3316
  }): Promise<PlatformEvent> {
3299
- await this.stmt(
3300
- 'INSERT INTO events (id, domain, entity_id, action, payload) VALUES (?, ?, ?, ?, ?)',
3317
+ // ONE round trip: the RETURNING clause (SQLite ≥ 3.35, D1 included)
3318
+ // answers the stored row seq + the default at off the INSERT
3319
+ // itself; the SELECT-by-id read-back retired (the 2026-09-06 audit:
3320
+ // two round trips per event, halved).
3321
+ const row = await this.stmt(
3322
+ 'INSERT INTO events (id, domain, entity_id, action, payload) VALUES (?, ?, ?, ?, ?) RETURNING *',
3301
3323
  input.id, input.domain, input.entityId, input.action, input.payload,
3302
- ).run()
3303
- const row = await this.stmt('SELECT * FROM events WHERE id = ?', input.id).first<Record<string, unknown>>()
3324
+ ).first<Record<string, unknown>>()
3304
3325
  return D1ServerStore.toPlatformEvent(row!)
3305
3326
  }
3306
3327
 
@@ -3322,8 +3343,28 @@ export class D1ServerStore implements ServerStore {
3322
3343
  }
3323
3344
 
3324
3345
  /** The subscription grammar's SQL resolution: the pinned columns match
3325
- * by equality; the free legs stay out of the WHERE. */
3326
- async eventsMatching(filter: EventKeyFilter, limit = 500): Promise<PlatformEvent[]> {
3346
+ * by equality; the free legs stay out of the WHERE. The BULK form
3347
+ * (the 2026-09-06 audit's notify-inbox seam) resolves every pinned
3348
+ * (domain, entityId) pair in ONE statement per EVENTS_BULK_KEY_CHUNK
3349
+ * keys — the OR-of-ANDs WHERE walks idx_events_domain_entity per term
3350
+ * (never a compound SELECT: the D1 5-term cap rule) — and answers the
3351
+ * MERGED set, seq-ordered, limit-truncated after the merge. */
3352
+ async eventsMatching(filter: EventKeyFilter | { keys: readonly EventEntityKey[] }, limit = 500): Promise<PlatformEvent[]> {
3353
+ if ('keys' in filter) {
3354
+ if (filter.keys.length === 0) return []
3355
+ const merged: PlatformEvent[] = []
3356
+ for (let i = 0; i < filter.keys.length; i += EVENTS_BULK_KEY_CHUNK) {
3357
+ const chunk = filter.keys.slice(i, i + EVENTS_BULK_KEY_CHUNK)
3358
+ const where = chunk.map(() => '(domain = ? AND entity_id = ?)').join(' OR ')
3359
+ const args = chunk.flatMap(k => [k.domain, k.entityId])
3360
+ const res = await this.stmt(
3361
+ `SELECT * FROM events WHERE ${where} ORDER BY seq LIMIT ?`, ...args, limit,
3362
+ ).all<Record<string, unknown>>()
3363
+ merged.push(...res.results.map(D1ServerStore.toPlatformEvent))
3364
+ }
3365
+ merged.sort((a, b) => a.seq - b.seq)
3366
+ return merged.slice(0, limit)
3367
+ }
3327
3368
  const where: string[] = []
3328
3369
  const args: unknown[] = []
3329
3370
  if (filter.domain !== undefined) { where.push('domain = ?'); args.push(filter.domain) }
@@ -21,8 +21,13 @@ export { ORG_FIELDS, CATALOG_STORES, orgIdOf } from '../../store'
21
21
  import type { EntityRow, EntityChange } from '../../store'
22
22
 
23
23
  export function listEntities(store: string): EntityRow[] {
24
+ // The ORDER BY is the seam's contract (the 0.2.3 pin, the D1 half's
25
+ // twin): (org_id, rowid) — the read's observable order since
26
+ // migration 0001's idx_entities_store_org walk, made planner-proof
27
+ // when migration 0023's expression index offered a second
28
+ // store-prefixed plan.
24
29
  return getDb()
25
- .prepare('SELECT store, id, org_id, data, updated_at FROM entities WHERE store = ?')
30
+ .prepare('SELECT store, id, org_id, data, updated_at FROM entities WHERE store = ? ORDER BY org_id, rowid')
26
31
  .all(store) as EntityRow[]
27
32
  }
28
33
 
@@ -9,7 +9,7 @@
9
9
  // ═══════════════════════════════════════════════════════════════════
10
10
 
11
11
  import { getDb } from './store'
12
- import type { EventKeyFilter, PlatformEvent } from '../../store'
12
+ import { EVENTS_BULK_KEY_CHUNK, type EventEntityKey, type EventKeyFilter, type PlatformEvent } from '../../store'
13
13
 
14
14
  interface EventRow {
15
15
  seq: number
@@ -40,12 +40,12 @@ export function appendEvent(input: {
40
40
  action: string
41
41
  payload: string
42
42
  }): PlatformEvent {
43
- const db = getDb()
44
- db.prepare(
45
- `INSERT INTO events (id, domain, entity_id, action, payload) VALUES (?, ?, ?, ?, ?)`,
46
- ).run(input.id, input.domain, input.entityId, input.action, input.payload)
43
+ // ONE statement: RETURNING answers the stored row (seq + the default
44
+ // at) off the INSERT itself — the same halving as the D1 half.
47
45
  return toPlatformEvent(
48
- db.prepare('SELECT * FROM events WHERE id = ?').get(input.id) as EventRow,
46
+ getDb().prepare(
47
+ `INSERT INTO events (id, domain, entity_id, action, payload) VALUES (?, ?, ?, ?, ?) RETURNING *`,
48
+ ).get(input.id, input.domain, input.entityId, input.action, input.payload) as EventRow,
49
49
  )
50
50
  }
51
51
 
@@ -69,8 +69,28 @@ export function getEvent(id: string): PlatformEvent | null {
69
69
  }
70
70
 
71
71
  /** The subscription grammar's SQL resolution: the pinned columns match
72
- * by equality; the free legs stay out of the WHERE. */
73
- export function eventsMatching(filter: EventKeyFilter, limit = 500): PlatformEvent[] {
72
+ * by equality; the free legs stay out of the WHERE. The BULK form (the
73
+ * 2026-09-06 audit's notify-inbox seam) resolves every pinned (domain,
74
+ * entityId) pair in ONE statement per EVENTS_BULK_KEY_CHUNK keys — the
75
+ * same chunking as the D1 half, so the backends stay answer-identical —
76
+ * and answers the MERGED set, seq-ordered, limit-truncated after the
77
+ * merge. */
78
+ export function eventsMatching(filter: EventKeyFilter | { keys: readonly EventEntityKey[] }, limit = 500): PlatformEvent[] {
79
+ if ('keys' in filter) {
80
+ if (filter.keys.length === 0) return []
81
+ const merged: PlatformEvent[] = []
82
+ for (let i = 0; i < filter.keys.length; i += EVENTS_BULK_KEY_CHUNK) {
83
+ const chunk = filter.keys.slice(i, i + EVENTS_BULK_KEY_CHUNK)
84
+ const where = chunk.map(() => '(domain = ? AND entity_id = ?)').join(' OR ')
85
+ const args = chunk.flatMap(k => [k.domain, k.entityId])
86
+ const rows = getDb()
87
+ .prepare(`SELECT * FROM events WHERE ${where} ORDER BY seq LIMIT ?`)
88
+ .all(...args, limit) as EventRow[]
89
+ merged.push(...rows.map(toPlatformEvent))
90
+ }
91
+ merged.sort((a, b) => a.seq - b.seq)
92
+ return merged.slice(0, limit)
93
+ }
74
94
  const where: string[] = []
75
95
  const args: unknown[] = []
76
96
  if (filter.domain !== undefined) { where.push('domain = ?'); args.push(filter.domain) }
@@ -406,22 +406,27 @@ export function eraseOpAccount(userId: string): {
406
406
  /** The last OP-side sign-in per account, FROM THE AUDIT CHAIN: the
407
407
  * newest auditEvents row whose action is a sign-in ('account.sign_in'
408
408
  * — the password login; 'upstream_sign_in' — a linked-provider
409
- * sign-in) per entity_id (the account id). */
409
+ * sign-in) per entity_id (the account id). The typed read (the
410
+ * 2026-09-06 audit): json_extract against idx_entities_store_action —
411
+ * an index walk over the sign-in slice, never the O(journal)
412
+ * data-LIKE scan; the json_valid guard spells the index's expression
413
+ * exactly (a corrupt entities row answers NULL legs, never a raised
414
+ * 'malformed JSON'). The action match stays exact (the retired LIKE's
415
+ * closing quote made it so too) — and now spelling-proof: the legs
416
+ * parse the JSON, so a serialized-with-spaces row answers and an
417
+ * embedded lookalike substring never does. */
410
418
  export function lastAccountSignIns(): Record<string, string> {
411
419
  const rows = getDb().prepare(
412
- `SELECT data FROM entities
420
+ `SELECT json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.entity_id') AS entity_id,
421
+ MAX(json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.timestamp')) AS ts
422
+ FROM entities
413
423
  WHERE store = 'auditEvents'
414
- AND (data LIKE '%"action":"account.sign_in"%' OR data LIKE '%"action":"upstream_sign_in"%')`,
415
- ).all() as Array<{ data: string }>
424
+ AND json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.action') IN ('account.sign_in', 'upstream_sign_in')
425
+ GROUP BY json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.entity_id')`,
426
+ ).all() as Array<{ entity_id: string | null; ts: string | null }>
416
427
  const out: Record<string, string> = {}
417
- for (const { data } of rows) {
418
- try {
419
- const event = JSON.parse(data) as { entity_id?: string; timestamp?: string }
420
- if (typeof event.entity_id !== 'string' || typeof event.timestamp !== 'string') continue
421
- if (!out[event.entity_id] || event.timestamp > out[event.entity_id]!) {
422
- out[event.entity_id] = event.timestamp
423
- }
424
- } catch { /* a malformed audit row is skipped, never trusted */ }
428
+ for (const { entity_id, ts } of rows) {
429
+ if (typeof entity_id === 'string' && typeof ts === 'string') out[entity_id] = ts
425
430
  }
426
431
  return out
427
432
  }
@@ -110,6 +110,18 @@ CREATE TABLE IF NOT EXISTS entities (
110
110
  PRIMARY KEY (store, id)
111
111
  );
112
112
  CREATE INDEX IF NOT EXISTS idx_entities_store_org ON entities (store, org_id);
113
+ -- The audit chain's typed legs (the 2026-09-06 audit's sign-in recency
114
+ -- read — migration 0023): (store, action, entity_id, timestamp) out of
115
+ -- the data JSON, so the per-account last-sign-in read walks the
116
+ -- sign-in slice instead of string-matching the journal. The json_valid
117
+ -- guard keeps a corrupt entities row writable (an unguarded
118
+ -- json_extract index expression would raise on the INSERT).
119
+ CREATE INDEX IF NOT EXISTS idx_entities_store_action ON entities (
120
+ store,
121
+ json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.action'),
122
+ json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.entity_id'),
123
+ json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.timestamp')
124
+ );
113
125
 
114
126
  -- The change journal: every write appends (seq, store, type, id) —
115
127
  -- the SSE stream tails it (each client filters its stores).
@@ -179,7 +179,7 @@ import {
179
179
  updateOpAccount,
180
180
  updateUserName,
181
181
  } from './sqlite/op-accounts-store'
182
- import { installStore, type AccountEmail, type AddAccountEmailResult, type AuthUserPayload, type CertificateHolderClaim, type CertificateHolderOrg, type CompleteEmailChangeResult, type CompleteEnrollmentResult, type EmailChangeToken, type EnrollmentToken, type EntityChange, type EntityRow, type EventKeyFilter, type FederationPeer, type IdentityApproval, type IdentityLink, type IdentityProvider, type InstrumentRegistration, type InstrumentRegistrationLifecycle, type InstrumentRegistrationScopeStatus, type NotifyDelivery, type NotifyDeliveryStatus, type NotifyEntityMute, type NotifyInboxState, type NotifyPreferences, type NotifyRule, type OAuthInitialAssignment, type OidcAccessToken, type OidcAuthorization, type OidcClient, type OidcClientLaunch, type OidcCode, type OidcConsentGrant, type OidcKeyRow, type OpAccountErasure, type OpClientRoleAssignment, type OpLiveSession, type OrgJoinRequest, type OrgMembership, type OrgMembershipState, type OrgRegistryContact, type OrgRegistryOrg, type OrgRegistryState, type PersonalAccessToken, type PlatformEvent, type ServerStore, type SessionView, type SsoSignInState, type UserAdminRow, type AdvanceCounterResult, type MfaPending, type RecoveryCodeState, type TotpSecret, type WebauthnChallenge, type WebauthnCredential } from '../store'
182
+ import { installStore, type AccountEmail, type AddAccountEmailResult, type AuthUserPayload, type CertificateHolderClaim, type CertificateHolderOrg, type CompleteEmailChangeResult, type CompleteEnrollmentResult, type EmailChangeToken, type EnrollmentToken, type EntityChange, type EntityRow, type EventEntityKey, type EventKeyFilter, type FederationPeer, type IdentityApproval, type IdentityLink, type IdentityProvider, type InstrumentRegistration, type InstrumentRegistrationLifecycle, type InstrumentRegistrationScopeStatus, type NotifyDelivery, type NotifyDeliveryStatus, type NotifyEntityMute, type NotifyInboxState, type NotifyPreferences, type NotifyRule, type OAuthInitialAssignment, type OidcAccessToken, type OidcAuthorization, type OidcClient, type OidcClientLaunch, type OidcCode, type OidcConsentGrant, type OidcKeyRow, type OpAccountErasure, type OpClientRoleAssignment, type OpLiveSession, type OrgJoinRequest, type OrgMembership, type OrgMembershipState, type OrgRegistryContact, type OrgRegistryOrg, type OrgRegistryState, type PersonalAccessToken, type PlatformEvent, type ServerStore, type SessionView, type SsoSignInState, type UserAdminRow, type AdvanceCounterResult, type MfaPending, type RecoveryCodeState, type TotpSecret, type WebauthnChallenge, type WebauthnCredential } from '../store'
183
183
  import {
184
184
  advanceWebauthnCounter,
185
185
  consumeMfaPending,
@@ -1008,7 +1008,7 @@ export function createSqliteServerStore(): ServerStore {
1008
1008
  async getEvent(id: string): Promise<PlatformEvent | null> {
1009
1009
  return getEvent(id)
1010
1010
  },
1011
- async eventsMatching(filter: EventKeyFilter, limit = 500): Promise<PlatformEvent[]> {
1011
+ async eventsMatching(filter: EventKeyFilter | { keys: readonly EventEntityKey[] }, limit = 500): Promise<PlatformEvent[]> {
1012
1012
  return eventsMatching(filter, limit)
1013
1013
  },
1014
1014
 
package/src/store.ts CHANGED
@@ -200,6 +200,22 @@ export interface EventKeyFilter {
200
200
  action?: string
201
201
  }
202
202
 
203
+ /** One pinned (domain, entityId) pair of the bulk history read — the
204
+ * participant-history shape (an entity's own event journal), never a
205
+ * partial pattern: both legs are pinned, so every key resolves against
206
+ * idx_events_domain_entity. */
207
+ export interface EventEntityKey {
208
+ domain: string
209
+ entityId: string
210
+ }
211
+
212
+ /** The bulk read's statement chunk: D1 caps a statement at 100 bound
213
+ * parameters (two per key + the limit), so a bulk call issues
214
+ * ceil(keys / 49) statements — ONE for any realistic inbox window —
215
+ * never one per key. The SQLite half chunks identically so the two
216
+ * backends stay answer-identical. */
217
+ export const EVENTS_BULK_KEY_CHUNK = 49
218
+
203
219
  // ── the notification subscriptions store (TODO.notify/02) ────────────
204
220
 
205
221
  /** The rule row's mode: 'subscribe' adds the user to the candidates of
@@ -1741,7 +1757,13 @@ export interface ServerStore {
1741
1757
  * the latest auditEvents row whose action is a sign-in
1742
1758
  * ('account.sign_in' — the password login; 'upstream_sign_in' — a
1743
1759
  * linked-provider sign-in) per entity_id. Answers userId → ISO
1744
- * timestamp; accounts that never signed in are absent. */
1760
+ * timestamp; accounts that never signed in are absent. The typed
1761
+ * read: json_extract legs (json_valid-guarded, the index's exact
1762
+ * spelling) against idx_entities_store_action (migration 0023) — an
1763
+ * index walk over the sign-in slice, never a data-LIKE scan of the
1764
+ * journal. The action match stays exact (the retired LIKE's closing
1765
+ * quote made it so too) and becomes spelling-proof: the legs parse
1766
+ * the JSON. */
1745
1767
  lastAccountSignIns(): Promise<Record<string, string>>
1746
1768
 
1747
1769
  // ── the account console (TODO.identity/06) ──
@@ -2199,17 +2221,39 @@ export interface ServerStore {
2199
2221
  ): Promise<InstrumentRegistration | null>
2200
2222
 
2201
2223
  // ── the workflow entity store + change journal ──
2224
+ /** The store's rows, in the seam's declared order: (org_id, rowid) —
2225
+ * the read's observable order since migration 0001 (NULL org ids
2226
+ * first, then by org id, then by insertion). Both backends spell the
2227
+ * ORDER BY explicitly — a list read's order is a consumer-visible
2228
+ * contract, never the planner's pick (the 0.2.3 pin, after
2229
+ * migration 0023's expression index flipped the unnamed walk). */
2202
2230
  listEntities(store: string): Promise<EntityRow[]>
2203
2231
  getEntity(store: string, id: string): Promise<EntityRow | undefined>
2204
2232
  putEntity(store: string, id: string, orgId: string | null, data: string): Promise<void>
2205
2233
  deleteEntity(store: string, id: string): Promise<boolean>
2206
2234
  changesAfter(seq: number, limit?: number): Promise<EntityChange[]>
2235
+ /** The GLOBAL journal high-water: the bootstrap snapshot's ETag leg
2236
+ * (any write anywhere bumps the one seq — conservative, never
2237
+ * stale).
2238
+ *
2239
+ * NAMED FOLLOW-UP, DOC ONLY (the smart side references it from
2240
+ * server/routes/bootstrap.ts: the per-store `seqs` snapshot carries
2241
+ * the journal position a delta-loading client would resume from):
2242
+ * the PER-STORE high-water — `latestChangeSeqFor(store)` — answering
2243
+ * MAX(seq) over one store's slice of the SAME journal. The contract
2244
+ * when it lands: the seq stays global and monotone (the per-store
2245
+ * read is a PROJECTION of the one journal, never a second
2246
+ * sequence); it exists so a per-store conditional revalidation /
2247
+ * SSE resume probe costs one indexed read instead of a payload
2248
+ * fetch; and its query wants the (store, seq) walk — the index
2249
+ * rides the same commit, expand-only per the migration contract. */
2207
2250
  latestChangeSeq(): Promise<number>
2208
2251
 
2209
2252
  // ── the platform event store (TODO.notify/01) ──
2210
2253
  /** Append one declared event (the emitter's write; one row inside the
2211
2254
  * acting request's envelope). Answers the stored row (seq + at read
2212
- * back). */
2255
+ * back) off the INSERT's own RETURNING — ONE round trip, never the
2256
+ * INSERT + SELECT-by-id pair. */
2213
2257
  appendEvent(input: {
2214
2258
  id: string
2215
2259
  domain: string
@@ -2229,6 +2273,20 @@ export interface ServerStore {
2229
2273
  * pins, equality-matched (`WHERE domain = ? AND entity_id = ?` — the
2230
2274
  * column split's whole point, never a string scan on a composed key). */
2231
2275
  eventsMatching(filter: EventKeyFilter, limit?: number): Promise<PlatformEvent[]>
2276
+ /** The BULK history read (the 2026-09-06 performance audit's
2277
+ * notify-inbox seam): the participant-history class awaits the
2278
+ * single-key form once per DISTINCT entity — ~100 D1 round trips for
2279
+ * a 500-event window. The bulk form resolves every pinned (domain,
2280
+ * entityId) pair in ONE statement per chunk of EVENTS_BULK_KEY_CHUNK
2281
+ * (the OR-of-ANDs WHERE walks idx_events_domain_entity per term;
2282
+ * never a compound SELECT — the D1 5-term cap rule).
2283
+ *
2284
+ * The ordering contract: the answer is the MERGED matched set,
2285
+ * seq-ordered (never grouped per key); `limit` truncates the merged
2286
+ * set exactly as the single-key form truncates its own; duplicate
2287
+ * keys match their rows once (the OR is a set union, not a concat);
2288
+ * an empty keys list answers [] without issuing a statement. */
2289
+ eventsMatching(filter: { keys: readonly EventEntityKey[] }, limit?: number): Promise<PlatformEvent[]>
2232
2290
 
2233
2291
  // ── the notification subscriptions store (TODO.notify/02) ──
2234
2292
  /** The user's own rule rows (both modes) — the settings page's list. */