Skip to content

feat(worker): Phase 2b — port simple owner-scoped reads (warranties/insurance/legal-cases/tools)#37

Open
chitcommit wants to merge 1 commit into
feat/hono-phase-2a-asset-readsfrom
feat/hono-phase-2b-simple-reads
Open

feat(worker): Phase 2b — port simple owner-scoped reads (warranties/insurance/legal-cases/tools)#37
chitcommit wants to merge 1 commit into
feat/hono-phase-2a-asset-readsfrom
feat/hono-phase-2b-simple-reads

Conversation

@chitcommit
Copy link
Copy Markdown
Contributor

Summary

Phase 2b of the Express→Hono migration. Ports 5 read-only GET routes for warranties, insurance, legal cases, and the tool-resources catalog to the Cloudflare Worker (Hono + Drizzle/Hyperdrive).

Stacks on #36 (Phase 2a asset reads) → #34 (schema canonical remediation) → #33 (Phase 1 Hono skeleton).

Note on roadmap doc: The plan referenced docs/migrations/phase2b-simple-reads.md on main (allegedly PR #35) but no such file exists on main or in the repo. Implementation followed the source-code shape in server/routes.ts and server/storage.ts directly, mirroring Phase 2a's harness and decisions exactly.

Routes Ported

Route Express ref Status
GET /api/assets/:assetId/warranties server/routes.ts:453 Migrated
GET /api/warranties/expiring server/routes.ts:464 Migrated
GET /api/assets/:assetId/insurance server/routes.ts:497 Migrated
GET /api/legal-cases server/routes.ts:529 Migrated
GET /api/tools/resources server/routes.ts:117 Migrated

Mounted in worker/src/index.ts BEFORE the 501 catch-all. Express server/routes.ts is untouched — all unmigrated routes still return not_yet_migrated.

Decided Unilaterally

1. Ownership without JOIN

warranties, insurance_policies, and legal_cases all carry user_id directly (denormalized on insert by the Express writer side). The storage-layer methods filter on user_id (and asset_id for the asset-scoped routes) without a JOIN to assets. The Hono port matches that exactly — no JOIN required. This preserves the Express semantics; if asset-level ownership ever diverges from row-level user_id, that's a Phase 3 reconciliation, not a 2b change.

2. Tool resource catalog inlined

The worker bundle cannot import server/toolRegistry.ts (Express-side ESM with sibling imports). The catalog is re-declared in worker/src/routes/tools.ts with a header comment instructing maintainers to keep both lists in lockstep. The schema-overlord audit will diff them at PR time. Test asserts all 5 known IDs are present so silent drift is caught.

3. drizzle-kit upgrade — attempted, deferred

npm install drizzle-kit@latest blocked by @uppy/aws-s3@^4.3.2 peer-dep conflict (ERESOLVE). Not worth introducing --legacy-peer-deps in a read-only-routes PR. Path taken: Phase 2a fallback — Neon branch br-spring-star-aky6u1mc + chitty_id-scoped fixtures + full afterAll cleanup. Documented in commit message; Phase 3 should pick this up as part of a broader dependency unwind.

4. Test seam identical to Phase 2a

registerWarrantyRoutes(app, authMiddleware) etc. — same factory pattern. Tests inject a pass-through middleware; production instantiation (export const warrantyRoutes) uses real requireChittyAuth. Zero production code paths bypass auth.

5. /api/warranties/expiring registered before /api/assets/:assetId/warranties

Both share the /api prefix on the same sub-app. Registered in safety order so Hono never tries to match "expiring" as an :assetId. Verified empirically by the /warranties/expiring test passing.

Validation Evidence

TypeCheck (npm run check)

Zero NEW errors in worker/src/** and the 4 new test files. All 85 remaining errors are pre-existing in server/ and client/ (Replit-era code untouched by this PR) — identical count to Phase 2a baseline.

Integration Tests

24/24 pass against Neon branch br-spring-star-aky6u1mc (12 Phase 2a + 12 new Phase 2b):

✓ worker/__tests__/asset-reads.integration.test.ts      (12 tests, Phase 2a)
✓ worker/__tests__/warranties.integration.test.ts        (6 tests, Phase 2b)
  ✓ /assets/:id/warranties — owner returns all 4
  ✓ /assets/:id/warranties — intruder empty list
  ✓ /assets/:id/warranties — bad UUID returns 400
  ✓ /warranties/expiring (default 30d) — only expiring+active
  ✓ /warranties/expiring?days=90 — widens window correctly
  ✓ /warranties/expiring — intruder empty list
✓ worker/__tests__/insurance.integration.test.ts         (3 tests, Phase 2b)
  ✓ /assets/:id/insurance — sorted by endDate desc
  ✓ /assets/:id/insurance — intruder empty list
  ✓ /assets/:id/insurance — bad UUID returns 400
✓ worker/__tests__/legal-cases.integration.test.ts       (2 tests, Phase 2b)
  ✓ /legal-cases — owner sees their cases
  ✓ /legal-cases — intruder empty list
✓ worker/__tests__/tools.integration.test.ts             (1 test, Phase 2b)
  ✓ /tools/resources — catalog + callableResources shape

 Test Files  5 passed (5)
      Tests  24 passed (24)
   Duration  9.36s

Sample SQL via Neon MCP run_sql (on br-spring-star-aky6u1mc)

For GET /api/warranties/expiring?days=30 (the non-trivial predicate route):

SELECT id, user_id, provider, end_date, is_active
  FROM warranties
 WHERE user_id = '01-A-CHT-ASST-P-9X-1-X'
   AND is_active = true
   AND end_date <= NOW() + INTERVAL '30 days'
   AND end_date >= NOW()
 ORDER BY end_date;

Result (after seeding one fixture row with end_date = NOW() + INTERVAL '10 days'):

[
  {
    "id": "74e4563c-6750-4e58-a919-8736b5d96443",
    "user_id": "01-A-CHT-ASST-P-9X-1-X",
    "provider": "Sub-Zero Manufacturer Warranty",
    "end_date": "2026-05-27T07:23:34.153Z",
    "is_active": true
  }
]

Confirms the Drizzle-emitted predicate parses, executes against Neon, and returns the expected single row. Fixture cleaned up after capture.

Wrangler Dry-Run

$ npx wrangler deploy --dry-run --env production
Total Upload: 578.97 KiB / gzip: 117.21 KiB
Bindings:
  env.CHITTYASSETS_DB  Hyperdrive Config 4bd7964c46dd42be86e8a5e3dd0d7376
  env.ASSETS           Assets
  env.ENVIRONMENT, CHITTYAUTH_*, CHITTYMINT_URL, CHITTYCONNECT_URL, CHITTYLEDGER_URL
Tail Workers: chittytrack
--dry-run: exiting now.

Upload size +5.76 KiB over Phase 2a — within the expected envelope for 4 route modules + inlined tool catalog.

Canonical Compliance

  • @canon: chittycanon://core/services/chittyassets on all 8 new files
  • ENTITY_TYPES = ["P", "L", "T", "E", "A"] unchanged in env.ts — all five P/L/T/E/A present
  • Owner is Person (P); warranties + insurance + tool resources are Thing (T); legal_cases are Event (E) — documented in each route module header
  • Ownership uses canonical ChittyID — never the Replit-era chitty_ prefix workaround

Stack

PR (this) → #36#34#33 (base: feat/hono-phase-2a-asset-reads)

Test Plan

  • 200 for owner listing their warranties (asset-scoped)
  • 200 for /warranties/expiring returns only active+in-window
  • 200 for /warranties/expiring?days=N adjusts window correctly
  • 200 for owner's insurance, sorted by endDate desc
  • 200 for owner's legal cases, sorted by createdAt desc
  • 200 empty for intruder on every owner-scoped route (no existence leak)
  • 400 for bad UUID on asset-scoped routes
  • tool resources catalog shape correct (id/name/category/callable/capabilities + callableResources)
  • typecheck clean (zero new errors; legacy count unchanged at 85)
  • dry-run deploy succeeds with Hyperdrive binding
  • Drizzle predicate validated end-to-end via Neon MCP

🤖 Generated with Claude Code

Ports 5 read-only GET routes from Express to the Cloudflare Worker
(Hono + Drizzle/Hyperdrive). Stacks on Phase 2a (#36).

Routes ported:
- GET /api/assets/:assetId/warranties   (server/routes.ts:453)
- GET /api/warranties/expiring          (server/routes.ts:464)
- GET /api/assets/:assetId/insurance    (server/routes.ts:497)
- GET /api/legal-cases                  (server/routes.ts:529)
- GET /api/tools/resources              (server/routes.ts:117, static)

Mounted in worker/src/index.ts BEFORE the 501 catch-all. Express
server/routes.ts untouched — all unmigrated routes still return
not_yet_migrated.

Ownership scoping matches the Phase 2a pattern:
warranties.userId / insurance_policies.userId / legal_cases.userId
== claims.chitty_id. Direct (no JOIN), since each table denormalizes
user_id on insert.

Tool resource catalog mirrored inline in worker/src/routes/tools.ts —
worker bundle cannot import Express-side modules. Kept in lockstep
with server/toolRegistry.ts; documented in module header.

NO MOCKS, NO FAKE DATA — every test exercises real Neon via
br-spring-star-aky6u1mc with chitty_id-scoped fixtures + afterAll
cleanup. drizzle-kit upgrade attempted but blocked by peer-dep
conflict (@uppy/aws-s3); deferred. Test branch strategy unchanged
from Phase 2a fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14144312-e010-4f34-abae-40211ee7a086

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/hono-phase-2b-simple-reads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c52ed2302

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const userId = claims.chitty_id;
const daysRaw = c.req.query("days");
const parsed = daysRaw ? parseInt(daysRaw, 10) : 30;
const daysAhead = Number.isFinite(parsed) && parsed > 0 ? parsed : 30;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve query semantics for negative days values

The new /api/warranties/expiring handler changes behavior from the Express path it ports: in server/routes.ts + storage.ts, days=-7 is passed through and yields an empty result window, but this worker code coerces any non-positive value back to 30, which can return active near-term warranties instead. That is a migration regression for callers that send negative values (intentionally or via client bugs), because the same request now returns materially different data after the move to Hono.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant