Public source file

ROADMAP.md

Documentation home
609 lines46,108 bytesread-only generated view
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
# Public Roadmap

Date: 2026-04-11

Scope:
- public-safe QR verification PoC
- narrowed verifier implementation
- React verifier frontend
- native iPhone end-user scanner
- documentation and packaging for eventual open-source use

## Goal

Publish a clean technical repository centered on the narrowed verifier flow,
without patent filing materials, personal data, or submission artifacts.

## Current Status

Authoritative open items:
- [x] complete the scanner-release evidence packet: 30 scanner-fleet artifacts plus 12 provider-profile artifacts under `docs/public/evidence/iphone/`
- [x] keep `make release-audit-strict` blocked until the scanner-fleet and provider-profile evidence artifacts are tracked
- [ ] finish production-reference network obligations that are intentionally outside the local PoC: vendor-specific KMS/HSM credentials and ceremonies, production-owned restore artifacts, production-owned packaged deployment approval records, and the actual operator evidence refs required by the production evidence requirements contract
- [x] keep the Python verifier lab stable while TypeScript reference-network services mature behind contract smoke tests
- [x] continue end-user iOS polish only after the release evidence packet can be regenerated from the app without manual filename mapping
- [x] add a production-style QR Trust management plane so root programs, delegated authorities, issuer enrollment, domain proofs, destination policies, verifier keys, runtime providers, NATS subscribers, audit rows, and publication outbox rows are managed through secure API and CLI workflows instead of hardcoded fixtures or runtime-only config

