beyond-local-file

Daemon design

One OS process loads one configuration set, catch-up’s projections, then observes those trees until stop. It is the only writer of projections and of mapping edits that originate from blf. Terms are in CONTEXT.md; decisions are in docs/adr/.

Hub and replicas

A link is a physical copy, so an edit in a target is invisible to the managed project until the daemon copies it. The managed project is the hub. A successful apply increments a per-path generation, then fans out to in-sync replicas except the source.

Hub apply then fan-out to in-sync replicas, skipping an out-of-sync replica Hub managed project · generation n+1 observe update fan-out skip Replica A · source already has the bytes not written by fan-out Replica C · in-sync receives generation n+1 disk matched expected base Replica B · out-of-sync loser bytes stay on disk further edits discarded 0002 hub-and-fan-out · 0010 isolate the replica, keep the hub moving
Fan-out never writes the source replica and never writes an out-of-sync replica.

Shape of the process

One process, two programs in one address space. The accept thread binds JSON IPC and answers status without hashing. Each managed project is a worker unit: one queue, at most one job, a LiveSync over that hub and its targets. Distinct units run in parallel. A mapping unit (hub × one target) is an expansion row, not a thread.

Shells and the resolve UI enter through accept and HTTP; work runs on per-hub worker units blf shells create · check · reload browser resolve UI · o on status JSON IPC HTTP 127.0.0.1 Accept thread status never joins a queue does not hash Resolve UI HTTP bound only in phase ready does not write the hub enqueue Worker units — one queue per managed project · at most one job each · units in parallel Worker unit · hub α idle observe · mailbox apply catch-up · shells · resolve apply Worker unit · hub β own trees, own 15s idle file lock if both splice yaml
0021: hashing or mutating a hub is a job on that unit. Submit from the resolve UI is a job too.

Lifecycle

IPC binds before catch-up. Start stays in the foreground until phase ready. Shells other than status wait through catch-up. Live observation and the resolve UI start only in ready. Out-of-sync flags and held copies survive catch-up; catch-up does not overwrite an isolated replica.

Start classifies mappings if needed, spawns, catch-up, then ready live loop until stop daemon start files ≠ snapshot? or no snapshot yes removal plan y / n on TTY y commit ingest n abort no / match spawn · bind IPC phase catch-up · status has pid baseline exists? 0003 no fresh catch-up yes update catch-up phase ready live observe · resolve UI binds idle · shells · resolve until daemon stop 0019 IPC during catch-up · live observation only in ready
Decline a removal plan, or start with no TTY when removals exist, and nothing is spawned.

Live path

Each worker unit scans its trees, coalesces in a mailbox keyed by (path, replica), then applies one change at a time. Update is compare-and-swap. Delete uses a generation window of 3 and held copies, not isolation.

Observe into mailbox, apply at hub, fan-out or isolate observe trees mailbox (path, replica) at most one pending · 0008 apply one change kind? create / update / delete delete gap ≤ 3? or base matches yes · delete-wins held copy, then delete update / create base = hub? CAS · 0010 yes · write hub mark stale-base legal apply fan-out generation replica eligible? not source · not OOS no pending · disk = base no skip / fan-out-mismatch yes copy or delete replica Isolation stays until bytes match the hub, or resolve overwrites the path stale-base does not write a held copy · 0009 / 0010
A mailbox pending on a replica also skips fan-out of that path, so a second replica is not overwritten while its own change is queued.

Mapping edits

The daemon does not watch mapping files. External edits take effect only by diffing the configuration set against the persisted mapping snapshot: on start, before spawn; on reload, when already ready. Internal revlink / remove writes do not go through this classifier.

Same classifier on start and reload; removals confirmed; adds automatic mapping files set now mapping snapshot last committed classify start or reload · 0007 removals · one plan project → target → item confirm y/n · n commits nothing additions apply automatically after catch-up new snapshot start: then ready reload: only units that changed
Same classifier both times. Start runs it in the foreground before spawn; reload runs it against the already-ready process.

Resolve UI

Status lists isolation; it does not pick a winner. While ready, the daemon serves localhost HTML on a second port (token in the set run directory). Submit is resolve: a new hub generation, every replica of that path overwritten, out-of-sync cleared for that path only.

Sequential merge in the browser, submit enqueues resolve on the worker unit status · o or printed URL hub-now left pane result editable middle replica N one differing copy mark as merged → N+1 · 0025 submit confirmed fact resolve hub gen +1 overwrite every replica clear OOS for path
Start does not ask an isolation ack: this UI is served by the running daemon. Reload still warns and acks. Held rows list a clause only; restore stays later.