OPS-T1 decision gate — R-WP21 (kh-ast-S9 → RATIFIED-S10)
OPS-T1 decision gate — R-WP21 (kh-ast-S9 → RATIFIED-S10)
Section titled “OPS-T1 decision gate — R-WP21 (kh-ast-S9 → RATIFIED-S10)”Status: RATIFIED-S10 — Liam selected option (c) only at S10 Wave 0 close. Source: ROADMAP Wave 5 R-WP21 +
type-safety-pipeline/PRODUCT.mdD-19 + R-WP17 output. Inputs:docs/generated/type-drift-report.md(full report) anddocs/generated/type-drift-baseline.json(37 fetcher-only interfaces). S10 binding output:investigations/S10-wave-0-synthesis.md§3.1.
Empirical picture (from R-WP17, baseline 187df260)
Section titled “Empirical picture (from R-WP17, baseline 187df260)”- 37 fetcher-only interfaces — declared in
types/*.tsorlib/query/fetchers.ts, consumed by ≥1 fetcher generic, with no matching route handler return-type annotation. - 2 route-only, 0 enforced, 21 unused — total 60 candidate interfaces.
- Subsystem density (by
/api/<subsystem>/prefix of the fetcher URL):
| Subsystem | fetcher-only count |
|---|---|
intelligence | 20 |
admin | 8 |
review | 5 |
tags | 3 |
items | 2 |
coverage | 2 |
bids | 2 |
source-documents | 1 |
pipeline-runs | 1 |
notifications | 1 |
entities | 1 |
digest | 1 |
| total | 47 call sites / 37 interfaces |
The intelligence subsystem alone accounts for 54% of the drift surface (20/37). Review + admin contribute another 35% (13/37). The remaining 11% is scattered across 9 subsystems with 1–3 interfaces each.
Both canonical false-negative fixtures from PRODUCT.md D-18 surface as fetcher-only: TaxonomySyncStatus (declared in lib/query/fetchers.ts) and ReviewStatsResponse (cast manually at app/api/review/stats/route.ts:76).
Options
Section titled “Options”(a) OPS-T1 defineRoute() wrapper rollout now
Section titled “(a) OPS-T1 defineRoute() wrapper rollout now”Author a typed defineRoute(ResponseSchema, handler) wrapper that enforces structural symmetry between the schema and the handler return. Migrate all ~193 KH routes to consume it.
- Effort: ~16–24h (2–3 days per the brief).
- Coverage: Closes Gap 1 at the root for the entire codebase, including the 156 routes that are currently
enforced=0 / route-only=2 / fetcher-only=37 / unused=21(out of ~60 named-interface routes; the other ~133 routes use anonymous payloads or have no client-side type). - Risk: large diff surface; merge contention with main-track route work; requires retraining authors on the wrapper.
- Leverage: the structural fix prevents future drift across all subsystems uniformly.
(b) Per-route annotations incrementally
Section titled “(b) Per-route annotations incrementally”For each fetcher-only interface, add an explicit return-type annotation to the handler: export async function GET(): Promise<NextResponse<X>>. Or migrate the cast site (return NextResponse.json(payload)) to return NextResponse.json<X>(payload) where the SDK supports the generic.
- Effort: ~30 min per interface × 37 ≈ 18.5h worst case. In practice ~4–6h for a single executor on the intelligence cluster (20 routes share the same hook pattern); another ~2–3h for admin + review; another ~2h for the long tail. Total realistic: 8–11h.
- Coverage: closes the 37 known gaps. New routes are NOT prevented from regressing.
- Risk: small per-route diffs; minimal merge contention.
- Leverage: lower than OPS-T1 in the long run, but parallelisable and incremental.
(c) Accept gap + use R-WP17 detector as CI gate
Section titled “(c) Accept gap + use R-WP17 detector as CI gate”Lock the current baseline (docs/generated/type-drift-baseline.json — already populated with the 37 rows at S9 close). PR CI runs bun run ast-dataflow type-drift-detect --ci. Build fails if any NEW fetcher-only interface is introduced without baseline update. The 37 existing gaps are tolerated as documented debt.
- Effort: ~1h to add the CI step to
.github/workflows/ci.yml(mirrors theschema-parity.ymlpattern). - Coverage: prevents regression. Does NOT close the existing 37 gaps.
- Risk: the baseline becomes a moving target if not actively shrunk.
- Leverage: highest ROI per hour for the prevention axis; zero for the remediation axis.
Recommendation
Section titled “Recommendation”Hybrid (b)+(c) over a 2-wave sequence, defer (a) until evidence demands it.
Wave A — incremental annotations on the intelligence cluster (proposed S10 work)
Section titled “Wave A — incremental annotations on the intelligence cluster (proposed S10 work)”- Scope: 20
fetcher-onlyinterfaces underapp/api/intelligence/**/route.ts. - Effort: ~4–6h single-executor (hook pattern is uniform across the cluster).
- Outcome: drift surface drops from 37 → 17 (54% reduction).
Wave B — CI gate ratchet (proposed S10 same session OR S11)
Section titled “Wave B — CI gate ratchet (proposed S10 same session OR S11)”- Add
type-drift-parityjob toci.yml. Style reference:.github/workflows/schema-parity.yml. - Baseline at session merge enforces 17 (or 37 if Wave A defers). PR adding a new fetcher-only interface fails CI unless the baseline is explicitly updated in the same PR.
- Effort: ~1h.
- Outcome: regression prevention; debt ledger.
Why defer (a) OPS-T1
Section titled “Why defer (a) OPS-T1”- The 37-interface surface is not large enough to justify ~3 days of refactor risk. Per-route annotation cost is ~30 min × 37 ≈ 18.5h worst-case, comparable to OPS-T1 effort, but with far smaller diff blast radius and zero retraining cost.
- Main-track MCP cleanup is the higher-leverage parallel work. OPS-T1 would compete for main-track attention; deferring keeps the bandwidth available.
- The CI gate alone (option c) is the dominant lever for preventing future drift. Pairing it with per-route annotations on the worst cluster (intelligence) addresses both the existing debt and the regression axis without taking on a 3-day structural refactor.
- R-WP17 detector becomes a permanent invariant. Whether or not OPS-T1 lands, the detector is the source of truth — the structural fix is one way to enforce the invariant, the detector + CI is another. Either is acceptable; (a) is not uniquely necessary.
Re-evaluation trigger
Section titled “Re-evaluation trigger”If, after Wave A + Wave B land, the drift surface grows beyond the post-Wave-A baseline by ≥10 interfaces (e.g. 17 → 27) within two sessions, re-open the OPS-T1 decision. The structural fix becomes load-bearing only once the per-route annotation cost compounds.
Sign-off
Section titled “Sign-off”-
Liam ratifies the recommendation (hybrid b+c).— superseded -
OR Liam selects (a) and the S10 work package shifts to— deferreddefineRoute()rollout. - Liam selects (c) only — Wave-A (b) dropped; Wave-A’ (a) deferred to S11+ codemod-led rollout. — RATIFIED-S10
Decision rationale (S10): Wave 0-A confirmed 96.4% route survival (no
OPS-T1 scope reduction available). Wave 0-C confirmed OPS-T1 itself is not
mechanisable under the current ast-dataflow + gitnexus + ccc toolchain —
needs a sibling scripts/codemods/wrap-define-route.ts ts-morph utility.
Liam’s binding direction: instead of investing 4-6h in tactical Wave-A
annotations that get partially overwritten by future OPS-T1, reserve S11
to author PRODUCT.md + TECH.md for the codemod utility so the eventual
OPS-T1 rollout aligns with main-track Phase 1 canonical-pipeline timing.
S10 close-out tracker:
- R-WP17 CI gate (option c): shipped S10 WP1 (
c99f5953). - R-WP21 row: marked RATIFIED-(c)-S10 in ROADMAP.
- OQ-R8 row: DECIDED-S10 (c-only).
- OQ-R9: still deferred —
S10-wave-0-synthesis.md§3.2. - S11 codemod authoring WP: see
S10-wave-0-synthesis.md§4.1.