Three issues named the branch that fixed them, and a branch is deleted when it merges — so every `fixed-by:` was a pointer that resolved to nothing by the time anyone followed it. They name commits and pull requests now, and playbook 03 says to. Two records were missing the thing a reader arrives for. 146 did not say that one of its fixes crash-looped the control plane on a running mesh, which is the whole reason the delivery subject carries the stream and the raise path was the only one exercised. 151 did not say that 152 removed the false reasons its roster moved, or that it stays open for the real ones. ADR 0080 enumerates what cycle.py enforces and named four things; it enforces five. A progressive insight names the fifth — the decision stands, the list had gone stale. The checks README and playbook 03 gained the same rule, and 155 points at all three.
59 lines
2.8 KiB
Markdown
59 lines
2.8 KiB
Markdown
---
|
|
status: resolved
|
|
opened: 2026-09-29
|
|
located-in: [hq 00-META/checks/cycle.py]
|
|
fixed-by: hq f89aef9 (PR 192)
|
|
amended-design:
|
|
---
|
|
|
|
# 155 — Two records may share a number, and every check passes
|
|
|
|
## What was observed
|
|
|
|
On 2026-09-29 two machines opened issues against this repository within the same hour. Both read
|
|
`main` correctly and both took "the next free number", and they collided twice:
|
|
|
|
| | one machine opened | the other had already used |
|
|
|---|---|---|
|
|
| first | 147, 148 | 147, 148 on an unmerged branch |
|
|
| second | 149, 150 | 149 on an unmerged branch, 150 from renumbering the first collision |
|
|
|
|
The first collision was reconciled by hand before merging. The second was **merged into `main`**, and
|
|
`records.py`, `cycle.py` and `index.py` all reported success over a tree holding
|
|
`149-a-declaration-that-shrinks-to-empty` beside `149-an-adopted-machines-data-cannot-be-placed-where-it-is`,
|
|
and two folders numbered 150.
|
|
|
|
## Why
|
|
|
|
The number is allocated as `max(main) + 1`, and `main` lags every open pull request — seven of them
|
|
that evening. Two readers of the same `main` therefore compute the same next number, and neither is
|
|
doing anything wrong. The existing reconciliation precedent (a second record numbered 127 became 149)
|
|
assumed a single writer, which stopped being true when a second machine began filing its own findings.
|
|
|
|
## Why it matters
|
|
|
|
An issue number is how every other record cites this one — `fixed-by:`, `located-in:`, a decision
|
|
record's consequence, a commit message. Two records answering to one number is a citation that
|
|
resolves to whichever folder the reader happened to open, and the failure is silent on both sides:
|
|
the citer is not wrong, and the cited record exists.
|
|
|
|
It is also exactly the class this repository says it does not permit — a rule (`00-META/process/03-issues.md`:
|
|
"take the next free number") enforced by nothing.
|
|
|
|
## How it was fixed, and how the fix is checked
|
|
|
|
`cycle.py` now refuses a tree in which two issue folders share a leading number, and names both.
|
|
Proven by adding a duplicate and watching it fail, then removing it and watching it pass.
|
|
|
|
The colliding records were renumbered 153 and 154, in the branch that landed last — renumbering a
|
|
branch whose author is still pushing only moves the race.
|
|
|
|
**The check catches the collision; it does not prevent it.** Allocating a number still needs the open
|
|
pull requests read as well as `main`. That is a habit the check now backstops rather than one it
|
|
replaces, and playbook [03](../../00-META/process/03-issues.md) now says so at the step where the
|
|
number is taken.
|
|
|
|
[ADR 0080](../../02-DECISIONS/0080-the-development-cycle-is-checked.md) enumerates what `cycle.py`
|
|
enforces and named four things; it carries a progressive insight naming the fifth. The decision
|
|
stands — this is one more thing frontmatter and file names can carry, found by its absence.
|