Name the constraint that rejected a command - #2532
Conversation
A constraint violation reaching a client said only that some constraint rejected the command, never which one. The name existed - Chronicle carries it on the violation - and was dropped on the way into the validation result, so a frontend wanting to say something specific about a particular constraint had no choice but to match on the English message the kernel composed. Rewording that message silently broke every consumer that did. Carry the name through as ReasonDetail: Reason says what kind of thing rejected the command, ReasonDetail says which one. It is a plain string rather than Chronicle's ConstraintName because Arc.Core does not depend on Chronicle, and a body property rather than a fifth positional parameter so the record's constructor and deconstruction are unchanged. This also closes a gap between the two halves of the framework. Chronicle's testing package already offers ShouldHaveConstraintViolationFor against a raw append, and Arc's own analyzer lists it among the named rejections, but the assertion died at the command boundary because the name did not survive the conversion - so the documented way to specify a constraint could not be written against a command result. It can now. Also fixes a client-side result composed after a failed validation losing its reason, which has been reporting every such result as an authored rule since the reason was introduced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: 49eab3a7-0743-4895-8303-bf5632b6a992
|
Reviewer context, kept out of the body. Shape and semver. Why the assertion matters as much as the field. Chronicle's testing package already ships Verification. Debug and Release, Mutation evidence, each applied and restored with the restore verified by hash:
Two notes for whoever reviews the surrounding area. The Not covered. The TypeScript specs are asserted green but were not mutation-tested. |
A failed query composed on the client dropped both the reason and the name of the violated constraint, while the doc comment on it promises it mirrors the command side so a caller reads the two the same way. The command side was fixed and the query side was not, which made the promise less true than before rather than more. Neither the query result's server mapping nor the failure it composes had any spec, so nothing noticed: removing both fields from the mapping left the whole suite green. Also corrects an assertion that could not fail. It filtered for a constraint name that appears nowhere in its fixture and asserted the count was zero, which holds whether or not the name is carried at all - it now filters for the name the fixture does carry and asserts it is found. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: 49eab3a7-0743-4895-8303-bf5632b6a992
|
Independent review found three things worth acting on. All are addressed in The Nothing on the query side was specced at all. The reviewer's mutation removing both fields from One of my new assertions could not fail. It filtered for a constraint name that appears nowhere in its fixture and asserted the count was zero, which holds whether or not the name is carried. It now filters for the name the fixture does carry and asserts it is found; it dies to the mapping mutation along with its siblings. On semver, the reviewer did the experiment I had only argued. Compiling a consumer against Record equality was checked and is inert — nothing keys, dedupes or groups by a whole Gate after the fixes: 305 spec files / 782 tests in the Arc package, Two things the reviewer raised that I did not act on: the helper takes a |
Added
ShouldHaveConstraintViolationForfor asserting a command result names a given constraint, matching the assertion already available against a raw append (a rejection carries no machine-readable identity, so nothing downstream can tell one rejection from another #2487)Fixed