Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
# CLAUDE.md

<!-- @canon: chittycanon://core/services/chittyassets -->

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Migration status (2026-05-16)

ChittyAssets is in active Express → Cloudflare Workers (Hono) migration. See
[docs/migrations/ROADMAP.md](docs/migrations/ROADMAP.md) for phase index and
status of in-flight PRs.

- **Tier**: 4 (Domain) — `chittycanon://core/services/chittyassets`
- **Entity types handled**: P, L, T, E, A (all five P/L/T/E/A — Authority included)
- **Auth**: ChittyAuth (JWKS) — Replit Auth listed below is the legacy stack
being replaced
- **DB binding (Worker)**: Hyperdrive `CHITTYASSETS_DB`
- **File storage**: GCS → R2 (cutover in Phase 4); ACL via `r2_object_acl` table

While migration is in flight, both runtimes coexist: Express serves prod traffic
until Phase 4 lands and the Cloudflare route flips to the Worker.

## Commands

### Development
Expand Down
96 changes: 96 additions & 0 deletions docs/migrations/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
uri: chittycanon://docs/tech/spec/chittyassets-migration-roadmap
namespace: chittycanon://docs/tech
type: spec
version: 1.0.0
status: DRAFT
registered_with: chittycanon://core/services/canon
title: ChittyAssets Express → Hono Migration Roadmap
author: chittycanon-code-cardinal
visibility: INTERNAL
tags: [migration, hono, cloudflare-workers, chittyassets]
category: migration
@canon: chittycanon://core/services/chittyassets
---

# ChittyAssets Migration Roadmap

Source-of-truth route inventory: `server/routes.ts` (673 lines). This document is the
index for the Express → Cloudflare Workers (Hono) migration. Each phase has a
dedicated plan under `docs/migrations/`.

## Canonical posture

- **Service**: `chittycanon://core/services/chittyassets`
- **Tier**: 4 (Domain)
- **Entity types handled**: P (Person), L (Location), T (Thing), E (Event), A (Authority) — all five P/L/T/E/A
- **Primary Thing (T)**: `asset` (the ChittyAsset itself)
- **Primary Event (E)**: `timeline_event` (asset lifecycle: created, frozen, minted, evidence_added)
- **Primary Authority (A)**: evidence trust score + ChittyChain mint receipt
- **Person (P)**: asset owner (`userId` from ChittyAuth, `chitty_${auth.userId}`)
- **Location (L)**: jurisdiction on legal_case, asset physical location metadata

## Dependency graph

```
chittyassets (Tier 4)
├─ chittyauth (Tier 1) — JWKS verify, Person principal
├─ chittyid (Tier 0) — chittyId mint for new assets
├─ chittytrust (Tier 0) — trust score on create
├─ chittyledger (Tier 4) — evidence ledger submit/verify
├─ chittymint (Tier 4) — ChittyChain freeze/mint
├─ openai (external) — document analysis, legal-doc generation
├─ Neon Postgres — via Hyperdrive binding CHITTYASSETS_DB
└─ R2 bucket — evidence files (replaces GCS)
```

## Phase status

| Phase | Branch | PR | Status |
|------|--------|----|--------|
| 0 — canonical artifacts (CHARTER, CHITTY, AGENTS, SECURITY, register.json) | `feat/hono-migration-phase-1` | #33 | OPEN |
| 1 — Hono Worker skeleton (`/health`, `/api/v1/status`, `/api/auth/user`, JWKS) | `feat/hono-migration-phase-1` | #33 | OPEN |
| Schema remediation — `chitty_id`, P/L/T/E/A enum, `entities` registry, `r2_object_acl` (Drizzle migration `0003`) | `chore/schema-canonical-remediation` | #34 | OPEN |
| 2a — asset reads (`GET /api/assets`, `/:id`, `/stats`, `/:id/evidence`, `/:id/timeline`) | `feat/hono-phase-2a-asset-reads` | in flight | IN PROGRESS |
| 2b — simple reads (warranties, insurance, legal-cases, tools/resources) | `feat/hono-phase-2b-simple-reads` (planned) | — | PLANNED → see [phase2b-simple-reads.md](./phase2b-simple-reads.md) |
| 2c — external reads (evidence-ledger get, ecosystem/status) | `feat/hono-phase-2c-external-reads` (planned) | — | PLANNED → see [phase2c-external-reads.md](./phase2c-external-reads.md) |
| 3a — asset writes (POST/PUT/DELETE `/api/assets`, evidence create) | `feat/hono-phase-3a-asset-writes` (planned) | — | PLANNED → see [phase3a-asset-writes.md](./phase3a-asset-writes.md) |
| 3b — domain writes (warranty, insurance, legal-case creates) | `feat/hono-phase-3b-domain-writes` (planned) | — | PLANNED → see [phase3b-domain-writes.md](./phase3b-domain-writes.md) |
| 3c — heavy writes (freeze, mint, AI analyze, trust-score, legal-doc, ledger submit/verify, seed-demo) | `feat/hono-phase-3c-heavy-writes` (planned) | — | PLANNED → see [phase3c-heavy-writes.md](./phase3c-heavy-writes.md) |
| 4 — R2 object routes (`/objects/:path`, upload, evidence-files ACL) | `feat/hono-phase-4-r2-routes` (planned) | — | PLANNED → see [phase4-r2-routes.md](./phase4-r2-routes.md) |

