Files
DarkRoom/docs/traceability.md
T
dtourolleandClaude Opus 5 fa12afed18
Build and test / Desktop (Linux) (push) Failing after 1s
Build and test / Android (aarch64) (push) Failing after 0s
Build and test / Layer separation (push) Failing after 1s
Traceability / Requirement traces (push) Failing after 2s
Keep originals on this device, by pin and by use
Fills in `image_cache`, which the previous commit's "On this device" filter
read but nothing wrote. Also carries in-flight work that shared these files:
the Android TLS root store, the settings page, and a regenerated
traceability report.

# Two populations, deliberately separate

An original is kept here for one of two reasons, and conflating them produces
the exact failure the feature exists to prevent.

**Pinned** originals were asked for. Pinning a collection before a trip is a
promise, so pinned rows are never evicted and never counted against the
budget — a cap that could silently delete a pinned trip would make pinning
worthless, because it could not be relied on without checking.

**Passively cached** originals are a side effect of working: develop already
downloads the whole file, so keeping it costs no bandwidth and saves the
entire transfer next time. This population is what the budget bounds, evicted
least-recently-used, because it otherwise grows until a day of culling fills
a disk.

Sharing one budget would let a large pin starve the passive cache, or let
browsing evict a pin. They are separate.

# What was built

`dr_catalog::cache` owns the bookkeeping — held tier, size, last use, pinned
— and writes the bytes; deciding to download stays with the caller, which is
what keeps a crate with no network out of the network's business. Files are
written to a temporary and renamed, so a dropped connection cannot leave a
truncated file recorded as a complete original. They are named by image id,
not filename: `Photos/IMG_0001.CR2` and `Trips/IMG_0001.CR2` are different
photographs, and a flat cache keyed on the name would serve one for the other.

`spawn_full_fetch` became read-through. A hit is a disk read; a miss stores
what it downloads and enforces the budget. A cache that cannot be opened is a
miss, not a failure to open the photograph.

Pinning writes intent — `tier_desired` — without downloading, so the button
responds immediately, and `spawn_pin_fetch` fills it in sequentially
afterwards. Sequential because these are tens of megabytes each: the lanes
that make the thumbnail sweep fast buy little against one connection's
bandwidth and cost a great deal of memory. A pin interrupted by a lost
connection resumes from where it stopped.

Schema v5 adds `pinned` and `path`. `pinned` is a column rather than something
inferred from `pinned_by_rule`, which is ON DELETE SET NULL and so cannot
answer for an image whose rule was deleted. A v4 catalog migrates in place;
existing rows default to unpinned, the safe direction.

The budget and "keep opened originals" come from the settings page rather than
a constant, and are applied at startup rather than only on change — a cache
capped at 2 GB last session would otherwise spend this one filling to the
default. Turning off keeping leaves what is already cached readable: those
bytes are paid for, and refusing them would re-download images sitting right
there, including pinned ones.

Also removes a doubled `#[test]` introduced in the previous commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 21:12:01 +02:00

17 KiB

Requirements traceability matrix

Denominators are parsed from requirements.md at run time, never hardcoded. Coverage is the intersection of tagged and defined IDs over defined IDs, so it cannot exceed 100%.

Summary

Metric Value
Source files scanned 92
TRACES tags found 130
Requirements defined 149
Requirements covered 69
Coverage 46.3% (69/149)

By type

Type Covered Defined
FR 53 95
NFR 14 48
R 2 6

Orphan tags

A tag naming an ID requirements.md does not define — what renumbering produces, and what a typo produces.

  • FR-CAT-15

Tagged requirements

