Skip to content

CI witness enrollment opt-in inversion (2026-07-04) - #6232

Merged
briansrls merged 16 commits into
mainfrom
claude/ci-witness-opt-in-10h2he
Jul 4, 2026
Merged

briansrls merged 16 commits into
mainfrom
claude/ci-witness-opt-in-10h2he

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Summary

Inverts CI witness enrollment from always-on (tree-wide discovery) to opt-in (explicit roster). Witnesses now run in CI only when declared in commit_gate_roster as CommitWitnessClaim rows on the GithubActionsCiJob surface. This eliminates quadratic roster growth that caused floor timeouts while preserving local tree-wide discovery for development.

Key Changes

Floor plan architecture:

  • Replaced tree-wide discovery (scan_dirs, discovery_scope_dirs) with explicit witness_entries roster in CiSpec
  • Removed always-on floor_grounding_whole_tree_node; grounding witness must be re-enrolled as a CommitWitnessClaim row
  • corpus_plan_nodes() now conditionally includes corpus nodes only when roster is non-empty (fail-closed: empty roster → no corpus node)
  • Parameterized floor schedule generation: floor_schedule_for(spec, serialize_heavy) replaces hardcoded gunbc_ci_spec references

Witness entry routing:

  • Added corpus_witness_entries() and execution_witness_entries() filters to partition roster by scope prefix (src/v2/test/claim/execution/)
  • corpus_runnable_for() and execution_corpus_runnable_for() now accept CiSpec and use explicit entries instead of discovery
  • Removed witness_discovery_scan_dirs import; discovery is now local-only (claim_batch --roster-from-discovery)

Enrollment authority:

  • gunbc.commit_workflow projects CommitWitnessClaim rows into ScheduleWitnessEntry list via project_ci_floor_witness_entries()
  • CiSpec.witness_entries is the single source of truth for CI enrollment
  • Added ci_spec_with_witness_entries() helper to construct specs with custom rosters (for testing)

Test coverage:

  • Split witness tests: ci_floor_plan_witnesses() covers live plan behavior; new ci_floor_optin_roster_witnesses() validates opt-in mechanics
  • New witnesses: witness_live_floor_discovery_tracks_optin_roster(), witness_optin_roster_drives_corpus_presence(), witness_optin_plan_gates_corpus_after_compile(), etc.
  • Added discovery_carries_exactly() to verify roster rows are carried as explicit entries, not discovered
  • Test specs: optin_spec (full roster), corpus_only_spec, zero_roster_spec (empty)

Fail-closed safety:

  • claim_executor runs naming hygiene walk (check_floor_filename_hygiene, discover_floor_corpus_rows) when plan has no DiscoveryBatch, ensuring witnesses remain nameable even when unenrolled
  • Empty roster produces no witness-corpus node; executor's empty-corpus diagnostic stays meaningful

Rust formatting:

  • Reformatted long function signatures and chained calls for readability (no logic changes)

Implementation Details

  • ScheduleWitnessEntry (from std.realization_schedule) carries {entry: String, function: String} pairs
  • Resource exclusion edges (floor_corpus_exclusion_resource_edges) now fold over actual corpus nodes in plan, not hardcoded list
  • Heavy-resolve chain (floor_heavy_resolve_chain_resource_edges) no longer includes grounding node
  • Data dependencies initialized by folding corpus_plan_nodes() instead of hardcoded three-node list
  • Parameterization enables testing with alternate specs without modifying gunbc_ci_spec (single authority preserved)

https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf

claude added 16 commits July 4, 2026 01:23
… declared roster row

Operator decision 2026-07-04: the tree-wide glob roster (543 files / ~1,551
test fns, growing quadratically - a witness is definable between any two
pipeline segments) pushed both CI jobs to the 90-min timeout: maximum cost,
zero delivered signal, every merge effectively fail-open. Existence no longer
enrolls a witness in CI.

- CiSpec.witness_entries: the opt-in roster, projected from CommitWitnessClaim
  rows on the GithubActionsCiJob surface of commit_gate_roster (the existing
  enrollment authority - no new mechanism). Empty today.
