Skip to content

Bind the two import-less plan sketches from their definers, not from pool coincidence - #7872

Merged
briansrls merged 1 commit into
mainfrom
session/keen-ibex-349
Aug 5, 2026
Merged

briansrls merged 1 commit into
mainfrom
session/keen-ibex-349

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

dag/gunbc/plans/branch_merge_admission_model.dag and merge_admission_gate_shape_proposal.dag are the only two of ~46 table-using modules under dag/gunbc/plans/ that carry no imports at all. They have had none since #6927/#7254, so every cross-module name in them binds by pool-membership coincidence: resolve_in finds the definer exactly when something unrelated in the currently-assembled closure has already dragged it in. That is the Class B pathology DESIGN's #6985 thread names, and its stated remedy is import-from-definer.

How it surfaced

It stopped being latent on #7862, whose compile-clean refused 127 names in exactly these two files — 96 cell and 31 row, an exact match to their 96 and 31 usages, so every occurrence failed while h2/h3/p/li/ul/quote from the same module resolved fine. Those two are md_helpers' only helpers whose signatures mention std.markdown's table types.

Why this lands separately from the PR that surfaced it

#7862 touches claim_executor.rs, and a non-selectable .rs path forces compile-clean to the whole-tree baseline:

compile-clean scope: touched path outside the selectable universe
  — compiler/infra change, whole-tree baseline

Main pushes get the scoped path instead — main@4f9112965 ran 396 affected entries (floor fast path) and passed without compiling these modules at all. Main's last whole-tree compile was 1d17ae068, which predates #7824.

So a whole-tree-only regression can merge to main and stay invisible until an unrelated PR happens to touch a .rs file. That exposure belongs to the gate, not to the PR that trips it, and this repair unblocks every .rs-touching PR rather than one. The scoped/whole-tree asymmetry is worth a separate look; it is the coverage-by-illusion tier DESIGN §6 describes.

What is proven, and what is not

Proven by execution — both modules compile with 0 blocking error(s) after this change, and the import lists are exactly complete. Adding md_helpers alone flips each module from ambient bare resolution to import-scoped visibility, which then refuses Plan and HasTrigger; gunbc.plan is therefore imported too, after which nothing is left unresolved. That mode flip is why the fix is two imports and not one.

Not proven — I could not reproduce the original refusal locally. Under an --entry closure both modules compile clean on main and on #7862's tree, so the refusal is closure-dependent and needs CI's whole-tree witness_layer_roots closure to appear. This change is therefore justified by mechanism, not by a local red-to-green: explicit imports make binding independent of pool composition, which is precisely the property whose absence produced the refusal.

Attribution between #7824 and #7862 is not established here, and no claim about it is made. Three hypotheses were formed and refuted by execution rather than acted on: a colliding row binder from #7862 (refuted — 158 files on main already use (row:, 44 use row =>); md_helpers absent from the pool (refuted — its MarkdownBlock-returning siblings resolve in the same files); and pre-existing breakage on main (refuted, then re-opened once main's clean whole-tree run turned out to predate #7824).

Import sets match the sibling convention used verbatim by ci_merge_freshness, ci_oom_reclassification and emission_ingestion_inverse.

@gunbai-bot gunbai-bot Bot changed the title shell -> dag (and TS -> dag) Bind the two import-less plan sketches from their definers, not from pool coincidence Aug 5, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 5, 2026 20:02
gunbai-bot Bot pushed a commit that referenced this pull request Aug 5, 2026
…this PR's compile-clean)

