crabidy/quality/orphans.md

103 lines
5.8 KiB
Markdown

# Quality gates — the `orphans` provider
Gates for `architecture/orphans.md` and the `crabidy-server/src/orphans.rs`
stub. Each is pass/fail by reading/reasoning or by a named test. Tests live in
`crabidy-server/src/crabidy_store.rs` (the store methods) and
`crabidy-server/src/orphans.rs` (the provider), run via
`devenv shell -- cargo test -p crabidy-server`.
## Correctness of the orphan diff
- [ ] **G1 — All items enumerated.** `list_orphans` counts every store entry
that has both a `<name>.cbd-store.toml` sidecar and a readable `<name>` audio
file. Test: capture two distinct tracks (two store entries), reference
neither → both listed.
- [ ] **G2 — Referenced entries excluded.** An entry referenced by a
`Playable::Store` toml under **any** `ref_root` is not listed. Test: capture
one track into a `/crabidy` save (which writes a store toml), then
`list_orphans` with that tree as a ref root → the entry is **not** an orphan;
delete the save's toml → it **becomes** an orphan.
- [ ] **G3 — `/fs` references count.** A `Playable::Store` toml under the `/fs`
root (as `scan --capture` writes) excludes its target. Test: hand-write a
store toml under an fs ref root → its target is not an orphan.
- [ ] **G4 — References outside ref roots do not count.** A store toml under a
directory that is **not** a ref root does not rescue its target from being an
orphan (documented boundary). Test asserts the target is still listed.
- [ ] **G5 — Malformed residue is reclaimable.** A sidecar with no audio (or
audio with no sidecar) is reported as an orphan, never as "referenced". Test:
drop a lone `x.cbd-store.toml` into the store → listed.
- [ ] **G6 — Fresh every call.** No persisted orphan list; two consecutive
`list_orphans` calls straddling a reference change reflect the change
(covered by G2's two-phase assertion).
## Presentation contract (reuse, no new wire)
- [ ] **G7 — Child capability flags.** Every `/orphans` child advertises
`is_queable`, `is_editable`, `is_deletable`, and `is_captured` all `true`, and
`is_downloadable = false`. The root is `is_queable = true`, `is_creatable =
false`. Verified by reading `get_lib_node`; test asserts the flags on a listed
child.
- [ ] **G8 — Leaf carries exactly one track.** `get_lib_node("/orphans/<seg>")`
returns a childless, queueable node with one track whose `is_captured` is true
and whose metadata matches the sidecar. Test asserts `tracks.len() == 1` and
`children.is_empty()`.
- [ ] **G9 — Queueing resolves the audio.** Resolving an orphan node (default
walk) yields one track whose `get_urls_for_track` returns `store_root/<name>`.
Test drives `resolve_tracks_into("/orphans")` and checks the resolved URL is
the store path.
- [ ] **G10 — No proto/TUI/web change.** Confirm by inspection that the feature
adds no field to any `proto` message and no binding/gesture to `cbd-tui` or
`cbd-web`: it relies solely on existing `is_editable`/`is_deletable`/
`is_queable` handling. (If this gate cannot hold, the design in
`architecture/orphans.md` must be revisited before implementing.)
## Rename safety
- [ ] **G11 — Renames both files.** `rename_orphan(old, new)` renames the audio
`<old>`→`<new>` **and** `<old>.cbd-store.toml`→`<new>.cbd-store.toml`; neither
old name remains on disk. Test.
- [ ] **G12 — Rename updates the index.** After a rename, the index resolves the
same content hash / provider ids to the **new** name and no longer to the old
(so a subsequent capture de-dups against the renamed entry). Test via
`StoreIndex` lookups or a follow-up capture.
- [ ] **G13 — Rename validates and refuses collisions.** An empty / separator /
leading-dot `new` is rejected (`InvalidInput`); a `new` already taken by
another store audio or sidecar is refused without touching disk. Tests for
both.
- [ ] **G14 — Rename is reference-safe by construction.** Only unreferenced
entries are exposed under `/orphans`, so a rename never invalidates a live
toml. Verified by reasoning against the diff definition; no test needed beyond
G2.
## Delete safety
- [ ] **G15 — Deletes both files.** `delete_orphan(name)` removes the audio and
the sidecar and drops the entry from the index. Test asserts both files gone
and `list_orphans` no longer lists it.
- [ ] **G16 — Idempotent.** Deleting an already-gone entry returns `Ok` (a
refreshed root), not an error. Test.
- [ ] **G17 — Delete stays inside the store root.** The name is a validated bare
file name; delete joins it onto `store_root` and never follows separators or
`..`. Verified by reasoning (mirrors `Playable::Store` bare-name validation)
plus a test that a crafted `/orphans/..%2Fx` path is `MalformedPath`, not a
file operation.
## Always-on rules (AGENTS.md)
- [ ] **G18 — No panics on input or I/O.** Every error path (missing entry,
unreadable dir, bad toml, taken rename target, malformed path) is a typed
`ProviderError`/`StoreError`; no `unwrap`/`expect`/`panic!`/`todo!` remains in
shipped code. A bad sidecar or track toml during the walk is skipped with a
warning, never fatal (mirrors `StoreIndex::scan` and `fsdy` listing).
- [ ] **G19 — Bounded work, no unbounded channels.** The reference walk and
enumeration use ordinary async fs iteration; if any channel is introduced it
is bounded. No new external calls (so no timeout/retry surface is added).
- [ ] **G20 — Public items documented.** Every public item in `orphans.rs` and
every new public `CrabidyStore`/`fsdy::Client` method has a doc comment
stating intent and error behavior.
- [ ] **G21 — Mount is optional and non-fatal.** `/orphans` mounts only when the
store is present; its absence (no data/state dir) drops the subtree without
affecting the server, exactly like `/crabidy` and `/fs`. `get_lib_root`
includes the `orphans` child only when mounted. Verified by reading the
orchestrator wiring.