OPS-T1 codemod — wrap-define-route — Implementation PLAN
OPS-T1 codemod — wrap-define-route — Implementation PLAN
Section titled “OPS-T1 codemod — wrap-define-route — Implementation PLAN”Status: AMENDED-S265 (Option-4 re-scope, Liam-ratified S262). The S11/S62C decomposition (Subtasks 32.5–32.16) is retained as HISTORICAL / SUPERSEDED- mechanics (§4 — the wrapping / classification / inference / binding machinery it built is SOUND and is REUSED); the Option-4 amendment adds §0 (the continuous real-corpus probe mandate, the single most important S262 process fix) and three new implementation Subtasks ({32.25}/{32.26}/{32.27}, §4a) that re-scope the deliverable from a compile-time codemod to a runtime strict-validation model. What changed: the
defineRoutecontract (payload-returning → pass-through), schema strictness (permissive.loose()→ INV-S strict), and the AC framing (§8 reinterpreted). What did NOT change: the codemod mechanics (classify → infer → wrap → emit). Source:ops-t1-codemod/PRODUCT.md(AMENDED-S265 — §8 INV-PT / INV-FP / INV-S + the reframed AC-1..AC-10),ops-t1-codemod/TECH.md(AMENDED-S265 — §2.4a pass-through contract, §2.4a.2 INV-FP failure policy, §3.1a schema- strictness rules, §11 continuous-probe mandate, §12 empirical verification),ops-t1-codemod/route-shape-inventory.md;docs/reference/test-philosophy.md(criteria 1–6 for testStrategy lines). Sibling:ops-t1-codemod/PRODUCT.md,ops-t1-codemod/TECH.md.
0. Continuous real-corpus probe mandate (the governing S262 process fix)
Section titled “0. Continuous real-corpus probe mandate (the governing S262 process fix)”This is the single most important process fix from the S262 saga, and the process change that would have caught defects B1–B4 on the first slice that introduced each, rather than at the end. PRODUCT §8 and TECH §11 both make this binding; this §0 is the PLAN-side restatement that PRODUCT §8’s “see PLAN §0” cross-reference resolves to. Mirrors TECH §11 verbatim in intent.
The failure it fixes. In S262 the real-corpus acceptance gate — apply the
codemod against the live app/api corpus, then run the route unit suite — ran
LAST, as a final gate (the original Subtask 32.16). Because it ran last, all
four defects surfaced only at the end:
- B1 —
withRequestContextsubstring false-positive (the codemod matchedwithRequestContextBareby substring; AC-7 now mandates an EXACT-callee match). - B2 — Source-A inference produced 0/195 real schema binds on disk (the
${interface}Schemalookup silently fell through toz.unknown()). - B3 — rewritten routes were missing their
z/ schema imports → 130+ routes threwReferenceErrorat module load (ESLint is blind tono-undefon TS, so the lint gate did NOT catch it — AC-9 now pairs the lint-delta with a tsc/no-undef gate). - B4 — the payload-returning
defineRoutecontract double-wrapped the ~178 routes that returnNextResponseinline → 1676/2075 route tests failed (the contract is now pass-through, INV-PT).
The mandate (binding on {32.25}/{32.26}/{32.27}). The real-corpus probe runs from the FIRST implementation slice onward, NOT as a final gate:
- The probe. Apply the codemod
--applyagainst a temp copy of the working tree (git archive HEAD | tar -xinto anmkdtempSyncdir withnode_modulessymlinked — the working tree is NEVER mutated), then run the route unit suite (vitest run __tests__/api) against the migrated copy, under the pass-through wrapper (INV-PT) so inlineNextResponseerror returns pass through unchanged. - Run it per slice, continuously. Every implementation Subtask runs the
probe as it lands — {32.25} to prove the pass-through redesign does not regress
the route suite (the B4 reinterpretation); {32.26} to surface any pre-existing
real drift the tightened schemas now reject, under the INV-FP loud-in-test
policy (S262 OQ-1); {32.27} as the green-on-all-AC closing assertion. Each
Subtask’s
details(intask-list.json) repeats this mandate. - It is the AC-8 oracle. AC-8 (PRODUCT §8.1) is, definitionally, this probe’s route-unit-suite pass under the pass-through wrapper. Do not treat AC-8 as a separate, after-the-fact check.
Reframing note for §§1–8 below. Sections 1–8 describe the SUPERSEDED S11/ S62C decomposition (Subtasks 32.5–32.16). They are preserved as the historical record of the SOUND mechanics now reused by the Option-4 Subtasks. The LIVE decomposition is §4a (Subtasks {32.25}/{32.26}/{32.27}) and the LIVE traceability matrix is §5a. Read §4a + §5a as authoritative; read §§1–8 as context for what was already built.
1. Decomposition overview — SUPERSEDED (S11/S62C historical record)
Section titled “1. Decomposition overview — SUPERSEDED (S11/S62C historical record)”SUPERSEDED-S265. This section and §§2–8 describe the original S11/S62C decomposition (Subtasks 32.5–32.16). Subtasks 32.5–32.16 SHIPPED (per the S262 id32 final report) and built the codemod mechanics — classify, infer, wrap, emit — which are SOUND and REUSED under Option-4. The Option-4 re-scope did NOT discard this work; it changed the
defineRoutecontract (payload-returning → pass-through, §4a {32.25}), schema strictness (permissive → INV-S strict, §4a {32.26}), and the AC framing (§4a {32.27}). The LIVE decomposition is §4a; the LIVE traceability matrix is §5a. Treat the records below as history, not as the dispatch plan.
ID-32 was originally decomposed into 12 implementation Subtasks (32.5..32.16) plus the 4 already-recorded spec Subtasks (32.1 RESEARCH ✓, 32.2 PRODUCT ✓, 32.3 TECH ✓, 32.4 PLAN — this document; the Option-4 amendment is Subtask 32.23, which authored §0 + §4a + §5a here).
Decomposition principles applied:
- One mechanism per Subtask. Each Subtask owns a single load-bearing mechanism (scaffold, classifier, inference Source A, rewrite, etc.) so that Checker review is bounded and rollback is per-Subtask.
- Single-method vs multi-method split. Subtask 32.10 covers the 137
MECHANISABLE single-method routes; Subtask 32.11 covers the 40 NEEDS-REVIEW
multi-method routes. Separated because multi-method logic is structurally
distinct (per-method schema inference, per-method wrap loop, per-method
codemod-needs-manual.jsonentries). - Verifier kept end-to-end. Subtask 32.15 wires
type-drift-detectas the post-migration gate per PRODUCT §7; not folded into 32.16’s acceptance probe because the verifier integration is a discrete contract surface (CLI args, exit codes, baseline update procedure). - Acceptance gate as its own Subtask. Subtask 32.16 runs AC-1..AC-10 end-to-end against the full 193-route corpus. Separated so AC failure can be attributed to a specific upstream Subtask without entangling the diagnosis with implementation work.
- All Subtasks sibling-only. No Subtask in 32.5..32.16 references any external Task — the Task-level dep on ID-31 is set at the Task record.
Subtask count: 12 implementation + 4 spec = 16 total within Task 32. Well within the 25-Subtask soft ceiling (§3.4 / A7).
2. Critical-path graph
Section titled “2. Critical-path graph”32.5 (scaffold + defineRoute wrapper) │ ├──> 32.6 (shape classifier) │ │ │ └──> 32.7 (test fixture corpus, 14 fixtures) │ │ │ ├──> 32.8 (ResponseSchema inference Source A) │ │ │ │ │ ├──> 32.9 (ResponseSchema inference Source B — optional) │ │ │ │ │ ├──> 32.10 (handler rewrite — single-method) │ │ │ │ │ │ │ └──> 32.13 (idempotency check) │ │ │ │ │ │ │ └──> 32.14 (apply mode + format pass) │ │ │ │ │ │ │ ├──> 32.15 (verifier integration) │ │ │ │ │ │ │ │ │ └──> 32.16 (AC-1..AC-10 gate) │ │ │ │ │ │ │ │ │ └──> 32.11 (handler rewrite — multi-method) │ │ │ │ │ └──> (joins into 32.13 above) │ │ │ └──> 32.12 (CLI + output artefact emitters) │ │ │ └──> (joins into 32.14 above)Parallelisable slices (within Task 32):
- 32.8 + 32.9 + 32.12 — once 32.7 lands, inference Source A, inference Source B, and the CLI/emitter Subtasks can be assigned to parallel Executors. None of them depend on each other.
- 32.10 + 32.11 — once 32.8 lands, single-method and multi-method rewrites can run in parallel (different files, different shape buckets).
Serial-only choke points:
- 32.5 → 32.6: classifier needs the
defineRouteimport target to test against. - 32.7 → 32.10/32.11: rewrite logic is exercised through the fixture set.
- 32.14 → 32.15: verifier integration runs against post-apply state.
- 32.15 → 32.16: acceptance gate runs after verifier confirms no regression.
3. Risk register
Section titled “3. Risk register”| ID | Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|---|
| R-1 | defineRoute() wrapper authoring (32.5) blocks every downstream Subtask. PRODUCT §9 declares it OOS for the codemod, but TECH.md and ACs require it to exist before any fixture test can run. | High | High | Subtask 32.5 folds the wrapper authoring into the scaffold Subtask as a hard prereq. OQ-1 escalates to Liam: fold-in vs separate task. If Liam prefers separate, sub-orchestrator splits as a new Task ID-32a pre-32.5. |
| R-2 | ResponseSchema name-convention lookup (Source A) fails for interfaces lacking a co-located Schema constant. TECH §3.A trade-off explicitly flags this. | Medium | Medium | Subtask 32.8’s testStrategy includes a fixture (with-schema-in-baseline.ts) for the happy path and a with-baseline-but-no-schema-constant.ts fixture for the placeholder fall-back. AC-5 only requires Source-A routes with a discoverable schema constant to receive a real schema. |
| R-3 | withRequestContext double-wrapping order regression. TECH §8.1 spells out that the outer wrapper order is request-context-propagation-sensitive. | Medium | High | Subtask 32.10’s testStrategy includes the auth-plain-with-wrc.ts fixture; AC-7 explicitly asserts the wrap order. Checker for 32.10 must verify against TECH §8.1 verbatim. |
| R-4 | bun run test regression after apply (AC-8) caused by ts-morph print-order changes or import-ordering drift not caught by 32.14’s format pass. | Medium | Medium | Subtask 32.14 runs bun run format against all modified files before save; Subtask 32.16’s AC-8 gate runs the full Vitest suite as the regression check. If new failures surface, Checker triages whether the codemod or pre-existing tests are responsible. |
| R-5 | type-drift-detect --ci (AC-10) fails post-apply because the baseline was not updated to reflect closed gaps. | Low | Medium | Subtask 32.15’s testStrategy is the verifier-integration walk-through (run —pretty, edit baseline, run —ci, confirm exit 0). Subtask 32.16 includes an explicit baseline-diff check as part of the AC-10 gate. |
4. Per-Subtask records
Section titled “4. Per-Subtask records”Test-invocation convention (applies to every Subtask 32.5..32.16 below that invokes the test suite): Run tests via
bun run test(Vitest) — NOTbun test(Bun’s built-in runner produces a Vitest config mismatch). Per TECH §8.6 / CLAUDE.md “Gotchas — Testing”. Applies uniformly to every testStrategy in this section without per-Subtask repetition.
Subtask 32.5 — Scaffold codemod + author defineRoute wrapper
Section titled “Subtask 32.5 — Scaffold codemod + author defineRoute wrapper”Scope: Create scripts/codemods/wrap-define-route.ts skeleton (CLI argv
parse stub, ts-morph Project init, route enumeration with --scope filter,
exit-code 0/1 contract). Author lib/api/define-route.ts — the typed wrapper
function that downstream Subtasks rewrite handlers to call. The wrapper takes
a Zod ResponseSchema and an async handler; returns a Next.js-compatible
route export that validates the handler’s return payload at runtime against
the schema (or, at minimum, type-checks the handler’s return shape at
compile time).
File ownership:
scripts/codemods/wrap-define-route.ts— new file (skeleton only; no rewrite logic yet)lib/api/define-route.ts— new file__tests__/lib/api/define-route.test.ts— new test file (wrapper unit tests, separate from codemod fixture suite)package.json— only if a new script alias is needed (e.g."codemod:wrap-define-route")
Dependencies: none (root Subtask).
Acceptance criteria (PRODUCT.md mapping):
- AC-1 (dry-run completes without exception) — scaffold must at minimum enumerate the 193 routes and exit 0.
testStrategy: Vitest fixture __tests__/lib/api/define-route.test.ts
asserts that defineRoute(SchemaX, handler) returns a callable route export
which (a) invokes the handler with the (request, ctx) tuple, (b) validates
the handler’s return value against SchemaX, (c) returns a 500 envelope when
validation fails. Codemod-side: __tests__/scripts/codemods/wrap-define-route.test.ts
asserts the CLI invocation with --help prints usage and exits 0, and with
no args returns exit 0 against a temp fixture directory.
Subtask 32.6 — Shape classifier
Section titled “Subtask 32.6 — Shape classifier”Scope: Implement the classifyRoute(sf: SourceFile): RouteShape function
per TECH §2.3. Classifier inspects imports, exported HTTP-method declarations,
path-parameterisation ([ in path), body usage (request.json() /
parseBody(), and withRequestContext presence. Returns one of the 10
primary shape codes plus the +WRC sub-variant flag.
File ownership:
scripts/codemods/wrap-define-route.ts— addclassifyRoute()function andgetExportedMethods()helper.scripts/codemods/types.ts— new file forRouteShapediscriminated union andNeedsManualReasontype.
Dependencies: [5]
Acceptance criteria (PRODUCT.md mapping):
- AC-2 (apply rewrites exactly 137 MECHANISABLE) — classifier must correctly bucket routes; tested via fixture suite in 32.7.
- AC-4 (codemod-needs-manual.json lists 16 MANUAL + NEEDS-REVIEW) — classifier determines which routes flow into the report.
testStrategy: Vitest unit test classifyRoute() against synthetic
ts-morph SourceFile objects built from each of the 10 shape templates;
asserts the returned shape code matches expected per fixture. Each test title
reads as a behaviour line: “classifies a single-method auth-wrapped handler
with no params as AUTH_PLAIN”, “classifies a cron-secret route as CRON”, etc.
Per test-philosophy §1, assertions are on the returned shape value (public
return), never on internal classifier helpers.
Subtask 32.7 — Test fixture corpus (14 fixtures)
Section titled “Subtask 32.7 — Test fixture corpus (14 fixtures)”Scope: Author the 14 fixture files per TECH §4 at
__tests__/scripts/codemods/fixtures/wrap-define-route/. Each fixture is a
self-contained route.ts-shaped TypeScript file representing one shape
variant (plus the three inference / idempotency special cases). Each fixture
ships with a matching expected-output snapshot file for the post-rewrite
state (for MECHANISABLE / NEEDS-REVIEW) or a reason-code expectation for
MANUAL.
File ownership:
__tests__/scripts/codemods/fixtures/wrap-define-route/auth-plain.ts__tests__/scripts/codemods/fixtures/wrap-define-route/auth-plain-with-wrc.ts__tests__/scripts/codemods/fixtures/wrap-define-route/param-body.ts__tests__/scripts/codemods/fixtures/wrap-define-route/body-validated.ts__tests__/scripts/codemods/fixtures/wrap-define-route/param-only.ts__tests__/scripts/codemods/fixtures/wrap-define-route/multi-param-body.ts__tests__/scripts/codemods/fixtures/wrap-define-route/multi-body.ts__tests__/scripts/codemods/fixtures/wrap-define-route/multi-param.ts__tests__/scripts/codemods/fixtures/wrap-define-route/cron.ts__tests__/scripts/codemods/fixtures/wrap-define-route/naked-no-auth.ts__tests__/scripts/codemods/fixtures/wrap-define-route/mcp.ts__tests__/scripts/codemods/fixtures/wrap-define-route/already-wrapped.ts__tests__/scripts/codemods/fixtures/wrap-define-route/with-schema-in-baseline.ts__tests__/scripts/codemods/fixtures/wrap-define-route/with-return-type-annotation.ts__tests__/scripts/codemods/wrap-define-route.test.ts— initial test harness importing the fixtures and asserting shape classification per fixture.
Dependencies: [6]
Acceptance criteria (PRODUCT.md mapping):
- AC-1..AC-10 (downstream Subtasks exercise the full AC matrix through these fixtures).
testStrategy: Vitest fixture harness loads each fixture into a temporary
ts-morph Project, runs classifyRoute(), and asserts the returned shape.
Test titles read as product behaviour: “AUTH_PLAIN fixture classifies as
AUTH_PLAIN”, “auth-plain-with-wrc fixture classifies as AUTH_PLAIN+WRC”, etc.
Per test-philosophy §6, fixture content is generated by a factory helper
(createRouteFixture({ shape, methods, withRequestContext })) where
possible, not hand-rolled per file.
Subtask 32.8 — ResponseSchema inference Source A (type-drift-baseline.json)
Section titled “Subtask 32.8 — ResponseSchema inference Source A (type-drift-baseline.json)”Scope: Implement Source A inference per TECH §3.A. Read
docs/generated/type-drift-baseline.json; for each baseline entry, map the
interface name to the route file via the R-WP17 heuristic URL matcher (read
the existing logic in lib/ast-dataflow/queries/type-drift-detect.ts); look
up ${interfaceName}Schema or ${interfaceName}ZodSchema in
lib/validation/schemas.ts; return the schema identifier or fall back to
z.unknown() placeholder with a NEEDS-SCHEMA reason code.
File ownership:
scripts/codemods/wrap-define-route.ts— addinferSchema(sf, method, project)function with Source A logic.scripts/codemods/inference-source-a.ts— extracted helper module if the baseline-loading + interface-to-route mapping exceeds 50 LOC.__tests__/scripts/codemods/fixtures/wrap-define-route/with-baseline-but-no-schema-constant.ts— new fall-back-path fixture authored alongside Source A inference logic. Represents a route that IS intype-drift-baseline.jsonbut whose interface has NO co-located${interfaceName}Schemaconstant inlib/validation/schemas.ts; exercises thez.unknown()+ NEEDS-SCHEMA fall-back branch. Counterpart towith-schema-in-baseline.ts(the happy path) authored by Subtask 32.7. Note: 32.7’s fixture set is 14; this raises the total fixture count to 15 once 32.8 lands.
Dependencies: [7]
Acceptance criteria (PRODUCT.md mapping):
- AC-5 (R-WP17 37-interface baseline routes get real schema from
type-drift-baseline.json, notz.unknown()). - AC-6 (routes with
z.unknown()placeholder get the TODO comment).
testStrategy: Vitest test against fixture
with-schema-in-baseline.ts asserts that inferSchema() returns
IntelligenceWorkspaceListResponseSchema (the synthetic schema constant
declared in the test’s mocked lib/validation/schemas.ts). A second fixture
with-baseline-but-no-schema-constant.ts asserts the function returns
z.unknown() + NEEDS-SCHEMA reason code. Titles read as behaviour: “inserts
real schema when baseline interface has a co-located Schema constant”,
“falls back to z.unknown() placeholder when the schema constant is missing”.
Subtask 32.9 — ResponseSchema inference Source B (return-type annotation) — OPTIONAL
Section titled “Subtask 32.9 — ResponseSchema inference Source B (return-type annotation) — OPTIONAL”Scope: Implement Source B inference per TECH §3.B. If the handler carries
Promise<NextResponse<X>> return-type annotation, extract X and resolve
via the same name-convention lookup as Source A. If no annotation present,
return null (signalling fall-back to Source A). Per TECH §3 recommended
ranking, this Subtask is time-boxed: implement only if Subtask 32.8
landed and there is bandwidth in the implementation session. Source C is
explicitly out of scope for this PLAN cycle.
File ownership:
scripts/codemods/wrap-define-route.ts— addextractNextResponseTypeArg()and extendinferSchema()to chain Source B → Source A (canonical runtime precedence: Source B’s explicit return-type annotation is authoritative; Source A is the heuristic fall-through when no annotation is present — see TECH §3 “Canonical chain order”).scripts/codemods/inference-source-b.ts— extracted helper if needed.
Dependencies: [8]
Acceptance criteria (PRODUCT.md mapping):
- (AC-5 is the Source-A contract; Source B is nice-to-have and not AC-gated per TECH §3 recommended ranking.)
testStrategy: Vitest test against fixture
with-return-type-annotation.ts asserts that inferSchema() extracts the
inner type and returns the matching schema identifier when one exists.
A negative test asserts Source B returns null when no annotation is present,
so Source A’s result is used. If this Subtask is descoped at dispatch time,
the fixture is left in place and the test is marked it.skip() with a
TODO referencing this PLAN.
Subtask 32.10 — Handler rewrite — single-method shapes
Section titled “Subtask 32.10 — Handler rewrite — single-method shapes”Scope: Implement the rewrite logic per TECH §2.4 for the four
single-method MECHANISABLE shapes: AUTH_PLAIN, PARAM_BODY,
BODY_VALIDATED, PARAM. Includes the withRequestContext sub-variant for
each: preserves the outer wrapper per TECH §8.1 / AC-7. Targets
FunctionDeclaration AST nodes with names in
{GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS}; rewrites to
VariableStatement with export const METHOD = defineRoute(Schema, async (...) => { ... }).
Preserves Next.js 15 Promise<params> second-argument shape per TECH §8.2.
Skips export const maxDuration, dynamic, runtime config exports per
TECH §8.3.
File ownership:
scripts/codemods/wrap-define-route.ts— addrewriteSingleMethod(sf, method, schema)function.scripts/codemods/rewrite-single-method.ts— extracted helper if rewrite logic exceeds 80 LOC.
Dependencies: [8]
Acceptance criteria (PRODUCT.md mapping):
- AC-2 (137 MECHANISABLE routes get rewritten — single-method portion).
- AC-7 (
withRequestContextouter-wrap preservation). - AC-6 (TODO comment for
z.unknown()placeholder routes).
testStrategy: Vitest snapshot tests against each single-method fixture
(auth-plain.ts, auth-plain-with-wrc.ts, param-body.ts,
body-validated.ts, param-only.ts) assert the post-rewrite source text
matches an inline snapshot. Test titles describe the observable
transformation: “wraps an AUTH_PLAIN handler with defineRoute”, “preserves
withRequestContext as the outer wrapper”, “preserves the PromiseFunctionDeclaration internal state.
Subtask 32.11 — Handler rewrite — multi-method shapes
Section titled “Subtask 32.11 — Handler rewrite — multi-method shapes”Scope: Implement the rewrite logic for the three NEEDS-REVIEW
multi-method shapes: MULTI_PARAM_BODY, MULTI_BODY, MULTI_PARAM. Each
exported method is wrapped individually with its own per-method
ResponseSchema; the codemod emits one codemod-needs-manual.json entry
per method with reason code MULTI_METHOD_SCHEMA. withRequestContext
sub-variant handling reuses 32.10’s logic.
File ownership:
scripts/codemods/wrap-define-route.ts— addrewriteMultiMethod(sf, methods, schemas)function.scripts/codemods/rewrite-multi-method.ts— extracted helper if needed.
Dependencies: [10]
Acceptance criteria (PRODUCT.md mapping):
- AC-2 (40 NEEDS-REVIEW routes wrapped; flagged in
codemod-needs-manual.json). - AC-4 (NEEDS-REVIEW routes appear in the report with
MULTI_METHOD_SCHEMAreason).
testStrategy: Vitest snapshot tests against multi-method fixtures
(multi-param-body.ts, multi-body.ts, multi-param.ts) assert each
exported method is independently wrapped and each method generates its own
MULTI_METHOD_SCHEMA entry in the mock codemod-needs-manual.json. Test
titles: “wraps GET and PATCH independently in a MULTI_PARAM_BODY route”,
“emits one MULTI_METHOD_SCHEMA entry per method”.
Subtask 32.12 — CLI + output artefact emitters
Section titled “Subtask 32.12 — CLI + output artefact emitters”Scope: Implement the CLI per TECH §5 (argv parse via util.parseArgs,
--apply, --scope, --help flags, exit codes 0/1). Implement the two
output artefact emitters per TECH §6: docs/generated/codemod-dry-run.md
(human-readable markdown report) and docs/generated/codemod-needs-manual.json
(JSONL with the NeedsManualEntry schema). Both emitted on every run
(dry-run and apply). The markdown report follows the format shown in
PRODUCT §5; the JSONL follows TECH §6.2 schema.
File ownership:
scripts/codemods/wrap-define-route.ts— addparseCliArgs(),emitDryRunReport(routes),emitNeedsManualReport(routes).scripts/codemods/emit-dry-run.ts— extracted markdown emitter helper.scripts/codemods/emit-needs-manual.ts— extracted JSONL emitter helper.
Dependencies: [7]
Acceptance criteria (PRODUCT.md mapping):
- AC-4 (both artefacts produced in every run; MANUAL + NEEDS-REVIEW routes appear with reason codes).
- AC-1 (CLI runs to completion without error).
testStrategy: Vitest tests invoke the CLI via bun scripts/codemods/wrap-define-route.ts --help (asserts usage text + exit 0),
--scope <fixture-dir> (asserts only scoped routes are reported), and the
default dry-run (asserts both artefact files appear in
docs/generated/). Title: “emits codemod-dry-run.md and
codemod-needs-manual.json on every invocation”. For the JSONL emitter, a
unit test asserts the emitted entry shape matches the NeedsManualEntry
TypeScript type via parse() round-trip.
Subtask 32.13 — Idempotency check
Section titled “Subtask 32.13 — Idempotency check”Scope: Implement the already-wrapped detector per PRODUCT §4. Before
rewriting, scan the source file for a top-level defineRoute(...) call
inside an export const METHOD = ... assignment. If found, mark the file as
SKIPPED and add a // already wrapped entry to the dry-run report (no
entry in codemod-needs-manual.json).
File ownership:
scripts/codemods/wrap-define-route.ts— addisAlreadyWrapped(sf, method)function called at the start of the per-route rewrite loop.
Dependencies: [10, 11]
Acceptance criteria (PRODUCT.md mapping):
- AC-3 (idempotency: re-apply on an already-wrapped tree produces no further file changes).
testStrategy: Vitest test against fixture already-wrapped.ts asserts
that isAlreadyWrapped() returns true and the rewrite loop produces no
file modification (assert via sf.print() vs original text). A second test
runs the apply mode twice in sequence against the same fixture and asserts
the second run produces zero modifications. Title: “skips routes that
already use defineRoute()”; “second consecutive apply run produces no
further modifications”.
Subtask 32.14 — Apply mode + format pass
Section titled “Subtask 32.14 — Apply mode + format pass”Scope: Wire sf.save() into the rewrite loop behind the --apply flag.
After all routes have been saved, run bun run format against the modified
file set (or invoke ts-morph’s organizeImports() before save) per TECH
§8.5. In dry-run mode, sf.save() is skipped; only sf.print() vs original
text is reported.
File ownership:
scripts/codemods/wrap-define-route.ts— addapplyAll(routes)function andrunFormatPass(modifiedFilePaths)helper.
Dependencies: [13, 12]
Acceptance criteria (PRODUCT.md mapping):
- AC-2 (apply mode writes 137 routes; leaves 56 untouched on disk).
- AC-8 (
bun run testpasses after apply — no test regressions caused by ts-morph print artefacts). - AC-9 (
bun lintreports 0 errors after apply).
testStrategy: Vitest test runs wrap-define-route.ts --apply against a
temporary copy of the fixture corpus and asserts: (a) each MECHANISABLE
fixture file has changed on disk, (b) each NEEDS-REVIEW fixture has had
each exported method rewritten, (c) each MANUAL fixture is bit-identical to
its original. Title: “writes 137 MECHANISABLE routes to disk under
—apply”; “leaves MANUAL routes bit-identical under —apply”.
Subtask 32.15 — Verifier integration with type-drift-detect
Section titled “Subtask 32.15 — Verifier integration with type-drift-detect”Scope: Implement the verifier integration workflow per PRODUCT §7 /
TECH §7. After a successful --apply run, the developer-facing workflow is:
(1) run bun run ast-dataflow type-drift-detect --pretty for human review,
(2) update docs/generated/type-drift-baseline.json to reflect closed gaps,
(3) run bun run ast-dataflow type-drift-detect --ci and confirm exit 0.
This Subtask does NOT modify lib/ast-dataflow/queries/type-drift-detect.ts
(the verifier is already shipped); it documents the integration in a
README.md adjacent to the codemod and adds the workflow as a section in
docs/runbooks/ops-t1-codemod.md (new runbook file).
File ownership:
scripts/codemods/README.md— new file documenting the post-migration workflow.docs/runbooks/ops-t1-codemod.md— new runbook covering the developer workflow (dry-run → schema authoring → apply → verifier).
Dependencies: [14]
Acceptance criteria (PRODUCT.md mapping):
- AC-10 (
bun run ast-dataflow type-drift-detect --cipasses after the baseline is updated to reflect the closed gaps for the AC-5 routes).
testStrategy: End-to-end integration test against the full fixture
corpus: run codemod in dry-run, run apply, run type-drift-detect --pretty, update a stub baseline file, run type-drift-detect --ci, and
assert exit 0. Title: “type-drift-detect —ci passes after the codemod
applies and the baseline is updated”. The test exercises the verifier’s
public CLI interface end-to-end (per test-philosophy §1: behaviour, not
implementation).
Subtask 32.16 — Acceptance gate (AC-1..AC-10 end-to-end probe)
Section titled “Subtask 32.16 — Acceptance gate (AC-1..AC-10 end-to-end probe)”Scope: Author the end-to-end acceptance probe that runs the codemod
against the full 193-route corpus (in a working-copy worktree, not the
fixture set) and asserts each of AC-1..AC-10 in turn. This Subtask is the
final gate before the codemod is considered done. If any AC fails, the
diagnosis traces back to a specific upstream Subtask via the
Subtask → AC mapping in §4.
File ownership:
__tests__/scripts/codemods/wrap-define-route-acceptance.test.ts— new Vitest test file running the full corpus.- (Optional)
scripts/codemods/acceptance-gate.ts— CLI helper that runs the full corpus + AC checks for use by a developer outside the test harness.
Dependencies: [15]
Acceptance criteria (PRODUCT.md mapping):
- AC-1, AC-2, AC-3, AC-4, AC-5, AC-6, AC-7, AC-8, AC-9, AC-10 — all 10 ACs asserted end-to-end.
testStrategy: Vitest acceptance test asserts each AC line-by-line; each
it() block maps to one AC. Per-AC mode split:
- AC-1, AC-2, AC-3, AC-4, AC-5, AC-6, AC-7 — read-only audit against the
live
app/api/directory in dry-run mode; verifies post-rewrite text viasf.print()without saving any file to disk. Safe to run against the working tree. - AC-8, AC-9, AC-10 — apply-against-temp-copy mode. The test copies
app/api/(andlib/validation/schemas.ts,docs/generated/type-drift-baseline.json) into atmpdir()-scoped fixture corpus, runs the codemod with--applyagainst the temp copy (NOT the working tree), then runsbun run test+bun lint+type-drift-detect --ciagainst the temp copy. The temp directory is torn down at test exit. This isolation is required because AC-8 / AC-9 / AC-10 each measure a post-apply state that cannot be obtained from a pure dry-run.
Titles read as product specs: “AC-1: dry-run completes without exception
on 193 routes”, “AC-2: apply mode rewrites exactly 137 MECHANISABLE
routes”, etc. Per test-philosophy §5. The test runs under the existing
Vitest sharding configuration; the temp-copy ACs (AC-8/9/10) must not
exceed the 4-shard quality-test job budget — if they do, mark the
temp-copy block as a separate integration test under
__tests__/integration/.
4a. Per-Subtask records — LIVE Option-4 decomposition ({32.25}/{32.26}/{32.27})
Section titled “4a. Per-Subtask records — LIVE Option-4 decomposition ({32.25}/{32.26}/{32.27})”This is the authoritative dispatch plan under Option-4. The three Subtasks form a sequential chain (32.25 → 32.26 → 32.27): the contract redesign must land before strictness (the probe needs the pass-through wrapper to run the route suite without B4 double-wrap noise), and both must land before the gate re-finalise (the gate asserts all ten ACs green under the new model). All deps are sibling-only within Task ID-32. The full load-bearing dispatch briefs (file paths, function names, verify-X lines, spec-slice refs) live in the Subtask
detailsfields indocs/reference/task-list.json(owned by the orchestrator) — the records below are the PLAN-level overview + traceability.Every {32.25}/{32.26}/{32.27}
detailsfield MUST repeat the §0 continuous real-corpus probe mandate.
Subtask {32.25} — Redesign defineRoute as a pass-through validator
Section titled “Subtask {32.25} — Redesign defineRoute as a pass-through validator”Scope: Redesign lib/api/define-route.ts from the S11 payload-returning
contract to the Option-4 pass-through contract per TECH §2.4a. The wrapper
calls the handler, then branches on the return value: a returned
Response/NextResponse is passed through with status / headers / redirects /
streaming + non-JSON bodies UNCHANGED, validating ONLY the cloned JSON body of
2xx application/json responses (INV-PT); a raw (non-Response) payload is
validated then NextResponse.json-wrapped (the polymorphic minority arm). The
failure policy (INV-FP, TECH §2.4a.2) is fail-open in production, LOUD in dev +
CI + test — LOUD when NODE_ENV !== 'production' OR process.env.CI is set
(so a CI runner with NODE_ENV=production + CI=true FAILS LOUD), fail-open
ONLY when in production AND not CI. The S11 “returns a 500 envelope on mismatch”
test is reworked to assert the new split (throw under NODE_ENV=test;
original-response + logged drift under NODE_ENV=production). Drop / repoint the
dead silent-failure-prevention-spec.md reference in the file header (TECH §12).
File ownership:
lib/api/define-route.ts— pass-through redesign (adds the canonicalloggervalue import from@/lib/logger, direct file import per the no-barrel rule; keepsztype-only).__tests__/lib/api/define-route.test.ts— rework the 500-envelope test to the INV-PT pass-through + INV-FP environment-split assertions.
Dependencies: [] (root of the Option-4 chain).
Implements: INV-PT + INV-FP (PRODUCT §8.0). Reinterprets AC-8 (pass-through, not payload-returning).
Continuous probe: runs the §0 real-corpus probe to prove the redesign does NOT regress the route unit suite UNDER pass-through (the B4 reinterpretation).
testStrategy (one-line acceptance): define-route.test.ts proves the
pass-through split — a handler returning NextResponse.json({error},{status:401})
yields that exact 401 unchanged (not a re-wrapped 200, not a 500); a matching 2xx
JSON body passes unchanged; a 3xx / 204 / text/event-stream passes through with
no schema parse; a drifting 2xx body throws under NODE_ENV=test and returns the
original + logs under NODE_ENV=production; the §0 probe (vitest run __tests__/api
on a temp-copy apply) shows no regression under pass-through (verifies real behaviour
per docs/reference/test-philosophy.md).
Subtask {32.26} — Tighten the R-WP17 response schemas to strict (INV-S)
Section titled “Subtask {32.26} — Tighten the R-WP17 response schemas to strict (INV-S)”Scope: Rework scripts/codemods/generate-response-schemas.ts to derive
strictness from the real source interfaces per TECH §3.1a, then regenerate
the managed block in lib/validation/schemas.ts (the
BEGIN/END generated: R-WP17 ResponseSchema constants (ID-32.20) block, currently
lines 2314–3334). Eliminate the 84 .loose() + 10 z.unknown() in that
block: plain object literals → zod-4 default z.object({...}) (strips
additive wire fields, REJECTS a renamed/removed/retyped declared field — NOT
z.strictObject, which would 500 on a legitimately-added field); string-literal
unions → z.enum/z.literal; named-interface refs → recurse into the referenced
type. Retain z.unknown() ONLY for genuinely un-narrowable Json/opaque-DB/
external-generic properties, and .loose() ONLY where the source declares a real
[k: string]: T index signature — each such exception recorded on the
machine-checkable allow-list (per §3.1a) citing the source property.
Empirically ~zero R-WP17 interfaces have a genuine index signature, so the
.loose() exception should fire essentially never. Corpus is on zod ^4.4.3 —
use zod-4 method names (TECH §12).
File ownership:
scripts/codemods/generate-response-schemas.ts— strictness rules + allow-list emission.lib/validation/schemas.ts— regeneratedBEGIN/END generatedblock (~2314–3334).
Dependencies: [25] (the tightened schemas must run UNDER the {32.25}
pass-through + INV-FP loud-in-test policy so newly-rejected drift surfaces as a
loud test failure, not a silent prod 500).
Implements: INV-S (PRODUCT §8.0). Tightens AC-5 (real binds must additionally be STRICT — binding itself already shipped via 32.20/32.21/32.22).
Continuous probe: runs the §0 probe to surface any pre-existing real drift the tightened schemas now reject (S262 OQ-1), under INV-FP loud-in-test.
testStrategy (one-line acceptance): a static check over the
BEGIN/END generated block asserts zero .loose() and zero z.unknown() except
allow-listed entries each citing a real index-signature or opaque-Json/external
source property; a runtime check proves a schema with a renamed declared field
(item_count → itemCount) now FAILS safeParse where the pre-amendment
.loose() schema passed it; the §0 probe stays green (any surfaced drift is a
loud test failure to be triaged, not a silent pass).
Subtask {32.27} — Re-finalise the 32.16 acceptance gate under the Option-4 model
Section titled “Subtask {32.27} — Re-finalise the 32.16 acceptance gate under the Option-4 model”Scope: Recover the acceptance test pair from tag
s262-32.16-acceptance-gate, flip the it.fails(...) placeholders to live
assertions, and re-finalise the gate so all of AC-1..AC-10 (PRODUCT §8.1) are
GREEN under the Option-4 model. Retire the now-stale escalation canary (the
assertion that apply ABORTS on withRequestContextBare: the B1 fix means apply
no longer aborts, so that canary is dead — TECH §12). Add the AC-9
tsc/no-undef gate (a tsc --noEmit / no-undef check proving every emitted
defineRoute(...) has its z + schema imports resolved — the gate that would
have caught B3, which ESLint missed) and the INV-S static check (the
allow-list-justified .loose()/z.unknown() assertion from {32.26}).
File ownership:
__tests__/scripts/codemods/wrap-define-route-acceptance.test.ts(and/or the integration acceptance test under__tests__/integration/) — recovered froms262-32.16-acceptance-gate, un-it.fails’d, canary retired, AC-9 tsc/no-undef- INV-S static checks added.
Dependencies: [25, 26] (the gate asserts the pass-through contract AND the
strict schemas; both must be in place for all ten ACs to go green).
Implements: the full AC-1..AC-10 matrix under the Option-4 model; closes the de-vacuum’d AC-9 (lint-delta + tsc/no-undef) and the INV-S static check.
Continuous probe: the §0 probe is the AC-8 oracle here — the gate’s green-on-all-AC closing assertion IS the route-unit-suite pass under pass-through.
testStrategy (one-line acceptance): the recovered acceptance suite runs the
codemod against a temp-copy of the live app/api corpus and asserts each of
AC-1..AC-10 green under the Option-4 model — AC-7 EXACT-callee match (no
withRequestContextBare false-positive), AC-8 route suite green under
pass-through, AC-9 lint-delta after ≤ before AND tsc/no-undef clean (no B3
ReferenceError), plus the INV-S static check — with the stale escalation canary
removed and no it.fails remaining.
5a. Subtask-to-AC / invariant traceability matrix — LIVE (Option-4)
Section titled “5a. Subtask-to-AC / invariant traceability matrix — LIVE (Option-4)”Authoritative under Option-4. The §5 matrix below is the SUPERSEDED S11/S62C mapping for the historical 32.5–32.16 decomposition.
Invariant ownership (PRODUCT §8.0):
| Subtask | INV-PT | INV-FP | INV-S |
|---|---|---|---|
{32.25} pass-through defineRoute | ● | ● | – |
| {32.26} strict R-WP17 schemas | – | – | ● |
| {32.27} acceptance gate re-finalise | ◐ | ◐ | ◐ |
AC ownership (PRODUCT §8.1, reframed):
| Subtask | AC-1 | AC-2 | AC-3 | AC-4 | AC-5 | AC-6 | AC-7 | AC-8 | AC-9 | AC-10 |
|---|---|---|---|---|---|---|---|---|---|---|
{32.25} pass-through defineRoute | – | – | – | – | – | – | – | ● | – | – |
| {32.26} strict R-WP17 schemas | – | – | – | – | ● | – | – | – | – | – |
| {32.27} acceptance gate re-finalise | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● |
Legend: ● = primary owner; ◐ = verifies / asserts (the gate proves the
invariants hold end-to-end); – = not covered.
Dependency chain (sibling-only, all within Task ID-32):
{32.25} pass-through defineRoute (INV-PT + INV-FP) deps = [] │ └──> {32.26} strict R-WP17 schemas (INV-S) deps = [25] │ └──> {32.27} acceptance gate re-finalise deps = [25, 26] (all AC-1..AC-10 green; AC-9 tsc/no-undef + INV-S check)The mechanics from the SUPERSEDED 32.5–32.16 chain (classify → infer → wrap → emit; §§2–4) are REUSED by {32.25}–{32.27}; only the contract, strictness, and AC framing changed. No cross-Task Subtask dependency is introduced (the Task-level dep on ID-31 remains at the Task record per §7).
4b. ID-50 corpus-rollout decomposition — LIVE (S267, the working-tree rollout)
Section titled “4b. ID-50 corpus-rollout decomposition — LIVE (S267, the working-tree rollout)”Authoritative dispatch plan for Task ID-50 (the working-tree corpus rollout that ID-32 deliberately deferred). Authored by the ops-rollout sub-orchestrator (S267) from the {50.1} assessment (
ops-t1-codemod/ASSESS-S267.md). PRODUCT/TECH inherit unchanged from the ratified OPS-T1 specs — no new product surface. The load-bearing per-Subtask dispatch briefs live in thedetailsfields of ID-50 intask-list.json; the records below are the PLAN-level overview + sequencing rationale. Every wave repeats the §0 continuous real-corpus probe mandate.
Premise (gate-proven). Full --apply to the working tree = ~369 tsc errors =
118 TS2345 ctx-contravariance + the test-call-site signature bulk + 24 runtime RSVE
(7 files). All mechanical; working tree is currently GREEN with zero routes wrapped.
Scope. 195 routes → 132 TRANSFORM + 45 NEEDS_REVIEW = 177 wrapped; 18 MANUAL
excluded by the codemod. Retirement pre-filter carves out app/api/upload/route.ts
(§12.2 STILL-OPEN) → net 176. digest→change-reports + bid→procurement
renames confirmed landed; source-documents/[id]/diff RETAINED-v1 (§12.1).
Decomposition (sibling-only deps, NUMBERS):
| Subtask | Scope | deps | Green outcome |
|---|---|---|---|
| {50.3} | Generic defineRoute ctx type (Design B variadic-tuple primary / A overload-pair fallback; decide on temp-copy residual) — resolves 118 TS2345 | [] | define-route.test.ts + temp-copy probe: zero residual ctx TS2345 over migrated routes |
| {50.4} | Codemod-assisted test-call-site migration tooling (inject createTestRequest, derive path from import) | [3] | tool validated on temp copy; no working-tree wrapping |
| {50.5} | Wave A — items+workspaces+layers+tags (~33) wrap + migrate | [3,4] | scoped tsc+next build+probe green |
| {50.6} | Wave B — procurement (29) wrap + migrate | [3,4] | scoped tsc+next build+probe green |
| {50.7} | Wave C — intelligence+review+governance (~29) wrap + migrate | [3,4] | scoped tsc+next build+probe green |
| {50.8} | Wave D — admin+taxonomy+entities+coverage (~38) wrap + migrate | [3,4] | scoped tsc+next build+probe green |
| {50.9} | Wave E — long-tail remainder (~47, EXCL upload) wrap + migrate | [3,4] | scoped tsc+next build+probe green |
| {50.10} | Reconcile the 24 RSVE strictness-drift (7 named files; fix shapes, NEVER .loose()) | [6,7] | route suite runs LOUD-clean under strict schemas |
| {50.11} | Close — all-176-wrapped verify + AC-10 type-drift-detect --ci baseline-to-zero + document upload carve-out | [5,6,7,8,9,10] | full tsc+next build+knip+bun run test + AC-10 exit 0 |
Waves {50.5}–{50.9} own disjoint app/api/<seg> + __tests__/api/<seg> file
sets → parallel-eligible once {50.3}+{50.4} land; the orchestrator cherry-picks
sequentially.
Sequencing constraint (audit coordination — RATIFIED S267, OQ-50.2-B option a): ID-50’s call-site migration is the canonical first mover. It is a compile-correctness prerequisite, so the s37 test-audit’s W-RC/W-RD api-tree assertion rewrites MUST follow ID-50’s migration on the green tree (or rebase onto ID-50’s wrapped files) — a wrapped route’s test does not type-check until migrated, and W-RD cannot rewrite assertions in a non-compiling file. The s37 audit is NOT an active terminal as of S267 (live terminals: ID-9, ID-34/35, ID-50, ID-51) → no live parallel collision; this ordering is the standing rule whenever the audit is next scheduled.
5. Subtask-to-AC traceability matrix — SUPERSEDED (S11/S62C historical)
Section titled “5. Subtask-to-AC traceability matrix — SUPERSEDED (S11/S62C historical)”SUPERSEDED-S265. The LIVE matrix is §5a. This matrix maps the historical 32.5–32.16 decomposition to the pre-amendment 10-AC framing; retained for the record of what the shipped mechanics covered.
| Subtask | AC-1 | AC-2 | AC-3 | AC-4 | AC-5 | AC-6 | AC-7 | AC-8 | AC-9 | AC-10 |
|---|---|---|---|---|---|---|---|---|---|---|
| 32.5 scaffold + defineRoute | ◐ | – | – | – | – | – | – | – | – | – |
| 32.6 classifier | – | ◐ | – | ◐ | – | – | – | – | – | – |
| 32.7 fixtures | – | – | – | – | – | – | – | – | – | – |
| 32.8 inference Source A | – | – | – | – | ● | ● | – | – | – | – |
| 32.9 inference Source B (opt) | – | – | – | – | – | – | – | – | – | – |
| 32.10 rewrite single-method | – | ● | – | – | – | ● | ● | – | – | – |
| 32.11 rewrite multi-method | – | ● | – | ● | – | – | – | – | – | – |
| 32.12 CLI + emitters | ● | – | – | ● | – | – | – | – | – | – |
| 32.13 idempotency | – | – | ● | – | – | – | – | – | – | – |
| 32.14 apply + format | – | ● | – | – | – | – | – | ● | ● | – |
| 32.15 verifier integration | – | – | – | – | – | – | – | – | – | ● |
| 32.16 AC gate | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● |
Legend: ● = primary owner; ◐ = partial / prerequisite; – = not covered.
Every AC has at least one primary owner upstream of 32.16. 32.16 acts as the final integration gate covering all 10.
6. Open Questions (OQ) for Liam ratification
Section titled “6. Open Questions (OQ) for Liam ratification”OQ-1 — defineRoute() wrapper authoring scope (HIGH priority)
Section titled “OQ-1 — defineRoute() wrapper authoring scope (HIGH priority)”PRODUCT §9 declares lib/api/define-route.ts out of scope for the
codemod itself but states “this must be authored separately as part of the
OPS-T1 implementation workpackage, before the codemod is run.” TECH.md and
AC-1..AC-10 assume the wrapper exists at the point the codemod’s fixture
tests run.
The wrapper does not exist in the current codebase (verified via
ls lib/api/define-route.ts on the worktree at the time of PLAN authoring).
Planner recommendation: Fold the wrapper authoring into Subtask 32.5 as written. Rationale: (a) the wrapper is a self-contained ~50-100 LOC module, (b) waiting on a separate task would block all 11 downstream Subtasks, (c) the codemod tests cannot pass without it. The “out of scope” wording in PRODUCT §9 reflects a codemod-vs-wrapper-design boundary, not a scheduling boundary.
Alternative: Liam creates a new Task ID-32a (or pulls forward an existing backlog item) that authors the wrapper as a precondition; ID-32 then strictly implements the codemod against the existing wrapper. This is cleaner per spec-boundary discipline but adds one Task-level dependency and one extra dispatch cycle.
Decision needed before Subtask 32.5 dispatches.
OQ-2 — Source C inference scope (LOW priority)
Section titled “OQ-2 — Source C inference scope (LOW priority)”TECH §3 recommends deferring Source C (handler return-statement walk).
PLAN as drafted excludes Source C entirely. Liam may want to schedule a
follow-on Task for Source C if the post-apply z.unknown() placeholder
count proves higher than expected.
Planner recommendation: Confirm Source C remains out of scope for this PLAN cycle; if Source C is needed, surface as a separate backlog item post-32.16 acceptance.
Alternative: Add Subtask 32.9b authoring Source C inline; raises Subtask
count to 13 implementation + 4 spec = 17 (still within 25-ceiling), adds
~2-3h, but reduces the z.unknown() count for non-baseline routes.
Decision needed before: Subtask 32.16 acceptance gate dispatch (post-apply placeholder count is the data point that retroactively validates or invalidates the Source-C-defer choice).
OQ-3 — bun lint baseline assumptions (MEDIUM priority)
Section titled “OQ-3 — bun lint baseline assumptions (MEDIUM priority)”AC-9 requires bun lint to report 0 errors after --apply. This presumes
the current bun lint baseline is already 0. If there are pre-existing
ESLint errors in app/api/**/route.ts (e.g. from the
local/no-unchecked-supabase-error rule), the codemod’s apply pass could
surface them as new “regressions” against the AC-9 gate when they were
pre-existing.
Planner recommendation: Subtask 32.16’s AC-9 check uses a
pre-apply baseline vs post-apply diff, not an absolute 0-error
assertion. Confirm with Liam whether the AC-9 acceptance criterion should
be reinterpreted as “no new lint errors introduced by the codemod” rather
than “absolute 0 lint errors”.
Alternative: Treat AC-9 as written (absolute 0-error assertion);
Subtask 32.5 then carries a prereq slice fixing the existing bun lint
baseline to 0 before any codemod rewrite lands. Adds unbounded scope to
32.5 (depending on pre-existing lint debt) and may itself fork into a
separate Task.
Decision needed before: Subtask 32.16 testStrategy authoring (diff-vs-absolute framing determines the assertion shape and the allowed-failures bucket for AC-9).
7. Sibling-only dependency verification
Section titled “7. Sibling-only dependency verification”Every Subtask dependency — in both the SUPERSEDED §4 chain (32.5–32.16) and the
LIVE §4a Option-4 chain ({32.25} deps [] → {32.26} deps [25] → {32.27} deps
[25, 26]) — references another Subtask integer ID within Task 32 only. No
Subtask references any external Task (e.g. ID-31, ID-30). The Task-level
dependencies: ["31"] on ID-32 captures the canonical-pipeline prerequisite at
the appropriate level; no Subtask-level cross-Task dep needed. (Per PRODUCT §9 /
TECH §10, the DB-layer warp-analog is the SEPARATE Task ID-47 — it is NOT pulled
into ID-32’s Subtask space, so no cross-Task Subtask dep arises there either.)
Cross-Task dependency surface verified clean for both chains. No escalation required at this layer.
8. Effort summary
Section titled “8. Effort summary”| Subtask | Estimate (hours) | Parallelisable with |
|---|---|---|
| 32.5 scaffold + defineRoute wrapper | 2-3 | — |
| 32.6 classifier | 1-2 | — |
| 32.7 fixtures (14) | 2-3 | — |
| 32.8 inference Source A | 2-3 | 32.9, 32.12 |
| 32.9 inference Source B (optional) | 1-2 | 32.8, 32.12 |
| 32.10 rewrite single-method | 3-4 | 32.11 |
| 32.11 rewrite multi-method | 2-3 | 32.10 |
| 32.12 CLI + emitters | 2-3 | 32.8, 32.9 |
| 32.13 idempotency | 1-2 | — |
| 32.14 apply + format | 1-2 | — |
| 32.15 verifier integration | 1-2 | — |
| 32.16 AC gate | 2-3 | — |
| Total | 20-32 h | — |
Task record’s existing estimate is 16-24h. The PLAN total (20-32h) is
slightly higher because OQ-1’s fold-in of defineRoute() wrapper authoring
adds ~2-3h that the original estimate did not include. Liam may wish to
adjust the Task estimate after OQ-1 resolution.
Parallel execution across 32.8/32.9/32.12 and 32.10/32.11 could compress wall-clock time by ~4-5h if multiple Executors are available.
9. Related documents
Section titled “9. Related documents”ops-t1-codemod/PRODUCT.md— user-facing behaviour, 10 ACs.ops-t1-codemod/TECH.md— ts-morph implementation strategy, 14 fixtures, 6 gotchas.ops-t1-codemod/route-shape-inventory.md— 193 routes empirical inventory.docs/reference/test-philosophy.md— 6 audit criteria governing testStrategy lines.docs/reference/task-list.jsonID-32 — Task record (PLAN.md update lands via sub-orchestrator after Checker pass).lib/ast-dataflow/queries/type-drift-detect.ts— verifier (already shipped).docs/generated/type-drift-baseline.json— 37-interface baseline (Source A input).