Measured on a real repository. Classification deliberately left open — one unrun command separates two very different diagnoses, and I have been wrong about this coupling twice already today.
The two refusals
Both on specs/algorand-hub/algorand-hub.spec.md, a delivery input shared by 13 changes (9 accepted, 4 verifying).
Predecessor cannot archive:
specsync change archive CHG-0036-...
error: delivery input `specs/algorand-hub/algorand-hub.spec.md` (owner `algorand-hub`) changed
after acceptance and no accepted or archived successor change covers it;
run `specsync change reopen CHG-0036-...`
Successor cannot archive either — after being driven verifying → accepted with a fresh review:
specsync change ship <successor>
error: archive post-move preflight would invalidate `CHG-0026-...`: delivery input
`specs/algorand-hub/algorand-hub.spec.md` (owner `algorand-hub`) changed after acceptance and
no accepted or archived successor change covers it; run `specsync change reopen CHG-0026-...`
Same invariant, same input, binding in both directions:
predecessor archive -> blocked: no accepted/archived successor covers the changed input
successor archive -> blocked: archiving it would invalidate that predecessor
The defect that stands regardless of classification
Each error names the other side as the thing that must move first. Neither mentions that the counterpart is symmetrically blocked.
A user following the printed next step from either end will:
- read that the other change must advance
- advance it
- hit the mirror-image refusal
- and still not know the two are coupled
The only advertised escape on both sides is reopen, which moves an accepted change backward — offered without any indication that it may be the only door.
That is a disclosure defect independent of whether the underlying rule is correct. The tool knows both records, knows the shared delivery input, and knows the other side's state. It could say "CHG-0026 is also blocked on this input; both are waiting on each other" instead of pointing each at the other in isolation.
What is undetermined — do not assume either branch
The successor is now accepted, which it was not when the predecessor first failed. So the predecessor may now have the accepted successor it was missing. One command decides:
specsync change archive CHG-0026-...
archives cleanly -> NOT a deadlock. An ORDERING problem: advance everything to accepted,
then archive oldest-first. The fix is documentation and better next-step
text, not a rule change.
fails again -> a genuine two-sided block on a 13-change coupled set, and `reopen` is the
only exit.
That command has not been run. The reporting session's authorization covered a different change and it correctly declined to widen scope on its own.
I am not classifying this until it is. Two earlier explanations of this same coupling — a forward finalize cascade, and "finalizing the successors will clear the predecessor" — both looked right and were both wrong. The coupling is real; my mechanisms for it have a poor track record.
Adjacent, filed here rather than separately because it may be the same root
Recorded verification evidence names a commit hash, checked as an ancestor of HEAD. The reporting repository squash-merges every PR, which rewrites that hash — so a change reads as unverified the moment its own PR lands. Observed twice in one day (#244, then #246): ship-status reported Verification: <hash> (not ancestor of HEAD) and forced a full re-verify plus a fresh human-recorded review.
The reporter explicitly declined to file that as a defect, calling it arguably repository configuration. Recording it because:
- squash-merge is the default on GitHub and common among adopters
- if the evidence-to-hash binding self-invalidates on merge, the blessed path invalidates itself for every squash-merging repo
- the step it forces — independent review — is precisely the one that requires a human
Related: #677 corrected the archive anchor predicate for exactly this reason (squash-merge guarantees the active path never reaches the default branch). verification.commit ancestry is a different check that was not touched, and may need the same treatment.
If the remedy is "rebase-merge your lifecycle commits", that belongs in docs/ADOPTING.md — it is not currently there, and nothing in the tool says it.
Measured on a real repository. Classification deliberately left open — one unrun command separates two very different diagnoses, and I have been wrong about this coupling twice already today.
The two refusals
Both on
specs/algorand-hub/algorand-hub.spec.md, a delivery input shared by 13 changes (9 accepted, 4 verifying).Predecessor cannot archive:
Successor cannot archive either — after being driven
verifying → acceptedwith a fresh review:Same invariant, same input, binding in both directions:
The defect that stands regardless of classification
Each error names the other side as the thing that must move first. Neither mentions that the counterpart is symmetrically blocked.
A user following the printed next step from either end will:
The only advertised escape on both sides is
reopen, which moves an accepted change backward — offered without any indication that it may be the only door.That is a disclosure defect independent of whether the underlying rule is correct. The tool knows both records, knows the shared delivery input, and knows the other side's state. It could say "
CHG-0026is also blocked on this input; both are waiting on each other" instead of pointing each at the other in isolation.What is undetermined — do not assume either branch
The successor is now
accepted, which it was not when the predecessor first failed. So the predecessor may now have the accepted successor it was missing. One command decides:That command has not been run. The reporting session's authorization covered a different change and it correctly declined to widen scope on its own.
I am not classifying this until it is. Two earlier explanations of this same coupling — a forward
finalizecascade, and "finalizing the successors will clear the predecessor" — both looked right and were both wrong. The coupling is real; my mechanisms for it have a poor track record.Adjacent, filed here rather than separately because it may be the same root
Recorded verification evidence names a commit hash, checked as an ancestor of
HEAD. The reporting repository squash-merges every PR, which rewrites that hash — so a change reads as unverified the moment its own PR lands. Observed twice in one day (#244, then #246):ship-statusreportedVerification: <hash> (not ancestor of HEAD)and forced a full re-verify plus a fresh human-recorded review.The reporter explicitly declined to file that as a defect, calling it arguably repository configuration. Recording it because:
Related: #677 corrected the archive anchor predicate for exactly this reason (squash-merge guarantees the active path never reaches the default branch).
verification.commitancestry is a different check that was not touched, and may need the same treatment.If the remedy is "rebase-merge your lifecycle commits", that belongs in
docs/ADOPTING.md— it is not currently there, and nothing in the tool says it.