@techierathore/techieflow 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/.claude/commands/TechieFlow/agents/analyst.md +5 -13
  2. package/.claude/commands/TechieFlow/agents/architect.md +1 -16
  3. package/.claude/commands/TechieFlow/agents/flow-master.md +42 -215
  4. package/.claude/commands/TechieFlow/agents/verifier.md +24 -64
  5. package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +24 -117
  6. package/.claude/commands/TechieFlow/tasks/_owner-language.md +26 -0
  7. package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +20 -84
  8. package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +10 -74
  9. package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +20 -58
  10. package/.claude/commands/TechieFlow/tasks/amend-docs.md +21 -143
  11. package/.claude/commands/TechieFlow/tasks/build-phase.md +14 -224
  12. package/.claude/commands/TechieFlow/tasks/create-deep-research-prompt.md +4 -0
  13. package/.claude/commands/TechieFlow/tasks/create-doc.md +4 -0
  14. package/.claude/commands/TechieFlow/tasks/create-project-brief.md +22 -0
  15. package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +31 -505
  16. package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +28 -213
  17. package/.claude/commands/TechieFlow/tasks/deploy-checklist.md +19 -0
  18. package/.claude/commands/TechieFlow/tasks/devguide.md +10 -219
  19. package/.claude/commands/TechieFlow/tasks/facilitate-brainstorming-session.md +4 -0
  20. package/.claude/commands/TechieFlow/tasks/fix-issues.md +10 -129
  21. package/.claude/commands/TechieFlow/tasks/generate-html.md +5 -1
  22. package/.claude/commands/TechieFlow/tasks/handoff-phase.md +18 -16
  23. package/.claude/commands/TechieFlow/tasks/log-miss.md +19 -158
  24. package/.claude/commands/TechieFlow/tasks/metrics-report.md +3 -1
  25. package/.claude/commands/TechieFlow/tasks/mockups.md +12 -117
  26. package/.claude/commands/TechieFlow/tasks/refresh-status.md +9 -130
  27. package/.claude/commands/TechieFlow/tasks/split-brd.md +10 -126
  28. package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +14 -0
  29. package/.claude/commands/TechieFlow/tasks/triage-issues.md +11 -152
  30. package/.claude/commands/TechieFlow/tasks/verify-phase.md +36 -572
  31. package/.opencode/plugin/techieflow.js +40 -13
  32. package/.tfcore/agents/analyst.md +5 -13
  33. package/.tfcore/agents/architect.md +1 -16
  34. package/.tfcore/agents/flow-master.md +42 -215
  35. package/.tfcore/agents/verifier.md +24 -64
  36. package/.tfcore/core-config.yaml +10 -0
  37. package/.tfcore/hooks/block-git.sh +4 -4
  38. package/.tfcore/hooks/guard-artifacts.sh +2 -2
  39. package/.tfcore/hooks/guard-build.sh +76 -0
  40. package/.tfcore/hooks/guard-db.sh +90 -0
  41. package/.tfcore/hooks/guard-metrics.sh +68 -0
  42. package/.tfcore/hooks/guard-status-html.sh +116 -71
  43. package/.tfcore/hooks/guard-status.sh +43 -16
  44. package/.tfcore/hooks/guard-verify-deps.sh +124 -0
  45. package/.tfcore/hooks/guard-verify.sh +70 -48
  46. package/.tfcore/hooks/sweep-artifacts.sh +2 -2
  47. package/.tfcore/install-manifest.yaml +0 -18
  48. package/.tfcore/routing.yaml +86 -14
  49. package/.tfcore/standards/coding-standards-core.md +61 -0
  50. package/.tfcore/standards/coding-standards-dotnet.md +93 -0
  51. package/.tfcore/tasks/_metrics-emit-gate.md +24 -117
  52. package/.tfcore/tasks/_owner-language.md +26 -0
  53. package/.tfcore/tasks/_smoke-test-policy.md +20 -84
  54. package/.tfcore/tasks/_status-update-gate.md +10 -74
  55. package/.tfcore/tasks/_yolo-mode.md +20 -58
  56. package/.tfcore/tasks/amend-docs.md +21 -143
  57. package/.tfcore/tasks/build-phase.md +14 -224
  58. package/.tfcore/tasks/create-deep-research-prompt.md +4 -0
  59. package/.tfcore/tasks/create-doc.md +4 -0
  60. package/.tfcore/tasks/create-project-brief.md +22 -0
  61. package/.tfcore/tasks/day1-brownfield.md +31 -505
  62. package/.tfcore/tasks/day1-greenfield.md +28 -213
  63. package/.tfcore/tasks/deploy-checklist.md +19 -0
  64. package/.tfcore/tasks/devguide.md +10 -219
  65. package/.tfcore/tasks/facilitate-brainstorming-session.md +4 -0
  66. package/.tfcore/tasks/fix-issues.md +10 -129
  67. package/.tfcore/tasks/generate-html.md +5 -1
  68. package/.tfcore/tasks/handoff-phase.md +18 -16
  69. package/.tfcore/tasks/log-miss.md +19 -158
  70. package/.tfcore/tasks/metrics-report.md +3 -1
  71. package/.tfcore/tasks/mockups.md +12 -117
  72. package/.tfcore/tasks/refresh-status.md +9 -130
  73. package/.tfcore/tasks/split-brd.md +10 -126
  74. package/.tfcore/tasks/triage-and-fix.md +14 -0
  75. package/.tfcore/tasks/triage-issues.md +11 -152
  76. package/.tfcore/tasks/verify-phase.md +36 -572
  77. package/.tfcore/telemetry/SCHEMA.md +100 -8
  78. package/.tfcore/telemetry/tf-metrics.sh +564 -32
  79. package/.tfcore/templates/stack-defaults/dotnet.md +35 -0
  80. package/.tfcore/templates/stack-questions.md +77 -0
  81. package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +3 -4
  82. package/.tfcore/templates/v4custom/app-architecture-tmpl.md +107 -115
  83. package/.tfcore/templates/v4custom/app-brd-tmpl.md +108 -184
  84. package/.tfcore/templates/v4custom/app-brief-tmpl.md +65 -0
  85. package/.tfcore/templates/v4custom/app-checklist-tmpl.md +48 -73
  86. package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +1 -1
  87. package/.tfcore/templates/v4custom/app-coding-standards-tmpl.md +52 -0
  88. package/.tfcore/templates/v4custom/app-decision-request-tmpl.md +85 -0
  89. package/.tfcore/templates/v4custom/app-deployment-checklist-tmpl.md +86 -0
  90. package/.tfcore/templates/v4custom/app-devguide-tmpl.md +69 -146
  91. package/.tfcore/templates/v4custom/app-library-feedback-tmpl.md +86 -31
  92. package/.tfcore/templates/v4custom/app-phases-tmpl.md +35 -0
  93. package/.tfcore/templates/v4custom/app-productguide-tmpl.md +38 -35
  94. package/.tfcore/templates/v4custom/app-project-status-tmpl.md +63 -73
  95. package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +62 -37
  96. package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +69 -54
  97. package/.tfcore/templates/v4custom/build-invocation-ladder.md +17 -180
  98. package/.tfcore/templates/v4custom/build-subagent-prompt.md +17 -0
  99. package/.tfcore/templates/v4custom/html-render-shell.md +1 -1
  100. package/.tfcore/templates/v4custom/metrics-report-template.md +2 -2
  101. package/.tfcore/user-guide.md +0 -23
  102. package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
  103. package/.tfcore/utils/__pycache__/tf-checklist-edit.cpython-312.pyc +0 -0
  104. package/.tfcore/utils/__pycache__/tf-codex-bind.cpython-312.pyc +0 -0
  105. package/.tfcore/utils/__pycache__/tf-codex-telemetry.cpython-312.pyc +0 -0
  106. package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
  107. package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
  108. package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
  109. package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
  110. package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
  111. package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
  112. package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
  113. package/.tfcore/utils/__pycache__/tf-perf-grade.cpython-312.pyc +0 -0
  114. package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
  115. package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
  116. package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
  117. package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
  118. package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
  119. package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
  120. package/.tfcore/utils/tf-brd-status.py +176 -0
  121. package/.tfcore/utils/tf-brd-status.sh +19 -0
  122. package/.tfcore/utils/tf-build-list.py +211 -0
  123. package/.tfcore/utils/tf-build-list.sh +21 -0
  124. package/.tfcore/utils/tf-build.sh +143 -0
  125. package/.tfcore/utils/tf-cdp-relay.ps1 +64 -0
  126. package/.tfcore/utils/tf-checklist-edit.py +133 -0
  127. package/.tfcore/utils/tf-day1-files.py +208 -0
  128. package/.tfcore/utils/tf-day1-files.sh +20 -0
  129. package/.tfcore/utils/tf-devguide-list.py +249 -0
  130. package/.tfcore/utils/tf-devguide-list.sh +18 -0
  131. package/.tfcore/utils/tf-doc-check.py +1369 -0
  132. package/.tfcore/utils/tf-doc-check.sh +29 -0
  133. package/.tfcore/utils/tf-emit.sh +545 -18
  134. package/.tfcore/utils/tf-fix-close.sh +115 -0
  135. package/.tfcore/utils/tf-gitignore-audit.sh +50 -3
  136. package/.tfcore/utils/tf-goal.sh +284 -66
  137. package/.tfcore/utils/tf-harness.sh +4 -9
  138. package/.tfcore/utils/tf-log-miss.py +197 -0
  139. package/.tfcore/utils/tf-log-miss.sh +14 -0
  140. package/.tfcore/utils/tf-misses-md.py +208 -0
  141. package/.tfcore/utils/tf-misses-md.sh +14 -0
  142. package/.tfcore/utils/tf-mockups-locate.py +66 -0
  143. package/.tfcore/utils/tf-mockups-locate.sh +18 -0
  144. package/.tfcore/utils/tf-model-pick.sh +475 -0
  145. package/.tfcore/utils/tf-opencode-audit.py +23 -7
  146. package/.tfcore/utils/tf-perf-grade.py +97 -0
  147. package/.tfcore/utils/tf-perf-grade.sh +12 -0
  148. package/.tfcore/utils/tf-phase.sh +92 -0
  149. package/.tfcore/utils/tf-render-html.py +20 -0
  150. package/.tfcore/utils/tf-routing-bind.sh +97 -6
  151. package/.tfcore/utils/tf-routing.sh +13 -7
  152. package/.tfcore/utils/tf-selfcheck.sh +323 -0
  153. package/.tfcore/utils/tf-split-brd.py +309 -0
  154. package/.tfcore/utils/tf-split-brd.sh +22 -0
  155. package/.tfcore/utils/tf-status-evidence.sh +68 -0
  156. package/.tfcore/utils/tf-status-facts.py +358 -0
  157. package/.tfcore/utils/tf-status-facts.sh +20 -0
  158. package/.tfcore/utils/tf-triage.py +243 -0
  159. package/.tfcore/utils/tf-triage.sh +16 -0
  160. package/.tfcore/utils/tf-verify-boot.sh +250 -0
  161. package/.tfcore/utils/tf-verify-emit.sh +110 -0
  162. package/.tfcore/utils/tf-verify-env.sh +88 -0
  163. package/.tfcore/utils/tf-verify-list.py +270 -0
  164. package/.tfcore/utils/tf-verify-list.sh +13 -0
  165. package/.tfcore/utils/tf-verify-screens.mjs +355 -0
  166. package/.tfcore/utils/tf-verify-screens.sh +31 -0
  167. package/.tfcore/utils/tf-verify-tests.sh +166 -0
  168. package/.tfcore/utils/tf-verify-verdict.py +292 -0
  169. package/.tfcore/utils/tf-verify-verdict.sh +15 -0
  170. package/.tfcore/utils/tf-yolo.sh +59 -0
  171. package/README.md +120 -1145
  172. package/docs/TechieFlow-Installation.md +1 -5
  173. package/opencode.jsonc +37 -33
  174. package/package.json +1 -5
  175. package/scripts/install.mjs +56 -46
  176. package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +0 -117
  177. package/.claude/commands/TechieFlow/tasks/author-brd.md +0 -79
  178. package/.claude/commands/TechieFlow/tasks/document-project.md +0 -343
  179. package/.claude/commands/TechieFlow/tasks/execute-checklist.md +0 -86
  180. package/.claude/commands/TechieFlow/tasks/index-docs.md +0 -173
  181. package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +0 -75
  182. package/.claude/commands/TechieFlow/tasks/shard-doc.md +0 -185
  183. package/.codex/config.toml +0 -46
  184. package/.codex/hooks.json +0 -63
  185. package/.codex/rules/techieflow.rules +0 -8
  186. package/.tfcore/hooks/codex-adapter.py +0 -248
  187. package/.tfcore/tasks/advanced-elicitation.md +0 -117
  188. package/.tfcore/tasks/author-brd.md +0 -79
  189. package/.tfcore/tasks/document-project.md +0 -343
  190. package/.tfcore/tasks/execute-checklist.md +0 -86
  191. package/.tfcore/tasks/index-docs.md +0 -173
  192. package/.tfcore/tasks/kb-mode-interaction.md +0 -75
  193. package/.tfcore/tasks/shard-doc.md +0 -185
  194. package/.tfcore/templates/architecture-tmpl.yaml +0 -650
  195. package/.tfcore/templates/brownfield-architecture-tmpl.yaml +0 -476
  196. package/.tfcore/templates/competitor-analysis-tmpl.yaml +0 -306
  197. package/.tfcore/templates/front-end-architecture-tmpl.yaml +0 -218
  198. package/.tfcore/templates/fullstack-architecture-tmpl.yaml +0 -823
  199. package/.tfcore/templates/market-research-tmpl.yaml +0 -252
  200. package/.tfcore/templates/project-brief-tmpl.yaml +0 -221
  201. package/.tfcore/utils/techieflow-doc-template.md +0 -325
  202. package/.tfcore/utils/tf-codex-bind.py +0 -196
  203. package/.tfcore/utils/tf-codex-telemetry.py +0 -60
  204. package/WORKFLOW.html +0 -1986
