Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
5ad0278
CI witness opt-in inversion: zero witnesses by default, enrollment by…
claude Jul 4, 2026
31f63a7
Enroll the first opt-in CI witness: variant_owner_expected_type (sing…
claude Jul 4, 2026
eb5a23a
Fix clippy absurd_extreme_comparisons on the emitted-Rust error ratch…
claude Jul 4, 2026
32ec4ec
Eliminate every degradation arm in floor selection: declared machiner…
claude Jul 4, 2026
73c03e0
Floor prelude: resolve the plan entry once, stamp every phase with wa…
claude Jul 4, 2026
2e658ff
CI job timeout: 90 -> 10 minutes (operator inversion of the timeout r…
claude Jul 4, 2026
d4fd494
Resolver graph-major design doc: once-per-node module evaluation (ope…
claude Jul 4, 2026
5749511
Hand-sync generated docs to their edited authorities (DESIGN.md CI bu…
claude Jul 4, 2026
14ba3c5
claim_batch: honor GUNBC_FLOOR_PHASE_PROFILE (same install as claim_e…
claude Jul 4, 2026
40adcca
Resolver design: phase verdict (typecheck-dominant) + one-scheduler-n…
claude Jul 4, 2026
eb3955b
S1a: process-level resolve store - fixed-entry machinery resolves onc…
claude Jul 4, 2026
24b8743
typecheck attribution: start markers (profile-gated) name the patholo…
claude Jul 4, 2026
af8a3ed
Phase heartbeat: live type-env amplification counters; pathology named
claude Jul 4, 2026
d44a846
Resolver design: operator ruling - constructor ambiguity is an ERROR;…
claude Jul 4, 2026
c57cf33
Resolver design: operator sequencing - union resolve first as interim…
claude Jul 4, 2026
911e0e7
Merge origin/main (force-updated) into claude/ci-witness-opt-in-10h2he
claude Jul 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ env:
jobs:
ci:
runs-on: [self-hosted, linux, arm64]
timeout-minutes: 90
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v5
Expand Down Expand Up @@ -99,7 +99,7 @@ jobs:
"$ROOT/target/release/gunbc" run --source-root dag --source-root src/v2 --entry dag/tools/merge_admission_gate.dag --function main
rust_tests:
runs-on: [self-hosted, linux, arm64]
timeout-minutes: 90
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v5
Expand Down Expand Up @@ -201,7 +201,7 @@ jobs:
deploy_dashboard_srv1:
runs-on: [self-hosted, linux, arm64, srv1]
needs: [ci]
timeout-minutes: 90
timeout-minutes: 10
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Checkout
Expand Down
2 changes: 1 addition & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,4 @@ hollow alias (minimality ≠ grounding) · state-space conflation (an `Option`/`

- `cargo test --workspace` · `cargo clippy --all-targets -- -D warnings` · `cargo fmt --all --check`
- one-time: `git config core.hooksPath .githooks` (pre-push runs `cargo fmt`)
- CI (`gunbc ci` generates `.github/workflows/ci.yml`; all `v1-compiler` seed bins, no shell gate): **one** composed floor pass — `claim_executor --source-root src/v2 --source-root dag --plan-entry src/v2/workflow/ci_floor_plan.dag --plan-function gunbc_ci_floor_batches`. The v2 scheduler decides the batches from the single-authority spec (`gunbc.ci_spec`); the only structural fact the plan adds is *compile-clean gates the rest* (batch-1 `dag_compile_clean_gate` → batch-2 everything else, a dependency edge). Witness **discovery is tree-wide and marker-driven** (roster derived from `gunbc.ci_layer_roots`: `witness_layer_roots = [src/v2, dag]`, `witness_discovery_scan_dirs = [dag/test/claim]`): the source-root pass walks every `*_test.dag` under **both** trees and auto-enrolls each `test fn` — fail-closed, a `test fn` in a non-`_test.dag` file is a hygiene violation, so a witness cannot silently hide; the scan-dir pass adds `unified_claim_*` BoolWitnessClaim decls. So a new witness is floor-covered by *naming it* `<x>_test.dag` with a `test fn`, not by hand-enrolling it (`discover_floor_corpus_rows` in `cli_run.rs`). Batch-2 also carries the effectful gates: rust fmt/clippy monolith gate (when a `.rs` changes), emit-host MVP smokes, layering-imports scan + perturb receipts, source-root-ingest, and the `ci.yml` drift+parse gate (ci.yml == `gunbc ci` output). The compile-clean gate is `--target dag`. Parse is grammar-owned: `ingest_yaml_source` (`dag/extdeps/languages/yaml/ingest.dag`), no shell/host parser.
- CI (`gunbc ci` generates `.github/workflows/ci.yml`; all `v1-compiler` seed bins, no shell gate): **one** composed floor pass — `claim_executor --source-root src/v2 --source-root dag --plan-entry src/v2/workflow/ci_floor_plan.dag --plan-function gunbc_ci_floor_batches`. The v2 scheduler decides the batches from the single-authority spec (`gunbc.ci_spec`); the only structural fact the plan adds is *compile-clean gates the rest* (batch-1 `dag_compile_clean_gate` → batch-2 everything else, a dependency edge). Witness **enrollment is OPT-IN** (operator inversion 2026-07-04): the floor runs exactly `CiSpec.witness_entries` — `CommitWitnessClaim` rows on the `GithubActionsCiJob` surface of `gunbc.commit_workflow.commit_gate_roster`, projected by `project_ci_floor_witness_entries` — and an **empty roster puts zero witness-corpus nodes in the plan**. Existence ≠ enrollment: the tree-wide glob roster grows quadratically with the corpus (a witness is definable between any two pipeline segments) and had reached the 90-min timeout — maximum cost, zero delivered signal, every merge fail-open. Naming hygiene stays fail-closed (a `test fn` outside `*_test.dag` is still a violation — the executor runs the zero-enrollment naming walk when no discovery batch is scheduled), and tree-wide discovery remains the **local** path (`claim_batch --roster-from-discovery --source-root dag --source-root src/v2 --scan-dir dag/test/claim --scan-dir src/v2/test/claim/manual`). Dissolve-on: affected-set selection (`v2.lens.affected_set`) + floor memoization make per-PR selection-by-affectedness affordable; enrollment then returns to discovery shrunk by the affected set. Batch-2 also carries the effectful gates: rust fmt/clippy monolith gate (when a `.rs` changes), emit-host MVP smokes, layering-imports scan + perturb receipts, source-root-ingest, and the `ci.yml` drift+parse gate (ci.yml == `gunbc ci` output). The compile-clean gate is `--target dag`. Parse is grammar-owned: `ingest_yaml_source` (`dag/extdeps/languages/yaml/ingest.dag`), no shell/host parser.
20 changes: 19 additions & 1 deletion dag/gunbc/ci_spec.dag
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import gunbc.ci_layer_roots {
import gunbc.commit_workflow {
commit_gate_roster,
project_ci_floor_gates,
project_ci_rust_job_gates
project_ci_rust_job_gates,
project_ci_floor_witness_entries
}
import std.realization_schedule { ScheduleWitnessEntry }
import gunbc.ci_failure_class { infra_retry_grep_alternation }
import gunbc.ci_deploy_access { DeployAccess, ci_deploy_srv1_access }
import gunbc.ci_deploy_target_host { gunbc_ci_deploy_srv1_runner_spec }
Expand All @@ -39,15 +41,20 @@ type DeployStage {
runner: RunnerSpec
}

data ci_spec_witness_entries_note: String = "witness_entries is the opt-in CI witness roster (operator inversion 2026-07-04): the floor runs exactly these declared witnesses, never a tree scan; empty means the plan carries no witness-corpus node at all. Enrollment authority: gunbc.commit_workflow.commit_gate_roster CommitWitnessClaim rows on the GithubActionsCiJob surface (see ci_witness_optin_inversion there)."

type CiSpec {
gates: List<Gate>
witness_entries: List<ScheduleWitnessEntry>
diff_policy: DiffPolicy
notice_title: String
deploy_stages: List<DeployStage>
}

data gunbc_ci_floor_gates: List<Gate> = project_ci_floor_gates(roster: commit_gate_roster)

data gunbc_ci_floor_witness_entries: List<ScheduleWitnessEntry> = project_ci_floor_witness_entries(roster: commit_gate_roster)

data gunbc_ci_rust_job_gates: List<Gate> = project_ci_rust_job_gates(roster: commit_gate_roster)

data gunbc_ci_gates: List<Gate> = concat(gunbc_ci_floor_gates, gunbc_ci_rust_job_gates)
Expand All @@ -69,6 +76,7 @@ data gunbc_ci_deploy_stages: List<DeployStage> = [gunbc_ci_deploy_srv1_stage]

data gunbc_ci_spec: CiSpec = {
gates: gunbc_ci_floor_gates,
witness_entries: gunbc_ci_floor_witness_entries,
diff_policy: {
base: "origin/main",
head: "HEAD",
Expand All @@ -78,6 +86,16 @@ data gunbc_ci_spec: CiSpec = {
deploy_stages: gunbc_ci_deploy_stages,
}

fn ci_spec_with_witness_entries(spec: CiSpec, entries: List<ScheduleWitnessEntry>) -> CiSpec {
CiSpec {
gates: spec.gates,
witness_entries: entries,
diff_policy: spec.diff_policy,
notice_title: spec.notice_title,
deploy_stages: spec.deploy_stages,
}
}

data ci_release_features: String = "text_lookup_work_counter"

fn ci_repo_root_shell() -> String {
Expand Down
7 changes: 4 additions & 3 deletions dag/gunbc/ci_workflow.dag
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import gunbc.merge_admission_produce {
ci_merge_admission_gate_script,
}
import gunbc.ci_workflow_expressions { ci_deploy_push_to_main_if }
import gunbc.commit_workflow { commit_gate_roster, project_ci_floor_gates }
import gunbc.commit_workflow { commit_gate_roster, project_ci_floor_gates, project_ci_floor_witness_entries }
import gunbc.runner_spec_from_offer { gunbc_ci_runner_spec }
import std.disposition { Disposition, Scaffold, SingleAuthority, Terminal }
import std.decl_ref { DeclarationRef, WholeDeclaration }
Expand Down Expand Up @@ -145,6 +145,7 @@ fn ci_prelude_steps() -> List<Step> {
fn ci_floor_spec_from_roster() -> CiSpec {
{
gates: project_ci_floor_gates(roster: commit_gate_roster),
witness_entries: project_ci_floor_witness_entries(roster: commit_gate_roster),
diff_policy: gunbc_ci_spec.diff_policy,
notice_title: gunbc_ci_spec.notice_title,
deploy_stages: [],
Expand Down Expand Up @@ -267,10 +268,10 @@ fn ci_deploy_step(stage: DeployStage) -> Step {
}
}

data gunbc_ci_job_timeout_policy_minutes: Int = 90
data gunbc_ci_job_timeout_policy_minutes: Int = 10

data gunbc_ci_job_timeout_policy_disposition: Disposition = Terminal {
reason: "gunbc CI policy (operator, 2026-07-02; interim 2026-07-03): CI jobs time out at 90 minutes. The 30min smoke alarm and subsequent 50min raise both sat below the live floor wall: batch-1 compile-clean alone ran ~48min still in progress when a 50min job cap canceled run 28645912887 (plus ~2min release build overhead in the same step). Measured whole-step wall is ~50min+ on ARM fleet (ROADMAP §3 `2-compile-clean-serial`); 90min is interim headroom until compile-clean is scoped/memoized below 30min again. default_job_timeout_minutes (extdeps/github/actions.dag) still models GitHub's 360min platform default — workflow-layer policy on top."
reason: "gunbc CI policy (operator, 2026-07-04): CI jobs time out at 10 minutes - a deliberate INVERSION of the prior ratchet (30min alarm -> 50min -> 90min, each raise buying headroom for the unfixed resolve cost; at 90min the queue backed up and runs still timed out delivering zero signal). The budget is now a forcing function: the floor must FIT it, and the itemized [t+Xs] phase marks say exactly which phase does not (measured 2026-07-04: plan-closure resolve was 15min+, whole-tree compile-clean ~48min on the ARM fleet - both resolve-dominated; see the floor-shared-compute-memoization plan). Every job stays red-by-timeout until resolve memoization lands; that red is honest, unlike a 90min lane that never completed. default_job_timeout_minutes (extdeps/github/actions.dag) still models GitHub's 360min platform default - workflow-layer policy on top."
}

fn ci_deploy_job(stage: DeployStage) -> Job {
Expand Down
29 changes: 28 additions & 1 deletion dag/gunbc/commit_workflow.dag
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import gunbc.ci_gate {
import gunbc.generated_artifact {
GeneratedArtifact, CiYamlArtifact, GithooksPrePushArtifact, artifact_path
}
import std.realization_schedule { string_list_eq }
import std.realization_schedule { string_list_eq, ScheduleWitnessEntry }

data doc_reachability_witness_entry: String = "dag/test/claim/doc_reachability_witness_test.dag"

Expand Down Expand Up @@ -84,6 +84,13 @@ data commit_gate_roster: List<CommitCheckEnrollment> = [
check: CommitSpecGate { gate: RustMonolithGate },
surfaces: [GithubActionsRustTestsJob]
},
CommitCheckEnrollment {
check: CommitWitnessClaim {
entry: "dag/test/claim/variant_owner_expected_type_test.dag",
check_fns: ["expected_type_picks_variant_owner_not_alpha_order"]
},
surfaces: [GithubActionsCiJob]
},
CommitCheckEnrollment {
check: CommitWitnessClaim {
entry: doc_reachability_witness_entry,
Expand Down Expand Up @@ -171,6 +178,26 @@ fn project_ci_rust_job_gates(roster: List<CommitCheckEnrollment>) -> List<Gate>
)
}

data ci_witness_optin_inversion: String = "CI witness enrollment is OPT-IN (operator inversion, 2026-07-04). Witness existence in the tree does not enroll it in CI: the tree-wide glob roster grows quadratically with the corpus (a witness is definable between any two pipeline segments) and had reached the point where the floor timed out - maximum cost, zero delivered signal. A witness gates CI exactly when a CommitWitnessClaim row on the GithubActionsCiJob surface declares it, projected by project_ci_floor_witness_entries into the floor plan's explicit entries (entry x check_fn). Tree-wide discovery remains the local path (claim_batch --roster-from-discovery). dissolve-on: affected-set selection (v2.lens.affected_set) plus floor memoization make per-PR selection-by-affectedness affordable; re-enroll the corpus through the affected set and dissolve this projection back into discovery."

fn witness_entries_from_enrollments(rows: List<CommitCheckEnrollment>) -> List<ScheduleWitnessEntry> {
fold(rows, init: [], f: fn(acc, row) {
match row.check {
CommitWitnessClaim { entry, check_fns } =>
concat(acc, fold(check_fns, init: [], f: fn(fns, check_fn) {
concat(fns, [ScheduleWitnessEntry { entry: entry, function: check_fn }])
}))
_ => acc
}
})
}

fn project_ci_floor_witness_entries(roster: List<CommitCheckEnrollment>) -> List<ScheduleWitnessEntry> {
witness_entries_from_enrollments(
rows: filter(roster, e => enrollment_has_surface(e: e, surface: GithubActionsCiJob))
)
}

fn local_tidy_check_eq(a: LocalTidyCheck, b: LocalTidyCheck) -> Bool {
match a {
LocalTidyWitnessCheck { entry, check_fns } =>
Expand Down
2 changes: 1 addition & 1 deletion dag/gunbc/design_document.dag
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ fn building_checks_blocks() -> List<MarkdownBlock> {
ul(items: [
li(text: "`cargo test --workspace` · `cargo clippy --all-targets -- -D warnings` · `cargo fmt --all --check`"),
li(text: "one-time: `git config core.hooksPath .githooks` (pre-push runs `cargo fmt`)"),
li(text: "CI (`gunbc ci` generates `.github/workflows/ci.yml`; all `v1-compiler` seed bins, no shell gate): **one** composed floor pass — `claim_executor --source-root src/v2 --source-root dag --plan-entry src/v2/workflow/ci_floor_plan.dag --plan-function gunbc_ci_floor_batches`. The v2 scheduler decides the batches from the single-authority spec (`gunbc.ci_spec`); the only structural fact the plan adds is *compile-clean gates the rest* (batch-1 `dag_compile_clean_gate` → batch-2 everything else, a dependency edge). Witness **discovery is tree-wide and marker-driven** (roster derived from `gunbc.ci_layer_roots`: `witness_layer_roots = [src/v2, dag]`, `witness_discovery_scan_dirs = [dag/test/claim]`): the source-root pass walks every `*_test.dag` under **both** trees and auto-enrolls each `test fn` — fail-closed, a `test fn` in a non-`_test.dag` file is a hygiene violation, so a witness cannot silently hide; the scan-dir pass adds `unified_claim_*` BoolWitnessClaim decls. So a new witness is floor-covered by *naming it* `<x>_test.dag` with a `test fn`, not by hand-enrolling it (`discover_floor_corpus_rows` in `cli_run.rs`). Batch-2 also carries the effectful gates: rust fmt/clippy monolith gate (when a `.rs` changes), emit-host MVP smokes, layering-imports scan + perturb receipts, source-root-ingest, and the `ci.yml` drift+parse gate (ci.yml == `gunbc ci` output). The compile-clean gate is `--target dag`. Parse is grammar-owned: `ingest_yaml_source` (`dag/extdeps/languages/yaml/ingest.dag`), no shell/host parser."),
li(text: "CI (`gunbc ci` generates `.github/workflows/ci.yml`; all `v1-compiler` seed bins, no shell gate): **one** composed floor pass — `claim_executor --source-root src/v2 --source-root dag --plan-entry src/v2/workflow/ci_floor_plan.dag --plan-function gunbc_ci_floor_batches`. The v2 scheduler decides the batches from the single-authority spec (`gunbc.ci_spec`); the only structural fact the plan adds is *compile-clean gates the rest* (batch-1 `dag_compile_clean_gate` → batch-2 everything else, a dependency edge). Witness **enrollment is OPT-IN** (operator inversion 2026-07-04): the floor runs exactly `CiSpec.witness_entries` — `CommitWitnessClaim` rows on the `GithubActionsCiJob` surface of `gunbc.commit_workflow.commit_gate_roster`, projected by `project_ci_floor_witness_entries` — and an **empty roster puts zero witness-corpus nodes in the plan**. Existence ≠ enrollment: the tree-wide glob roster grows quadratically with the corpus (a witness is definable between any two pipeline segments) and had reached the 90-min timeout — maximum cost, zero delivered signal, every merge fail-open. Naming hygiene stays fail-closed (a `test fn` outside `*_test.dag` is still a violation — the executor runs the zero-enrollment naming walk when no discovery batch is scheduled), and tree-wide discovery remains the **local** path (`claim_batch --roster-from-discovery --source-root dag --source-root src/v2 --scan-dir dag/test/claim --scan-dir src/v2/test/claim/manual`). Dissolve-on: affected-set selection (`v2.lens.affected_set`) + floor memoization make per-PR selection-by-affectedness affordable; enrollment then returns to discovery shrunk by the affected set. Batch-2 also carries the effectful gates: rust fmt/clippy monolith gate (when a `.rs` changes), emit-host MVP smokes, layering-imports scan + perturb receipts, source-root-ingest, and the `ci.yml` drift+parse gate (ci.yml == `gunbc ci` output). The compile-clean gate is `--target dag`. Parse is grammar-owned: `ingest_yaml_source` (`dag/extdeps/languages/yaml/ingest.dag`), no shell/host parser."),
]),
]
}
Expand Down
2 changes: 2 additions & 0 deletions dag/gunbc/plans/ci_selection_vs_scheduling.dag
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ fn ci_selection_vs_scheduling_body() -> List<MarkdownBlock> {
p(text: "Affected-set and parallelization-by-realization are the **same work from two ends**: selection shrinks **what** runs per-PR (to the affected set); realization shrinks **how long** it takes (width / placement / cache / Share). Together they make *\"run the affected set per-PR, fast\"* the default — which dissolves the need for a cost-based nightly dump entirely. The only residue is (1) non-hermetic and (2) the selector backstop — neither is \"expensive\"; both are about soundness / determinism."),
h2(text: "6. The seam, precisely"),
p(text: "§4 selection produces the must-run set; §1 scheduling runs **that** set affordably (parallelism / cache) and runs the **complement** on a backstop cadence. Selection decides *membership*; scheduling decides *cadence / placement / width*. Cost lives only on the scheduling side."),
h2(text: "7. 2026-07-04 — the run-all baseline stood down IN CI (opt-in inversion)"),
p(text: "Step 1's \"sound baseline\" stopped being a baseline the day the floor stopped *completing*: at ~1,551 discovered witnesses the composed pass hit the 90-minute job timeout on every run — maximum cost, **zero delivered signal**, and every merge effectively fail-open (a gate that always times out gates nothing). A run-all that never finishes is not a superset of the affected set; it is the empty set billed at full price. Operator decision: invert to **declared opt-in enrollment** — `CiSpec.witness_entries`, projected from `CommitWitnessClaim` rows on the `GithubActionsCiJob` surface of `commit_gate_roster`; an empty roster puts zero witness-corpus nodes in the floor plan. This is NOT the §0 cost-routed selection this doc rejects: no affected test is demoted by expense, because *no* witness currently gates by discovery — the inversion replaces an always-red-by-timeout gate with an honest, completing floor (gates + declared witnesses), and the discovery roster remains the local path (`claim_batch --roster-from-discovery`). The §3 ladder is unchanged as the *destination*: land affected-set selection + floor memoization, then re-enroll the corpus through the affected set and dissolve the opt-in roster (dissolve-on marker lives on `ci_witness_optin_inversion` in `gunbc.commit_workflow`). Found during the inversion: `resolve_floor_runner_context` had been failing on a comment block the v1 grammar cannot parse (`affected_set_floor_runner.dag`), so the affected-set skip silently fell back to full-corpus on every run — step 2 was dead in CI while the doc described it as CI-enrolled."),
]
}

Expand Down
Loading
Loading