Skip to content

fix(evidence): make branch order and check identity representable - #41

Merged
shiv2123 merged 1 commit into
mainfrom
fix/bundle-schema-1-1
Aug 8, 2026
Merged

shiv2123 merged 1 commit into
mainfrom
fix/bundle-schema-1-1

Conversation

@shiv2123

@shiv2123 shiv2123 commented Aug 8, 2026

Copy link
Copy Markdown
Owner

What changed

Bundle schema 1.0 → 1.1, ENGINE_VERSION 0.0.1 → 0.1.0.

  • commits[].sequence — position GitHub returned the commit in, so branch order survives the authored_at sort applied during canonicalization.
  • commits[].parents — deliberately unsorted; parent order is semantic in git. Lets branch order be verified rather than believed.
  • checks[].id — a re-run repeats the name for the same SHA; without an id there is no "current result for this check".
  • derived.last_production_commit_at removed. Replaced by commit_shas_in_order, commit_order_verified, head_commit_sha, last_commit_at (committer date), last_authored_at.
  • Array sorts now use the item's own canonical form as a final tiebreak, on every array rather than just checks.

Why this approach

The obvious alternative was to leave the bundle alone and build CM-2.A2 on max(authored_at). That ships a control I already know misreads any rebased branch, and the corpus is not yet frozen, so this is the cheapest this change will ever be.

Three things were wrong at once:

  1. Branch order was unrecoverable. Sorting commits by authored_at is correct for hashing and destructive for reasoning. sequence carries the order; parents makes it checkable. When the chain does not walk — force-push, absent parents, unusual merge shape — commit_order_verified is false and a dependent check reports INDETERMINATE rather than guessing (AGENTS.md rule 5).

  2. last_production_commit_at lied three ways. It took production_paths and never applied it, so the name claimed a scoping that did not happen and a docs-only follow-up commit flipped a good approval to stale. And author date is preserved by rebase, amend, and cherry-pick, so a rebased branch read as freshly approved. Names now say what the values are.

  3. checks had no unique tiebreak. Python's sort is stable, so ties fall back to input order, and input order is the GitHub API's — which promises nothing. Two check runs from a re-run tie on (name, source). Identical evidence could hash differently between runs, with nothing raised. Same shape as all three Stage 0 bugs. Fixed by appending the canonical form as final tiebreak to every array, so the failure mode closes rather than the instance.

_max_stamp also filters to the canonical timestamp shape. Canonicalization passes an unparseable timestamp through verbatim by design, and a raw string max() would return it — letters sort above digits. A mangled input now yields None, so a check sees unknown and reports unknown.

Migration note

No migration path. Nothing consumed 1.0 outside this repository and the eval corpus is not frozen. ENGINE_VERSION bumped so any cached verdict computed under the old derivation is correctly invalidated. Schema history recorded in docs/technical-design.md §1.

How it was verified

  • New tests: TestCommitOrder, TestCommitTimestamps (test_derive), TestArrayOrdering (test_canonical), TestCommitShaping (test_collector).
  • Regression guards for each failure mode above, including the hard ordering case — two check runs with the same name, same source, same second, and no id, where only the canonical-form tiebreak can separate them.
  • make test and make lint on 3.10 and 3.12 via CI.

Bundle schema 1.0 could not answer the question CM-2.A2 exists to ask.
Canonicalization sorts commits by authored_at, which destroys branch order,
and parents were never collected -- so "which commits landed after this
approval" was uncomputable. Author dates also survive rebase and cherry-pick,
so the timestamp fallback read a rebased branch as freshly approved.

derived.last_production_commit_at compounded it: the name claimed a scoping
that never happened (it took production_paths and ignored them) and returned
max(authored_at) over every commit, so a docs-only follow-up flipped a good
approval to stale.

Separately, checks tied on (name, source) and carried no id. Re-running a
workflow produced two entries that sorted by whatever the API returned that
minute, so identical evidence could hash differently between runs -- the same
silent shape as the three Stage 0 bugs. The tiebreak is now the item's own
canonical form, applied to every array, so ordering depends on contents alone.

Schema 1.0 -> 1.1. ENGINE_VERSION 0.0.1 -> 0.1.0.
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Tickmark — evidence collected

Control evaluation is not wired up yet. This is the evidence the engine sees.

Signal Value
Files changed 9 (+401 / -32)
Production paths touched 4
Test paths touched 4
Commits 1
Approving reviews 0
Checks reported 3 (0 success)
Linked issues 0
Work item references no pattern configured
Revert no
All files exempt no
Branch protection readable no
Notices
  • warn branch_protection_unavailable — Could not read branch protection (HTTP 403). Token likely lacks admin scope. Controls relying on it will report NOT_APPLICABLE.
Provenance
  • Evidence digest: sha256:8da0183bba62266e81eb8662fadf9f3b2eda1f5acdf45fa63093f1f26966aef6
  • Head SHA: ef701af6a1081b8db3b3a0f26751f7345b825ec4
  • PR state: open
  • Fork PR: False

@shiv2123
shiv2123 merged commit 95c8e70 into main Aug 8, 2026
1 of 3 checks passed
@shiv2123
shiv2123 deleted the fix/bundle-schema-1-1 branch August 8, 2026 05:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant