Add secure credential storage, sessions, and a launch screen

Login now persists properly rather than through the JSON file the test
harness was using.

  dr-plat            SecretStore trait plus a Secret Service backend.
                     Verified against the live GNOME Keyring: store,
                     retrieve, delete, confirm-gone all round-trip.
  Session/SessionStore   splits credentials from settings — the app
                     password goes to the keyring (FR-NC-2), while
                     server, login, chosen root and format selection are
                     ordinary config. A test asserts the credential never
                     appears in the config file.
  LaunchModel        the launch-screen state machine, testable without a
                     display server: sign in, approve in browser, choose
                     folder, tick formats, sign out.
  launch.slint       the screen itself, in its own file.

Absence of a secrets daemon is an explicit degraded mode, not a silent
fallback to plaintext — the screen says sign-in will not persist rather
than letting the user find out next launch. Android's Keystore backend
fails loudly for the same reason: a no-op store would look like it
worked and then lose the credential.

Two bugs caught by tests rather than by running it:

  - fail() after busy() signed the user out, because busy() had already
    discarded the session. A failed *scan* would have logged you out.
    Busy now carries the session.
  - normalise_server upgrades http:// to https:// rather than accepting
    it. NFR-SEC-3 requires TLS, and silently sending a credential in the
    clear is not a decision to make on the user's behalf.

launch.slint is not yet wired into app.slint. Calling slint_build::compile
twice replaces the generated module rather than adding to it, which broke
the other in-flight work on dr-ui; I reverted that immediately. Wiring it
needs an import inside app.slint, which is that work's file to change.