Expected merge order: #33 → #34 → 2a → 2b → 2c → 3a → 3b → 3c → 4 → Express server retirement.

## Cross-cutting docs

- [test-strategy.md](./test-strategy.md) — real-Neon integration test harness (no mocks)
- [registry-status.md](./registry-status.md) — ChittyRegistry registration state

## ChittyRegistry status

Query against `https://registry.chitty.cc/api/v1/search?q=chittyassets` on
2026-05-16 returned `count: 0`. Service is **NOT REGISTERED**. See
[registry-status.md](./registry-status.md) for recommended registration payload.
Registration is a sensitive-intent operation and requires explicit operator
authorization via `ch1tty -> ChittyConnect`; this doc does not execute it.

## Compliance triad status

`CHARTER.md`, `CHITTY.md`, `AGENTS.md`, `SECURITY.md`, and `register.json`
land via PR #33 (`feat/hono-migration-phase-1`, commit `806c8a5`). They are
**not present on `main`** at the time this roadmap was written, but will be once
PR #33 merges. This doc PR does not recreate them to avoid merge conflicts
with PR #33. After PR #33 merges, re-validate the triad against this roadmap's
canonical posture section (especially Tier 4 classification, the binding name
`CHITTYASSETS_DB`, and the full P/L/T/E/A entity-type list).

## Routes not covered by phase plans

All routes in `server/routes.ts` are accounted for across phases 1, 2a–c, 3a–c, 4
except:

- `GET /api/auth/user` (`server/routes.ts:27`) — implemented in Phase 1 Worker
(`worker/src/index.ts`). No further migration work.

When the last phase merges, `server/index.ts`, `server/routes.ts`, and the
Express middleware chain can be deleted; the Worker becomes the sole runtime.
Comment on lines +95 to +96
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Include GitHub webhook routes before retiring Express

Before removing the Express runtime, this roadmap needs to account for GitHub App endpoints that are currently outside the phase plans: setupGitHubWebhooks(app) is still invoked in server/routes.ts and registers /api/github/webhooks, /api/github/callback, and /api/github/setup in server/githubWebhooks.ts (lines 316–340). As written, deleting the Express middleware chain after Phase 4 would drop those routes entirely, causing a functional regression for webhook/OAuth flows.

Useful? React with 👍 / 👎.

105 changes: 105 additions & 0 deletions docs/migrations/phase2b-simple-reads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
uri: chittycanon://docs/tech/spec/chittyassets-phase2b
namespace: chittycanon://docs/tech
type: spec
version: 1.0.0
status: DRAFT
title: ChittyAssets Phase 2b — Simple Reads
visibility: INTERNAL
tags: [migration, hono, phase-2b]
@canon: chittycanon://core/services/chittyassets
---

# Phase 2b — Simple Reads (Neon-only, no external services)

## Scope

Pure-read endpoints that hit Neon via Hyperdrive (`CHITTYASSETS_DB`) and return
JSON. No external service calls, no AI, no R2.

| Method | Path | Source (Express) | Storage call |
|--------|------|------------------|--------------|
| GET | `/api/assets/:assetId/warranties` | `server/routes.ts:453` | `storage.getAssetWarranties(assetId, userId)` |
| GET | `/api/warranties/expiring?days=N` | `server/routes.ts:464` | `storage.getExpiringWarranties(userId, daysAhead)` |
| GET | `/api/assets/:assetId/insurance` | `server/routes.ts:497` | `storage.getAssetInsurance(assetId, userId)` |
| GET | `/api/legal-cases` | `server/routes.ts:529` | `storage.getUserLegalCases(userId)` |
| GET | `/api/tools/resources` | `server/routes.ts:117` | `listToolResources()` (static) |

## File layout

```
worker/src/routes/
warranties.ts # GET /api/assets/:assetId/warranties, GET /api/warranties/expiring
insurance.ts # GET /api/assets/:assetId/insurance
legal-cases.ts # GET /api/legal-cases
tools.ts # GET /api/tools/resources
worker/src/db/
queries/warranties.ts
queries/insurance.ts
queries/legal-cases.ts
```
Comment on lines +30 to +40
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language specifier to fenced code block.

