docs(canon): ADR-008 hub authority and local cache model (proposed)
Central hub on railiance is authoritative as a reading of the repositories; local instances become rebuildable caches, never peer databases. Local work requires no hub at all — repo files are self-describing. Classifies hub data by origin: file-derived (central derives, never accepts pushes, conflicts are git conflicts) vs hub-native (central owns, needs a write path and an append-only offline buffer). Neither kind needs a hub-side conflict model. Measured divergence: 955 local / 649 primary / 320 local-only, of which 288 are backed by files that all exist on disk. Only 28 orphans need disposition, and they are the ADR-001 violations. Corrects ADR-007's 'development read replica' wording — the workstation instance was the larger of the two by 306 workplans. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
495ecc61bd
commit
f8038ecde5
2 changed files with 167 additions and 3 deletions
|
|
@ -109,9 +109,15 @@ Decision 1 must therefore be enforced before derivation ships.
|
||||||
|
|
||||||
*Interim state (A).* Until derivation lands, exactly one instance writes hub
|
*Interim state (A).* Until derivation lands, exactly one instance writes hub
|
||||||
identifiers into repository files. Other instances may read, project, and serve,
|
identifiers into repository files. Other instances may read, project, and serve,
|
||||||
but must not mint workplan or task UUIDs into git-tracked files. The interim
|
but must not mint workplan or task UUIDs into git-tracked files.
|
||||||
registrar is the automated production instance; workstation hubs are development
|
|
||||||
read replicas.
|
> **Corrected 2026-08-17, superseded by `ADR-008` decisions 1–3.** This decision
|
||||||
|
> originally described workstation hubs as "development read replicas". That was
|
||||||
|
> wrong on both counts: the workstation instance was not a replica, and it was
|
||||||
|
> the *larger* of the two, holding 306 more workplans than the primary. The two
|
||||||
|
> instances were peer databases. `ADR-008` establishes the central hub as
|
||||||
|
> authoritative and local instances as rebuildable caches, which is what makes
|
||||||
|
> this interim rule coherent.
|
||||||
|
|
||||||
The interim is policy, enforced by discipline, and it has a real cost:
|
The interim is policy, enforced by discipline, and it has a real cost:
|
||||||
registration requires connectivity to the registrar, so disconnected work cannot
|
registration requires connectivity to the registrar, so disconnected work cannot
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
---
|
||||||
|
id: ADR-008
|
||||||
|
type: architecture-decision-record
|
||||||
|
title: "Hub Authority, Local Cache, and the Two Kinds of Hub Data"
|
||||||
|
status: proposed
|
||||||
|
decided_by: Bernd Worsch
|
||||||
|
date: "2026-08-17"
|
||||||
|
tags: ["architecture", "state-hub", "repo-manager", "hub-core", "authority", "cache", "offline", "adr-001"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# ADR-008: Hub Authority, Local Cache, and the Two Kinds of Hub Data
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Proposed.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Investigating a workplan-identifier flip-flop (`ADR-007`) revealed that two
|
||||||
|
State Hub instances were operating as **peer databases**, not as a primary and a
|
||||||
|
replica:
|
||||||
|
|
||||||
|
| Address | Instance | Workplans |
|
||||||
|
|---|---|---|
|
||||||
|
| `127.0.0.1:8000` (IPv4) | local uvicorn on docker postgres | 955 |
|
||||||
|
| `[::1]:8000` (IPv6) | ssh tunnel `state-hub-primary` → railiance01 | 649 |
|
||||||
|
|
||||||
|
Both listen on port 8000, separated only by IP family, so every tool defaulting
|
||||||
|
to `127.0.0.1` reached the local instance while believing it was the primary.
|
||||||
|
|
||||||
|
Measured divergence — 320 records exist locally but not on the primary:
|
||||||
|
|
||||||
|
```text
|
||||||
|
backed by a file that exists on disk 288 fully rebuildable
|
||||||
|
no backing file recorded 28 not rebuildable as-is
|
||||||
|
same filename, different UUID 4 duplicate registration
|
||||||
|
```
|
||||||
|
|
||||||
|
**No file was missing for any of the 288.** The divergence is therefore almost
|
||||||
|
entirely redundant: it can be discarded and rebuilt from repositories without
|
||||||
|
losing anything.
|
||||||
|
|
||||||
|
`ADR-001` already says work originates as repository files and the hub is a read
|
||||||
|
model. The two-peer-database situation contradicts that in practice: a second
|
||||||
|
database accumulated 306 net records of its own.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
**1. One authoritative hub, deriving from repository files.** The central State
|
||||||
|
Hub — running on railiance — is authoritative. It is authoritative *as a reading
|
||||||
|
of the repositories*, not as a second place data lives. Repository files remain
|
||||||
|
the source of truth (`ADR-001`).
|
||||||
|
|
||||||
|
**2. A local hub is a cache, never a database.** Local instances hold a
|
||||||
|
rebuildable projection. A cache may be discarded and reconstructed from the
|
||||||
|
repositories at any time, and losing it must never lose work.
|
||||||
|
|
||||||
|
This replaces the peer-database arrangement. It is also why the divergence is
|
||||||
|
tractable: a divergent database is a merge problem, a stale cache is a refresh
|
||||||
|
problem.
|
||||||
|
|
||||||
|
**3. Local work requires no hub at all.** Repository files are self-describing —
|
||||||
|
identifier, status, tasks, all in frontmatter. Working in a repository requires
|
||||||
|
reading files, not querying an index. A cache is an optimization for cross-repo
|
||||||
|
questions, never a prerequisite for doing work.
|
||||||
|
|
||||||
|
**4. Hub data is classified by origin, and the two kinds have opposite rules.**
|
||||||
|
|
||||||
|
| | File-derived | Hub-native |
|
||||||
|
|---|---|---|
|
||||||
|
| Examples | workplans, tasks, statuses, dependencies | progress events, decisions, inbox messages, token events |
|
||||||
|
| Source of truth | the repository file | the hub |
|
||||||
|
| Offline behaviour | write the file and commit — the commit *is* the write | buffer locally, replay when reachable |
|
||||||
|
| Conflict model | **none**; conflicts are git conflicts, resolved in git | **none**; append-only merges regardless of order |
|
||||||
|
| Central accepts pushes? | no — it derives | yes |
|
||||||
|
|
||||||
|
Neither kind needs a hub-side conflict model. That is the point of the split: if
|
||||||
|
central derives file-backed state, it cannot hold a conflicting version of it —
|
||||||
|
it re-derives whatever git settles on. Two people editing one workplan is a git
|
||||||
|
conflict and belongs to git.
|
||||||
|
|
||||||
|
**5. Central derives file-backed state; it does not accept pushes of it.**
|
||||||
|
"Authoritative" means authoritative *reading*, so nothing may inject derived
|
||||||
|
state directly. Hub-native records are the exception and keep a real write path.
|
||||||
|
|
||||||
|
**6. Preliminary until confirmed.** Locally registered data and uncommitted
|
||||||
|
repository state are preliminary until the central service has seen them.
|
||||||
|
Mitigation is by changing the repository files and the local cache — never by
|
||||||
|
editing central to match a local view.
|
||||||
|
|
||||||
|
Combined with `ADR-007` decision 2 (identifiers derived from `PREFIX-WP-NNNN`),
|
||||||
|
"preliminary" largely stops mattering: a cache computes the same identifier
|
||||||
|
central will, so offline-registered data is already correct on arrival and needs
|
||||||
|
confirmation rather than reconciliation.
|
||||||
|
|
||||||
|
**7. Every record has exactly one authoritative hub.** The State Hub retirement
|
||||||
|
splits one hub into several. Multiple central hubs are permitted only under this
|
||||||
|
rule: the owning hub is determined by the record's repository and domain. Without
|
||||||
|
it, the same peer-database divergence recurs at larger scale.
|
||||||
|
|
||||||
|
**8. Cache reads are advisory and must carry their age.** Cross-repo answers from
|
||||||
|
a cache are advisory and should be presented with staleness. For the repository
|
||||||
|
an agent is working in, the file is truth and the cache is never consulted for
|
||||||
|
correctness.
|
||||||
|
|
||||||
|
## Orphan disposition
|
||||||
|
|
||||||
|
The 28 records with no backing file are the only ones a cache rebuild would drop.
|
||||||
|
They fall into three classes, to be separated before any rebuild:
|
||||||
|
|
||||||
|
1. **Broken links** — a file exists but `backing_filename` was never recorded.
|
||||||
|
`RMGR-WP-0004` is one: the workplan file exists and is committed. These are
|
||||||
|
metadata repairs, not data loss, and are likely the largest class.
|
||||||
|
2. **Live hub-first records** — `proposed`, `ready`, or `backlog` with no file,
|
||||||
|
in `activity-core`, `core-hub`, `hub-core`, `issue-core`, `ops-hub`,
|
||||||
|
`prj-forgejo-org-refactor`, `railiance-enablement`, `railiance-infra`,
|
||||||
|
`reef-railiance`. Each needs a repository file written or an explicit drop.
|
||||||
|
These are `ADR-001` violations and must not be preserved as hub-only records.
|
||||||
|
3. **Closed hub-first records** — `finished` or `archived` with no file. Retain
|
||||||
|
as historical provenance where cheap; do not reconstruct plans that are done.
|
||||||
|
|
||||||
|
A cache rebuild enforces `ADR-001` retroactively: the only casualties are records
|
||||||
|
that broke it.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
**Positive.** The divergence becomes discardable rather than mergeable. Offline
|
||||||
|
work is fully supported without a write buffer for file-backed state — the git
|
||||||
|
commit is the write. No hub-side conflict model is needed for either data kind.
|
||||||
|
Authority stops being a policy claim and becomes a structural property.
|
||||||
|
|
||||||
|
**Negative.** The 28 orphans require case-by-case disposition before a rebuild.
|
||||||
|
Any consumer that treats a local hub as authoritative must be corrected. The
|
||||||
|
one-hub-per-record rule constrains the retirement's hub split.
|
||||||
|
|
||||||
|
**Rescoping.** `STATE-WP-0068` (offline write buffer and edge relay) is scoped as
|
||||||
|
a single mechanism. Under decision 4, most of what it buffers does not need
|
||||||
|
buffering — only the append-only hub-native stream does. Its scope should be
|
||||||
|
re-examined before more is built on it; this may reduce work rather than add it.
|
||||||
|
|
||||||
|
**Correction to `ADR-007`.** Decision 2 there calls the workstation instance a
|
||||||
|
"development read replica". It was neither a replica nor smaller — it held 306
|
||||||
|
more workplans than the primary. Superseded by decisions 1–3 here.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
Owned by `repo-manager` for file-derived state (decision `747011c6`; it already
|
||||||
|
owns repository representation, file-backed record indexing, and reconciliation)
|
||||||
|
and by `hub-core` for hub-native records. Tracked under `RMGR-WP-0005`.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- ADR-001 — workplans originate as repo files; hub is a read model
|
||||||
|
- ADR-007 — identifier uniqueness, derived identifiers, worker topology
|
||||||
|
- Decision `747011c6` — repository standards belong to Repo Manager
|
||||||
|
- `RMGR-WP-0005` — registrar consolidation and deterministic identifiers
|
||||||
|
- `STATE-WP-0068` — offline write buffer and edge relay (rescope candidate)
|
||||||
|
- Divergence measurement, 2026-08-17: 955 local / 649 primary / 320 local-only
|
||||||
Loading…
Add table
Add a link
Reference in a new issue