Completed:
- [x] separated local-only patent and filing materials into ignored `private/`
- [x] excluded `archive/`, `private/`, `.claude/`, `.env`, and `.venv` from Git
- [x] kept active backend PoC services in the tracked repo
- [x] reduced `docs/` to public-safe documentation only
- [x] added a public release checklist
- [x] selected Apache-2.0 as the public license
- [x] added a bounded public patent-position statement
- [x] refreshed backend dependencies through `uv add`
- [x] added a pytest baseline for core verifier services and API routes
- [x] added a Dockerfile and `compose.yml` for `api`, `postgres`, and `redis`
- [x] added a schema-backed restore automation evidence packet and smoke check for backup, scratch restore, rollback rehearsal, and operator failover handoff
- [x] added a schema-backed packaged deployment approval evidence packet and smoke check for artifact fingerprinting, contract smoke, release/security/operations approval, and rollback acceptance
- [x] added a schema-backed operator evidence index and smoke check that maps the twelve readiness controls to reviewed public-safe or operator-owned evidence references
- [x] added a schema-backed production evidence requirements contract and smoke check for production-candidate operator proof obligations
- [x] added an operator-facing production evidence collection template and smoke check that turns required proof obligations into fillable `ops://qrtrust/` checklist slots without treating placeholders as proof
- [x] added a production evidence gap report and smoke check that compares required operator proof obligations against the production-candidate evidence index without treating the report as production proof
- [x] enforced role-qualified production evidence refs so the gap report counts only `ops://qrtrust/` refs reviewed by the required operational role for each control
- [x] mirrored production-candidate evidence role, operator-backed status, and `ops://qrtrust/` ref constraints into the public operator evidence schema and contract smoke checker
- [x] wired the production evidence gap report into the reference-network handoff bundle so reviewers see missing operator-owned refs beside adoption and readiness claims
- [x] split the reference handoff's general operator evidence packet from its production-candidate operator evidence packet and added consistency checks so the production evidence gap report cannot drift from the requirements and production index it summarizes
- [x] added a production evidence intake gate and smoke check that rejects incomplete gaps, placeholders, duplicate refs, and stale inputs before reviewer handoff
- [x] added a production evidence closure bundle that turns the remaining operator-owned production obligations into a local JSON/Markdown handoff without treating the public repo as private evidence storage
- [x] added a private production evidence handoff workflow that writes ignored operator starter templates and validates private operator evidence indexes without printing evidence refs or bodies
- [x] surfaced the production evidence closure bundle in `make release-readiness-report` so public-release readiness cannot be confused with production readiness
- [x] added CI coverage for release-readiness report production-boundary visibility
- [x] added a public-release audit guard against stale native-evidence blocker wording and updated public docs now that scanner-fleet and provider-profile evidence are tracked
- [x] reconciled the public companion plan and release checklist so stale native-evidence TODOs no longer appear as live blockers
- [x] validated the compose stack, migrations, and live verifier flow
- [x] added QR artifact encode/render/decode support for the narrowed verifier envelope
- [x] added end-to-end tests from QR PNG generation through scanned verification
- [x] added a browser-based verifier lab for local client testing
- [x] scaffolded a Vite 8 / React 19 / Tailwind v4 / shadcn frontend workspace
- [x] built a first-pass React verifier workbench for key issue, runtime status, QR generation, upload decode, and scanned verification
- [x] wired Vite proxying for `/verifier/*` so the frontend can stay same-origin in local development
- [x] removed the legacy `/poc/*` alias and kept `/verifier/*` as the only supported API surface
- [x] added a bundled fallback QR image decoder path for browsers without `BarcodeDetector`
- [x] removed private signing key exposure from the demo API surface
- [x] added QR payload and image decode size guardrails to the public verifier flow
- [x] tightened local runtime defaults with same-origin CORS behavior and localhost-only published ports
- [x] disabled the older broad write-oriented API routes by default
- [x] added per-client rate limits to the public verifier POST endpoints, with Redis-backed coordination when available
- [x] added sanitized request tracing with `X-Request-ID` and metadata-only request logs
- [x] added optional API key auth for verifier POST endpoints, with lab support when keys are configured
- [x] added admin-managed verifier API key issue, rotate, revoke, and list flow
- [x] added a runtime status endpoint so the lab and smoke tests can detect server posture directly
- [x] added compose-backed live HTTP smoke coverage for the running verifier service
- [x] added compose-backed workbench smoke coverage for the React shell and lab comparison route
- [x] expanded the browser lab with camera selection, request tracing, and QR export helpers
- [x] added an optional Playwright browser regression test for the verifier lab
- [x] added React workbench browser evidence capture for accepted and payload-mismatch outcomes
- [x] added a native SwiftUI iPhone scanner app for real-device user-facing decisions and immediate feedback
- [x] added session-backed iPhone demo generation so the phone can publish an exact QR display URL for a second screen
- [x] documented a deterministic iPhone payload-mismatch drill using a shareable display URL
- [x] added a public release audit for repo boundary, evidence, docs, and validation hooks
- [x] added a strict public-release audit gate for the final no-warning cut
- [x] added a local native iPhone evidence capture packet generator
- [x] added a native iPhone evidence status reporter for capture/import progress
- [x] added a local release readiness report for private-remote review
- [x] added a native iPhone evidence checker for captured screenshot or recording artifacts
- [x] switched native iOS evidence export to one shareable evidence folder and pointed its generated README at the combined scanner-release importer so release evidence can be regenerated without manual filename mapping
- [x] added public GitHub collaboration files for security reports, support, contributions, issues, and pull requests
- [x] added GitHub Actions CI for release audit, backend tests, frontend lint/build, compose workbench smoke, route-navigation smoke, and manual native iOS smoke
- [x] added Dependabot configuration and release-candidate status documentation
- [x] add scanner UX friction layer with risk stripe, reason codes, and hold-to-open gating for amber/red outcomes
- [x] added an ignored local iOS verifier-provider profile generator plus a tracked checker so physical-device builds do not depend on stale Xcode run-scheme variables
- [x] hardened native iPhone evidence import/status checks with PNG signature validation and structured accessibility trace validation
- [x] surfaced reusable, one-time, and time-limited QR use semantics in the native iOS result screen so printed/shared codes are not confused with replay-protected session codes
- [x] clarified native iOS provider and privacy copy so users understand what is checked, what is sent to the verifier, and why production profiles should be signed rather than pasted endpoints
- [x] added a first-run and revisitable native iOS QR-use guide explaining reusable public, one-time, and time-limited QR semantics without weakening the four-layer trust model
- [x] added a reviewer-facing iOS provider profile import path so local verifier endpoints can be refreshed without source edits or raw endpoint entry
- [x] prepared native iOS localization by moving generated scanner and provider status copy to `String(localized:)` and documenting the String Catalog handoff
- [x] added a signed iOS provider profile import contract with a constrained local-reviewer exception for localhost, `.local`, and private LAN verifier endpoints
- [x] added a signed iOS provider-profile fixture plus cryptographic fixture/generator checks for reviewer imports
- [x] added native iOS provider-profile evidence contract and non-strict status reporting for import, active, stale, revoked, rejected, and local-reviewer states
- [x] added a local iOS provider-profile evidence capture packet generator for screenshot and accessibility-trace handoff
- [x] added a strict iOS provider-profile evidence importer so captured screenshots and accessibility traces are validated before tracking
- [x] expanded iOS provider-profile evidence status so it reports local incoming capture files before strict import
- [x] split provider-profile evidence into its own public-release audit warning/check
- [x] split release-readiness reporting and public-release docs between scanner evidence and provider-profile evidence
- [x] isolated deterministic signing fixture keys in a clearly documented test-only module and added a public-release audit guard against PEM blocks elsewhere
- [x] reconciled public-release checklist status against the current audit so stale housekeeping items no longer hide the real native-evidence blockers

In progress:
- [x] review code and comments for filing-oriented naming that should become public technical naming
- [x] decide the public API surface for the verifier PoC
- [x] turn the verifier reference flow into an end-to-end scannable QR workflow
- [x] design the replacement client app for the archived `QRCode-PoC` scanner
- [x] harden the browser lab and QR artifact flow for broader device/browser coverage
- [x] reach camera-capture parity in the React frontend before retiring the backend-served static lab
- [x] define additive `scanner_ux` response contract for risk score, reason codes, hold requirement, and destination display

