Repository navigation
fix(verify): fail closed without a base or with a deleted manifest - #1070
Conversation
verify with no --base and no detectable default branch read no committed change, published the empty change set as complete and granted merge. It now keeps every base it found before, follows check's ladder (#649) when it found none, and otherwise exits 2 naming --base and --no-base (base_unresolved). A missing manifest that the compared commit holds is a removed gate: a human stop instead of the configure/init route. SHIP-VERIFY-CI-GATE-REMOVED resolves changed paths against the evaluated tree root, every archive packs the commit's tree so a depth-1 checkout archives --head, and check leaves verify's reports directory out of a working-tree change set.
|
🤖 Engineering review — independent review, cycle 1
No P0 or P1 findings. Judgement points
Commands run (detached worktree at the head; baseline worktree at merge base
|
A named --base the checkout does not hold cannot show whether the manifest was removed, so it is now the refs_missing fetch_base stop in both CI modes. The setup route keeps its precedence only where complete history from the head shows the manifest never existed. STABILITY says diff_status.detail may begin with base_unresolved.
…rify-base-and-gate-removal # Conflicts: # CHANGELOG.md # STABILITY.md # tests/shard_seconds.json
|
🤖 Engineering review — implementer, addressed cycle 1 New head:
Also in this push: merged Checks on
|
|
🤖 Engineering review — independent review, cycle 2
No P0 or P1 findings. Judgement points
Commands run (detached worktree at the head, with a second worktree at
Earlier findings
|
path_ever_present_from answers None when info/grafts exists, since a graft cuts history while the clone reads as complete. Tests pin the depth-1 named-base stop (with and without a manifest) and the graft case, each failing with its guard removed.
|
🤖 Engineering review — implementer, addressed cycle 2 New head:
Also in this push:
Checks on
|
|
🤖 Engineering review — independent review, cycle 3 No P0–P2 findings. Delta reviewed: Commands and results
The CHANGELOG and STABILITY now both say that a depth-1 checkout with an unfetched Earlier findings
|
|
🤖 Engineering review — merge readiness
|
Summary
--basenor--no-base,verifykeeps every base it already chose (remote-only detection runs first), and only when that finds nothing does it followcheck's ladder (checkdefaults to the merge-base against the worktree;--basealone is accepted #649): a default branch already at the head is an empty comparison, and a repository with no remote uses its localmain/master(resolve_verify_baseincli/verify/git.py). When nothing resolves, the run exits 2 before any scan:base_status: ref_missing,diff_statusunavailable/refs_missingwithdetailbeginningbase_unresolved, afetch_basenext action whose text names--baseand--no-base, and every permission denied. Thedeveloprepro (PR deleting the gate workflow) now exits 2 instead of 0/mergeable.verifyasks whether the compared commit (merge base with the base, orHEADfor a worktree run without one) holds the configured manifest. If it does: a humanstopnaming the removal, exit 2, noconfigure/initialize. If detection found no base: the base-unresolved stop. If the base holds it but the merge base cannot be read (shallow): the existing diff-inputfetch_basestop. A repository that never had a manifest keeps the setup route.SHIP-VERIFY-CI-GATE-REMOVEDresolves changed paths against the evaluated tree root, derived fromVerificationContext.configured_manifest_path, instead of the manifest's directory. A comment in the root workflow underapps/agent/shipgate.yamlno longer fires; a root deletion still does, and a nested copy no longer hides it.shipgate diffdies with a traceback on any shallow clone; the integrity check names no next step #683), so--headarchives in a depth-1 checkout rather than failing fsck on the missing parent (which agent mode routed to "edit shipgate.yaml"). Unscoped archives also stop packing all history.checkexcludes the default reports directory from a working-tree change set through the sameworktree_exclusion, only whenclassify_output_directoryshows it holds nothing but uncommitted Shipgate artifacts.verify --basehelp mentions the remote-less fallback and the exit 2.Type
Authorship
No person has read this diff before merge; it merges only if the independent review cycles and merge gates recorded in the PR comments pass.
Verification
CI is authoritative for
python -m ruff check .,python -m compileall -q src tests, andpython -m pytest.Additional local checks run (Python 3.14, main checkout venv):
python -m ruff check .: all checks passedpython -m compileall -q src tests: okpython scripts/generate_schemas.py --check: exit 0 (no schema drift)python scripts/regenerate_goldens.py --check: 24 sample artifacts, 0 changedpython scripts/verify_dependency_lock.py: OKtests/test_verify_base_and_gate_removal.py(15 tests): run against the unfixed tree, 9 of the first 14 failed and the 5 contrast tests (named base blocks,--no-base, default-branch tip, never-had-a-manifest, nested root deletion) passed; the shallow-base case was added afterwards. All 15 pass after the fix.test_verify*.py,test_verifier_*.py,test_check_*.py,test_first_look.py,test_trigger_command.py,test_current_control.py,test_scoped_base_tree.py,test_capability_diff_partial_clone.py,test_hook_script_archive.py,test_output_directory_content.py,test_manifest_free_pr_rows.py): 656 run, 2 failures, both fixed and re-run green (see Decisions: a named base that is not available keeps the missing-config precedence those tests pin).python -m pytest -m "not perf and not slow" -q -p no:cacheprovider --ignore=tests/test_adapter_static_only.py --ignore=tests/test_ci_timing.py --ignore=tests/test_release_source.py --ignore=tests/test_required_source_availability.py: running locally in the background (about 3 hours serially); CI's six shards are authoritative. The result will be posted as a comment.suiteshards,verify,verify-selfand the launcher jobs passed; thetestjob failed only intests/test_adapter_static_only.py, whose allowlist pins line numbers incli/verify/git.pythat the new helper moved. The two entries are repinned (3268 → 3317, 3677 → 3726; same calls, same rationale) and the file passes locally.c4050229, after mergingorigin/mainwith fix(verify): one receipt predicate for attest, org bundle and Action #1065, both sides of the CHANGELOG/STABILITY conflicts kept, shard weights re-measured): ruff, compileall, schema check, goldens (0 changed) and lock check pass; 862 targeted tests (test_verify_base_and_gate_removal.pynow 17 tests,test_adapter_static_only.py,test_verification_receipt_consumers.py,tests/integration/github_action/test_agent_result.py,test_verify.py,test_verify_auto_base.py,test_manifest_free_pr_rows.py,test_scoped_base_tree.py,test_check_default_comparison.py,test_shard_partition.py) pass. The static-only allowlist lines moved again (3317 → 3350, 3726 → 3759) for the new history helper../shipgate verify --workspace . --config shipgate.yaml --base "$(git merge-base origin/main HEAD)" --head HEAD --out /tmp/verify-1023 --json: exit 0,control.state: complete, all permissions true,release_decision: passed,merge_verdict: mergeable.Release-readiness notes
docs/checks.mdSTABILITY.mdDecisions taken
base_unresolvedis named without a schema bump. Verifier0.21shipped in 1.2.0 and itsbase_statusanddiff_status.reasonare closed enums; adding a value means verifier0.22, a new schema file, and edits to protected discovery surfaces. The run instead publishes the existingref_missing/refs_missing(fetch-repairable,fetch_base) and namesbase_unresolvedindiff_status.detailand the headline, as other diff headlines name their reason. A typed value can follow in the next verifier version if wanted.stoproute, notmerge_verdict: blocked.VerifierArtifactrequiresmerge_verdictto project from arelease_decision(one decision engine), and with no manifest at the head there is no scan to produce one. The removal therefore takes the route an unsafe block takes:control.state: human_review_required,next_actionhumanstop,permissions.merge: false, exit 2,merge_verdict: unknown. Exit 2 rather than 20 because no verdict was reached, consistent with the existing missing-manifest exit.check's ladder (withallow_local_when_no_remoteandallow_equal_head) is consulted only when it finds nothing. A default branch at the head is a resolved empty comparison with no base scan, exactly as before, sotest_verify_auto_base.pyis unchanged.--basethat is not available fails closed when the manifest is missing (review cycle 1, C1-1): therefs_missingfetch_basestop in both CI modes. The setup route keeps its precedence only where complete (non-shallow) history from the head shows the manifest never existed (path_ever_present_from,rev-list --full-history), which keepstests/test_verify.py::test_verify_missing_config_takes_precedence_over_missing_base.tests/test_manifest_free_pr_rows.py::test_a_refusal_that_never_reached_a_base_prints_no_reference_linenamed a base absent from a depth-1 clone, which can no longer prove anything; it now fetches that base at depth 1 so only the merge base lies past the graft, and still pins theshallow_historyrefusal rendering it exists for.actions/checkoutlayout work with--no-base, matches what scoped archives already do, and stops unscoped archives packing the whole history. The commit-to-tree binding is still read from the repository with replace objects disabled.checkexcludes only a provably safe reports directory. It reusesclassify_output_directoryso a committed or trust-root path underagents-shipgate-reports/is never hidden; any failure leaves the exclusion off, which can only surface more paths..agents-shipgate/is not excluded: it holds committed baselines and locks.### Changes(Unreleased has noFixedsubsection).Protected paths touched
none
Follow-up candidates
verify --previewstill uses remote-only detection (src/agents_shipgate/cli/verify/orchestrator.py:7034), so its relevance answer and its auto-base can differ fromverify's in a remote-less repository.trigger --git-diff(src/agents_shipgate/cli/trigger.py:138) still counts the reports directory;_git_diff_contextnow acceptsexclude=.ConfigErrors (src/agents_shipgate/cli/verify/git.py:2687,:2706,:2719, e.g. a partial clone missing objects) still reach the manifest diagnoser in agent mode and can be routed to "edit shipgate.yaml".base_unresolved/manifest_removedvalue could ride the next verifier schema version.