419 tests passing across ten crates.
This commit is contained in:
2026-08-09 15:20:39 +02:00
parent c8bb08e661
commit 09e3043f4c
33 changed files with 3506 additions and 155 deletions
+34 -34
View File
@@ -9,18 +9,18 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n
| Metric | Value |
|---|---|
| Source files scanned | 44 |
| TRACES tags found | 37 |
| Source files scanned | 63 |
| TRACES tags found | 49 |
| Requirements defined | 143 |
| Requirements covered | 36 |
| **Coverage** | **25.2%** (36/143) |
| Requirements covered | 49 |
| **Coverage** | **34.3%** (49/143) |
### By type
| Type | Covered | Defined |
|---|---|---|
| FR | 27 | 90 |
| NFR | 7 | 47 |
| FR | 36 | 90 |
| NFR | 11 | 47 |
| R | 2 | 6 |
## Orphan tags
@@ -33,36 +33,49 @@ _None._
| ID | Tagged in |
|---|---|
| FR-CAT-1 | [`tools/traceability/src/lib.rs:473`](../tools/traceability/src/lib.rs#L473), [`tools/traceability/src/lib.rs:505`](../tools/traceability/src/lib.rs#L505) |
| FR-CAT-1a | [`core/dr-types/src/lib.rs:23`](../core/dr-types/src/lib.rs#L23) |
| FR-CAT-2 | [`tools/traceability/src/lib.rs:473`](../tools/traceability/src/lib.rs#L473) |
| FR-CAT-1 | [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-sync/src/scan.rs:50`](../core/dr-sync/src/scan.rs#L50), [`core/dr-types/src/lib.rs:174`](../core/dr-types/src/lib.rs#L174), [`tools/traceability/src/lib.rs:473`](../tools/traceability/src/lib.rs#L473), [`tools/traceability/src/lib.rs:505`](../tools/traceability/src/lib.rs#L505) |
| FR-CAT-1a | [`core/dr-types/src/lib.rs:36`](../core/dr-types/src/lib.rs#L36) |
| FR-CAT-2 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1), [`tools/traceability/src/lib.rs:473`](../tools/traceability/src/lib.rs#L473) |
| FR-CAT-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1) |
| FR-CAT-4 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1) |
| FR-CAT-5 | [`core/dr-decode/src/lib.rs:216`](../core/dr-decode/src/lib.rs#L216) |
| FR-CAT-9 | [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-types/src/lib.rs:80`](../core/dr-types/src/lib.rs#L80) |
| FR-CAT-6 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1) |
| FR-CAT-7 | [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1) |
| FR-CAT-9 | [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-types/src/lib.rs:93`](../core/dr-types/src/lib.rs#L93) |
| FR-CULL-1 | [`core/dr-decode/src/preview.rs:96`](../core/dr-decode/src/preview.rs#L96) |
| FR-CULL-2 | [`core/dr-decode/src/preview.rs:123`](../core/dr-decode/src/preview.rs#L123) |
| FR-DEV-3a | [`core/dr-pipeline/src/graph.rs:14`](../core/dr-pipeline/src/graph.rs#L14), [`core/dr-pipeline/src/graph.rs:32`](../core/dr-pipeline/src/graph.rs#L32), [`core/dr-pipeline/src/graph.rs:88`](../core/dr-pipeline/src/graph.rs#L88) |
| FR-DEV-3b | [`core/dr-pipeline/src/graph.rs:32`](../core/dr-pipeline/src/graph.rs#L32) |
| FR-DEV-3c | [`core/dr-pipeline/src/graph.rs:88`](../core/dr-pipeline/src/graph.rs#L88) |
| FR-DEV-3e | [`core/dr-decode/src/lib.rs:325`](../core/dr-decode/src/lib.rs#L325), [`core/dr-decode/src/lib.rs:439`](../core/dr-decode/src/lib.rs#L439) |
| FR-DEV-3 | [`core/dr-pipeline/src/framing.rs:174`](../core/dr-pipeline/src/framing.rs#L174) |
| FR-DEV-3a | [`core/dr-pipeline/src/graph.rs:128`](../core/dr-pipeline/src/graph.rs#L128), [`core/dr-pipeline/src/graph.rs:15`](../core/dr-pipeline/src/graph.rs#L15), [`core/dr-pipeline/src/graph.rs:33`](../core/dr-pipeline/src/graph.rs#L33) |
| FR-DEV-3b | [`core/dr-pipeline/src/graph.rs:33`](../core/dr-pipeline/src/graph.rs#L33) |
| FR-DEV-3c | [`core/dr-pipeline/src/graph.rs:128`](../core/dr-pipeline/src/graph.rs#L128) |
| FR-DEV-3d | [`core/dr-pipeline/src/framing.rs:174`](../core/dr-pipeline/src/framing.rs#L174) |
| FR-DEV-3e | [`core/dr-decode/src/lib.rs:325`](../core/dr-decode/src/lib.rs#L325), [`core/dr-decode/src/lib.rs:445`](../core/dr-decode/src/lib.rs#L445) |
| FR-DEV-4 | [`core/dr-gpu/src/lib.rs:123`](../core/dr-gpu/src/lib.rs#L123) |
| FR-DSP-1 | [`ui/dr-ui/src/lib.rs:31`](../ui/dr-ui/src/lib.rs#L31) |
| FR-EXP-9 | [`core/dr-decode/src/lib.rs:242`](../core/dr-decode/src/lib.rs#L242) |
| FR-NC-1 | [`core/dr-sync-nextcloud/src/auth.rs:132`](../core/dr-sync-nextcloud/src/auth.rs#L132), [`core/dr-sync-nextcloud/src/auth.rs:44`](../core/dr-sync-nextcloud/src/auth.rs#L44) |
| FR-NC-12 | [`core/dr-sync-nextcloud/src/lib.rs:32`](../core/dr-sync-nextcloud/src/lib.rs#L32), [`core/dr-sync/src/lib.rs:128`](../core/dr-sync/src/lib.rs#L128), [`core/dr-sync/src/lib.rs:34`](../core/dr-sync/src/lib.rs#L34) |
| FR-NC-12 | [`core/dr-sync-nextcloud/src/lib.rs:34`](../core/dr-sync-nextcloud/src/lib.rs#L34), [`core/dr-sync/src/lib.rs:130`](../core/dr-sync/src/lib.rs#L130), [`core/dr-sync/src/lib.rs:36`](../core/dr-sync/src/lib.rs#L36) |
| FR-NC-3 | [`core/dr-decode/src/preview.rs:123`](../core/dr-decode/src/preview.rs#L123), [`core/dr-sync/src/capability.rs:41`](../core/dr-sync/src/capability.rs#L41) |
| FR-NC-4 | [`core/dr-sync-nextcloud/src/propfind.rs:100`](../core/dr-sync-nextcloud/src/propfind.rs#L100), [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`core/dr-sync/src/capability.rs:6`](../core/dr-sync/src/capability.rs#L6), [`core/dr-sync/src/lib.rs:128`](../core/dr-sync/src/lib.rs#L128) |
| FR-NC-4 | [`core/dr-sync-nextcloud/src/propfind.rs:100`](../core/dr-sync-nextcloud/src/propfind.rs#L100), [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`core/dr-sync/src/capability.rs:6`](../core/dr-sync/src/capability.rs#L6), [`core/dr-sync/src/lib.rs:130`](../core/dr-sync/src/lib.rs#L130), [`core/dr-sync/src/scan.rs:50`](../core/dr-sync/src/scan.rs#L50) |
| FR-NC-5 | [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51) |
| FR-NC-6c | [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-types/src/lib.rs:131`](../core/dr-types/src/lib.rs#L131), [`core/dr-types/src/lib.rs:80`](../core/dr-types/src/lib.rs#L80) |
| FR-PLAT-AND-1 | [`core/dr-types/src/lib.rs:23`](../core/dr-types/src/lib.rs#L23) |
| FR-RAW-1 | [`core/dr-decode/src/lib.rs:174`](../core/dr-decode/src/lib.rs#L174), [`core/dr-types/src/lib.rs:90`](../core/dr-types/src/lib.rs#L90) |
| FR-NC-6a | [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1) |
| FR-NC-6c | [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-types/src/lib.rs:175`](../core/dr-types/src/lib.rs#L175), [`core/dr-types/src/lib.rs:93`](../core/dr-types/src/lib.rs#L93) |
| FR-NC-9 | [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1) |
| FR-PLAT-AND-1 | [`core/dr-types/src/lib.rs:36`](../core/dr-types/src/lib.rs#L36) |
| FR-PLAT-AND-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1) |
| FR-RAW-1 | [`core/dr-decode/src/lib.rs:174`](../core/dr-decode/src/lib.rs#L174), [`core/dr-types/src/lib.rs:103`](../core/dr-types/src/lib.rs#L103), [`core/dr-types/src/lib.rs:174`](../core/dr-types/src/lib.rs#L174) |
| FR-RAW-3 | [`core/dr-decode/src/lib.rs:242`](../core/dr-decode/src/lib.rs#L242), [`core/dr-decode/src/lib.rs:70`](../core/dr-decode/src/lib.rs#L70) |
| FR-RAW-4 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1) |
| FR-RAW-5 | [`core/dr-decode/src/lib.rs:98`](../core/dr-decode/src/lib.rs#L98) |
| FR-UI-1 | [`ui/dr-ui/src/lib.rs:39`](../ui/dr-ui/src/lib.rs#L39) |
| FR-UI-2 | [`ui/dr-ui/src/lib.rs:39`](../ui/dr-ui/src/lib.rs#L39) |
| NFR-ARCH-2 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1) |
| NFR-ARCH-4 | [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1) |
| NFR-OPS-1 | [`tools/traceability/src/lib.rs:266`](../tools/traceability/src/lib.rs#L266) |
| NFR-P1 | [`tools/traceability/src/lib.rs:473`](../tools/traceability/src/lib.rs#L473) |
| NFR-P1 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`tools/traceability/src/lib.rs:473`](../tools/traceability/src/lib.rs#L473) |
| NFR-P13 | [`core/dr-decode/src/preview.rs:96`](../core/dr-decode/src/preview.rs#L96) |
| NFR-R1 | [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1) |
| NFR-R5 | [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1) |
| NFR-R7 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) |
| NFR-R8 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) |
| NFR-RES-1 | [`ui/dr-ui/src/lib.rs:31`](../ui/dr-ui/src/lib.rs#L31) |
@@ -72,7 +85,7 @@ _None._
## Not yet tagged
107 of 143 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built.
94 of 143 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built.
<details><summary>Show untagged requirements</summary>
@@ -81,10 +94,6 @@ _None._
- FR-CAT-12
- FR-CAT-13
- FR-CAT-14
- FR-CAT-3
- FR-CAT-4
- FR-CAT-6
- FR-CAT-7
- FR-CAT-8
- FR-CULL-3
- FR-CULL-4
@@ -93,8 +102,6 @@ _None._
- FR-CULL-7
- FR-DEV-1
- FR-DEV-2
- FR-DEV-3
- FR-DEV-3d
- FR-DEV-3f
- FR-DEV-3g
- FR-DEV-5
@@ -120,13 +127,10 @@ _None._
- FR-NC-11
- FR-NC-2
- FR-NC-6
- FR-NC-6a
- FR-NC-6b
- FR-NC-7
- FR-NC-8
- FR-NC-9
- FR-PLAT-AND-2
- FR-PLAT-AND-3
- FR-PLAT-AND-4
- FR-PLAT-AND-5
- FR-PLAT-AND-6
@@ -143,9 +147,7 @@ _None._
- NFR-A11Y-2
- NFR-A11Y-3
- NFR-ARCH-1
- NFR-ARCH-2
- NFR-ARCH-3
- NFR-ARCH-4
- NFR-COMPAT-1
- NFR-COMPAT-2
- NFR-OPS-2
@@ -167,11 +169,9 @@ _None._
- NFR-PORT-1
- NFR-PORT-2
- NFR-PORT-3
- NFR-R1
- NFR-R2
- NFR-R3
- NFR-R4
- NFR-R5
- NFR-R6
- NFR-RES-2
- NFR-RES-3