docs(canon): correct ADR-010 on divergence shapes, mechanism, and outcome
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:
parent
3c1e93186f
commit
563e7811b9
2 changed files with 91 additions and 1 deletions
|
|
@ -32,6 +32,20 @@ replica:
|
||||||
Both listen on port 8000, separated only by IP family, so every tool defaulting
|
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.
|
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:
|
Measured divergence — 320 records exist locally but not on the primary:
|
||||||
|
|
||||||
```text
|
```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
|
tractable: a divergent database is a merge problem, a stale cache is a refresh
|
||||||
problem.
|
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 —
|
**3. Local work requires no hub at all.** Repository files are self-describing —
|
||||||
identifier, status, tasks, all in frontmatter. Working in a repository requires
|
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
|
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
|
- `RMGR-WP-0005` — registrar consolidation and deterministic identifiers
|
||||||
- `STATE-WP-0068` — offline write buffer and edge relay (rescope candidate)
|
- `STATE-WP-0068` — offline write buffer and edge relay (rescope candidate)
|
||||||
- Divergence measurement, 2026-08-17: 955 local / 649 primary / 320 local-only
|
- 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.
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ only there.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: CUST-WP-0067-T06
|
id: CUST-WP-0067-T06
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "007bfcf1-3b17-5d4a-b16a-b80ebf273934"
|
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
|
Acceptance: ADR-010 revised with a dated superseding note linked to this
|
||||||
workplan; `ops-bridge` and the port map documented as the structural fix.
|
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
|
## Repoint the fleet's copied port map
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue