|
1 | 1 | ## Context |
2 | 2 |
|
3 | | -`Paths.databases` currently fixes database files under `<state>/databases`, and `Database.data` derives |
4 | | -every engine bind source from it. Generated assets, backup staging, and locks also derive from `Paths`, |
5 | | -but they must remain in the canonical state tree when database files move to dedicated storage. |
6 | | - |
7 | | -The source configuration is strict and human-owned. evdb does not retain configuration history or move |
8 | | -database data, while generated Compose already records the last bind source used for each managed role. |
| 3 | +`Paths.databases` supplies the canonical database root. The first implementation moved that choice to |
| 4 | +one `Host.data_root`, but one host-wide value cannot place roles on different disks. Generated assets, |
| 5 | +backup staging, and locks must remain in the canonical state tree regardless of role placement. |
9 | 6 |
|
10 | 7 | ## Goals / Non-Goals |
11 | 8 |
|
12 | 9 | **Goals:** |
13 | 10 |
|
14 | | -- Store one explicit host-wide data root and use it for every database role. |
15 | | -- Make fresh guided and direct setup support a mounted-disk directory. |
16 | | -- Reject unsafe roots and prevent an existing generated role from silently switching to an empty path. |
17 | | -- Keep the canonical root as the setup default. |
| 11 | +- Store an ordered allowlist of host database roots and one exact selection on every role. |
| 12 | +- Collect roots during fresh setup and choose placement during database creation. |
| 13 | +- Permit later catalog edits while preventing removal of referenced roots. |
| 14 | +- Reject unsafe roots and prevent an existing generated role from silently changing placement. |
18 | 15 |
|
19 | 16 | **Non-Goals:** |
20 | 17 |
|
21 | | -- No per-project or per-role data paths. |
| 18 | +- No arbitrary per-role paths outside the host catalog. |
22 | 19 | - No mount creation, persistence, startup ordering, or availability monitoring. |
23 | 20 | - No data copy, relocation, import, compatibility default, or source migration. |
24 | 21 | - No change to generated assets, backup staging, locks, or repositories. |
25 | 22 |
|
26 | 23 | ## Decisions |
27 | 24 |
|
28 | | -### Put the root on Host and keep Paths as the canonical default |
| 25 | +### Store exact paths in an ordered catalog and on each role |
| 26 | + |
| 27 | +Replace the single root with required `Host.data_roots: tuple[Path, ...]`. Add required |
| 28 | +`data_root: Path` to `Postgres` and `KV`, and derive `Database.data` from the role setting. Exact paths |
| 29 | +avoid an alias layer and make placement visible beside engine settings. `Paths.databases` remains only |
| 30 | +the initial default. List order is preserved, but multiple roots have no implicit direct-creation default. |
29 | 31 |
|
30 | | -Add required `Host.data_root: Path` and serialize it as `host.data_root`. `Database.data` uses this field, |
31 | | -while `Paths.databases` remains the default selected by fresh initialization. This keeps operator policy |
32 | | -in source configuration without making all runtime paths configurable. |
| 32 | +### Validate the catalog as one unit |
33 | 33 |
|
34 | | -### Validate a dedicated safe directory |
| 34 | +Every root must be a unique normalized absolute path with no symlinked or non-directory existing |
| 35 | +component. Roots must not overlap one another, evdb configuration, generated assets, Traefik, backups, |
| 36 | +locks, or a local Restic repository. For a non-canonical root, the immediate parent must already exist. |
| 37 | +Every role selection must exactly match one catalog entry. Initialization prepares every root. |
35 | 38 |
|
36 | | -The value must be a normalized absolute path with no symlinked or non-directory existing component. It |
37 | | -must not be a broad filesystem ancestor or overlap evdb configuration, generated assets, Traefik, |
38 | | -backups, locks, or a local Restic repository. For a custom root, the immediate parent must already exist; |
39 | | -initialization creates or converges only the selected leaf as a private managed directory. |
| 39 | +Fresh guided setup collects roots until the operator is done and shows the ordered list in review. |
| 40 | +Operators may later edit `config.yml` and rerun initialization. Adding or removing an unused root is |
| 41 | +valid; removing a referenced root fails source validation. |
40 | 42 |
|
41 | | -The setup review states the selected path. The operator is responsible for mounting storage before evdb |
42 | | -or Docker starts; evdb does not imply that a custom directory is a persistent mount. |
| 43 | +### Select placement during creation and keep it immutable |
43 | 44 |
|
44 | | -### Treat the root as immutable after role generation |
| 45 | +Guided database creation asks for a root when the catalog has multiple entries and includes it in review. |
| 46 | +Direct creation uses the sole root automatically; with multiple roots it requires `--data-root PATH`. |
| 47 | +Creation writes the exact path under the role. Idempotent add rejects a different supplied path. |
45 | 48 |
|
46 | | -Fresh setup and source loading require the field. Existing-host initialization accepts only the stored |
47 | | -value. Before replacing an existing role Compose file, database rendering compares the current primary |
48 | | -data bind with the desired bind. A different source fails before creating the new data directory or |
49 | | -rewriting Compose. This catches both supported command misuse and direct source edits without adding a |
50 | | -machine-state file. |
| 49 | +Before replacing existing role Compose, rendering compares the current primary data bind with the |
| 50 | +desired bind. A different source fails before creating the new data directory or rewriting Compose. The |
| 51 | +database settings editor never exposes placement. |
51 | 52 |
|
52 | 53 | ## Risks / Trade-offs |
53 | 54 |
|
54 | | -- [A configured disk is not mounted when Docker starts] -> State clearly that mounts and startup ordering |
55 | | - are operator-managed; evdb validates the directory but does not claim mount availability. |
56 | | -- [A source edit points at an empty directory] -> Compare the generated primary data bind before any |
57 | | - render mutation and reject a changed source. |
58 | | -- [Existing source lacks the required field] -> Reject it under the existing strict-schema policy; the |
59 | | - operator must provide current source explicitly. |
| 55 | +- [A configured disk is not mounted when Docker starts] -> Mounts and startup ordering remain |
| 56 | + operator-managed; evdb validates directories but does not claim mount availability. |
| 57 | +- [A source edit points at an empty directory] -> Compare generated primary data binds before mutation. |
| 58 | +- [A catalog entry is removed while in use] -> Require every role selection to remain in the catalog. |
| 59 | +- [Existing source lacks catalog or role placement] -> Reject it under the strict-schema policy. |
60 | 60 | - [Generated Compose is absent] -> Allow rendering because no prior managed bind exists to preserve. |
0 commit comments