Next:
- [x] write a public verifier profile document
- [x] add public test vectors
- [x] add a public run guide for the backend demos
- [x] make `/verifier/*` the canonical reference API
- [x] add real-browser/device testing guidance for the verifier lab
- [x] generate actual QR artifacts and fixture payloads from the narrowed verifier flow
- [x] build a client scanner against `/verifier/verify-scanned`
- [x] decide whether the legacy `/poc/*` alias should stay once the client is rebuilt
- [x] replace the legacy password hashing path with direct `bcrypt`
- [x] add camera/browser compatibility notes and fallback guidance to the lab
- [x] verify the new frontend service in compose and document the split between the React workbench and the legacy camera page
- [x] add optional HTTPS support for the React frontend so iPhone Safari can treat the workbench as a secure context
- [x] scaffold a native iPhone scanner app for cases where browser camera feedback is insufficient
- [x] add iPhone-specific smoke coverage beyond the compile/build check
- [x] add browser screenshots for accepted, replay, and payload-mismatch outcomes
- [x] add a repeatable public-release audit target
- [x] add a validator for native-device evidence artifacts
- [x] add public GitHub collaboration templates and policies
- [x] add public CI workflows
- [x] add dependency update automation and release-candidate status
- [x] track the unified native iOS scanner-release evidence packet for green, orange, and red scanner outcomes plus provider-profile import/settings/rejection states
- [x] add an iOS provider-profile config check to catch stale local HTTPS verifier endpoints before physical-device testing
- [x] wire `scanner_ux` into the native iPhone scanner result card, decision path, and open-action friction
- [x] wire `scanner_ux` into the React lab scanner-visible preview and event log
- [x] add seeded A/B demo fixtures for control vs friction treatment logs
- [x] export scanner UX evidence logs with `risk_score`, `reason_codes`, `hold_required`, and open timing
- [x] reconcile the older public companion plan against the current repo so stale pre-release tasks no longer look like live blockers
- [x] add `CITATION.cff` for software citation metadata
- [x] add a production adoption gap map that ties the paper's trust layers to reference services, operator-owned controls, and required evidence
- [x] split governance fixture publication into an explicit ordered artifact-input plan before store, queue, or event-bus publication
- [x] add a Postgres-backed governance publication source that plans artifacts from active source-of-truth rows
- [x] add a minimal authority publication service that publishes Postgres governance bundles through the artifact event surface
- [x] prove authority publication handoff into Postgres event outbox rows and envelope-only NATS propagation messages
- [x] add a reset-guarded live authority outbox drill from Postgres governance rows through NATS JetStream
- [x] add a live verifier-cache drill that consumes propagated governance references and writes derived cache entries
- [x] add a live scanner-decision drill that reads persisted verifier-cache state plus runtime observations
- [x] close the remaining Section 7.2 authority hierarchy, naming, and state-distribution gaps with root-aware policy, namespace isolation, signed artifact validation, status-event invalidation, approved NATS subscriber materialization, and multi-root contradiction drills
- [x] build the production-style management plane: Alembic-owned QR Trust schema, scoped operator auth, management API, `qrtrustctl` CLI, operator UI workflows, DB-backed NATS subscriber authorization, audit logging, idempotent governance mutations, and removal of fixture/config-backed governance writes from production paths

## Section 7.2 Authority Hierarchy and State Distribution

Goal: align the PoC implementation with the paper's Section 7.2 flow where accepted roots are configured before scan evaluation, root-to-authority-to-issuer paths define the trust namespace, signed artifacts distribute issuer and policy state, and fresher revocation or status state overrides stale verifier cache entries.

Findings from the current codebase review:
- [x] accepted-root policy is now a first-class verifier-cache input, and ungoverned scanner decisions no longer publish fake demo-root governance events
- [x] namespace isolation now keys destination policies by `root_program_id`, `delegated_authority_id`, `issuer_id`, and `destination_policy_id`
- [x] verifier-cache materialization now requires signed-artifact acceptance before accepting root manifests, delegated-authority manifests, issuer records, destination policies, or status events
- [x] revocation, suspension, certificate-status, destination-policy-status, delegated-authority, and trust-key status events now invalidate or update derived verifier-cache state through signed status artifacts
- [x] approved NATS governance subscribers now run as durable workers, consume event envelopes only, fetch authoritative artifacts, validate hashes/signatures/scope, and materialize derived verifier-cache state
- [x] precedence rules now have deterministic drills for lower-level state without authority context, unaccepted roots, signed status invalidation, source refresh after invalidation, and multi-root isolation
- [x] state-distribution contracts now document accepted roots, artifact refs, freshness, subscriber authorization, source-of-truth fetch behavior, and PoC versus production NATS security
- [x] assurance-tier downgrade semantics should become a dedicated signed status artifact when the PoC needs a separate behavior from suspension, revocation, expiry, or retired trust-key state

Implementation plan:
- [x] make accepted roots explicit in TypeScript scanner decision, verifier cache materialization, and live drills; a missing or unaccepted root must produce an orange or red decision, not a green decision or a demo-root event
- [x] replace bare destination-policy cache keys with a stable namespace key: `root_program_id`, `delegated_authority_id`, `issuer_id`, and `destination_policy_id`
- [x] add signed-artifact verification to the read-model materializer before it accepts root manifests, delegated-authority manifests, issuer records, destination policies, or status events
- [x] emit and consume real issuer status, certificate status, destination-policy status, revocation, and suspension artifacts from the Postgres source of truth
- [x] add an approved NATS subscriber worker that treats NATS as notification transport only, fetches source-of-truth artifacts, validates signatures and freshness, then updates the verifier cache
- [x] add deterministic multi-root and stale-state drills for Section 7.2 precedence rules
- [x] document the complete state-distribution contract in `docs/public/network-contracts/`, including accepted roots, artifact refs, freshness windows, subscriber authorization, and failure behavior
- [x] add explicit assurance-change status artifacts only if the research/demo flow needs assurance downgrade to be modeled separately from the current lifecycle status events

Container and node plan:
- [x] do not add Kafka or a second database technology; Postgres remains the only source of truth, and NATS remains the propagation transport
- [x] add logical worker nodes as compose services only when implementation begins: `network-governance-subscriber-worker` for signed governance artifacts; `network-runtime-subscriber-worker` remains optional until runtime-feed subscriber implementation begins
- [x] add an optional second verifier-node service or profile only when federation demos need independent verifier-cache state, stale cache, or cross-root contradiction evidence
- [x] keep the production note: stronger but heavier NATS accounts, JWT, and NKeys are closer to production multi-tenant governance, but the setup is larger and should follow after the PoC's Postgres-source-of-truth and approved-subscriber flow is working

## Production Management Plane

Goal: make the PoC operable like a production QR Trust deployment without
turning local demos into hidden authority. Operators should manage root
programs, delegated authorities, issuer enrollment, domain proofs, destination
policies, verifier clients, runtime providers, and NATS subscribers through
controlled API, CLI, and UI workflows backed by Postgres.

Findings from the current codebase review:
- [x] governance fixture loaders and demo seed programs are isolated as
  non-production examples; production-like trust state is reproducible through
  management API/CLI/UI workflows backed by Postgres audit and outbox rows
- [x] backend Alembic is now the canonical migration owner for QR Trust schema
  and management-plane tables; TypeScript schema apply scripts remain
  non-production drift checks and reset-guarded smoke helpers
- [x] verifier client keys and admin tokens are still config, Redis, or memory
  backed; production-style operation needs DB-backed management keys, scoped
  operator roles, revocation records, and audit history
  - [x] `/admin/*` now accepts active DB-backed management API keys from
    `qr_trust.management_api_keys`, enforces route scopes, and attributes
    audit rows to the management key when present; local config admin tokens
    remain a development bootstrap path
  - [x] added management API key issue, list, and revoke workflows so scoped
    operator credentials can be created and retired through audited `/admin/*`
    endpoints instead of direct SQL
  - [x] constrained management key issuance to known management scopes so typo
    or unsupported grants cannot become durable credentials
  - [x] added scoped `/admin/verifier-clients/api-keys/*` workflows so verifier
    client keys are issued, listed, and revoked through DB-backed management
    credentials with audit rows instead of the legacy verifier admin surface
  - [x] tightened verifier-client key rotation semantics so only active
    Postgres-backed dynamic keys can rotate; revoked credentials cannot be
    reactivated through a rotation workflow
  - [x] centralized verifier-client key ID construction so management API and
    service-issued dynamic verifier-client records use the same `vkey_`
    identifier format while retaining full-hash lookup for authentication
  - [x] added `VERIFIER_BOOTSTRAP_ADMIN_TOKENS_ENABLED=false` as the secure
    default so config admin tokens are inert unless a local bootstrap run
    explicitly opts in; DB-backed management keys remain the production path
  - [x] added `VERIFIER_STATIC_API_KEYS_ENABLED=false` as the secure default
    so config verifier-client keys are inert unless a local static-key run
    explicitly opts in; Postgres-backed verifier-client keys remain the
    production path
  - [x] separated verifier-client credentials from operator evidence reads so
    scanner/API client keys cannot unlock `/verifier/status` operator posture;
    bootstrap admin tokens or DB-backed management keys with read scopes are
    required when verifier auth is enabled
  - [x] rejected expired credential `expires_at` values at the management API
    request boundary so management and verifier-client keys cannot be issued
    already invalid
  - [x] retired the legacy `/verifier/admin/api-keys/*` lab mutation surface
    with `410 Gone` pointers to `/admin/verifier-clients/api-keys/*`, and
    routed React/static lab key issuance plus live smoke coverage through the
    audited management endpoint
  - [x] blocked `verifier:client` keys from loading as management principals
    so scanner/API client credentials cannot authenticate to `/admin/*`
  - [x] added that verifier-client/admin separation to
    `qrtrustctl management-live-drill` so the production workflow drill proves
    scanner credentials cannot act as operator credentials