The file layout section uses a fenced code block without a language specifier. Adding one improves rendering and addresses the markdownlint warning.

📝 Proposed fix
-```
+```plaintext
 worker/src/routes/
   warranties.ts    # GET /api/assets/:assetId/warranties, GET /api/warranties/expiring
   insurance.ts     # GET /api/assets/:assetId/insurance
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 30-30: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/migrations/phase2b-simple-reads.md` around lines 30 - 40, The fenced
code block showing the file layout lacks a language specifier; update that block
to include a language (e.g., plaintext or text) so the snippet renders correctly
and satisfies markdownlint—modify the fenced block that lists worker/src/routes/
(warranties.ts, insurance.ts, legal-cases.ts, tools.ts) and
worker/src/db/queries (queries/warranties.ts, queries/insurance.ts,
queries/legal-cases.ts) to start with ```plaintext (or another appropriate
language).


Each route module exports a `Hono` sub-app mounted in `worker/src/index.ts`
under the existing auth-guarded group established in Phase 2a.

## Query shapes (Drizzle pseudocode)

```ts
// queries/warranties.ts
import { eq, and, lte, gte } from 'drizzle-orm';
import { warranties, assets } from '../../../shared/schema';

export async function getAssetWarranties(db: DrizzleDB, assetId: string, userId: string) {
// join enforces ownership at the SQL layer (defense in depth alongside auth)
return db
.select()
.from(warranties)
.innerJoin(assets, eq(warranties.assetId, assets.id))
.where(and(eq(warranties.assetId, assetId), eq(assets.userId, userId)))
.orderBy(warranties.expirationDate);
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 Use existing warranty date column in query examples

The Drizzle pseudocode uses warranties.expirationDate, but the actual schema defines warranties.endDate (shared/schema.ts), and current storage logic also filters/sorts by endDate. If implementers follow this example literally, Phase 2b query code will not compile and can drift from existing API behavior for expiring-warranty filtering.

Useful? React with 👍 / 👎.

}

export async function getExpiringWarranties(db: DrizzleDB, userId: string, daysAhead: number) {
const cutoff = new Date(Date.now() + daysAhead * 86_400_000);
return db
.select()
.from(warranties)
.innerJoin(assets, eq(warranties.assetId, assets.id))
.where(and(
eq(assets.userId, userId),
gte(warranties.expirationDate, new Date()),
lte(warranties.expirationDate, cutoff),
))
.orderBy(warranties.expirationDate);
}
```

`getAssetInsurance` and `getUserLegalCases` follow the same ownership-via-join
pattern. `tools/resources` returns a static manifest derived from
`server/toolResources.ts` — port the module to `worker/src/lib/tool-resources.ts`
verbatim (no DB).

## External deps

None. This is the cleanest phase to land — pure Neon reads via the Hyperdrive
binding established in Phase 1.

## Validation gates

1. `npm run check` clean (Worker tsconfig).
2. `wrangler deploy --dry-run --env production` ≤ 260 KiB.
3. Real-Neon integration test (see [test-strategy.md](./test-strategy.md)):
- Seed one user (Person, P) `did:chitty:user:phase2b-test`
- Seed one asset (Thing, T) with `chitty_id` matching the canonical pattern
`VV-G-LLL-SSSS-T-YM-C-X`
- Seed two warranties (one expiring in 15 days, one in 90 days)
- Hit each route via `app.request(...)` and assert shape + ownership filter
4. `curl` against deployed Worker post-merge:
- `curl https://assets.chitty.cc/api/legal-cases -H 'Authorization: Bearer …'`
- Assert `200` + array shape

## Estimated PR size

~400 LOC added (4 route modules + 4 query modules + tests). 0 LOC removed
(Express stays running in parallel — the Worker takes traffic only after the
Cloudflare route is flipped, post-Phase 4).
118 changes: 118 additions & 0 deletions docs/migrations/phase2c-external-reads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
uri: chittycanon://docs/tech/spec/chittyassets-phase2c
namespace: chittycanon://docs/tech
type: spec
version: 1.0.0
status: DRAFT
title: ChittyAssets Phase 2c — External Reads
visibility: INTERNAL
tags: [migration, hono, phase-2c, external-services]
@canon: chittycanon://core/services/chittyassets
---

# Phase 2c — External Reads (ChittyLedger, ecosystem status)

## Scope

| Method | Path | Source | External dep |
|--------|------|--------|--------------|
| GET | `/api/evidence-ledger/:chittyId` | `server/routes.ts:80` | ChittyLedger (`chittycanon://core/services/chittyledger`) |
| GET | `/api/ecosystem/status` | `server/routes.ts:105` | All Tier 0–4 service `/health` endpoints |

