Appearance
Keryx requirements-to-test traceability
Approved: 17 September 2026
This document is the P4-01 coverage packet for Keryx for SafeCall. It lists launch-relevant requirements, maps each to evidence class and current path, defines per-commit / per-release / per-launch cadence, and records regression ownership.
It does not claim that VoiceOver/TalkBack or OEM soak passed, that store-track testers approved a freeze build, or that #112 signed compile/SBOM/Swift CI exists. An unsigned GitLab CI first slice is documented in ci.md. An in-repo accessibility slice is accessibility.md.
Purpose and non-goals
Purpose
- Give G3 (client readiness) a single requirements-to-test map.
- Record what is already proven in-repo versus what remaining GitLab items own.
- Tell #112 which checks to run. The unsigned first slice is ci.md; signed compile, SBOM, and Swift remain on that issue.
- Open implementation issues only for automated gaps that existing P4/P5 items do not already own.
Non-goals
- Adding signed compile, SBOM, or a Mac runner (#112 residual; unsigned YAML is ci.md).
- Completing VoiceOver/TalkBack or a WCAG pass/fail (#116 closed 20 September 2026 without an AT/WCAG claim; in-repo denial UI is accessibility.md).
- Running the physical OEM / killed-state / Doze matrix (#117).
- Proving upgrade, reboot, cache, rescan, or server migration (#118).
- Closing the phone-side guide (#121), client pilot (#122), or TestFlight / Play Internal soak (#124).
- Treating Nav
bun test:contractsas Keryx QR coverage.
Approved decisions
| Topic | Approved position |
|---|---|
| Owner and approver | Thomas Minitsios, 17 September 2026 (P0-05 RACI) |
| Policy source | Existing GTM packets only; this close does not invent product, privacy, or store rules |
| Evidence classes | automated (Dart / Bun / Playwright), native (Kotlin / Swift unit), manual-device, soak, pilot, external |
| G3 automated bar | Priority slice below must have automated or native evidence, or an open implementation issue, before go/no-go |
| CI consumption | Unsigned first slice (ci.md) runs the per-commit set plus bun test:keryx-native --android. Signed compile, SBOM, and Swift remain #112 |
| Secrets in evidence | GitLab notes and NAS evidence/keryx/ hold checksums and pass/fail only. No QR photos, tk_… tokens, or client names |
Evidence classes
| Class | Meaning | Typical command or record |
|---|---|---|
| automated | Headless, repeatable, no phone | cd keryx && flutter test; cd server && bun test; bun test:e2e (mobile-access.spec.ts) |
| native | Vendored ntfy JVM / XCTest | bun test:keryx-native (both); --android / --ios slices |
| manual-device | Operator on a supported phone/tablet | manual-testing.md §2.7; #117/#118 scripts |
| soak | Store-installed candidate over duration | #124 / #117 long-running rows |
| pilot | Named client administrators | #122 |
| external | Counsel, store forms, live HTTPS | #107 deploy, #125 console, OA-legal-cs |
Current inventory (17 September 2026)
GitLab #115 still said “three Dart test files.” The tree is larger:
| Surface | What exists |
|---|---|
Dart (keryx/test/) | 11 files: QR v2 parse/reject, config store (locale survives reset), notification store and archive filters, alert state/policy, envelope parse against shared fixtures, recover, notification-open, delivery-log redaction, fuzzy search, widget/l10n (onboarding, pause, jargon hiding, Czech ARB keys, category cards) |
| Shared fixtures | lib/src/ntfy_alert_envelope.fixtures.json (Dart + TypeScript parsers); lib/src/keryx_qr.fixtures.json (Dart KeryxConfig.fromQrPayload + server KeryxQrPayloadSchema) |
| Native | Kotlin under keryx/vendor/ntfy/android/src/test/kotlin/io/karsmiths/ntfy/; Swift under keryx/vendor/ntfy/ios/ntfy/Tests/ntfyTests/ (policy gate, delivery, FCM wake, poll catch-up, notification open, delivery log, tags) |
| Server | server/tests/api/keryx-qr.test.ts, server/tests/keryx_ntfy.test.ts |
| Web E2E | web/tests/e2e/mobile-access.spec.ts: operational categories default on, admin features collapsed, v2 QR, enlarge without subscribe_auth in page text |
| Manual | manual-testing.md §2.7 Mobile Access |
| Release | bun run release:keryx runs flutter analyze + flutter test; --contracts runs Ariadne bun test:contracts, not a Keryx QR golden |
| Native harness | bun test:keryx-native (#160 closed). --android is the Linux CI slice |
| CI | Unsigned first slice in .gitlab-ci.yml / ci.md: keryx:dart, keryx:server, keryx:native-android. #112 stays open for signed compile, SBOM, Swift/macOS |
Requirement catalog and evidence map
Owners are GitLab work items. G3 means the row must have recorded evidence (or an accepted risk in the roadmap) before gate G3. Closing this packet does not complete G3.
Product
| ID | Requirement | Source | Class | Current evidence | Gap | Owner | G3 |
|---|---|---|---|---|---|---|---|
| P-01 | Administrator-only companion; no generic end-user product | P0-01 / strategy | automated + manual-device + pilot | Widget onboarding copy; English app-onboarding-guide.md | Representative walk OA-121-app-walk (done 19 September 2026); Czech/live web remain on #121 | #121 / #122 | Yes |
| P-02 | Friendly alert categories; QR hides server URL, topic IDs, and subscribe_auth from ordinary UI | P0-03, P1-02 | automated | Widget: subscribed cards hide https://notify.example/ and TST000-sos; standard notification detail hides Topic / ntfy message ID / raw JSON. E2E: enlarge QR does not expose subscribe_auth | Web E2E does not assert ordinary Mobile Access copy avoids “ntfy” / “topic” jargon | #160 | Yes |
| P-03 | Scan-on-own-device QR; no manual ntfy configuration | P0-03 | automated + manual-device | Widget shows Scan QR code when unconfigured; E2E generates v2 QR from Mobile Access | No test drives mobile_scanner or injects a scanned payload through the camera callback | #160 mocked ingest; physical scan #117 / OA-121-app-walk | Yes |
| P-04 | English + Czech UI parity for Keryx and admin provisioning | P0-02 / localization | automated | Czech ARB key parity; Czech onboarding and stateful-card widgets; web catalogs from #108 | Czech Mobile Access E2E labels; live Czech HTTPS #107 residual | #160 E2E CS; #121 leftover web | Yes |
| P-05 | Pause / resume is local and obvious | P1-10 / #121 | automated | Settings pause switch widget; home shows Notifications are paused | Persistence across process death is #118 | #118 | Yes |
| P-06 | Reset App clears config and history, not the language preference; does not revoke the ntfy token | P1-05, #120/#121 | automated + external | keryx_config_store_test locale survives clear(); pause resets to false | No widget for the Reset App confirmation path; revoke remains SafeCall/ntfy host | #160; revoke is host-side (#120 handbook) | Yes |
| P-07 | Debug / test categories stay behind admin warning; ordinary QR has no debug | P1-01 A12 / P1-02 | automated | E2E: admin features collapsed; Test and debug unchecked; warning modal on Test or debug | None for this control | #115 inventory (covered) | Yes |
Security and privacy
| ID | Requirement | Source | Class | Current evidence | Gap | Owner | G3 |
|---|---|---|---|---|---|---|---|
| S-01 | HTTPS-only ntfy URL; reject HTTP | P1-02 | automated | Dart rejects http server URLs; server keryx_ntfy_https_required tests | None for parser | #115 inventory (covered) | Yes |
| S-02 | QR is safecall.keryx.ntfy.v2 with subscribe_auth; reject v1 and non-Keryx types | P1-02 / versioning | automated | Shared golden lib/src/keryx_qr.fixtures.json: Dart fromQrPayload plus Valibot KeryxQrPayloadSchema; POST /keryx/qr key-set must match the golden; E2E asserts payload type | Minted subscribe_auth / token_id / created_at are dynamic (not byte-for-byte). Mocked scan ingest is #160 | #159 closed 17 September 2026 | Yes |
| S-03 | Subscribe token is read-only, per QR; publish token never in QR | P1-02 | automated | Server mint/revoke tests; E2E enlarge redaction | Physical token ACL on client ntfy | #119 (closed dual-run); deny-all OA-119-ntfy-cutover later | Yes |
| S-04 | Ordinary UI never shows subscribe_auth or raw topic IDs | P1-05 | automated | Widget jargon hiding; delivery-log redaction test | Camera-result / paste error paths | #160 | Yes |
| S-05 | History cap 200; tombstones suppress cache reintroduction | P1-05 | automated | Notification store cap, archive, deleted-not-reintroduced, recover tests | Upgrade of stored JSON shape | #118 | Yes |
| S-06 | Full lock-screen title+body is the launch policy (disclose, do not silently redact) | P1-05 / P1-07 / P1-08 | external + soak | Policy packets; first-device swipe-away on 1.2.1 (8) | Nutrition label / Data Safety paste; OEM lock-screen variants | #125 / #117 | Yes |
| S-07 | No analytics or crash SDK at launch | P0-05 / P1-09 | automated + external | dependency-ownership inventory; pubspec has no analytics SDK | Machine SBOM / CI advisory gate | #112 | Yes |
| S-08 | Debug metadata only when debug_access | P1-05 | automated | Widget standard vs debug notification detail | None material | #115 inventory (covered) | Yes |
Platform and delivery
| ID | Requirement | Source | Class | Current evidence | Gap | Owner | G3 |
|---|---|---|---|---|---|---|---|
| D-01 | No perpetual Android dataSync FGS | P1-03 | native + manual-device | Architecture + code note 13 September 2026; Kotlin tests exist for wake/delivery | Native tests not in flutter test; physical FGS absence | #160 harness; #117 | Yes |
| D-02 | Android FCM poll_request / message → authenticated poll → local notification | P1-03 | native + soak | Kotlin NtfyFirebaseWakeTest, NtfyDeliveryTest; operator first-device proof | Harness + OEM/Doze/killed-state matrix | #160; #117 / #124 | Yes |
| D-03 | iOS APNs / poll_request catch-up ingest; generic wake does not spam locals | P1-04 | native + soak | Swift NtfyPollCatchUpTests, NtfyRemoteWakeTests, NtfyNotificationOpenTests; Dart open/recover tests | Harness + physical iPhone matrix | #160; #117 / #124 | Yes |
| D-04 | Delivery is supplemental; Focus/DND/force-stop/OEM battery can suppress | P1-10 | manual-device + soak | Claims packet; handbooks | Recorded device matrix | #117 / #121 | Yes |
| D-05 | Foreground stream while the app is open; pause stops presentation | P1-03 / #121 | automated + manual-device | Pause widgets; stream service used in widget harness | Live SSE against ntfy | #117 | Yes |
| D-06 | Camera + notification permission denial recovers via OS Settings | P0-04 | automated + manual-device | Widget overlays + Settings CTA (accessibility.md) | Physical first/permanent deny accepted out of scope 20 September 2026 | #116 closed | No |
| D-07 | iOS 17+ / Android API 31+; phone and tablet orientations | P0-04 | manual-device + external | Policy; source floors may still be lower | Align source + listings; physical orientations | #117 / #125 | Yes |
| D-08 | Tier 1 Pixel/AOSP + Samsung; Tier 2 only with evidence | P0-04 | soak | None | Device list and results | #117 | Yes |
Store, support, and compatibility
| ID | Requirement | Source | Class | Current evidence | Gap | Owner | G3 |
|---|---|---|---|---|---|---|---|
| C-01 | WCAG 2.2 AA critical flows; VoiceOver/TalkBack; 200% text; contrast; 44 pt / 48 dp | P0-04 | manual-device + automated | Category-card semantics; scanner/scan/pause/reset names; 200% widget tests (accessibility.md) | AT/WCAG device evidence declined 20 September 2026 (trained hospital/admin operators; no conformance claim) | #116 closed | No |
| C-02 | Czech Republic only; Business; not a medical device | P0-02 / P1-10 | external | Listing-copy packet | Console paste | #125 | Yes |
| C-03 | SafeCall-channel support; no standalone Keryx SLA | P0-05 | external + pilot | Handbooks #120/#121 | OA-121-app-walk; pilot | #121 / #122 | Yes |
| C-04 | Store-like upgrade preserves config and history when QR schema is unchanged | versioning | manual-device | None | Upgrade / package-replace | #118 | Yes |
| C-05 | Reboot, offline, cache expiry, network change, clock skew, rescan, server migration | P4-04 | manual-device | Recover helpers unit-tested | Physical and store-track | #118 / #124 | Yes |
| C-06 | Sideload downgrade unsupported; stores do not roll back | versioning | external | Policy only | Support wording at launch | #121 / #130 | No |
| C-07 | Reviewer QR + isolated HTTPS ntfy | P5-01 | external | In-repo packet reviewer-environment.md | Live ACL, {public_base}/keryx/review, durable QR, off-LAN proof | #123 residual; OA-123-review-host | Gate G4 |
| C-08 | Client administrators provision without transport help | P4-08 | pilot | Handbook walk OA-120-admin-walk (install+scan) | Full pilot scenarios | #122 | Yes |
Priority slice (GitLab #115 AC)
These rows are the issue’s named priorities. Cadence below must keep them green or explicitly owned.
| Priority | IDs | Automated/native today | Residual owner |
|---|---|---|---|
| QR / auth / versioning | S-01, S-02, S-03, P-03 | Shared QR golden + Dart reject/parse; server mint key-set; E2E v2 type | Mocked scan ingest #160; physical scan #117 |
| Persistence / migration | S-05, P-06, C-04 | Store cap, tombstones, locale-survives-reset | Reset App widget #160; upgrade #118 |
| Lifecycle / tombstones | S-05, D-05 | Store + alert policy + recover tests | Soak #117/#124 |
| Debug gating | P-07, S-08 | E2E warning; widget debug vs ordinary detail | None material |
| Permissions | D-06, C-01 | Denial overlays + Settings CTA + 200% tests (accessibility.md) | AT/WCAG device evidence declined (#116 closed 20 September 2026) |
| Notifications | D-02, D-03, D-04, S-06 | Dart open/envelope; native tests on disk | Native harness #160; #117 |
| Native services | D-01, D-02, D-03 | Kotlin + Swift unit tests + bun test:keryx-native | Linux CI --android; Swift remains local / future macOS job (#112) |
| Contracts | S-02, envelope fixtures | Shared alert body and QR payload goldens (lib/src/*.fixtures.json); cd keryx && flutter test + cd server && bun test | Nav test:contracts is out of scope |
Cadence and retention
Per-commit (local and unsigned GitLab CI)
Run when touching keryx/**, vendored ntfy, SafeCall Keryx QR, or alert envelopes:
bash
cd keryx && flutter analyze && flutter test
cd server && bun testGitLab (keryx:dart, keryx:server, keryx:native-android) also runs bun test:keryx-native --android. Packet: ci.md.
Also run bun test:e2e (or at least mobile-access.spec.ts) when web/** or the Keryx QR HTTP contract changes.
cd keryx && flutter test and cd server && bun test cover S-02 (shared QR golden parse + mint key-set). Do not use bun test:contracts as Keryx evidence. That orchestrator writes SafeCall Nav fixtures and runs ariadne/test/contract_test.dart.
Per-release (bun run release:keryx / bun keryx)
- Per-commit set above (
flutter analyze+flutter testalready in the script). - Signed AAB + IPA verify (
verify:keryx-aab,verify:keryx-ipa). - Native ntfy unit tests:
bun test:keryx-native(Android + Swift locally). Linux CI already ran--android. - Optional
--contractsremains Nav-only; record it as Nav, not Keryx QR.
Copy keryx/build/release-evidence/*.txt to NAS evidence/keryx/.
Per-launch / G3
| Item | Owner |
|---|---|
| Accessibility and permission recovery | #116 closed 20 September 2026 (no WCAG/AT claim) |
| Real-device / OEM / delivery matrix | #117 |
| Upgrade / reboot / cache / rescan | #118 |
| Client pilot | #122 |
| Store-track soak and tester approval | #124 |
G4 reviewer environment is #123 (not G3). In-repo packet: reviewer-environment.md. Live host is OA-123-review-host. Console paste is #125.
Retention
| Record | Where |
|---|---|
| Automated logs / checksums | GitLab notes on the owning issue; NAS evidence/keryx/ |
| Device matrix results | NAS evidence/keryx/ + #117/#118/#124 notes |
| Release evidence template | templates/release-record.md “CI and test evidence” line |
Never put QR photographs, tk_… values, keystore passwords, or real client identifiers in git, chat, or tickets.
Follow-up issues
Do not file duplicates of #112, #116, #117, #118, #121, #122, #123, or #124.
Filed with this close:
| Issue | Scope | Launch blocker |
|---|---|---|
| #159 | Done 17 September 2026. Shared lib/src/keryx_qr.fixtures.json: Dart parse + Valibot + minted key-set. Nav test:contracts is not Keryx. | Closed — S-02 / contracts slice |
| #160 | Done 17 September 2026. Mocked QR ingest, Reset App widget, ordinary Mobile Access jargon E2E, bun test:keryx-native. Linux CI consumes --android (ci.md) | Closed — P-02, P-03, P-06, native harness |
Physical OEM delivery and upgrade remain on #117/#118.
Regression ownership
| Change type | Who must keep the map green |
|---|---|
| Keryx Dart/UI | Author runs flutter analyze + flutter test; extends widget tests for new ordinary-admin strings |
QR schema / /keryx/qr | Author updates server tests and lib/src/keryx_qr.fixtures.json (Dart parse + Valibot + minted key-set) |
| Alert envelope | Author updates lib/src/ntfy_alert_envelope.fixtures.json plus Dart and TypeScript parse tests |
| Vendored ntfy native | Author extends Kotlin/Swift tests; runs bun test:keryx-native (or --android / --ios) |
| Delivery architecture | #117 evidence before changing claims or Play/ASC wording |
| Support copy | #120/#121 packets; OA-121-app-walk does not close #121 |
Thomas Minitsios is Accountable (P0-05). Unsigned CI enforces dart + server + Android native; remaining #112 ACs stay on that issue.
Evidence handoff
| Remaining work | Owner |
|---|---|
| Protected CI signed compile, SBOM, license/advisory, Swift/macOS | #112 residual; unsigned slice is ci.md |
| Accessibility + permission denial | #116 closed 20 September 2026; in-repo slice is accessibility.md. No WCAG claim |
| Physical OEM / killed-state / soak delivery | #117 |
| Upgrade / reboot / cache / reprovision | #118 |
| Phone-side guide leftovers (walk, live web, Czech, screenshots) | #121 |
| Client pilot | #122 |
| Reviewer QR environment | #123 residual; in-repo slice is reviewer-environment.md; OA-123-review-host |
| Store-track soak | #124 |
| Privacy-safe monitoring and support intake | #132 residual; in-repo slice is monitoring-intake.md; OA-132-intake-drill done |
| Hotfix, stop-rollout, and rejection | #129 residual; in-repo slice is hotfix-runbook.md; OA-129-hotfix-tabletop done |
Launch posture
Closing #115 approves the map and cadence. It does not claim:
- #112 signed compile, SBOM, or Swift CI (unsigned YAML is ci.md);
- VoiceOver, TalkBack, or WCAG pass (in-repo denial UI is accessibility.md; #116 closed 20 September 2026 without that claim);
- OEM / Doze / killed-state soak;
- store-track tester approval or a frozen build number;
- that #160 was still open (closed 17 September 2026). #159 QR goldens are in-repo.