preflight · git:20260801.012f6e0 · 2026-08-01 · sha256 3da02c1fcb799666
preflight git:20260801.012f6e0B
Immutable. This exact content is served forever at /api/v1/blob/3da02c1fcb799666.
--- description: "Executed pre-completion checklist — third sibling of /prospect (before execution) and /retrospect (after shipping). Six checks, three outcomes each (PASS/FAIL/INVALID); an unrun check blocks the verdict. Catches failures that emit no error: a check whose FORM bounds what it finds, and a decision correct in isolation but wrong in context. Use BEFORE claiming done/fixed/shipped/verified/'none found', before a PR, or before posting a result on a ticket. Triggers: '/preflight', '/preflight ticket <id>', '/preflight file <path>'." argument-hint: "[<scope>] [<scope-arg>] [--claim \"<sentence>\"] [--quick]" allowed-tools: Read, Write, Edit, Glob, Grep, Bash, WebFetch, WebSearch --- # /preflight — the checklist you run before you report `/prospect` looks forward at a plan. `/retrospect` looks back at shipped work. **This runs at the moment in between: you believe you are done, and you are about to say so.** ## What this is NOT **It is not `superpowers:verification-before-completion`,** which owns the principle — *no completion claim without fresh verification evidence* — and owns it well. That skill answers **whether** to verify. This one answers **what to verify**, and specifically: the checks whose *absence produces no signal at all*. If you have not internalised the Iron Law, read that skill first. This one assumes it and adds the cases where you ran a verification, it passed, and the thing was still broken. ## The two failure families this exists for Every check below descends from one of these. Neither produces an error, a failing test, or a warning. 1. **The check's FORM bounds what it can find.** A search whose pattern bounds its result; a review whose scope bounds its result; a probe whose anchor bounds its result. The instrument reports truthfully about the thing it measured — and you asked it the wrong question. 2. **A decision correct in isolation, wrong in context**, where nothing you looked at was measuring the part that broke. A green build measures compilation, not reachability. A docstring records a decision without disclosing it. A model's name states one job, not both. > **The consequence they share: the absence of a problem signal is not evidence there is no problem.** > Family 1 because your check couldn't see it. Family 2 because nothing was checking. ## How to run it Work through the checks that apply to your change. **Every applicable check gets a recorded result.** An unrun check is not a pass — it blocks the verdict. Each check has **three** outcomes, never two: | | | |---|---| | **PASS** | the check ran and the property holds | | **FAIL** | the check ran and the property does not hold | | **INVALID** | the check could not distinguish absence from a broken probe — **report no verdict, fix the probe** | The third outcome is the whole point. A probe that returns "nothing found" because it was pointed at a path that does not exist reads exactly like a clean result. --- ## P1 — Requirements diff · *does what shipped match what was asked?* **A rationalisation in code is not a disclosure.** Nobody diffs docstrings against requirements at review, so an omission explained in the source is invisible in exactly the place it needed a second opinion. 1. List every field, endpoint, behaviour and response shape the request named. Mark each **shipped / not shipped**. Mechanical; it takes a minute. 2. Every "not shipped" goes in a **ticket comment** — not a docstring, not a commit message, not a PR body. 3. **"There is nowhere to store this" is a question, not a decision.** Say so and stop. Do not narrow the deliverable to fit the schema you found — that is the signal the request and the schema disagree, and that is the owner's call. 4. Check what the **UI promises** against what you returned. A surface labelled with a word the data cannot support is a defect even when every line is correct. 5. Where you substituted something, **name the substitution.** "Returned the resolved email instead of the requested username" is reviewable; silence is not. 6. **Enumerate the paths, don't re-read the diff.** A fix can close a disclosure on two code paths and leave it open on a third — re-reading the change will not show you the path you never touched. --- ## P2 — Consumer census · *does this symbol have a second job?* **A model's name and fields do not tell you its jobs.** A row can be a record *and* a permission at the same time; a delete that is obviously right for the first is a silent privilege change for the second, and nothing in the model, the migration or your endpoint will mention it. ```bash # every consumer, minus vendored code and migrations grep -rn "<SYMBOL>" --include='*.<ext>' . | grep -v "/<vendor_dir>/\|/migrations/" # of those, which sit inside an authorization decision? grep -rn "<SYMBOL>" --include='*.<ext>' . | grep -viE "test|migration" \ | grep -iE "allow|permission|access|authoriz|can_view|visibility|entitle" ``` 1. Run it **before** writing the endpoint, not after. 2. **Write out what each consumer uses it for.** The census is worthless unread; writing it is what surfaces the surprise. 3. If any consumer is an authorization or access check, **a delete on that model is a permission change** — say so explicitly and get it ruled on rather than shipping it inside a feature. 4. Ask what the **absence** of a row means, not just its presence. Where presence grants something, deletion revokes it — and "cancel" is then the wrong word for the button. 5. **Beware the inverted fix.** On discovering a row doubles as a grant, do not "clean up" by deleting it at some lifecycle point. That revokes access. The fix is usually a new column or a derived flag. 6. **Aliasing:** can two rows point at the same underlying resource? If so, deleting A's "old" resource may destroy B's live one. --- ## P3 — Reachability · *is it actually wired in?* **A green build measures compilation, not reachability.** A component nothing imports is tree-shaken out: it compiles, it deploys, and it is not in the product. **Frontend** ```bash # does anything import it? zero here = the feature does not exist grep -rn "<COMPONENT>" src --include='*.js' | grep -v "<COMPONENT>/" ``` After deploy, read the **source map's module list**, not the bundle's strings — a string search cannot distinguish "not deployed yet" from "deployed but tree-shaken." Both return zero. **Always include a control module from the same page**, or "absent" is indistinguishable from a broken probe. **Backend / any callable** Ask what makes it reachable, then verify *that*: a route table entry, a registered webhook, a scheduled job actually installed, a feature flag. **Include a control** — a sibling you did not touch — so a zero result is interpretable. ⚠ **A test that calls a function directly does not exercise its routing.** If your suite invokes the handler rather than the URL, the suite stays green with the route deleted. That is a real gap, not a pedantic one; check the wiring separately and say which you verified. ⚠ **Never write "build green" or "tests pass" as evidence of shipping.** Say "imported and present in the bundle," or "routed at `<path>`," or make no claim. --- ## P4 — Census bound · *what could this search NOT have found?* **Report the instrument, not just the result.** A census reports its own bound or it is not a census. 1. **Run ≥2 idioms.** A symbol may be reached by a bare name, a dynamic dispatch, a `**kwargs` spread, a re-export, or a differently-quoted string. One idiom returning zero is not absence. 2. **Census by BEHAVIOUR, not by name.** Names move; behaviour is what you care about. Resolve an argument back to its assignment rather than pattern-matching the call. 3. ⛔ **`grep | wc -l` cannot report failure.** A missing path, a genuine absence and a real zero all print `0`. Read grep's **exit code**, or verify the path first. 4. **Discount definitions, re-exports and tests.** A symbol existing is not a symbol running; what remains after those are removed is the answer. 5. **State the bound in the report.** "Censused X across A, B, C excluding D — this is a code census, not a data census." A bound stated is a bound a reader can challenge. 6. **A handed-over finding list is a LOWER bound.** Re-census in your own consumer. --- ## P5 — Non-vacuity · *did the test actually execute the code?* **The most dangerous green is the one that ran nothing.** A request refused upstream, a fixture missing a field, an exception swallowed into a success envelope — the assertions never execute and every one of them passes. 1. **Assert a POSITIVE signal that the code ran**, not merely the absence of a failure: a call count, a success envelope, an observable side effect. Put it in the shared fixture so it protects every test, not the one that happened to notice. 2. ⚠ **An HTTP status is not that signal** where the codebase wraps failures in a 200 envelope. Check the envelope's own success field. 3. **Scope the guard to the right unit.** A blanket "this was never called" can fail for a reason unrelated to the property under test — and, worse, can pass while the defect is live if the defect operates by a different route. 4. **Ask what ELSE produces this outcome.** An outcome consistent with your hypothesis is not the cause. Was the precondition for failure ever actually met? --- ## P6 — Mutation · *have you SEEN the guard go red?* **Never cite a gate you have not watched fail.** This applies to guards you wrote *and* guards you inherited. 1. Break the thing the guard protects. Confirm it goes red, and that it reds **for the stated reason** rather than erroring. 2. **Restore from a byte backup taken before the mutation**, then `cmp`. Do not restore with version control if the working edit is uncommitted — that discards it. 3. Check the kill lands in the **right scope**. A mutation in module A reddening only module B's tests means the two are coupled, or one is inert. 4. ⚠ **On a scripted multi-file edit, read the insert/delete ratio.** For a purely additive change, deletions should be zero or exactly the lines you meant to rewrite. A net-negative diff on an additive edit is a first-class signal that something was dropped. --- ## Output Emit a compact table, then the verdict. Do not bury a FAIL in prose. ``` PREFLIGHT — <scope> P1 requirements diff PASS 3 named / 3 shipped P2 consumer census FAIL ProfileInvites is read by 2 authz views — delete = permission change P3 reachability PASS routed at /api/x, control /api/y present P4 census bound PASS 2 idioms, exit codes read; CODE census, data not checked P5 non-vacuity PASS envelope asserted + 5 size-writes observed P6 mutation INVALID probe restored from git, working edit lost — redo from byte backup VERDICT: NOT READY — 1 FAIL, 1 INVALID ``` **Any FAIL or INVALID ⇒ do not make the claim.** Fix, or state the limitation explicitly in the same sentence as the claim. An INVALID is not a soft pass — it means you do not know. **Skipped checks are listed as skipped, with why.** "N/A — no model touched" is a result. Silence is not. ## Recording Write the table to `knowledge/logs/preflight/<date>-<scope>.md` when the change is non-trivial, and run aria's standard intake. A preflight that found something is a candidate insight — the pattern that produced the FAIL is usually more reusable than the fix. ## Composes with - **`superpowers:verification-before-completion`** — owns the Iron Law. Run it; this adds the *what*. - **`/prospect`** — same discipline, earlier: before the work rather than before the claim. - **`/retrospect`** — after shipping. A FAIL here that you shipped anyway belongs there. - **Rule 22 / Rule 38** — per-edit scope, and closing the class rather than the instance. > **Why a skill and not a rule.** Every check here descends from a rule someone already knew. The > failures happened anyway — including one where a defective probe was run within an hour of reading > the note warning against it. Reading a rule does not execute it. **Run the checks.**