Released2 Aug 2026Commitb0ecc291Line0.2.xLateston its line

v0.2.8

folio 0.2.8 — an id now identifies one task, and says so when it does not

Notes

0.2.7 preserved task ids across a move. It also, correctly, made a COLLISION PERMANENT: moves used to renumber, which broke collisions apart by accident. This release makes collisions rare, refuses to act on the ones that exist, and gives you a way to say which task you meant.

WHY THERE WERE COLLISIONS AT ALL — arithmetic, not a defect. 1274 distinct ids in a 4-hex (65,536) space with no uniqueness check predicts 12.4 colliding pairs. Fourteen were found. Nothing needed explaining beyond the space being too small and nothing checking.

  Widened to 6 hex, accept-short/mint-long. All existing 4-hex ids stay valid
  and every reference in every note keeps resolving; nothing validates id shape
  and length was decided in exactly one place. At this corpus 6 hex expects
  0.05 collisions against 12.5.
  Minting now rejects a candidate already in use.
  complete / reopen / update / move REFUSE on a multi-match and name every
  match with its file and description, rather than silently picking one and
  returning a success message. Batches refuse per-id: the ambiguous one stops,
  the rest still run.
  A  qualifier says which copy you mean, and the refusal message ends by
  suggesting it — so refusal to retry is two steps, not a dead end.
  store-project-files is SORTED. Unsorted readdir meant which task an ambiguous
  id resolved to depended on filesystem order and was not stable across
  machines.

THE RESOLUTION BUG WAS NOT WHAT IT LOOKED LIKE. The mutators do not use different lookup orders; they iterate the identical list, inbox first. What happened is that A MOVE DELETES THE ITEM THAT WAS DISAMBIGUATING THE ID: an inbox task is found first and deterministically, and moving it out of the inbox lets the next call fall through to project files, where a different task with the same id wins. Proved with a matched pair — renaming the fixture files so aaa-real.md and zzz-probe.md swap decides which task wins, and nothing about 'real vs probe' enters into it.

That is why 'act, then check where it went, then proceed' could not have worked: the check is valid only until the next mutation.

Found by damage, not by prediction: a leader probe minted t-de62 for a disposable item, an id that already belonged to a live task, and the cleanup completed the real one. Restored byte-identical.

Not fixed here: the 14 existing collisions. Repairing them is a separate, reference-rewriting change, deliberately not bundled with a library release.

Commit

b0ecc291

The commit this tag names.

Releases