@@ -1,579 +1,43 @@
1
1
  # verify-phase
2
2
 
3
- Autonomously verify implemented requirements against their numbered REQ IDs. The AGENT performs every step itself in its own shell. The user runs nothing.
3
+ `*verify {scope} {App}` proves every checklist row in scope against the running application and writes the verdicts. Scope is `ui`, `functional`, `all` or a list of ids; a caller that chains this task passes its scope and nothing is asked. YOLO is its default mode. The application runs on this machine, never on a cloud host. This task never edits source, the BRD or the mockups. It writes tests under `tests/verify/`, evidence under `tests/.artifacts/verify/`, the ledger `docs/.last-verify.json`, and the checklist cells through the verdict script only: a `Verified` written by hand is refused by the hook.
4
4
 
5
- ## Purpose
5
+ First: `bash .tfcore/utils/tf-phase.sh start verify-phase {App}` prints the start time; keep it. Then `bash .tfcore/utils/tf-yolo.sh on --source verify` unless YOLO is already on.
6
6
 
7
- Replace manual verification. Given a scope, prove whether each numbered requirement is actually implemented and behaving, by running real headless browser tests and unit tests against the running application. Verdicts are written into the owning checklist's Requirements Status table (the single source of truth) plus a console miss list.
7
+ ## The seven checks
8
8
 
9
- ## Inputs
9
+ Applied to every row in this order; the first that fails is the row's verdict.
10
10
 
