Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
with:
# The toolchain is pinned, not floating. The coverage floor is a
# number about this compiler's instrumentation: rustc 1.100.0
# measures the same source 4.9 points lower than 1.99.1 does
# (decision 0102). A floating `stable` moved that floor
# silently, and the GitForge image (rust:1.99) kept the old
# number, so the two pipelines disagreed.
toolchain: 1.99.1
components: rustfmt
- name: Check formatting
run: cargo fmt --all -- --check
Expand All @@ -133,6 +140,11 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
with:
# Pinned to the compiler the coverage floor was measured with
# (decision 0102). `stable` is not a measurement basis: 1.100.0
# reads the same source 4.9 points lower than 1.99.1 does, and
# the GitForge image stays on 1.99.
toolchain: 1.99.1
components: clippy, rustfmt
- name: Install Aegis
run: |
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ jobs:

- name: Install Rust
uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
with:
# The release artifacts are built with the compiler the gate
# judged, not with whatever `stable` happens to be that week
# (decision 0102).
toolchain: 1.99.1

- name: Cache cargo
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
Expand Down
2 changes: 1 addition & 1 deletion docs/GAPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ authoritative source that supersedes this table.
| **Shipped** | v0.2.5 on GitHub (2026-11-03) | ✅ `v0.2.4` was released without a tag; see PROGRESS |
| **CI** | GitHub Actions: `fmt` (fast) + `gate` (`xtask ci-no-fmt`); GitForge 7-job pipeline | ✅ |
| **aegis** | Baseline clean; scan 0.17 s | `.aegisignore` excludes `target/`, `web/dist/`, `fuzz/`; baseline is content-keyed (decision 0074), which supersedes the rule-class and per-finding suppression of decision 0070 |
| **Coverage** | floors 81.1 % lines / 78.7 % funcs (`xtask/src/coverage.rs`) | Local `llvm-cov` fails on stale artifacts; CI works |
| **Coverage** | floors 81.1 % lines / 78.7 % funcs (`xtask/src/coverage.rs`), measured with rustc 1.99.1 | The toolchain is pinned (decision 0102): rustc 1.100.0 reads the same tree 4.9 points lower, so a floating `stable` moved the floor. Local `llvm-cov` fails on stale artifacts; CI works |
| **Code** | 144,122 Rust lines | No `unwrap()`/`panic!()` outside `#[cfg(test)]`; no `todo!`/`unimplemented!` |
| **Registry** | 253 commands | Every command executable from the GUI via the Ctrl+P palette (19.4); menu reachability is the remaining gap |
| **Menu reachability** | 2 inert of 296 items | *live* — derived + gated (`menus::tests::inert_count_never_rises`) |
Expand Down
5 changes: 5 additions & 0 deletions docs/NEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ columns when re-blessing.
The floors were raised 60% → 81.1% lines / 78.7% functions (measured
baseline, 2026-10-26). The gate is rising-only from here.
**2026-10-26: 81.1/78.8 measured, floors 81.1/78.7; 14.1 landed.**
**2026-11-12: the floor is a claim about a compiler too.** rustc 1.100.0
measures a byte-identical tree at 77.0%/76.1% where 1.99.1 measures
81.9%/78.9%, so the CI toolchain is now pinned to 1.99.1 and the gate names
the compiler that measured it (decision 0102). A re-bless names its compiler
as well.

