This is the design, and it now has a schema. The model below is
CONTRACT-SCHEMA v0, which ships as JSON Schema 2020-12 with a hand-mirrored
TypeScript type set, three worked examples and a dependency-free validator. It
has no backend behind it. It is published because arguing with a model is
cheaper before it is built than after.
The twelve
The contract is the atom
There is no “create a workspace” step and no envelope to put things in. A Contract exists, it gets sent, and the Room between the two sides comes into being because a contract crossed between them. A draft has no Room at all. The Room is born at the first Send, and itscreatedAt is that Send’s timestamp — not a moment somebody chose.
Blocks hold words nobody can overwrite
A Block’s text is not a string you can write to. Words move only when a Proposal is accepted. That is the rule the whole system turns on: words never move by proposal alone. There is no call, button, key or agent that writes clause text directly. You propose; the other side decides; the words move or they do not. Blocknumber is display-only. Never key on one — renumbering a contract must not
be able to void a signature.
Accepting and signing are two records
Acceptance
A party agreeing to a Version’s words, recorded against that Version’s
wordsHash.Signature
A party’s name on that same
wordsHash. It requires an Acceptance by the same
party on the same hash, recorded no later.Signatures have three states
held
Signed, and a question is now open on the paper. The words have not moved,
so what that person signed still reads exactly as it did. The signature
survives.
bound
Signed and standing. The words it names are the words in force.
void
The words moved to a new hash, so the signature names text nobody agreed to. It
is dead, a reason says why, and its party has a re-sign waiting.
Versions are minted at two moments
A Version is an immutable snapshot plus awordsHash. One is minted on every
Send, and one on the first signature against a set of words. A mintedBy
field says which moment made it.
Where the words have not moved since the last Send, the signing mint carries the
identical hash. That is not duplication: the hash is the identity of the words,
and the Version is the identity of the moment.
Both mints are needed. Mint only at signing and an unsent negotiation has nothing to
cite — a redline’s “before” would be a quote from a snapshot nobody kept. Mint only
at Send and a signature taken in the room, which is the normal case, binds to a
snapshot that was never struck.
Governing law is a list
Real papers name more than one place — law and forum differ, a schedule carries its own law, a group names one law per entity.governingLaw is therefore always an
array of place slugs, and the door already lets a person pick more than one.
The four router countries — united-kingdom, united-states, canada,
australia — hold several legal systems each, so nothing can be governed by them.
The door refuses them and the schema refuses them too.
The eight laws
Each of these is carried in the schema itself with a named check in the validator. A law with no check fails the run, so none can be left as a comment.L1 · Accept is not sign
L1 · Accept is not sign
Separate records. A Signature requires an Acceptance by the same party on the
same
wordsHash, no later than the signature.L2 · Lex writes, never sends and never signs
L2 · Lex writes, never sends and never signs
LexNote has three kinds, none of them a verb that leaves the room. An Event
with a Lex actor may only be created, edited, proposed or decided.L3 · Found, never read
L3 · Found, never read
Lex finds words and says where. No kind, state or event type in the schema is
named read, reviewed, analysed or summarised — the validator greps to keep it
that way.
L4 · No model before sign-in
L4 · No model before sign-in
A Contract may carry a LexNote only once its sender has an account.
L5 · Governing law may be several places
L5 · Governing law may be several places
An array, always.
L6 · The other side never pays and never makes an account
L6 · The other side never pays and never makes an account
An account id is optional everywhere, and the schema has no price, plan, seat,
billing, invoice, payment or subscription property anywhere — checked by name.
L7 · A Room is derived from the first Send
L7 · A Room is derived from the first Send
A draft’s room is null. Otherwise the room’s
createdAt equals the first sent
event.L8 · A Version is immutable, and so is the log
L8 · A Version is immutable, and so is the log
Sequence runs 1, 2, 3…, times never go backwards, and the same words always give
the same hash.
The gap: CANON-1 does not exist
Every signature in this model binds tosha256 of CANON-1 canonical text, the
certificate names contracts.io/canon/1, and no file anywhere defines what
CANON-1 does.
Nothing about signing is checkable by anybody outside this system until it is
written. The examples’ hashes use a stated stand-in so the files are internally
consistent, and every one of them changes the day CANON-1 lands.
It has to answer six things and nothing else: whitespace, whether numbering is
excluded (it should be), Unicode normalisation, block order and separators, whether
governing law is inside the hash (it should be), and the exact encoding.
The nouns, settled
Contract not paper, Proposal not ask, Version not receipt — the glossary picks one
of each and says what lost.