- [x] issuer enrollment, domain proofing, destination-policy publication, and
  status/revocation actions now have secure operator-facing API, CLI, and UI
  workflows backed by management audit and outbox rows
- [x] NATS is correctly treated as propagation transport, but approved
  subscriber identity, subject authorization, and replay/recovery ownership need
  DB-backed management records before the broker can be operated as a
  production-like service
  - [x] added management API and `qrtrustctl` workflows for DB-backed NATS
    subscriber authorization and subject allowlist inspection
  - [x] added fail-closed NATS subject validation at the management API and
    subscriber-loader boundaries so broad grants like `qrtrust.>` cannot become
    approved subscriber state
  - [x] added the same QR Trust subject validation to `qrtrustctl
    nats-subscriber-authorize` so scripted operator workflows reject malformed
    or over-broad grants before making management API calls
  - [x] isolated local worker NATS credentials by role so Compose overrides for
    the outbox publisher, governance subscriber, and runtime subscriber cannot
    accidentally collapse least-privilege broker users into one shared login
- [x] the React operator surface explains runtime posture and exposes guided
  governance workflows for enrollment, review, approval, publication,
  revocation, outbox health, and subscriber authorization
- [x] operator CLI workflows now cover bootstrap, emergency review, scripted
  issuer enrollment, subscriber registration, and outbox inspection
  - [x] added `qrtrustctl outbox-status` and `qrtrustctl audit-list` for
    management API-backed event-outbox and governance-audit inspection

Implementation plan:
- [x] make backend Alembic the canonical migration path for `qr_trust` schema
  evolution and management-plane additions; TypeScript schema apply scripts may
  remain drift checks and local smoke helpers, but not the production source of
  migration truth
  - [x] added a backend Alembic reference-schema revision that creates the
    QR Trust governance, evidence, cache, and event-outbox tables for compose
    databases before management API reads or writes need them
  - [x] moved shared-infra migration Make targets to backend Alembic and kept
    TypeScript reference-schema apply as a local drift/smoke helper
  - [x] reset-guarded live drills rewind the backend Alembic ledger to the
    pre-QR-Trust base revision and reapply Alembic after Postgres-to-NATS retry
    proof, preventing stale `alembic_version` state after schema reset drills
- [x] add DB-backed operator identities, role assignments, management API keys,
  idempotency keys, governance audit log, runtime provider registry, NATS
  subscriber registry, and subscriber subject authorization tables
  - [x] wired active operator role assignments into management authorization so
    DB-backed keys bound to operators fail closed and are limited by both key
    scopes and active operator roles
  - [x] added `/admin/operators` and `/admin/operator-role-assignments` so
    operators and role bindings can be managed through scoped API calls with
    audit rows instead of direct SQL
- [x] add a management service that writes governance state, audit rows, and
  event-outbox rows in one transaction; no API, CLI, or UI path should publish
  trust state without that transaction
  - [x] fixed management mutation row accounting for CTE-based state writes so
    transaction results report `SELECT 1` state mutations correctly
  - [x] added `Idempotency-Key` enforcement for management mutations so
    completed retries replay the recorded result and conflicting key reuse
    returns `409` without duplicating audit or outbox rows
  - [x] included the normalized request hash and request instance in
    management outbox event IDs for idempotency-keyed mutations so expired-key
    reuse cannot mutate source state and audit rows while colliding with an
    older outbox event
- [x] add scoped admin endpoints under `/admin/*` for root programs, delegated
  authorities, issuers, domain proofs, destination policies, status events,
  trust keys, verifier clients, runtime providers, NATS subscribers, outbox
  health, and audit review
  - [x] added root-program and delegated-authority upsert endpoints so issuer
    enrollment has operator-managed parent governance records
  - [x] added issuer enrollment and domain-proof upsert endpoints so
    destination-policy publication can depend on operator-managed issuer and
    domain-control state
  - [x] added issuer status update and guarded destination-policy upsert
    endpoints so policy rows require active issuer state plus verified
    domain-control evidence before entering the publication path
  - [x] added destination-policy status update endpoint for active,
    suspended, revoked, and expired policy state changes through the same
    audit/outbox transaction path
  - [x] added `/admin/outbox` and `/admin/audit` read paths backed by
    `qr_trust.event_outbox` and `qr_trust.governance_audit_log`
  - [x] added `/admin/management-keys/issue`, `/admin/management-keys`, and
    `/admin/management-keys/{key_id}/revoke` for DB-backed management key
    lifecycle operations with scoped auth and audit rows
  - [x] added audited outbox event remediation so operators can quarantine
    stale or malformed event rows and retry remediated rows through the
    management API without direct SQL
  - [x] guarded outbox remediation so already published propagation events stay
    immutable and require a correcting event instead of being requeued
  - [x] added runtime provider upsert/list endpoints backed by
    `qr_trust.runtime_safety_providers` so runtime safety providers can be
    managed through audited governance mutations instead of hardcoded config
  - [x] added verifier client key issue, list, and revoke endpoints under
    `/admin/verifier-clients/api-keys/*` with `verifier_clients:read` and
    `verifier_clients:write` scopes
  - [x] added trust-key upsert, status update, and list endpoints under
    `/admin/trust-keys*` with `trust_keys:read` and `trust_keys:write` scopes
    so signer key governance uses the same state/audit/outbox path
  - [x] rejected expired or reversed trust-key validity windows at the
    management API request boundary so signer keys cannot be created already
    unusable or with contradictory activation intervals
  - [x] rejected expired verified domain-proof windows and timezone-naive
    domain-proof expirations at the management API boundary so issuer
    enrollment cannot publish already-stale domain-control evidence
  - [x] rejected hierarchy role assignments that omit required scope IDs so
    root, delegated-authority, and issuer admins cannot be granted against
    ambiguous authority boundaries
