docs: storage plan — path is the id, label is queryable display metadata

Refine the naming decision: a volume's mount path IS its identity id (GPT GUID,
else fat-<serial>/mbr-<sig>-<index>) — a stable, unique, content-derived handle
software uses. The label (FAT volume label / GPT partition name) is mutable
display metadata, NOT in the path, exposed by a volume-manager `volumes` verb
returning {id, mount_path, label} — the database id/name split. This dissolves
the label-collision problem: same-label-different-id volumes get distinct paths
automatically; only identical ids (dd-clones) hit first-wins-and-log. S1 carries
both key (id) and label (display); S2 derives the id-path and adds the query.
This commit is contained in:
Daniel Samson
2026-08-09 22:54:36 +01:00
parent addd264880
commit eff95416d0
+59 -31
View File
@@ -35,7 +35,8 @@ S5 removal robustness ── independent; single-volume ── may land any time
comes FIRST because a volume's mount name is now its identity (below), and the comes FIRST because a volume's mount name is now its identity (below), and the
friendly form of that name is the FAT label / GPT name S1 parses. friendly form of that name is the FAT label / GPT name S1 parses.
- **S2** moves the last policy out of hardcode into `volumes.csv` + - **S2** moves the last policy out of hardcode into `volumes.csv` +
`filesystems.csv`, and names each volume by its identity — no port-name. `filesystems.csv`, and names each volume by its identity **id** (a GUID/key),
keeping the label as separate, queryable display metadata — no port-name.
- **S3** generalizes to N volumes across N devices. - **S3** generalizes to N volumes across N devices.
- **S4** adds exFAT — a COMPLETE second engine that proves the V1 harness - **S4** adds exFAT — a COMPLETE second engine that proves the V1 harness
extraction. Needs S2 (to route by signature) and S3 (to run a second volume). extraction. Needs S2 (to route by signature) and S3 (to run a second volume).
@@ -52,7 +53,10 @@ rung-4 identity into a ladder that reads the richest available content identity:
GPT partition GUID (rung 1, 128-bit), FAT volume serial + label (rung 3), MBR GPT partition GUID (rung 1, 128-bit), FAT volume serial + label (rung 3), MBR
signature + index (rung 4, kept), bare-FAT (kept, enriched to its serial). The signature + index (rung 4, kept), bare-FAT (kept, enriched to its serial). The
`u64` identity becomes a small tagged struct `Identity{ rung, key: u128, label, `u64` identity becomes a small tagged struct `Identity{ rung, key: u128, label,
has_label }`. Because GPT metadata is at LBA 1 and the entry array beyond it, and has_label }` — `key` is the **id** (the path handle), `label` is the **display
name** (the GPT 36-char partition name, or the FAT volume label), a separate
field per the id/name split S2 relies on. Because GPT metadata is at LBA 1 and
the entry array beyond it, and
the FAT serial is in each partition's VBR, `firstVolume` stops taking one the FAT serial is in each partition's VBR, `firstVolume` stops taking one
preloaded block-0 slice and takes a `SectorReader` (context + read-one-sector preloaded block-0 slice and takes a `SectorReader` (context + read-one-sector
fn, mirroring the engine's `BlockDevice` vtable) — host-testable against a fn, mirroring the engine's `BlockDevice` vtable) — host-testable against a
@@ -97,20 +101,36 @@ never drift onto magic constants).
## S2 — the mount map: volumes.csv + filesystems.csv ## S2 — the mount map: volumes.csv + filesystems.csv
**Goal.** Move the last two pieces of storage policy out of hardcode into **Goal.** Move the last two pieces of storage policy out of hardcode into
configuration read by the volume manager. `filesystems.csv` (content signature → configuration read by the volume manager, and split a volume's **id** from its
filesystem binary) so the VM picks the binary from the probed signature; **label** (the database model: the id is the real, stable, unique key software
`volumes.csv` (identity → mount prefix, danos's fstab) as the explicit override uses; the label is a mutable display name). `filesystems.csv` (content signature
for a volume the user wants at a fixed path. **The default mount name is the → filesystem binary) so the VM picks the binary from the probed signature;
volume's own identity, never a port or role name**: `/volumes/<label>` when the `volumes.csv` (id → mount prefix, danos's fstab) as the explicit override for a
medium carries a label (the FAT label / GPT partition name S1 reads), else volume the user wants at a fixed path.
`/volumes/vol-<hex-key>`; duplicate names follow the rationale's duplicate-
identity policy (first keeps it, second suffixed and logged). There is no **The mount path IS the identity id, never a label or a port/role name.** A
`/volumes/usb` and no `/volumes/boot` — the boot volume is detected by content volume mounts at `/volumes/<id>` — the GPT partition GUID for a GPT volume, a
(it installs the `/system/configuration` + `/system/logs` rewrites) but is NAMED `fat-<serial>` / `mbr-<sig>-<index>` form otherwise (exact rendering is an impl
by its identity like any other. Parsed with `library/csv` exactly as detail; the point is a stable, unique, content-derived string). Because the path
`device-registry` parses `devices.csv`. The VM hands the binary + volume-id + is the id and never the label, two distinct volumes that happen to share a label
mount specs to fat at spawn (the argv channel V3b already uses for the volume (`Backup`, `UNTITLED`, unlabeled) get distinct paths automatically and never
id); fat retires `fat_mounts` and reads mounts from argv[2..]. collide; only genuinely identical *ids* (dd-cloned media) hit the rationale's
duplicate-identity policy (first mounts, second suffixed and logged).
**The label is display metadata, exposed by a protocol query, not the path.** S1
reads it off the medium (FAT volume label, GPT partition name); a volume-manager
`volumes` verb returns `{ id, mount_path, label }` per volume so a future
shell/UI can show the friendly name — the id↔name split, like a table's primary
key vs its display column. (`volumes.csv` may optionally carry a chosen label
override alongside the path override, both keyed on id.) There is no
`/volumes/usb` and no `/volumes/boot`; the boot volume is detected by content (it
installs the `/system/configuration` + `/system/logs` rewrites) but is named by
its id like any other.
Parsed with `library/csv` exactly as `device-registry` parses `devices.csv`. The
VM hands the binary + volume-id + mount specs to fat at spawn (the argv channel
V3b already uses for the volume id); fat retires `fat_mounts` and reads mounts
from argv[2..].
**Key touchpoints.** New pure-logic modules `filesystem-map.zig` (parse + **Key touchpoints.** New pure-logic modules `filesystem-map.zig` (parse +
`match(signature)`) and `volume-map.zig` (parse + `mountsFor(identity)` + `match(signature)`) and `volume-map.zig` (parse + `mountsFor(identity)` +
@@ -123,21 +143,24 @@ argv; `fat.zig` deletes `fat_mounts`, parses argv[2..] into a bounded
MBR disk signature so the boot volume's identity is a legible non-zero key. MBR disk signature so the boot volume's identity is a legible non-zero key.
**Steps (commits).** (1) partition emits a signature. (2) `filesystem-map` **Steps (commits).** (1) partition emits a signature. (2) `filesystem-map`
parser + host tests. (3) `volume-map` parser (identity→prefix override) + parser + host tests. (3) `volume-map` parser (id→prefix override) + the id-path
identity-derived default naming (label→hex) + host tests. (4) Ship the tables + deriver + the `volumes` label-query verb + host tests. (4) Ship the tables +
VM integration **behind fat's still-hardcoded mounts** (behavior-preserving — VM integration **behind fat's still-hardcoded mounts** (behavior-preserving —
parsers proven before consumption flips; full suite green). (5) fat consumes parsers proven before consumption flips; full suite green). (5) fat consumes
argv mounts; the VM names the boot volume by its identity (`/volumes/DANOS`, from argv mounts; the VM mounts the boot volume at its id-path (`/volumes/fat-12345678`,
the FAT label S1 read); **migrate the fixtures + QEMU regexes off `/volumes/usb`** from its serial); **migrate the fixtures + QEMU regexes off `/volumes/usb`**
(fat-test, badge-scope-test, vfs-test, and the four cases) in the same commit; (fat-test, badge-scope-test, vfs-test, and the four cases) in the same commit;
the `volume-identity-name` QEMU case (shown failing against HEAD~1); flip the the `volume-identity-name` QEMU case (shown failing against HEAD~1); flip the
docs' pending markers. docs' pending markers.
**Discrimination.** QEMU `volume-identity-name`: the boot volume mounts at **Discrimination.** QEMU `volume-identity-name`: the boot volume mounts at its
`/volumes/DANOS` (its FAT label), a line the old hardcoded `/volumes/usb` never id-path (`/volumes/fat-12345678`, from its serial), a line the old hardcoded
emits. Host: an unlabeled identity derives `/volumes/vol-<hex>`; a `volumes.csv` `/volumes/usb` never emits; the `volumes` query returns that id paired with the
override sends a mapped identity to its chosen prefix; `match(.fat)` returns the label `DANOS`. Host: two volumes sharing a label but not an id get distinct
configured binary; fat's argv parser makes installed mounts a function of argv. id-paths (the collision the label-as-name approach could not resolve stably); a
`volumes.csv` override sends a mapped id to its chosen prefix; `match(.fat)`
returns the configured binary; fat's argv parser makes installed mounts a
function of argv.
**Top risks.** Step 5's blast radius — a parser/argv bug, OR the `/volumes/usb`→ **Top risks.** Step 5's blast radius — a parser/argv bug, OR the `/volumes/usb`→
`/volumes/DANOS` migration missing a fixture/regex, breaks every fat-dependent `/volumes/DANOS` migration missing a fixture/regex, breaks every fat-dependent
@@ -319,12 +342,17 @@ uses a geometry re-probe as the liveness oracle, which is correct but indirect.
Both flagged decisions are settled: Both flagged decisions are settled:
1. **Volume names are the identity (S2/S3).** Every volume mounts at 1. **A volume's path is its id; the label is display metadata (S1/S2).** The
`/volumes/<its-identity>` — the FAT label / GPT name where present, else the mount path is the identity id — the GPT GUID, else a `fat-<serial>` /
identity-hex — never a port name (`/volumes/usb`) or a role name `mbr-<sig>-<index>` form — a stable, unique, content-derived handle software
(`/volumes/boot`). `volumes.csv` remains the explicit override for a chosen uses. The label (FAT volume label / GPT partition name) is a mutable display
fixed path. This makes S1 (which reads the label) precede S2, and folds the name, NOT in the path; a volume-manager `volumes` verb returns
`/volumes/usb` → `/volumes/DANOS` fixture + regex migration into S2 step 5. `{ id, mount_path, label }` so a UI can show the friendly name (the database
id/name split). Same-label-different-id volumes therefore never collide; only
identical ids (dd-clones) hit first-wins-and-log. `volumes.csv` overrides the
path (and optionally the label) for a chosen volume, keyed on id. Makes S1
precede S2 and folds the `/volumes/usb` → id-path fixture + regex migration
into S2 step 5.
2. **exFAT is implemented in full (S4).** A complete exFAT: full read and write, 2. **exFAT is implemented in full (S4).** A complete exFAT: full read and write,
directories, rename, and the on-disk up-case table for correct case-folding — directories, rename, and the on-disk up-case table for correct case-folding —