Read a command's key without Chronicle - #2431
Conversation
A read model is loaded by the command's key, and only the Chronicle integration resolved one. An application whose read models are backed by Entity Framework Core or MongoDB and which has no Chronicle therefore had no key at all, and every read model it injected into a command failed as a validation error. Arc now reads the key from the command itself, but only when nothing wrote one - Chronicle always writes one, empty included, so an application with Chronicle resolves keys exactly as it always has. Two rules, both things the application says out loud: the command composes its key through ICanProvideKeyForCommand, or marks the property holding it with the data annotations [Key]. Nothing is inferred from the shape of a command - one carrying two identifiers has no answer that is not a guess, and one that grew a second identifier later would silently stop resolving a read model injected somewhere else entirely. An application that keys its commands its own way says so once through ICanResolveKeyForCommand, which is asked before the rule Arc ships whichever order the two are discovered in. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRa2Z1cA6D3Fuw9TAo6m2p
Two attributes are spelled [Key], and in an application with Chronicle the data annotations one silently does nothing: Chronicle resolves keys from its own KeyAttribute, finds no key property, invents a fresh event source id, and every read model keyed by the command resolves to nothing - surfacing as 'the entity does not exist' rather than as the wiring mistake it is. ARCCHR0008 makes it a build warning. It is reported only where Chronicle is referenced, and only on a command: the data annotations attribute is what an Entity Framework Core read model marks its primary key with, and what Arc reads without Chronicle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRa2Z1cA6D3Fuw9TAo6m2p
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRa2Z1cA6D3Fuw9TAo6m2p
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRa2Z1cA6D3Fuw9TAo6m2p
|
Closes the boundary #2425 shipped and documented: only the Chronicle integration wrote The one thing that makes this safeArc reads a command's key only when nothing wrote one. That is load-bearing, so it is verified rather than assumed:
A written key stands as it is, empty included — overturning an integration's "no usable key" verdict by reading the command behind its back would be worse than the gap this closes. Specs cover the written, written-empty, and unwritten cases separately. Worth your eyes
Why no inference from a command's shapeThe rules are only what the application says out loud. Inferring "the single id-shaped property" would resolve
ARCCHR0008The failure mode it catches is silent: data annotations It is gated on VerificationClean All 15 spec projects green: Arc.Core 1593 (17 new), Chronicle 563 (3 new), Chronicle.CodeAnalysis 40 (4 new), plus Screenplay 1165, ProxyGenerator 1039, MongoDB 148, EF 300 and the rest unchanged. The new specs were also run isolated by filter, not only as part of their project — a whole-project green hid an ordering bug in #2425 earlier today.
Not addressed
ReviewAuthored by an agent with no human review. The |
| foreach (var resolver in _resolvers) | ||
| { | ||
| if (resolver.Resolve(command) is { Length: > 0 } key) | ||
| { | ||
| return key; | ||
| } | ||
| } |
|
The four
Not rerunning it: a rerun would cancel that other pull request's in-progress run in turn. A maintainer rerun at merge time settles it. Everything that does not fan out from |
The analyzer shipped with no reference page, the failure page described only the Chronicle way of declaring a key, and the one page written for readers running Arc without an event store said nothing about a command taking the read model it acts on - which is the audience the whole change is for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRa2Z1cA6D3Fuw9TAo6m2p
The page had grown to cover five topics and 297 lines, past the point one page holds together: where to declare the dependency and what nullable means is a different question from which store the read model comes from and what key loads it. The second is also the half a reader without Chronicle needs, and it was buried at the bottom of a page about Chronicle read models. Every inbound link is re-pointed, and the anchors both pages are linked by are verified to resolve. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRa2Z1cA6D3Fuw9TAo6m2p
Summary
A read model is loaded by the command's key, and only Chronicle resolved one.
Added
[Key], or anICanProvideKeyForCommandcomposing it. The key may be aGuid,int,long,string, or aConceptAs<T>wrapping one of those (Support injecting read models from non-Chronicle providers into the command pipeline #2303)ICanResolveKeyForCommand, for keying commands your own way across an application. It is asked before the rule Arc ships, whichever order the two are discovered in (Support injecting read models from non-Chronicle providers into the command pipeline #2303)ARCCHR0008reports a command marking its key with the data annotations[Key]in an application that uses Chronicle, where it silently resolves nothing (Support injecting read models from non-Chronicle providers into the command pipeline #2303)Fixed