These wrap remote HTTP calls. The Express implementation uses
`server/chittyCloudMcp.ts` helpers (`getEvidenceLedger()`,
`getChittyServices()`); under Hono we replace MCP-style indirection with thin
typed HTTP clients.

## File layout

```
worker/src/
clients/
chittyledger.ts # HTTP client for ledger.chitty.cc
ecosystem.ts # parallel /health probes
routes/
evidence-ledger.ts # GET /api/evidence-ledger/:chittyId
ecosystem.ts # GET /api/ecosystem/status
```
Comment on lines +29 to +37
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language specifier to fenced code block.

The file layout section uses a fenced code block without a language specifier. Adding one improves rendering and addresses the markdownlint warning.

📝 Proposed fix
-```
+```plaintext
 worker/src/
   clients/
     chittyledger.ts    # HTTP client for ledger.chitty.cc
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 29-29: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/migrations/phase2c-external-reads.md` around lines 29 - 37, The fenced
code block showing the project layout (containing lines like "worker/src/",
"clients/chittyledger.ts", "routes/evidence-ledger.ts", etc.) lacks a language
specifier; update that block to include a language label (e.g., "plaintext") on
the opening triple-backticks so the snippet renders properly and fixes the
markdownlint warning.


## Client contracts

### `clients/chittyledger.ts`

```ts
import { z } from 'zod';

const EvidenceRecordSchema = z.object({
chittyId: z.string().regex(/^[0-9A-Z]{2}-[0-9]-[A-Z]{3}-[0-9]{4}-[PLTEA]-[0-9A-Z]{2}-[0-9]-[0-9A-Z]$/),
status: z.enum(['draft', 'verified', 'sealed', 'disputed']),
trustScore: z.number().min(0).max(1),
retentionUntil: z.string().datetime(),
chainResult: z.object({ ipfsHash: z.string(), txHash: z.string().optional() }).nullable(),
});

export class ChittyLedgerClient {
constructor(private baseUrl: string, private bearerToken: string) {}

async getEvidence(chittyId: string, signal?: AbortSignal) {
const res = await fetch(`${this.baseUrl}/api/v1/evidence/${encodeURIComponent(chittyId)}`, {
headers: { authorization: `Bearer ${this.bearerToken}` },
signal,
});
if (res.status === 404) return null;
if (!res.ok) throw new LedgerError(res.status, await res.text());
return EvidenceRecordSchema.parse(await res.json());
}
}
```

### `clients/ecosystem.ts`

Probes all configured service base URLs in parallel with a 2s per-probe timeout
via `AbortSignal.timeout(2000)`. Returns
`{ services: Array<{ name, url, status: 'ok'|'degraded'|'down', latencyMs }> }`.

Service URL list is read from environment, not hardcoded. Suggested env vars
(Worker `[vars]`):

```jsonc
"CHITTY_LEDGER_URL": "https://ledger.chitty.cc",
"CHITTY_ID_URL": "https://id.chitty.cc",
"CHITTY_TRUST_URL": "https://trust.chitty.cc",
"CHITTY_AUTH_URL": "https://auth.chitty.cc",
"CHITTY_MINT_URL": "https://mint.chitty.cc",
```

The bearer token used for upstream ledger calls is the same ChittyAuth JWT
presented by the caller (token forwarding), not a service-to-service secret.
This preserves the authorization subject end-to-end.

## Retry / timeout policy

- **Per-call timeout**: 5s for `getEvidence`, 2s for each `/health` probe
- **Retries**: 2 retries on 5xx or network error, exponential backoff
100ms → 300ms; no retry on 4xx
- **Circuit breaker**: out of scope for Phase 2c; tracked in
`chittyobservability` integration (deferred)
- **Failure mode**: ledger 5xx after retries → return 503 with
`{ error: 'upstream_unavailable', service: 'chittyledger', correlationId }`;
ecosystem probe failure does NOT fail the whole status response — per-service
`status: 'down'` with `error` field

## Validation gates

1. Type-check clean.
2. Real upstream integration test against staging ledger
(`https://ledger-staging.chitty.cc`):
- Submit a known ChittyID, then `GET /api/evidence-ledger/:chittyId` through
the Worker, assert round-trip
3. Failure-injection test: point `CHITTY_LEDGER_URL` at a `127.0.0.1:1` URL,
assert 503 + correlation ID, not a 500 with stack trace.
4. `curl https://assets.chitty.cc/api/ecosystem/status` returns 200 with
per-service health.

## Estimated PR size

~500 LOC (clients, routes, tests, env wiring). External-service integration
tests are the long pole; budget time for staging-env coordination with the
ChittyLedger team.
Loading
Loading