Skip to content

CI verify on push to base: stacked-run walker via --from #10

Description

@chtnnh

Motivation

know-code verify is PR-only today because, after a checkout of the new main tip, origin/main is HEAD (aheadCount === 0). The range candidate is skipped (on base tip) and a range trailer cannot match. That is an implementation gap, not a GitHub limitation.

GitHub already gives the previous tip as github.event.before (all-zeros only for a new branch). Local tests of squash / rebase / merge landings already show verify can pass when the merge-base is that parent, not HEAD.

PR checks are not enough:

  • Direct push to a protected branch (admin bypass, missing “require a pull request”, ruleset gaps) never runs pull_request verify.
  • Local pre-push is same-UID and skippable (git push with hooks disabled, core.hooksPath).
  • Several GitHub landings can arrive in one git push (stacked squashes, a merge commit plus its PR commits, rebase-and-merge replay). A single combined before..HEAD patch is not equal to any per-range trailer, so “hash the whole push” is the wrong check.

This branch (test/verify-merge-from-main) already adds GitHub-shaped PR verify tests and merge-method docs. The walker is the missing CI half: prove each landed run still carries a grounded Know-Code-Verified trailer after it hits the base branch.

Design

  • know-code verify --from <oid> (push job passes github.event.before).
  • Walk from..HEAD oldest-first (rev-list --reverse --topo-order).
  • Split into runs that share the same Know-Code-Verified hash.
  • Merge commits with no trailer attach to the current run (incoming PR side).
  • Linear (single-parent) commits with no trailer fail closed.
  • Each run: hash parent-of-first tree → run-tip tree via a historical tree-pair (not live write-tree). Trailer must match that hash.
  • Single-commit runs also accept the index hash (empty-tree → that commit’s tree) so hotfixes still verify.
  • --from not an ancestor of HEAD → fail closed (force-push / missing object).
  • from == HEAD → nothing to walk, exit 0.
  • All-zero before → skip (new branch); CLI and workflow both treat this as success with a log line.
  • PR path unchanged: no --from; still checkout head.sha; still HEAD trailer then merge-base..HEAD scan.

Push + PR both running on a GitHub merge is intentional defense in depth.

Receipt-mode ranges (trailer only on the tip) still pass PR verify. The push walker is stricter: every non-merge in before..HEAD needs a trailer. GitHub squash (one landing commit, often a hoisted trailer) and rewrite ranges pass. A merge-commit landing of a tip-only receipt PR fails on push unless those commits were rewritten — document that.

Acceptance criteria

  • know-code verify --from <oid> walks from..HEAD and verifies each trailer run against a historical tree-pair hash (parent-of-first tree → run-tip tree), not write-tree.
  • Merge commits without a trailer attach to the current run; a merge with no current run fails closed.
  • A linear commit without a trailer fails closed.
  • A single-commit run accepts either the range tree-pair or the empty-tree (index) hash.
  • Two or more stacked ranges/squashes in one push each verify independently (combined before..HEAD patch must not be required to match any one trailer).
  • --from that is not an ancestor of HEAD (force-push / unknown object) fails closed with a clear error.
  • from == HEAD exits 0 (empty walk).
  • All-zero --from / github.event.before is skipped (exit 0), not a hard fail.
  • Dirty index does not change --from results (historical trees only).
  • PR verify path is unchanged (no --from; still head.sha; Update branch / pull/N/merge range scan still pass).
  • .github/workflows/know-code.yml triggers on push to main and runs know-code verify --from with github.event.before; PR job still uses head.sha.
  • know-code init --workflow template, composite action/, and action README match (new from input; not PR-only).
  • Docs no longer claim verify cannot run on push / that on base tip is the end of the story; they describe --from + the walker, and the receipt-vs-rewrite push caveat.
  • Tests cover the AC above (local git only — no live GitHub). commands-core no longer asserts the consumer workflow is PR-only.

Out of scope

  • Restamping PR ci: bump Actions off Node 20 runtime (#6) #7 (old hash algorithm) — wait until this lands on main.
  • Changing quiz/hash formulas for local pass / know-code commit.
  • Phase 2 human-signature-in-CI (threat model). This only closes the “verify never sees the landing commit” hole.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions