Amendment A216 — T148
When we ran the T147 data migration last week, we discovered that 14 amendments had been created with status="done" — a state that exists in the goal and task flows but not in the amendment flow (scoped / in-progress / commit-ready / committed / merged / rejected). The MCP create_amendment tool accepted the call without error, stored the item, and returned success.
The root cause: Smeldr has always validated state *transitions* at transition time (via validateTransition), but never validated the *initial* state at create time. Any string that happened to be a valid state in *any* registered flow was accepted silently.
What changed
Two gaps are now closed:
Create-time (Gap 1). Both createHandler (HTTP POST) and MCPCreate (the MCP create path) now call validateInitialState after field validation, before the item is persisted. If the caller supplies a status value that is not a registered state in the type's own flow, the request is rejected with a 409 Conflict.
Transition-time (Gap 2). validateTransition now checks whether the *target state* exists in the flow before looking up the transition edge. Previously, a transition to a non-existent state and a transition to a valid-but-unreachable state both produced the same generic "transition not permitted" error. Now, the first case produces a specific "not a valid target state" message — easier to diagnose, especially for AI agents that have no other signal.
Fail-open design
Both checks are fail-open on structural errors: nil DB, non-SQLite, missing flow, query failure. This matches the existing convention for all state-flow checks in Smeldr — enforcement degrades gracefully rather than blocking all creates when the DB is unavailable.
Lesson
If your Smeldr instance uses custom state flows (or the built-in orchestration flows for decisions, amendments, goals, tasks, and signals), the status field on any create request is now validated against that type's own flow. Passing a state that belongs to a different type's flow will result in a 409, not a silent success.
No exported Go symbols changed. No version bump required.