ID Tagged in
FR-CAT-1 core/dr-catalog/src/scan.rs:1, core/dr-sync/src/scan.rs:93, core/dr-types/src/lib.rs:181, tools/traceability/src/lib.rs:473, tools/traceability/src/lib.rs:505, ui/dr-ui/src/library.rs:1
FR-CAT-11 ui/dr-ui/src/library.rs:138
FR-CAT-12 core/dr-pipeline/src/sidecar.rs:108
FR-CAT-1a core/dr-types/src/lib.rs:43
FR-CAT-2 core/dr-catalog/src/lib.rs:1, core/dr-catalog/src/schema.rs:1, tools/traceability/src/lib.rs:473
FR-CAT-3 core/dr-catalog/src/jobs.rs:1, core/dr-sync/src/scan.rs:69, core/dr-thumbs/src/codec.rs:1, core/dr-thumbs/src/lib.rs:1, ui/dr-ui/src/derived_sync.rs:1
FR-CAT-4 core/dr-catalog/src/lib.rs:1, core/dr-catalog/src/query.rs:1, ui/dr-ui/src/library.rs:1, ui/dr-ui/src/library_ui.rs:1
FR-CAT-5 core/dr-catalog/src/rating.rs:1, core/dr-decode/src/lib.rs:231, core/dr-pipeline/src/sidecar.rs:125
FR-CAT-6 core/dr-catalog/src/collections.rs:1, core/dr-catalog/src/lib.rs:1, core/dr-catalog/src/query.rs:1, core/dr-catalog/src/rating.rs:1, core/dr-types/src/selector.rs:1, ui/dr-ui/src/library.rs:165
FR-CAT-7 core/dr-catalog/src/collections.rs:1, core/dr-catalog/src/merge.rs:1, core/dr-catalog/src/sync.rs:1, core/dr-types/src/selector.rs:1, ui/dr-ui/src/collections_ui.rs:1, ui/dr-ui/src/derived_sync.rs:1, ui/dr-ui/ui/collections.slint:4
FR-CAT-8 core/dr-pipeline/src/sidecar.rs:89, ui/dr-ui/src/library.rs:277
FR-CAT-9 core/dr-catalog/src/cache.rs:1, core/dr-catalog/src/scan.rs:1, core/dr-catalog/src/schema.rs:230, core/dr-sync-nextcloud/src/desktop_client.rs:30, core/dr-sync/src/reachability.rs:1, core/dr-types/src/lib.rs:100, ui/dr-ui/src/library.rs:126, ui/dr-ui/src/library.rs:182, ui/dr-ui/src/library.rs:2118, ui/dr-ui/src/library.rs:987, ui/dr-ui/src/library_ui.rs:1243, ui/dr-ui/src/library_ui.rs:1398, ui/dr-ui/src/library_ui.rs:165, ui/dr-ui/src/library_ui.rs:1683, ui/dr-ui/src/library_ui.rs:1757, ui/dr-ui/src/library_ui.rs:1867, ui/dr-ui/src/library_ui.rs:2712, ui/dr-ui/src/library_ui.rs:2740, ui/dr-ui/src/library_ui.rs:281, ui/dr-ui/src/library_ui.rs:938, ui/dr-ui/src/library_ui.rs:980
FR-CULL-1 core/dr-decode/src/preview.rs:96
FR-CULL-2 core/dr-decode/src/locate.rs:1, core/dr-decode/src/preview.rs:123
FR-CULL-4 core/dr-catalog/src/rating.rs:1, core/dr-pipeline/src/sidecar.rs:125, ui/dr-ui/src/library.rs:165, ui/dr-ui/src/library.rs:277
FR-DEV-3 core/dr-pipeline/src/framing.rs:174
FR-DEV-3a core/dr-pipeline/src/descriptor.rs:91, core/dr-pipeline/src/graph.rs:138, core/dr-pipeline/src/graph.rs:15, core/dr-pipeline/src/graph.rs:39, core/dr-pipeline/src/operation.rs:104
FR-DEV-3b core/dr-pipeline/src/descriptor.rs:91, core/dr-pipeline/src/graph.rs:39, core/dr-pipeline/src/operation.rs:104
FR-DEV-3c core/dr-pipeline/src/graph.rs:138
FR-DEV-3d core/dr-pipeline/src/framing.rs:174
FR-DEV-3e core/dr-decode/src/lib.rs:442, core/dr-decode/src/lib.rs:562
FR-DEV-4 core/dr-gpu/src/lib.rs:123
FR-DSP-1 ui/dr-ui/src/lib.rs:43
FR-EXP-1 core/dr-types/src/settings.rs:1, ui/dr-ui/src/settings_ui.rs:1
FR-EXP-2 core/dr-types/src/settings.rs:1, ui/dr-ui/src/settings_ui.rs:1
FR-EXP-3 core/dr-types/src/settings.rs:1, ui/dr-ui/src/settings_ui.rs:1
FR-EXP-4 ui/dr-ui/src/settings_ui.rs:1
FR-EXP-5 ui/dr-ui/src/settings_store.rs:1
FR-EXP-6 core/dr-types/src/settings.rs:1, ui/dr-ui/src/settings_ui.rs:1
FR-EXP-8 ui/dr-ui/src/settings_ui.rs:1
FR-EXP-9 core/dr-decode/src/lib.rs:359
FR-NC-1 core/dr-sync-nextcloud/src/auth.rs:132, core/dr-sync-nextcloud/src/auth.rs:44, core/dr-sync-nextcloud/src/session.rs:95, ui/dr-ui/src/launch.rs:49
FR-NC-12 core/dr-sync-nextcloud/src/lib.rs:34, core/dr-sync/src/lib.rs:155, core/dr-sync/src/lib.rs:38, core/dr-sync/src/reachability.rs:1
FR-NC-2 core/dr-sync-nextcloud/src/session.rs:95
FR-NC-3 core/dr-decode/src/locate.rs:1, core/dr-decode/src/preview.rs:123, core/dr-sync/src/capability.rs:41, core/dr-thumbs/src/lib.rs:1, ui/dr-ui/src/library.rs:1, ui/dr-ui/src/library_ui.rs:1
FR-NC-4 core/dr-sync-nextcloud/src/propfind.rs:100, core/dr-sync-nextcloud/src/propfind.rs:51, core/dr-sync/src/capability.rs:6, core/dr-sync/src/lib.rs:155, core/dr-sync/src/scan.rs:93, ui/dr-ui/src/launch.rs:49
FR-NC-5 core/dr-sync-nextcloud/src/propfind.rs:51
FR-NC-6a core/dr-catalog/src/cache.rs:1, core/dr-catalog/src/schema.rs:230, core/dr-catalog/src/schema.rs:635, core/dr-types/src/selector.rs:1, core/dr-types/src/settings.rs:1, ui/dr-ui/src/lib.rs:1281, ui/dr-ui/src/lib.rs:876, ui/dr-ui/src/library.rs:813, ui/dr-ui/src/library.rs:826, ui/dr-ui/src/library.rs:987, ui/dr-ui/src/library_ui.rs:1043, ui/dr-ui/src/library_ui.rs:173, ui/dr-ui/src/library_ui.rs:184, ui/dr-ui/src/library_ui.rs:193, ui/dr-ui/src/library_ui.rs:242, ui/dr-ui/src/library_ui.rs:252, ui/dr-ui/src/library_ui.rs:2729, ui/dr-ui/src/library_ui.rs:292, ui/dr-ui/src/library_ui.rs:307, ui/dr-ui/src/library_ui.rs:338, ui/dr-ui/src/library_ui.rs:701, ui/dr-ui/src/library_ui.rs:802, ui/dr-ui/src/library_ui.rs:905, ui/dr-ui/src/settings_store.rs:1, ui/dr-ui/src/settings_ui.rs:1, ui/dr-ui/ui/library.slint:783
FR-NC-6c core/dr-sync-nextcloud/src/desktop_client.rs:30, core/dr-types/src/lib.rs:100, core/dr-types/src/lib.rs:182
FR-NC-7 core/dr-sync-nextcloud/src/lib.rs:95, ui/dr-ui/src/derived_sync.rs:1
FR-NC-8 core/dr-pipeline/src/sidecar.rs:108, core/dr-pipeline/src/sidecar.rs:89, ui/dr-ui/src/library.rs:277
FR-NC-9 core/dr-catalog/src/merge.rs:1, core/dr-catalog/src/sync.rs:1, core/dr-pipeline/src/sidecar.rs:212
FR-PLAT-AND-1 core/dr-types/src/lib.rs:43
FR-PLAT-AND-3 core/dr-catalog/src/jobs.rs:1
FR-PLAT-LIN-1 core/dr-types/src/settings.rs:1, ui/dr-ui/src/settings_store.rs:1
FR-RAW-1 core/dr-decode/src/lib.rs:189, core/dr-types/src/lib.rs:110, core/dr-types/src/lib.rs:181
FR-RAW-3 core/dr-decode/src/lib.rs:359, core/dr-decode/src/lib.rs:85
FR-RAW-4 core/dr-decode/src/error.rs:1
FR-RAW-5 core/dr-decode/src/lib.rs:113
FR-UI-1 ui/dr-ui/src/lib.rs:51
FR-UI-2 ui/dr-ui/src/lib.rs:51
FR-UI-3 ui/dr-ui/ui/collections.slint:4
FR-UI-5 ui/dr-ui/src/collections_ui.rs:1, ui/dr-ui/ui/collections.slint:4
NFR-ARCH-2 core/dr-catalog/src/jobs.rs:1
NFR-ARCH-4 core/dr-catalog/src/error.rs:1, core/dr-thumbs/src/error.rs:1
NFR-OPS-1 tools/traceability/src/lib.rs:266
NFR-P1 core/dr-catalog/src/lib.rs:1, core/dr-catalog/src/scan.rs:1, tools/traceability/src/lib.rs:473
NFR-P13 core/dr-decode/src/preview.rs:96
NFR-P9 ui/dr-ui/src/collections_ui.rs:1, ui/dr-ui/src/library.rs:1, ui/dr-ui/src/library_ui.rs:1, ui/dr-ui/src/trash.rs:1
NFR-R1 core/dr-catalog/src/sync.rs:1
NFR-R2 core/dr-catalog/src/trash.rs:1
NFR-R5 core/dr-catalog/src/collections.rs:1, core/dr-catalog/src/error.rs:1, core/dr-catalog/src/schema.rs:1
NFR-R7 core/dr-gpu/src/error.rs:1
NFR-R8 core/dr-gpu/src/error.rs:1
NFR-RES-1 ui/dr-ui/src/lib.rs:43
NFR-RES-4 core/dr-catalog/src/cache.rs:1, core/dr-catalog/src/schema.rs:230, core/dr-thumbs/src/codec.rs:1, core/dr-thumbs/src/lib.rs:1, core/dr-thumbs/src/lib.rs:341
NFR-SEC-1 core/dr-decode/src/error.rs:1
R1 tools/traceability/src/lib.rs:489, tools/traceability/src/lib.rs:493
R4 core/dr-gpu/src/lib.rs:123

