Skip to content

the scoped-review descendant walk fails closed forever after a squash, and its guarantee is unavailable rather than violated #694

Description

@0xLeif

The second half of #689. The readiness half is fixed separately; this one needs a decision rather than a patch.

Symptom

After a squash-merge, ship refuses:

error: independent scoped review is stale; open or update the PR so `SpecSync scoped review` can run

Re-running change review does not help on a change that has advanced to accepted — record_scoped_review requires ChangeState::Verifying (src/change.rs:5850). So a squash-merged change that reached accepted has no forward verb at all. That is the wedge in #690.

Why this one cannot be fixed the way readiness was

#689's readiness fix replaced commit ancestry with content agreement, because verification.commit ancestry was freshness wearing a trust costume — never resolved to a tree, never compared to a digest.

This check is not that. review_commit_is_current_checked (src/change.rs:5686-5760) walks every commit between review.implementation_commit and HEAD and requires every changed path to sit under:

let allowed_prefix = git_repo_relative_path(root, &format!("{CHANGES_PATH}/{}/", record.id))?;

So it proves: between the review and HEAD, nothing changed except this change's own lifecycle records.

Compare what scoped_review_is_current already checks by content — contract_digest, execution_digest, and project_input_digest == review.workspace_digest. Those cover product and spec files. But project_input_is_volatile (src/change.rs:10493) excludes:

.specsync/changes/
.specsync/archive/

precisely the region the walk polices. So the walk's unique contribution is detecting that another change's records, or the archive, moved after the review was recorded. Content digests cannot see that, by design.

That is a real guarantee. Deleting the walk would lose it silently.

The actual defect

A squash destroys review.implementation_commit, so the walk cannot run at all. The guarantee is not violated — it is unavailable. The code treats those identically and fails closed forever.

Measured on this repository: 2 of 105 archived reviews are reachable in a fresh clone. The walk is inert for 98% of history already; it simply fails loudly instead of admitting it cannot answer.

Options, none chosen

  1. Fall back to content when the commit is unreachable, and say so. Cheapest. scoped_review_is_current's other four checks still run. The output must state that the descendant guarantee was unavailable — an unavailable guarantee reported as a satisfied one is worse than the current failure.
  2. Re-anchor the review at finalize, as accept_change_with_gate already re-anchors verification.commit when allow_verified_tree_adoption is set. Keeps a walk, but over a window that no longer spans the reviewed work — worth checking whether that window proves anything.
  3. Move the region into a digest. .specsync/changes/ and .specsync/archive/ are volatile by design because they churn during the lifecycle; a scoped digest over other changes' records might be tractable and would survive any merge strategy.

What I am not doing

Guessing. Three explanations of this repository's change coupling were wrong today, each killed by someone running a command. This one changes what a review proves, so it should be decided deliberately and stated in the spec rather than inferred from whichever patch lands.

Related: #689 (readiness, fixed), #690 (the wedge this produces), #688 (the two-sided archive block).

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