From 563e7811b9052a6673fd9f05c611acf93b9a8831 Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 24 Aug 2026 23:46:33 +0200 Subject: [PATCH] docs(canon): correct ADR-010 on divergence shapes, mechanism, and outcome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- ...010-hub-authority-and-local-cache-model.md | 70 +++++++++++++++++++ ...WP-0067-hub-authority-target-resolution.md | 22 +++++- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/canon/architecture/adr-010-hub-authority-and-local-cache-model.md b/canon/architecture/adr-010-hub-authority-and-local-cache-model.md index c6f6987..7fdac66 100644 --- a/canon/architecture/adr-010-hub-authority-and-local-cache-model.md +++ b/canon/architecture/adr-010-hub-authority-and-local-cache-model.md @@ -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. diff --git a/workplans/CUST-WP-0067-hub-authority-target-resolution.md b/workplans/CUST-WP-0067-hub-authority-target-resolution.md index d9e6386..1736a74 100644 --- a/workplans/CUST-WP-0067-hub-authority-target-resolution.md +++ b/workplans/CUST-WP-0067-hub-authority-target-resolution.md @@ -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