Files
EpicNext-Cms/.superpowers/sdd/2026-08-26-housekeeping-completion/task-9-report.md
T
Simo 0117b45d74
CI / check (pull_request) Successful in 29s
CI / release (pull_request) Skipped
CI / deploy (pull_request) Skipped
fix(housekeeping): make route matching deterministic
2026-08-28 21:55:07 +02:00

10 KiB

Task 9 report — canonical route dispatch

Status

  • DONE: matcher, registry collision guard, empty handler aggregate, preview root routing, and catch-all dispatch are implemented.
  • Base verified before edits: 2970dff56378cf5259fd125794bf0d89bec25b25 on codex/housekeeping-complete.
  • Commit message: feat(housekeeping): dispatch canonical domain routes.
  • .remember/ remained untouched and untracked.
  • No pull, push, PR/MR update, deployment, database operation, Task 10 work, or worktree was performed.

TDD evidence

RED — tests written before production

Exact command:

pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/foundation/routing/match-route.test.ts src/features/housekeeping/foundation/registry.test.ts src/features/housekeeping/route-handlers.test.ts src/features/housekeeping/foundation/preview-route-contract.test.ts

Observed exit 1:

Test Files  4 failed (4)
Tests       1 failed | 14 passed (15)

Cannot find module './match-route'
Cannot find module './route-handlers'
Cannot find package '@/app/ase-next/[domain]/[[...segments]]/page'

registry > rejects duplicate dynamic route shapes regardless of parameter name
AssertionError: expected [Function] to throw an error

This proved the three missing production boundaries and the existing registry's acceptance of equivalent :id / :username route shapes.

First targeted GREEN

The same exact command after the minimum implementation exited 0:

Test Files  4 passed (4)
Tests       94 passed (94)

An intermediate run had 92/94 passing because two import-boundary fixtures still used the old route file's relative depth. The fixture imports were moved one directory higher for the new catch-all location; the forbidden-module assertions were unchanged.

Full-suite contract correction

The first full housekeeping run correctly exposed one obsolete Task 1 expectation:

Test Files  1 failed | 37 passed (38)
Tests       1 failed | 348 passed (349)
Expected NEXT_REDIRECT:/ase-next/operations
Received NEXT_NOT_FOUND

server-capability-context.test.ts was updated to the Task 9 ruling: with the real registered route set still empty, /ase-next calls notFound() and must not redirect to an empty Operations placeholder. Its request-scoped context isolation assertions remain intact.

Implemented behavior

  • matchHousekeepingRoute compares decoded path segments without constructing a regular expression from route text.
  • Static routes win over same-depth dynamic routes; dynamic and nested parameters are returned in a frozen readonly record.
  • Unknown, cross-domain, malformed, repeated-separator, trailing-separator, query/fragment, invalid-percent, encoded-separator, dot-segment, and backslash paths fail closed.
  • Registry construction rejects duplicate dynamic shapes even when parameter names differ.
  • HousekeepingPageInput is exactly the readonly { context, match } pair.
  • The global route-handler aggregate is empty and its test proves one-to-one equality with the currently empty manifest route set; no placeholder handlers were added.
  • /ase-next searches registered routes in manifest order, requires both domain and route capability, redirects to the first permitted route, and calls notFound() when none exists.
  • /ase-next/<domain>/<segments> derives the domain's canonical /ase path, matches it, finds the exact handler, reacquires the cached request-scoped context, rechecks domain and route capability, and invokes the handler with that same context and match.
  • Unknown routes fail before context loading; inaccessible matched routes load one context and never invoke a handler.

Verification evidence

Targeted routing/registry/handler/preview tests:

pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/foundation/routing/match-route.test.ts src/features/housekeeping/foundation/registry.test.ts src/features/housekeeping/route-handlers.test.ts src/features/housekeeping/foundation/preview-route-contract.test.ts
Test Files  4 passed (4)
Tests       94 passed (94)

Directly affected context contract:

pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/foundation/server-capability-context.test.ts
Test Files  1 passed (1)
Tests       2 passed (2)

Full housekeeping suite:

pnpm test:housekeeping
Test Files  38 passed (38)
Tests       349 passed (349)

TypeScript:

pnpm typecheck
$ tsc --noEmit
exit 0

The only output note was the existing engine warning: local Node 26.7.0 is below the package request >=26.8.1 <27.

Targeted Biome with formatting disabled:

pnpm exec biome check --formatter-enabled=false <11 changed Task 9 source/test files>
Checked 11 files in 47ms. No fixes applied.

git diff --check exited 0 before staging. Cached-diff and committed-tree checks are run as the final staging/commit gates.

Exact Task 9 files