- ci_floor_plan: an empty roster puts NO witness-corpus node in the plan
  (structural omission, not an empty glob - the executor's empty-corpus
  fail-closed diagnostic stays meaningful); a non-empty roster becomes
  explicit-entries discovery batches (scan_dirs/discovery_scope_dirs empty, so
  the glob path is unreachable from CI), partitioned corpus vs execution by
  path prefix, still compile-gated and heavy-serialized. The dedicated
  grounding whole-tree node dissolved into the roster.
- claim_executor: when a plan carries no DiscoveryBatch, run the naming-
  hygiene tree walk (test fn outside *_test.dag, __ basenames) once,
  fail-closed - a witness must stay nameable even when not enrolled.
- Witness tests inverted: live plan asserts discovery tracks the roster
  (zero today); the enrollment machinery is proven on synthetic rosters
  (presence, partition, gating after compile, heavy serialization, exclusion
  edges) with red discrimination.
- Rust lane, same inversion: the two whole-tree tests (func_env whole-tree
  ptr-count, whole-corpus semantic oracle) are now #[ignore] with written
  reasons per the #5427 discipline; run via -- --ignored.
- Found and fixed two files the v1 interpreter grammar could not parse
  (top-level // comments): affected_set_floor_runner.dag - which made
  resolve_floor_runner_context fail and silently disabled the affected-set
  skip, falling back to full corpus on every floor run - and
  phase_profile_proof_plan.dag. Comments converted to data-string markers.
- Local tree-wide discovery unchanged (claim_batch --roster-from-discovery).
- Dissolve-on: affected-set selection + floor memoization make per-PR
  selection-by-affectedness affordable; enrollment returns to discovery
  shrunk by the affected set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
…le pure-fn canary)

Per operator intent: exactly one witness in CI for now, to observe the floor's
behavior around a known-tiny payload. The canary is
dag/test/claim/variant_owner_expected_type_test.dag ::
expected_type_picks_variant_owner_not_alpha_order - a single test fn over
self-contained pure functions (no host intrinsics, one std.logic import;
measured locally: 3ms resolve / 2-module closure, ~0ms eval), so whatever time
the CI witness lane now costs is the floor's fixed overhead, not the witness.

Also: run the witness naming-hygiene walk unconditionally in claim_executor's
plan path, BEFORE plan evaluation - explicit-entries discovery batches skip
the glob scan that used to carry the placement checks, and pre-plan ordering
makes a naming violation the cheapest possible failure. Verified by execution:
red probe (planted test fn in a non-_test.dag file) fails closed with the
placement diagnostic in seconds; green probe on the live tree passes the walk
and proceeds to plan eval; the enrolled canary runs green via claim_batch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
…et (pre-existing on main; surfaces on any .rs-touching PR)

The ratchet constant deliberately sits at its minimum (0); scoped allow with
justification keeps raising the ceiling a one-constant edit instead of
hardcoding == 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
…y fails immediately

Operator decision: 'fail closed' here means DON'T PROCEED, never 'silently run
more'. skip_unaffected_node_frontier: true is a declared capability, so every
input it needs is required - a failure is a loud typed error:

- git diff observation failed -> error (was: run full corpus). The
  FloorGitDiffOutcome degradation enum is deleted; an absent diff is no longer
  a representable input state to selection. To run without selection, declare
  skip_unaffected_node_frontier: false - an explicit bit, not a silent state.
- diff->declaration attribution failed -> error (was: run full corpus)
- affected-set runner unresolvable -> error naming the declared entry
  (was: run full corpus - the arm that hid the broken runner for weeks)
- same per shard -> error through the shard join (was: run all rows in shard)
- precompute_would_skip / per-witness would_skip / frontier-touch fns
  erroring -> error naming the witness (was: run anyway)
- roster row file unreadable -> error (was: silently reclassify as
  host-scaffold)

