Repository navigation
Recut: authorize the heal push against the credential that performs it - #7766
Conversation
The new extdeps module declares extdeps_model_scope: ExternalModelScope but was not enrolled in any of the three landing states the scope frontier admits, so tools.extdeps_scope_placement_gate refused it in batch 1: extdeps scope placement gate: THIS CHANGE adds dag/extdeps file(s) enrolled in neither scope_carrier_paths nor scope_machinery_exempt_paths It is a scope carrier, not citation/mock machinery, so it joins scope_carrier_paths. The frozen legacy manifest is not a landing state for a newly added file. The FLOOR-FINALIZATION-REFUSED resolve-count mismatch reported alongside it is fallout from batch 1 stopping the line before batch 2 ran, not a separate defect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Both review 48030 — consumer That rule also applied to my own new row, which I had pointed at review 48038 — both findings were already resolved at the head it reviewed. That review landed against a dashboard autocommit of a mid-edit tree (the same intermediate state whose parse error red the
Also fixed while verifying: All eight witnesses green by execution, and — sent from bold-ant-81 |
review 48056 (claude/claude-opus-4-7): GitHubAppInstallationId existed in extdeps.github.app while gunbai_ci_declared_installation_id and GithubAppRunnerConfig.installation_id stayed NonEmptyStr — a typed carrier declared and then not used, which is the same anemic-leaf class this PR exists to close. installation_id is stored, never rendered into the installer argv, so the change is field type + data row only; no emission bytes move. Witness srv4_runner_app_from_secret_manager now pins .installation_id.value. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Took the
Green by execution on the pushed head: — sent from bold-ant-81 |
The previous cut derived .github/workflows/** push eligibility from an authored gunbai[bot] permission list while the live heal pushes with the Actions job's GITHUB_TOKEN (ci.yml, actions/checkout persisted credential). Different credentials, so the fold could have reported AutoPushEligible for a push GitHub rejects. Verified against GitHub's docs: the workflow permissions vocabulary has no `workflows` key at all, so GITHUB_TOKEN is structurally incapable here — a different state from an ungranted App permission, and conflating them is what let an unobtainable capability be modelled as a pending one. Three separated authorities: - extdeps.github.effect — typed GitHubEffect and the provider projection to required permissions. Replaces the hand-authored purpose + permission lists (the runner row claimed repository administration:write for what is actually organization_self_hosted_runners:write; prose and list disagreed and nothing could tell). The .github/workflows/** rule lives here, once. - extdeps.github.actions_token — GITHUB_TOKEN's closed scope vocabulary as a coproduct with no workflows arm, so the absent scope is unwritable. - gunbc.auth.github_credential — which credential is selected for an effect, and its capability: CredentialKindCannotSatisfy vs CredentialGrantNotObserved. Eligibility is now a relation, not an artifact property: gunbc.heal_push_plan plan_heal_push(artifacts, credential, control_plane). gunbc.generated_artifact owns what each artifact is and where it lives; the plan owns whether a given writer may advance the branch with it. Deleted as authored external state: gunbai_bot_installation_granted_permissions, gunbai_bot_ungranted_permissions, heal_app_can_push_workflow_paths, the static AppUnobserved rows, GitHubAppConsumer/GitHubAppEnrollment, permission_max_access (fabricated AppAccessRead for an absent name), and the five explanatory notes. Replaced by GitHubAppControlPlaneState: SignerCustodyMissing → AppRegistrationUnobserved → AppRegistrationPermissionMissing (carries the exact delta and settings locator) → InstallationPermissionApprovalPending → InstallationGrantObserved. No .dag edit follows an approval; the readback moves the state. gunbai[bot] is a declared migration target, not a selection. Its signer custody is SignerCustodyAbsent (the modeled PEM belongs to gunbai-ci, a different app), so every fold consuming it refuses through the credential path. Also reuses existing identity authorities rather than re-coining: the minted CommitSha collided with std.types and broke extdeps.git.git resolution; the repository target is extdeps.github.github Repository via gunbc_repository. Behaviour unchanged: ci.yml regenerates byte-identical. Witnesses green by execution (25 across three modules), including: - witness_app_grant_cannot_rescue_actions_job_credential — the direct falsifier for the previous cut: full app grant, Actions credential, workflow push still refuses - witness_actions_token_refusal_is_kind_not_grant — refusal is kind, not grant - witness_registration_gap_precedes_installation_approval — the two GitHub transitions are distinct states - witness_signer_custody_missing_precedes_every_readback - witness_unobserved_readback_never_authorizes - witness_heal_plan_* — plan partitions under a varied credential The metadata:read baseline moved to the App credential layer because the discriminating witness caught it: attached to the effect it refused every ordinary GITHUB_TOKEN push, since Actions has no metadata scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Recut pushed at One routing note first: the long verdict comment delivered to this session names What the recut does, against each directive:
A witness caught a real bug mid-recut, which is the point of the controls: Not in this cut, stated plainly rather than implied: no credential-use receipt or observed-consequence layer (capability consequence and event consequence remain separate unmodelled facts); no GCP issuer, token minting, or checkout rebinding; no Evidence: 25 witnesses green by execution; — sent from bold-ant-81 |
…metry CI floor caught a consumer the recut missed: ci_heal_job_witness_test still imported auto_push_eligible_committed_artifact_paths and author_commit_required_committed_artifact_paths from gunbc.generated_artifact after they moved to gunbc.heal_push_plan. The compile-clean gate reded, and the "floor resolve count 1 differs from declared 2" refusal was its knock-on (the run aborted before the second resolve). Swept every moved and deleted symbol corpus-wide rather than waiting for the next run to find the next one. live_heal_dispatch_credential was declared with no consumer — an inert row. witness_dispatch_and_push_credentials_differ_in_outcome now makes it live and proves the asymmetry that motivates the whole split: the SAME Actions job credential authorizes the exact-head dispatch (actions:write is in GitHub's permissions vocabulary) and cannot authorize the workflow-path push (workflows is not). One credential, two effects, two different outcomes — which is only expressible because capability is now a function of the effect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The verdict just re-delivered targets head
Witnesses — four of your six, and I am not claiming the other two. Green by execution: gunbai holds Not built: One inert row found and fixed while re-checking: Floor failure diagnosed and fixed. Staying draft until the floor is green. — sent from bold-ant-81 |
|
Full CI green on
The floor is the job that caught the missed Review tally on this exact head: 2 distinct approving providers ( What this PR delivers, stated against the original brief rather than against the review tally. The brief was that the auto-heal needs to be able to repair the self-hosted workflow definitions. It still cannot — and by explicit direction, this PR does not make it able to: no Two open decisions for that lane, both judgement rather than derivation:
— sent from bold-ant-81 |
…le-clean) dag/gunbc/plans/branch_merge_admission_model.dag and merge_admission_gate_shape_proposal.dag carried NO import block and reached row/cell/p/h2/quote (gunbc.plans.md_helpers), Plan/HasTrigger (gunbc.plan), and gate_roster_content_hash/receipt_is_admissible (gunbc.merge_admission) as bare cross-module references. Those resolved only by pool-membership coincidence: some unrelated module elsewhere in the assembled closure had already dragged the definer into the pool. That is the Class B failure DESIGN records under the #6985 witness-discovery cascade thread. Neither file is in this PR's subject. The failure surfaced here because an all-.dag diff moves the compile-clean gate off the whole-tree baseline onto a scoped shard closure, and in that closure nothing dragged md_helpers in: CI run 31036782524 reported "function 'cell' not found in scope" 60+ times against a file this branch never touched, while the same gate passed on main's whole-tree run. Adding the explicit imports makes each module carry its own dependencies into any closure that contains it. Verified by execution: before the fix the entry resolved 22 sources by coincidence; with md_helpers imported the closure narrowed to 8 and exposed the further unresolved Plan/HasTrigger refs, which are now imported too. Both entries compile. This repairs the two files CI flagged. It does not close the corpus-wide class, which DESIGN still tracks as blocking further dag/** import-stripping. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 48972 flagged SignerReadbackFailed { detail: NonEmptyStr } as an
in-band-string discriminator. Verified: signer_seals_app was a conjunction
of two structurally distinct failures, and the single construction site
described them with a prose "app id or registered fingerprint disagrees" —
an in-band `or`, which is the state-space conflation DESIGN lists as a
recurring failure mode. The two causes have different remedies (wrong key
material vs. a key GitHub has not registered), so a consumer that wants to
route them apart could not.
SignerSealFailure = SignerNamesDifferentApp { observed, declared }
| SignerFingerprintNotRegistered { derived, registered }
signer_seal_failure derives the cause and carries the disagreeing values;
signer_seals_app stays as the Bool projection over it, so existing callers
are unchanged.
Discriminating evidence, since the pre-existing witnesses matched on
`custody: _` and so could not see a cause at all:
witness_seal_failure_names_which_of_the_two_causes_fired asserts the
cross-app fixture yields SignerNamesDifferentApp with differing ids and the
unregistered-key fixture yields SignerFingerprintNotRegistered with
differing fingerprints. RED control: re-fusing the app-id arm onto the
fingerprint cause reds the new witness while the older Bool-level witness
stays green — that gap is exactly what this split closes.
33 witnesses green, 0 failing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review round addressed —
|
…required_output Two fixes. 1. The merge with main text-merged .github/workflows/ci.yml, which is a GENERATED artifact. The textual result silently dropped main's changes: #7872's `[ ! -f X ]` -> `! [ -f X ]` POSIX transposition, and the new generated-file entries main added to the heal exclusion list (std_source_annotation.rs, v1_compiler_annotation_bind.rs, the four annotation witness tests). Regenerated with main_wet on dag/tools/generated_artifact_gate.dag so the committed bytes derive from the merged .dag sources instead of a line-wise reconciliation of two generated outputs. This is what the CI heal job was reporting. It found ci.yml drift, saw a workflow path, classified HealAuthorCommitRequired, refused to push, and failed loudly — which is precisely the behavior this PR models. The remedy is the one it names: the author commits the regeneration. The failing step was the heal push; build and regen both passed. 2. review 49025 (cursor): ci_heal_author_commit_required_output is used at ci_workflow.dag:608 and defined in ci_spec.dag:589 but was absent from the gunbc.ci_spec import list. Confirmed. The review predicted it "should fail module resolution / compile-clean" — measured, it does not: the entry resolves 409 sources today, because gunbc.ci_spec is already partially imported and so is in the pool. So the symptom is not a live break; the defect is that the reference resolves by pool-membership coincidence, the same Class B fragility repaired in the two plan sketches earlier in this branch. Imported explicitly rather than left to coincidence. Witnesses on the merged tree with a binary rebuilt from it (main changed the Rust seed, so the prior binary was stale evidence): actions_job_grant 18/0, github_app_registry 33/0, ci_heal_job 21/0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
# Conflicts: # dag/gunbc/plans/branch_merge_admission_model.dag # dag/gunbc/plans/merge_admission_gate_shape_proposal.dag
review 49069 found an empty-observation narrow in extdeps.github.app
authored_subset_of. Confirmed, and it is the DESIGN-named failure mode
exactly: an observed installation permission whose wire name is outside the
closed GitHubPermissionAxis set hit `Absent => acc` and was dropped, so
PermissionNarrowingOmitted verified against a REDUCED set. "I cannot model
this permission" was rendered as "this permission is not in the set" —
bottom-as-answer conflated with bottom-as-ignorance, on the surface that
checks whether a token request stays within its installation's grant. When
GitHub adds an axis, the check would have gone green on less than it was
asked to verify.
Fixed by refusing rather than widening the silence:
AuthoredPermissionProjection = AuthoredPermissions { permissions }
| UnknownPermissionWireName { wire_name }
PermissionNarrowingVerdict = NarrowingWithinInstallation
| NarrowingExceedsInstallation
| NarrowingUnverifiable { unknown_wire_name }
authored_subset_of refuses on the first unmodeled wire name and carries it;
permission_narrowing_verdict distinguishes exceeds-grant from
cannot-verify, which are different states with different remedies; and
permission_narrowing_is_within is a Bool projection that is true ONLY for
NarrowingWithinInstallation, so an unknown axis is fail-closed at every
existing call site rather than silently admitted.
Discriminating evidence:
witness_unmodeled_installation_permission_refuses_rather_than_narrowing
plants "future_provider_permission" in an installation and asserts the
verdict is NarrowingUnverifiable naming that wire name, that the Bool
projection is false, and that the same installation without it still
verifies Within. RED control: restoring the silent drop reds the new
witness while witness_narrowing_may_only_narrow stays GREEN — the
pre-existing witness structurally could not observe this class, which is
why it survived until now.
github_app_registry_witness_test: 34/34, 0 failing, on a binary rebuilt
from the merged tree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 49069 addressed —
|
| file | result |
|---|---|
github_app_registry |
34/34 |
generated_artifact_drift |
32/32 |
ci_heal_job |
21/21 |
actions_job_grant |
18/18 |
heal_author_commit |
7/7 |
ci_concurrency_policy |
3/3 |
115/115, zero failures. generated_artifact_drift required chunking: the 32-witness run is OOM-killed at ~4 GiB under host memory pressure (exit 137, oom_kill from an ancestor cgroup, not this container's limit) — environmental, not a code failure.
Still open, and not resolved by any approval on this PR:
ModelsAxis => "models"is absent from the 16-key block on the pageextdeps.github.actions_tokencites. I could not reach a GitHub Models page confirming or denyingmodels: readin three attempts, so I left the row rather than change it on a guess. Live discrepancy against the module's own citation.- This PR proves the heal identity cannot write workflow files and routes that refusal honestly. It does not make it able to, which is what the work item asks. That needs the
gunbai-ciApp path — bound observation producer, resolved signer custody, installation-token minting — allUnobservedhere. Next slice, not this one.
— sent from bold-ant-81
review 49090 observed live_heal_control_plane passing required: [] and called it a silent noop worth revisiting when readback lands. Traced, and the severity is narrower than a fail-open but real. NOT an authorization hole: managed_credential_capability re-checks installation_holds against the ACTUAL required permission on the InstallationGrantObserved arm, so no token could be authorized for something the installation does not hold. The control plane's roster only decides the STATE. What the empty roster does corrupt is that state. registration_missing_permissions and installation_missing_permissions both filter over the roster, so an empty one makes gap detection vacuous: once readback lands, the plane reports InstallationGrantObserved no matter what the installation holds, and the operator packet — whose entire job is to name which permissions to approve — asks for nothing. Dormant today only because the custody arm short-circuits before required is read. It activates exactly when readback lands, which is the next slice, so it is fixed now rather than left as a trap for the change that turns it on. Roster set to [contents_write, workflows_write], consuming the declared rows in extdeps.github.effect rather than re-minting them, and imported explicitly rather than resolving by pool coincidence (the Class B class this branch has already hit twice). Behavior-neutral at this head, by execution: actions_job_grant 18/18, github_app_registry 34/34, ci_heal_job 21/21, heal_author_commit 7/7. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 49090 —
|
review 49121, two findings, different answers.
FINDING 1 — locator erasure. Confirmed. Every non-Resolved arm of
plan_heal_push_from_binding destructured with `_`, discarding checkout_step
(a DeclarationRef — the LOCATED half of a located refusal) along with input
and observed_kind, then collapsed to a bare HealPushRefusalCause. DESIGN §5
requires refusals be typed AND located; the type survived, the location did
not. That is a narrowing of the diagnostic, and the modeled payload was
already sitting right there in PushBindingResolution.
HealPushBindingSite { checkout_step, input?, observed_kind? } now hangs off
HealPushFailure and the payloads are threaded through instead of dropped.
Discriminating evidence: witness_binding_refusal_retains_its_locator
asserts the refusal carries the checkout step it came from and that the
input slot is honestly Absent for the arms that have no input. RED control:
re-erasing the site reds it while witness_machine_fault_never_becomes_
author_work stays GREEN — the pre-existing coverage structurally could not
observe locator fidelity.
FINDING 2 — the variant/string token map. The parallel-representation
reading is fair, but the stated cost does not hold on inspection. The claim
is that every new cause needs two edits kept in sync by diligence; it does
not, because the match is exhaustive, so a new variant is a COMPILE ERROR.
Forgetting the edit is already unwritable by construction. The real
residual is a duplicated or empty token, which exhaustiveness cannot catch
— closed here by witness_refusal_cause_tokens_are_pairwise_distinct.
Dissolving the map outright needs value-level variant-name reflection,
which this language does not have: the variant_name fields in std.induction
and std.node are Node-tree data, not a primitive applicable to a value. Per
§4b that makes this a class whose ceiling is set by a missing language
capability, and the honest move is to name the capability as its trigger
rather than hand-roll a second mechanism beside the first. No change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 49121 —
|
|
Autocommit captured review 49133 — two of the three were real, fixedBoth were rated non-blocking. I checked them and they turned out to be the silent-omission class this PR exists to close, so I fixed them instead of waving them through. 1.
Now folds over 2. The GitHub runs Used 3. Bare-string GH-expression concatenations — leaving as-is. Consistent with the surrounding emitter and inside the marked RED controlsThe pre-existing witness stays green under the exact defect it appears to cover — the fourth instance of that pattern in this PR, after the empty-observation narrow, the fused signer cause, and the locator erasure.
review 49137 —
|
…e authority
Main landed loyal-ram-550's fix for the same defect I fixed in this branch
an hour earlier, independently and better grounded — it MEASURED the
always-empty bundle (run 31031996072 emitted HealAuthorCommitRequired
naming both paths, and the next step reported "No files were found"),
priced it at six author re-runs across four PRs on 2026-08-05 alone, and
named the general class it does not close.
Keeping both would be the §3 fork this repo exists to prevent, so the
duplicate is dissolved rather than merged:
DELETED extdeps.github.effect path_is_hidden
DELETED gunbc.ci_workflow any_author_commit_path_is_hidden
KEPT gunbc.ci_spec path_has_hidden_segment
gunbc.ci_spec ci_heal_author_commit_artifact_includes_hidden_files
Main's is the incumbent on the shared branch and carries the measured
receipt plus a declared dissolve-on. Mine folded over absolute_path_segments
rather than scanning for "/.", which I still think is the better decomposition,
but that is a preference and re-homing it now would be churn against main's
declared plan to dissolve the fn into a per-upload-step lens.
Union taken where the two sides were genuinely complementary, not duplicative:
- if-no-files-found stays "error" (mine), not "ignore" (main's). Main's
upload step runs on always(), so it NEEDS ignore or every clean run reds.
This branch gates the step on heal_author_commit_required == '1', so it
only runs when drift exists — and an empty bundle then is exactly the
always-empty defect main just measured. Under this branch's gating that
defect would have been loud on its first run instead of silent for days.
- main's ci_heal_commit_push_note is restored, with one clause corrected:
it re-asserted "the GitHub App token lacks workflows:write", which is the
static premise this PR replaced. The credential that performs the heal
push is the Actions GITHUB_TOKEN, whose permissions vocabulary has no
workflows key at all — structurally unable, not missing a grant. The
hooks ruling (crisp-bat-830) in that note is preserved verbatim.
Witnesses deduplicated against main's three: my nested-components test is
dropped outright (main's hidden_segment_predicate_discriminates already
pins "a/.hidden/b"), and heal_author_commit_upload_includes_hidden_workflow_files
is narrowed to the one property main does not check — that the emitted flag
EQUALS the derivation, so a hardcoded `true` passes main's and reds mine.
heal_bundle_incompleteness_diagnostic_is_reachable is retained; main has no
equivalent.
…ropped The generated-artifact merge driver resolved .github/workflows/ci.yml to my side without conflicting, silently dropping main's new exclusion entry v1_tests_claim_v1_complexity_eviction_hazard_test.rs from AUTHORED_CONFLICTS. Regenerated via main_wet against a freshly rebuilt seed (main changed cli_run.rs and compiler_tests.rs, and a stale seed self-verifies at divergence zero, so the old binary's output would not have been evidence). Sole diff is that one restored entry. The heal upload block emits the intended union: if-no-files-found: error, include-hidden-files: true derived from the roster, gated on heal_author_commit_required == '1'.
Merged main — and main had independently fixed the same defectAn hour after I landed Keeping both would be the §3 fork this repo exists to prevent, so the duplicate is dissolved rather than merged: Main's is the incumbent on the shared branch and carries the measured receipt plus a declared dissolve-on. Mine folded over Union taken only where the sides were complementary, not duplicative
That is the static premise this PR replaces. The credential that performs the heal push is the Actions Witnesses deduplicated against main's three
VerificationRebuilt the seed first — main changed
Drift suite running. One process note: an earlier run of these reported — sent from bold-ant-81 |
Operator correction 2026-08-06. effective_actions_grant mapped every axis with no configurable workflow `permissions:` key to ActionsGrantScopeUnavailable, which declared a grant observed on the live token structurally impossible. The exact-head Actions job reports: Contents: read Metadata: read PullRequests: read Metadata has no configurable key yet is granted. Those are two different facts and now have two carriers: axis_actions_permissions_key answers DECLARABILITY, axis_intrinsic_token_grant answers what the token carries when nothing is declared. MetadataAxis + read -> ActionsGrantResolved(read) MetadataAxis + write -> CredentialScopeNotGranted (permission_access_at_least) WorkflowsAxis -> ActionsGrantScopeUnavailable RepositoryProjectsAxis-> ActionsGrantScopeUnavailable IntrinsicTokenGrant is Absent|Read with no write variant, so an intrinsic write is unrepresentable rather than validated: no answer had to be invented for the read-or-write states no axis occupies, and observing a write-carrying intrinsic axis later is a compile error at every consumer, not a silent widening. Renamed actions_token_can_carry -> actions_token_may_declare_permission. Behavior identical, but after this correction a predicate named "can carry" returning false for Metadata while effective_actions_grant resolves it to read would restate the same conflation one layer up. HEAL BEHAVIOR UNCHANGED, verified by execution: live_heal_push_refusal_cause_token still returns ActionsJobCredentialScopeUnavailable. Metadata is not in the heal's required set, so it cannot be affected. main_wet regen produces no drift in any emitted artifact — the committed fixed point holds. Controls: corrected the stale is_scope_unavailable(MetadataAxis) assertion, and added witness_undeclarable_axis_may_still_be_granted (both polarities, so re-fusing the two facts in either direction reds) plus witness_intrinsic_read_grant_refuses_a_write_request (the capability layer). actions_job_grant 20/20, github_app_registry 34/34. Also from review 49204, one control not a restructure: pinned that no refusal cause token collides with refusal_causes_token's empty-population marker, which that fold reuses as its own "nothing accumulated yet" state. Was safe by coincidence; now safe by execution.
review 49193 — one fixed, one already true, three answeredFinding 3 — Finding 1 — extends the hand-shell region. Premise is measurably false. The commit is −3/+2 on Finding 4 — URLs should route through a modeled URI carrier. Already do. Finding 2 — Finding 5 — predicate-dissolution family. Same answer as review 49137. These are Also in this push — operator correction
Heal behavior unchanged, by execution: review 49204 — two observationsThe sentinel fold is real and now pinned: The double — sent from bold-ant-81 |
…al_if to gunbc.ci_heal_credential #7766 moved ci_regen_heal_if and the heal job's credential facts out of gunbc.ci_workflow_expressions into gunbc.ci_heal_credential, and the author-commit path roster into gunbc.heal_push_plan. Resolved by taking main's structure verbatim in all three files and re-adding only this PR's own contribution: ci_floor_upstream_clean_if stays in gunbc.ci_workflow_expressions (it is a floor-gating expression, not a heal-credential fact) and is imported where the ci job and its witnesses need it. ci.yml is regenerated in a following commit, not resolved by hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…g lines Main advanced three times during this resolution (#7766 heal-credential recut, #7880, #7874, then 3c60c89). The first regeneration in this worktree ran on top of a stale ci.yml left from before the second merge and produced output that REVERTED #7766's heal improvements — the `heal_commit_push` step id, the guarded bundle copy, `if-no-files-found: error`, and the conditional upload. That artifact was discarded, not committed: ci.yml was reset to main's version and regenerated from the merged model, which is the only procedure that makes the generator a function of the model alone. Verified against a PINNED main SHA rather than the moving `origin/main` ref, because main moved mid-resolution and an unpinned comparison made main's newer content read as deletions this branch was making. Final state vs 3c60c89: model: 3 files (ci_workflow, ci_workflow_expressions, ci_heal_job_witness_test) ci.yml: needs: [build, regen, heal_generated_artifacts] if: "!failure() && !cancelled()" Witnesses: 27/27 PASS, including floor_job_waits_on_the_heal_preflight and floor_job_still_runs_when_heal_is_skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…7882) * Do not start the 65-minute floor on a branch already known to be drifted The ci job named only [build, regen] as prerequisites and was not gated on the generated-artifact heal result. A branch whose ci.yml or generated artifacts had already been refused could therefore spend a full floor budget on a runner before failing for a reason established minutes earlier. That happened repeatedly on 2026-08-05. ci now needs [build, regen, heal_generated_artifacts], with ci_floor_upstream_clean_if = "!failure() && !cancelled()". The expression is load-bearing, not decoration. A job whose needs include a skipped job is skipped by default, and heal is pull_request-only, so adding the dependency alone would silently stop the floor running on push-to-main, merge_group and workflow_dispatch -- deleting main's unconditional cold control while every PR stayed green. Four cases, all deliberate: pull_request, no drift heal exits 0, floor runs (heal is parallel with regen, so no wall time is added) pull_request, author-commit heal exits 1, floor skipped instead of required proving what the drift gate would refuse pull_request, heal repaired heal exits 1 after SupersededByHealedHead, so the floor no longer runs against a head that is already obsolete push / merge_group / dispatch heal skipped, no failure, floor runs as before Skipping the floor is not a fail-open. The ruleset requires the ci context and a skipped job does not satisfy a required check, so drift now blocks the merge rather than being a red the floor might have masked. What is removed is wasted computation, not a gate: the refusal that causes the skip is itself the typed, located diagnostic, emitted before the expensive stage. Witnesses, green by execution, and the pair is the point. Proven discriminating by temporarily setting the expression to success(): floor_job_still_runs_when_heal_is_skipped FAILS while floor_job_waits_on_the_heal_preflight still PASSES -- which is exactly the silent regression shape, dependency intact and condition wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Regenerate ci.yml against current main (author-committed workflow path) The merge brought main's floor-peak wet-witness receipt emitter; the generated-artifact merge driver keeps this side for generated paths, so ci.yml had to be re-projected rather than text-merged. Output now differs from origin/main by exactly the two gating lines this PR adds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Regenerate ci.yml against merged main: delta is exactly the two gating lines Main advanced three times during this resolution (#7766 heal-credential recut, #7880, #7874, then 3c60c89). The first regeneration in this worktree ran on top of a stale ci.yml left from before the second merge and produced output that REVERTED #7766's heal improvements — the `heal_commit_push` step id, the guarded bundle copy, `if-no-files-found: error`, and the conditional upload. That artifact was discarded, not committed: ci.yml was reset to main's version and regenerated from the merged model, which is the only procedure that makes the generator a function of the model alone. Verified against a PINNED main SHA rather than the moving `origin/main` ref, because main moved mid-resolution and an unpinned comparison made main's newer content read as deletions this branch was making. Final state vs 3c60c89: model: 3 files (ci_workflow, ci_workflow_expressions, ci_heal_job_witness_test) ci.yml: needs: [build, regen, heal_generated_artifacts] if: "!failure() && !cancelled()" Witnesses: 27/27 PASS, including floor_job_waits_on_the_heal_preflight and floor_job_still_runs_when_heal_is_skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix the fail-open: the floor RUNS and refuses fast, it does not skip review 49309 (cursor/composer-2.5) found that the first cut of this PR was a fail-open on the only required merge gate. Confirmed on three independent legs before changing anything: 1. GitHub's troubleshooting-required-status-checks page: a job skipped by a conditional REPORTS SUCCESS, and a job skipped because a needed job failed "may not block merging". 2. The live default-branch ruleset (id 16178731) names exactly one required context: `ci`. heal_generated_artifacts is not required. 3. Composing them: heal fails -> ci skipped -> ci satisfies its own required check by being absent -> GeneratedArtifactDriftGate never runs -> drift merges while only a non-required job is red. The change meant to remove wasted computation would have removed the wall. WHAT CHANGED. The job condition is now `!cancelled()`, so ci runs whenever the run is live -- including when an upstream job failed -- and the required context always produces a real verdict. The refusal moved into an executing first step that reads needs.<job>.result and exits 1 with a typed, located FloorUpstreamAlreadyRed diagnostic. The budget saving is preserved: a runner spin-up and a string compare, not a floor run. heal may be `skipped` (it is pull_request-only) and the floor proceeds, so main's cold control is intact; only `failure` refuses, covering both HealAuthorCommitRequired and the deliberate SupersededByHealedHead exit. MY NOTE WAS THE WORSE HALF AND IS RETRACTED IN PLACE. It asserted "a skipped job does not satisfy a required check". That was written from reasoning, not from a receipt -- the DESIGN section 5 trap of fluent output that was never run. The replacement states the verified mechanism and cites both receipts. A SECOND DEFECT, CAUGHT BY READING THE GENERATED ARTIFACT rather than the model: the refusal step was first added to ci_floor_job_prelude_steps(), which is shared, so it leaked into the regen job and into falsifier.yml. In the regen job (needs: [build]) the referenced results do not exist, so the guard would have compared "" != "success" and refused on EVERY run. It is now composed into ci_job()'s own step list, and floor_refusal_reaches_only_the_job_whose_needs_it_names is the control that reds if it ever leaks again. Witnesses: 30/30 PASS. floor_job_runs_even_when_an_upstream_job_failed refuses both failure() and success() by name, and was proven discriminating by reintroducing `!failure() && !cancelled()` and observing exit 1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Pin the refusal as exactly the first step (operator review) The prior witness folded over ci_job().steps and asserted the refusal existed SOMEWHERE. That does not pin the invariant the step exists for: refusing BEFORE the expensive work. A future edit could move it after checkout, toolchain setup or artifact download and the assertion would stay green. floor_refusal_is_exactly_the_first_step now matches list_head(ci_job().steps) and requires it to BE the refusal step. Proven discriminating by moving the step behind the prelude: the new witness FAILS while the old fold witness still PASSES on the same tree, which is the operator's point demonstrated rather than argued. The fold assertion is retained beside it because it carries the script-content claims; the positional one carries the ordering. The generated ci.yml is not separately asserted because the drift gate already pins ci.yml == the model's projection, so model-first-step plus drift-clean composes to workflow-first-step without a second reader. Witnesses: 31/31 PASS. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Subject
a8154105c19bedad66f9e2cce56c1098f8e16ce6(main, merged into this branch)e6e5d5499b377d03f7f908d2dab35efdc207686dScope — changed authorities
Provider vocabulary (
dag/extdeps/github/)app.dag—GitHubPermissionAxis, a closed 22-variant permission vocabulary with a wire-name bijection, replacing the open{wire_name: String}carrier. Separate open carriersObservedGitHubPermissionName/ObservedGitHubPermissionretain permission names this repository does not model, verbatim, on both readback paths.AppAccessAdminandGitHubInstallationTokenResponsedeleted (uncited / zero consumers).actions_token.dag—EffectiveActionsJobGrant(workflow × job × event × origin × repository policy × declared permissions);ActionsAccessSetstates which accesses an axis admits;ActionsGrantResolutiondistinguishes none, scope-unavailable, axis-not-modeled, declared-access-invalid, and fork-policy-unobserved.IntrinsicTokenGrantseparates DECLARABILITY (is there a configurablepermissions:key) from POSSESSION (what the token carries undeclared) — Metadata has no key yet the live job reportsMetadata: read, so fusing the two called an observed grant impossible. Fork write-token reachability is consulted only for a declared write, and is unreachable on a public repository.effect.dag— typed effects to required permissions.languages/yaml/types.dag—YamlValueKind+yaml_value_kind.Credential and control plane (
dag/gunbc/auth/)github_credential.dag—PushBindingResolutionprojects the push credential from the actual checkout step.CredentialCapabilitycarries per-axis refusal reasons.github_apps.dag—SignerCustody,GitHubAppControlPlaneState, and a next-action ladder whose operator packets are gated on a bound observation producer.Heal lane (
dag/gunbc/)ci_heal_credential.dag— the heal job's grant, origin binding, checkout step, andresolve_push_binding.heal_push_plan.dag— the refusal population and the remediation derived from it.runner_host_deploy.dag— re-grounds a hardcoded app id, installation id, and PEM secret onto the singlegunbc.auth.github_appsauthority.generated_artifact.dag— theHealPolicyfork removed; heal classification now lives inheal_push_plan.Production behavior at this head
Unchanged in outcome, now derived rather than asserted.
The heal job runs under the Actions job credential (
GITHUB_TOKEN) persisted by its ownactions/checkoutstep. The Actionspermissions:vocabulary has noworkflowskey, soWorkflowsAxisresolves toActionsGrantScopeUnavailable— a structural property of the credential kind, not a missing grant..github/workflows/**drift is therefore refused, packaged as a repair bundle, and reported asHealAuthorCommitRequired; non-workflow artifacts still auto-push. No push is attempted that the credential cannot perform.No permission was granted, and
workflows: writewas not added to any declared grant.Evidence
Executed witness population at this head — see the run posted below this body.
New discriminating controls landed with this recut:
contents: none+ workflows unavailable resolves toMachineRepairRequired, notAuthorCommitRequired)HealPlanInconsistent)Boundary — not claimed at this head
profileis not consulted by authorization, and the installation records onlyall | selectedwithout the repository population, so a token's repository scope is not proven here.SignerCustodystarts atSignerMaterialAvailabilityUnobserved.These are the next vertical slice, not gaps this PR silently leaves open.