Add C4 release evidence dashboard - #87
Conversation
Replace narrativeState.hooks z.array(z.any()) with a strict NarrativeHookSchema verified against the NarrativeHook interface and both writers (franchise-week-helpers weekly refresh, convention-save seed). type stays a free-form string: hooks-engine categories are an open set. Adds 5 schema tests.
New scripts/evidence-dashboard.mjs (pnpm evidence:dashboard) refreshes a marked Evidence Dashboard section at the top of STATUS.md: remote CI gate per-check conclusions, G6 route-sweep contract presence, Ecology Lab nightly scoreboard, local gate contract, bundle size vs ceiling, Math.random scan, opt-in playtest anomalies, and ledger freshness. --check exits non-zero on red rows for future CI use.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 60ddf16d1c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const job = jobs.find((j) => j.name === name); | ||
| return `${name}:${job ? job.conclusion : '?'}`; | ||
| }); | ||
| const status = ci.conclusion === 'success' ? OK : ci.status !== 'completed' ? WARN : FAIL; |
There was a problem hiding this comment.
Require named CI jobs before marking the gate green
When the CI workflow succeeds after one of the expected jobs is renamed or removed, this still marks the remote gate ✅ even though the evidence text reports that job as ?. Because this row is meant to attest specifically to test, determinism-gate, and release-gate, derive the status from all three named job conclusions as well as the workflow conclusion; otherwise a weakened CI contract is displayed as healthy.
Useful? React with 👍 / 👎.
| const gateList = tryExec(process.execPath, ['scripts/release-gate.mjs', '--list']); | ||
| const hasSweep = /g6|route sweep/i.test(gateList.out); |
There was a problem hiding this comment.
Match the visual sweep rather than any G6 step
If the visual route-sweep steps are removed while any other G6 step remains, this broad regex still turns the route-sweep row green. The inspected release plan contains unrelated g6-chip, g6-ux, and g6-mobile steps, so the check should look for the specific g6-visual-sweep-* IDs or g6-visual group before claiming that the visual/playability sweep remains in the contract.
Useful? React with 👍 / 👎.
| evidence: gateList.ok && hasSweep | ||
| ? 'G6 sweep step present in the 37-step release contract (runs inside every release-gate, local + remote)' | ||
| : 'G6 sweep step NOT found in release-gate contract', | ||
| pointer: 'node scripts/release-gate.mjs --only browser (local sweep)', |
There was a problem hiding this comment.
Point the route-sweep refresh at its actual gate group
The advertised refresh command does not run the G6 route sweep: inspecting scripts/release-gate.mjs --only browser --list selects only built-page-smoke, g1-full-setup-desktop, and g5-cba-commissioner-browser. The visual sweep is in the g6-visual group, with its mobile counterpart under g6-mobile, so following this dashboard instruction cannot refresh the evidence named by the row.
Useful? React with 👍 / 👎.
| pointer: 'pnpm --filter @mfd/web build && bash scripts/check-bundle-size.sh', | ||
| }); | ||
| } else { | ||
| const chunk = readdirSync(distDir).find((f) => /^engine-.*\.js$/.test(f) && !f.startsWith('engine-content-')); |
There was a problem hiding this comment.
Reject multiple engine chunks before reporting bundle health
When a build contains multiple non-content engine-*.js chunks, find silently measures whichever filename readdirSync returns first and may report ✅. The authoritative scripts/check-bundle-size.sh explicitly fails unless exactly one matching engine chunk exists, so this dashboard can disagree with the real release gate after an accidental chunk split; count the candidates and fail the row unless there is exactly one.
Useful? React with 👍 / 👎.
Understanding
C4 (Fable board #14): "is the ledger current?" was ambiguous — gate results, bundle size, route sweep, and anomaly counts lived in different places with no single freshness view.
What changed
scripts/evidence-dashboard.mjs(alsopnpm evidence:dashboard) refreshes a marker-delimited## Evidence Dashboardsection at the top ofSTATUS.md. Idempotent: reruns replace only the marked section.gh), G6 route-sweep presence in the 37-step release contract, Ecology Lab nightly scoreboard, local release-gate contract integrity, engine-chunk bundle size vs the 312 KB ceiling (when a local build exists), Math.random determinism scan, opt-in fast-tier playtest anomalies (--with-playtest), and run-ledger freshness.--checkexits non-zero on any red row (future CI hook).STATUS.mdgains the dashboard section plus a run-ledger entry (which also flips the freshness row green).package.jsongainsevidence:dashboard.What did not change
Verification
node scripts/evidence-dashboard.mjs10d9da3; idempotent across reruns ✅node scripts/evidence-dashboard.mjs --checkRemote 37-step gate runs on this PR as a required check.
Rollback
Revert this commit; removes the script and STATUS.md section only.