Skip to content

predecessor and successor archive refusals cite the same invariant and name each other, and neither says the counterpart is blocked too #688

Description

@0xLeif

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:

  1. read that the other change must advance
  2. advance it
  3. hit the mirror-image refusal
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:governanceLifecycle, approval, adoption, or release-policy behaviorarea:securityIntegrity, authentication, supply-chain, or secret-handling riskimpact:developer-experienceAffects authors, maintainers, or local workflowsimpact:maintainerPrimarily affects CI, release, or repository operatorspriority:p1Highest-impact or release-blocking riskresolution:partialRelated work landed but meaningful residual work remainsscope:specsync-6Applies to current SpecSync 6 behavior or release contract

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions