Two records shared a number, twice, and every check passed
Two machines filing issues in the same hour both read main correctly and both took "the next free number". main lags every open pull request — seven that evening — so they collided twice. The second collision reached main with records, cycle and index all reporting success. cycle.py now refuses a tree where two issue folders share a leading number, and names both. Proven by adding a duplicate and watching it fail. The colliding records become 153 and 154, renumbered in the branch that lands last, because renumbering a branch whose author is still pushing only moves the race. The check catches a collision; it does not prevent one. Taking a number still means reading the open pull requests as well as main — issue 155 says so.
This commit is contained in:
+23
-1
@@ -14,7 +14,8 @@ What is enforced:
|
|||||||
its owning code (`code:`) -- no development without a design that says where.
|
its owning code (`code:`) -- no development without a design that says where.
|
||||||
issues a known `status:`; once `located`, `located-in:` names the owner;
|
issues a known `status:`; once `located`, `located-in:` names the owner;
|
||||||
once `resolved`, `fixed-by:` says what fixed it (prose counts --
|
once `resolved`, `fixed-by:` says what fixed it (prose counts --
|
||||||
"nothing, the capability existed" is an answer).
|
"nothing, the capability existed" is an answer). And no two records share a
|
||||||
|
number -- the number is how a record is cited.
|
||||||
research a known `status:`; a `graduated` overview says what it `became:`, and every
|
research a known `status:`; a `graduated` overview says what it `became:`, and every
|
||||||
target it names exists.
|
target it names exists.
|
||||||
decisions every accepted record is REACHABLE from the cycle: cited by a design doc's
|
decisions every accepted record is REACHABLE from the cycle: cited by a design doc's
|
||||||
@@ -109,6 +110,27 @@ def main():
|
|||||||
"without a design that says where" % status)
|
"without a design that says where" % status)
|
||||||
|
|
||||||
# ---- issues ------------------------------------------------------------------------
|
# ---- issues ------------------------------------------------------------------------
|
||||||
|
# Two records may not share a number. Numbers are taken as "next free after main", and work
|
||||||
|
# sits on unmerged branches for days -- so two people reading the same main allocate the same
|
||||||
|
# number, and nothing said so. It happened twice in one evening between two machines, and the
|
||||||
|
# second collision landed on main with all three checks passing (issue 155). An issue number is
|
||||||
|
# how every other record cites this one; two records answering to it means a pointer that
|
||||||
|
# resolves to whichever the reader happened to open.
|
||||||
|
seen = {}
|
||||||
|
for folder in sorted(glob.glob(os.path.join(ROOT, "04-ISSUES", "*", ""))):
|
||||||
|
name = os.path.basename(os.path.normpath(folder))
|
||||||
|
number = name.split("-", 1)[0]
|
||||||
|
if not number.isdigit():
|
||||||
|
continue
|
||||||
|
if number in seen:
|
||||||
|
bad(os.path.join("04-ISSUES", name),
|
||||||
|
"is numbered %s, and so is %s -- an issue number is how it is cited, and two "
|
||||||
|
"records answering to one means a citation that resolves to whichever the reader "
|
||||||
|
"opened. Take the next free number across main AND every open pull request"
|
||||||
|
% (number, seen[number]))
|
||||||
|
else:
|
||||||
|
seen[number] = name
|
||||||
|
|
||||||
for path in sorted(glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md"))):
|
for path in sorted(glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md"))):
|
||||||
front = frontmatter(path)
|
front = frontmatter(path)
|
||||||
if front is None:
|
if front is None:
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ fixed-by:
|
|||||||
amended-design:
|
amended-design:
|
||||||
---
|
---
|
||||||
|
|
||||||
# 149 — An adopted machine's data cannot be placed where it is
|
# 153 — An adopted machine's data cannot be placed where it is
|
||||||
|
|
||||||
## What was observed
|
## What was observed
|
||||||
|
|
||||||
+1
-1
@@ -8,7 +8,7 @@ fixed-by:
|
|||||||
amended-design:
|
amended-design:
|
||||||
---
|
---
|
||||||
|
|
||||||
# 150 — A machine's own network is not a reach
|
# 154 — A machine's own network is not a reach
|
||||||
|
|
||||||
## What was observed
|
## What was observed
|
||||||
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
status: resolved
|
||||||
|
opened: 2026-09-29
|
||||||
|
located-in: [hq 00-META/checks/cycle.py]
|
||||||
|
fixed-by: hq issue/two-records-share-a-number-and-nothing-says-so
|
||||||
|
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.
|
||||||
Reference in New Issue
Block a user