Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ jobs:
- uses: actions/checkout@v4
- run: bash docs/validate_docs.sh
- run: python3 scripts/check_arch_tree.py
# Cross-reference resolution: ~3,300 ADR/concern/disagreement/path citations across the
# governance corpus, none of which was checked before 2026-09-02. Offline by design —
# issue references need the network and are left to a manual `--check-issues` sweep.
- run: python3 scripts/check_doc_refs.py

# Formatting, kept out of the four-version matrix for the same reason as the
# docs job: `ruff format` does not depend on the Python version, so running it
Expand Down
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ CI additionally gates 100% line+branch coverage

## Governance

Constitutional ADRs 000–010, project ADRs 011–026, CICs for every non-trivial
surface (7 active incl. the package-level `Summarize.md` and `Reconcile.md`),
Constitutional ADRs 000–009, the governance ADR 010, project ADRs 011–029, CICs for
every non-trivial surface (9 active incl. the package-level `Summarize.md` and
`Reconcile.md`),
contributor protocols, and standards live in `docs/`. The technical risk register
(`reports/technical_risk_register.md`) is the curated concern/decision log. Run
`bash docs/validate_docs.sh` to check documentation consistency. Build *against*
Expand Down
4 changes: 2 additions & 2 deletions docs/ADRs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ ADRs are divided into:

1. **Constitutional ADRs (000–009)** — foundational architectural rules.
2. **Governance ADRs (010)** — the technical risk register.
3. **Project-Specific ADRs (011–027)** — the ratified contract, estimator, sibling-package,
3. **Project-Specific ADRs (011–029)** — the ratified contract, estimator, sibling-package,
freeze/immutability, and surface-scope decisions.

---
Expand Down Expand Up @@ -51,7 +51,7 @@ Together, these define the invariant layer of the system.

---

## Project-Specific ADRs (011–027) — the ratified project decisions
## Project-Specific ADRs (011–029) — the ratified project decisions

**011–016** ratify the six founding contract decisions from the design bible (README §13a,
all Accepted 2026-06-21); **017–027** are the post-v1 decisions — sibling packages, the API
Expand Down
125 changes: 125 additions & 0 deletions docs/validate_docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,131 @@ fi
[ "$errors" -eq "$before" ] && echo " OK (wheel ships $pkg_count packages; checked $(printf '%s' "$WHEEL_DOCS" | wc -w) wheel-describing documents)"


