Goal
Make the ledger's merge provenance real: fill the field nobody was responsible for
filling, stop rows losing their evidence to an unquoted comma, and refuse to release a
milestone on provenance that does not exist.
The mechanical half of #278. The judgement half — whether REQ-CORE-006 is satisfied by
the merged #239, and what REQ-MARKET-002's evidence should say while #270 stands — stays
there, because deciding a requirement is met is the ACCEPTOR's call, not an operator's.
Evidence
Found by the external QA voice in #278 and verified independently.
MERGE_COMMIT was blank on 11 of 23 rows, 8 of them IMPLEMENTED. The AUTHOR
cannot write it: the row lands inside its own pull request and the squash commit does
not exist until that pull request merges. AUTHOR_RUNBOOK.md therefore called the field
optional — while release_tag.py, added the day before, consumes it as authoritative
provenance. Optional and nobody's job are the same thing.
The consequence is on the release tagger itself: coverage_digest hashes
(REQ_ID, MERGE_COMMIT) pairs, so with blanks a milestone repaired at new commits
hashes identically to the one released before and the patch tag that exists for exactly
that case is never cut. Release notes rendered the blanks as an em dash.
10 of 23 rows parsed into more than the six declared fields. An unquoted comma in
EVIDENCE splits the row; csv.DictReader files the surplus under the key None,
validate() never looked at it, and every consumer read only the text before that
comma. REQ-CORE-005 lost everything after jurisdictionChanges — 620 characters of
evidence rendered as 120.
Scope
docs/spec/implementation_status.csv, docs/spec/IMPLEMENTATION_STATUS.md
scripts/implementation_status.py
scripts/backfill_merge_commits.py, scripts/tests/test_backfill_merge_commits.py
scripts/release_tag.py
.github/workflows/release-tag.yml
docs/zendev/AUTHOR_RUNBOOK.md
Non-goals
Changing any STATUS. Whether a requirement is satisfied is the ACCEPTOR's judgement
against acceptance criteria; this records only where work landed. REQ-CORE-006 stays
PARTIAL here even though #278 argues convincingly that #239 completed it.
Acceptance criteria
- No row that names a merged pull request lacks its merge commit, and a test asserts
this of the repository's own ledger.
implementation_status.py --check rejects a row that parses into more than six
fields, naming the row and the reason.
- Every repaired row's evidence survives a parse round-trip intact.
- The tagger refuses to release a milestone with a blank merge commit, warning which
requirements are missing it, rather than hashing an empty string.
- Backfill runs before the tagger on every push that touches the ledger, and cannot
loop: a run that changes nothing pushes nothing.
- Backfill never rewrites a commit already recorded and never touches a row whose pull
request is not merged.
- The runbook says the field is filled after the merge, not that it is optional.
Verification
python -m unittest discover -s scripts/tests,
python scripts/implementation_status.py --check, and
python scripts/release_tag.py --dry-run.
Goal
Make the ledger's merge provenance real: fill the field nobody was responsible for
filling, stop rows losing their evidence to an unquoted comma, and refuse to release a
milestone on provenance that does not exist.
The mechanical half of #278. The judgement half — whether REQ-CORE-006 is satisfied by
the merged #239, and what REQ-MARKET-002's evidence should say while #270 stands — stays
there, because deciding a requirement is met is the ACCEPTOR's call, not an operator's.
Evidence
Found by the external QA voice in #278 and verified independently.
MERGE_COMMITwas blank on 11 of 23 rows, 8 of themIMPLEMENTED. The AUTHORcannot write it: the row lands inside its own pull request and the squash commit does
not exist until that pull request merges.
AUTHOR_RUNBOOK.mdtherefore called the fieldoptional — while
release_tag.py, added the day before, consumes it as authoritativeprovenance. Optional and nobody's job are the same thing.
The consequence is on the release tagger itself:
coverage_digesthashes(REQ_ID, MERGE_COMMIT)pairs, so with blanks a milestone repaired at new commitshashes identically to the one released before and the patch tag that exists for exactly
that case is never cut. Release notes rendered the blanks as an em dash.
10 of 23 rows parsed into more than the six declared fields. An unquoted comma in
EVIDENCEsplits the row;csv.DictReaderfiles the surplus under the keyNone,validate()never looked at it, and every consumer read only the text before thatcomma.
REQ-CORE-005lost everything afterjurisdictionChanges— 620 characters ofevidence rendered as 120.
Scope
docs/spec/implementation_status.csv,docs/spec/IMPLEMENTATION_STATUS.mdscripts/implementation_status.pyscripts/backfill_merge_commits.py,scripts/tests/test_backfill_merge_commits.pyscripts/release_tag.py.github/workflows/release-tag.ymldocs/zendev/AUTHOR_RUNBOOK.mdNon-goals
Changing any
STATUS. Whether a requirement is satisfied is the ACCEPTOR's judgementagainst acceptance criteria; this records only where work landed.
REQ-CORE-006staysPARTIALhere even though #278 argues convincingly that #239 completed it.Acceptance criteria
this of the repository's own ledger.
implementation_status.py --checkrejects a row that parses into more than sixfields, naming the row and the reason.
requirements are missing it, rather than hashing an empty string.
loop: a run that changes nothing pushes nothing.
request is not merged.
Verification
python -m unittest discover -s scripts/tests,python scripts/implementation_status.py --check, andpython scripts/release_tag.py --dry-run.