Not yet tagged

80 of 149 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built.

Show untagged requirements
  • FR-CAT-10
  • FR-CAT-13
  • FR-CAT-14
  • FR-CULL-10
  • FR-CULL-11
  • FR-CULL-12
  • FR-CULL-3
  • FR-CULL-5
  • FR-CULL-6
  • FR-CULL-7
  • FR-CULL-8
  • FR-CULL-9
  • FR-DEV-1
  • FR-DEV-2
  • FR-DEV-3f
  • FR-DEV-3g
  • FR-DEV-5
  • FR-DEV-6
  • FR-DEV-7
  • FR-DEV-8
  • FR-DSP-2
  • FR-DSP-3
  • FR-DSP-4
  • FR-DSP-5
  • FR-DSP-6
  • FR-DSP-7
  • FR-DSP-8
  • FR-EXP-7
  • FR-NC-10
  • FR-NC-11
  • FR-NC-6
  • FR-NC-6b
  • FR-PLAT-AND-2
  • FR-PLAT-AND-4
  • FR-PLAT-AND-5
  • FR-PLAT-AND-6
  • FR-PLAT-LIN-2
  • FR-PLAT-LIN-3
  • FR-RAW-2
  • FR-UI-4
  • FR-UI-6
  • FR-UI-7
  • NFR-A11Y-1
  • NFR-A11Y-2
  • NFR-A11Y-3
  • NFR-ARCH-1
  • NFR-ARCH-3
  • NFR-COMPAT-1
  • NFR-COMPAT-2
  • NFR-OPS-2
  • NFR-OPS-3
  • NFR-OPS-4
  • NFR-P10
  • NFR-P11
  • NFR-P12
  • NFR-P14
  • NFR-P15
  • NFR-P2
  • NFR-P3
  • NFR-P4
  • NFR-P5
  • NFR-P6
  • NFR-P7
  • NFR-P8
  • NFR-PORT-1
  • NFR-PORT-2
  • NFR-PORT-3
  • NFR-R3
  • NFR-R4
  • NFR-R6
  • NFR-RES-2
  • NFR-RES-3
  • NFR-SEC-2
  • NFR-SEC-3
  • NFR-SEC-4
  • NFR-SEC-5
  • R2
  • R3
  • R5
  • R6