## Summary
This PR advances the Rhodes → Aerie migration by making Aerie own the Rhodes operational data model and business logic, rather than delegating writes back to Rhodes. The MCP write path no longer hand-rolls behavior inside mcp.ts; instead it dispatches to a new set of canonical Rhodes mutation runtime modules in Aerie Convex, which encode the authoritative write semantics (authorization, ID rewriting, derived state, audit logging, milestone/lifecycle handling). On top of that, dual-write and read-source paths are hardened to fail closed, due-diligence writeback is mirrored into Aerie's migrated sites, and the bulk-baseline + migration importers gain retained-graph handling, placeholder users, and serialized-ID rewriting so a re-runnable import reconciles cleanly. Extensive parity tests assert the new Aerie runtime matches Rhodes behavior across MCP mutations, the dashboard, and dual-write.
### Changes
Canonical Rhodes mutation runtime (new core)
- chat/convex/rhodes/runtime/mutationDispatcher.ts *(new)* — Central dispatch from MCP tool calls to canonical write modules.
- chat/convex/rhodes/runtime/mutationAuthorization.ts *(new)* — Authorization checks for Rhodes mutations.
- chat/convex/rhodes/runtime/actors.ts *(new)* — Resolves the acting user/actor for writes and audit attribution.
- chat/convex/rhodes/runtime/audit.ts *(new)* — Audit-log helpers for site writes.
- chat/convex/rhodes/runtime/ids.ts *(new)* — ID resolution/rewriting between Rhodes legacy IDs and Aerie IDs.
- chat/convex/rhodes/runtime/derivedState.ts, milestones.ts, pendingLifecycle.ts, constants.ts, siteReadModels.ts, documentGapReadModels.ts *(new)* — Derived stage/quality-bar status, milestone due-date logic, pending-mutation lifecycle, shared constants, and read models.
- chat/convex/rhodes/runtime/writes/*.ts *(new)* — Per-entity canonical write behavior: siteWrites, taskWrites, noteWrites, documentWrites, costBreakdownWrites, changeLogWrites, workUnitWrites, workUnitGroupWrites.
- chat/convex/rhodes/mcp.ts — Reduced from ~1300 lines to a thin dispatcher; MCP writes now delegate to the runtime modules instead of containing inline logic.
- chat/convex/rhodes/functions.ts, dashboard.ts, dualWrite.ts, migration.ts, pendingMutations.ts, userRefs.ts — Wire the dashboard, dual-write, and migration paths to the new runtime; gate DRI assignment to Clerk users; derive stage/quality-bar status via triggers.
Due diligence
- chat/convex/portfolio/dueDiligence.ts — Audit-log Aerie DD site writes with the acting user; mirror DD writeback and cron into Aerie migrated sites.
- chat/app/api/portfolio-sites/[slug]/fields/route.ts — Route phasing and security saves to Aerie.
Read source / cache hardening
- chat/lib/school-site-read-source.ts — Fail closed on missing or invalid SCHOOL_SITE_READ_SOURCE.
- chat/lib/rhodes-portfolio-cache.ts — Bypass the process cache for Aerie Convex reads.
- chat/lib/aerie-rhodes-dashboard-server.ts *(new)* — Server-side Aerie dashboard reads.
Migration / bulk baseline (sync)
- sync/src/scripts/rhodes-migration.ts, rhodes-bulk-baseline.ts — Retained graph, placeholder users for missing Rhodes users, serialized JSON ID rewriting, reconcile hardening, and ignore-missing-email handling.
- chat/convex/migrations/resetRhodesBulkBaselineTables.ts *(new)* — Reset baseline tables for re-runnable imports.
- chat/convex/migrations/rhodesBackfill.ts — Backfill adjustments.
MCP repoint / worker config
- chat/scripts/rhodes-mcp-repoint-check.mjs — Validate the MCP handshake and required tools; drop runDriveAudit from the allowlist.
- chat/rhodes-worker/wrangler.jsonc, .dev.vars.example, .env.example — Document the Aerie shared secret and permission-lookup vars.
Dependencies
- chat/package.json, pnpm-lock.yaml — Add convex-helpers for Convex triggers.
Tests *(new)* — rhodesMcpMutationParity.test.ts, rhodesDashboardParity.test.ts, rhodesDualWrite.test.ts, rhodesMcpParity.test.ts, plus runtime write-parity coverage and additions to dueDiligence.test.ts, rhodesBackfill.test.ts, rhodes-migration.test.ts, rhodes-bulk-baseline.test.ts.
Docs
- features/rhodes-migration/RUNTIME_WRITE_PARITY_GAPS.md *(new)* — Tracks behavioral gaps between Rhodes and the new Aerie runtime.
- features/rhodes-migration/RHODES_DEV_TO_AERIE_DEV_GOAL_PROMPT.md *(new)*, PLAN.md — Migration plan/goal updates.
### Design Decisions
- Aerie owns the write behavior. Rather than continuing to delegate MCP writes to Rhodes /aerie/mutations/*, the canonical mutation logic is reimplemented in Aerie's rhodes/runtime/* modules so Rhodes can eventually be archived. mcp.ts becomes a dispatcher.
- Fail closed everywhere. Dual-write and read-source resolution error out rather than silently drifting or falling back, consistent with the migration's non-negotiable decisions.
- Legacy IDs are mapped, not preserved. Aerie generates its own document IDs and rewrites embedded/serialized Rhodes IDs via the legacyIdMap and the runtime ids helpers.
- DRI assignment gated to Clerk users; stage and quality-bar status are derived via Convex triggers (hence the convex-helpers dependency) instead of being set manually.
## Test Plan
- [ ] pnpm typecheck
- [ ] pnpm biome check
- [ ] Parity test suites pass (rhodesMcpMutationParity, rhodesDashboardParity, rhodesDualWrite, rhodesMcpParity, runtime write-parity)
- [ ] Reviewer: confirm MCP repoint check passes against the Aerie worker (chat/scripts/rhodes-mcp-repoint-check.mjs)
- [ ] Reviewer: validate bulk-baseline + migration dry run reconciles cleanly on dev data
- [ ] Reviewer: verify DD writeback + cron land on Aerie migrated sites with correct actor attribution