Phase: Phase 2 — CRUD + Rename to Prism · Status:
closed · Filed 2026-04-16What Worked
- Single shared service module (phase2_service.py) — five entities, one file
- Per-project monotonic number via (project_id, number) UNIQUE
- Backward-compatible rename — zero external identifiers flipped
- Smoke-test-as-you-go caught MissingGreenlet PATCH bug in isolation
- One-line MCP verb dispatchers — verb logic stays in backend
- Frank stayed out of the loop the whole session — AI FIRST worked
What Didn’t
- MissingGreenlet on PATCH after onupdate=func.now() — needed await session.refresh(row)
- Stale uvicorn binding port 8010 — old server still bound, new one failed silently
- Edit tool failures on unread files — three wasted tool calls
- Wakeup timer fired stale — completed work before timer expired
- Initial smoke missed PATCH — POST/GET looked green but PATCH blew up
- ScheduleWakeup at 60s burned cache for nothing — should have polled /health
Lessons
- [project] L5 — All ORM models with onupdate=func.now() need await session.refresh(row) after any write
- [project] L6 — Every CRUD smoke must include PATCH, not just POST/GET
- [project] L7 — Rename without flipping external identifiers. ADR-017 pattern is the template
- [global] G3 — In async SQLAlchemy, onupdate=func.now() is a footgun unless paired with explicit refresh()
- [global] G4 — Before debugging ‘my code isn’t loading,’ verify the running process is actually the one I started

