Skip to content

feat(decision-records): warn on, and mint past, IDs taken on origin/main - #11

Merged
trungfinity merged 2 commits into
mainfrom
feat/upstream-collision-warning
Aug 21, 2026
Merged

trungfinity merged 2 commits into
mainfrom
feat/upstream-collision-warning

Conversation

@trungfinity

Copy link
Copy Markdown
Contributor

Every uniqueness check in this tool reads one tree, so a duplicate ID can only fail once both copies are in it — after a rebase or a merge. On a branch the ID you minted is unique and everything is green; the collision comes into being when the trees meet, by which point the record is written, renamed, and cross-referenced. lanh-ai hit exactly this with PRD-005 today, and its migration sequence has hit it before with 0052.

Two commits, one for each side of the problem.

check warns about an ID origin/main already gives to a different file (6e6a812). Same ID and same filename is that record, edited — not a collision — and a record that moved accepted/ to archived/ keeps its filename and stays silent. Counters get the next free number across both trees appended as the fix; draft mnemonics get "re-mint it". Filenames only, so a record whose front-matter is still being written reads like any other.

WARN decisions: 0044 is taken on origin/main by 0044-template-authority.md, and here by 0044-something-else.md — renumber to 0049 before the trees meet
WARN drafts: CONF is taken on origin/main by CONF-confidence.md, and here by CONF-a-different-idea.md — re-mint it before the trees meet

It warns and never gates: origin/main moves under a branch on every fetch, so a failure here would reject correct work for a reason its author cannot act on. It never fetches — a hook that reaches the network is a hook people turn off — and when the ref is not on disk (a fresh clone, an offline machine, a CI checkout that took only the branch) it reports nothing, because a missing ref is not a finding. Read-only git ls-tree/rev-parse against the object store; nothing is checked out and no ref moves, so it is safe from any worktree.

promote mints past what origin/main holds (12cea62). Minting is the other side of it: a write. By the time anything reads the number, the record has been renamed, its H1 rewritten and every inbound link repathed, so next_counter asks the ref rather than leaving check to report the collision afterwards. It prints which counters it stepped over, or the jump is a mystery — the tree ends at 0048 and the record comes back 0050.

That skip leaves what reads as a hole, so the gap rule is told to read a counter origin/main holds as held rather than missing: the record exists, it is just not on this branch yet, and the rebase closes the sequence. A number neither tree has still fails. This is the one place a gate consults the ref, and it only ever suppresses an error, never raises one — a fetch can make check greener, never redder.

With no ref on disk, both fall back to this tree alone, which is exactly what they did before.

Verification

Watched failing on a known collision before being kept, against lanh-ai's real docs/decisions/: renaming 0044-template-authority.md fired the warning naming 0049; restoring it went silent. The minting half was probed read-only against the same tree with 0048 removed to simulate a branch that forked before it landed — the local tree alone answers 0048 (the collision), with origin/main it answers 0049.

116 tests pass, 8 new. Every guard is mutation-checked: reverting the mint to local-only fails 4 tests (and its captured stderr shows it minting the very collision the warning then reports); dropping the gap exemption fails 3; making the gap rule always-permissive fails the "a counter neither tree has is still a gap" test. The pre-existing test_gap_in_counters runs outside git and still passes, which pins the no-ref fallback. Tests build real git work trees — nothing about git is stubbed, because the behaviour under test is reading refs off disk.

Note for consumers

lanh-ai runs the same shape over its PRD and migration counters from a shared scripts/upstream.py (skymavis/lanh-ai#feat/upstream-collision-warning). This copy is deliberate — a skill has to run in a repo that has none of that — and both sides say so. Keep them in step.

A consumer does not get this until its skills-lock.json is bumped and the skill re-materialized; lanh-ai's decisions-check hook runs the materialized copy.

Uniqueness is checked against a single tree, so two branches can each
mint 0044 — or the same draft mnemonic — and both stay green until they
meet. The collision comes into being on the rebase, by which point the
record is written and cross-referenced.

`check` now also reads the origin/main already on disk and prints a
WARN line when an ID there names a different file, with the next free
counter. Same ID and same filename is that record, edited or moved —
accepted/ to archived/ keeps the name and stays silent.

It never fetches, never fails the run, and says nothing when the ref is
absent: a fresh clone or an offline machine is not a finding. lanh-ai
runs the same shape over its PRD and migration counters from a shared
scripts/upstream.py; a skill has to stand alone, so this is a
deliberate second copy.
`check` only warns about a counter taken upstream, because origin/main
moves under a branch and a gate there would fail correct work. Minting
is the other side of it: a write. By the time anything reads the
number, the record has been renamed, its H1 rewritten and every
inbound link repathed — so `next_counter` now asks the ref rather than
leaving `check` to report the collision afterwards.

Stepping over a counter origin/main holds leaves what reads as a hole,
so the gap rule is told to read those as held rather than missing: the
record exists, it is just not on this branch yet, and the rebase
closes the sequence. A number neither tree has still fails. `promote`
prints which counters it stepped over, or the jump is a mystery — the
tree ends at 0048 and the record comes back 0050.

With no ref on disk both fall back to this tree alone, which is what a
fresh clone and an offline machine have always done.
@trungfinity
trungfinity merged commit cb49501 into main Aug 21, 2026
10 checks passed
@trungfinity
trungfinity deleted the feat/upstream-collision-warning branch August 21, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant