Repository navigation
Harden Mercurial ancestry and head validation - #7392
Merged
Merged
Conversation
briansrls
added a commit
that referenced
this pull request
Jul 29, 2026
…ry missing function as "no main" (#7401) The generated-artifact heal ran a binary and a source tree from DIFFERENT revisions. `build` compiles the default pull_request checkout — the merge ref — while `heal` checks out `github.head_ref`, the branch head, because it pushes the regeneration back. Nothing paired them. A generated artifact's identity is (authority source x compiler), and the job silently combined two revisions of it. Latent until a seed change adds a Rust->.dag call. #7348 landed the scoped-run observation seed, whose Rust calls gunbc.observation_seed_render.scoped_run_begin_write_measured; every open PR whose branch head predated it went red at once (#7392, #7394, #7395 — identical failure, none of them at fault), while main stayed green because ci_regen_heal_if skips this job on push. The regression was invisible where it landed and blamed three PRs that did not cause it. Reproduced by execution: ONE binary built from the merge ref, run against a pre-#7348 tree reproduces the exact CI failure; run against the same tree with the advance merged in it exits 0 with zero drift. The guard compares the tree against `github.sha` — the revision the build job actually compiled — not against `origin/main`. Per operator ruling 2026-07-29, `origin/main` names several different things (this runner's, a developer's, the real source of truth) and can race an advance mid-run; the binary's own provenance has no such ambiguity and is known exactly within the same workflow run. An unresolvable provenance refuses rather than assuming agreement. It is deliberately narrow: only SEED (src/v1) commits can introduce a Rust->.dag expectation, so a .dag-only advance cannot skew a compiled binary and does not red the PR. At the incident's branch point the set is two commits, one of which IS 445bac3 (#7348) — the guard names the responsible commit in its own refusal; at current main and post-merge it is empty and silent. Preventing a stale branch from MERGING is deliberately out of scope: that needs a source-of-truth model the repo does not have yet (SCM lane, after v1 deletion). Landed as a declared Scaffold — validation where construction (heal building from the tree it heals) was too expensive today — with that named dissolution trigger. Second, independent defect, and the reason this cost hours to find: both run_in_context and run_in_context_with_args threw away the name they had just failed to look up and reported InterpError::NoMainFunction, so EVERY missing named function surfaced as "no main function found". main_wet was never missing. NoSuchFunction { name } already existed; both sites now use it, and since those were NoMainFunction's only two constructors the variant is deleted rather than left unconstructible. Its one consumer, run_claim_failure_receipt, had to match both variants precisely because of this bug and now matches the single honest arm. Proven by a discriminating run: the same failure now reads "no such function: scoped_run_begin_write_measured". Verified: whole-tree compile 0 blocking errors; generated_artifact_drift_test 8/8 including witness_committed_is_fixed_point (ci.yml regenerated from the model, not hand-edited) and its RED control; observation_seed_scoped_run_witness_test 10/10; cargo test --test scoped_run_observation 7/7; cargo fmt --all --check clean; emitted guard bash -n clean. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Sep 2, 2026
… emission The heal capability died with `ci.yml` in the 2026-08-15 floor cut (gunbc.rung_drop floor_cut_heal). Everything the job DOES survived that cut unconsumed -- gunbc.heal_push_plan decides per artifact whether the Actions job credential may advance its path, gunbc.ci_spec renders the git config / drift census / staging / commit / push script from that decision, and gunbc.ci_heal_credential models the checkout step whose persisted credential is the push authority. What did not survive was a WORKFLOW emitting a job that binds them, so the whole model was a check with no consumer. This lands that consumer as a job of gunbc.witness_floor_workflow, the repository's only CI workflow authority, rather than as a restored second workflow file: witnesses.yml is already a registered generated artifact, so no GeneratedArtifact variant or registry row is needed and DESIGN's "CI is one emission" is preserved. FOUR ARMS OF THE HISTORICAL JOB ARE ABSENT BY CONSTRUCTION, NOT BY DELETION. The skew guard, its automatic merge remedy, the `checkout --ours` conflict arm and the ~200-path hand-maintained `:(exclude)` roster all existed to manage one defect: the old job's binary came from a job that checked out the MERGE ref while heal checked out the BRANCH HEAD, so the two could name different revisions of src/v1 (incident 2026-07-29; #7392, #7394, #7395). This job builds its own `gunbc` from the SAME branch-head checkout it then regenerates, in one job, so binary and tree are one revision and there is no second revision to skew against. DESIGN 4b: the class moves from rung 2 to rung 4, and the arms dissolve with the climb. Two of them would also be defects to restore -- taking the ours side is what the generated-artifact merge driver now refuses to do, and the roster was a second authority for a fact gunbc.generated_artifact owns. The auto-push-versus-author-commit split is derived from that registry instead. THE PUSHED HEAD IS NOT REVALIDATED AND THE JOB SAYS SO. An Actions-credential push starts no workflow run, so after a heal the PR's checks describe the prior head. tools.ci_heal_dispatch targets `ci.yml`, which does not exist, and witnesses.yml declares no expected_healed_sha input -- dispatching without it would fabricate a revalidation rather than perform one. The job instead prints SupersededByHealedHead naming both revisions and exits nonzero. The gap is declared in the emission and its trigger names the capability (a dispatch input the run binds github.sha and its checkout against), not an artifact. The job carries no lane and no `needs` edge of the required `witnesses` aggregate: heal REMEDIATES, the build lane's generated-artifact phase ADJUDICATES. Making a remediator a gate would let its own nonzero arm block the branch it had just repaired. Also repaired: ci_heal_credential's ci_heal_job_ref and ci_heal_workflow_ref were DeclarationRefs to the deleted gunbc.ci_workflow, so the grant deciding whether the heal push is authorized was computed against a workflow and a job with no existence. They now name the emission that carries the job. floor_cut_heal is NOT retired here. Its trigger demands the capability observed writing and pushing on a real divergence; an emission that typechecks is not that observation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVcFN21urwK3LttBSF6EWK
briansrls
pushed a commit
that referenced
this pull request
Sep 2, 2026
… emission (#10118) * MAIN-H: restore the generated-artifact heal job as a modeled workflow emission The heal capability died with `ci.yml` in the 2026-08-15 floor cut (gunbc.rung_drop floor_cut_heal). Everything the job DOES survived that cut unconsumed -- gunbc.heal_push_plan decides per artifact whether the Actions job credential may advance its path, gunbc.ci_spec renders the git config / drift census / staging / commit / push script from that decision, and gunbc.ci_heal_credential models the checkout step whose persisted credential is the push authority. What did not survive was a WORKFLOW emitting a job that binds them, so the whole model was a check with no consumer. This lands that consumer as a job of gunbc.witness_floor_workflow, the repository's only CI workflow authority, rather than as a restored second workflow file: witnesses.yml is already a registered generated artifact, so no GeneratedArtifact variant or registry row is needed and DESIGN's "CI is one emission" is preserved. FOUR ARMS OF THE HISTORICAL JOB ARE ABSENT BY CONSTRUCTION, NOT BY DELETION. The skew guard, its automatic merge remedy, the `checkout --ours` conflict arm and the ~200-path hand-maintained `:(exclude)` roster all existed to manage one defect: the old job's binary came from a job that checked out the MERGE ref while heal checked out the BRANCH HEAD, so the two could name different revisions of src/v1 (incident 2026-07-29; #7392, #7394, #7395). This job builds its own `gunbc` from the SAME branch-head checkout it then regenerates, in one job, so binary and tree are one revision and there is no second revision to skew against. DESIGN 4b: the class moves from rung 2 to rung 4, and the arms dissolve with the climb. Two of them would also be defects to restore -- taking the ours side is what the generated-artifact merge driver now refuses to do, and the roster was a second authority for a fact gunbc.generated_artifact owns. The auto-push-versus-author-commit split is derived from that registry instead. THE PUSHED HEAD IS NOT REVALIDATED AND THE JOB SAYS SO. An Actions-credential push starts no workflow run, so after a heal the PR's checks describe the prior head. tools.ci_heal_dispatch targets `ci.yml`, which does not exist, and witnesses.yml declares no expected_healed_sha input -- dispatching without it would fabricate a revalidation rather than perform one. The job instead prints SupersededByHealedHead naming both revisions and exits nonzero. The gap is declared in the emission and its trigger names the capability (a dispatch input the run binds github.sha and its checkout against), not an artifact. The job carries no lane and no `needs` edge of the required `witnesses` aggregate: heal REMEDIATES, the build lane's generated-artifact phase ADJUDICATES. Making a remediator a gate would let its own nonzero arm block the branch it had just repaired. Also repaired: ci_heal_credential's ci_heal_job_ref and ci_heal_workflow_ref were DeclarationRefs to the deleted gunbc.ci_workflow, so the grant deciding whether the heal push is authorized was computed against a workflow and a job with no existence. They now name the emission that carries the job. floor_cut_heal is NOT retired here. Its trigger demands the capability observed writing and pushing on a real divergence; an emission that typechecks is not that observation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVcFN21urwK3LttBSF6EWK * Fix two defects the floor's parse phase and the citation-debt roster caught BOTH ARE MINE AND BOTH WERE INVISIBLE TO THE INSTRUMENT I HAD BEEN READING. (1) SOURCE ANNOTATION AT THE WRONG GRAIN. The comment explaining why the heal regen step is capability_neutral sat INSIDE the WitnessFloorBoundStep record literal. DESIGN 4c models annotations at module-item grain only, so the floor's parse phase refused with nine located diagnostics and zero witnesses ran. The reasoning is preserved verbatim, hoisted above heal_generated_artifacts_bound_steps where it is authorable. WHY IT REACHED CI: `gunbc run ... main_wet` returned 0 over the whole tree and I read that as "the tree typechecks". It is not that check -- the annotation-grain rule executes in compile-clean and in the floor's parse phase, neither of which main_wet runs. A green from an instrument that does not cover the class is not evidence about the class. (2) SPENT CITATION-DEBT ROWS. Repointing ci_heal_job_ref and ci_heal_workflow_ref at gunbc.witness_floor_workflow made their PRE_EXISTING_CITATION_DEBT rows in declaration_index.rs stale: the citations no longer refuse, and that roster only shrinks, so the rows are deleted. Found by running v1_src_dag_parse, which reports it directly -- not by CI, which had not reached it yet. It would have been the next red. The second one is the reason to go looking for a covering instrument after a failure instead of repairing only the line the failure named. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVcFN21urwK3LttBSF6EWK * Drop the heal job's `actions` permission: its only consumer is not built ci_heal_job_permissions carried actions: PermWrite for exactly one consumer -- github.Workflows.CreateDispatch, which the heal job invoked to revalidate the head it had just pushed (#7544). That dispatch is deliberately not restored in this cut, so the job was emitting `actions: write` for a capability it does not exercise: a standing control-plane grant held in anticipation of a follow-up that may never land. The credential surface should describe what the job DOES. Now `contents: write` / `actions: none`. Where the grant comes back, when it does: with the dispatch consumer, as that consumer's own carrier, at the point it becomes reachable -- rather than pre-granted here where the follow-up would land against a permission nobody re-derived. VERIFIED NOT TO MOVE THE PLAN'S VERDICT, which is the thing this edit could have changed silently: the auto-push versus author-commit split is still exactly the three workflow projections refused and 33 paths staged, and the refusal cause is still ActionsJobCredentialScopeUnavailable -- that split turns on the `workflows` axis, not on `actions`. Also merges current main (8e025f8; the design-ledgers split into design-failure-modes.md and design-rung-drops.md), and re-derives the projection against it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVcFN21urwK3LttBSF6EWK * EVIDENCE PROBE: hand-edit a generated projection without regenerating it DELIBERATE DRIFT, and the only commit on this branch that is meant to be undone by a machine rather than by me. gunbc.rung_drop floor_cut_heal retires on one thing: the heal capability OBSERVED writing regenerated bytes back and pushing them, on a real divergence. An emission that typechecks and a green no-drift run establish neither. THE SUBJECT IS CHOSEN, NOT CONVENIENT. docs/plans/input-envelope-roadmap.md is auto-push-eligible, verified against the emitted script itself rather than assumed: it carries a `git add` line and does NOT appear in the AUTHOR_COMMIT_DRIFT population. The three workflow projections would have been the wrong subject -- they deliberately select AuthorCommitRequired, so drifting one exercises the bundle-and-refuse arm while LOOKING like a heal run and would have told me nothing about whether this job pushes. PRE-STATE, recorded here because the receipt is worthless without the base it is a claim about: the clean file is 3555 bytes, sha256 4da64e1495ef627c31fa21f3e31e2746ba28c5be447c9f6565d9a004f2c23ead. EXPECTED, written before the run so the result cannot be read to fit: the generated-artifact phase in the build lane goes RED on this commit (it adjudicates, and this tree is genuinely not at its fixed point); the heal job regenerates, stages exactly this path, commits as gunbc-ci-auto-heal, pushes to this branch, prints HealProduced with both heads and CHANGED_ARTIFACTS naming this file alone, then prints SupersededByHealedHead and EXITS NONZERO because nothing has revalidated the head it just created. If instead it prints HealNoChange, or HealAuthorCommitRequired, or exits zero, the capability is not what this PR claims and the rung-drop row stays exactly where it is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVcFN21urwK3LttBSF6EWK * chore: regenerate drifted generated artifacts (ci auto-heal) * Retire floor_cut_heal on an observed repair, and let the ledger say so TWO CHANGES, COUPLED: the retirement, and the rendering repair without which the retirement is invisible at the surface DESIGN sends readers to. THE RETIREMENT, ON EXECUTION AND NOT ON THE EMISSION. floor_cut_heal's trigger is a capability: a required-run consumer that, on a branch head whose committed generated artifacts diverge from their authorities, writes the regenerated bytes back and pushes them under a credential the repository already models -- observed doing so on a real divergence, not merely emitted. PROBE 330c74f hand-edited docs/plans/input-envelope-roadmap.md, a generated projection, 3555 -> 3754 bytes, and did not regenerate it. Pre-drift sha256 4da64e1495ef627c31fa21f3e31e2746ba28c5be447c9f6565d9a004f2c23ead, recorded BEFORE the drift. Subject chosen against the emitted script rather than assumed: it carries a `git add` line and is absent from the AUTHOR_COMMIT_DRIFT population, so it exercises the PUSH arm. The three workflow projections would have exercised bundle-and-refuse while looking like a heal run. RUN 33683175090 job 100433326005: HealProduced prior_head=330c74f07 healed_head=bc70468754 changed_artifacts=docs/plans/input-envelope-roadmap.md -- exactly one path, no blast radius across the other 32 auto-push rows -- then SupersededByHealedHead and exit 1. PUSH branch head moved to bc70468, authored gunbc-ci-auto-heal, 1 file changed, 2 deletions. IDENTITY, two ways: the healed file is byte-identical to the pre-drift digest, and a clean source-built regeneration ON the healed head changed zero files. src/ and dag/ are identical between that head and the tree the binary was built from, so there is no compiler/tree pairing assumed. The predictions were written into the probe commit before the run, so the result is not a story fitted to an outcome. Restored rung: 2. NOT restored and not claimed: revalidation of the head heal creates, which keeps its own capability trigger. floor_cut does not move -- its conjunction is derived by standing_rung_drops(), so it follows this row without being edited. THE LEDGER COULD NOT EXPRESS A RETIREMENT, which is why it is repaired here rather than deferred. gunbc.design_ledgers read subject/declared/authored and never read `standing`, so all four retired rows -- including three that predate this PR -- rendered under headings spelled identically to the live ones, and trigger_fired was rendered nowhere. A reader counting outstanding safety regressions in the file DESIGN points them to counted retired ones among them. Rendering only: no model state moves, and standing_rung_drops remains the authority anything joins against. ACCEPTANCE TEST, a membership join and not a count, since a count can agree while the sets differ: subjects rendered without the retired marker in docs/design-rung-drops.md == subjects whose rows carry standing: Standing in gunbc.rung_drop. PASS at identity grain, 18 standing and 4 retired, both sets equal, LC_ALL=C pinned on both sides after locale collation alone produced a false difference. SHOWN TO DISCRIMINATE rather than asserted: un-marking one retired heading makes it go red with one extra subject. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVcFN21urwK3LttBSF6EWK --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow up the merged Mercurial R0 model with construction-safe zero/one/two-parent ancestry, fail-closed cycle and duplicate-parent detection for changelog and filelog revisions, and validation that observed open named-branch heads equal the heads derived from the captured graph. Keep R0 conditional advance unavailable until an executing transport supplies a typed receipt.
This addresses the substantive Mercurial findings from review 44144 and the cross-PR model review without introducing a common SCM carrier.
Test plan
dag/test/claim/mercurial_upstream_model_witness_test.dag: PASS.PATH=/tmp/gunbc-mercurial-source.2qwAwR/mercurial-7.2.3:$PATH target/debug/claim_batch --wet --source-root dag --source-root src/v2 --entry dag/test/manual/mercurial_upstream_model_execution_test.dag --functions witness_mercurial_cli_fixture_projects_and_independently_reads_back: PASS.git diff --check: PASS.