Not this PR's defect. dag/gunbc/plans/branch_merge_admission_model.dag and
merge_admission_gate_shape_proposal.dag are the only two of ~46 table-using
modules under dag/gunbc/plans/ carrying no imports at all, so their cross-module
names bind by pool coincidence (DESIGN #6985 Class B). This PR touches
claim_executor.rs, and a non-selectable .rs path forces compile-clean to the
whole-tree baseline, which compiles them; main pushes take the scoped path and
never do.

Same change as #7872 (authored off main). Carried here so this PR can go green
without waiting for that to merge; when it does, the identical lines merge clean.

Verified by execution: both modules compile with 0 blocking errors. Two imports
are required, not one — adding md_helpers alone flips each module from ambient
bare resolution to import-scoped visibility, which then refuses Plan/HasTrigger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@briansrls
briansrls merged commit 5f44610 into main Aug 5, 2026
3 of 8 checks passed
@briansrls
briansrls deleted the session/keen-ibex-349 branch August 5, 2026 20:16
gunbai-bot Bot pushed a commit that referenced this pull request Aug 5, 2026
Resolve branch_merge_admission_model import conflict: keep both
std.markdown (TableBlock, AlignNone) and md_helpers from our CI fix
alongside main's md_helpers addition from #7872.

Co-authored-by: Cursor <cursoragent@cursor.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 5, 2026
Main already restored md_helpers imports in #7872; keep file byte-identical
to main so this PR carries only the wet-batch observation work.

Co-authored-by: Cursor <cursoragent@cursor.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 5, 2026
…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>
briansrls added a commit that referenced this pull request Aug 5, 2026
…ived from the existing witness row-cost receipt, with promotion-to-refusal as its declared trigger (#7862)

* WIP: Make the 500ms mandatory-migration threshold a counted disclosure derive

* WIP: Make the 500ms mandatory-migration threshold a counted disclosure derive

* Bind the two import-less plan sketches from their definers (unblocks this PR's compile-clean)

Not this PR's defect. dag/gunbc/plans/branch_merge_admission_model.dag and
merge_admission_gate_shape_proposal.dag are the only two of ~46 table-using
modules under dag/gunbc/plans/ carrying no imports at all, so their cross-module
names bind by pool coincidence (DESIGN #6985 Class B). This PR touches
claim_executor.rs, and a non-selectable .rs path forces compile-clean to the
whole-tree baseline, which compiles them; main pushes take the scoped path and
never do.

Same change as #7872 (authored off main). Carried here so this PR can go green
without waiting for that to merge; when it does, the identical lines merge clean.

Verified by execution: both modules compile with 0 blocking errors. Two imports
are required, not one — adding md_helpers alone flips each module from ambient
bare resolution to import-scoped visibility, which then refuses Plan/HasTrigger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Migrate the migration-disclosure receipt to WitnessRowCost's named fields

#7845 replaced WitnessRowCost's tuple shape with named fields and inserted
eval_cpu_nanos in the middle, so every positional index in this branch's new code
shifted: .0/.1/.2/.5 are entry/function/eval_wall_nanos/outcome. Merging main
migrated the pre-existing call sites; these six were the residue this branch adds.

The skip test now calls row_measurement_is_absent(&row.outcome) rather than
comparing the outcome string inline. Same behavior — that helper tests exactly
selection-skipped — but it is the existing authority for the question, and the
inline compare was a second copy of it (DESIGN section 3). TimedOut rows still
fall through to be measured, which is deliberate: they are the population this
disclosure exists to surface.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.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>
briansrls pushed a commit that referenced this pull request Aug 6, 2026
…-PR — it is absent from three job logs and its observability receipt emits zero rows (#7857)

* WIP: CI floor: prove whether the 64-row bin-witness wet batch executes per-PR

* WIP: CI floor: prove whether the 64-row bin-witness wet batch executes per-PR

* Fix cargo fmt in claim_executor wet receipt trace.

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: CI floor: prove whether the 64-row bin-witness wet batch executes per-PR

* Regenerate ci.yml and falsifier.yml after rebase onto main.

Rebase merged #7856 path_intent cgroup ascent with wet receipt post-step;
main_wet restores committed workflow bytes for heal author-commit gate.

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: CI floor: prove whether the 64-row bin-witness wet batch executes per-PR

* Restore md_helpers import on branch_merge_admission_model plan.

Class B pool-coincidence: stripped file resolved only when an unrelated
closure dragged md_helpers into the pool. Whole-tree compile-clean after
workflow yml commit exposed the latent main defect; restore imports per
sibling plan modules until closure-independent binding lands.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Drop noop branch_merge_admission_model diff after rebase.

Main already restored md_helpers imports in #7872; keep file byte-identical
to main so this PR carries only the wet-batch observation work.

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: CI floor: prove whether the 64-row bin-witness wet batch executes per-PR

* Derive discovery corpus log labels from schedule profile, not roster size.

Thread spawns_host_compiler from RunnableDiscoveryBatch through BatchUnit
into run_discovery_batch_node so bin-witness vs execution lanes stay
correctly labeled even if the execution roster grows past the old count
heuristic threshold.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls pushed a commit that referenced this pull request Aug 6, 2026
…pt-index long-lane row (#7880)

* WIP: Make the 500ms mandatory-migration threshold a counted disclosure derive

* WIP: Make the 500ms mandatory-migration threshold a counted disclosure derive

* Bind the two import-less plan sketches from their definers (unblocks this PR's compile-clean)

Not this PR's defect. dag/gunbc/plans/branch_merge_admission_model.dag and
merge_admission_gate_shape_proposal.dag are the only two of ~46 table-using
modules under dag/gunbc/plans/ carrying no imports at all, so their cross-module
names bind by pool coincidence (DESIGN #6985 Class B). This PR touches
claim_executor.rs, and a non-selectable .rs path forces compile-clean to the
whole-tree baseline, which compiles them; main pushes take the scoped path and
never do.

Same change as #7872 (authored off main). Carried here so this PR can go green
without waiting for that to merge; when it does, the identical lines merge clean.

Verified by execution: both modules compile with 0 blocking errors. Two imports
are required, not one — adding md_helpers alone flips each module from ambient
bare resolution to import-scoped visibility, which then refuses Plan/HasTrigger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Follow-up to #7872: the std.markdown symbols were still binding by coincidence

review 48984, on the merged #7872, is correct and my completeness claim there was
not. Both plan sketches still referenced MarkdownBlock, TableBlock and AlignNone
with no std.markdown import, so those three kept binding by pool coincidence —
the same #6985 Class B pathology #7872 exists to close, left in place by the
change closing it.

THE BAD INFERENCE, named because it is the trap and not a slip: I read
"0 blocking error(s)" as "every name binds from a declared import". It does not.
Bare resolution still answers for whatever the closure happens to contain, so a
clean compile is precisely the evidence that CANNOT separate a declared binding
from a coincidental one — the compiler is green either way. Only reading the file
against its own import list decides it, which is what the reviewer did and I had
not. Verifying the fix by the same instrument that was blind to the defect is a
DESIGN §5 specification-without-execution failure wearing an execution costume.

Symbol set verified against std.markdown's 34 exports this time rather than
inferred from a clean compile: these two modules use MarkdownBlock, TableBlock and
AlignNone and nothing else from it. Byte-identical to the most common sibling
form, carried by 18 plan modules including the three #7872 cited as precedent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Migrate the migration-disclosure receipt to WitnessRowCost's named fields

#7845 replaced WitnessRowCost's tuple shape with named fields and inserted
eval_cpu_nanos in the middle, so every positional index in this branch's new code
shifted: .0/.1/.2/.5 are entry/function/eval_wall_nanos/outcome. Merging main
migrated the pre-existing call sites; these six were the residue this branch adds.

The skip test now calls row_measurement_is_absent(&row.outcome) rather than
comparing the outcome string inline. Same behavior — that helper tests exactly
selection-skipped — but it is the existing authority for the question, and the
inline compare was a second copy of it (DESIGN section 3). TimedOut rows still
fall through to be measured, which is deliberate: they are the population this
disclosure exists to surface.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Dissolve the concept-index long-lane row by fixing the witness's cost

The witness's cost is its POOL, and the pool was 638 files.

concept_index_enumeration_pool_roots was "src/v2/test/claim", and
concept_decl_facts parses every .dag under the root on every call, so each
parse-only witness parsed 638 files / ~3.0 MB and grew with every unrelated
module added anywhere beneath it. That is precisely what the SubstrateLongLaneRow
recorded ("corpus-breadth cost grows with unrelated modules in the pool") and what
pushed witness_coproduct_arms_parse_only to 6065ms thread-CPU against the 5000ms
fast-lane budget, rehoming it to test/claim/long/ under #7756.

Breadth was never the subject. Every assertion names ONE concept by qualified
name and checks its extracted fields, so the requirement is a pool CONTAINING
those concepts, and the smallest pool that crosses the boundary is a directory of
its own (operator ruling 2026-08-04: a witness's size is DERIVED from what it
proves, never chosen for verisimilitude). src/v2/test/fixture/concept_index_pool
is that pool.

MEASURED, same host, same binary, toggling only the .dag source:

  witness_coproduct_arms_parse_only   3952ms -> 2ms
  whole 11-witness family, slowest      51ms  (was ~4000ms for parse-only rows)

All 11 pass, including the returned witness. The pool is proven non-vacuous
rather than merely quiet: witness_parse_only_extract_nonempty_in_scoped_pool
asserts the pool is non-empty AND contains the target, and
witness_parse_only_extract_reflects_perturbed_field_type reads RecordPerturb's
beta: Int out of the fixture — so the stale-field RED control cannot be passing
because the pool is empty.

WHY THE TYPES DID NOT ALL MOVE. The two witness families consume different
inputs: concept_decl_facts_live reads the loaded module REGISTRY, concept_decl_facts
reads FILES under a root. The live-registry witnesses keep local declarations
(a locally-declared record IS their subject); the parse-only witnesses read the
fixture pool. That is two inputs to two mechanisms, not one concept forked.

A CHANGE THAT DID NOT EARN ITS PLACE ON THIS WITNESS, kept and labeled rather
than quietly credited: enumerate_concepts_from_decls folded with list_snoc_item,
which is list_append(xs, [item]) and therefore O(n^2) over the decl count. It is
replaced with list_map (fold_list_right + Cons, O(n), order-preserving, identical
output). Measured effect on this witness: 3952ms -> 3795ms, ~4%, inside noise —
because the dominant cost was the host-side pool parse, not the fold. It stays
because it is a proven cost-shape defect on a function enumerate_concepts() also
calls corpus-wide (DESIGN section 6: fixed regardless of the realized n), NOT
because it fixed anything here. My first hypothesis was that this fold WAS the
cost; measurement refuted it, and the pool root was the answer.

Dissolves: the SubstrateLongLaneRow, the long/ file, and
coproduct_arms_parse_only_long_lane_note, all deleted together per the row's own
trigger ("eval lands under gunbc_ci_fast_lane_witness_eval_budget on a measured
cold run and re-enrolls per-PR discovery").

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* WIP: shell -> dag (and TS -> dag)

* Drop the stale concept-index parse-only basis row invalidated by this branch

witness_coproduct_arms_parse_only carried a dated basis of 3850ms from run
30550328673, measured when concept_decl_facts parsed all 638 files under
src/v2/test/claim. Narrowing the pool to the witness family's own directory
takes the same witness to ~1.5ms, so that basis is a measurement of a
different implementation and no longer a reference for this one.

Leaving it is not neutral: the drift comparator is a 2x ratio, so a 3850ms
basis pins this row WithinBasis below 7700ms forever and the drift gate goes
vacuous for it exactly where a future regression would show up. That is the
failure committed_witness_row_cost_basis_loads_and_excludes_the_censored_row
already guards against for the 900794ms censored row, in the same file.

Dropped rather than re-signed: witness_row_cost_basis_host_class_note requires
basis rows to be signed from a cited arm64 srv-fleet run, never a local
measurement, and this container is x86. BasisAbsent is the honest third state
(typed and counted, per witness_row_cost_spine_note) until the next fleet
cadence re-signs it. Drift is falsifier-cadence counted and not merge-refusing,
so this changes no gate verdict. 1190 rows remain, over the >=1000 floor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.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>
briansrls pushed a commit that referenced this pull request Aug 6, 2026
#7766)

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* Enroll dag/extdeps/github/app.dag in scope_carrier_paths

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>

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* Ground installation_id on the typed GitHubAppInstallationId carrier

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>

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* Recut: authorize the heal push against the credential that performs it

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>

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* Repoint the last heal-policy consumer; witness the dispatch/push asymmetry

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>

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* Remediation ownership, open permission observation, producer-gated operator packets, fail-closed repair bundle

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* Fix closure-dependent resolution in two plan sketches (unblocks compile-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>

* Split SignerReadbackFailed's fused prose detail into typed causes

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>

* Regenerate ci.yml after the main merge; import ci_heal_author_commit_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>

* Refuse unmodeled installation permissions instead of narrowing past them

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>

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* Give the heal control plane its real required-permission roster

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>

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* Retain the located payload on binding refusals; pin token distinctness

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>

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* Regenerate ci.yml after the merge: restore the entry the text-merge dropped

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'.

* WIP: gunbc-ci-auto-heal needs to be able to run self hosted workflows on gith

* Separate configurable permission keys from intrinsic job-token grants

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.

---------

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>
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