- [x] add `qrtrustctl`, a stdlib-argparse CLI that calls the management API by
  default and reserves direct database access for explicit bootstrap or
  emergency modes
  - [x] added scripted root-program and delegated-authority upsert commands
    with `Idempotency-Key` support for retry-safe bootstrap
  - [x] added scripted issuer enrollment and domain-proof upsert commands so
    bootstrap flows can establish the publication preconditions through the
    management API
  - [x] added scripted issuer status update and destination-policy upsert
    commands for the active-issuer plus verified-domain publication gate
  - [x] added scripted destination-policy status update for emergency
    suspend/revoke workflows without direct database mutation
  - [x] added scripted NATS subscriber authorize/list commands through the
    management API
  - [x] constrained scripted NATS subject arguments to the management API
    subject contract so bad subscriber grants fail at CLI parse time
  - [x] added scripted outbox status and governance audit list commands through
    the management API
  - [x] added scripted outbox event remediation for retry and quarantine
    workflows through the management API
  - [x] added scripted management key issue, list, and revoke commands so
    bootstrap and credential retirement no longer require SQL access
  - [x] added scripted runtime provider upsert/list commands so runtime
    safety provider registry changes use the management API, audit log, and
    event outbox
  - [x] added scripted verifier client key issue/list/revoke commands through
    the management API so lab-scanner credentials can be managed without the
    legacy verifier admin route
  - [x] added scripted operator upsert/list and operator role upsert/list
    commands so bootstrap and access-control workflows can be performed
    without direct SQL
  - [x] added scripted trust-key upsert/list/status commands so key issuance,
    rotation, and revocation evidence can flow through the management API
  - [x] constrained operator status, operator role, trust-key scope, and
    trust-key lifecycle parser choices to the management API schema values so
    scripted access-control and key-governance workflows fail closed locally
  - [x] constrained delegated-authority type and management-key scope parser
    choices to the management API schema values so bootstrap credentials and
    authority setup cannot carry typo or verifier-client-only grants
  - [x] centralized delegated-authority type and management-key scope choices
    in a shared management contract module so the CLI and API cannot silently
    drift on authority setup or bootstrap credential grants
  - [x] centralized management lifecycle/status/parser choices in the same
    shared contract module so issuer, domain, destination, operator,
    runtime-provider, trust-key, and outbox remediation workflows use one
    API/CLI vocabulary
  - [x] constrained issuer, domain-proof, and destination-policy lifecycle
    parser choices to the management API schema values so scripted operator
    workflows reject invalid governance states before sending requests
  - [x] reused the management role-assignment request schema in `qrtrustctl`
    so scripted hierarchy-role grants fail locally when required scope IDs are
    missing instead of relying on an API round trip
  - [x] reused the management domain-proof request schema in `qrtrustctl` so
    scripted verified domain-control evidence rejects stale or malformed
    expiration windows before attempting an API mutation
  - [x] reused the management and verifier-client key issue schemas in
    `qrtrustctl` so scripted credential issuance rejects expired or malformed
    `expires_at` windows before attempting an API mutation
  - [x] reused the trust-key upsert request schema in `qrtrustctl` so scripted
    signer-key issuance rejects expired, reversed, or malformed validity
    windows before attempting an API mutation
  - [x] reused the destination-policy upsert request schema in `qrtrustctl` so
    scripted policy publication rejects empty or malformed approved-destination
    payloads before attempting an API mutation
  - [x] reused the remaining management request schemas in `qrtrustctl` so
    root, authority, issuer, runtime-provider, NATS subscriber, outbox
    remediation, and status mutation commands fail locally on malformed
    payloads before attempting API mutations
