From 20005c2247d1cef2849a202a1019c8f6f050045f Mon Sep 17 00:00:00 2001 From: Michael Kinney Date: Wed, 11 Nov 2026 15:17:48 -0600 Subject: [PATCH 1/3] docs(release): record the live web deployment's digest receipt The live deployment was recorded as serving the v0.2.5 release bytes. Measured by digest on 2026-11-11, that is false: the live bundle's trunk asset name (2274ac8c2925b61d) and wasm sha256 (f4f0a3f0...) match neither the published v0.2.5 release asset (f9dbe2f14305b2d9 / a8a4fa4f...) nor a rebuild of the v0.2.5 tag (103c62d3366aef17 / 5934702f...). The earlier match was inferred from equal file sizes and an identical appinfo.json, not from digests. The served index.html does differ from the build's own, and that difference is fully explained: the asset names and their integrity digests are substituted, and the Cloudflare Pages analytics beacon is injected. The live deployment corresponds to no identifiable release, which is what blocker B-6 in the task board already says. The receipt is in T18-release.md, and Project-Status.md now states the deployment's relationship to main instead of leaving it to be re-derived. Co-Authored-By: Claude Code --- docs/tasks/T18-release.md | 52 ++++++++++++++++++++++++++++++++++++- docs/wiki/Project-Status.md | 2 +- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/docs/tasks/T18-release.md b/docs/tasks/T18-release.md index d7533c2e..9fcd80a6 100644 --- a/docs/tasks/T18-release.md +++ b/docs/tasks/T18-release.md @@ -1,6 +1,56 @@ # T18 — Verify v0.2.5 release and web deployment -**Status:** the `v0.2.5` release is complete. On 2026-11-04, the served Pages manifest's JS/wasm/index hashes differed from local `dist/web`; the deployed source revision remains unknown. +**Status:** the `v0.2.5` release is complete. The live deployment is +**not** the v0.2.5 release artifact and is **not** reproducible from +the v0.2.5 tag — measured 2026-11-11, receipt below. The deployed +source revision remains unknown. + +## Deployment receipt — 2026-11-11 + +Three distinct web bundles were compared by digest. The trunk asset +name carries a source-tree hash, so a differing name is a differing +source; the wasm digest is the build's byte identity. + +| Bundle | trunk asset name | wasm bytes | wasm sha256 | +|---|---|---|---| +| **live** — `https://firecraft-web.pages.dev` | `2274ac8c2925b61d` | 21 231 377 | `f4f0a3f06fa3f5057485715c5c039fda50de9a6a0e72a451a62bf56366dbdb11` | +| **v0.2.5 release asset** — `firecraft-web-0.2.5.zip` | `f9dbe2f14305b2d9` | 21 238 451 | `a8a4fa4f28aa81a9e5fa6bb2a24eec1fe8a38b7ae4759333e12cc8c38b201865` | +| **v0.2.5 tag rebuild** — `xtask dist --web` at tag `v0.2.5` | `103c62d3366aef17` | 21 229 720 | `5934702ff141ea4cc7b0557abebaf95b78219adbc9ed2dc64607740a4a3e9e7b` | + +Consequences, each measured rather than inferred: + +- **The live bundle is not the v0.2.5 release asset.** Different asset + name, different wasm digest, different byte count. The JS text + differs only in wasm-bindgen closure `shim_idx` values and the + internal wasm-bindgen symbol hashes — a toolchain difference, not + proof of a source difference; the asset *name* is the source + identity, and it differs. +- **The live bundle is not reproducible from the v0.2.5 tag** with + the current CI toolchain either (the tag rebuild carries a third + asset name). The published v0.2.5 asset was therefore built from a + tree that is neither the tag nor the live deployment. +- **The live HTML difference is explained.** The served `index.html` + is the build's own `index.html` with the asset names and their + `integrity` digests substituted, plus the Cloudflare Pages + analytics beacon injected by the CDN. That accounts for the whole + 2 195 → 2 409 byte difference; there is no unexplained drift. +- **`appinfo.json` matches** the release asset byte for byte + (`7d039c36f28e9b1f67f5ee45fd7bdfd705873e716cc13b09d093be19ea0a3ffa`), + so the live app reports version `0.2.5` — but every commit since the + v0.2.5 stamp also reports `0.2.5`, so the version stamp alone + identifies nothing. + +The earlier reconciliation recorded on this ticket — that the live +site's JavaScript and wasm "match the v0.2.5 release bytes" — is +**falsified**: the digests differ, and the match was inferred from +equal file *sizes* and an identical `appinfo.json`, not from +digests. The live site corresponds to no identifiable release, which +is what blocker B-6 in the task board says. + +**What would close it:** deploy a bundle whose asset name and wasm +digest are recorded here, from a named commit, and re-measure the +live host against that record. That is a production publish and stays +gated for explicit maintainer approval. **Objective:** verify the already tagged v0.2.5 release and confirm which web build is live on Cloudflare Pages. diff --git a/docs/wiki/Project-Status.md b/docs/wiki/Project-Status.md index 08157f29..e7b8bbdb 100644 --- a/docs/wiki/Project-Status.md +++ b/docs/wiki/Project-Status.md @@ -4,7 +4,7 @@ The last audited source snapshot is `main` at `5fac7a2` (2026-11-04); the latest release tag is `v0.2.5`. The detailed [Progress page](https://github.com/aliasfoxkde/FireCraft/blob/main/docs/PROGRESS.md) is the authority for current counts and validation receipts. -The source snapshot has a recent optimized web distribution whose manifest verifies. The public Pages manifest has different JavaScript, wasm, and index hashes from that local distribution; its deployed source revision is unknown. Do not infer the live build from the source version alone. +The source snapshot has a recent optimized web distribution whose manifest verifies. The public Pages deployment is a **third** bundle: on 2026-11-11 its trunk asset name (`2274ac8c2925b61d`) and wasm digest (`f4f0a3f0…`) matched neither the published v0.2.5 release asset (`f9dbe2f14305b2d9` / `a8a4fa4f…`) nor a rebuild of the v0.2.5 tag (`103c62d3366aef17` / `5934702f…`), so the live build corresponds to no identifiable release and its source revision is unknown. The served `index.html` differs from the build's own only by the substituted asset names and the Cloudflare analytics beacon — that difference is explained. Do not infer the live build from the source version alone: every commit since the v0.2.5 stamp reports version `0.2.5`. The measurement is in [T18](../tasks/T18-release.md#deployment-receipt--2026-11-11). ## Where work stands From fcfe6db10f1f8361e9f43a6a43cca8051eb0782d Mon Sep 17 00:00:00 2001 From: Michael Kinney Date: Wed, 11 Nov 2026 15:46:38 -0600 Subject: [PATCH 2/3] ci: size the pipeline watchdogs from the fleet's measured wall-clock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A job's `timeout:` is a watchdog, not a gate: it kills the container and records `timed_out` with no diagnostic about what was slow. The budgets were sized by guess, and the guess is now falsified by the fleet's own record — a job was killed at 20.2 min and at 20.3 min while still working, and the release job's worst successful run finished 1.7 minutes inside its 90-minute budget. The runner holds 4 concurrent containers on a 16-core host and each compiles the workspace, so a co-tenant wave inflates a job roughly 4x. Measured on 2026-11-11 with 9 runs in flight (idle in parentheses): job budget worst success idle fmt-layers 20m 20.1 min 0.3 min clippy-test 90m 76.9 min 10.3 min wasm-web 60m 45.6 min 9.0 min corpus-golden 30m 19.6 min 2.5 min perf-budgets 20m 14.3 min 3.2 min fuzz 60m 52.9 min 10.6 min generated-docs 20m 16.1 min 2.1 min release 90m 88.3 min 38.8 min Each budget below is ~2.5x the worst successful duration, so a loaded job still fits and a genuinely hung job is still killed: fmt-layers 45m, clippy-test 150m, wasm-web 90m, corpus-golden 45m, perf-budgets 45m, fuzz 120m, generated-docs 45m, release 180m No gate is weakened: the budgets decide when a container is killed, not whether a check passes. Refs #126 Co-Authored-By: Claude Code --- .gitforge.yml | 46 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/.gitforge.yml b/.gitforge.yml index 9015388a..ca1c2777 100644 --- a/.gitforge.yml +++ b/.gitforge.yml @@ -40,6 +40,18 @@ # docker save firecraft-ci-rust:1 | ssh docker load # - Step logs are head-capped: cargo's compile spam comes first, so a # failure late in a long step can be truncated. Keep steps small. +# - A job's `timeout:` is a WATCHDOG, not a gate: it kills the +# container and records `timed_out`, with no diagnostic about what +# was slow. The budgets below are therefore sized from the fleet's +# measured wall-clock, not from a round guess. The runner holds 4 +# concurrent containers on a 16-core host and each compiles the +# workspace, so a co-tenant wave inflates a job roughly 4x: +# fmt-layers measured 0.3 min idle and 20.1 min loaded; perf-budgets +# 3.2 min idle and 14.3 min loaded. Each budget below is ~2.5x the +# worst *successful* duration observed on 2026-11-11 with 9 runs in +# flight, so a loaded job still fits its budget and a genuinely hung +# job is still killed. A budget that a passing job can outrun is +# indistinguishable from a failure (issue #126). # # The image recipe is infrastructure/docker/ci-rust.Dockerfile; rebuild # and re-ship it when a pinned tool version or the Rust minor moves. A @@ -60,7 +72,11 @@ jobs: - name: Layer-table gate # Compiles xtask alone; the gate itself is cargo metadata. run: CARGO_TARGET_DIR=/tmp/ci-target cargo run --locked -p xtask -- layers - timeout: 20m + # 45m: the gate compiles xtask, so it carries the workspace's + # build wave. Measured 20.1 min on 2026-11-11 with 9 runs in + # flight (0.3 min idle); the previous 20m budget killed a run that + # was 0.5 min from finishing. + timeout: 45m - name: clippy-test image: firecraft-ci-rust:1 @@ -76,7 +92,8 @@ jobs: run: CARGO_TARGET_DIR=/tmp/ci-target cargo test --workspace --locked --no-fail-fast -- --test-threads=2 # The egui/eframe graph is the bulk of the workspace; the wall # absorbs co-tenant build waves on these shared hosts. - timeout: 90m + # 150m: measured 76.9 min on 2026-11-11 with 9 runs in flight. + timeout: 150m - name: wasm-web image: firecraft-ci-rust:1 @@ -89,7 +106,8 @@ jobs: # the image, the fwpng smoke suite executes for real (no # compile-only downgrade), and trunk builds the web bundle. run: CARGO_TARGET_DIR=/tmp/ci-target cargo run --locked -p xtask -- wasm - timeout: 60m + # 90m: measured 45.6 min on 2026-11-11 with 9 runs in flight. + timeout: 90m - name: corpus-golden image: firecraft-ci-rust:1 @@ -99,7 +117,8 @@ jobs: run: CARGO_TARGET_DIR=/tmp/ci-target cargo run --locked -p xtask -- corpus - name: Golden render pins + determinism run: CARGO_TARGET_DIR=/tmp/ci-target cargo run --locked -p xtask -- golden - timeout: 30m + # 45m: measured 19.6 min on 2026-11-11 with 9 runs in flight. + timeout: 45m - name: perf-budgets image: firecraft-ci-rust:1 @@ -110,7 +129,11 @@ jobs: # fleet. The report line names the host, so a trip is # diagnosable as regression vs environment shift. run: CARGO_TARGET_DIR=/tmp/ci-target cargo run --locked -p xtask -- perf - timeout: 20m + # 45m: measured 14.3 min on 2026-11-11 with 9 runs in flight, but + # two runs were killed at the previous 20m budget while still + # working (one on 2026-11-11 at 20.2 min, one at 20.3 min). The + # budget must exceed what a passing job can take. + timeout: 45m - name: fuzz image: firecraft-ci-rust:1 @@ -131,7 +154,9 @@ jobs: # 30m proved too tight: a cold sandbox target dir spends ~20m # compiling before fuzz-quick starts (dfd7f46 run was killed by # the watchdog 10m into the campaign itself). - timeout: 60m + # 120m: measured 52.9 min on 2026-11-11 with 9 runs in flight, so + # the 60m budget left less than a quarter of an hour of margin. + timeout: 120m - name: generated-docs image: firecraft-ci-rust:1 @@ -173,7 +198,8 @@ jobs: CARGO_TARGET_DIR=/tmp/ci-target cargo run --locked -p xtask -- panel-snapshot cmp /tmp/parity.committed docs/planning/generated/parity.md cmp /tmp/scorecard.committed docs/planning/generated/scorecard.md - timeout: 20m + # 45m: measured 16.1 min on 2026-11-11 with 9 runs in flight. + timeout: 45m - name: release image: firecraft-ci-rust:1 @@ -267,4 +293,8 @@ jobs: # directory, so verify from inside each one. (cd artifacts/web && sha256sum -c MANIFEST.sha256) (cd artifacts/native && sha256sum -c MANIFEST.sha256) - timeout: 90m + # 180m: measured 88.3 min on 2026-11-11 with 9 runs in flight — + # 1.7 minutes inside the previous 90m budget. The release job is + # the longest in the chain and runs last, when the fleet is most + # loaded, so it is the job most likely to be killed by its budget. + timeout: 180m From eceb0a2537d01cc1ca7c075f25014df9cf37c235 Mon Sep 17 00:00:00 2001 From: Michael Kinney Date: Thu, 12 Nov 2026 00:40:16 -0600 Subject: [PATCH 3/3] docs(release): the task board and the progress snapshot carry the measurement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The board's deployment row still claimed the live site's JavaScript, wasm and `appinfo` "match" the v0.2.5 release bytes — the inference the digest receipt on T18 falsifies. The row and the two progress claims now state what was measured: the live bundle matches neither the published release asset nor a rebuild of the tag, so it corresponds to no identifiable release. Co-Authored-By: Claude Code --- docs/PROGRESS.md | 22 +++++++++++++--------- docs/TASKS.md | 2 +- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index a2db7a87..4a3c2c12 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -13,13 +13,17 @@ - **Release:** `v0.2.5` is the latest tag; `main` carries the shell-lineage train (#107) and the post-train dependency work - (#117) beyond it. The live Pages deployment was reconciled on - 2026-11-11 (#104): its JavaScript, wasm and `appinfo` match the - released v0.2.5 web bytes; `host.html`/`index.html` differ only by - Cloudflare's injected analytics beacon. Decision recorded in #104: - the release bytes are the source of truth; republishing from the - release artifact is the pending next step (T18 row in - [`TASKS.md`](TASKS.md)). + (#117) beyond it. The live Pages deployment was measured on + 2026-11-11: its trunk asset name and wasm digest match neither the + published v0.2.5 release asset nor a rebuild of the v0.2.5 tag, so + the live build corresponds to no identifiable release and its source + revision is unknown. The earlier reconciliation (#104) inferred a + match from equal file sizes and an identical `appinfo.json`; the + digest measurement falsifies it. The served `index.html` differs + only by the substituted asset names and Cloudflare's injected + analytics beacon — that difference is explained. Republishing from a + named commit, with its digests recorded, is the pending next step + (T18 row in [`TASKS.md`](TASKS.md)). - **Build evidence:** the earlier optimized `dist/web/` distribution, built 2026-11-04 16:45 local time, had a 21,274,323-byte wasm (below the 21,500,000-byte ceiling). It was built from `56ee1bf`, @@ -77,8 +81,8 @@ | Earlier optimized `dist/web/` | 2026-11-04 16:45 local; 21,274,323-byte wasm; manifest verified | Build from `56ee1bf`, whose Git tree matches `5fac7a2`; deployment comparison and earlier bridge smoke | | T22 optimized rebuild in `dist/web/` | 2026-11-04; 21,271,259-byte wasm | `5fac7a2` plus dock and inspector changes; six browser captures and interaction checks in [T22](tasks/T22-screenshot-ui.md#receipts--2026-11-04) | | Raw `web/dist/` | 2026-11-04 18:21 local; 95,583,065-byte wasm | Development output; not the optimized distribution | -| Browser smoke | Earlier optimized artifact: bridge smoke in session history; T22 rebuild: six captures and interaction checks in T22 | Local end-to-end proof for each recorded artifact; the live deployment was reconciled against the release bytes on 2026-11-11 (#104) | -| Live Pages | Reconciled 2026-11-11 (#104) | JS/wasm/appinfo match the released v0.2.5 web bytes; `host.html`/`index.html` differ only by Cloudflare's injected beacon; release bytes are the source of truth | +| Browser smoke | Earlier optimized artifact: bridge smoke in session history; T22 rebuild: six captures and interaction checks in T22 | Local end-to-end proof for each recorded artifact; the live deployment was measured against the release bytes on 2026-11-11 — it is neither the release asset nor a rebuild of the tag | +| Live Pages | Measured 2026-11-11 by digest | The live bundle's trunk asset name (`2274ac8c2925b61d`) and wasm digest (`f4f0a3f0…`) match neither the published v0.2.5 release asset nor a rebuild of the v0.2.5 tag; the live build corresponds to no identifiable release and its source revision is unknown. `index.html` differs only by the substituted asset names and Cloudflare's injected beacon | diff --git a/docs/TASKS.md b/docs/TASKS.md index 0760f2b0..f16e7781 100644 --- a/docs/TASKS.md +++ b/docs/TASKS.md @@ -46,7 +46,7 @@ | P0 | Phase 22: CS6 shell fidelity and screenshot audit | In progress | [T22](tasks/T22-screenshot-ui.md) — 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) | | P1 | Phase 20: daily-driver editing features | **In progress — 20.1–20.3, 20.5–20.7 on main; 20.4 in PR** | In-canvas text editing landed 2026-11-06 ([T20](tasks/T20-text-editing.md)); multi-document tabs (20.5), history rewind (20.6), and slice/hotspot behavior editing with HTML preview (20.7) landed 2026-11-11 in #107; the Optimize panel (20.2, [T7](tasks/T7-optimize-panel.md)) and the animation timeline (20.3, [T8](tasks/T8-animation-timeline.md)) landed 2026-11-11 in #118. Mask stack (20.4) is in open PR #113. Do not count open PR work as shipped until merged and validated. [details](NEXT.md#phase-20--daily-driver-features-p1) | | P1 | Phase 21: modern differentiators | **Done — all items landed 2026-11-11** | Autosave/crash recovery, HiDPI, command palette v2 (fuzzy scoring, recents, Save Steps As Command), preferences, accesskit accessibility and in-app scripting (`.jsf`/`.fwsteps`) all landed with the shell-lineage train (#107, PR #15); [details](NEXT.md#phase-21--modern-differentiators) | -| P1 | Reconcile live web deployment | **Reconciled — republish pending** | #104 (`docs/web-deployment-reconciliation`) reconciled the live site against v0.2.5: the JS, wasm and `appinfo` match; `host.html`/`index.html` fail the site's own manifest because Cloudflare injects an analytics beacon after it is written. Decision recorded there: the release bytes are the source of truth. Next: republish from the release artifact. See [T18](tasks/T18-release.md). | +| P1 | Reconcile live web deployment | **Measured — the live site is not the v0.2.5 release bytes** | Measured 2026-11-11 by digest: the live bundle's trunk asset name (`2274ac8c2925b61d`) and wasm digest (`f4f0a3f0…`) match neither the published v0.2.5 release asset (`f9dbe2f14305b2d9` / `a8a4fa4f…`) nor a rebuild of the v0.2.5 tag (`103c62d3366aef17` / `5934702f…`), so the live build corresponds to no identifiable release and its source revision is unknown. The earlier reconciliation (#104) inferred a match from equal file *sizes* and an identical `appinfo.json`; the digest measurement falsifies it. The served `index.html` difference is explained (substituted asset names plus the Cloudflare analytics beacon). **What would close it:** deploy a bundle whose asset name and wasm digest are recorded here, from a named commit — a production publish, gated for explicit maintainer approval. See [T18](tasks/T18-release.md). | | P2 | Next-release asset breadth and platform verification | Open | [T23](tasks/T23-release-assets.md); recipes exist, but non-Linux artifacts need real platform build/launch receipts. | | P2 | Coverage and test quality ratchet | Open | Replace the old aspirational 99% label with measured per-crate targets and snapshots; [NEXT.md](NEXT.md). | | P2 | Aegis gate: content-keyed baseline, so a doc edit no longer fails the gate | **Done — landed on `main`** | [decisions/0074-aegis-content-keyed-baseline.md](decisions/0074-aegis-content-keyed-baseline.md). The baseline keys on content (pattern, file, digest) rather than on line, and every rule class stays armed. |