Files
pi-skills/.agents/skills/evolve/references/pre-flight-schema-check.md
T

65 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Pre-Flight Schema Check — Stop Migration Cycles That Can't Succeed
Three phase-2 port migrations attempted in cycles 156-157 of this repo all failed for the same structural reason: the port surface projected ~5 fields, the consumer read ~13 fields. The migration would have silently dropped 8+ fields the consumer relied on. The lesson is captured in `docs/learnings/2026-05-13-bc-ports-narrowness-postmortem.md`. This document encodes that lesson as a pre-cycle check.
## When this gate runs
Before invoking `/rpi` on a cycle whose selected work matches any of:
- title contains `migrate`, `rewire`, `route through`, `port-ify`, `adapt to <Port>`
- title contains `<Module> → <Port>Port` or similar arrow syntax
- bd issue has label `migration` or `bc-port-adoption`
- selected_source is `harvested` and the queue entry has `kind: "migration"`
## Procedure
1. **Identify the port surface.** From the work title, locate the port type definition (typically `cli/internal/ports/<port>.go`). Extract the projected struct fields.
2. **Sample two consumer call sites.** From the work description, find the function or file being migrated. Grep for *its* current consumers — the call sites that depend on the data the migration will route through the port. Pick two representative ones (different files, different formatting/decision shapes).
3. **Field-fit comparison.**
```bash
PORT_FIELDS=$(grep -E '^\s+[A-Z][a-zA-Z0-9_]+\s+' cli/internal/ports/<port>.go | awk '{print $1}' | sort -u)
CONSUMER_FIELDS=$(grep -oE '\.[A-Z][a-zA-Z0-9_]+\b' <consumer-files> | sed 's/^\.//' | sort -u)
comm -23 <(echo "$CONSUMER_FIELDS") <(echo "$PORT_FIELDS")
```
The output is the set of fields the consumer reads that the port does NOT project.
4. **Decision.**
| Missing fields | Action |
|---|---|
| 0 | Migration is safe. Proceed with `/rpi`. |
| 12 | Migration with widening. Add the missing fields to the port + writer + tests in the same cycle. Mark mode `port-widen-and-migrate`. |
| 3+ | **Abort the migration cycle.** File a port-widening bd issue with the field list, log this cycle as `unchanged` with `selected_source: "pre-flight-schema-fail"`, return to Step 3 selection. |
5. **Special case — zero-data target.** If the consumer reads a field (e.g. `Target`) that exists in the type definition but *no actual entries in the production data store have that field set*, the consumer is dead-code-in-disguise. Don't widen — investigate whether the consumer was ever wired. Log as `selected_source: "pre-flight-dead-target"`.
## Why this check is cheap
A migration cycle costs ~15-30 minutes of /rpi (discovery + crank + validation). The pre-flight check is two `grep` calls and one `comm` — under 30 seconds. Catching even one bad migration pays for ~50 pre-flight runs. In this repo, three cycles spent on bad migrations (156, 157, plus the soc-0pku bead) would have been caught by 90 seconds of pre-flight.
## What this does NOT replace
This is a *cycle-selection* gate, not a code-quality gate. Validating the actual migration code still belongs to `/rpi` → `/validate`. The pre-flight only answers "is this migration even shaped right for the current port?"
## Worked example — cycle 157 (soc-0pku)
Selected work: `context_assemble.go → LoopReaderPort`
```
PORT_FIELDS=Number,Mode,Result,Commit,Milestone # 5
CONSUMER_FIELDS=Timestamp,Cycle,Target,GoalIDs,Result,Status,Sha,CanonicalSha,LogSha,GoalsPassing,GoalsTotal,Summary,Error # 13
Missing: Timestamp,Target,GoalIDs,Status,Sha,CanonicalSha,LogSha,GoalsPassing,GoalsTotal,Summary,Error # 11
```
Missing fields = 11. Threshold 3+. **Abort.** File port-widening issue: "Widen CycleEntry: +Timestamp,+Summary at minimum; full set deferred." Cycle is logged as `unchanged`, ladder advances.
This is exactly what cycle 161 ended up doing reactively — adding `StartedAt` and `Title` after the migrations bounced. With pre-flight, the widening would have happened *before* burning three cycles on doomed migrations.
## See also
- `docs/learnings/2026-05-13-bc-ports-narrowness-postmortem.md` — the source observation
- `references/scout-mode.md` — first-class result for cycles that intentionally don't execute
- `references/metronome-gate.md` — sibling cross-cycle gate