-
Notifications
You must be signed in to change notification settings - Fork 0
docs: migration roadmap (phases 2b–4) + canonical triad refresh #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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. | ||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 |
||
|
|
||
| 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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The Drizzle pseudocode uses 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). | ||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 |
||
|
|
||
| ## 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. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 inserver/routes.tsand registers/api/github/webhooks,/api/github/callback, and/api/github/setupinserver/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 👍 / 👎.