Removes the dual-write into messages.tool_calls / messages.tool_results JSON columns and drops the columns. message_parts is now the only source of truth for tool calls and tool results. 10 dual-write sites stripped (5 in tool-phase.ts, 2 in routes/skills.ts, 2 in routes/messages.ts, 1 in routes/chats.ts fork-clone). The recon-driven grep caught 2 sites beyond the original v1.13.2 roadmap inventory and an extra fixture file (tool_cost_stats.test.ts) with a direct legacy-column INSERT. messages_with_parts view rewritten to parts-only subselects (COALESCE fallbacks gone). View runs via CREATE OR REPLACE so it lands before the column DROPs in startup DDL — Postgres rejects column-drop on view-referenced cols. v1.12.1 cleanup DO block (DROP CONSTRAINT messages_status_check / messages_role_check) removed; those one-shots have done their work. Adversarial review caught a runtime bug the green test suite missed: the discard_stale endpoint (chats.ts) had a RETURNING ... tool_calls, tool_results clause that would have crashed on every 60s-no-token-activity recovery in production. Fixed by switching to two-step UPDATE returning id, then SELECT from messages_with_parts so parts-synthesized fields keep flowing on the wire. Message API type retains tool_calls? / tool_results? — the view synthesizes those keys from parts so the wire shape is unchanged; frontend reads need no update. Override on the original v1.13.2 plan, captured in the openspec proposal. 339/339 server tests passing (including 7 DB-integration tests that applied the schema migration to a live DB and ran the parts-only view end-to-end). tsc + web build clean. Pairs with v1.13.0-ai-sdk-v6 (introduced the dual-write) and v1.13.1-B (moved the read path to messages_with_parts). Umbrella v1.13 tag ships on this same commit, marking the strangler-fig closed. CLAUDE.md picks up Sam's pre-existing edits documenting tag-naming and CHANGELOG conventions — both already in use by v1.13.19 / v1.13.20. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
openspec
Per-batch documentation convention adopted v1.13.15-openspec.
Lift source: Fission-AI/OpenSpec directory layout. No CLI dependency — just the folder shape. Full OpenSpec lifecycle adoption is a future v1.14+ batch.
Layout
openspec/
changes/
<slug>/ # one folder per shipped or planned batch
proposal.md # Why + scope summary
tasks.md # implementation step list
design.md # architecture / data-model decisions (optional)
specs/ # reserved for future OpenSpec CLI adoption
archived/ # snapshots of pre-v1.13.15 batch docs
<original-filename>.md
specs/ # global specs, future v1.14+ use
Conventions
- Slugs are lowercase-hyphenated derived from the batch title
(e.g.
v1-13-10-per-tool-cost,file-attachments-v3-5). - Already-shipped pre-v1.13.15 batches live in
changes/archived/as single-file snapshots. They were not split into proposal/tasks because the work was already complete; archiving preserves git history. - New v1.13.15+ batches should land directly in
changes/<slug>/proposal.md(+ tasks.md, + design.md when applicable). proposal.mdcarries the "Why" and scope.tasks.mdis the action list (numbered or checkbox).design.mdis for non-trivial architectural decisions worth recording separately.- A canonical dispatch brief (matching the v1.13.9 / v1.13.10 format) is most naturally split as proposal.md (Where we are, Why this matters, rationale sections) + tasks.md (Scope items, Build + smoke) + design.md (Attribution model, Filtering, Canonical mapping).