- [x] add operator UI workflows for enroll issuer, verify domain, create or
  approve destination policy, rotate or revoke keys, authorize subscribers,
  inspect outbox lag, and review audit history
  - [x] exposed read-only outbox status and governance audit evidence in the
    React operator workflow surface
  - [x] exposed DB-backed management key issue, list, copy-once, and revoke
    workflows in the React access-control surface without mixing them with
    lab verifier client keys
  - [x] added guided operator forms for authority setup, issuer enrollment and
    status, domain proof, destination policy and status, NATS subscriber
    authorization, and outbox remediation through the management API
  - [x] aligned operator destination-policy query controls with verifier
    semantics so strict no-query policies use `query_policy: "none"` and
    unsupported values are rejected before DB mutation
  - [x] added runtime provider management to the React operator workflow so
    provider status, TTL, stale behavior, and unavailable behavior can be
    governed through the same management API path
  - [x] routed React operator verifier client key issue/list flows through
    `/admin/verifier-clients/api-keys/*` and displays the Postgres-backed
    management record state
  - [x] converted trust-key validity-window inputs from browser-local
    `datetime-local` values to timezone-aware UTC payloads so operator key
    rotation workflows satisfy the management API request contract
- [x] route existing demo/seed flows through managed Postgres rows where they
  claim governance state; keep fixtures labeled as non-production examples
  - [x] added `qrtrustctl demo-bootstrap` to compose the reference demo root,
    authority, runtime provider, issuer, domain proof, active issuer status,
    destination policy, governance NATS subscriber, and runtime-observation
    NATS subscriber through retry-safe management API mutations instead of
    direct fixture writes
  - [x] routed lab verifier-client key issuance through the management API and
    disabled the older verifier-admin key routes so local demos do not bypass
    scoped management credentials or audit history
  - [x] kept live scratch-Postgres reset drills runnable while making their
    non-production reference seed opt-in explicit at the Makefile boundary
  - [x] made the live outbox-metrics smoke use the same disposable database,
    reset guard, and non-production seed opt-in path as the other live reset
    drills before it can apply schema or seed deterministic outbox rows
- [x] update NATS worker authorization so approved subscribers are loaded from
  Postgres and constrained by DB-managed subject allowlists
  - [x] added operator-managed subscriber registration/listing to feed the
    Postgres-backed allowlist used by the worker
  - [x] preserved the full DB-approved subject allowlist in the governance
    subscriber's JetStream consumer filters so multi-family subscribers do not
    silently consume only the first authorized subject
  - [x] kept local compose credentials service-specific for NATS workers so
    DB-managed subject authorization is not undermined by broad shared broker
    credentials in the development deployment path
  - [x] constrained governance and runtime subscriber workers to the subject
    families owned by their JetStream streams, and made runtime replay durable
    ownership come from the Postgres-approved subscriber record
- [x] add live drills for management transaction atomicity, idempotent mutation,
  issuer enrollment to publication, NATS subscriber allow/deny, and outbox
  retry after broker outage
  - [x] added `qrtrustctl management-live-drill` to exercise precondition
    failure, idempotent replay, managed issuer-to-policy publication, NATS
    allow/deny behavior, and optional failed-outbox retry through `/admin/*`
  - [x] expanded the live-drill report to show both approved subscriber lanes
    (governance state and runtime observations) before proving broad subject
    grants are rejected
  - [x] verified the drill against the local HTTPS admin stack with explicit
    local-only self-signed TLS handling
  - [x] added strict broker-outage retry evidence mode so production drill runs
    fail instead of silently skipping outbox remediation when no failed event is
    supplied
  - [x] added a reset-guarded live outbox retry drill that fails real Postgres
    outbox rows under simulated broker outage, recovers NATS, and proves the
    same rows publish after retry

Container and node plan:
- [x] no new database technology is required; Postgres remains the only source
  of truth
- [x] no new broker technology is required; NATS remains propagation transport
  for event envelopes and artifact references
- [x] add an optional management CLI container only when scripted deployment
  evidence needs a containerized operator command surface
  - [x] added an optional `management-tools` Compose profile and
    `qrtrustctl-container-help` target so scripted operator commands can run in
    the packaged backend runtime without adding a direct database writer
- [x] add worker services only as logical nodes on the existing network image:
  `network-event-outbox-worker`, `network-governance-subscriber-worker`, and
  `network-runtime-subscriber-worker`
  - [x] exposed `network-governance-subscriber-worker` through the NATS compose
    overlay, Makefile start/log targets, and run guide so approved subscriber
    materialization can be operated without hand-editing Docker commands
  - [x] added `network-runtime-subscriber-worker` to consume DB-authorized
    `runtime.verdict.observed` envelopes, fetch and hash-check runtime
    observation artifacts, and persist normalized runtime-observation rows
  - [x] updated the HTTPS shared-infra NATS target to start the outbox,
    governance subscriber, and runtime subscriber workers together so the
    production-like local stack includes the full propagation path
- [x] add an optional second verifier-node profile only for federation,
  stale-cache, subscriber authorization, or cross-root contradiction demos
  - [x] added an opt-in `verifier-federation` Compose profile with
    `api-verifier-b`, isolated `QRTRUST_SCANNER_VERIFIER_ID`, separate Redis DB,
    and Make targets for starting and tailing the secondary verifier node
