@oimlsmart/platform-server 0.1.3 → 0.1.5
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/migrations/0019_org_member_category.sql +34 -0
- package/package.json +1 -1
- package/src/store/d1.ts +26 -3
- package/src/store/sqlite/schema.sql +12 -1
- package/src/store/sqlite/store.ts +35 -3
- package/src/store.ts +29 -2
- package/src/vocab/permissions.ts +32 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
-- Migration 0019 — the OIML Member category (TODO.identity-features/10,
|
|
2
|
+
-- the taxonomy correction): the org_registry gains the two designation
|
|
3
|
+
-- LINK columns and the CS status facet.
|
|
4
|
+
--
|
|
5
|
+
-- The corrected model: the Utilizer/Associate are DESIGNATED BODIES
|
|
6
|
+
-- (their own organization rows, signing the Declaration per PD-08),
|
|
7
|
+
-- never statuses on a member; "OIML Member" is the category with the
|
|
8
|
+
-- member-state / corresponding-member kinds. The links:
|
|
9
|
+
--
|
|
10
|
+
-- designated_by the designating body: a Utilizer's is its MEMBER
|
|
11
|
+
-- STATE, an Associate's its CORRESPONDING MEMBER, a
|
|
12
|
+
-- Test Laboratory's its associated ISSUING AUTHORITY
|
|
13
|
+
-- (the participants model's designated_by:
|
|
14
|
+
-- issuing_authority);
|
|
15
|
+
-- proposed_by an Issuing Authority's proposing MEMBER STATE (a
|
|
16
|
+
-- member state participates in the OIML-CS by
|
|
17
|
+
-- PROPOSING an IA and DESIGNATING a Utilizer);
|
|
18
|
+
-- cs_status the designated bodies' Declaration standing
|
|
19
|
+
-- (signed-active / suspended / withdrawn — the CS
|
|
20
|
+
-- layer's fact projected onto the identity plane).
|
|
21
|
+
--
|
|
22
|
+
-- Expand-only, and deliberately NULL-defaulted: the kind enforcement of
|
|
23
|
+
-- the links (which kind may point at which) is the PROGRAM's write path
|
|
24
|
+
-- (the store keeps the kind column opaque by doctrine), and a legacy
|
|
25
|
+
-- row — a utilizer/associate curated before this migration — keeps its
|
|
26
|
+
-- home untouched: the links read NULL ("not recorded"), never a
|
|
27
|
+
-- destructive move. The journals' discipline holds.
|
|
28
|
+
-- schema.sql carries the same end state for fresh databases —
|
|
29
|
+
-- test/migrations.test.ts pins the UNION of every migration to
|
|
30
|
+
-- schema.sql's CREATE set.
|
|
31
|
+
|
|
32
|
+
ALTER TABLE org_registry ADD COLUMN designated_by TEXT;
|
|
33
|
+
ALTER TABLE org_registry ADD COLUMN proposed_by TEXT;
|
|
34
|
+
ALTER TABLE org_registry ADD COLUMN cs_status TEXT;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oimlsmart/platform-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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
|
@@ -323,6 +323,16 @@ export class D1ServerStore implements ServerStore {
|
|
|
323
323
|
)`,
|
|
324
324
|
).run()
|
|
325
325
|
await this.db.prepare('CREATE INDEX IF NOT EXISTS idx_org_registry_state ON org_registry (state)').run()
|
|
326
|
+
// TODO.identity-features/10 (the OIML Member category): the
|
|
327
|
+
// designation links + the CS status facet arrive with migration
|
|
328
|
+
// 0019 — a dev D1 predating it grows the columns here (the
|
|
329
|
+
// PRAGMA probe + ALTER posture of ensureUserColumns). NULL = not
|
|
330
|
+
// recorded: existing rows keep their posture silently.
|
|
331
|
+
const cols = await this.db.prepare('PRAGMA table_info(org_registry)').all<{ name: string }>()
|
|
332
|
+
const names = new Set(cols.results.map(c => c.name))
|
|
333
|
+
if (!names.has('designated_by')) await this.db.prepare('ALTER TABLE org_registry ADD COLUMN designated_by TEXT').run()
|
|
334
|
+
if (!names.has('proposed_by')) await this.db.prepare('ALTER TABLE org_registry ADD COLUMN proposed_by TEXT').run()
|
|
335
|
+
if (!names.has('cs_status')) await this.db.prepare('ALTER TABLE org_registry ADD COLUMN cs_status TEXT').run()
|
|
326
336
|
})()
|
|
327
337
|
}
|
|
328
338
|
return this.orgRegistrySupportEnsured
|
|
@@ -2188,6 +2198,9 @@ export class D1ServerStore implements ServerStore {
|
|
|
2188
2198
|
country: (row.country as string | null) ?? null,
|
|
2189
2199
|
contacts,
|
|
2190
2200
|
participantRef: (row.participant_ref as string | null) ?? null,
|
|
2201
|
+
designatedBy: (row.designated_by as string | null) ?? null,
|
|
2202
|
+
proposedBy: (row.proposed_by as string | null) ?? null,
|
|
2203
|
+
csStatus: (row.cs_status as string | null) ?? null,
|
|
2191
2204
|
state: row.state as OrgRegistryState,
|
|
2192
2205
|
createdAt: row.created_at as string,
|
|
2193
2206
|
createdBy: (row.created_by as string | null) ?? null,
|
|
@@ -2219,14 +2232,18 @@ export class D1ServerStore implements ServerStore {
|
|
|
2219
2232
|
country?: string | null
|
|
2220
2233
|
contacts?: OrgRegistryContact[]
|
|
2221
2234
|
participantRef?: string | null
|
|
2235
|
+
designatedBy?: string | null
|
|
2236
|
+
proposedBy?: string | null
|
|
2237
|
+
csStatus?: string | null
|
|
2222
2238
|
createdBy?: string | null
|
|
2223
2239
|
}): Promise<OrgRegistryOrg | null> {
|
|
2224
2240
|
await this.ensureOrgRegistrySupport()
|
|
2225
2241
|
const res = await this.stmt(
|
|
2226
|
-
`INSERT OR IGNORE INTO org_registry (id, name, short_name, kind, country, contacts, participant_ref, created_by)
|
|
2227
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2242
|
+
`INSERT OR IGNORE INTO org_registry (id, name, short_name, kind, country, contacts, participant_ref, designated_by, proposed_by, cs_status, created_by)
|
|
2243
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2228
2244
|
input.id, input.name, input.shortName ?? null, input.kind ?? null, input.country ?? null,
|
|
2229
|
-
JSON.stringify(input.contacts ?? []), input.participantRef ?? null,
|
|
2245
|
+
JSON.stringify(input.contacts ?? []), input.participantRef ?? null,
|
|
2246
|
+
input.designatedBy ?? null, input.proposedBy ?? null, input.csStatus ?? null, input.createdBy ?? null,
|
|
2230
2247
|
).run()
|
|
2231
2248
|
if ((res.meta.changes ?? 0) === 0) return null
|
|
2232
2249
|
return this.getOrgRegistryOrg(input.id)
|
|
@@ -2242,6 +2259,9 @@ export class D1ServerStore implements ServerStore {
|
|
|
2242
2259
|
country?: string | null
|
|
2243
2260
|
contacts?: OrgRegistryContact[]
|
|
2244
2261
|
participantRef?: string | null
|
|
2262
|
+
designatedBy?: string | null
|
|
2263
|
+
proposedBy?: string | null
|
|
2264
|
+
csStatus?: string | null
|
|
2245
2265
|
},
|
|
2246
2266
|
actor?: string | null,
|
|
2247
2267
|
): Promise<OrgRegistryOrg | null> {
|
|
@@ -2254,6 +2274,9 @@ export class D1ServerStore implements ServerStore {
|
|
|
2254
2274
|
if (patch.country !== undefined) { sets.push('country = ?'); params.push(patch.country) }
|
|
2255
2275
|
if (patch.contacts !== undefined) { sets.push('contacts = ?'); params.push(JSON.stringify(patch.contacts)) }
|
|
2256
2276
|
if (patch.participantRef !== undefined) { sets.push('participant_ref = ?'); params.push(patch.participantRef) }
|
|
2277
|
+
if (patch.designatedBy !== undefined) { sets.push('designated_by = ?'); params.push(patch.designatedBy) }
|
|
2278
|
+
if (patch.proposedBy !== undefined) { sets.push('proposed_by = ?'); params.push(patch.proposedBy) }
|
|
2279
|
+
if (patch.csStatus !== undefined) { sets.push('cs_status = ?'); params.push(patch.csStatus) }
|
|
2257
2280
|
sets.push("updated_at = datetime('now')", 'updated_by = ?')
|
|
2258
2281
|
params.push(actor ?? null)
|
|
2259
2282
|
const res = await this.stmt(`UPDATE org_registry SET ${sets.join(', ')} WHERE id = ?`, ...params, id).run()
|
|
@@ -668,7 +668,7 @@ CREATE INDEX IF NOT EXISTS idx_mfa_pending_user ON mfa_pending (user_id);
|
|
|
668
668
|
-- registry never merge (the spec's §4), and a membership row's honesty
|
|
669
669
|
-- (its lifecycle state) never depends on a join.
|
|
670
670
|
-- The D1 migration set carries the identical end state
|
|
671
|
-
-- (0013_org_registry.sql).
|
|
671
|
+
-- (0013_org_registry.sql + 0019_org_member_category.sql).
|
|
672
672
|
-- ═══════════════════════════════════════════════════════════════════
|
|
673
673
|
CREATE TABLE IF NOT EXISTS org_registry (
|
|
674
674
|
id TEXT PRIMARY KEY,
|
|
@@ -684,6 +684,17 @@ CREATE TABLE IF NOT EXISTS org_registry (
|
|
|
684
684
|
-- The participant-link annotation (which participant record the org
|
|
685
685
|
-- mirrors); documentation only.
|
|
686
686
|
participant_ref TEXT,
|
|
687
|
+
-- The designation links + the CS status facet (0019_org_member_category.sql,
|
|
688
|
+
-- TODO.identity-features/10): a Utilizer's designated_by is its MEMBER
|
|
689
|
+
-- STATE, an Associate's its CORRESPONDING MEMBER, a Test Laboratory's
|
|
690
|
+
-- its associated ISSUING AUTHORITY; an Issuing Authority's proposed_by
|
|
691
|
+
-- is its proposing MEMBER STATE; cs_status is the designated bodies'
|
|
692
|
+
-- Declaration standing (signed-active / suspended / withdrawn). All
|
|
693
|
+
-- NULL = not recorded; the kind enforcement is the program's write
|
|
694
|
+
-- path, the store keeps the columns opaque.
|
|
695
|
+
designated_by TEXT,
|
|
696
|
+
proposed_by TEXT,
|
|
697
|
+
cs_status TEXT,
|
|
687
698
|
state TEXT NOT NULL DEFAULT 'active',
|
|
688
699
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
689
700
|
created_by TEXT,
|
|
@@ -115,6 +115,22 @@ function migrateAuthTables(db: Database.Database): void {
|
|
|
115
115
|
if (membershipCols.length && !membershipCols.some(c => c.name === 'cone')) {
|
|
116
116
|
db.exec('ALTER TABLE org_memberships ADD COLUMN cone TEXT')
|
|
117
117
|
}
|
|
118
|
+
// TODO.identity-features/10 (the OIML Member category): the
|
|
119
|
+
// designation links + the CS status facet arrive with migration 0019 —
|
|
120
|
+
// a dev file predating it grows the columns here. NULL = not recorded:
|
|
121
|
+
// existing rows keep their posture silently.
|
|
122
|
+
const registryCols = db.prepare('PRAGMA table_info(org_registry)').all() as Array<{ name: string }>
|
|
123
|
+
if (registryCols.length) {
|
|
124
|
+
if (!registryCols.some(c => c.name === 'designated_by')) {
|
|
125
|
+
db.exec('ALTER TABLE org_registry ADD COLUMN designated_by TEXT')
|
|
126
|
+
}
|
|
127
|
+
if (!registryCols.some(c => c.name === 'proposed_by')) {
|
|
128
|
+
db.exec('ALTER TABLE org_registry ADD COLUMN proposed_by TEXT')
|
|
129
|
+
}
|
|
130
|
+
if (!registryCols.some(c => c.name === 'cs_status')) {
|
|
131
|
+
db.exec('ALTER TABLE org_registry ADD COLUMN cs_status TEXT')
|
|
132
|
+
}
|
|
133
|
+
}
|
|
118
134
|
// TODO.identity-sso/02+03 (the strong-authentication wave): the amr
|
|
119
135
|
// provenance columns on sessions → codes → access tokens.
|
|
120
136
|
if (!sessionCols.some(c => c.name === 'amr')) {
|
|
@@ -1008,6 +1024,9 @@ interface OrgRegistryRow {
|
|
|
1008
1024
|
country: string | null
|
|
1009
1025
|
contacts: string
|
|
1010
1026
|
participant_ref: string | null
|
|
1027
|
+
designated_by: string | null
|
|
1028
|
+
proposed_by: string | null
|
|
1029
|
+
cs_status: string | null
|
|
1011
1030
|
state: OrgRegistryState
|
|
1012
1031
|
created_at: string
|
|
1013
1032
|
created_by: string | null
|
|
@@ -1042,6 +1061,9 @@ function orgRegistryPayload(row: OrgRegistryRow): OrgRegistryOrg {
|
|
|
1042
1061
|
country: row.country,
|
|
1043
1062
|
contacts: parseOrgContacts(row.contacts),
|
|
1044
1063
|
participantRef: row.participant_ref,
|
|
1064
|
+
designatedBy: row.designated_by ?? null,
|
|
1065
|
+
proposedBy: row.proposed_by ?? null,
|
|
1066
|
+
csStatus: row.cs_status ?? null,
|
|
1045
1067
|
state: row.state,
|
|
1046
1068
|
createdAt: row.created_at,
|
|
1047
1069
|
createdBy: row.created_by,
|
|
@@ -1071,14 +1093,18 @@ export function createOrgRegistryOrg(input: {
|
|
|
1071
1093
|
country?: string | null
|
|
1072
1094
|
contacts?: OrgRegistryContact[]
|
|
1073
1095
|
participantRef?: string | null
|
|
1096
|
+
designatedBy?: string | null
|
|
1097
|
+
proposedBy?: string | null
|
|
1098
|
+
csStatus?: string | null
|
|
1074
1099
|
createdBy?: string | null
|
|
1075
1100
|
}): OrgRegistryOrg | null {
|
|
1076
1101
|
const res = getDb().prepare(
|
|
1077
|
-
`INSERT OR IGNORE INTO org_registry (id, name, short_name, kind, country, contacts, participant_ref, created_by)
|
|
1078
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1102
|
+
`INSERT OR IGNORE INTO org_registry (id, name, short_name, kind, country, contacts, participant_ref, designated_by, proposed_by, cs_status, created_by)
|
|
1103
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1079
1104
|
).run(
|
|
1080
1105
|
input.id, input.name, input.shortName ?? null, input.kind ?? null, input.country ?? null,
|
|
1081
|
-
JSON.stringify(input.contacts ?? []), input.participantRef ?? null,
|
|
1106
|
+
JSON.stringify(input.contacts ?? []), input.participantRef ?? null,
|
|
1107
|
+
input.designatedBy ?? null, input.proposedBy ?? null, input.csStatus ?? null, input.createdBy ?? null,
|
|
1082
1108
|
)
|
|
1083
1109
|
if (res.changes === 0) return null
|
|
1084
1110
|
return getOrgRegistryOrg(input.id)
|
|
@@ -1095,6 +1121,9 @@ export function updateOrgRegistryOrg(
|
|
|
1095
1121
|
country?: string | null
|
|
1096
1122
|
contacts?: OrgRegistryContact[]
|
|
1097
1123
|
participantRef?: string | null
|
|
1124
|
+
designatedBy?: string | null
|
|
1125
|
+
proposedBy?: string | null
|
|
1126
|
+
csStatus?: string | null
|
|
1098
1127
|
},
|
|
1099
1128
|
actor?: string | null,
|
|
1100
1129
|
): OrgRegistryOrg | null {
|
|
@@ -1106,6 +1135,9 @@ export function updateOrgRegistryOrg(
|
|
|
1106
1135
|
if (patch.country !== undefined) { sets.push('country = ?'); params.push(patch.country) }
|
|
1107
1136
|
if (patch.contacts !== undefined) { sets.push('contacts = ?'); params.push(JSON.stringify(patch.contacts)) }
|
|
1108
1137
|
if (patch.participantRef !== undefined) { sets.push('participant_ref = ?'); params.push(patch.participantRef) }
|
|
1138
|
+
if (patch.designatedBy !== undefined) { sets.push('designated_by = ?'); params.push(patch.designatedBy) }
|
|
1139
|
+
if (patch.proposedBy !== undefined) { sets.push('proposed_by = ?'); params.push(patch.proposedBy) }
|
|
1140
|
+
if (patch.csStatus !== undefined) { sets.push('cs_status = ?'); params.push(patch.csStatus) }
|
|
1109
1141
|
sets.push("updated_at = datetime('now')", 'updated_by = ?')
|
|
1110
1142
|
params.push(actor ?? null)
|
|
1111
1143
|
const res = getDb().prepare(`UPDATE org_registry SET ${sets.join(', ')} WHERE id = ?`).run(...params, id)
|
package/src/store.ts
CHANGED
|
@@ -898,19 +898,40 @@ export interface OrgRegistryContact {
|
|
|
898
898
|
* scheme consumer); the program side bounds the assignable per-org
|
|
899
899
|
* roles by it. `participantRef` is the OPTIONAL annotation documenting
|
|
900
900
|
* which participant record the org mirrors (the link's documentation,
|
|
901
|
-
* never a key the store resolves).
|
|
901
|
+
* never a key the store resolves).
|
|
902
|
+
*
|
|
903
|
+
* TODO.identity-features/10 (the OIML Member category, the taxonomy
|
|
904
|
+
* correction): the designation links + the CS status facet (migration
|
|
905
|
+
* 0019). `designatedBy` is the designating body (a Utilizer's member
|
|
906
|
+
* state, an Associate's corresponding member, a Test Laboratory's
|
|
907
|
+
* associated issuing authority); `proposedBy` is an Issuing
|
|
908
|
+
* Authority's proposing member state; `csStatus` is the designated
|
|
909
|
+
* bodies' Declaration standing ('signed-active' | 'suspended' |
|
|
910
|
+
* 'withdrawn'). All three are opaque strings to the store — the
|
|
911
|
+
* per-kind link enforcement (which kind may point at which) is the
|
|
912
|
+
* program's write path; NULL reads "not recorded", honestly. */
|
|
902
913
|
export interface OrgRegistryOrg {
|
|
903
914
|
id: string
|
|
904
915
|
/** The display name. */
|
|
905
916
|
name: string
|
|
906
917
|
shortName: string | null
|
|
907
918
|
/** The participant kind ('issuing-authority' | 'test-laboratory' |
|
|
908
|
-
* 'utilizer' | 'associate' on the OIML-CS program
|
|
919
|
+
* 'utilizer' | 'associate' on the OIML-CS program, 'member-state' |
|
|
920
|
+
* 'corresponding-member' on the OIML Member category), NULL for a
|
|
909
921
|
* non-participant org. Opaque to the store. */
|
|
910
922
|
kind: string | null
|
|
911
923
|
country: string | null
|
|
912
924
|
contacts: OrgRegistryContact[]
|
|
913
925
|
participantRef: string | null
|
|
926
|
+
/** The designating body's org id (TODO.identity-features/10) — the
|
|
927
|
+
* designated-body kinds carry it; NULL = not recorded. */
|
|
928
|
+
designatedBy: string | null
|
|
929
|
+
/** The proposing member state's org id (the issuing-authority kind). */
|
|
930
|
+
proposedBy: string | null
|
|
931
|
+
/** The Declaration's standing on the designated bodies
|
|
932
|
+
* ('signed-active' | 'suspended' | 'withdrawn'); NULL = not
|
|
933
|
+
* recorded. */
|
|
934
|
+
csStatus: string | null
|
|
914
935
|
state: OrgRegistryState
|
|
915
936
|
createdAt: string
|
|
916
937
|
createdBy: string | null
|
|
@@ -1702,6 +1723,9 @@ export interface ServerStore {
|
|
|
1702
1723
|
country?: string | null
|
|
1703
1724
|
contacts?: OrgRegistryContact[]
|
|
1704
1725
|
participantRef?: string | null
|
|
1726
|
+
designatedBy?: string | null
|
|
1727
|
+
proposedBy?: string | null
|
|
1728
|
+
csStatus?: string | null
|
|
1705
1729
|
createdBy?: string | null
|
|
1706
1730
|
}): Promise<OrgRegistryOrg | null>
|
|
1707
1731
|
/** Edit the display data (the id is the stable slug — never editable);
|
|
@@ -1716,6 +1740,9 @@ export interface ServerStore {
|
|
|
1716
1740
|
country?: string | null
|
|
1717
1741
|
contacts?: OrgRegistryContact[]
|
|
1718
1742
|
participantRef?: string | null
|
|
1743
|
+
designatedBy?: string | null
|
|
1744
|
+
proposedBy?: string | null
|
|
1745
|
+
csStatus?: string | null
|
|
1719
1746
|
},
|
|
1720
1747
|
actor?: string | null,
|
|
1721
1748
|
): Promise<OrgRegistryOrg | null>
|
package/src/vocab/permissions.ts
CHANGED
|
@@ -145,6 +145,12 @@ export const TRANSITION_PERMISSIONS: Record<string, readonly ActionPermission[]>
|
|
|
145
145
|
// records mode (TODO.adoption/02): the registered-offline entry — the
|
|
146
146
|
// application's honest hop into ACCEPTED without a fake walked history.
|
|
147
147
|
'application:register_offline->ACCEPTED': ['records.register'],
|
|
148
|
+
// DEMO_FLOWS wave 1: the lifecycle closure — the IA marks the Evaluation
|
|
149
|
+
// Project COMPLETED once the completion gate's settling conditions all
|
|
150
|
+
// hold (the model's completion_settled guard refuses otherwise); the act
|
|
151
|
+
// is the IA's decision desk on the application, the same family as the
|
|
152
|
+
// accept/reject decisions.
|
|
153
|
+
'application:ia_marks_completed->COMPLETED': ['application.accept'],
|
|
148
154
|
|
|
149
155
|
// test_request (the dispatch; TODO.adoption/07's quote leg: the
|
|
150
156
|
// laboratory's quotation response and the IA's commercial decision on it)
|
|
@@ -161,6 +167,9 @@ export const TRANSITION_PERMISSIONS: Record<string, readonly ActionPermission[]>
|
|
|
161
167
|
// test_report
|
|
162
168
|
'test_report:lab_submits->SUBMITTED': ['tr.submit'],
|
|
163
169
|
'test_report:ia_starts_review->UNDER_REVIEW': ['tr.review'],
|
|
170
|
+
// DEMO_FLOWS wave 1: the review period's opening on the test report —
|
|
171
|
+
// the IA's TR-review desk (the consultation rides the review class).
|
|
172
|
+
'test_report:ia_opens_consultation->CONSULTATION': ['tr.review'],
|
|
164
173
|
'test_report:ia_accepts->ACCEPTED': ['tr.review'],
|
|
165
174
|
'test_report:ia_rejects->REJECTED': ['tr.review'],
|
|
166
175
|
'test_report:lab_withdraws_for_edit->DRAFT': ['tr.submit'],
|
|
@@ -184,6 +193,9 @@ export const TRANSITION_PERMISSIONS: Record<string, readonly ActionPermission[]>
|
|
|
184
193
|
// Fired by the TR-review service when the last report is determined —
|
|
185
194
|
// the reviewing officer holds tr.review, the evaluation worker er.review.
|
|
186
195
|
'evaluation_report:last_tr_determined->ALL_TR_REVIEWED': ['tr.review', 'er.review'],
|
|
196
|
+
// DEMO_FLOWS wave 1: the review period's opening — the IA officer's
|
|
197
|
+
// evaluation-work act (the consultation is the ER's review surface).
|
|
198
|
+
'evaluation_report:ia_opens_consultation->CONSULTATION': ['er.review'],
|
|
187
199
|
'evaluation_report:ia_approves->APPROVED': ['er.finalize'],
|
|
188
200
|
'evaluation_report:ia_rejects->REJECTED': ['er.finalize'],
|
|
189
201
|
'evaluation_report:ia_approves_with_conditions->CONDITIONALLY_APPROVED': ['er.finalize'],
|
|
@@ -217,6 +229,22 @@ export const TRANSITION_PERMISSIONS: Record<string, readonly ActionPermission[]>
|
|
|
217
229
|
'certificate:revise->ACTIVE': ['certificate.issue'],
|
|
218
230
|
'certificate:transfer_ownership->ACTIVE': ['certificate.issue'],
|
|
219
231
|
|
|
232
|
+
// certificate_registration (DEMO_FLOWS wave 1 — the register's
|
|
233
|
+
// auto-publish doctrine, the program owner's 2026-08-28 decision): the
|
|
234
|
+
// hub-side registration submission lifecycle — the registrant
|
|
235
|
+
// announcement's shell, the signed package's intake verification, the
|
|
236
|
+
// publication, the recorded refusal. All three edges are SYSTEM acts:
|
|
237
|
+
// the intake automation walks them on the verified signed submission
|
|
238
|
+
// (no human gate between verify and publish — the register's trust is
|
|
239
|
+
// in the signature + the receipt machinery; the human review surfaces
|
|
240
|
+
// stay read postures). The map names the desk authority the automation
|
|
241
|
+
// exercises — the same shape the other engine-fired edges ride (the
|
|
242
|
+
// sample_verification projection's transitions name
|
|
243
|
+
// verification.perform).
|
|
244
|
+
'certificate_registration:intake_verifies->SUBMITTED': ['certificate.register'],
|
|
245
|
+
'certificate_registration:auto_publishes->PUBLISHED': ['certificate.register'],
|
|
246
|
+
'certificate_registration:intake_refuses->REFUSED': ['certificate.register'],
|
|
247
|
+
|
|
220
248
|
// test_assignment (the laboratory's work items; `omit` rides the IA's
|
|
221
249
|
// dispatch withdrawal cascade)
|
|
222
250
|
'test_assignment:lab_accepts->ACCEPTED': ['run.perform'],
|
|
@@ -354,6 +382,10 @@ export const STORE_MACHINES: Record<string, string> = {
|
|
|
354
382
|
formInstances: 'form_instance',
|
|
355
383
|
evaluationReports: 'evaluation_report',
|
|
356
384
|
certificates: 'certificate',
|
|
385
|
+
// DEMO_FLOWS wave 1: the register's registration submissions are
|
|
386
|
+
// machinated too (the intake-verify → auto-publish chain); the generic
|
|
387
|
+
// write path gates status writes on the machine's declared edges.
|
|
388
|
+
certificateRegistrations: 'certificate_registration',
|
|
357
389
|
testAssignments: 'test_assignment',
|
|
358
390
|
testRuns: 'test_run',
|
|
359
391
|
measuringInstrumentSamples: 'measuring_instrument_sample',
|