Closes 175. Epic.doneCriteria and EpicTask.doneCriteria are both JSON blobs joined only by a free-form id, and nothing checked that a node's ids existed on its parent Epic. A typo produced a criterion belonging to nobody. The reconciliation added in 170 surfaces those as `unowned`, which is honest but late: the mistake was made at write time and is far cheaper to refuse there. Now create and update both reject unknown ids, naming every one of them and listing what the Epic actually has — or saying plainly that the Epic has no criteria yet, which is the likelier cause when a whole roadmap comes back empty. Enforced in the service, not the schema: production provisions with `prisma db push`, so a constraint would never exist where it matters. A node with no criteria of its own is still fine — not every roadmap node claims one — and an update that doesn't touch criteria doesn't pay for the check. This deliberately stops short of the EpicCriterion table with a real FK. That remains the stronger answer if criteria ever need to be addressed independently, but it costs a schema change plus migrating everything currently in the JSON blobs, and the invariant closes the actual failure mode today. Also files 177: agent chat --project reaches project prompts and MCP tools while checking only run:agents:<name>. Raised by the agent that built membership, which correctly refused to invent the policy mid-task. Pre-existing behaviour for a legacy pin, but membership widens the blast radius considerably — it should be a deliberate decision with a test. Local: build clean, workspace 2557 passed (+7), lint unchanged at 794 errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4wNHWf7xSwnZCWpJcyv9p
344 KiB
344 KiB