compare state.md's currency instead of declaring it

The verified-against sha could only ever be wrong. A commit cannot name itself,
so the line had to be written after the commit it described, which forced a
trailing `state:` commit every time — against conventions.md, which has always
said code, test, state.md row and ADR belong in one commit. Folding those
trailing commits away then left the sha naming a commit that no longer existed.
backup/pre-fold shows the pattern, and 8905686 is the commit that had to name
the survivor afterwards.

Git already knows when each file last changed. The gate now compares the last
commit touching docs/state.md against the last touching a .go file, and the
line is gone. Same intent, nothing to maintain, and no rewrite can invalidate it.

Also records two commit rules the human stated this session: state.md never gets
a commit of its own, and a minor change asked for just after a commit is amended
into it while it is unpushed rather than accumulating as noise.
This commit is contained in:
Claude Opus 5
2026-08-01 10:54:30 +06:00
committed by bdeshi
parent b5ec3bfc9d
commit 7f9ac3c412
8 changed files with 55 additions and 19 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ description: Reconcile the docs against the actual code and report drift
Reconcile documentation with reality. The code is the truth; the docs are the suspects. Reconcile documentation with reality. The code is the truth; the docs are the suspects.
1. Inventory the actual Go files, their line counts, and the non-stdlib dependencies in `go.mod`. 1. Inventory the actual Go files, their line counts, and the non-stdlib dependencies in `go.mod`.
2. Compare against `docs/state.md`: inventory rows, counters, latent items, `verified against`. 2. Compare against `docs/state.md`: inventory rows, counters, latent items.
Recount the counters **from the code** — number of render transforms, routing cases, views, Recount the counters **from the code** — number of render transforms, routing cases, views,
output formats, extensions — rather than trusting the recorded numbers. output formats, extensions — rather than trusting the recorded numbers.
3. Check `docs/architecture.md` STATUS lines: has a primitive become real, or is one described 3. Check `docs/architecture.md` STATUS lines: has a primitive become real, or is one described
+2 -1
View File
@@ -120,7 +120,8 @@ not run something, say which and why.
Same change, not later. Triggers: `docs/README.md`. Walk the propagation table above — every surface Same change, not later. Triggers: `docs/README.md`. Walk the propagation table above — every surface
done or explicitly n/a. Minimum: done or explicitly n/a. Minimum:
- `docs/state.md`: inventory rows, counters, `verified against` line, latent items added/removed. - `docs/state.md`: inventory rows, counters, latent items added/removed — in *this* commit, never a
trailing one, which is what `verify.sh` compares (ADR-0057).
- `docs/decisions.md`: an ADR if the choice is expensive to reverse. Six lines. First open the doc - `docs/decisions.md`: an ADR if the choice is expensive to reverse. Six lines. First open the doc
that owns the topic — if it already carries the rule, amend it there; an ADR restating an that owns the topic — if it already carries the rule, amend it there; an ADR restating an
existing doc is a duplicate, not a decision. existing doc is a duplicate, not a decision.
+6 -3
View File
@@ -118,8 +118,9 @@ In order, cheapest first:
1. `./scripts/verify.sh` — one command, tells you whether the thing is still coherent and whether 1. `./scripts/verify.sh` — one command, tells you whether the thing is still coherent and whether
`docs/state.md` has fallen behind the code. `docs/state.md` has fallen behind the code.
2. `docs/state.md` — what exists, the earn-it counters, the latent list, the commit it was last verified 2. `docs/state.md` — what exists, the earn-it counters, the latent list. This is the only doc that
against. This is the only doc that describes the present. Its companion `docs/surface.md` is generated: describes the present, and `verify.sh` compares its last commit against the last `.go` one rather than
trusting a sha written by hand (ADR-0057). Its companion `docs/surface.md` is generated:
every declaration and its line, so you can find your way around without opening anything. every declaration and its line, so you can find your way around without opening anything.
3. `git log --oneline` — one revertible unit per commit, each body saying *why* (`conventions.md`). This is the 3. `git log --oneline` — one revertible unit per commit, each body saying *why* (`conventions.md`). This is the
real map of how the code got here. real map of how the code got here.
@@ -211,7 +212,9 @@ that describe it, in the same change.**
changing — except `.claude/settings.json`, which is Claude Code's permission list rather than anything changing — except `.claude/settings.json`, which is Claude Code's permission list rather than anything
about khosra. That exemption is one path: `scripts/budgets.env` and every other file under `scripts/` about khosra. That exemption is one path: `scripts/budgets.env` and every other file under `scripts/`
and `.claude/` stays gated, and khosra's own settings are not exempt from anything. This file is the current description of the machine, not a snapshot of its design. and `.claude/` stays gated, and khosra's own settings are not exempt from anything. This file is the current description of the machine, not a snapshot of its design.
- `verify.sh` fails when a `.go` file changes without `docs/state.md` changing, and when `cmd/` or - `verify.sh` fails when a `.go` file changes without `docs/state.md` changing, warns when `state.md`'s last
commit is older than the last `.go` one — the two together mean the doc ships inside the change, never in a
commit trailing it (ADR-0057) — and fails when `cmd/` or
`internal/` code changes without a `_test.go` changing — behaviour ships with a test. A comment-only or `internal/` code changes without a `_test.go` changing — behaviour ships with a test. A comment-only or
`gofmt`-only diff is exempt: it ships no behaviour, and failing it would only teach you `--no-verify`. `gofmt`-only diff is exempt: it ships no behaviour, and failing it would only teach you `--no-verify`.
- The gate is not optional. `scripts/hooks/pre-commit` runs it on every commit; enable once per clone - The gate is not optional. `scripts/hooks/pre-commit` runs it on every commit; enable once per clone
+1 -1
View File
@@ -78,7 +78,7 @@ Update in the Document step of the change that caused them. Not later.
| Change | Update | | Change | Update |
|---|---| |---|---|
| Any code change at all | `state.md` inventory + `verified against` line | | Any code change at all | `state.md` inventory — in the same commit, which is what `verify.sh` compares (ADR-0057) |
| New transform, route, view, extension, or dependency | the counters table in `state.md` | | New transform, route, view, extension, or dependency | the counters table in `state.md` |
| A choice expensive to reverse | new ADR in `decisions.md` | | A choice expensive to reverse | new ADR in `decisions.md` |
| A primitive becomes real (earned) | STATUS line in `architecture.md` + counters | | A primitive becomes real (earned) | STATUS line in `architecture.md` + counters |
+8
View File
@@ -109,6 +109,14 @@ already wrong beforehand is a separate unit even when it lands in the same sitti
Imperative subject under 72 characters; body says *why*, and carries the numbers the change earned. Imperative subject under 72 characters; body says *why*, and carries the numbers the change earned.
Committing happens by itself and needs no request (`CLAUDE.md` §4); pushing never does. Committing happens by itself and needs no request (`CLAUDE.md` §4); pushing never does.
**Never a `state:` commit.** `state.md` is part of the change, not a follow-up to it — nothing about a
change is knowable only after committing it, now that currency is compared rather than declared
(ADR-0057). A doc commit of its own is for a correction to something that was already wrong.
**Amend rather than accumulate.** A minor change the human asks for just after a commit — a comment
removed, a wording fixed, a line they decided against — is amended into it while it is still unpushed.
History records units of work, not the order in which someone noticed things.
**Authorship names who wrote the bytes, not who asked for them.** Three cases, no fourth: **Authorship names who wrote the bytes, not who asked for them.** Three cases, no fourth:
| Who wrote the change | `Author` | Trailer | | Who wrote the change | `Author` | Trailer |
+22
View File
@@ -882,3 +882,25 @@ previous index. Both halves are internally coherent and the gap is microseconds,
of the disk, and closing it means one pointer holding both, which is `web.Handler`'s signature. of the disk, and closing it means one pointer holding both, which is `web.Handler`'s signature.
Revisit if: that microsecond gap ever matters — then the index and the theme become one snapshot behind Revisit if: that microsecond gap ever matters — then the index and the theme become one snapshot behind
one pointer, and `web.Handler` takes an accessor for it instead of two arguments. one pointer, and `web.Handler` takes an accessor for it instead of two arguments.
## ADR-0057 — `state.md`'s currency is compared, not declared
Date: 2026-08-01 · Status: accepted (retires the `verified against` sha the harness carried from day one)
Decision: `verify.sh` decides whether `docs/state.md` is current by comparing the last commit that touched
it against the last commit that touched a `.go` file — current when the doc's commit is the same or newer.
The hand-written `**Verified against:** <sha>` line is deleted, and no commit exists solely to write one.
Why: the sha could only ever be wrong. A commit cannot name itself, so the line had to be written *after*
the commit it described, which forced a trailing `state:` commit every time — and `conventions.md` has
always said code, test, `state.md` row and ADR belong in one commit, so the gate was pushing against the
convention it was meant to protect. Folding the trailing commits away then left the sha naming a commit
that no longer existed, which is how this session reproduced the same mess the last history rewrite
cleaned up. Two commits in `backup/pre-fold` per feature, and `8905686` needed afterwards to name the
survivor, are the evidence. Git already knows when each file last changed; asking it is free and cannot
drift.
Consequence: cheap — one fewer thing to write, one fewer commit per feature, and a rewrite of history no
longer invalidates a doc. The gate's intent is unchanged, so `state.md` still cannot lag the code silently.
Expensive — the doc no longer records *which* commit a human last reconciled it against, so a deliberate
`/refresh-docs` pass leaves no mark beyond its own commit; if that turns out to matter, it is a line in the
commit body, not a value in the file. The check is also weaker on a squashed or rebased history, where both
files move in the same commit by construction.
Revisit if: someone wants the reconciliation *moment* recorded rather than the currency, which is a
different fact and belongs in the arc retro log.
+3 -1
View File
@@ -1,6 +1,8 @@
# State # State
**Verified against:** `cf3edd7` on 2026-08-01 — update this line every change. **Currency is compared, not declared.** `verify.sh` warns unless this file's last commit is at least as new
as the last commit touching `.go`, so it ships *inside* the change it describes — no sha to write, and no
trailing commit to write one (ADR-0057).
If this file disagrees with the code, the code is right and this file is a bug. If this file disagrees with the code, the code is right and this file is a bug.
## Inventory ## Inventory
+12 -12
View File
@@ -52,7 +52,7 @@ if [ -d .git ] && command -v git >/dev/null 2>&1; then
pass "working tree clean — nothing to couple" pass "working tree clean — nothing to couple"
else else
if echo "$changed" | grep -qE '\.go$' && ! echo "$changed" | grep -qx 'docs/state.md'; then if echo "$changed" | grep -qE '\.go$' && ! echo "$changed" | grep -qx 'docs/state.md'; then
bad "*.go changed but docs/state.md did not — inventory, counters and the verified-against line move with the code" bad "*.go changed but docs/state.md did not — inventory, counters and latent items move with the code"
else else
pass "code/state.md coupling" pass "code/state.md coupling"
fi fi
@@ -171,18 +171,18 @@ if [ -d .git ] && command -v git >/dev/null 2>&1; then
fi fi
done done
# True staleness: has any Go file changed since the commit state.md claims to describe? # True staleness: was state.md updated no earlier than the last commit that touched Go?
recorded=$(awk -F'`' '/^\*\*Verified against:\*\*/{print $2; exit}' docs/state.md 2>/dev/null) #
if git rev-parse --verify -q HEAD >/dev/null 2>&1; then # Compared, never declared. A hand-written sha cannot do this job: a commit cannot name itself, so the
if git rev-parse --verify -q "${recorded:-nonexistent}^{commit}" >/dev/null 2>&1; then # doc update had to trail the code in a commit of its own, and folding the two together then left the
behind=$(git rev-list "$recorded..HEAD" -- '*.go' 2>/dev/null | wc -l | tr -d ' ') # sha naming a commit that no longer existed (ADR-0057).
if [ "${behind:-0}" -gt 0 ]; then lastgo=$(git log -1 --format=%H -- '*.go' 2>/dev/null)
note "docs/state.md describes $recorded; $behind commit(s) have touched .go since" laststate=$(git log -1 --format=%H -- docs/state.md 2>/dev/null)
if [ -n "$lastgo" ]; then
if [ -n "$laststate" ] && git merge-base --is-ancestor "$lastgo" "$laststate" 2>/dev/null; then
pass "docs/state.md is current with the code"
else else
pass "docs/state.md is current with HEAD" note "docs/state.md was last updated in ${laststate:-no commit}, older than the .go change in $(git log -1 --format=%h -- '*.go')"
fi
else
note "docs/state.md 'verified against' does not name a commit this repo knows ($recorded)"
fi fi
fi fi
# The generated surface is only worth reading if it cannot be wrong. Regenerate and compare rather # The generated surface is only worth reading if it cannot be wrong. Regenerate and compare rather