- [x] keep the production note: stronger but heavier NATS accounts, JWT, and
  NKeys are closer to production multi-tenant governance, but that is a larger
  setup and should follow after the PoC's Postgres-source-of-truth and
  DB-approved subscriber flow works end to end


## Scanner UX Friction Layer

Goal: reduce blind opens without changing the core trust model.

Principles:
- hold-to-open is a friction control, not a trust mechanism
- green results stay fast and normal
- amber and red results expose concise reason codes and may require a short hold before opening
- random plain URL QR codes should usually be amber, not red, unless a concrete high-risk signal exists
- the final decision remains with the user, while the scanner makes issuer legitimacy, destination binding, runtime safety, and decision state visible

Implementation slices:
- [x] backend scanner decision responses include an additive `scanner_ux` block
- [x] reason codes map back to trust-model layers instead of replacing them
- [x] iOS result cards show risk stripe, domain display, reason summary, and hold-to-open for flagged opens
- [x] React lab mirrors the same risk stripe and reason codes for classroom/reviewer parity
- [x] heuristic scoring expands beyond trust-model state to include caption/domain mismatch, known-bad or new domains, suspicious URL syntax, redirect depth, HTTPS posture, and first-seen domain history
- [x] deterministic local scanner scoring covers embedded URL credentials, suspicious domain endings, HTTPS absence, and redirect depth without external domain-intelligence calls
- [x] acceptance logs include preview, hold start, hold completion, opened/cancelled, score, and reason codes
- [x] local event export returns recent scanner UX events for reviewer/demo evidence packets
- [x] seeded A/B fixture endpoint returns control vs treatment logs and acceptance metrics for reviewer demos

## Active Public Files

- `.github/`
- `backend/app/services/payload_revalidation_poc.py`
- `backend/app/services/signed_schema_poc.py`
- `backend/app/services/narrowed_verifier_poc.py`
- `backend/app/services/qr_artifact_poc.py`
- `backend/app/services/scanner_ux_ab_fixture.py`
- `backend/tests/`
- `frontend/`
- `ios/`
- `backend/scripts/payload_revalidation_poc_demo.py`
- `backend/scripts/signed_schema_poc_demo.py`
- `backend/scripts/narrowed_verifier_poc_demo.py`
- `backend/scripts/qr_artifact_poc_demo.py`
- `backend/scripts/capture_react_lab_evidence.py`
- `backend/app/api/endpoints/verifier.py`
- `frontend/src/App.tsx`
- `frontend/src/lib/verifier-client.ts`
- `frontend/vite.config.ts`
- `frontend/Dockerfile`
- `backend/Dockerfile`
- `network/src/services/governance-publication.ts`
- `network/src/services/authority-publication.ts`
- `network/src/services/postgres-governance-publication-source.ts`
- `network/src/programs/governance-publication-plan-smoke.ts`
- `network/src/programs/authority-publication-service-smoke.ts`
- `network/src/programs/authority-publication-live-outbox-drill.ts`
- `network/src/programs/verifier-cache-live-read-model-drill.ts`
- `network/src/programs/scanner-decision-live-cache-drill.ts`
- `network/src/programs/ios-provider-profile-evidence-artifacts-status.ts`
- `network/src/programs/postgres-reference-seed.ts`
- `network/src/programs/postgres-governance-publication-source-smoke.ts`
- `network/src/services/postgres-verifier-cache.ts`
- `compose.yml`
- `scripts/compose_workbench_smoke.sh`
- `scripts/iphone_evidence_check.sh`
- `scripts/ios_provider_profile_evidence_packet.sh`
- `scripts/ios_provider_profile_evidence_status.sh`
- `scripts/import_ios_provider_profile_evidence.sh`
- `scripts/ios_provider_profile_evidence_check.sh`
- `scripts/ios_harness_smoke.sh`
- `scripts/public_release_audit.sh`
- `docs/public/OPEN_SOURCE_DIRECTION.md`
- `docs/public/PUBLIC_RELEASE_CHECKLIST.md`
- `docs/public/RELEASE_CANDIDATE_STATUS.md`
- `docs/public/RUN_GUIDE.md`
- `docs/public/VERIFIER_PROFILE.md`
- `docs/public/TEST_VECTORS.md`
- `docs/public/BROWSER_TEST_MATRIX.md`
- `docs/public/network-contracts/production-adoption-gap-map.md`
- `docs/public/network-contracts/ios-provider-profile-evidence.md`
- `docs/public/network-contracts/ios-provider-profile-evidence.schema.json`
- `docs/public/network-contracts/examples/ios-provider-profile-evidence-reference.json`
- `docs/public/evidence/README.md`
- `docs/public/evidence/iphone/README.md`
- `SECURITY.md`
- `CONTRIBUTING.md`
- `SUPPORT.md`
- `LICENSE`

## Local-Only Material

The following are intentionally outside the tracked repo boundary:
- `private/`
- `archive/`