11
- - `{AppName}` — required for the checklist path; resolve from the command argument, `core-config.yaml` → `customTechnicalDocuments`, or the `docs/{AppName}-Checklist.md` filename. Ask only if it cannot be resolved.
12
- - `{scope}` — see Modes.
13
- - **The DevGuide** (`docs/{AppName}-DevGuide.md`, or the split set under `docs/devguides/`) — the screen-by-screen, **per-control** test map. The verifier uses it to know which controls each screen must render (the render gate, §4a). If it is missing, the verifier still runs but notes that no control map exists and recommends `*devguide {AppName}`; if it exists, the verifier **refreshes its observed render-status tags** for every screen it exercises (§6b) — this is the DevGuide ⇄ Checklist ⇄ Verifier loop.
14
-
15
- ## Modes
16
-
17
- - **Full mode** (default, `*verify {scope}`): run the entire pipeline below. There is **one checklist** (`docs/{AppName}-Checklist.md`); scopes filter its Requirements Status table by REQ prefix, they do NOT pick a separate file. Scope values:
18
- - `ui` — all `REQ-UI-*` rows.
19
- - `functional` — all `REQ-FN-*` / `REQ-NFR-*` / `REQ-RAG-*` rows.
20
- - `all` — every row.
21
- - An explicit ID list (`REQ-UI-007,REQ-UI-013`) — just those rows.
22
- - `phase-N` — LEGACY fallback for pre-split projects: grade the BRD's Phase-N numbered IDs (see §0 legacy path).
23
- - **Setup-only mode** (`*setup`): execute only Section 1, then HALT.
24
-
25
- ## Local-only deployment policy (CRITICAL — read before every run)
26
-
27
- The verifier MUST run the application **LOCALLY**. Never propose, suggest, or initiate any of the following to "make Playwright work":
28
-
29
- - Cloudflare / Cloudflare Pages / Cloudflare Tunnels
30
- - Vercel / Netlify / Render / Fly.io
31
- - Azure App Service / AWS / GCP / any cloud deploy
32
- - Pushing to a remote branch so a preview environment builds
33
- - Asking the user to "deploy somewhere I can reach"
34
-
35
- The user runs the harness (Claude Code or OpenCode, both in WSL) on their own machine. Local boot is always possible. If you find yourself drafting "let me deploy this to X first" — STOP, that is a banned escape hatch. The user has explicitly called this out as an "idiotic option" — do not repeat it.
36
-
37
- **Local device hosts are NOT cloud.** Driving the MAUI Android head via the Appium server on the **Windows host** and the MAUI iOS / Mac Catalyst heads via the Appium server on the **LAN Mac** (`runtimeVerification.appium` in `core-config.yaml`, see §3b) is local infrastructure the owner set up once (`WORKFLOW.html §0b`) — it is the native analogue of headless Playwright, not a remote deploy. Use it. Only the cloud-deploy escape hatches above are banned.
38
-
39
- If the WSL-side `dotnet run` is unreachable by Playwright (port collision, WSL networking quirks, missing browser deps), the correct escalation order is:
40
-
41
- 1. **Use the platform-specific bridge.** In WSL, rung #4 `cmd.exe /c "dotnet run --project ..."` launches Windows-side dotnet. In OpenCode Docker, use `winrun "dotnet run --project ..."`; direct `cmd.exe` is expected to be absent. The Windows-side port is reachable through the configured Docker host bridge. Try the appropriate bridge BEFORE asking the user anything.
42
- 2. **Try a different port** if rung #2 is on a busy port (5099 → 5599).
43
- 3. **Ask the user to run it locally themselves.** Output the EXACT two-line shell recipe and a `go` prompt:
44
-
45
- ```
46
- I can't reach the app from Playwright in this environment. Please run these two
47
- commands in separate terminals on the Windows side (or in your usual dev setup):
48
-
49
- Terminal 1 (API): dotnet run --project src/{AppName}.Api --urls http://localhost:5100
50
- Terminal 2 (Web): dotnet run --project src/{AppName}.Web --urls http://localhost:5099
51
-
52
- When both show "Now listening on", reply `go` and I'll run the verifier against
53
- those URLs.
54
- ```
55
-
56
- Wait for `go`. Then point Playwright at `http://localhost:5099` (and `http://localhost:5100` for API checks). Do NOT skip verification. Do NOT propose cloud anything.
57
-
58
- This applies to EVERY phase task that boots an app (the `build-phase` self-smoke, the `fix-issues` repro, the `devguide` OBSERVE pass, and this verifier). They all share this policy.
59
-
60
- ## SEQUENTIAL Execution (do not proceed until current section is complete)
61
-
62
- ### 0. Load inputs and build the working list
63
-
64
- - **First action, before anything else:** run `date -u +%Y-%m-%dT%H:%M:%SZ` and keep the value. It is this run's `started` **and** its `run_id` for the §6a telemetry emit. You cannot reconstruct it at the end, and an invented duration is a fabricated measurement.
65
- - Load `.tfcore/core-config.yaml`; resolve `{AppName}` per Inputs above. Note any `runtimeVerification.appium` endpoints — they tell you which MAUI native heads (Android/iOS/Mac Catalyst) this app ships and how to reach them (§3b).
66
- - Determine the scope from the command argument (`ui` / `functional` / `all` / explicit REQ IDs / legacy `phase-N`). **When this task is chained from `build-phase` or `fix-issues`, the caller has already stated the scope — use it and ask nothing.** Only if invoked standalone with NO scope: ask the user once "Verify which scope — ui, functional, all, or specific REQ IDs?" — this is the ONLY question you may ask.
67
- - **Checklist path (normal):** open the one checklist `docs/{AppName}-Checklist.md`. From the **Requirements Status** table, filter rows by the scope's REQ prefix (`REQ-UI-*` for `ui`; `REQ-FN/NFR/RAG-*` for `functional`; all rows for `all`) and build the working list `[{id, text, status, type-guess, perf-budget}]` (type-guess ∈ {ui, behavioral, backend-logic, nonfunctional}; pull each REQ's acceptance criteria from its Details anchor section). While reading the acceptance criteria, capture any `perf-budget:` line verbatim — that, and only that, is what makes a REQ perf-gradeable in §4c. Most REQs will have none; that is expected and is not a gap to fill.
68
- - **`Done (pre-existing)` is NOT a free pass — it is an unverified migrated claim.** Do NOT skip those rows. They were carried over from a dev-plan and have **never been runtime-verified** — this is exactly how blank-rendering screens slipped through (a `Done (pre-existing)` home page with an empty table and a blank list). Every in-scope row, **including `Done (pre-existing)`**, gets the **§4a DevGuide render sweep** at minimum (load its screens, confirm every control actually renders its data). Only `N/A` rows are skipped. Rows already `Verified` get a fast re-confirm (render sweep + re-run their test); `Done (pre-existing)` that passes the render sweep gets a `runtime render-confirmed {date}` remark and may stay Done; anything that renders blank/empty/errors → `Needs re-verify`/`FAIL` (§6).
69
- - **Load the DevGuide map.** Read the DevGuide (`docs/{AppName}-DevGuide.md`, or the split set under `docs/devguides/`) and, for each in-scope screen, extract the list of controls + their expected data source from the *Controls*/*Data lineage* tables. This is the checklist of what must render in §4a. If no DevGuide exists, note it and fall back to acceptance-criteria-only grading, and recommend `*devguide {AppName}` in the report.
70
- - **Legacy BRD path (only for `phase-N` scope on pre-split projects):** locate the BRD (`customTechnicalDocuments`, then `docs/{AppName}-BRD*.md`, then `docs/brd*.md`/`docs/BRD*.md`) and extract that phase's numbered IDs (`BRD-12`, `REQ-12`, `FR-12`, `[BRD-12]`). If no numbered IDs exist, STOP auto-grading and tell the user: "This phase has no numbered, checkable requirements, so coverage cannot be graded by ID. Number them as BRD-N (or run `*split-brd`) and re-run." Then offer a loose best-effort prose verification.
71
- - If the working list is empty (all rows `N/A`), report "Nothing to verify — all rows N/A" and skip to §8. (Note: `Verified` and `Done (pre-existing)` rows are NOT empty-list — they still get the §4a render sweep.)
72
-
73
- ### 1. Ensure the verification environment (self-healing)
74
-
75
- Run these checks yourself and act on them. Echo a one-line summary of what you did.
76
-
77
- - If no `package.json` in repo root: `npm init -y`.
78
- - Check Playwright: run `npx playwright --version`. If it errors or is absent: `npm install -D @playwright/test`.
79
- - Check browsers: attempt `npx playwright install chromium`. If it reports missing system libraries (common on a fresh WSL/Ubuntu): run `npx playwright install --with-deps chromium`. If that needs sudo and sudo is unavailable, run `sudo npx playwright install --with-deps chromium`; if sudo also fails, report the single apt command the user must run once and HALT (this is the only situation where a manual step is unavoidable, and it happens at most once per machine).
80
- - **Ensure `playwright.config.ts` at repo root, and ensure it pins `outputDir`.** If absent, create it. If present but missing `outputDir` (or pointing anywhere outside `tests/`), **edit it** — an unpinned config defaults to a repo-root `test-results/`, which is the exact litter this rule exists to stop. Canonical config:
81
-
82
- ```ts
83
- import { defineConfig } from '@playwright/test';
84
- export default defineConfig({
85
- testDir: './tests/verify',
86
- outputDir: './tests/.artifacts/test-results', // ALL run artifacts live under tests/ — never the repo root
87
- reporter: 'line', // if you add 'html', set outputFolder: 'tests/.artifacts/playwright-report'
88
- use: { headless: true, screenshot: 'only-on-failure', trace: 'retain-on-failure' },
89
- });
90
- ```
91
-
92
- - **ARTIFACT-LOCATION RULE (hard, no exceptions).** Everything this run generates that is not a deliverable — **what the tools output** (Playwright output, screenshots, traces, videos, Appium captures, HTML reports) **and what you write to drive them** (throwaway smoke/verify/load/cleanup scripts) — lands under **`tests/.artifacts/`**. Both halves matter: pinning only the output location leaves the agent free to invent a root-level home for its scripts, which is the same bug with a different noun. Consequences you must honor:
93
- - **NEVER create a repo-root sibling directory for run material — not `test-results-<something>/`, not `scripts-<something>/`, not any other suffixed variant.** Per-run/per-cluster directories at the root (`test-results-cluster-a/`, `test-results-<slug>/`, `scripts-cluster-a/`, `scripts-<slug>/`) are BANNED: a suffixed name escapes the ignore pattern written for the unsuffixed one, so it lands in the owner's `git status` as untracked machine output, and it accumulates one directory per run forever with nothing to clean it up. This has actually happened twice — one app ended up with fourteen `test-results-*` dirs, and after those were banned the next fan-out produced four `scripts-cluster-*` dirs instead. **Do not reason by the letter of this list**: any *new* root-level `<name>-<slug>/` you are about to create for a fan-out is the same defect, whatever the noun.
94
- - **The harness enforces this MECHANICALLY** (added 2026-08-25, after TechieBlog accumulated ten root `test-results-*` dirs on top of the two prior recurrences this rule already records): the `.tfcore/hooks/guard-artifacts.sh` PreToolUse hook blocks any Bash command that points `--output` / `--output-dir` at a root-level `test-results*` or `scripts-*`, or `mkdir`s one — in every harness (Claude Code hook, Codex via `codex-adapter.py`, OpenCode via the plugin bridge). A blocked call is **the policy working** — re-run writing under `tests/.artifacts/`, never reword the command to slip past it. `--output tests/.artifacts/<slug>` is allowed by design, as are `tests/`, `docs/screenshots/` and the project's own tracked `scripts/`.
95
- - **Never pass `--output test-results-…`.** The config's `outputDir` is the single answer, and Playwright already namespaces per test *inside* it and **wipes it at the start of every run**, which is what keeps the tree from growing. If a parallel fan-out genuinely needs isolation, the only permitted form is a **subfolder**: `--output tests/.artifacts/<slug>`.
96
- - **A harness script you write is an artifact, not project source — it goes under `tests/.artifacts/harness/`.** A smoke/verify/load/cleanup script you author for this run is scratch: name it per cluster *inside* that folder (`tests/.artifacts/harness/smoke-cluster-b.mjs`), never as a root-level folder per cluster. The repo's own **`scripts/`** directory — release, publish, dev-setup scripts the project owns and tracks — **is not yours**: never write a run harness into it, and never delete it. If a harness turns out to be worth keeping, it has stopped being scratch: promote it to a real spec under `tests/verify/` so the runner owns its config, its output location, and its lifecycle.
97
- - **A harness that drives the Playwright *library* still obeys this rule, and nothing enforces it for you.** `import { chromium } from 'playwright'` does **not** load `playwright.config.ts` — `outputDir` does not apply, and nothing wipes what the script writes. So such a script must place its own captures under `tests/.artifacts/` explicitly, and must **never hardcode an absolute path** (`/mnt/c/…/test-results-cluster-b`); resolve relative to the repo root. Prefer a real spec under `tests/verify/` run by `npx playwright test`, which inherits the pinned `outputDir` for free; reach for a standalone script only when the runner genuinely cannot express the check.
98
- - Screenshots are **failure evidence with a session lifetime**, not work product; so are the harnesses that produced them. They exist so §6 can cite a path in a Remark during this run. Nothing under `tests/.artifacts/` is ever committed, and nothing there is expected to survive the next run. Evidence that must outlive the run is the one-line Remark in the checklist, and — for reviewed screens only — the deliberately tracked `docs/screenshots/{AppName}/` set the DevGuide owns.
99
- - **Gitignore what you provision — in this same step, never later.** Git is manual in TechieFlow: the owner runs every commit and must NEVER have to triage machine artifacts. Check `.gitignore` for each of: `node_modules/`, `/package.json`, `/package-lock.json`, `tests/.artifacts/`, `test-results/`, `test-results-*/`, `/scripts-*/`, `playwright-report/`, `.verify/`, `logs/`, `/docs/.last-verify.json`, `.DS_Store` — append any that are missing under a `# TechieFlow agent artifacts — machine-generated test harness & logs, never commit` header (the scaffold/update scripts manage the same block since 2026-07-11; doing it here too self-heals projects refreshed earlier). `test-results/`, `test-results-*/` and `/scripts-*/` are kept for **legacy** trees only — a compliant run writes none of them. Note the leading slash on `/scripts-*/`: it must match only a **root-level** `scripts-<slug>/`, never a legitimate nested one, and it deliberately does **not** match the project's own tracked `scripts/`. Match tolerantly (strip `\r`, accept anchored/slash variants) and never rewrite existing owner content. `playwright.config.ts` stays TRACKED — committed test suites depend on it. This rule generalizes: **any machine-generated file a task introduces (npm, Playwright output, logs, caches) gets its `.gitignore` entry in the same step that creates it.**
100
- - **Expired run material is deleted for you (added 2026-08-26).** The `.tfcore/hooks/sweep-artifacts.sh` SessionStart hook removes files under `tests/.artifacts/` and `.verify/` older than the retention window (default 7 days; `artifactRetentionDays:` in `.tfcore/core-config.yaml`) and any banned repo-root legacy dir, in every harness. Consequences: **never rely on a previous run's material still existing** — a screenshot or log you want to cite in a Remark must be cited (path + date) in the same run that produced it; anything worth keeping past a week has stopped being scratch and is promoted (a harness to `tests/verify/`, a capture to `docs/screenshots/`). Do not "protect" material by moving it out of `tests/.artifacts/` — that recreates the litter the rule exists to stop.
101
- - **Sweep legacy artifact dirs once, here.** Repo-root leftovers from pre-rule runs, both classes (the SessionStart sweep now removes these too, so this step is a belt-and-braces check on a project that has not had a fresh session since the update):
102
- - Any repo-root `test-results/` or `test-results-*/` — machine output from a pre-`outputDir` run.
103
- - Any repo-root `scripts-*/` (hyphen required: `scripts-cluster-b/`, `scripts-tr054/`) — a fan-out's throwaway harness folder.
104
-
105
- Delete those directories (`rm -rf`), report the count in your one-line summary, and move on. They are fully regenerable — that is the whole reason they were never work product. **Delete only root-level directories whose name matches `test-results*` or `scripts-*`.** Two hard exclusions: the project's own **`scripts/`** (no hyphen — release/publish/dev-setup scripts the repo tracks and owns) is NEVER touched, and neither is `tests/`, `docs/screenshots/`, or anything else. If a `scripts-*/` directory contains something that is plainly not a run harness, do not delete it — name it in the report and leave it for the owner.
106
- - In SETUP-ONLY mode: print "Verification environment ready." and HALT here.
107
-
108
- ### 2. Locate and characterize the Blazor app
109
-
110
- - Find the startup `*.csproj` (the one referencing `Microsoft.NET.Sdk.Web` or a Blazor SDK; if multiple, prefer one named like `*.Web`, `*.Server`, `*.App`, else ask once).
111
- - Read `Properties/launchSettings.json` for `applicationUrl`. Capture both the https and http URLs. **Prefer the http URL** for testing to avoid dev-cert friction in WSL. If only https is exposed, run `dotnet dev-certs https --trust` (best-effort) and fall back to http if the cert blocks navigation.
112
- - Note whether it is Blazor Server (SignalR — needs explicit waits) or WASM (slower first paint).
113
-
114
- ### 3. Boot the app headless in the background
115
-
116
- - **Pick the right rung first** — read the startup `.csproj`. If it targets a MAUI **Android / iOS / Mac Catalyst** head (`<UseMaui>` + `-android`/`-ios`/`-maccatalyst`) → this section (Playwright boot) does not apply; jump to **§3b** to drive it over Appium. The MAUI **Windows** head builds/runs via rung #4 (`cmd.exe /c "dotnet run ..."`) and is driven by FlaUI/Appium-Windows as before. Otherwise (Blazor) use the ladder's platform rung: on WSL rung #2 (`~/.dotnet/dotnet run --project ...`); on macOS / native Linux `dotnet` is on PATH — plain `dotnet run` (ladder §A; `~/.dotnet/dotnet` does not exist there).
117
- - Start it yourself, detached, capturing logs (WSL form shown — on macOS/Linux substitute plain `dotnet`):
118
- `nohup ~/.dotnet/dotnet run --project <csproj> --urls http://localhost:5099 > .verify/app.log 2>&1 &`
119
- (Pin a known free port like 5099 so the test URL is deterministic. Create `.verify/` if needed.)
120
- - Poll `http://localhost:5099` (curl, up to ~60s) until it returns HTTP 200, or until `app.log` shows "Now listening on".
121
- - If the log shows `NETSDK1178` / workload missing / `Microsoft.iOS.Sdk missing` → you picked the wrong rung. Kill the process, switch to rung #4, retry.
122
-
123
- ### 3a. Boot-failure fallback — ASK USER, never propose cloud
124
-
125
- If the boot doesn't succeed in 60s, OR Playwright cannot connect to it (e.g. WSL networking blocks the WSL→localhost lookup):
126
-
127
- 1. Capture the last 30 lines of `.verify/app.log` and read them. If the error is fixable (port already in use → pick another port; missing dev cert → http only) — retry once.
128
- 2. Try rung #4 of the build invocation ladder: `cmd.exe /c "cd C:\\path\\to\\project && dotnet run --project src\\{AppName}.Web --urls http://localhost:5099"`. Detached, log to `.verify/app.log`. This launches Windows-side dotnet which Playwright in WSL can reach via `http://localhost:5099` (WSL2 forwards localhost to the Windows host).
129
- 3. If both fail, fall through to the **ask-user flow** (per the "Local-only deployment policy" at the top of this file). Output the two-terminal shell recipe. **Wait for `go`** and proceed.
130
-
131
- **NEVER**: propose cloud deploy, propose skipping verification, propose "let me push to staging", propose "let me deploy to Cloudflare Pages so Playwright can reach it." Banned escape hatches.
132
-
133
- ### 3b. MAUI native heads — drive with Appium, not Playwright
134
-
135
- §3/§3a boot a **Blazor** app for **Playwright**. A MAUI **Android / iOS / Mac Catalyst** screen has no browser — it is driven over the **Appium** WebDriver endpoint instead. Appium is the native analogue of Playwright: it returns a `base64` screenshot (for §4b) and an element tree with `rect` + text + page source (for §4a), so **the §4a render gate and §4b visual-truth gate run unchanged — only the driver differs.** The MAUI **Windows** head keeps its existing FlaUI / Appium-Windows path (its boot is rung #4, not this section — but the **window-binding & input discipline below applies to it in full**); Blazor keeps Playwright.
136
-
137
- Trigger: the startup/owning `.csproj` has `<UseMaui>` / MAUI SDK and a `-android` / `-ios` / `-maccatalyst` target framework. For each such head in scope:
138
-
139
- 1. **Resolve the endpoint** from `core-config.yaml → runtimeVerification.appium.{android|ios|maccatalyst}`. If the app didn't register that head, note "no Appium endpoint configured for {head}" and stamp the head `⚠ STATIC-ONLY` (do NOT fake a pass); tell the user in §8 to add it per `WORKFLOW.html §0b`.
140
- 2. **Bring the device host up yourself** (boot-it-yourself rule, same as §3):
141
- - **Android** — run the registry `launch` command (e.g. `winrun "powershell -File start-android-verify.ps1"`) to start the emulator (`avd`) + Appium on the Windows host; then poll `curl http://localhost:4723/status` (Win11 mirrored networking → `localhost`) until `{"ready":true}` (emulator cold start can take minutes — poll, don't assume).
142
- - **iOS / Mac Catalyst** — the LAN Mac runs Appium; `curl http://<mac>:4723/status`. You do **not** start the Mac; if it is unreachable, that is a session dependency → stamp the head `⚠ STATIC-ONLY` with "Mac build host unreachable" and continue with the heads you can reach. Never mark those REQs `Verified` on visual grounds you couldn't observe.
143
- 3. **Build + install + launch the app on the device** via the build ladder runtime-observe leg (`build-invocation-ladder.md §D`): Android via rung #4 (`cmd.exe`), iOS/Catalyst on the Mac. Start an Appium session with the right driver (`uiautomator2` / `xcuitest` / `mac2`) pointed at the built `.apk`/`.app`.
144
- 4. Hand the live session to §4a/§4b: the fan-out subagents target this Appium session (selectors by `AutomationId` per the coding standard) instead of a Playwright `baseURL`. Screenshots land under `tests/.artifacts/` exactly as Playwright's do — the §1 artifact-location rule is driver-independent, so an Appium capture never gets its own root-level folder either.
145
-
146
- **Window binding & input discipline (ALL native heads, INCLUDING MAUI Windows).** The classic desktop-automation failure is typing into the WRONG window: a global keystroke goes to whatever happens to hold focus (the IDE, a terminal, another app) — not the app under test. This has actually happened; it is banned, mechanically:
147
-
148
- - **Bind the session to the app under test, by identity, before any interaction.** MAUI **Windows**: you launched the app, so you know its **PID** — attach the driver to THAT process's top-level window (Appium Windows: `appium:appTopLevelWindow` = the window handle resolved from the PID; FlaUI: `Application.Attach(pid).GetMainWindow(...)`). Never attach by "the active window" or a partial title search across the desktop. **Android / iOS / Catalyst**: the Appium session you created with the app's package/bundle id IS the binding — assert `currentPackage` / the active app id matches before interacting.
149
- - **Element-scoped input ONLY.** Locate every target by `AutomationId` (the coding standard) *within the bound session/window*, and act on the **element** — `element.Click()`, `element.SendKeys(...)` / ValuePattern `SetValue(...)`. **NEVER inject global input**: FlaUI `Keyboard.Type(...)` / `Mouse.Click(x, y)` at desktop coordinates, PowerShell `SendKeys` / `SendInput`, `wshell.SendKeys`, or `adb shell input` aimed outside the app — all of these write into whichever window has focus and are BANNED.
150
- - **Focus is verified, not assumed.** Before any input burst, assert the bound window is foreground (title + PID match); if it isn't, activate it through the automation API (`window.SetForeground()` / `driver.activateApp(...)`) and re-assert — never "just type".
151
- - **Re-resolve after window changes.** A login dialog, popup, or navigation that spawns a new top-level window means you re-resolve the handle from the app's PID before continuing — a stale handle is how input lands in a dead or foreign window.
152
- - **A missing `AutomationId` is a defect, not a license.** If a control can't be located by `AutomationId`, log it to the owning REQ's Remarks as a coding-standard defect (MAUI UI-testability rule, day1 §4) and fall back to name/type lookup *within the bound window* — never to screen coordinates or global keys.
153
-
154
- If a head genuinely can't be reached after this escalation, it is `⚠ STATIC-ONLY` for that head — never a faked `Verified`. Tear the Appium session + any emulator you started down in §7.
155
-
156
- ### 4. Generate / refresh tests from the scoped requirement IDs (PARALLEL FAN-OUT)
157
-
158
- **Test users (per `.tfcore/tasks/_smoke-test-policy.md`):** any test that needs a logged-in user MUST use a documented/existing account — first from `docs/{AppName}-UsageGuide.md`'s Test-users table, else looked up from the database via the connection string in `appsettings*.json`. **NEVER auto-create random verify/smoke users** (that pollutes the DB). If no usable account exists, STOP and ask the user to provide or confirm credentials before creating any — then record the created account in the UsageGuide table. Pass the chosen credentials into each subagent prompt so all tests share the same accounts.
159
-
160
- **Single-agent test generation is a banned anti-pattern for the verifier too** — splits cleanly along test-type lines.
161
-
162
- Group the working list by `type-guess`:
163
-
164
- - **Cluster UI** — all `ui` / `behavioral` IDs (Playwright tests).
165
- - **Cluster API** — all `backend-logic` IDs with HTTP surface (Playwright `page.request` or dotnet test integration tests).
166
- - **Cluster Unit** — all `backend-logic` / `nonfunctional` IDs without HTTP surface (dotnet unit tests).
167
-
168
- For each non-empty cluster, spawn a parallel **test-writer subagent** in ONE assistant turn: `tf-test-writer` if the harness registers it, otherwise the harness's general subagent (Claude Code: the `Agent` tool, `subagent_type=tf-test-writer|general-purpose`; OpenCode: the `task` tool, `subagent_type=tf-test-writer|general`). Each subagent prompt MUST include:
169
- - The cluster's REQ IDs + requirement text.
170
- - The running app URL (`http://localhost:5099` or whatever §3/§3a resolved to).
171
- - Path to where the test files should land: `tests/verify/{scope}-{cluster}.spec.ts` (Playwright; scope slug = `ui` / `functional` / `req-list` / `phase-N`) or appropriate `*Tests.csproj` for dotnet tests.
172
- - Black-box rule: do NOT touch application source.
173
- - Return contract: `{ testsAdded[], testsRefreshed[], unobservable[] }`.
174
-
175
- After all subagents return, you (the verifier) own running the tests in §5. Do NOT have subagents run tests — concurrent test runs against one running app would race.
176
-
177
- Per-requirement guidance for subagents:
178
-
179
- - **ui / behavioral** → one `test()` per requirement, title PREFIXED with the ID, e.g. `test('BRD-12 user can filter the grid by status', ...)`. Use Playwright auto-waiting plus explicit `await page.waitForSelector(...)` for Blazor Server render/SignalR. Assert the observable outcome the requirement describes.
180
- - **backend-logic / nonfunctional** → if a `*Tests.csproj` exists, ensure a matching unit/integration test is present (do not rewrite existing passing tests; only flag absence). If no test project exists, mark the requirement `NOT-OBSERVABLE` rather than guessing.
181
- - Reference the running URL as `baseURL`.
182
- - Keep tests black-box and idempotent.
183
-
184
- ### 4a. DevGuide render sweep — the STRICT RENDER GATE (run for every in-scope screen)
185
-
186
- Acceptance tests check "does the feature behave"; this sweep checks the thing that actually slips through — **"does every control on the screen actually render its data, or is it blank?"** A page that returns HTTP 200 with an empty table, a count badge over zero visible rows, or a chart with no data is a DEFECT, not a pass. This is what the verifier missed before.
187
-
188
- For **every in-scope screen** in the DevGuide map (including those backing `Done (pre-existing)` and `Verified` REQs), generate/refresh a Playwright check (fan out by role-cluster like §4) that:
189
- - Logs in as the role that reaches the screen (use the DevGuide's stated role + the `_smoke-test-policy.md` test user), navigates to the screen's route.
190
- - For **each control the DevGuide lists** for that screen, asserts it actually rendered its data:
191
- - grids/tables/lists → **row count > 0 AND cells are non-empty** (catch the "count says 16 but rows blank" case: assert visible data cells, not just the count badge);
192
- - charts → the chart/SVG/series node exists and is non-empty;
193
- - detail/value panels → the value element is present and not blank/placeholder;
194
- - a control hidden behind a null guard (`@if (X != null)`) that never appears → **render-empty**, not "absent by design", unless the DevGuide says it's conditional.
195
- - Record per control: **RENDERS** / **RENDER-EMPTY** (blank, zero-rows, count-vs-rows mismatch, null-guarded-away) / **RENDER-ERROR** (console/Blazor error) / **UNREACHABLE** (route/auth failed).
196
-
197
- A screen with any RENDER-EMPTY / RENDER-ERROR control **fails the render gate** for every REQ that owns a control on it (§6). Capture a screenshot for each failing control. These observations are also what feed the DevGuide refresh in §6b — the DevGuide's render-status tags become *observed runtime facts*, not static inferences.
198
-
199
- **For MAUI native heads (§3b),** run the identical assertions over the **Appium** session instead of Playwright: locate each control by its `AutomationId`, read the element's text / child count from the page source (grid rows non-empty, value non-blank), and treat a missing/empty element exactly as RENDER-EMPTY. Same verdicts, same screenshots — only the locator API changes.
200
-
201
- ### 4a2. ASSET-INTEGRITY gate — did the assets the page DECLARES actually arrive
202
-
203
- §4a proves the data is in the DOM. It does not prove the page received the files that make it a page. **Nothing else in the gate set asks this question, and nothing else can.**
204
-
205
- The failure that produced this gate (TfLens TF-007, 2026-08-28): `*handoff-phase` declared an app READY FOR UAT on **140 of 143 `Verified`** — acceptance, data-render, visual-truth, standards, parity and perf all green. The owner opened `/login` and got an unstyled single column. One stylesheet, the Blazor scoped-CSS bundle carrying 100% of that page's layout, had 404ed.
206
-
207
- | Gate | Why it passed |
208
- |---|---|
209
- | acceptance | every control was present, every behavioural assertion held — an unstyled page behaves correctly |
210
- | render (§4a) | "does this control carry non-placeholder text?" — yes; text renders fine with no CSS |
211
- | visual (§4b) | "do these boxes overlap / clip / leave the viewport?" — no. **A single stacked column overlaps nothing. It is the tidiest possible failure** — partial breakage overlaps, total breakage stacks neatly |
212
- | standards | file-level; never loads the app |
213
- | perf | an unstyled page is, if anything, faster |
214
-
215
- A 404 on a `<link rel=stylesheet>` or `<script src>` produces no console error the gates read, no server log line, no Blazor error boundary and no failed assertion. The app renders something that looks intentional.
216
-
217
- **Run the shipped harness — do not hand-roll it:**
218
-
219
- ```bash
220
- bash .tfcore/utils/tf-assets.sh --base http://localhost:5099 \
221
- --paths "/,/login,/export" \
222
- --json-out tests/.artifacts/assets/run.json
223
- ```
224
-
225
- Login-gated app? Present the session, the same way §4c does:
226
-
227
- ```bash
228
- bash .tfcore/utils/tf-assets.sh --base http://localhost:5099 --paths "/,/export" \
229
- --cookie 'AuthCookie=<value from your Playwright login>'
230
- ```
231
-
232
- **Reading the exit code:**
233
-
234
- | Exit | Meaning | What to record |
235
- |---|---|---|
236
- | `0` | every declared asset arrived | gate passes; `assets` in `gates_run` |
237
- | `5` | at least one declared asset did not arrive | **`ASSET-FAIL`** — see §6 |
238
- | `4` | every path answered 3xx (auth wall) | `ASSETS-UNMEASURED (auth wall)` — re-run with `--cookie`. **`assets` is OMITTED from `gates_run`**; never record it as a pass |
239
- | `2` | the app was unreachable | a `build` gate problem, not an assets result |
240
-
241
- **It grades same-origin assets by default, and that is deliberate.** A CDN that is briefly slow or rate-limited would fail the gate on every screen for something that is not the app's defect — and a gate that cries wolf teaches the owner to ignore verdicts, which costs more than the defects it catches (the same rule §4c states for perf). Cross-origin assets are listed and marked `"external": true`. Pass `--include-external` only when a REQ actually depends on a third-party asset.
242
-
243
- **`"ungradeable": true` in the output is not a pass.** It means no asset was graded — the pages declared none, or all were cross-origin. Record `ASSETS-UNGRADEABLE` and leave `assets` out of `gates_run`, exactly as with the auth wall.
244
-
245
- Cost: ~100 ms on a healthy page. There is no reason to skip it.
246
-
247
- ### 4b. VISUAL-TRUTH gate — does the screen actually LOOK right (run for every in-scope screen)
248
-
249
- The §4a render gate proves the data is *present in the DOM*; it does NOT prove the screen is *usable*. The exact failure the owner hit: every control renders its data, the verifier passes — but the controls **overlap, sit off-screen, are clipped to zero height, or the layout is broken**, so the running app is unusable. "Has data" ≠ "looks right." This gate closes that hole.
250
-
251
- For **every in-scope screen** (same screens as §4a, same role login), at **at least two viewport widths** — desktop (1280×800) and mobile (390×844) — assert via Playwright:
252
- - **No overlap:** no two sibling/interactive controls have intersecting bounding boxes (`boundingBox()` rectangles must not overlap beyond a small tolerance). Overlapping controls = `VISUAL-FAIL`.
253
- - **In-viewport & sized:** every control the DevGuide lists has a bounding box with width > 0 and height > 0, and lies within the page bounds (not pushed off-canvas, not clipped to 0). A control with a zero-size or off-screen box = `VISUAL-FAIL`.
254
- - **Not occluded / not collapsed:** key containers are not zero-height and content is not spilling out of its container.
255
- - **Screenshot + vision:** capture a full-page screenshot at each width and **inspect it** — broken grid, stacked/overlapping text, unstyled fallback (raw HTML with no TrBlazeUI styling), or content overflowing its region is a `VISUAL-FAIL` even if the box-geometry checks passed.
256
- - **Mockup comparison is NO LONGER done by eye here** — it is its own mechanical gate, **§4b2** below. A prose instruction to "compare against the mockup" is what this section used to carry, and it caught nothing: 13 of 14 screens drifted structurally with this gate green (TfLens TF-008). Brownfield still has no mockup — rely on the geometry + vision checks here (and, where present, the reviewed DevGuide screenshots from `docs/screenshots/{AppName}/`).
257
-
258
- Record per screen: **VISUAL-OK** / **VISUAL-FAIL** (with the failing controls + which width + a screenshot path). A screen with any `VISUAL-FAIL` fails the visual gate for every REQ that owns a control on it (§6). These observations also feed the DevGuide refresh (§6b).
259
-
260
- ### 4b2. MOCKUP-PARITY gate — does the built screen carry the structure its mockup draws
261
-
262
- §4b proves the screen is not broken. It does not prove the screen is **right**. That is a different question, and until 2026-08-31 no gate asked it.
263
-
264
- The failure that produced this gate (TfLens TF-008, 2026-08-29): a checklist reading **145 `Verified`** over a running app with structural drift on **13 of 14 comparable screens** — 20 findings, 15 REQs demoted in one sitting, all found by a human comparing 18 screenshots by hand.
265
-
266
- | Symptom | What the app actually did |
267
- |---|---|
268
- | Header wrapped to two rows on all six report routes | 105px against the mockup's 64px |
269
- | Value column starved by a `nowrap` label column | **71px**; `Cache read 2,287,975,139` broke across 3 lines, mid-number |
270
- | `Days since` column pushed out of its card | present in the DOM, clipped off the right edge |
271
- | Status pill rendered as plain text | the one value on the page meant to be read at a glance |
272
- | Measured-vs-estimate tile distinction dropped | the mockup's own note says losing it hands the reader "a plausible wrong number" |
273
-
274
- Neither existing gate could catch any of it, and not because of a bug in them: **§4a asks whether the control shows data — a badge rendered as plain text has text. §4b asks whether anything overlaps — a header that wraps to two rows overlaps nothing, and neither does a 71px column splitting a number across three lines.** Adding acceptance criteria does not help either: in every case the acceptance existed and was met. The missing thing was a gate that could fail.
275
-
276
- **Run the shipped harness:**
277
-
278
- ```bash
279
- bash .tfcore/utils/tf-mockup-parity.sh --base http://localhost:5099 \
280
- --screen login=/login --screen harness=/harness --screen export=/export \
281
- --cookie 'AuthCookie=<value from your Playwright login>' \
282
- --json-out tests/.artifacts/mockup-parity/run.json
283
- ```
284
-
285
- One `--screen name=/route` per screen that has a `docs/mockups/{name}.html`. It compares **structurally, never pixel-wise** — pixel diffing on live data is unusable and would be ignored within a week — at 1280 and 390, grading eight classes:
286
-
287
- `badge` · `icon` · `color` · **`stroke`** · `wrap` · `clip` · `token` · `missing`, plus a document-height assertion.
288
-
289
- Three of those exist because the first version of this gate shipped without them:
290
-
291
- - **`stroke`** (TfLens TF-009). A mockup says *provisional / estimated / inactive* with a **border style**, precisely because a dashed rule stays legible on both the light and the dark surface without spending a colour on it. A dashed grey border and a solid grey border are the **same semantic colour bucket**, so the `color` clause is satisfied and the tile ships styled exactly like the measured one beside it — the defect the design existed to prevent. `stroke` reads `border-*-style` quantised to `none` / `solid` / `dashed`.
292
- - **`missing`** (TF-008's own first two escape rows). Key-pairing cannot see an element that is **not there**, so a badge flattened to plain text and an omitted icon were invisible. Deliberately narrow: only a mockup element that is chrome or carries an icon is reported when absent, and only when its parent paired — an unrestricted "the DOM shapes differ" report would fire on every wrapper div and become the always-present finding that trains a reader to skim.
293
- - **document-height** (TF-008 §2). `document.scrollHeight <= clientHeight + 2` on every route. Cheap, no false positives in a shell-scrolled app, and it alone would have caught a page that escaped the app shell's scroll container and rendered ~1,700px of blank void with the shell repainted at the bottom.
294
-
295
- **READ THE COVERAGE. A `PASS` is only worth what the gate could see (TfLens TF-011).** The gate compares elements that both sides anchor, so on a screen whose mockup anchors little it can grade almost nothing — and **the failure mode is inverted: the less of a screen the gate sees, the cleaner its verdict looks.** That produced a real false statement upstream: a run reported "10 PASS / 2 FAIL / 0 findings" and eight UI rows were written `Verified` on that basis while a screen was visibly wrong.
296
-
297
- So every screen publishes `coverage: {compared, content_graded, app_controls, mockup_anchors, ungradeable}`, and the verdicts are:
298
-
299
- | Verdict | Meaning | Effect on the REQ |
11
+ | Check | Question | Tool |
300
12
  |---|---|---|
301
- | `PASS` | graded, and it matches | may contribute to `Verified` |
302
- | `FAIL` | at least one finding | **`MOCKUP-FAIL`** (§6) |
303
- | `UNGRADEABLE` | **no clause that reaches inside a container ever fired** | `NOT-OBSERVABLE`. **Never a pass, and it must not license a `Verified`** |
304
- | `NO-MOCKUP` | no `docs/mockups/{screen}.html` | reported, never a silent pass — same discipline as `⚠ STATIC-ONLY` |
305
-
306
- `content_graded`, not an anchor ratio, is the measure — and the difference matters. One upstream screen graded deeply off 8 body anchors while another graded nothing off 7, because the difference was table-vs-card, not count. **Count comparisons that could have produced a finding, never anchors.**
307
-
308
- **A `FAIL` can also be ungradeable.** `coverage.ungradeable` stays `true` independently of the verdict, so a screen with one document-height finding and no structural coverage says both things at once. Read it.
309
-
310
- **Closing the gap is mechanical, not a research task.** Each screen reports `anchor_deficit.add_data_testid_to_mockup` — the app `data-testid`s the mockup does not carry. Add them to the mockup and the next run grades deeper. Note the walker already descends any anchored subtree (card, column, grid, `<dl>`, list, table alike), so an anchor on a container is worth something now; it was worth almost nothing when only `<table>` was walked.
311
-
312
- **Exit codes:** `0` every screen graded and clean · `5` at least one screen FAILED · `6` nothing failed but at least one screen was `UNGRADEABLE` / `NO-MOCKUP` — **exit 0 must never be read as "everything was graded"** · `4` Playwright missing (run §1) · `3` bad arguments.
313
-
314
- **Brownfield and `library` / `docs` projects:** no `docs/mockups/`, so this gate does not run. Omit `mockup-parity` from `gates_run` — do not list a gate that never fired (SCHEMA §3.5).
315
-
316
- **For MAUI native heads (§3b),** run the identical overlap / in-viewport / sized / screenshot-inspection checks over the **Appium** session: `element.rect` gives each control's bounding box (overlap + zero-size + off-screen detection), and `driver.get_screenshot_as_base64()` gives the full-screen image to inspect. Drive the device's natural size plus, where the simulator/emulator supports it, a phone vs tablet/desktop variant. Same `VISUAL-OK` / `VISUAL-FAIL` verdicts.
317
-
318
- ### 4c. PERFORMANCE gate — is it fast enough to meet the budget the BRD declared
319
-
320
- §4a proves the data is there; §4b proves the screen is usable. Neither says anything about *speed* — a page that renders every control perfectly and takes nine seconds passes both gates today. This gate closes that, and it is deliberately the narrowest of the three.
321
-
322
- **THE GOVERNING RULE: this gate never invents a budget.** It runs for a REQ **only** if that REQ's acceptance criteria declare one, in this exact form:
323
-
324
- ```
325
- perf-budget: p95 ttfb <= 500ms @ concurrency 50
326
- perf-budget: p95 load <= 2000ms @ concurrency 1
327
- ```
328
-
329
- `p50|p95|max` · `ttfb|load` · a millisecond number · optional `@ concurrency N` (default 1). **No `perf-budget:` line → the gate does not run for that REQ**, it is omitted from `gates_run`, and nothing is reported. That is not a gap to fill by picking a sensible-sounding number: a threshold the owner never agreed to would produce failures they never asked for, and the first false failure is the moment gate verdicts stop being believed. A REQ whose prose says "should be fast" without a number is **not** perf-gated — flag it in §8 as *"REQ-NFR-00X asks for performance but declares no `perf-budget:` — add one to the BRD to make it gradeable"* and move on.
330
-
331
- **Measure with the shipped harness — never hand-roll timing:**
332
-
333
- ```bash
334
- bash .tfcore/utils/tf-perf.sh --base http://localhost:5099 \
335
- --paths "/,/posts,/admin/dashboard" --levels 1,50 --requests 8 \
336
- --build-config Release --label REQ-NFR-001 \
337
- --json-out tests/.artifacts/perf/REQ-NFR-001.json
338
- ```
339
-
340
- Paths come from the DevGuide screens the REQ owns (its routes), not from a guess. Exit `2` means the app was unreachable — that is a `build`-gate problem, not a perf result. The harness prints one JSON object: `levels[].ttfb_ms.{p50,p95,max}`, `load_ms`, a `per_path` breakdown (which page is the slow one), `samples`, `errors`, `redirects` / `redirect_rate`, and a `weak` flag.
341
-
342
- **If the app requires a login, PRESENT A SESSION — do not measure the door.** Pass the authenticated cookie or token through:
343
-
344
- ```bash
345
- bash .tfcore/utils/tf-perf.sh --base http://localhost:5099 --paths "/,/export" \
346
- --cookie 'AuthCookie=<value from your Playwright login>' --build-config Release
347
- # or: --header 'Authorization: Bearer <token>' (--header is repeatable)
348
- ```
349
-
350
- Grab the value from the same login your Playwright spec already performs in §3. **Exit `4` = `status:"redirected"`: every request was turned away and NOTHING was measured** — grade `PERF-UNMEASURED (auth wall)` and re-run with a session; never record the redirect latency as a page figure. (Added 2026-08-27, TfLens TF-002: the harness had no auth option, so on a gated app it timed the 302 to `/login` and reported `p95 = 4.1 ms` — the speed of being turned away at the door — with nothing marking the number meaningless.)
351
-
352
- **Grading — three bands, and the middle one exists specifically so this gate does not cry wolf:**
353
-
354
- | Measured vs budget | Verdict | Effect |
355
- |---|---|---|
356
- | ≤ budget | `PERF-OK` | gate passes |
357
- | > budget, ≤ budget × 1.25 | `PERF-MARGINAL` | **does NOT block `Verified`** — writes a dated remark `⚠ perf: p95 {metric} {measured}ms vs budget {budget}ms (marginal)` so the drift is visible before it becomes a failure |
358
- | > budget × 1.25 | `PERF-FAIL` | fails the gate for that REQ (§6) |
359
-
360
- **Errors during the run — read the RATE, not the count.** This is the one place where "an error occurred" does not mean "do not grade", and getting it backwards makes the gate useless at exactly the concurrency levels it exists for:
361
-
362
- - **`error_rate` ≥ 0.10 with timeout/connection error kinds → `PERF-FAIL`, `failure_class: "timeout"`.** The app did not serve the declared concurrency. Requests timing out under load **is** the perf result, not an obstacle to measuring it — a budget of `@ concurrency 100` is precisely a claim that this does not happen. Report the completed/attempted split and the p95 of what *did* complete, and say plainly that the latency figure describes only the survivors.
363
- - **`0 < error_rate < 0.10` → `PERF-UNMEASURED (errors during run)`.** A handful of failures in an otherwise healthy run tells you the sample is contaminated, not that the app is overloaded. Re-run; if it persists, the failure belongs to the `acceptance` gate.
364
- - **Any `non_200` → `PERF-UNMEASURED (non-200 responses)`** regardless of rate. A 404 means the path set is wrong (a test-setup bug), and a 5xx is a correctness failure the `acceptance` gate owns. Neither is a latency verdict. A non-zero **`redirect_rate`** is the auth case specifically — supply `--cookie` / `--header` and re-measure rather than grading the redirect.
365
-
366
- **Three further conditions under which you must NOT record `PERF-FAIL`** — each produces `PERF-UNMEASURED` plus a one-line reason in §8, because a wrong perf failure costs more than a missing one:
367
-
368
- 1. **`build_config` is not `Release`.** A Debug-build number is not evidence — no tiered JIT steady state, no optimizations. Boot the app `-c Release` and re-measure, or stamp `PERF-UNMEASURED (Debug build)`.
369
- 2. **`weak: true`** (fewer than 20 samples behind the p95). Raise `--requests` and re-run; if you still cannot get samples, the number is indicative only. Note the interaction with the rule above: a run that shed most of its load can be *both* `weak` and a genuine `PERF-FAIL` — the load-shed verdict wins, because it does not depend on the latency distribution being trustworthy.
370
- 3. **The host is visibly contended** — you are running builds or other agents concurrently, or `wall_s` is wildly inconsistent between levels. Re-run once when quiet; if it stays noisy, say so rather than grading it.
371
-
372
- **Never compare across machines or across runs on different hosts.** The harness stamps a `machine` block for exactly this reason. The budget is absolute (the BRD declared it); the measurement is local; a number from the Mac and a number from WSL are not the same measurement, and neither is a trend.
373
-
374
- **MAUI native heads (§3b) are not perf-gated.** The harness speaks HTTP; an Appium-driven Android/iOS/Catalyst screen has no URL to sample, and app-launch/frame timing is a different discipline with different tooling. For a native head, the gate does not run — omit `perf` from `gates_run` and never substitute a Blazor measurement for a native one (same rule as `⚠ STATIC-ONLY`: an unmeasured thing is unmeasured, not passed).
375
-
376
- **This gate is informational during a self-smoke.** `_smoke-test-policy.md` lets build-phase run the harness to catch an obvious regression early, but a self-smoke may never write a perf verdict into the checklist — the smoke ceiling stays `Implemented`, exactly as it does for §4a/§4b.
377
-
378
- ### 5. Run the tests
379
-
380
- - Playwright: `npx playwright test --reporter=line`. **No `--output` flag** — the config's `outputDir` (`tests/.artifacts/test-results`) is the single artifact destination (§1 artifact-location rule). If you must isolate a parallel run, the only permitted override is a subfolder: `--output tests/.artifacts/<slug>`.
381
- - Unit/integration (if a test project exists): `dotnet test --nologo`.
382
- - Collect: per-test pass/fail, and for FAILURES only, the screenshot path under `tests/.artifacts/` and the relevant assertion message. Do not open artifacts for passing tests.
383
-
384
- ### 6. Build the coverage matrix AND write it into the Status table
385
-
386
- Map every requirement ID to exactly one verdict:
387
-
388
- - `PASS` — a real test for this ID passed against the running app / a unit test passed, **AND** every control the DevGuide attributes to this REQ's screens passed the §4a render gate **AND** the §4b visual-truth gate **AND** (only if the REQ declared a `perf-budget:`) the §4c perf gate.
389
- - `RENDER-FAIL` — the acceptance behavior may work, but at least one of the REQ's controls is **RENDER-EMPTY / RENDER-ERROR** (blank table, count-vs-rows mismatch, empty chart, blank value, Blazor error). Include the control + screenshot. This is a real defect even if an old status said `Done`.
390
- - `ASSET-FAIL` — a stylesheet or script the page **declares** did not arrive (§4a2, exit 5). Include the asset URL and its status. This is a real defect on a screen where every other gate is green — that is the entire reason the gate exists.
391
- - `VISUAL-FAIL` — the data renders, but the screen does not LOOK right: controls overlap, sit off-viewport, are clipped to zero size, or the layout is broken/unstyled (§4b). Include the failing control(s), the width, and a screenshot. A real defect even if §4a passed and an old status said `Done`.
392
- - `MOCKUP-FAIL` — the screen is not broken, but it does not carry the structure its approved mockup draws (§4b2, exit 5). Include the finding class, the element key and the width.
393
- - `PERF-FAIL` — the REQ declared a `perf-budget:` and the measured p95 exceeded it by more than 25% under the four measurement preconditions of §4c. Include metric, measured value, budget, and concurrency. **Only for REQs that declared a budget** — a REQ with no `perf-budget:` line is never graded here.
394
- - `FAIL` — a test for this ID ran and failed (include the one-line reason + screenshot path).
395
- - `NOT-IMPLEMENTED` — feature/element the requirement needs was absent (test could not even find it).
396
- - `NOT-OBSERVABLE` — backend/nonfunctional requirement with no test project to assert it; needs a human or a test to be written.
397
-
398
- **A missing file is NEVER a verdict until you have read its literal path.** Before grading a REQ `NOT-OBSERVABLE` — or writing any remark that a required script, oracle, fixture, or harness "is not present in this tree" — read the exact path the REQ, its BRD section, or this task names. Paths under `.tfcore/` are hidden **and** gitignored, so Grep and Glob return nothing for files that are plainly there (`_status-update-gate.md` §"The framework tree is INVISIBLE to search"; `rg -uu` if you must search). A "the tool does not exist here" verdict sourced from a default search is a false negative that lands in the checklist Remarks, the BRD §4 status row, and every gate downstream of it — and the next agent reads it as established fact.
399
-
400
- **STRICT GATE (non-negotiable):** a REQ may be `Verified` **only if** its acceptance test passes AND all its DevGuide-listed controls RENDER their data (§4a) AND every asset its screens declare arrived (§4a2) AND every screen it owns passes VISUAL-TRUTH (§4b) AND — *where a mockup exists* — is not `MOCKUP-FAIL` (§4b2) AND — *where and only where the REQ declared a `perf-budget:`* — it is not `PERF-FAIL` (§4c). Never mark `Verified` when any owned control is RENDER-EMPTY/RENDER-ERROR, any owned screen is VISUAL-FAIL, any declared asset 404ed, or any owned screen drifts from its mockup — those are the exact failure modes these gates exist to stop. A `Done (pre-existing)` REQ whose screens pass the gates stays `Done (pre-existing)` with a `runtime render+visual-confirmed {date}` remark; one that fails any drops to `Needs re-verify`.
401
-
402
- **An UNGRADEABLE or UNMEASURED result is not a pass, and it is not a failure either — it is an absence of evidence, and it must be visible as one.** `MOCKUP-UNGRADEABLE`, `NO-MOCKUP`, `ASSETS-UNMEASURED`, `PERF-UNMEASURED` and `⚠ STATIC-ONLY` all behave the same way: the gate is omitted from `gates_run`, no gate record claims it fired, and the REQ is never `Verified` **on that gate's grounds**. It may still reach `Verified` on the gates that *did* run — say which, in the Remark. The one thing you may never do is let silence read as success; that is the defect TF-011 filed as a blocker.
403
-
404
- **The perf gate never widens the `Verified` bar for a REQ that never declared a budget.** Most REQs are not perf-gated and must not be treated as if they failed something they were never measured against — `PERF-UNMEASURED` and "no budget declared" are both simply *absent* from the verdict, not a demotion.
405
-
406
- **Write the run ledger FIRST (mechanical unlock).** Immediately before recording the first verdict, Write `docs/.last-verify.json` (overwrite the previous run's file; it lives next to the checklist):
407
-
408
- ```json
409
- {"date":"<today YYYY-MM-DD>","app":"{AppName}","scope":"{scope}","booted":"<rung/URL or Appium target>","gates":["acceptance","data-render","asset-integrity","visual-truth","mockup-parity"],"evidence":"<tests/.artifacts/ path or one-line pointer>"}
410
- ```
411
-
412
- List in `gates` the gates this run actually applied — add `"performance"` when §4c graded at least one REQ, `"asset-integrity"` when §4a2 graded at least one asset, and `"mockup-parity"` when §4b2 returned `PASS` or `FAIL` on at least one screen. (`guard-verify.sh` reads only `date`; the array is the human-readable record of what the run did, so an accurate list costs nothing and a padded one is a false audit trail.)
413
-
414
- The PreToolUse hook `.tfcore/hooks/guard-verify.sh` BLOCKS any checklist write that introduces a `Verified` status without a same-day ledger — this is what makes "only the verifier writes `Verified`" mechanical rather than prose. You may write the ledger ONLY after actually executing §3–§5 above (boot + scoped tests + both gates); writing it without having run them is falsifying the audit record.
415
-
416
- Then **write each verdict into the Requirements Status table** of the one checklist `docs/{AppName}-Checklist.md` (find the row by its REQ ID). This is the single source of truth; there are no dated coverage files. Update only the Status / % / Remarks cells (never the requirement text or app source). Map verdict → table Status:
417
-
418
- | Verdict | Status | % | Remark |
419
- |---------|--------|---|--------|
420
- | PASS | `Verified` | 100% | date + test path + `render+visual gate: all controls render and look right` |
421
- | RENDER-FAIL | `Needs re-verify` | lower to reflect reality | date + `⚠ render gate: {control} renders empty/error on {screen}` + screenshot path |
422
- | ASSET-FAIL | `Needs re-verify` | lower to reflect reality | date + `⚠ assets: {n} declared asset(s) did not arrive on {screen} — {url} → {status}` |
423
- | VISUAL-FAIL | `Needs re-verify` | lower to reflect reality | date + `⚠ visual: {control(s)} overlap/clip/off-viewport on {screen} @ {width}` + screenshot path |
424
- | MOCKUP-FAIL | `Needs re-verify` | lower to reflect reality | date + `⚠ mockup-parity: {class} on {element key} @ {width} — {one line}` + the §4b2 run JSON path |
425
- | MOCKUP-UNGRADEABLE | **`N/A`** if that is the REQ's only evidence, else **unchanged** | keep | date + `mockup-parity: UNGRADEABLE on {screen} — graded 0 content-level comparisons off {n} mockup anchors; add data-testid per anchor_deficit`. **It may never license a `Verified` on design grounds.** |
426
- | ASSETS-UNMEASURED / ASSETS-UNGRADEABLE | **unchanged** | keep | date + `assets: not graded — {auth wall / no same-origin assets declared}` |
427
- | PERF-FAIL (slow) | `Needs re-verify` | lower to reflect reality | date + `⚠ perf: p95 {metric} {measured}ms vs budget {budget}ms @ concurrency {n}` + the slowest path from `per_path` |
428
- | PERF-FAIL (load shed) | `Needs re-verify` | lower to reflect reality | date + `⚠ perf: {failed}/{attempted} requests timed out @ concurrency {n} — budget {budget}ms not servable` + the p95 of the completed subset, labelled as survivors only |
429
- | PERF-MARGINAL | **unchanged** (may still be `Verified`) | keep | date + `⚠ perf: p95 {metric} {measured}ms vs budget {budget}ms (marginal)` — a warning, never a demotion |
430
- | PERF-UNMEASURED | **unchanged** | keep | date + `perf: not measured — {Debug build / weak sample / errors during run / host contended}` |
431
- | FAIL | `FAIL` | keep | date + one-line reason + screenshot path |
432
- | FAIL caused by a library gap | `Blocked` | keep | date + link the `TR-NNN` entry in `docs/{AppName}-TrBlazeUI-Feedback.md` or the `TR-RAG-NNN` entry in `docs/{AppName}-TechieRag-Feedback.md` (add the entry if the implementing agent didn't — one feedback file per library) |
433
- | NOT-IMPLEMENTED | `In Progress` | 25% | date + "element/feature absent" |
434
- | NOT-OBSERVABLE | `N/A` | — | date + "no test surface; needs human/test" |
435
-
436
- A FAIL counts as "caused by a library gap" when the failing behavior traces to TrBlazeUI/TechieRag rather than app code (matches an entry in the owning library's feedback file, or your diagnosis pins it on the library). `Blocked` rows pass through phase-completion checks — they are not the app's fault.
437
-
438
- If a graded ID has no row in the Status table yet, add one.
439
-
440
- ### 6a. Emit gate telemetry (immediately after the ledger + verdicts)
441
-
442
- Read `.tfcore/tasks/_metrics-emit-gate.md` once; it carries the constraints. Schema: `.tfcore/telemetry/SCHEMA.md` §3.
443
-
444
- `docs/.last-verify.json` is the *same-day unlock ledger* and is overwritten every run — it is not history. This step is what makes the run durable. **Emit ONE `gates.jsonl` record per REQ you evaluated in §6** — pass or fail, no exceptions, including `Blocked` and `Done (pre-existing)`.
445
-
446
- For each REQ:
447
-
448
- 1. **`gate` — the FIRST gate that failed, or `null` on a pass.** This one field produces the whole gate-catch distribution; get it right and everything else is secondary. Execution order is `build` → `acceptance` → `render` → **`assets`** → `visual` → **`mockup-parity`** → `perf` → `standards`. A REQ that failed §4a *and* §4b records `render`, not `visual` — recording the later gate inflates the visual gate's apparent catch rate. Same for `perf`: a REQ that was already `VISUAL-FAIL` records `visual`, even if its p95 also blew the budget.
449
-
450
- **`assets` sits before `visual` on purpose**: an asset that never arrived *explains* the visual result rather than being explained by it. A page with no stylesheet is unstyled **because of** the 404, and recording `visual` there credits the gate that merely saw the consequence. **`mockup-parity` sits after `visual`** for the mirror reason — a screen that overlaps or clips is broken on its own terms, before any question of whether it matches a design.
451
- 2. **`attempt` — derive it, never guess it:** `bash .tfcore/utils/tf-emit.sh --next-attempt REQ-UI-004` prints the number. Run it per REQ.
452
- 3. **`gates_run`** — only the gates that actually executed this pass. A `⚠ STATIC-ONLY` head did not run `render` or `visual`; leave them out rather than crediting a gate that never fired. **`assets` belongs here only when §4a2 actually graded at least one asset** — an auth wall (exit 4) or an `"ungradeable": true` payload graded nothing, and listing it would inflate the gate's measured coverage exactly as a padded `perf` would. **`mockup-parity` belongs here only when the screen's verdict was `PASS` or `FAIL`** — never on `UNGRADEABLE` and never on `NO-MOCKUP`, which are absences of evidence (SCHEMA §3.5). **`perf` belongs here only when a `perf-budget:` existed AND the §4c preconditions held** — a REQ with no budget, a native head, a Debug build, or a `PERF-UNMEASURED` outcome did not run the perf gate, and listing it would silently inflate the gate's measured coverage. This field is the only thing that makes the perf gate's catch rate readable at all (SCHEMA §3.5), so it is worth getting exactly right.
453
- 4. **`prior_verdict`** — the Status cell value you just overwrote (`null` if the row is new).
454
- 5. **`failure_class`** — from the closed enum only (`blank-data` · `zero-rows` · `overlap` · `clipped` · `offscreen` · `slow-ttfb` · `slow-load` · `timeout` · `exception` · `assert-fail` · `naming` · `build-error` · `missing-asset` · `mockup-drift` · `other`). An `assets` failure is `missing-asset`; a `mockup-parity` failure is `mockup-drift`, whichever of the eight classes drifted — **never the URL and never the element key**, which are file paths and app internals that constraint 7 keeps out of these streams. Both belong in the Remark and the run JSON. **Never free text** — a description here would leak requirement and client detail. A `perf` gate failure is `slow-ttfb` or `slow-load` depending on which metric the budget named, or `timeout` when the run failed by shedding load rather than by being slow; **never emit the measured milliseconds** — a number is a measurement of this machine on this day and does not belong in an append-only stream that is read across hosts.
455
- 6. **`run_id`** — the same value for every REQ in this pass: the timestamp you noted when the verify run started.
456
-
457
- ```bash
458
- ATT=$(bash .tfcore/utils/tf-emit.sh --next-attempt REQ-UI-009)
459
- cat <<JSON | bash .tfcore/utils/tf-emit.sh gates
460
- {"kind":"gate","app":"{AppName}","run_id":"2026-08-08T03:41:02Z",
461
- "req_id":"REQ-UI-009","req_class":"UI","attempt":$ATT,"verdict":"Needs re-verify",
462
- "gate":"visual","gates_run":["acceptance","render","visual"],
463
- "failure_class":"overlap","prior_verdict":"Implemented"}
464
- JSON
465
- ```
466
-
467
- Map the §6 verdict table to the record: PASS → `verdict:"Verified"`, `gate:null` · RENDER-FAIL → `"Needs re-verify"`, `gate:"render"` · **ASSET-FAIL → `"Needs re-verify"`, `gate:"assets"`, `failure_class:"missing-asset"`** · VISUAL-FAIL → `"Needs re-verify"`, `gate:"visual"` · **MOCKUP-FAIL → `"Needs re-verify"`, `gate:"mockup-parity"`, `failure_class:"mockup-drift"`** · **PERF-FAIL → `"Needs re-verify"`, `gate:"perf"`, `failure_class:"slow-ttfb"|"slow-load"|"timeout"`** · FAIL → `"FAIL"`, `gate:"acceptance"` (or `"build"` if it never compiled/booted) · library-caused FAIL → `"Blocked"`, `gate` = whatever actually failed · NOT-IMPLEMENTED → `"FAIL"`, `gate:"build"`, `failure_class:"other"` · NOT-OBSERVABLE → **emit nothing**; there was no gate and no verdict, and a fabricated record would poison the distribution.
468
-
469
- **`MOCKUP-UNGRADEABLE`, `NO-MOCKUP`, `ASSETS-UNMEASURED` and `ASSETS-UNGRADEABLE` emit no failure and no gate credit.** They are NOT-OBSERVABLE for that gate: leave the gate out of `gates_run` and let the REQ's record reflect whatever the gates that *did* run found. Inventing a pass would be worse than inventing a failure here — it is the exact false green (TF-011) that put eight `Verified` rows on a visibly wrong screen.
470
-
471
- **`PERF-MARGINAL` and `PERF-UNMEASURED` are NOT failures and must not be emitted as one.** A marginal REQ that otherwise passed emits an ordinary pass (`gate:null`) with `perf` present in `gates_run` — the warning lives in the checklist Remark, where a human reads it, and not in a stream whose whole purpose is counting what each gate caught. `PERF-UNMEASURED` emits a pass with `perf` **absent** from `gates_run`. Inventing a failure record for a warning would corrupt the one number this gate exists to produce.
472
-
473
- **Then, for every REQ that did NOT pass, also emit a `misses.jsonl` record** (SCHEMA.md §5.5). The gate record says *that* it failed; the miss record says *what* was missed and *which phase let it through* — which is the only way a build-phase mistake ever becomes visible as a build-phase mistake instead of a verify statistic.
474
-
475
- **Collapse first — a REQ that fails three passes is ONE miss, not three:**
476
-
477
- ```bash
478
- OPEN=$(bash .tfcore/utils/tf-emit.sh --open-miss REQ-UI-009) # "<miss_id> <miss_class>", or empty
479
- ```
480
-
481
- - Output is empty → emit a new `miss`.
482
- - Output's `miss_class` **equals** the one you would record → **emit nothing.** Same defect, still open.
483
- - Output's `miss_class` **differs** → emit a new `miss`. The REQ is failing for a different reason now, and that is new information.
484
-
485
- ```bash
486
- MID=$(bash .tfcore/utils/tf-emit.sh --next-miss-id)
487
- cat <<JSON | bash .tfcore/utils/tf-emit.sh misses
488
- {"kind":"miss","miss_id":"$MID","req_id":"REQ-UI-009","req_class":"UI",
489
- "miss_class":"partial-implementation","artifact":"src","severity":"major",
490
- "why_missed":"insufficient-verify-method",
491
- "origin_phase":"build-phase","origin_agent":"trblazeui",
492
- "origin_run_id":"<started of the build-phase run that last touched this REQ>",
493
- "found_by":"gate","found_phase":"verify-phase","found_gate":"visual",
494
- "found_run_id":"<this run's start>","failure_class":"overlap"}
495
- JSON
496
- ```
497
-
498
- - **`origin_run_id`** — find it by reading `docs/metrics/runs.jsonl` for the most recent non-backfilled `build-phase` (or `fix-issues`) record whose `reqs_touched` contains this REQ, and pass its `started`. If there is no such record, pass nothing: `tf-emit.sh` will mark the record `origin_confidence:"inferred"` and force the model to `null`, which is the honest outcome. **Never type a model name** — the emitter looks it up (constraint 10).
499
- - **`miss_class`** — from the closed enum. `partial-implementation` when the REQ was built but an acceptance bullet is unmet · `missed-requirement` when nothing was built at all · `regression` when `prior_verdict` was `Verified` · `wrong-behaviour` when it works but not as specified · `standards-violation` for a `standards` gate failure · `unspecified-gap` when the REQ's own acceptance never covered the behaviour that broke (that is a *spec* miss surfacing late, and recording it as a build miss blames the wrong phase).
500
- - **`why_missed`** (optional, SCHEMA §5.5.6) — which *practice* let it through. From a verify run the honest answers are usually `insufficient-verify-method` (the acceptance existed and the gate that ran could not catch this class of defect) or `missing-checklist-item` (nothing covered the behaviour at all). Use `code-audit-limitation` where the head was `⚠ STATIC-ONLY`. **Omit it rather than guess** — `null` means "not assessed" and is honest.
501
- - **`found_gate`** = the same first-failing gate you put in the `gates` record. **`severity`** is owner-visible impact — `blocker` / `major` / `minor` — never an estimate of effort.
502
- - **A `Blocked`-by-library verdict is still a miss**, with `artifact:"src"` and `miss_class:"other"` only if nothing better fits; the library gap itself is logged where it always was, in the per-library feedback file.
503
- - **NOT-OBSERVABLE emits nothing here either** — no gate ran, so nothing was missed that this run can attest to.
504
-
505
- Then emit ONE `runs.jsonl` record for the verify pass itself:
506
-
507
- ```bash
508
- cat <<'JSON' | bash .tfcore/utils/tf-emit.sh runs
509
- {"kind":"run","app":"{AppName}","cmd":"verify-phase","mode":null,
510
- "started":"<run start>","ended":"<now>","duration_s":<n>,
511
- "reqs_touched":["REQ-UI-009","REQ-FN-011"],"reqs_count":2,
512
- "subagents":[],"files_written":<n>,"build_result":"pass"}
513
- JSON
514
- ```
515
-
516
- **Hard rules for this step.** Telemetry has **no veto** — if an emit fails, the verify run still succeeded; do not retry, do not diagnose, do not mention it, and never let it change a verdict. Do not touch `docs/.last-verify.json`. Do not write metrics into the checklist or PROJECT-STATUS. Never emit `"backfilled":true` — only the owner-run `tf-metrics.sh` writes those. **Never write `origin_model`, `origin_harness`, `origin_confidence`, or any token/cost field on a miss** — the emitter resolves them from the run you name, and a value you supply is overwritten or discarded.
517
-
518
- ### 6b. Refresh the DevGuide render-status (close the loop)
519
-
520
- For every screen you exercised, update its entry in the DevGuide (`docs/{AppName}-DevGuide.md`, or the matching role file under `docs/devguides/`) so its control render-status reflects **what you just observed at runtime** — not the old static inference:
521
- - Tag each control **renders ✓ (runtime-confirmed {date})** / **renders-empty (DEFECT — {what}, {date})** / **render-error** / **unreachable**, matching the §4a sweep, and add the §4b visual result for the screen **looks-right ✓ (runtime-confirmed {date})** / **visual-broken (DEFECT — {what} @ {width}, {date})**.
522
- - Update/refresh the screen's *Known issues* with any new RENDER-EMPTY/ERROR **or VISUAL-FAIL** finding (with file refs if known), and clear ones that now render and look right.
523
- - Stamp the DevGuide file header (or the screen block) `Runtime-verified {date} as {roles exercised}`. If a screen could not be reached/booted, mark it `NOT RUNTIME-VERIFIED` rather than leaving a stale "renders" claim.
524
- - This is markdown only — do NOT render the checklists to HTML; DO re-render the touched DevGuide `.html` via `.tfcore/tasks/generate-html.md` (it is a human doc).
525
-
526
- If no DevGuide exists, skip this and add to the report: "No DevGuide to refresh — run `*devguide {AppName}` to create the control map, then re-verify."
527
-
528
- ### 7. Tear down
529
-
530
- - Kill the app process you started (by the pinned port / stored PID). Confirm the port is free.
531
- - For MAUI native heads (§3b): quit the Appium session, and shut down any **emulator** you started yourself (leave a pre-existing/owner-run emulator or the LAN Mac's Appium server up — only tear down what you booted).
532
- - **Leave no artifact directory outside `tests/.artifacts/`.** If this run produced a repo-root `test-results/`, `test-results-*/` or `scripts-*/` anyway (a stray `--output`, a tool default you didn't pin, a harness you dropped at the root), delete it now — you created it, you remove it. The project's own `scripts/` is never yours to remove. `tests/.artifacts/` itself STAYS: it is gitignored, Playwright wipes it at the start of the next run, and the paths you cited in §6 Remarks must still resolve for the owner reading the report today.
533
-
534
- ### 8. Report and HALT
535
-
536
- Print a compact report (you have already updated the checklist Status table in §6; do NOT modify requirement text or app source):
537
-
538
- ```
539
- # Verification — {scope}
540
- Source checklist(s): <path(s)> | Requirements graded: N
541
-
542
- | ID | Requirement (short) | Status | Evidence |
543
- |---------|----------------------------|-----------------|-----------------------------------|
544
- | BRD-10 | ... | PASS | tests/verify/phase-2.spec.ts |
545
- | BRD-12 | ... | FAIL | screenshot: tests/.artifacts/.../...png — "expected grid rows > 0" |
546
- | BRD-13 | ... | NOT-IMPLEMENTED | element [data-testid=status] absent |
547
- | BRD-14 | ... | NOT-OBSERVABLE | no test project; logic-only |
548
-
549
- ## MISS LIST (act on these)
550
- - BRD-12 FAIL — <one line>
551
- - BRD-13 NOT-IMPLEMENTED — <one line>
552
-
553
- ## Summary: PASS x / FAIL y / BLOCKED b / NOT-IMPLEMENTED z / NOT-OBSERVABLE w
554
- ## Assets (§4a2): graded a assets over p pages — OK / n did not arrive / UNMEASURED u
555
- ## Mockup-parity (§4b2): PASS p / FAIL f / UNGRADEABLE u / NO-MOCKUP m — c content-level comparisons over s screens
556
- ## Perf (§4c): graded p of q budget-carrying REQs — PERF-OK a / MARGINAL m / FAIL f / UNMEASURED u
557
- ```
558
-
559
- **The mockup-parity line MUST print the comparison count, and `UNGRADEABLE` / `NO-MOCKUP` must never be folded into `PASS`.** A verdict summary that reads "10 PASS / 2 FAIL / 0 findings" while most of those screens were never really graded is a false statement, and it is one this project has already made once (TF-011). If any screen is `UNGRADEABLE`, name it and quote its `anchor_deficit` so the fix is a mechanical edit to the mockup.
560
-
561
- Print the Perf line **only when at least one in-scope REQ declared a `perf-budget:`** — on a project where none do, the gate did not run and a line saying so is noise. When you did skip REQs for a missing budget, name them once: *"REQ-NFR-003, REQ-NFR-007 ask for performance but declare no `perf-budget:` — add one to the BRD to make them gradeable."*
562
-
563
- - The per-REQ detail now lives in the checklist Status table (§6) — do NOT also save a dated `docs/verify/*.md` report. The console report above plus the updated Status table ARE the deliverable.
564
- - **FINAL GATE — update PROJECT-STATUS (MANDATORY, non-skippable).** Before you HALT, update PROJECT-STATUS — **both `PROJECT-STATUS.md` AND re-rendered `PROJECT-STATUS.html`** — per `.tfcore/tasks/_status-update-gate.md`: `last_updated`, `last_verified_build`/`last_verified_date`, sync Open requirements to the Status table (anything not `Verified` stays open), set the Next command (**command-against-checklist only — re-run the phase naming the FAILed REQ IDs if there are FAILs; advance if all `Verified` — no prose to-do narrative**), and add a verification-log row pointing at the checklist's `#requirements-status`. **Record this run's outcome as that ONE Verification-log row + the per-REQ Remarks you wrote in the checklist (§6) — do NOT add a `## *verify all — coverage matrix (date)` section (or any new H2) to PROJECT-STATUS; it is a fixed-shape snapshot you overwrite, never an append-log (see `_status-update-gate.md`).** A verification run that updates the markdown but not the HTML, or that does not update PROJECT-STATUS at all, is incomplete.
565
- - Do not fix source. If the user wants fixes, that is a separate instruction to the dev/orchestrator agent referencing the miss list.
566
- - HALT.
567
-
568
- ## Key principles
569
-
570
- - The user typed one command. You did everything else.
571
- - Grade only against numbered IDs; never inflate coverage.
572
- - **A feature is not "done" until its UI renders its data, receives its assets, looks right, and matches the design it was built from.** Behaves-correctly AND renders-its-data (§4a) AND got-its-assets (§4a2) AND looks-right (§4b) AND matches-its-mockup (§4b2) are ALL required for `Verified`. HTTP 200 with a blank table is a FAIL; data-present-but-overlapping is a FAIL; a page whose stylesheet 404ed is a FAIL even though it stacks tidily; a status pill flattened to plain text is a FAIL even though it has text.
573
- - **The gate set measures whether a screen is ALIVE; §4a2 and §4b2 exist because it did not measure whether the screen is RIGHT.** Three defect classes in three days passed every gate and were caught by a human (TfLens TF-007, TF-008, TF-009). All three had the same shape, and it is worth naming because the next one will too: *every gate asserted something true about the screen, and none of them asserted the thing that was wrong.*
574
- - **A gate that graded nothing reports that it graded nothing.** `UNGRADEABLE`, `NO-MOCKUP`, `ASSETS-UNMEASURED`, `PERF-UNMEASURED` and `⚠ STATIC-ONLY` are one rule wearing five names: **silence is not success.** A verdict that looks cleanest exactly where the gate is blindest is worse than no gate, because it is believed.
575
- - **Speed is graded only against a budget someone declared** (§4c). The perf gate is the narrowest of the four on purpose: it fires only for a REQ carrying a `perf-budget:`, it warns before it fails, and it refuses to grade a Debug build or a thin sample. A gate that produces false failures teaches the owner to ignore verdicts, which costs more than the defects it would have caught.
576
- - **`Done (pre-existing)` is an unverified claim, not a pass** — it gets the render sweep like everything else. The first runtime pass either confirms it or flags it.
577
- - **Close the loop:** the DevGuide is your control map (§0/§4a) and you write your runtime observations back into it (§6b). DevGuide ⇄ Checklist ⇄ Verifier stay in sync; a build phase marking a REQ done → this verifier → updated checklist + DevGuide → repeat.
578
- - Results go in the checklist Status table + PROJECT-STATUS (+ the DevGuide render tags) — never a separate dated file.
579
- - Lean context: read failures, ignore successes, tear down what you booted.
13
+ | build | does the application build and boot | `tf-verify-boot.sh` |
14
+ | acceptance | does the test carrying the row's id pass | `tf-verify-tests.sh` |
15
+ | render | does every control the mockup anchors show something, no header-only table, no blank page, no error | `tf-verify-screens.sh` |
16
+ | assets | did every stylesheet and script the screen declares arrive | `tf-assets.sh` |
17
+ | visual | nothing overlaps, nothing clipped or off-screen, no sideways scroll, a stylesheet loaded | `tf-verify-screens.sh` |
18
+ | mockup | does the built screen carry the structure its mockup draws | `tf-mockup-parity.sh` |
19
+ | speed | within the row's `perf-budget:` line, only when the row declares one | `tf-perf.sh`, `tf-perf-grade.sh` |
20
+
21
+ Standards has no script yet and is never listed as run. A row is `Verified` only when every check that ran passed. A check that could not run is absent from the record, never a pass: an unreachable head, a mockup the parity tool could not grade, an auth wall, a Debug build, a thin sample are all written down as "not measured", and the row is never `Verified` on that check's grounds.
22
+
23
+ ## Read
24
+
25
+ The phase's checklist, UIDesign and `docs/mockups/` (`appPhase` in `core-config.yaml` names the phase); the UsageGuide's test users.
26
+
27
+ ## Steps
28
+
29
+ 1. `bash .tfcore/utils/tf-verify-list.sh {App} {scope}` prints the rows to grade with their acceptance line, screen, route, mockup and perf budget, the screens to drive, and the test users; it writes `tests/.artifacts/verify/list.json`. Every row in scope is graded whatever its status; only N/A is skipped, so a `Verified` row is re-confirmed and an old `Done` claim is tested. NOTHING means run the status gate and stop. A row without an acceptance line cannot be graded: fix the checklist first. A dialog is listed under its parent screen and checked there; never give a dialog a route so it can be tested.
30
+ 2. `bash .tfcore/utils/tf-verify-env.sh` prints READY, or the one command the owner must run once.
31
+ 3. `bash .tfcore/utils/tf-verify-boot.sh start` boots the head it finds (`--head` or `--project` when there are several) and prints BOOTED with a URL, or a CDP URL for an embedded-browser desktop head, which the browser tools attach to. A dependent service (database, API) is yours to start first, from its own configuration, in order. NONE `kind=build-error`: every row fails the build check; go to step 6. NONE `kind=host` or `kind=no-driver` (an Android, iOS or Mac head): go to step 6 as well; those rows are written "not verified" with the reason. Never a static-only pass, never "the owner should start it".
32
+ 4. Tests. Every UI and functional row has one test whose title starts with its id, written from the acceptance line, black box, under `tests/verify/`; an NFR row without a screen has a unit test carrying its id in the test project, or is not observable. Reuse a test that exists. Fan out one test-writer sub-agent per screen group in one turn, each with its rows, their acceptance lines, the URL and the test user; a test writer touches no source. Sign in as a UsageGuide test user (`.tfcore/tasks/_smoke-test-policy.md`).
33
+ 5. Run the checks, with `<url>` from step 3 and, on a signed-in app, the session cookie from the test login for the tools that take `--cookie`:
34
+ - `bash .tfcore/utils/tf-verify-tests.sh --base <url>`
35
+ - `bash .tfcore/utils/tf-verify-screens.sh --list tests/.artifacts/verify/list.json --base <url>` (or `--cdp <url>`; `--login-path /login --user … --password …` when the app has a sign-in page)
36
+ - `bash .tfcore/utils/tf-assets.sh --base <url> --paths "<every screen route, comma-separated>" --json-out tests/.artifacts/verify/assets.json`
37
+ - `bash .tfcore/utils/tf-mockup-parity.sh --base <url> --screen <name>=<route> … --json-out tests/.artifacts/verify/parity.json` (one `--screen` per screen with a mockup; skip on a project with none)
38
+ - for each row with a `perf-budget:` line: `bash .tfcore/utils/tf-perf.sh --base <url> --paths "<its screen's route>" --levels <its concurrency> --requests 24 --build-config <Release or Debug, as booted> --json-out tests/.artifacts/verify/perf/<REQ>.json`
39
+ Open every failing screenshot under `tests/.artifacts/verify/screens/` and look at it before step 6.
40
+ 6. `bash .tfcore/utils/tf-verify-verdict.sh {App} --apply --started <the step-0 time>` applies the seven checks in order, writes the ledger and rewrites the Status, % and Remarks cells of every graded row, and prints the verdict table. Then `bash .tfcore/utils/tf-verify-emit.sh {App} --started <the step-0 time>` appends the gate records, one miss per newly failing row, and this run's record.
41
+ 7. `bash .tfcore/utils/tf-verify-boot.sh stop`.
42
+ 8. Run the status gate (`.tfcore/tasks/_status-update-gate.md`); its run record is the one step 6 wrote. A head that could not be driven is named under "Known blockers". When this run is the last step of a goal, write the sentinel (`.tfcore/tasks/_yolo-mode.md`). When the verifier was chained by a build or a fix, that caller runs the gate.
43
+ 9. Report: what was booted (head, rung, URL); rows by verdict; the first-failing-check counts; screens driven and screenshots; the parity coverage line (screens graded, failed, ungradeable, without a mockup); the speed lines only for rows that carry a budget; every not-verified row with its reason; the evidence folder; the next command from the gate.