- **14.1 Editor surface tests** — LANDED 2026-10-26 (`cbfe7eb`, 30
tests). The listed residuals are all covered since: `import_styles`
Expand Down
2 changes: 1 addition & 1 deletion docs/TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
|---|---|---|---|---|---|
| 5 | 19.6 inert-menu wiring campaign | [tasks/T5-inert-burndown.md](tasks/T5-inert-burndown.md) | `DONE — 2026-11-11` | UI/verb lane | Batches 1–17 are complete. The current pinned census is 2 inert rows of 296; eight other rows remain explicitly feature/evidence-gated and are not part of an active wiring campaign (`menus::tests::inert_count_never_rises`, `menus::tests::menu_item_total_is_pinned`). |
| 194 | Aegis gate: content-keyed baseline, so a doc edit no longer fails the gate (decision 0074) | [decisions/0074-aegis-content-keyed-baseline.md](decisions/0074-aegis-content-keyed-baseline.md) | `IMPLEMENTED` — the content-keyed gate and its decision record are in the tree; the gate has been exercised on this branch's tree | CI/tooling lane | **landed in `main` (#18)** and merged here; attach a run id before claiming the fleet-wide state |
| — | Release workflow hardening: every action pinned to a commit SHA, a fail-closed pin check in CI (decision 0099), a classified release mode with a fail-closed aggregate gate (decision 0100) | [decisions/0099-workflow-actions-are-pinned.md](decisions/0099-workflow-actions-are-pinned.md) · [decisions/0100-release-mode-classification-and-a-tested-release-gate.md](decisions/0100-release-mode-classification-and-a-tested-release-gate.md) | `IMPLEMENTED` — the 19 action references are pinned; the aggregate gate is a tested script that refuses a missing, empty, extra, mismatched or platform-less artifact set; a rehearsal is a `TEST`-titled prerelease on a disposable tag; the validator and the three release self-tests pass locally; the CI receipt is pending | CI/tooling lane | land the branch on both receipts; the checks run in the backup CI's `changes` job, and mirroring them into the GitForge pipeline waits for the `:2` pipeline shape (PR #123, PR #129); then rehearse the release path end to end through `workflow_dispatch` |
| — | Release workflow hardening: every action pinned to a commit SHA, a fail-closed pin check in CI (decision 0099), a classified release mode with a fail-closed aggregate gate (decision 0100), a pinned measuring toolchain (decision 0102) | [decisions/0099-workflow-actions-are-pinned.md](decisions/0099-workflow-actions-are-pinned.md) · [decisions/0100-release-mode-classification-and-a-tested-release-gate.md](decisions/0100-release-mode-classification-and-a-tested-release-gate.md) · [decisions/0102-coverage-floor-is-measured-with-a-pinned-toolchain.md](decisions/0102-coverage-floor-is-measured-with-a-pinned-toolchain.md) | `IMPLEMENTED` — the 19 action references are pinned; the aggregate gate is a tested script that refuses a missing, empty, extra, mismatched or platform-less artifact set; a rehearsal is a `TEST`-titled prerelease on a disposable tag; the validator and the three release self-tests pass locally; the CI receipt is pending | CI/tooling lane | land the branch on both receipts; the checks run in the backup CI's `changes` job, and mirroring them into the GitForge pipeline waits for the `:2` pipeline shape (PR #123, PR #129); then rehearse the release path end to end through `workflow_dispatch` |
| 27 | Cross-platform release engineering: Windows, macOS, Linux package matrix, Android feasibility, source, SBOM/provenance, signing, CI aggregation and certification | [tasks/T27-cross-platform-release.md](tasks/T27-cross-platform-release.md) | `IN PROGRESS` — implementation is ahead of verification; **no target is `CERTIFIED`** and nothing is signed | release-workflow lane | land an end-to-end tag run of the repaired workflow and attach the run id as the RP-07 receipt; consolidate the duplicated `dist --source`/`--sbom` verbs (B-3) |
| 26 | Refresh user-visible CS6 parity matrix | [tasks/T26-parity-matrix.md](tasks/T26-parity-matrix.md) | `IMPLEMENTED` — matrix published | parity lane | every CS6 menu, tool, panel, inspector field set, workflow, import/export surface, canvas interaction, keymap area, and selection state is classified with an evidence citation and an owning ticket in [`CS6-PARITY.md`](CS6-PARITY.md); the browser-sampling check the ticket named was performed on 2026-11-04 against a fresh optimized bundle and is recorded in T22 |
| 22 | Phase 22: CS6 shell fidelity and screenshot audit | [tasks/T22-screenshot-ui.md](tasks/T22-screenshot-ui.md) | `IN PROGRESS` | UI lane | screenshot acceptance is complete; 22.1 panel inventory and 22.2 tool strip landed on `main` with the shell-lineage train (#107); 22.5 chrome remains partial, and 22.4 still has evidence-gated bitmap transform/fill-stroke work. [Phase 22](NEXT.md#phase-22--cs6-shell-fidelity-p0-the-looks-and-acts-like-fireworks-phase) |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# 0102 — The coverage floor is measured with a pinned toolchain

Date: 2026-11-12 · Tickets: [T27-57](../tasks/T27-57-workflow-hardening.md) ·
Status: accepted

## Context

On 2026-11-12 the GitHub `gate` job went red on two pull requests whose Rust
source was byte-identical to `main`'s.

PR #134's tree differs from `main` only in `docs/` and `packaging/`. The Rust
diff is empty:

```
git diff --shortstat origin/main origin/chore/windows-recipe-audit \
-- crates xtask Cargo.toml Cargo.lock
```

Yet #134 measured **77.0 % lines / 76.1 % functions** and failed the
81.1 / 78.7 floor, while `main` measured **81.9 / 78.9** and passed.

The differentiator is the compiler, not the code:

| GitHub run | tree | rustc | measurement |
|---|---|---|---|
| `43157085372` | `main` @ `bc8abfb` | 1.99.1 | 81.9 / 78.9 ✓ |
| `43169461346` | #130 @ `002eb56e` | 1.99.1 | 81.9 / 78.8 ✓ |
| `43284817650` | #134 @ `f570c86` | **1.100.0** | 77.0 / 76.1 ✗ |
| `43284861290` | #134 @ `f570c86` | **1.100.0** | 77.0 / 76.1 ✗ |
| `43287370012` | #134 @ `545e7a5` | **1.100.0** | 77.0 / 76.1 ✗ |

The workflow installs `toolchain: stable`. rustc 1.100.0 was released on
2026-11-09 and reached the runners during 2026-11-12. The GitForge pipeline
did not move: its image is `rust:1.99-slim-bookworm` (rustc 1.99.1), which is
why the two pipelines disagreed about the same commit — GitForge's
`clippy-test` job passed the floor while GitHub's `gate` job failed it.

The build cache is not the explanation. The failing runs restored the same
Cargo.lock-keyed build cache as the passing ones, and the last failing run
(`43287370012`) restored a warm cache and still read 77.0.

So the floor was never a property of the source tree alone. It was a property
of the source tree *and* the compiler that instrumented it — and the workflow
said `stable`.

## Decision

- **The GitHub workflows pin the toolchain.** `ci.yml` (the `fmt` and `gate`
jobs) and `release.yml` install `toolchain: 1.99.1` — the compiler the
floors were measured with. A release is built with the compiler the gate
judged, not with whatever `stable` happened to be that week.
- **The CI image is pinned to the patch.** `rust:1.99-slim-bookworm` becomes
`rust:1.99.1-slim-bookworm`. The two tags resolve to the same image digest
today (`sha256:392f2219…`), so the runner's image does not change; the
explicit patch stops a future `rust:1.99` from moving under the floor.
- **The gate names the compiler that measured it.** The coverage output
carries `measured with rustc <version>`, and says so plainly when that is
not the version the floors were measured with. The gate warns; it does not
fail, because a developer's local compiler is not the CI compiler and a
local `xtask ci` must not become impossible.
- **The floor is not re-blessed.** 81.1 / 78.7 stands. The measurement moved,
not the code, and lowering the floor to meet a moving measurement is the
weakening the gate exists to prevent.

## Evidence

- The run table above: five runs, two toolchains, one Rust tree.
- `rust:1.99.1-slim-bookworm` and `rust:1.99-slim-bookworm` are the same
digest on Docker Hub (`sha256:392f221964669402bf4ae133d2a46c3ae270f64734133f4b2703d40019235c1a`),
so the image pin changes no artifact.
- `xtask/src/coverage.rs` names the measuring compiler and carries unit tests
for the version parser.
- PR #133 (`feat/play-animation`) also failed the floor, at 73.3 / 72.8 —
and its diagnostic run was on 1.100.0 as well. Its deficit is therefore
partly the toolchain and partly real: the animation code it adds is not
covered. That is a separate, content-level shortfall, and this decision
does not excuse it.

## Consequences

- The GitHub and GitForge pipelines judge the same rustc again, which is what
`infrastructure/docker/ci-rust.Dockerfile` already asked for ("this pin
matches backend-ci-rust:1 so both pipelines judge the same rustc").
- A toolchain bump is now an explicit change with a measured consequence,
instead of an event that silently moves the floor under every open pull
request.
- The floor's basis is written down: 81.1 / 78.7, measured with rustc
1.99.1. A future re-bless names the compiler it was measured with.
- The local toolchain (1.99.0 here) is one patch behind the pinned one; the
gate warns when it measures with anything other than 1.99.1.

See also decision [0099](../decisions/0099-workflow-actions-are-pinned.md)
— the same class of drift, one layer down: an unpinned *action* versus an
unpinned *compiler*.
Loading
Loading