Resolve, publish and continue builder contribution lineage at the exact head - #989
Resolve, publish and continue builder contribution lineage at the exact head#989jeffhuber wants to merge 5 commits into
Conversation
A pull request can change hands. The PR opener, the branch prefix and the single active builder label each describe at most one lane, so after a takeover they disagree: PR #959 stayed authored on a Devin branch while Codex wrote the final commit and the active label moved to builder:codex, and the Devin reviewer still excluded itself because Devin had opened it. Add a shared exact-head lineage resolver and make the reviewer-admission seam consume it. - src/code_mower/builder_lineage.py resolves ordered contribution episodes bound to repository, PR, branch, source lane, destination lane, expected head and resulting head. Episodes come from the verified handoff and delivery path; an opener, a label, a body marker or a caller-supplied boolean cannot attest a takeover. Unchained, duplicated, unbound, malformed or behind-the-head evidence fails closed with one concise owner action instead of naming a writer. It also carries an idempotent metadata-only record store and a bounded hidden-marker transport. - src/code_mower/provider_runners/lineage.py is the one admission seam for direct reviewer wrappers: it resolves lineage from trusted PR metadata at the head the wrapper pinned and refuses any lane that contributed. - Codex, Claude and Devin CLI wrappers now admit through that seam after the trusted metadata/head fetch and before provider execution. The Devin wrapper keeps its bot-author deny list as a floor so an unconfigured checkout never becomes more permissive than it is today. - audit_labeler_lib exclusion and builder_runs auto-record resolve the same lineage rather than collapsing history to the latest author or label. Contribution independence stays a separate decision from role eligibility: a qualified lane that wrote the diff is still not independent of it. Closes #963 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gate built its own builder identity from one label map plus the PR author, then excluded exactly one lane and failed the whole PR when two identities appeared. After a verified takeover two identities is the correct answer, not a contradiction. - The gate now reads contribution episodes from the hidden lineage marker, but only out of comments it already trusts via trusted_comment_author, so publishing a marker stays a transport and never an authorization. Trusted checkout loading and comment attestation are unchanged. - It resolves through resolve_builder_lineage and excludes every verified contributor, not just the one the active label names. It still blocks when no independent lane remains. - Conflicting evidence now fails with the resolver's owner action; lineage that is behind the current head is pending, not a guess. - Unreadable published evidence fails closed rather than being ignored. Applied identically to the template, its packaged mirror and the canonical generated workflow, and tools/ mirrors plus the package manifest are kept in parity so the gate's tools.* import path resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Update — head The gate item in the PR description's "Not done" list is now done and that item is stale.
Still open on this branch
Tests at this head
|
Work Order: complete #963 acceptance on this PRContinue the same Claude-owned branch and keep this PR draft. The current head
The Code Mower broker independently checked immutable Claude remains the sole source author; Code Mower Codex is the planned independent exact-head reviewer after full delivery. No merge, no hosted Devin creates, no source edits in the parallel presentation/release scope, and no publication of private paths, bindings, prompts or provider output. |
The resolver landed with no producer: contribution episodes existed as a type, and every consumer that could have read one was still deciding from the PR opener or the single active label. This connects both ends. Producing side. `lane_handoff.record_contribution` is now the only writer of contribution lineage, and it writes only from evidence the handoff boundary already verified: repository, PR, branch, lanes and expected head from the accepted handoff, the source writer state from the acceptance record rather than the caller, and the resulting head from a fresh observation checked against what the runner reported. It refuses without a verified acceptance and a reserved launch, so a caller that merely names a handoff records nothing. Episodes live beside the intent store, never in it, so the private source binding cannot reach a record that reviewer admission and the public projection read. `lane-delivery classify --handoff-state-dir` records after a validated delivery; `lane-delivery lineage --reconcile-labels` moves the active builder label to the verified current writer, rechecking the exact head on both sides of the mutation and failing closed on either move. The maintained and generated Mac runners call both. Consuming side. Controller reviewer selection reads the resolved lineage instead of one label-derived lane: every verified contributor is excluded, role eligibility is consulted as a separate decision, and a head with no qualified independent reviewer left blocks with one owner action rather than merging on a reviewer that may have written the diff. Lane status resolves the lineage from the durable record; the Board carries the status and contributor lane names as bounded metadata, and the cloud contract is unchanged. The direct reviewer wrappers now load actual recorded evidence instead of the resolver's empty default, and `devin_review.ReviewInput.check` consults the same seam rather than its author deny list alone. A missing or malformed identity file no longer makes a contributing reviewer admissible: each wrapper names its own lane's label and accounts as a floor, which can only add exclusion. Init's exclusion payload carries the narrow resolver context the generated gate and labelers need -- branch identity per lane, and whether verified lineage is required rather than an identity-only answer. Refs #963 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Update — head This round connects the resolver that landed in What landed1. Verified delivery → resulting-head provenance. Wired through 2. Automatic active-label reconciliation. 3. Controller / lane status / Board. 4. Direct wrappers load actual evidence. 5. Init resolver context. TestsNot run in this checkout.
These are runtime denials, not implementation failures. Please run the canonical checks at Remaining on this branch
Labels unchanged: |
Codex audit (merge-authority lane)Head SHA: Codex Audit: BLOCKED Summary: The resolver's 26 read-only tests passed, but five production integration findings affect generated gate dependencies, takeover evidence, and lineage resolution. Findings:
|
Work Order: consolidated #963 repair and final verification candidateThe independent Code Mower Codex audit on
Use this previously successful provider-side form exactly, without a leading environment command: The broker is running the canonical full suite at immutable |
|
Canonical full-suite diagnostic at immutable The single failure is This is diagnostic evidence for the earlier implementation head, not final acceptance of the repaired/current-main-integrated head. The Ruff F401 and five independent Codex findings remain part of the already published repair work order. |
Resolves the five findings from the Codex audit of 420198a. - Generated product repositories now receive tools/builder_lineage.py, the dependency audit_labeler_lib imports. Without it a gate runner with no Code Mower package installed cannot import its own helper. - The runner publishes verified episodes as a bounded hidden marker before reconciling the active builder label, and abandons the label move if publication does not succeed. The GitHub gate reads episodes only from trusted comments, so a moved label with no published evidence was exactly the conflict this path exists to prevent. - The trailer and SaaS labeler callers carry trusted repository, branch, head and published-episode evidence, so an independent reviewer's exact-head verdict on a verified takeover reaches its done label while every contributor stays excluded. - Ordinary same-writer rounds after a takeover record a continuation episode, so a normal fix round no longer leaves lineage permanently behind the head. A continuation is a distinct episode kind with its own writer state; it cannot be forged into a handoff, and it displaces no other writer, so no quiescence or reservation contract is bypassed. - Recording and every reviewer reader resolve the same LANE_HANDOFF_STATE_DIR. A configured but non-absolute value fails closed rather than silently consulting a different store. The previously unused episodes_from_comment_body import in audit_labeler_lib now has a production use in published_lineage_episodes, clearing Ruff F401. Adds tests/test_builder_lineage_consumers.py: 37 consumer-level regressions covering the generated-gate standalone import, publication ordering, idempotency and bounded payload, all real labeler entry paths, continuation new heads/replay/stale evidence, the configured store, role eligibility kept separate from contribution independence, and ordinary single-builder behaviour. Closes #963 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Fix round complete — head All five findings from the Codex audit of Findings
Tests run
What remains
PR remains draft. Re-requesting the Codex audit at this head. |
Work Order: finish #963 against the accepted #955 snapshot interfaceContinue the same Claude-owned PR from The last canonical diagnostic run passed 480 tests and 277 subtests, with one controller failure and one Ruff B023 error. No final audit was requested on that incomplete candidate.
Use the known-working provider test command form |
Closes #963
What this PR does
Provenance, labels and reviewer exclusion were each centred on a single signal — the PR opener, the branch prefix, or the one active
builder:*label. None survives a takeover. PR #959 stayed authored on a Devin branch, Codex wrote the final repair commit, the active label moved tobuilder:codex, and the Devin reviewer still excluded itself purely because Devin had opened the PR.This adds one shared exact-head lineage resolver, a verified producer for it, and routes every consumer seam through it.
The resolver —
src/code_mower/builder_lineage.pyOrdered, metadata-only contribution episodes, each bound to repository, PR number, branch, source lane, destination lane, expected head and resulting head.
waiting, never a guess.conflictwith one concise owner action.handoffmoves the pen between two lanes; acontinuationis the lane that already holds it advancing the same PR. They carry different writer states and opposite source/destination constraints, so neither can be forged into the other, and lineage that does not begin with a handoff fails closed.The producer
lane_handoff.record_contributionwrites takeover episodes from the acceptance record and a fresh head observation.lane_handoff.record_continuationwrites ordinary same-writer rounds: it refuses unless the recorded tip already names the lane as current writer, unless the round started from exactly the head that tip left behind, and unless the head genuinely moved. Replay is idempotent. No handoff is manufactured and no quiescence or reservation contract is bypassed — a continuation displaces no other writer, because the writer that went quiescent is the lane's own supervised round, already terminated and reaped before classification runs.lane-delivery classifyis wired to both, and the maintained (tools/lanes/) and generated (templates/,src/code_mower/templates/) runners now pass--handoff-state-diron every PR round, not only handoff rounds.Publication, then reconciliation
lane-delivery lineage --publish --reconcile-labelspublishes the verified episodes as a bounded hidden marker on the PR, and only then moves the active builder label. Publication failure abandons the label move and exits non-zero: the GitHub gate reads episodes exclusively from comments it already trusts, so a reconciled label with no published evidence is the conflict this path exists to prevent. Publication is idempotent, publishes only lineage that resolves at the pinned head, and carries only lane names, a repo slug, a PR number, a branch and commit shas.Consumers connected
init.PRODUCT_SUPPORT_FILESnow copiestools/builder_lineage.pyalongsidetools/audit_labeler_lib.py, so a gate runner with no Code Mower package installed can import its helper.codex_audit_pr,claude_audit_pranddevin_cli_audit_pradmit throughprovider_runners.lineageafter the trusted metadata/head fetch and before provider execution.trailer_comment_labelerandsaas_reviewer_labelerbuild aLineageContextfrom the repository they run in, the head they fetched, the branch, and episodes published by the repository's configured decision authorities — then pass it toauthor_exclusion_reason/builder_identity_matches. Trust is the caller's author check; a marker's presence is never authorization.LANE_HANDOFF_STATE_DIR. A configured but non-absolute value fails closed rather than silently reading a different store.Audit findings resolved (from the Codex audit of
420198a4)codex:5f53e76584997c80c318(P1)tools/builder_lineage.pyadded toPRODUCT_SUPPORT_FILES; a test copies the list into a clean directory and imports the helper in a subprocess with no package and noPYTHONPATH.codex:4725ce39cb69b7ac68e7(P1)publish_lineage_evidence+lineage --publish, ordered before reconciliation in code and in all three runner mirrors.codex:9bac84962f544d9fa4bb(P2)LineageContextthreaded through both labelers and all their entry paths.codex:7b1f8c5e122de3ff7727(P2)record_continuationand thecontinuationepisode kind, wired intoclassify.codex:69acb7733dd8b7ba1b31(P2)lane_handoff.configured_root(), used byrecorded_episodesand_lineage_main.episodes_from_comment_bodynow has a production use inpublished_lineage_episodes, which finding 3 required.Verification
Run in this checkout with
pytest -o pythonpath=src:tests/test_builder_lineage_consumers.py— 37 passed, 3 subtests passed. New consumer-level regressions: generated-gate standalone import; publication ordering, idempotency, bounded payload, and the label left alone when publication fails; both labelers' real entry paths including the independent-Claude-on-a-Devin-to-Codex-takeover case and the identity-only failure it replaces; continuation new heads, replay, stale evidence, wrong writer, undelivered and unmoved rounds; the configured store on both recording and admission; role eligibility kept separate from contribution independence; ordinary single-builder behaviour.tests/test_builder_lineage.py— 28 passed, 16 subtests passed.tests/test_release_hygiene.py -k "manifest or materializer or readiness or product_support"— 98 passed, 427 subtests passed.tests/test_builder_identity.py tests/test_builder_runs.py tests/test_trailer_comment_labeler.py— passed.tests/test_lane_handoff.py tests/test_branch_policy.py tests/test_lane_delivery_contract.py— 31 failed, 160 passed, 1 skipped, 178 subtests passed. Byte-identical counts with and without this diff (verified by stashing): these are pre-existing sandbox failures where the tests' temporary directories fall inside a Git checkout, which the private context store refuses by design.Factual limitations
ruff check src testsis denied by this sandbox, so Ruff has not been run here. The F401 the audit named is resolved by giving the import a production use; any remaining lint is for CI to report.pull_request_reviewentry path does not fetch issue comments, so on that path the lineage context carries the verified repository, branch and head but no published episodes. It falls back to identity-only resolution there, which is today's behaviour; theissue_commentand label-backfill paths do carry episodes.CODE_MOWER_DECISION_AUTHORITIESto be configured. An unconfigured checkout trusts nobody, reads no episodes, and behaves exactly as it does today — this is deliberate, and narrower than trusting audit comment authors.Privacy
Everything crossing a public or cloud boundary is lane names, a repo slug, a PR number, a branch name and commit shas. No source, diffs, prompts, transcripts, paths, session identifiers, provider references or raw output. No new cloud schema and no hosted implementation. A test asserts the published payload contains no field beyond the bounded episode contract.
Provenance
Implemented by Claude (Opus 5) in the Code Mower Claude builder lane. No other builder or agent contributed to this diff, so Code Mower Codex remains an eligible independent exact-head reviewer.