src/app/ase-next/[domain]/[[...segments]]/page.tsx
src/app/ase-next/[domain]/layout.tsx
src/app/ase-next/[domain]/page.tsx (deleted)
src/app/ase-next/page.tsx
src/features/housekeeping/foundation/preview-route-contract.test.ts
src/features/housekeeping/foundation/registry.test.ts
src/features/housekeeping/foundation/registry.ts
src/features/housekeeping/foundation/routing/match-route.test.ts
src/features/housekeeping/foundation/routing/match-route.ts
src/features/housekeeping/foundation/server-capability-context.test.ts
src/features/housekeeping/route-handlers.test.ts
src/features/housekeeping/route-handlers.ts
.superpowers/sdd/2026-08-26-housekeeping-completion/task-9-report.md

Self-review and tooling

  • Mutation check: dynamic-name normalization removal, regex-style static matching, static-priority removal, decoded-separator acceptance, domain mismatch acceptance, skipped route ACL, context reload inside the handler, missing handler lookup, placeholder handler addition, and empty-domain redirect each break a focused test.
  • The catch-all route imports only housekeeping foundation/manifests/handlers and retains the existing forbidden database/auth/permissions/actions/legacy-page boundary audit.
  • apply_patch created all new files, but the Windows sandbox helper repeatedly failed to read existing files with apply deny-read ACLs. Existing-file edits therefore used controller-approved exact-anchor/full-file fallbacks only after resolving absolute paths and validating every target under E:\Users\simol\Desktop\EpicNext-cms.

Fix Round 1 — deterministic encoded route matching

Status and scope

  • Fix base: e5c230ba35aec2b4c471608d32b8a16ecc1ee382.
  • Only the two Important matcher blockers were addressed.
  • The three parked Minor findings remain unchanged; no changed line required an adjustment to them.
  • Commit message: fix(housekeeping): make route matching deterministic.
  • .remember/ remained untouched. No worktree, push, PR/MR, database operation, deployment, or Task 10 work was performed.

Root-cause evidence

  1. The catch-all receives decoded Next segments and re-encodes them with encodeURIComponent. The matcher decoded the request path into decodedSegments but compared static route text against rawSegments. Therefore literal a+b[1] did not equal a%2Bb%5B1%5D; the competing :id route captured the request and could change the selected capability/handler.
  2. Candidate sorting used only total dynamic-segment count. Intersecting patterns /:kind/settings and /users/:id have the same count, so stable sort preserved manifest order and allowed registration order to decide dispatch.

RED

Exact command after test setup was validated:

pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/foundation/routing/match-route.test.ts src/features/housekeeping/foundation/registry.test.ts src/features/housekeeping/route-handlers.test.ts src/features/housekeeping/foundation/preview-route-contract.test.ts

Observed exit 1:

Test Files  2 failed | 2 passed (4)
Tests       2 failed | 95 passed (97)

catch-all encoded literal:
Expected routeId people.literal-tool with params {}
Received routeId people.tool-detail with params { id: "a+b[1]" }

equal-count specificity:
Expected routeId people.user-detail with params { id: "settings" }
Received routeId people.kind-settings with params { kind: "users" }

The registration-order table exercises both orders. Before production changes the general-first order failed while the reverse order passed, proving that order was the deciding variable.

An earlier RED attempt exposed a test-table setup error (manifest.routes is not iterable); the table was changed from spread array rows to named { routes } rows, then rerun to obtain the behavioral RED above before any production edit.

Fix

  • A single decodeCanonicalSegment boundary now normalizes request segments and static route-pattern segments exactly once.
  • Invalid percent encoding, empty values, decoded / or \, and decoded . / .. remain fail closed.
  • Dynamic markers retain their parameter names and receive the already-decoded request segment.
  • Candidate specificity is compared left-to-right. At the earliest static/dynamic difference, the static segment wins; manifest order no longer selects among intersecting patterns.
  • Existing static-over-dynamic behavior and registry duplicate-shape rejection remain unchanged.

GREEN and pre-commit verification

Targeted command above, exit 0:

Test Files  4 passed (4)
Tests       97 passed (97)

Full housekeeping suite, exit 0:

pnpm test:housekeeping
Test Files  38 passed (38)
Tests       352 passed (352)

TypeScript, exit 0:

pnpm typecheck
$ tsc --noEmit

The only output note remained the existing Node warning: local 26.7.0, package request >=26.8.1 <27.

Exact changed-file Biome, exit 0:

pnpm exec biome check --formatter-enabled=false src/features/housekeeping/foundation/routing/match-route.ts src/features/housekeeping/foundation/routing/match-route.test.ts src/features/housekeeping/foundation/preview-route-contract.test.ts
Checked 3 files in 25ms. No fixes applied.

git diff --check exited 0 before the report update. Cached and committed-tree checks are final staging/commit gates.

Exact fix files

src/features/housekeeping/foundation/routing/match-route.ts
src/features/housekeeping/foundation/routing/match-route.test.ts
src/features/housekeeping/foundation/preview-route-contract.test.ts
.superpowers/sdd/2026-08-26-housekeeping-completion/task-9-report.md