Rationale: the 'safe' superset fallback conflated broken artifacts with
missing observations, hid the defect permanently (no consumer of the eprintln),
and at corpus scale 'run more' inverted into fail-open-by-timeout. The runner
provisioning, fetch step, and observers are all declared by this repo's own
spec, so there is no 'environment' excuse category: a missing input is a bug
in a declared step and stops the floor.

cargo build green, clippy -D warnings green, fmt clean; floor-scoped lib unit
tests and a live red/green probe through a discovery batch are running and any
regression they surface lands as a follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
…ll-clock marks

The 'gunbc ci' step went silent for 35+ minutes after the release build on the
self-hosted runner - all of it pre-witness prelude: naming walk, output-policy
install, plan-entry resolve, interpreted scheduler eval (gunbc_ci_floor_batches),
then a SECOND resolve of the same plan entry for spawn-width, then another
interpreted eval. Nothing printed until all of it finished, so a 30-minute
prelude was indistinguishable from a hang.

- resolve_entry_graph(plan_entry) now runs once; the hermetic plan eval and the
  wet spawn-width eval share the resolved graph (the double-paid-compute trap
  from the floor memoization thread, paid at minutes per resolve).
- claim_executor stamps [t+Xs] phase marks: hygiene walk, policy install, plan
  resolved, plan evaluated, width evaluated / walk starting. The floor log now
  itemizes its own prelude.

eval_plan stays as a resolve+eval wrapper for the --perturb-check path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
…atchet)

The prior ratchet went 30 -> 50 -> 90, each raise buying headroom for the
unfixed resolve cost; at 90 the queue backed up and runs still died at the
cap having delivered zero signal. The budget is now a forcing function: the
floor must fit it, and the [t+Xs] phase marks itemize exactly which phase
does not. Jobs stay red-by-timeout until resolve memoization lands - an
honest red, unlike a 90-minute lane that never completed.

ci.yml hand-synced with the ci_workflow.dag authority (3 job sites); the
generated-artifact drift gate byte-verifies the pairing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
…rator session 2026-07-04)

Names the target architecture for the resolve problem measured during the CI
dig-out: request-major private universes -> cheap edge graph + reverse-reachable
minimal set + forward once-per-node evaluation (construction, not cache lookup),
windowed-frontier memory (interface vs body artifacts), isolation-as-purity, and
the full v1/v2 change surface. Staged S1 (union resolve, in-process, ships in
the current PR line) / S2a (module nodes + Merkle keys) / S2b (persistence,
gated on determinism #5941) / S3 (shared store) so it is not a 10-PR arc.
Cross-linked from the M1/M2 memoization doc it supersedes (that doc's own M1
dissolution trigger anticipated exactly this design).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
…llet, ci-selection-vs-scheduling section 7)

The interpreted main_wet regeneration was killed twice by container restarts
(~2 CPU-hours each attempt, itself a receipt for the resolver-graph-major
design). The generated outputs are hand-applied to byte-match what the
emitters produce from the already-committed authorities
(dag/gunbc/design_document.dag, dag/gunbc/plans/ci_selection_vs_scheduling.dag);
the GeneratedArtifactDriftGate adjudicates exactness once the floor fits its
10-minute budget.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
…xecutor)

Without it, claim_batch diagnostics cannot attribute time to
resolve/typecheck/eval phases - a 20-minute silent resolve is
uninterpretable, and the resolver investigation needs per-phase receipts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
…o-special-cases section; per-module typecheck attribution

Instrumented, isolated measurement: the 11-module closure finishes
parse+resolve+normalize in ~1.1s then sits in typecheck 13+ minutes -
inference is ~99.9% of cold resolve cost. Reprioritizes the plan: pathology
lane first (per-module [typecheck-attribution] lines now name any module
over 2s), unification (module obligations under the existing executor)
second, union-of-global-passes demoted to a ~1s-class cut.

Adds the operator's formulation as design section 1b: resolution is the
system's one special case - the Bazel-shaped executor schedules eleven
coarse gates while the dominant work hides in one opaque recursive host
call; module resolution IS dependency execution over the same substrate.
Maps the external witness-frontier-cursor vocabulary onto existing
authorities (Runnable/RunnableCompile, Realization content-hash key +
EffectShape, executor batches, receipts) per the section-3 anti-nickname
discipline, and adopts the four-terminal-state invariant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
…e per process

