docs(canon): correct ADR-010 on divergence shapes, mechanism, and outcome
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Three dated corrections, following the blockquote convention ADR-007 uses:

- Decision 2's merge/refresh dichotomy is not exhaustive. Repository records
  took a third shape — cache-only records whose source exists and is reachable
  but was never introduced to central. Refreshing would have destroyed them.
- The Context recorded the shared port but not why it was silent: an unbound
  ssh -L binds every loopback family, and ExitOnForwardFailure fires only when
  every bind fails.
- A new Outcome section states that the ADR's own remediation never landed and
  the condition was still live seven weeks later.

Refs CUST-WP-0067-T06

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
codex 2026-08-24 23:46:33 +02:00
parent 3c1e93186f
commit 563e7811b9
2 changed files with 91 additions and 1 deletions

View file

@ -32,6 +32,20 @@ replica:
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.
> **Mechanism identified 2026-08-24 by `CUST-WP-0067`.** This ADR recorded the
> shared port as the hazard but not why it was *silent*, which is the part that
> let it persist. An `ssh -L` forward with no bind address listens on every
> loopback family, and `ExitOnForwardFailure=yes` fires only when **every**
> requested bind fails. The IPv4 bind lost to the local uvicorn, the IPv6 bind
> succeeded, and the tunnel therefore reported success while serving an address
> nothing resolved to by default. The collision was declared in
> `~/.config/bridge/tunnels.yaml`, and `ops-bridge` guarded only tunnel against
> tunnel, so a non-bridge listener was invisible to it.
>
> Fixed by pinning local forwards to `127.0.0.1`, so a contested port fails
> loudly (`ops-bridge` commit `2213847`). A shared port is recoverable; a shared
> port that reports healthy is not.
Measured divergence — 320 records exist locally but not on the primary:
```text
@ -63,6 +77,23 @@ 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.
> **Corrected 2026-08-24 by `CUST-WP-0067`.** Those two shapes are not
> exhaustive, and repository records took a third one. Measured that day:
> 122 repositories on the cache, 78 on central, **zero central-only**. A strict
> subset in the cache's favour is neither a merge problem nor a refresh problem
> — refreshing the cache would have *destroyed* the 44 extra records rather than
> reconciling them, and central held no path to re-derive repositories it had
> never been told existed.
>
> The third shape: **cache-only records whose authoritative source exists and is
> reachable, but was never introduced to central.** Its remedy is re-derivation
> from source — not refresh, not merge. Of the 44, 43 had a working copy, all 43
> were pushed, and 34 carried a classification file; nothing was unrecoverable,
> but nothing would have recovered itself either.
>
> Onboarding by date showed a clean break: central's repository registrations
> stopped at 2026-07-08 while the workstation kept accepting them.
**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
@ -160,3 +191,42 @@ and by `hub-core` for hub-native records. Tracked under `RMGR-WP-0005`.
- `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
## Outcome (2026-08-24)
> Added by `CUST-WP-0067`. This ADR read as though its remediation had landed.
> It had not: the two-instance condition it measured on 2026-08-17 was still
> live seven weeks later, and had continued to accumulate records the whole
> time. An ADR that describes a fix in the present tense is easily mistaken for
> a fix that happened — worth stating plainly for the ones that follow.
The peer-database arrangement is now resolved, by deletion rather than by
reconciliation:
- **The local hub instance is retired.** Rather than making two instances
coexist safely, the second one was removed. Exactly one process binds
`127.0.0.1:8000` and it is the tunnel to central. Nothing remains that could
impersonate the primary, and no call site needed editing — retiring the
impersonator made the existing `127.0.0.1:8000` defaults correct.
- **Decision 3 carried the argument.** Because local work requires no hub at
all, and Repo Manager already maintains a file-derived index, the local
instance was redundant rather than load-bearing.
- **Repo Manager gained the write path it was already assigned.**
`hub-record-authority.yaml` gives it `managed_repos` as `file-derived`, but it
exposed no command for it; the only working path lived in the State Hub repo
and defaulted to a local address. `rmgr repo-onboard` closes that, and refuses
to onboard a repository whose backing file is uncommitted, unpushed, or has no
upstream — a record whose source is only local cannot be re-derived, which is
the failure this ADR exists to prevent.
- **33 of the 44 were re-derived onto central**, taking it from 78 to 111
repositories. The remaining 11 carry written dispositions rather than guessed
values, per the orphan-disposition principle above.
One cause sat deeper than the topology: the hub resolved its classification
allowed-values file from three hardcoded developer-workstation checkouts, so in
a container every classification write failed. Repository classification could
*only* be written from a workstation. That is a second instance of this ADR's
own theme — authority that depends on where a process happens to run is not
authority — and is why "central derives" had never been achievable for this
record type.

View file

@ -265,7 +265,7 @@ only there.
```task
id: CUST-WP-0067-T06
status: todo
status: done
priority: medium
state_hub_task_id: "007bfcf1-3b17-5d4a-b16a-b80ebf273934"
```
@ -290,6 +290,26 @@ this record class and record the local-instance retirement as the outcome.
Acceptance: ADR-010 revised with a dated superseding note linked to this
workplan; `ops-bridge` and the port map documented as the structural fix.
**Done (2026-08-24):** three dated corrections added to `ADR-010`, following the
in-file blockquote convention `ADR-007` already uses.
- **Decision 2** — the merge/refresh dichotomy is not exhaustive. Records the
third shape: cache-only records whose authoritative source exists and is
reachable but was never introduced to central, remedied by re-derivation
rather than refresh or merge. Refreshing would have destroyed the 44.
- **Context** — the ADR recorded the shared port but not why it was silent,
which is the part that let it persist for seven weeks. An unbound `ssh -L`
binds every loopback family and `ExitOnForwardFailure` fires only when all
binds fail, so the tunnel reported success while serving an address nothing
resolved to by default.
- **Outcome section** — states plainly that the ADR's own remediation never
landed, and that the condition it measured was still live seven weeks later.
An ADR describing a fix in the present tense reads as a fix that happened.
Also records the deeper cause as a second instance of the ADR's own theme:
the hub resolved its classification vocabulary from developer-workstation paths,
so authority depended on where a process happened to run.
## Repoint the fleet's copied port map
```task