@oimlsmart/platform-server 0.2.13 → 0.2.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oimlsmart/platform-server",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
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
@@ -47,6 +47,7 @@ import {
47
47
  type EmailChangeToken,
48
48
  type EnrollmentToken,
49
49
  type EntityChange,
50
+ type EntityListOptions,
50
51
  type EntityRow,
51
52
  type EntityWriteInput,
52
53
  type EventEntityKey,
@@ -3542,7 +3543,7 @@ export class D1ServerStore implements ServerStore {
3542
3543
 
3543
3544
  // ── the workflow entity store + change journal ───────────────────
3544
3545
 
3545
- async listEntities(store: string): Promise<EntityRow[]> {
3546
+ async listEntities(store: string, options?: EntityListOptions): Promise<EntityRow[]> {
3546
3547
  // The ORDER BY is the seam's contract (the 0.2.3 pin): the answer
3547
3548
  // arrives in (org_id, rowid) order — the read's observable order
3548
3549
  // since migration 0001, when the planner walked
@@ -3551,6 +3552,19 @@ export class D1ServerStore implements ServerStore {
3551
3552
  // insertion (the smart app's render-baseline red, oimlsmart/smart
3552
3553
  // PR #264) — a list read's order is a consumer-visible contract,
3553
3554
  // never the planner's pick.
3555
+ // options.orgId narrows the candidate set (the seam's EntityListOptions
3556
+ // — the portal-load audit's R3-fix3): the kept groups (NULL stamps,
3557
+ // then the named org) are the two lowest org_id buckets, so the
3558
+ // filtered ORDER BY is the unfiltered order's restriction to the
3559
+ // candidates — a gate-driven projection of either answer is
3560
+ // byte-identical.
3561
+ if (options?.orgId) {
3562
+ const res = await this.stmt(
3563
+ 'SELECT store, id, org_id, data, updated_at FROM entities WHERE store = ? AND (org_id = ? OR org_id IS NULL) ORDER BY org_id, rowid',
3564
+ store, options.orgId,
3565
+ ).all<EntityRow>()
3566
+ return res.results
3567
+ }
3554
3568
  const res = await this.stmt(
3555
3569
  'SELECT store, id, org_id, data, updated_at FROM entities WHERE store = ? ORDER BY org_id, rowid', store,
3556
3570
  ).all<EntityRow>()
@@ -18,7 +18,7 @@ import { PUT_ENTITIES_CHUNK, orgIdOf } from '../../store'
18
18
  export type { EntityRow, EntityChange } from '../../store'
19
19
  export { ORG_FIELDS, CATALOG_STORES, orgIdOf } from '../../store'
20
20
 
21
- import type { EntityRow, EntityChange, EntityWriteInput, JournalAppend } from '../../store'
21
+ import type { EntityRow, EntityChange, EntityListOptions, EntityWriteInput, JournalAppend } from '../../store'
22
22
 
23
23
  /** The journal fan-out's ISOLATE-scope registry (the seam's
24
24
  * onJournalAppend, the D1 half's twin): one module-scope set — a
@@ -69,12 +69,23 @@ const CERTIFICATE_NUMBER_SQL = `SELECT store, id, org_id, data, updated_at
69
69
  AND json_extract(CASE WHEN json_valid(data) THEN data ELSE '{}' END, '$.certificate_number') COLLATE NOCASE = ?
70
70
  ORDER BY org_id, rowid`
71
71
 
72
- export function listEntities(store: string): EntityRow[] {
72
+ export function listEntities(store: string, options?: EntityListOptions): EntityRow[] {
73
73
  // The ORDER BY is the seam's contract (the 0.2.3 pin, the D1 half's
74
74
  // twin): (org_id, rowid) — the read's observable order since
75
75
  // migration 0001's idx_entities_store_org walk, made planner-proof
76
76
  // when migration 0023's expression index offered a second
77
77
  // store-prefixed plan.
78
+ // options.orgId narrows the candidate set (the seam's EntityListOptions
79
+ // — the portal-load audit's R3-fix3): the kept groups (NULL stamps,
80
+ // then the named org) are the two lowest org_id buckets, so the
81
+ // filtered ORDER BY is the unfiltered order's restriction to the
82
+ // candidates — a gate-driven projection of either answer is
83
+ // byte-identical.
84
+ if (options?.orgId) {
85
+ return getDb()
86
+ .prepare('SELECT store, id, org_id, data, updated_at FROM entities WHERE store = ? AND (org_id = ? OR org_id IS NULL) ORDER BY org_id, rowid')
87
+ .all(store, options.orgId) as EntityRow[]
88
+ }
78
89
  return getDb()
79
90
  .prepare('SELECT store, id, org_id, data, updated_at FROM entities WHERE store = ? ORDER BY org_id, rowid')
80
91
  .all(store) as EntityRow[]
@@ -194,7 +194,7 @@ import {
194
194
  updateOpAccount,
195
195
  updateUserName,
196
196
  } from './sqlite/op-accounts-store'
197
- import { installStore, type AccountEmail, type AddAccountEmailResult, type AuthUserPayload, type CertificateHolderClaim, type CertificateHolderOrg, type CompleteEmailChangeResult, type CompleteEnrollmentResult, type ConsumeOidcRefreshTokenResult, type EmailChangeToken, type EnrollmentToken, type EntityChange, type EntityRow, type EntityWriteInput, type EventEntityKey, type EventKeyFilter, type EventWriteInput, type FederationPeer, type IdentityApproval, type IdentityLink, type IdentityProvider, type InstrumentRegistration, type InstrumentRegistrationLifecycle, type InstrumentRegistrationScopeStatus, type InstrumentRegistrationWriteInput, type JournalAppend, 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 OidcRefreshToken, 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'
197
+ import { installStore, type AccountEmail, type AddAccountEmailResult, type AuthUserPayload, type CertificateHolderClaim, type CertificateHolderOrg, type CompleteEmailChangeResult, type CompleteEnrollmentResult, type ConsumeOidcRefreshTokenResult, type EmailChangeToken, type EnrollmentToken, type EntityChange, type EntityListOptions, type EntityRow, type EntityWriteInput, type EventEntityKey, type EventKeyFilter, type EventWriteInput, type FederationPeer, type IdentityApproval, type IdentityLink, type IdentityProvider, type InstrumentRegistration, type InstrumentRegistrationLifecycle, type InstrumentRegistrationScopeStatus, type InstrumentRegistrationWriteInput, type JournalAppend, 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 OidcRefreshToken, 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'
198
198
  import {
199
199
  advanceWebauthnCounter,
200
200
  consumeMfaPending,
@@ -1015,8 +1015,8 @@ export function createSqliteServerStore(): ServerStore {
1015
1015
  },
1016
1016
 
1017
1017
  // ── the workflow entity store + change journal ──
1018
- async listEntities(store: string): Promise<EntityRow[]> {
1019
- return listEntities(store)
1018
+ async listEntities(store: string, options?: EntityListOptions): Promise<EntityRow[]> {
1019
+ return listEntities(store, options)
1020
1020
  },
1021
1021
  async getEntity(store: string, id: string): Promise<EntityRow | undefined> {
1022
1022
  return getEntity(store, id)
package/src/store.ts CHANGED
@@ -138,6 +138,29 @@ export interface EntityRow {
138
138
  updated_at: string
139
139
  }
140
140
 
141
+ /** listEntities' candidate narrowing (the 2026-09-01 portal-load audit,
142
+ * R3-fix3): the entities table carries an indexed org_id column
143
+ * (idx_entities_store_org, migration 0001) that reads never used —
144
+ * every list scanned the whole store and left the org cone to the
145
+ * consumer's per-row gate. */
146
+ export interface EntityListOptions {
147
+ /** Narrow the CANDIDATE set to the rows stamped under this org (org_id
148
+ * = the value — orgIdOf's first-org-field stamp, or the writer's own
149
+ * org on the stamping write paths) PLUS the unstamped rows (org_id IS
150
+ * NULL — a stamp-less row is never excluded: the stamp's absence says
151
+ * nothing about the row's cone). A candidate filter, NEVER a
152
+ * visibility decision: which rows of the narrowed set answer is the
153
+ * consumer's in-memory gate, authoritative as before. The consumer
154
+ * enables the filter only where its gate provably denies every row
155
+ * the filter drops (the per-store verification rides the consumer's
156
+ * change — org_id stamps the FIRST org field, so multi-org-field
157
+ * stores are never narrowed). The answer keeps the seam's declared
158
+ * order: (org_id, rowid) over the kept rows — the unfiltered order's
159
+ * restriction to the candidate set, so a gate-driven projection of
160
+ * either answer is byte-identical. */
161
+ orgId?: string
162
+ }
163
+
141
164
  // ── federation peers (TODO.federation/04) ────────────────────────────
142
165
 
143
166
  /** A pinned federation peer: the counterparty instance's descriptor,
@@ -2485,8 +2508,11 @@ export interface ServerStore {
2485
2508
  * first, then by org id, then by insertion). Both backends spell the
2486
2509
  * ORDER BY explicitly — a list read's order is a consumer-visible
2487
2510
  * contract, never the planner's pick (the 0.2.3 pin, after
2488
- * migration 0023's expression index flipped the unnamed walk). */
2489
- listEntities(store: string): Promise<EntityRow[]>
2511
+ * migration 0023's expression index flipped the unnamed walk).
2512
+ * `options.orgId` narrows the candidate set in SQL (EntityListOptions
2513
+ * — the portal-load audit's R3-fix3); the narrowed answer is the same
2514
+ * order's restriction to the candidates. */
2515
+ listEntities(store: string, options?: EntityListOptions): Promise<EntityRow[]>
2490
2516
  getEntity(store: string, id: string): Promise<EntityRow | undefined>
2491
2517
  /** The public register's certificate-number lookup (the verify
2492
2518
  * surfaces' keyed read — the 2026-09-07 performance audit's
@@ -420,6 +420,12 @@ export const CREATION_PERMISSIONS: Record<string, readonly ActionPermission[]> =
420
420
  // document list is maintained by the certificate's issuers.
421
421
  operatedSchemes: ['instance.settings'],
422
422
  certificateDocumentLists: ['certificate.issue'],
423
+ // Registering a participant is the scheme operator's named act (the
424
+ // 2026-09-15 direct-registration posture: participation cases are
425
+ // CREATED active by the Secretariat — the B 18:2025 §11 RC/MC ballot
426
+ // pipeline is modelled, not operated). The organ split holds: the MC
427
+ // decides (suspension/reinstatement), the Secretariat administers.
428
+ participantApplications: ['participants.manage'],
423
429
  }
424
430
 
425
431
  /** FIELD guards: a field going unset → set is the act (the test