Adding an artifact no longer creates one. It opens a signing session over
an ArtifactEvent, and the artifact appears -- on every member's device at
once, authored by the group's shared key rather than by whoever typed it
-- when enough members have signed. The same trade the dialects made: a
submission says "I am putting this in front of the group" and the group's
only recourse afterwards is social, while a signature is the group saying
it and it takes a quorum to say. A library is the group's.
**The first version.** This is the part the dialect had no answer for. An
artifact was creating an initial ArtifactVersion as a second submitted
event, and that cannot survive the change: a chapter attaches to a
version rather than to an artifact, so an artifact without one is inert,
but a version cannot be submitted before the artifact it points at
exists, cannot have its own quorum without costing a second signing
session per form, and cannot be invented locally -- an invented id
differs on every device, so members would silently disagree about which
version a chapter hangs off while every screen showed the same artifact.
So the label rides on the artifact as an `artifactVersion` tag and the
row is derived from the signed artifact's own fields when it is applied.
Same bytes in, same row out, everywhere. It is a rumor, because nobody
signed it; what the group signed is the artifact that declares it.
**What went away.** MantraDao.addArtifact and its way up through the
repository. Nothing called it once the screen proposed instead, and
leaving a path that authors an artifact under a member's key while the UI
insists on a quorum would have double-created the version besides.
**Tests.** Three files, and each was checked against a broken
implementation rather than only against a working one: deriving the
version from the clock, dropping the label from the proposal, authoring
the derived row as its reader, and losing the signature on the way out of
the session are all caught. SignedArtifactTest runs a real 2-of-3 quorum
over an actual proposal, because the claim worth holding -- the row is
the group's, and carries proof of it -- is invisible when it breaks.
Not covered: applyInnerEvent's two upserts, which need a database no test
here stands up, and AddArtifactViewModel, which is plumbing across two
dispatchers over a template the tests already pin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>