# 11. Counts stated in prose match what is on disk.
#
# Three documents state numbers derived from the doc set itself — how many active CICs
# exist, which range the project ADRs span, how the register's totals add up. Every one is
# a hand-maintained copy of something countable, and on 2026-09-02 two of them were wrong
# AND wrong differently from each other: CLAUDE.md said "7 active" CICs against 9 on disk
# and "project ADRs 011-026", while the ADR index said "011-027", both against 029. Two
# copies of one number drifting independently is the signature of no shared source.
echo "--- Checking counts stated in prose against the filesystem ---"
before=$errors
cic_actual=$(ls CICs/*.md 2>/dev/null | grep -vE 'README|template' | wc -l | tr -d ' ')
adr_max=$(ls ADRs/[0-9]*.md 2>/dev/null | sed 's|.*/||;s/_.*//' | sort -n | tail -1)
if [ -z "$cic_actual" ] || [ "$cic_actual" -eq 0 ] || [ -z "$adr_max" ]; then
echo " ERROR: could not count CICs or ADRs on disk; the check cannot be vacuously true"
errors=$((errors + 1))
else
claimed_cic=$(grep -oE '\(([0-9]+) active' ../CLAUDE.md 2>/dev/null | grep -oE '[0-9]+' | head -1)
if [ -n "$claimed_cic" ] && [ "$claimed_cic" != "$cic_actual" ]; then
echo " ERROR: CLAUDE.md claims $claimed_cic active CICs; $cic_actual exist"
errors=$((errors + 1))
fi
# Every "project ADRs NNN-MMM" style range must end at the highest ADR on disk.
while IFS=: read -r file line; do
[ -z "$file" ] && continue
end=$(echo "$line" | grep -oE '0[0-9]{2}[^0-9]{1,3}0[0-9]{2}' | tail -1 | grep -oE '0[0-9]{2}$')
[ -z "$end" ] && continue
if [ "$end" != "$adr_max" ]; then
echo " ERROR: $file states a project-ADR range ending $end; the highest ADR is $adr_max"
errors=$((errors + 1))
fi
done <<EOF
$(grep -niE 'project(-specific)? ADRs? \(?0[0-9]{2}' ../CLAUDE.md ADRs/README.md 2>/dev/null | sed 's/^\([^:]*\):[0-9]*:/\1:/')
EOF
# The register's header must agree with its own body.
reg=../reports/technical_risk_register.md
if [ ! -f "$reg" ]; then
echo " ERROR: no $reg; cannot check its header arithmetic"
errors=$((errors + 1))
else
hdr_total=$(grep -m1 '^| Total Concerns' "$reg" | grep -oE '[0-9]+')
hdr_open=$(grep -m1 '^| Open Concerns' "$reg" | grep -oE '[0-9]+')
hdr_res=$(grep -m1 '^| Resolved Concerns' "$reg" | grep -oE '[0-9]+')
body_total=$(grep -cE '^### C-[0-9]+' "$reg")
body_open=$(grep -E '^### C-[0-9]+' "$reg" | grep -vc RESOLVED)
if [ "$hdr_total" != "$body_total" ]; then
echo " ERROR: register header says $hdr_total concerns; $body_total entries exist"
errors=$((errors + 1))
fi
if [ "$hdr_open" != "$body_open" ]; then
echo " ERROR: register header says $hdr_open open; $body_open entries are not RESOLVED"
errors=$((errors + 1))
fi
if [ "$((hdr_open + hdr_res))" != "$hdr_total" ]; then
echo " ERROR: register header does not add up: $hdr_open open + $hdr_res resolved != $hdr_total"
errors=$((errors + 1))
fi
fi
fi
[ "$errors" -eq "$before" ] && echo " OK ($cic_actual active CICs, ADRs to $adr_max, register header reconciles)"

# 12. Every test file a CIC names in its Test Alignment section still exists.
#
# Each CIC's Section 10 cites the tests that hold its guarantees. Those citations are the
# only link between a contract and its evidence, and nothing stopped a rename stranding
# one — a CIC would keep claiming a test that no longer exists, which is worse than
# claiming none.
echo "--- Checking test files named in CIC Test Alignment sections ---"
before=$errors
ref_count=0
while read -r ref; do
[ -z "$ref" ] && continue
ref_count=$((ref_count + 1))
file="../${ref%%::*}"
if [ ! -f "$file" ]; then
echo " ERROR: a CIC names $ref, but that test file does not exist"
errors=$((errors + 1))
continue
fi
case "$ref" in
*::*)
fn="${ref##*::}"
grep -qE "^def ${fn}\(" "$file" || {
echo " ERROR: a CIC names $ref, but $file defines no $fn"
errors=$((errors + 1))
} ;;
esac
done <<EOF
$(grep -rhoE 'tests/test_[A-Za-z0-9_]+\.py(::[A-Za-z0-9_]+)?' CICs/*.md 2>/dev/null | sort -u)
EOF
if [ "$ref_count" -eq 0 ]; then
echo " ERROR: found no test references in any CIC; the check cannot be vacuously true"
errors=$((errors + 1))
fi
[ "$errors" -eq "$before" ] && echo " OK (checked $ref_count test references named in CICs)"

# 13. An amendment is declared on both sides.
#
# When ADR-A carries "Amendment (date, ADR-B)", ADR-B must mention ADR-A. A one-sided
# amendment is how a reader arrives at the amended document and never learns it was
# amended. The ADR-018 / ADR-025 / ADR-028 triangle was correct by hand; this keeps it so.
echo "--- Checking amendment declarations are two-sided ---"
before=$errors
amend_count=0
for adr in ADRs/[0-9]*.md; do
self=$(basename "$adr" | sed 's/_.*//')
while read -r other; do
[ -z "$other" ] && continue
amend_count=$((amend_count + 1))
target=$(ls ADRs/${other}_*.md 2>/dev/null | head -1)
if [ -z "$target" ]; then
echo " ERROR: $adr names an amendment by ADR-$other, which has no file"
errors=$((errors + 1))
continue
fi
grep -qE "ADR-0*${self#0}|ADR-${self}" "$target" || {
echo " ERROR: $adr says it was amended by ADR-$other, but ADR-$other never mentions ADR-$self"
errors=$((errors + 1))
}
done <<EOF
$(grep -oE 'Amendment \([^)]*ADR-([0-9]{3})' "$adr" 2>/dev/null | grep -oE '[0-9]{3}$' | sort -u)
EOF
done
[ "$errors" -eq "$before" ] && echo " OK (checked $amend_count amendment declaration(s))"


echo ""
if [ "$errors" -gt 0 ]; then
echo "=== FAILED: $errors issue(s) found ==="
Expand Down
58 changes: 56 additions & 2 deletions reports/technical_risk_register.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
| Project | views-frames |
| Owner | VIEWS platform maintainers |
| Last Updated | 2026-08-18 |
| Total Concerns | 94 |
| Total Concerns | 95 |
| Open Concerns | 10 |
| Resolved Concerns | 84 |
| Resolved Concerns | 85 |
| Disagreements | 12 |

---
Expand Down Expand Up @@ -441,6 +441,48 @@ Cross-refs: C-47 (eval provenance kept out of the generic header — the precede

> Resolved 2026-07-31 by **ADR-027** (Epic #208 / S1 #209) — the #113 decision.

### C-98: counts stated in prose drifted from what is on disk — RESOLVED

| Field | Value |
|-------|-------|
| ID | C-98 |
| Tier | 4 |
| Resolved | 2026-09-02 |
| Resolution | Four stale counts corrected, and `docs/validate_docs.sh` check 11 added so prose counts are held against the filesystem. |
| Source | governance consistency review, Phase 0 (2026-09-02) |
| Cross-refs | C-96 (the wheel package count — the same class, one document over), C-70 (the README banner epoch-lag that produced check 6), C-84/C-86 (the stale trees), the *unchecked completeness claims* cluster. |

Three documents stated numbers derived from the document set itself, and they were wrong
**differently from each other** — which is the signature of hand-maintained copies with no
shared source:

| Site | Claimed | Actual |
|---|---|---|
| `CLAUDE.md` | "CICs … (**7 active**)" | **9** |
| `CLAUDE.md` | "project ADRs **011–026**" | **011–029** |
| `docs/ADRs/README.md` (×2) | "Project-Specific ADRs (**011–027**)" | **011–029** |

Two documents counting the same range, disagreeing with each other *and* with the filesystem,
is worse than either being wrong alone: a reader who checks one against the other finds a
contradiction with no way to tell which is right.

**A third defect surfaced while fixing them.** `CLAUDE.md` described the ADR set as
"Constitutional ADRs 000–010, project ADRs 011–026", folding **ADR-010** into the
constitutional range. The ADR index has three sections, not two — constitutional 000–009, a
**Governance ADRs** section containing only ADR-010, then project 011–029. The index's
structure is the authority; `CLAUDE.md` now matches it.

Check 11 pins the active-CIC count, every "project ADRs NNN–MMM" range against the highest ADR
on disk, and the register's own header against its body (total, open, and that open + resolved
sums to total). Mutation-tested: a drifted CIC count, a new ADR landing with ranges unupdated,
and a header that stops matching its body all error.

**What it does not catch:** a count stated in a phrasing the pattern does not match. This is a
targeted check on three known claim shapes, not a general numeric auditor — a general one would
flag every number in the corpus and be switched off within a week.

---

### C-97: the status banners asserted a release state the repo cannot verify — RESOLVED

| Field | Value |
Expand Down Expand Up @@ -1943,6 +1985,18 @@ A spatial-forecasting showcase with no spatial display under-serves the audience
- **Skipped ids:** **C-04** was merged into C-18 (the "SpatialLevel slippery slope"). **C-30** is intentionally skipped — it is *pipeline-core's* external id for the cross-repo contract-test gap (referenced in ADR-005 / ADR-016), not a views-frames concern. **C-48** is intentionally skipped — it is *views-reporting's* external id for the run-identity concern (referenced in D-02 / ADR-020), not a views-frames concern.
- **Foreign ADR references:** an unqualified `ADR-xxx` always means *this* repository's ADR. A sibling repo's ADR must name the repo ("views-datafactory's ADR-044 **there**"). Three currently referenced numbers — **ADR-034** (pipeline-core), **ADR-044** (views-datafactory), **ADR-055** (paired with a `D-29` that does not exist here) — have no file in `docs/ADRs/`, which is correct, but only one of the three said so plainly. Same rule as the concern-id convention below.
- **Foreign ids (collisions, not skips):** unlike the skipped ids above, **C-65** exists in *both* registers — pipeline-core's C-65 is the reversed entity-first tuple (cited in **C-18**), while *this* register's C-65 is the non-finite fail-loud blocked-path gap (resolved 2026-06-28). Any cross-register id must name its repo; an unqualified `C-xx` always means this register.
- **The full non-local id list (added 2026-09-02).** The two conventions above named the ids
that had come up in conversation; a cross-reference sweep found nine more already cited in
this corpus and documented nowhere, which is the same shape as the drift they exist to
prevent. `scripts/check_doc_refs.py` holds the machine-readable copy and **fails if an id it
allowlists is not also named here**, so the two halves cannot diverge again. Foreign
concerns: **C-30**, **C-48**, **C-108**, **C-135**, **C-164**, **C-165**, **C-167**,
**C-184**, **C-186** (views-reporting) and **C-198** (views-pipeline-core). Foreign
disagreements: **D-28**, **D-33** (views-pipeline-core) and **D-29** (paired with ADR-055).
Skipped local id: **C-04**. Note that `perspectives/` and `critiqus/` are written from other
repositories' points of view — ids and paths in those documents are the *author's* namespace
and are deliberately not resolved against this register.

- **Causal clusters** (assigned by `review-rr`, last reviewed **2026-08-17**). This list is the **single authority** on clustering — the Open-section preamble points here and must not restate it:
- **doc↔code topology drift** = {**C-86**; resolved C-82, C-84; + resolved C-09 as the origin, C-39, C-23, C-70} — **REOPENED 2026-08-17, same day it was closed.** S1 #241 and S2 #242 closed C-82 and C-84, and the closure note said those two were "the whole of it". One story later, S3's sweep for documents restating what it was correcting found a **third** stale shape-claim — the `README.md` directory tree, missing 11 of 36 modules, plus a §9 pointer sending consumers to a `tests/conformance/` path that does not exist (C-86). The correction is recorded rather than quietly amended, because a cluster declaring itself closed while a member remains is the same failure the cluster is about. *The documents that describe the system's shape were never re-verified against it.* ADR-002 and `docs/standards/physical_architecture_standard.md` both describe an intended structure that the code moved past: `io/` on top importing the frames (the code is the inverse), a directory tree missing three shipped modules and containing two that never shipped, and one of three packages. The origin is datable — **C-09**, resolved 2026-06-21, moved `io/` onto a generic state-dict contract and inverted the dependency; neither topology document was amended, and `Persistable` (which puts `save`/`load` on the frame) makes the code's direction the only one available under the ADR-018 freeze. **The two entries were one editing session**, and were sequenced as one: the C-82 amendment touched `physical_architecture_standard.md:48-49,:67`, exactly where C-84's rewrite started, so S2 was blocked on S1 rather than run beside it. The standard now carries a `Last reviewed` date, a perishability note, and `scripts/check_arch_tree.py`, which makes the next drift detectable in one command. Distinguished from the cluster below by *what* is unverified: here it is a claim about structure, there it is a claim about coverage.
- **unchecked completeness claims** = {C-85, C-77, C-80; + resolved C-64, C-74, C-75, C-81, C-51, C-67} — **an artifact asserts something about its own coverage or result, and nothing checks the assertion.** `docs/CICs/README.md` has claimed "fully contracted" wrongly three times (C-64 `Reconcile.md`, C-81 `Conformance.md`, now `FrameMetadata` in C-85); `GOVERNANCE.md` names three of six published conformance exports; ADR-018 inventories a frozen surface that omits `feature_names`; resolution fields described intent rather than result four times in one epic (C-77); the test suite's self-description does not match its contents (C-80). C-81's own resolution text is the tell: it was *"found only because this claim of completeness was audited against the code."* **The remedy is one mechanical change, not five edits:** `docs/validate_docs.sh` already runs in CI (C-74) and already checks placeholders, dangling references and the version banner — it checks no enumeration. Three assertions would have caught four of these findings automatically (every `__all__` name appears in its CIC; every public class has a CIC or a listed exemption; `GOVERNANCE.md`'s conformance names match `conformance.__all__`). Correcting the lists without the script edit schedules the fourth instance. **Tier within this cluster follows who reads the claim:** an external reader (a consumer running the floor, a future maintainer executing C-66's MAJOR instructions) → Tier 3; an internal auditor → Tier 4. That is why C-85 and C-77 are 3 while C-80 is 4, and the rule should be applied to any entry joining this cluster.
Expand Down
Loading
Loading