First implementation increment of the resolver graph-major design (and of
the stratification rule: resolution is declared shared work, not ambient
per-consumer control flow). A thread-local store keyed by (source_roots,
entry) now serves the floor runner context, the diff observer, the output
policy, the group syntax, the wet-hermetic roster prefix, and the
executor's plan entry - each was previously a private resolve, re-paid per
consumer and per discovery batch within one process. Failure semantics
unchanged: a miss resolves exactly as before, including the typed error
path; the store never converts an error into a fallback.

Receipt: process_resolve_store_dedupes_repeat_resolve - Rc identity on the
second resolve proves zero recompute (0.00s, fixture tree under target/).
Thread-local by design: resolved graphs are Rc-based (not Send); shard
threads keep their own store rather than smuggling Rc across threads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
…gical module

Verdict from the instrumented run: all std modules in the closure -
including std.algebra and std.realization_schedule themselves - typecheck
in under 2s each; the call that runs 17+ minutes is typecheck_module on
the 80-line CONSUMER module (test.claim.realization_schedule_witness).
The inference pathology is at generic use sites, not definition sites.
Next lane: the existing type-env lookup profiler (text_lookup_work_counter
feature, reset_type_env_lookup_profile) pointed at that one module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
Heartbeats now carry env_flatten/env_builds/env_merges/env_rewires from the
existing v1_compiler_infer_env atomics. First live capture on the
pathological entry: env_flatten grows ~80k/sec unbounded (9.7M by t=120s)
while builds=11, merges=37, rewires=0 stay flat.

Root site: record_lit_variant_fields_from_visible_env
(v1_compiler_infer.rs, generated from v1.compiler.infer) - every VARIANT
LITERAL flattens the entire visible environment (recursive parent fold; the
counter) and then scans every visible binding with
expand_type_for_field_access per candidate to find the variant's owner.
Cost = variant literals x |visible env| x expansion - definitions cheap,
consumers explode. 04_env.dag:35 carries an invariant expecting
flatten_visible_parent_recurses==0 on import chains; live count says 9.7M
in 2 minutes. Fix shape (next block, dag source + regen): a variant-name ->
owner index built once per env, and expected-type short-circuit before any
global scan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
… resolution follows the binding edge

The ambiguity support (bare constructor names legal across coproducts,
expected-type tie-break, whole-env owner scan as fallback) is itself the
bug - accidental semantics, never intended. The DAG already provides the
namespace: imports bind arm names to arm nodes; the parent edge names the
owner; resolution is following the edge already held. Rules: bound-name
resolution via parent edge; unbound constructor literal = typed error
(today a silent undeclared-dependency fail-open via the scan); double
binding = collision error at env build; patterns resolve via scrutinee
type; expected-type owner-picking deleted. The pathological scan is
removed, not memoized.

Census: 5,526 arm names, 221 global collisions (uniqueness must be scoped
to co-visibility); <=4,572 unbound arm-name uses upper bound, dominated by
legal pattern positions. Control run killed at 60+ min still inside one
typecheck_module call on the 80-line module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
… with explicit contract; flat name visibility refinement

Union resolve leads the resolve lane with a recorded contract: minimum
upper bound (resolve cost <= 1x union closure, receipt-enforced), successor
must be maximally parallel (topo-antichain module typecheck, budget-tree
width) and frontier-window efficient; it dissolves into S2a and must not
grow features that delay that. Namespacing ruling refined per operator:
visibility is FLAT - locals + direct imports only, never transitive -
making collision detection per-file with zero graph traversal; types
propagate by graph identity underneath.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
Conflicts: src/v1/stage0/src/cli_run.rs import_closure_equivalence_tests -
both hunks resolved to the rewritten main's side (our branch never touched
that test module; the conflict was old-main vs force-pushed-main content).
Post-merge: rustfmt over the incoming fmt-dirty generated files, release
bins rebuild clean, process_resolve_store receipt green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcouXUHJmm4NPqwDyvnDKf
@briansrls
briansrls merged commit 51c5107 into main Jul 4, 2026
0 of 2 checks passed
@briansrls
briansrls deleted the claude/ci-witness-opt-in-10h2he branch July 4, 2026 06:40
briansrls added a commit that referenced this pull request Jul 5, 2026
…(E0 / PR-0c) - verdict 5-green/1-red

