Deep dives
Architecture and design decisions cover what pgoverlay is and why it is shaped the way it is. This document covers the seven hardest problems in the codebase — the ones where the obvious implementation was wrong, the bug was subtle enough to survive review, and the fix generalizes past this project.
Each follows the same shape:
- The problem — what the code was trying to do.
- Why it's subtle — the trap; why the naive version looks correct.
- The fix — what actually lives in the code, cited as
file:func. - The lesson — the general principle worth carrying elsewhere.
Every claim was checked against the code. Where the narrative and the code disagreed, these notes follow the code and say so.
1. Reconcile deleted a live branch's only copy of its data
The problem
pgoverlay's flagship feature is branch-from-branch: take a ready branch and
fork a new branch off its current state. On the overlay (OverlayFS) backend the
parent's writable layer cannot be shared read-write with a child, so the parent
is frozen into an immutable layer. The freeze saga
(internal/engine/freeze.go:freezeAndProvision) does roughly:
CHECKPOINT parent → stop parent → fresh parent rw volume →
restart parent over [oldParentRW(frozen), …chain…, source] (wait ready) →
start child over the same frozen chain (wait ready) →
CommitFreeze (layer row + parent rw swap, atomic) → child ready
The crucial detail: from the moment the parent transitions ready → resetting
(freeze.go:freezeAndProvision, the TransitionBranchCtx(... BranchResetting)
call), the parent's live data still lives in its original rw_volume, and
stays there until the very end. CommitFreeze
(internal/registry/registry.go:CommitFreezeCtx) is what turns that old
rw_volume into a frozen layer row and swaps the parent onto the new fresh
volume — atomically, in one transaction. Before that commit, the old volume is
not a layer; it is the parent's only copy of its data.
Separately, branchd runs a reconcile loop
(internal/engine/reconcile.go:Reconcile) that, among other things, fails
branches "stuck" in a transient state. PlanReconcile step (b) calls
ListStuckBranches, and applyAction's ActionFailStuck case fails the row and
calls removeBranchLayer to clean up its half-built resources — including its
rw_volume.
Why it's subtle
The stuck detector keys off updated_at. ListStuckBranches
(internal/registry/registry.go:ListStuckBranches) is literally:
SELECT … FROM branches
WHERE state IN ('creating','resetting') AND updated_at < ? ORDER BY created_at
where ? is now - stuckTimeout (default 10 minutes;
internal/api/server.go:DefaultStuckTimeout, cmd/branchd/main.go
--stuck-timeout).
The trap: a freeze is a long operation. It can pull a cold Postgres image,
replay a large WAL, and it waits for readiness twice — once for the parent
restart and once for the child start, each with a 90-second budget
(freeze.go:freezeAndProvision, the two waitReady(ctx, …, 90*time.Second)
calls). A legitimately slow but perfectly healthy freeze could easily run past
10 minutes.
And here is the bug: nothing in the freeze saga was bumping updated_at.
The parent went ready → resetting once (which does set updated_at), then the
saga spent many minutes doing real work without ever touching the row again.
SetBranchContainer, which the saga does call when it owns a new container,
did not bump updated_at either. So to the reconcile loop the parent looked
like it had been frozen in resetting and untouched for 10+ minutes — i.e.
abandoned. Reconcile then "cleaned up" the stuck row by deleting its
rw_volume. That volume was the parent's live data. Permanent data loss.
sequenceDiagram
participant F as freeze saga
participant P as parent row
participant R as reconcile loop
Note over P: state=ready, updated_at=T0
F->>P: ready → resetting (updated_at=T0)
Note over F: CHECKPOINT, stop, fresh vol,<br/>restart parent (cold pull + WAL),<br/>wait 90s, start child, wait 90s…
Note over P: still resetting,<br/>updated_at STILL = T0
R->>P: T0 + 10m elapsed?
Note over R: ListStuckBranches: updated_at < now-10m → STUCK
R->>P: ActionFailStuck → removeBranchLayer
Note over R: deletes parent rw_volume = LIVE DATA 💥
F->>P: CommitFreeze (too late — volume gone)
The fix
Two independent parts, because the race has two failure modes:
(a) Keep the liveness timer fresh while the op is alive. Saga progress now
bumps updated_at:
SetBranchContainer(internal/registry/registry.go:SetBranchContainer) now writesupdated_at=…now()alongsidecontainer_id. Its doc comment spells out exactly why: "recording the in-flight container is saga progress, so a slow-but-alive create/freeze keeps resetting the stuck-timer."- A new
TouchBranch(internal/registry/registry.go:TouchBranch) bumpsupdated_atwith no other change — a pure checkpoint. The freeze saga calls it at its waypoints: after the parent restart becomes ready and before the child start, it touches both the parent and child rows (freeze.go:freezeAndProvision, the twoTouchBranchcalls labelled "freeze: touch parent/child stuck-timer").
So a healthy-but-slow freeze keeps re-arming the timer; only a genuinely wedged op crosses the threshold.
Update (v1.0). Waypoint touches still left gaps: a single long step (a slow
masking script, a large first recovery) could outlast the timeout between two
waypoints. Every branch saga now runs a heartbeat, Engine.keepAlive
(internal/engine/engine.go), that bumps the rows it works on every
min(--stuck-timeout/4, 30s) until the saga returns, and seeds heartbeat their
source row the same way. The stuck timeout now measures time without
progress, not total time. The freeze also claims the parent's new volume on
the parent row (pending_volume, schema v14) before creating it, so volume GC
cannot mistake it for an orphan mid-freeze.
(b) Never let GC delete a volume that is still referenced. Even a correctly
detected stuck parent must not have its rw_volume deleted if a child depends on
it. ActionFailStuck (internal/engine/reconcile.go:applyAction) now calls
CountLiveBranchesReferencingRW
(internal/registry/registry.go:CountLiveBranchesReferencingRW) before touching
the volume:
SELECT count(*) FROM branches
WHERE state!='destroyed' AND name!=?
AND (source_volume=? OR (parent_branch_name=? AND base_layer_id IS NULL))
If any live branch references the volume (as its source_volume, or as a
child of this parent whose freeze has not committed yet, which has no base
layer), reconcile fails the row but keeps the volume. (The
base_layer_id IS NULL refinement came later: a child whose freeze did
commit depends on the frozen layer, not on the parent's current volume, and
counting it kept the guard from ever releasing a failed parent's volume.)
A parent failed this way keeps its data, and pgb branch recover restarts
it on that data. The same guard protects the force-destroy path: DestroyBranch
(internal/engine/saga.go:DestroyBranch) forces a branch out of
creating/resetting to failed so an operator need not wait out the 10m
timeout — and when it does that forced transition (forcedFromTransient), it
runs the identical CountLiveBranchesReferencingRW guard before
removeBranchLayer. A normally-destroyed ready/failed branch is not
guarded, because a post-CommitFreeze parent has already swapped to a fresh
volume.
The lesson
A background reconciler plus a liveness heuristic (a timer) is only safe if the foreground work keeps refreshing the heuristic. If the slow path forgets to say "I'm still alive," the reaper will eventually reap live work. And destructive GC must check references, not just state — a row being in a "cleanup-eligible" state is necessary but not sufficient; you also have to prove nothing else depends on the resource you're about to destroy. Defence in depth: fix (a) makes the false-positive rare; fix (b) makes it non-destructive even when it happens.
2. The state-machine TOCTOU → compare-and-swap
The problem
Branch state is a small state machine (internal/registry/registry.go,
legalBranch): creating → {ready, failed}, ready → {destroying,
resetting}, resetting → {ready, failed}, failed → {resetting,
destroying}, destroying → destroyed. Every transition must be
legal (the edge exists) and atomic (two concurrent callers can't both
fire the same edge).
Why it's subtle
The original TransitionBranch was a read-check-write:
SELECT stateto read the current state,- check legality in Go,
setState— which itself did a separateSELECT+UPDATE.
The registry opens SQLite with SetMaxOpenConns(1)
(internal/registry/registry.go, the db.SetMaxOpenConns(1) line). The natural
assumption is "single connection ⇒ everything is serialized ⇒ no races." That's
the trap. SetMaxOpenConns(1) serializes individual statements — two
statements never physically execute at the same instant. It does not make a
sequence of statements atomic. Between your SELECT and your UPDATE, the
single connection can be handed to another goroutine that runs its own
SELECT+UPDATE.
So a concurrent destroy and reset on the same ready branch could both read
ready, both pass the legality check (ready→destroying and ready→resetting
are both legal edges), and both write — double-executing two mutually
exclusive operations on one branch.
The fix
Collapse the read-check-write into a single compare-and-swap:
TransitionBranchCtx (internal/registry/registry.go:TransitionBranchCtx) now
runs inside a transaction and the mutation is a state-guarded conditional
update:
UPDATE branches SET state=?, updated_at=…now()
WHERE id=? AND state=? -- the second ? is the from-state we just read
If a concurrent winner already moved the row out of that from-state,
RowsAffected()==0 — we lost the race. The code re-reads the current state and
returns the same illegal branch transition <cur> -> <to> error a from-state
mismatch would have produced, so the loser fails cleanly instead of clobbering
the winner. The transition is journaled in the same transaction.
This wasn't a new invention — CommitFreeze
(internal/registry/registry.go:CommitFreezeCtx) already used exactly this
pattern: it reads the parent's state inside the tx, refuses unless it is still
resetting, and does all of the layer-insert + rw-swap + child-rebase work in
one transaction. The fix made TransitionBranch consistent with it.
stateDiagram-v2
[*] --> creating
creating --> ready
creating --> failed
ready --> destroying
ready --> resetting
resetting --> ready
resetting --> failed
failed --> resetting: reset or recover
failed --> destroying
destroying --> destroyed
destroyed --> [*]
note right of ready
Concurrent destroy + reset both read `ready`.
CAS: UPDATE … WHERE state='ready' — exactly one
UPDATE affects a row; the other gets RowsAffected()==0.
end note
The lesson
Single-connection is not the same as atomic transactions. Connection-pool
limits constrain concurrency at the statement level, not the
read-modify-write level. For a state machine, the correct primitive is a
conditional UPDATE guarded by the expected current state — a CAS. It is
atomic by construction, it needs no external lock, and RowsAffected() tells you
unambiguously whether you won.
3. The saga + compensation model
The problem
Provisioning a branch is a multi-step side-effecting operation: create a volume, write an entrypoint, start a container, wait for readiness, mask data, rotate credentials, mark ready. Any step can fail. If step 4 fails, steps 1–3 left real resources behind (a volume, a container). Naively bailing on error leaks those resources forever.
Why it's subtle
The failure can come from anywhere — including request cancellation. If the
caller's context is cancelled mid-provision, the cleanup itself needs a context
to run (to talk to the container runtime). If you reuse the cancelled context,
your cleanup is dead on arrival and you leak anyway.
The fix
Every saga in internal/engine/saga.go (and the freeze saga in freeze.go)
builds an undo []func() slice as it goes: each successful resource step
appends its compensating action. On failure, fail(stepErr) walks undo in
reverse and runs each compensation, then returns the original error
(saga.go:provision, saga.go:provisionZFS, freeze.go:freezeAndProvision).
The freeze saga adds restoreParent (freeze.go:restoreParent) on top: after
the generic compensations remove the fresh volume and new containers, it puts the
parent back on its original rw volume and chain, so the parent's data is always
preserved — and if even that restore fails, the parent is marked failed, never
left half-frozen.
Two refinements make this robust:
- Compensations run on a non-cancellable context. Each saga captures
bg := context.WithoutCancel(ctx)(saga.go:provisionetc.) and the undo closures usebg, so cleanup survives the cancellation that may have caused the failure. - Best-effort cleanup is made observable. A compensation that itself fails
can't abort control flow (the caller still proceeds to mark the row failed),
so the failure is recorded via
logCompensationErr(internal/engine/engine.go:logCompensationErr), which emits aslog.Warnand incrementspgoverlay_compensation_failures_total{kind}(kind =transition|undo|cleanup;internal/metrics/metrics.go:IncCompensationFailure). The reconcile loop is the backstop that eventually cleans whatever a failed compensation leaked.
sequenceDiagram
participant S as saga
participant U as undo[]
S->>S: create rw volume ✓
S->>U: push removeVolume
S->>S: start container ✓
S->>U: push stopRemove
S->>S: waitReady ✗ (timeout)
S->>U: fail(): run undo in REVERSE
U->>U: stopRemove(bg) (WithoutCancel)
U->>U: removeVolume(bg)
Note over U: any undo error → logCompensationErr +<br/>pgoverlay_compensation_failures_total
S-->>S: return original error (reconcile is the backstop)
The lesson
Multi-step provisioning needs explicit rollback or it leaks. Model it as a saga: each step registers its own compensation, and failures unwind in reverse. Run compensations on a context that survives the cancellation that triggered them. And because best-effort cleanup will sometimes fail, make it observable (a metric + a log) and give it a backstop (the reconciler) — don't pretend it always succeeds.
4. The reconcile / GC loop
The problem
A stateful system drifts. Containers die out from under their registry rows; branches outlive their TTL; freezes leave half-built resources; layers lose their last reference; volumes get orphaned. Something has to periodically converge actual state (the container runtime) back to desired state (the registry).
Why it's subtle
A GC loop is the most dangerous code in the system — it deletes things. Three ways it can go wrong:
- TOCTOU. It plans an action based on a snapshot, then the world changes before it acts. A branch gets provisioned, a layer gets referenced, a volume gets claimed — and the now-stale plan deletes something that just became legitimate.
- Cross-instance reaping. Multiple branchd instances can share one container daemon. A naive loop would happily reap another instance's containers.
- Irreversibility. Unlike a request handler, a wrong GC has no retry — the data is gone (see deep-dive #1).
The fix
internal/engine/reconcile.go splits cleanly into plan (read-only) and
apply:
PlanReconcilecomputes aReconcilePlanofActions without mutating anything: reap TTL-expired (ListExpiredBranches), fail stuck rows (ListStuckBranches) and stuck seeding sources, retry destroys stuck indestroying, restart ready branches whose container is gone and record moved addresses, remove orphan containers and finished helpers, GC zero-refcount layers (CountBranchesReferencingLayer), GC volumes owned by no live branch/source (LiveVolumeSet) and older than the stuck timeout. This is the read-only half that backspgb doctorandGET /v1/reconcile/plan.ApplyReconcileruns the plan but re-validates every destructive action against the live registry immediately before acting (applyAction). Each case re-checks: still stuck? still refcount 0? still unclaimed? If the drift is gone, it returnsapplied=falsewith no error and moves on. This is the TOCTOU mitigation — the plan is advisory; the re-check at apply time is authoritative. This is whatpgb gcandPOST /v1/reconcilecall.
For cross-instance safety, everything is scoped by ownership. Orphan-container
detection skips any managed container whose pgoverlay.instance label
(runtime.LabelInstance) doesn't match this registry's InstanceID()
(PlanReconcile, the c.Labels[runtime.LabelInstance] != instanceID check), and
volume GC is scoped via ListManagedVolumes(ctx, instanceID). Two pgoverlay
instances on the same daemon never reap each other.
Idempotency falls out naturally: re-running a converged plan is a no-op (every
re-check says "skip"), and the loop runs once at startup then on a ticker
(RunReconcile).
The lesson
Reconcilers are how you converge a stateful system — the same control-loop
shape Kubernetes controllers use. Three properties are non-negotiable:
idempotency (safe to re-run), re-validation before mutation (the plan can
go stale; the apply-time check is the source of truth), and ownership
scoping (only ever touch resources you own). Splitting plan (read-only,
doctor) from apply (mutating, gc) also makes the whole thing inspectable
before it's trusted to delete.
5. The concurrency model in one place
The problem
pgoverlay has one registry (SQLite) and multiple writers in-process: API request handlers mutating branches, and the reconcile loop converging state — all concurrently. Plus, optionally, multiple branchd replicas. What actually guarantees correctness?
Why it's subtle
It's tempting to reason "I gave SQLite one connection, so I'm safe." That intuition is exactly what produced the TOCTOU in deep-dive #2.
The fix — and its precise limits
The registry opens SQLite with three settings
(internal/registry/registry.go, the sql.Open DSN + SetMaxOpenConns(1)):
?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=foreign_keys(1)&_txlock=immediate
db.SetMaxOpenConns(1)
(_txlock=immediate came later: it makes every transaction take SQLite's
write lock at BEGIN and wait under busy_timeout, instead of upgrading a
read transaction mid-way and failing with SQLITE_BUSY_SNAPSHOT when another
process, such as a second HA replica, has written the file in between.)
What this guarantees:
SetMaxOpenConns(1)— at most one connection, so no two statements physically interleave; you never get two half-applied writes tangled together.journal_mode(WAL)— readers don't block the writer and vice-versa; better concurrency for the read-heavy plan/list paths.busy_timeout(5000)— a contended statement waits up to 5s rather than failing immediately withSQLITE_BUSY.foreign_keys(1)— FK constraints are enforced (e.g. a layer row can't be deleted while a child layer chains onto it; seeDeleteLayer).
What this does NOT guarantee:
- A multi-statement read-modify-write is not atomic. One connection
serializes statements, not sequences. The gap between a
SELECTand a laterUPDATEis exactly where deep-dive #2's double-execution lived. The fix was the CAS (WHERE id=? AND state=?) and, where multiple rows change together, wrapping the work in an explicit transaction (CommitFreezeCtx,TransitionBranchCtx).
The reconcile loop runs concurrently with API mutations — which is precisely why it re-validates at apply time (deep-dive #4) and why GC checks references (deep-dive #1).
For the multi-replica case, leader election ensures a single writer instance.
internal/ha/leader.go (package doc) describes --leader-elect (kube-only):
replicas contend for a lease via client-go's leaderelection; only the leader
runs the reconcile loop and opens its API LeaderGate to mutating /v1
requests, while non-leaders keep serving reads. So at most one branchd is ever
issuing writes/reconcile.
The lesson
Be precise about what your concurrency primitive actually buys. "One connection" gives you statement-level serialization and nothing more; correctness of a logical operation that spans multiple statements still needs a transaction or a CAS. State the guarantee and its boundary — the bugs live exactly at the boundary people forget.
6. What an adversarial pre-v1 review found
The problem
Before tagging v1, pgoverlay went through an adversarial security review rather than a "looks fine to me" pass: a threat model built from the outside, and a bug-hunting pass over the attack surface, both run so that findings would be re-derived from the code instead of inherited from the author's mental model of it.
Why it's subtle
The author already "knows" the system works — that is the blind spot. Deep-dive #1 is the proof: the same person wrote the freeze saga and the reconcile loop and still did not see that one starved the other's liveness timer. The review re-derived that root cause from the outside.
What it surfaced (and how each was fixed)
- The data-loss root cause (deep-dive #1) — re-derived independently; fixed
by the
updated_atbumps + the reference-count guard. - ZFS source-name path injection. A source name flows into dataset names
(e.g.
tank/pgoverlay/src-<name>-gN). A name like../../rpool/ROOTis shell-safe yet would traverse the dataset namespace. Fixed byvalidateSourceName(internal/engine/saga.go:validateSourceName), which applies the same anchored regex ([a-z0-9][a-z0-9-]{0,40}, no/or.) as branch names, gated at the engine boundary (AddSource,internal/engine/engine.go) so every backend and caller is covered. - Cross-PR branch-reset vector in ghook. An untrusted PR ref could collide
with another branch's name (another PR, or a human-created branch), letting one
PR reset another's branch. The first fix namespaced every webhook-created
branch under a
gh-prefix. The v1 review found it incomplete: PR #7 of two allow-listed repositories still sharedgh-pr-7, a fork's branch name could land on one of your own branches ingit-branchmode, and truncated long refs could collide. The rule now lives inpgoverlayconnect/names.go, shared with the connect helpers: a six-hex repository key in every name (gh-<repo-key>-pr-<n>,gh-<repo-key>-<ref>), fork PRs always named by number, and long refs cut and suffixed with a hash of the full ref. - Branch-password plaintext-at-rest. The registry file lives on a
hostPath/PVC; a plaintext
passwordcolumn hands every live branch's working credential to anyone who can read the file. The first fix encrypted it with AES-256-GCM (internal/registry/crypto.go) undersha256(PGOVERLAY_TOKEN)and made the read path fail loudly on an undecryptable value. That turned out to be the wrong coupling: rotating the admin token, the first thing you do after an incident, made every row fail to read and took list, reset, destroy and reconcile down with it (issue #9). Passwords are now encrypted under a dedicated random key with a key id (enc:v2:<kid>:...), legacyenc:v1rows are re-encrypted at startup, and an undecryptable row reads aspassword_unavailableinstead of failing (ADR-10). - Proxy error-message enumeration. The wire proxy authenticates after
routing, so a distinctive error for "unknown branch" vs "branch not ready" vs
"backend down" would let an unauthenticated client enumerate branch names
and states. Mitigated with a single uniform refusal: every routing failure
returns the same
genericRouteRefusal = "pgoverlay: database not available"with SQLSTATE3D000(internal/pgproxy/proxy.go, theroutemethod and thegenericRouteRefusalconst); the real reason is logged server-side only. The residual: a ready branch still answers with its auth challenge, so its existence can be confirmed before authenticating, and a blackholed backend is refused only after the dial timeout.MaxStartupsPerIPbounds concurrent probes but is not a rate limit; a full fix needs the proxy to take part in authentication.
The lesson
A review that re-derives the threat model finds what the author's model misses. The author optimizes for the happy path they designed; a pass that starts from the code and works outward trips over the cases the author rationalized away. The common thread across these five fixes is validate-at-the-boundary (one anchored name gate covers every downstream runtime; one uniform refusal covers every routing failure) and defence in depth (bump the timer and guard the delete). The second review's corrections to three of them are the other lesson: a fix is a new design, and it needs the same adversarial pass as the original. Failing loudly on an undecryptable password was defence in depth on paper and a fleet-wide outage after a token rotation.
7. A copy-on-write system that copies on read
The problem
The whole premise is that a branch shares the seed and pays only for what it
changes. The founding bug (benchmarks) broke that at
create time: Postgres's pre-recovery sync opened every file read-write, and on
OverlayFS a read-write open of a lower-layer file copies the whole file up.
recovery_init_sync_method=syncfs removed that pass, branch creation dropped
to 33 MiB and two seconds, and the docs said a branch "stores only the blocks
it actually changes".
Why it's subtle
The fix was measured, and the measurement was right: a fresh branch really is
33 MiB, and a 1-row UPDATE on a small table leaves it at 33.5 MiB. What was
never measured was a read. The mental model said reads fall through to the
lower layer, which is how OverlayFS is usually described. But OverlayFS
decides on copy-up from the open flags, at open() time, and PostgreSQL's
storage manager opens every relation segment O_RDWR, whatever the query
(src/backend/storage/smgr/md.c; checked in REL_14_STABLE and
REL_17_STABLE). The pre-recovery
sync pass was not a special case; it was the first of many read-write opens.
The v1 review measured it on a 489 MB table frozen on the source (so hint-bit
writes could not explain anything): one SELECT count(*) took the branch from
33.1 MiB to 523.1 MiB, and the new file in the rw layer was the whole table.
The fix
The first fix was positioning. The open flags are how stock Postgres reads, and changing them seemed to mean patching Postgres, so the README, concepts, benchmarks and observability said that a branch grows by every table file it opens, and pointed read-heavy workloads at the backends that copy blocks instead of files: zfs and Kubernetes csi.
The real fix came in v1.0.0 (#49),
after every alternative was measured on one host
(the evaluation). The flags Postgres asks
for do not have to be the flags the kernel sees. An LD_PRELOAD library in
the branch's Postgres, the lazyrw shim, opens relation files read-only and
reopens one read-write, on the same descriptor number, at its first
write-class call, so OverlayFS copies a file when it is written instead of
when it is opened (how).
Two things the first measurement had hidden then mattered:
- The review had frozen its table on the source, so hint bits could not
explain the growth. pgoverlay's own
pg_basebackupseed is not frozen, and with the shim an unfrozen seed still copied 68-119 MiB on the first read, because setting hint bits is a real write. Hence seed settle. - Correctness now rests on a kernel property: a read-only descriptor opened by one backend must see what another backend writes after its upgrade, which OverlayFS guarantees only since Linux 4.19. The entrypoint tests exactly that property on every start instead of trusting a version number, and falls back to the old behaviour, loudly, when it fails.
A read of a 521 MB frozen table went from +555 MiB and 20.8 s to 0 bytes and 277 ms, with no new privileges and stock images.
The lesson
Measure the claim you publish, on the path your users take. The benchmark tested creation and a write, because that is what the design was about, and the headline claim was about reads. And measure the fix on the data your users will have, not the data that makes the measurement clean: the frozen table that isolated the copy-up also hid the hint-bit writes. And copy-on-write is defined by the layer that decides when to copy: the question is never "does the application write?", it is "what does the filesystem see the application ask for?".
Cross-references
- The freeze saga and the overlay model: code tour, core concepts.
- Why a daemon + CLI instead of an operator, why SQLite, why the proxy: design decisions.
- Source of truth is always the code — every
file:funcabove is verifiable.