Executes lens_contract_cost_mode_repromotion_trigger's own named actions,
exactly (the row is deleted - fired):

- dag/gunbc/commit_workflow.dag: CommitWitnessClaim row enrolls
  budget_roster_completeness_test.dag::complexity_budget_roster_family_gate_holds
  on the GithubActionsCiJob surface (the #6232 opt-in authority; zero ci.yml
  change - the floor plan reads spec.witness_entries at eval time).
- contract.dag: lens_contract_cost.mode AuditOnly -> Blocking. The claim is
  now TRUE by derivation: the enforcement gate reads the roster and derives
  consumer_observed = FloorGate; enforcement_consistency_gate_holds (Blocking
  requires a live consumer) is green BY EXECUTION - the same gate that
  proved the old Blocking claim false now proves this one true.
- enforcement_live_witness_test.dag: pinned verdict updated in the same PR
  (roadmap-as-executable): claimed GREEN, scope RED (fn-body reflection
  residue - the E1 lane), subjects GREEN, consumer GREEN, red_control GREEN,
  self_application GREEN. 5 green / 1 red.

What is now enforced that was not before: the cost lens family has a real
merge-surface consumer - the fixture-band budget gate (4-exemplar roster,
family-complete check) runs on the CI floor witness corpus, and any lens
contract claiming Blocking without such a consumer reds the floor.

Receipts (by execution, 2026-07-05): bound witness green;
enforcement_consistency_gate_holds green at Blocking; new 5/1 pin green.
The old 3-red/3-green pin fails against this tree by construction - the
expectation update rides the same PR, per the drift-witness design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Jul 5, 2026
…(E0 PR-0c) - verdict 5-green/1-red (#6276)

* WIP: complexity lens enforcement

* Cost contract re-promoted to Blocking: budget witness enrolled on CI (E0 / PR-0c) - verdict 5-green/1-red

Executes lens_contract_cost_mode_repromotion_trigger's own named actions,
exactly (the row is deleted - fired):

- dag/gunbc/commit_workflow.dag: CommitWitnessClaim row enrolls
  budget_roster_completeness_test.dag::complexity_budget_roster_family_gate_holds
  on the GithubActionsCiJob surface (the #6232 opt-in authority; zero ci.yml
  change - the floor plan reads spec.witness_entries at eval time).
- contract.dag: lens_contract_cost.mode AuditOnly -> Blocking. The claim is
  now TRUE by derivation: the enforcement gate reads the roster and derives
  consumer_observed = FloorGate; enforcement_consistency_gate_holds (Blocking
  requires a live consumer) is green BY EXECUTION - the same gate that
  proved the old Blocking claim false now proves this one true.
- enforcement_live_witness_test.dag: pinned verdict updated in the same PR
  (roadmap-as-executable): claimed GREEN, scope RED (fn-body reflection
  residue - the E1 lane), subjects GREEN, consumer GREEN, red_control GREEN,
  self_application GREEN. 5 green / 1 red.

What is now enforced that was not before: the cost lens family has a real
merge-surface consumer - the fixture-band budget gate (4-exemplar roster,
family-complete check) runs on the CI floor witness corpus, and any lens
contract claiming Blocking without such a consumer reds the floor.

Receipts (by execution, 2026-07-05): bound witness green;
enforcement_consistency_gate_holds green at Blocking; new 5/1 pin green.
The old 3-red/3-green pin fails against this tree by construction - the
expectation update rides the same PR, per the drift-witness design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansrls@gunb.ai>
Co-authored-by: Claude Fable 5 <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.

2 participants