Skip to content

feat(agreement): 1.0 agreement, party and runtime models - #200

Open
mttrbrts wants to merge 9 commits into
mainfrom
claude/agreement-1.0-north-star
Open

feat(agreement): 1.0 agreement, party and runtime models#200
mttrbrts wants to merge 9 commits into
mainfrom
claude/agreement-1.0-north-star

Conversation

@mttrbrts

@mttrbrts mttrbrts commented Aug 23, 2026

Copy link
Copy Markdown
Member

Closes #N/A

Proposes the 1.0 family of Accord Project models: an agreement is a set of documents rather than a single instrument, a template is identified independently of anything created from it, and the types a template extends are held apart from the envelopes that carry them.

This is a design target, not a migration-ready change. It exists so the structural questions behind a stable 1.0 release can be reviewed as a whole rather than one namespace at a time. It builds on #197, #198 and #196, whose commits are included beneath it.

The namespaces

namespace holds imports Concerto
data@1.0.0 TemplateData, StateData nothing ^3.0.0
party@1.0.0 Party, PartyRef nothing ^3.0.0
template@1.0.0 TemplateReference, TemplateArtifact, TemplateArtifactRole crypto ^3.0.0
agreement@1.0.0 Agreement, AgreementDocument, Clause, Clauses, AgreementParty, AgreementReference crypto, data, party, template ^4.0.0
runtime@1.0.0 Request, Response, AgreementState, ClauseStates crypto, data, agreement ^4.2.0
obligation@1.0.0 Obligation and its subtypes, ObligationTransition, FulfilmentAttempt crypto, money, party, agreement ^4.0.0
obligation/settlement@1.0.0 SettlementObligation, SettlementEvidence, and the evidence concepts crypto, party, obligation ^4.0.0

The Concerto floors follow the map types: only agreement and runtime declare one. runtime needs 4.2.0 specifically because both the declared value type of ClauseStates and the concrete types an instance carries are imported from other namespaces.

agreement@1.0.0 replaces the contract@1.0.0 namespace proposed in #197.

An agreement is a set of documents

A master agreement with schedules and confirmations is one agreement recorded across several instruments. Agreement holds an ordered list of AgreementDocument, each of which may bind its own subset of the parties and may be generated from its own template — or from none, where an instrument binds sub-templates per clause without a document-level template of its own.

Clause instances are addressed by instance path

AgreementDocument.clauses is a map keyed by the TemplateMark instance path of each clause: payment, or deliveries[2].inspection. A static clause tree cannot express this, because the number of instances is decided by the data — a ClauseDefinition inside a ListBlockDefinition yields one instance per element, and one inside a ConditionalDefinition may yield none.

CiceroMark remains authoritative for document structure. This model records provenance over it and does not restate it.

Template data is carried by composition

A template declares one concrete subtype of data@1.0.0.TemplateData holding its instantiated variables, and that subtype is carried in AgreementDocument.data. A template no longer subclasses the agreement envelope, so the envelope's type and its relationship URIs stay stable across template versions.

The data of a clause is a subtree of its document's data, addressed by the clause's instance path. A clause carries no copy of its own.

Runtime state is one revisioned document per agreement

AgreementState is a single identified, revisioned snapshot covering the agreement and every clause instance in it, with clauseStates keyed by the same instance paths as AgreementDocument.clauses. A transition spanning scopes — marking a clause breached because of a condition detected in a peer clause — is then one write producing one revision and one hash, with sibling state in scope without a further lookup. Per-clause state assets would need a cross-asset transaction Concerto does not provide, and would leave revision and previousStateHash without a well-defined meaning.

data@1.0.0 holds only what templates extend

TemplateData and StateData are empty abstract concepts, and they are the two types every template subclasses. Holding them in a namespace that imports nothing means declaring the shape of a template's own data does not drag in the agreement envelope, the state envelope, or the party and hash types those carry.

The template quoted under Engine compatibility below is the whole of the effect: it declares concerto version "^3.0.0", and its imports are data@1.0.0, runtime@0.2.0 and money@0.3.0 — no agreement, no crypto, no party, and no Concerto 4 floor, because the map types requiring it are in the consumers rather than the extension points.

template@1.0.0 is independent of agreements

A template is cited by records that hold no agreement — obligations, attestations and settlement evidence all name the template an artefact derives from, and a registry may catalogue templates with no agreement in sight. template@1.0.0 imports only the hash types, so naming a template does not require taking on the agreement model.

One party representation

party@1.0.0 defines Party, which is registry-resident, and PartyRef, a portable projection for records that must travel standalone and cannot assume a resolver. The 1.0 family uses these two throughout, in place of the three representations in use today (--> Party, obligation's local PartyRef, signature's Signatory).

Engine compatibility

Measured against unmodified cicero-core@2.1.1 and template-engine@4.0.0.

Composition works on today's engine. A @template concept that extends data@1.0.0.TemplateData rather than an agreement asset, with nested clauses that are plain concepts, loads, validates and renders with no engine change:

templateModel: test.msa4@1.0.0.MsaData | super: org.accordproject.data@1.0.0.TemplateData
requestTypes: [ 'org.accordproject.runtime@0.2.0.Request', 'test.msa4@1.0.0.FeeRequest' ]
sample validates: OK
--- RENDER ---
Agreement with Acme Corporation for a fee of 15000.0 USD.
Confidentiality. Term of 5 years.

Neither half of the engine requires Contract or Clause inheritance. findTemplateConcept looks for exactly one non-abstract declaration carrying @template, and {{#clause X}} resolves property X on the current model and recurses.

Concerto 4 is not a consumer-side constraint. cicero-core@2.1.1 and template-engine@4.0.0 both already bundle concerto-core@4.1.4, and map-bearing models load and render today. runtime@1.0.0's 4.2.0 floor is real but confined to it: on 4.1.4 an AgreementState carrying template-defined clause states fails to serialize, and on 4.2.0 the same instance round-trips with every key and $class preserved (accordproject/concerto#1279).

Adopting runtime@1.0.0 needs a cicero-core change. Template resolves the runtime base types by fully-qualified name against runtime@0.2.0, and getType() throws when a namespace is absent, so a template on a 1.0 runtime namespace cannot be opened at all. This is version-bump coupling that lands on any runtime@1.0.0, including the conservative promotions in #198 and #199. Fixed by accordproject/template-archive#946.

Landing order

Publish order is a hard chain, since the build resolves imports over HTTP:

data → party → template → agreement → runtime → obligation → obligation/settlement

data, party, template, agreement, obligation and obligation/settlement can land and publish independently of the engine. Templates adopt the 1.0 data shape while keeping runtime@0.2.0 for Request and Response.

runtime@1.0.0 should follow the release of accordproject/template-archive#946. Publishing it earlier creates a namespace no template can adopt, and model URLs are permanent.

For the same reason contract@1.0.0 must never be published if this direction is taken. A dead namespace is worse than the rename.

Migration cost

Measured against the 37 active templates in accordproject/cicero-template-library. The library's other 20 carry "archived": true and are excluded from its index page by run.js, so they are excluded here too.

what changes active templates affected needs an engine change?
asset TemplateModel extends Contract|Clause becomes concept … extends TemplateData 37 of 37 no
extends Clause has no direct analogue; a standalone clause template becomes a document with data 22 no
extends runtime@0.2.0.Obligation (an event) becomes an obligation@1.0.0 asset 16 no
extends State becomes extends StateData, composed rather than subclassed 10 yes — template-archive#946
extends Request / extends Response — unchanged 32 / 31 n/a

Every active template subclasses Contract or Clause, so the inheritance-to-composition change reaches all of them without exception. It is a mechanical edit that today's engine already accepts.

Composition of templates, which this model is largely built to support, is used by exactly 1 of 37 active templates today (copyright-license). The case for it is forward-looking rather than a fix for current pain, and that is worth being explicit about.

Deliberately not modelled

Each of these was considered and left to an extension profile. The models document what a profile would have to define.

  • Order of precedence — which document prevails where two conflict. Agreement.documents holds reading order, and precedence is commonly its inverse.
  • Supersession and amendment — a link between documents rather than an ordering over them.
  • Incorporation by reference — including terms published elsewhere, and whether as at a fixed version or as amended from time to time.
  • Cross-level hash commitment — every hash commits only to its own level. There is no Merkle relationship between an agreement, its documents and its clauses.
  • Party role vocabularyAgreementParty.role is an open string.
  • Template artifact manifests — where TemplateReference.artifacts is populated, a profile must define the manifest canonicalization and require the list to agree with artifactManifestHash.

Other notes

  • Obligations may be created without template provenance. AgreementReference.template is optional, where obligation's local AgreementRef required templateId and templateHash. An agreement recorded across several documents has no single template, so the stricter rule was not satisfiable.
  • runtime@1.0.0 drops the deprecated runtime@0.2.0.Obligation event. 21 templates subclass it; obligation@1.0.0 is the destination.
  • data@1.0.0 is a working name. templatedata or extension are equally defensible.

Screenshots or Video

Not applicable. Concerto model changes only.

Related Issues

Author Checklist

  • Ensure you provide a DCO sign-off for your commits using the --signoff option of git commit.
  • Vital features and changes captured in unit and/or integration tests
  • Commits messages follow AP format
  • Extend the documentation, if necessary
  • Ensure that CI passes

🤖 Generated with Claude Code

mttrbrts pushed a commit to accordproject/cicero-template-library that referenced this pull request Aug 23, 2026
Prototypes migrating copyright-license's nested PaymentClause onto the
model design proposed in accordproject/models#200 ("Agreement 1.0 Model
Redesign"): composition over inheritance (TemplateData/ClauseData composed
into an AgreementDocument instead of subclassing Contract/Clause), a
unified Party type replacing the duplicated licensee*/licensor* string
fields, and clause instances addressed by path through a Concerto 4 Map.

The vendored party@0.1.0/agreement@0.1.0 models, sample.json round-trip,
and trigger() logic all work end to end. Natural-language rendering is
marked as an expected failure: markdown-template's grammar resolution
only looks at properties declared directly on the @template class and
never walks the supertype chain, so a composed/inherited envelope isn't
renderable with today's tooling -- consistent with that PR being flagged
"a design target, not migration-ready". Details are in the model file
comments and the template's README.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011LWZaJoaq9HFMVceNxCXMS
Signed-off-by: Claude <noreply@anthropic.com>
@mttrbrts

Copy link
Copy Markdown
Member Author

Findings from the prototype migration, and four proposed changes

cicero-template-library#528 migrates copyright-license onto this design. It surfaced two problems — duplicated clause data in sample.json, and a grammar that has to wrap its whole body in {{#with data}}. Both are worth acting on here, though only one is a defect in this PR.

The duplication is a divergence from this PR, not a flaw in it

Clause here has no data property — it was removed during review precisely because a clause's data is a subtree of the document's data. The prototype re-added it, along with a ClauseData type that doesn't exist in this namespace:

// this PR                            // prototype's agreement.cto
concept Clause {                      concept Clause {
  o String path                         o String path
  o TemplateReference template          o ClauseData data   <- reintroduced
  o String clauseId optional          }
  o ContentHash clauseHash optional
}

The cost shows up in the prototype's logic.ts, which cannot tell which copy is authoritative:

const paymentTerms = (clauses['paymentTerms']?.data as IPaymentTerms) ?? licenseData.paymentTerms;

But it exposed a real gap in this PR. Clause.template is required, because Clause means "a sub-template archive composed into this document". copyright-license has no sub-template: its old PaymentClause extends Clause was nested data carrying an id, and {{#clause paymentTerms}} is an inline grammar block. Its clauses map should therefore be absent, not populated with a duplicate. This PR never says so plainly, which is how it came to be read the other way. Worth noting that copyright-license is the one active template that does not exercise composition of templates, so it is a harder migration target than it looks.

The {{#with data}} wrapper is caused by @template sitting on the envelope

The prototype declares @template asset TemplateModel extends AgreementDocument {}, so the grammar's root is the envelope and every variable is one level down under data.

That is not only verbose, it cannot work at all. In markdown-template/lib/TypeVisitor.js:

construct line lookup walks supertypes
VariableDefinition 184 getProperty yes
ClauseDefinition 219 getOwnProperty no
WithDefinition 249 getOwnProperty no

Confirmed against concerto-core: for Sub extends AgreementDocument {}, getProperty("data") returns true but getOwnProperty("data") returns false. So {{#with data}} on a subclass with an empty body can never resolve, which is why the prototype's render tests are expected failures. Plain {{variable}} works through inheritance; block constructs do not.

Proposed fix: the renderable root is the TemplateData subtype

@template
concept CopyrightLicenseData extends TemplateData {
  o DateTime effectiveDate
  o PartyRef licensee
  o PartyRef licensor
  o String territory
  o String purposeDescription
  o String workDescription
  o PaymentTerms paymentTerms
}

This resolves all three symptoms together:

  • no {{#with}} — the grammar roots at the data, so {{effectiveDate}} resolves directly
  • no getOwnProperty failure — the @template class declares its properties directly, so nothing depends on the supertype walk
  • no duplication — sample.json's root becomes the data, so there is no envelope and no clauses map in it

The division of labour becomes explicit: a template author writes data; the runtime wraps it in a provenance envelope. The envelope keeps the stable $class that motivated composition in the first place, and the template author never has to see it.

Template data should use PartyRef, not --> Party

The prototype hand-rolls a resolveParty() helper to walk the parties array, because a relationship arrives at trigger() as a "resource:...#me" string with no resolution step. That is exactly what PartyRef is for in party@1.0.0 — a portable, embedded party value for records that travel standalone, and template data is such a record. --> Party belongs on the envelope's AgreementParty, where a resolver exists. Making this change deletes resolveParty entirely.

Changes to make in this PR

  1. Document the @template placement rule: the renderable root is the TemplateData subtype, never the envelope.
  2. State that clauses records composed sub-template archives only; inline {{#clause}} blocks get no entry.
  3. Pin the root of Clause.path. The prototype used "data.paymentTerms"; this PR intends "paymentTerms". Moving @template onto the data makes that unambiguous.
  4. Note that template data should carry PartyRef rather than --> Party.

Points 1 and 3 are the substantive ones: they mean the agreement envelope is a runtime concern rather than something a template author writes, which is a clearer statement of the design than the PR currently makes.

Not pushed as a commit — raising for discussion first.

@mttrbrts
mttrbrts force-pushed the claude/agreement-1.0-north-star branch from 3a0f758 to cbff6f2 Compare August 24, 2026 08:00
@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

🧩 Concerto Playground Preview

The .cto model changes in this PR are pre-loaded into the Concerto Playground — click a link below for a live preview.

File Preview
src/accordproject/agreement@1.0.0.cto Diagram · Code · Open in Playground
src/accordproject/obligation/settlement@1.0.0.cto Diagram · Code · Open in Playground
src/accordproject/obligation@1.0.0.cto Diagram · Code · Open in Playground
src/accordproject/party@1.0.0.cto Diagram · Code · Open in Playground
src/accordproject/runtime@1.0.0.cto Diagram · Code · Open in Playground
src/accordproject/template@1.0.0.cto Diagram · Code · Open in Playground
src/accordproject/templatedata@1.0.0.cto Diagram · Code · Open in Playground

All 7 changed models together: Preview combined

Auto-generated from commit 8095087. Updates automatically on new commits.

niallroche and others added 5 commits August 27, 2026 09:16
Signed-off-by: Niall Roche <niall.roche@gmail.com>
Signed-off-by: Niall Roche <niall.roche@gmail.com>
Signed-off-by: Niall Roche <niall.roche@gmail.com>
Signed-off-by: Niall Roche <niall.roche@gmail.com>
Builds on the contract, runtime and obligation 1.0 models from #197, #198
and #196, which are included here unchanged as the commits beneath this
one. This commit is the delta: it keeps their type inventory and reworks
the structure underneath it.

This is a design target, not a migration-ready change. It settles the
structural questions that a stable 1.0 release needs answered, so that the
answers can be reviewed together rather than one PR at a time.

- Rename contract@1.0.0 to agreement@1.0.0, and model an agreement as a
  set of documents rather than a single instrument, so that a master
  agreement with schedules and confirmations is expressible.
- Add party@1.0.0 defining Party and its portable PartyRef projection, so
  the 1.0 family has one party representation instead of three.
- Address clause instances by TemplateMark instance path in a map, rather
  than by a static tree, since clause instances are data-driven: a list
  block yields one instance per element and a conditional may yield none.
- Carry template data by composition in TemplateData rather than by
  subclassing the agreement envelope, so the envelope's type and its
  relationship URIs stay stable across template versions.
- Hold runtime state as one revisioned document per agreement, with
  per-clause state keyed by the same instance paths.
- Point obligation@1.0.0 at the shared PartyRef and AgreementReference in
  place of its local equivalents.

contract@1.0.0 is deleted rather than kept alongside agreement@1.0.0.
Model URLs are permanent, so it must never be published if this direction
is taken: a dead namespace is worse than the rename.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Matt Roberts <code@rbrts.uk>
@mttrbrts
mttrbrts force-pushed the claude/agreement-1.0-north-star branch from cbff6f2 to 34df2fd Compare August 27, 2026 08:19
@mttrbrts mttrbrts changed the title feat(agreement): 1.0 agreement, party and runtime models (north star) feat(agreement): 1.0 agreement, party and runtime models Aug 27, 2026
mttrbrts and others added 2 commits August 27, 2026 10:25
…ate wrapper

Two changes that the 1.0 models were waiting on, plus the namespace split
that falls out of the first.

Collapse ClauseState.
  runtime@1.0.0.ClauseStates took a single-property ClauseState wrapper
  rather than StateData directly, because a map value whose concrete type
  came from another namespace could not be serialized, and a template's
  StateData subtype always does. accordproject/concerto#1279 shipped in
  concerto-core 4.2.0, so the wrapper is gone and the map says what it
  means. Verified on the real models: an AgreementState carrying two
  template-defined clause states, keyed by instance path, round-trips
  losslessly on 4.2.0 and fails on 4.1.4.

  The declared range moves to ^4.2.0, since that is now genuinely the
  floor, and the build's 4.x slot moves with it so the declaration
  resolves rather than silently falling back to the default parser.

Extract template@1.0.0.
  TemplateArtifactRole, TemplateArtifact and TemplateReference leave
  agreement@1.0.0 for a namespace of their own. They describe an archive,
  not an agreement, and they are cited by records that hold no agreement:
  obligations, attestations and settlement evidence all name the template
  an artefact derives from, and a registry may catalogue templates with no
  agreement in sight. The new namespace imports only the hash types, so
  naming a template no longer pulls in the agreement model.

Publish order becomes: party, template, agreement, runtime, obligation,
obligation/settlement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Matt Roberts <code@rbrts.uk>
The two extension points every template subclasses no longer live in the
namespaces that consume them.

TemplateData sat in agreement@1.0.0 and StateData in runtime@1.0.0, so
declaring the shape of a template's own data meant importing the agreement
envelope or the runtime state envelope, and the party, hash and map types
they carry. That is the wrong dependency direction: a template subclassing
TemplateData is describing its variables, not asserting anything about
agreements, documents or clause structure.

Both are now in data@1.0.0, which declares nothing but the two empty
abstract concepts and imports nothing at all. A template that subclasses
them takes on no envelope and, because the map types requiring Concerto 4
are in the consumers rather than the extension points, stays on Concerto 3
syntax.

runtime@1.0.0.ClauseStates now takes a value type that is itself imported,
as well as carrying concrete types from a template's namespace. Both are
handled by concerto-core 4.2.0, which the namespace already requires.

Publish order becomes: data, party, template, agreement, runtime,
obligation, obligation/settlement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Matt Roberts <code@rbrts.uk>
@mttrbrts
mttrbrts marked this pull request as ready for review August 27, 2026 10:04
@mttrbrts
mttrbrts requested a review from a team August 27, 2026 12:37
@niallroche

Copy link
Copy Markdown
Contributor

thanks for consolidating into this PR Matt and for catching the 'runtime@0.2.0' dependency. I am happy for agreement@1.0.0 to replace contract@1.0.0, and to close #197.

Here are some observations:

1. Hash rules

Agreement.agreementHash, AgreementDocument.documentHash, AgreementState.stateHash and AgreementReference.agreementHash are each documented as committing to "the canonical representation, excluding the property itself", and the canonicalization is not yet defined. As it stands two conformant implementations could produce different hashes for the same agreement and neither would be wrong.

This is the same point I raised about stateHash on #198, and I mention it again only because the surface has grown from one field to four, which makes it worth settling deliberately rather than by default.

The awkward part is that optional-to-required is a breaking change, so whichever way 1.0 publishes is effectively permanent. That suggests either making the four optional in the core models and letting a deterministic profile require them and define the rules, or defining the canonicalization normatively here.

I have a working canonicalization for this in the x402 settlement work — RFC 8785 JCS over Concerto JSON with the hash field excluded, with golden and tamper vectors. I would be glad to write it up as a profile PR against whichever of the two routes you prefer, so it is not extra work landing on you.

2. Finishing the party consolidation

Collapsing the three party representations is clearly right, and Party registry-resident with PartyRef portable is a cleaner statement of the distinction than I managed — scheme in particular is a real improvement on the bare identifier I had in Signatory.

One point on signature:

signature@1.0.0 Signatory { identifier, name optional, role optional, uri optional }
party@1.0.0     PartyRef  { id, scheme, label optional }

role is doing work in attestations — it distinguishes a signatory who is a party from a witness, a delegate, or a counter-signing provider, and an attestation that cannot record which is weaker as evidence.

I do not think it belongs on PartyRef, since the same party may sign in different roles on different documents. It sits more naturally on the attestation. If you agree, I will make that change in #195 as part of moving it onto party@1.0.0, and you would not need to alter anything here.

3. Clause.path and the map key

AgreementDocument.clauses is keyed by clause instance path, and Clause also declares o String path, so the same value is recorded in both places with nothing requiring them to agree.

Dropping Clause.path and letting the key be authoritative would remove the question. If it is there for the case where a Clause travels outside its map, that is a good reason to keep it, and documenting the invariant plus a test would cover it.

4. templateId

A small consideration is that identifier inside a TemplateReference still reads to me as though it identifies the reference rather than the template, which is the confusion behind the "should this be identified?" comments on #197. Deferring identifier syntax to a profile is right and separate from what the field is called. Entirely your call.

5. Questions

  • Is AgreementState intentionally registry-bound? It holds --> Agreement, and Agreement holds --> AgreementDocument[], so state cannot travel without a resolver, while obligations and settlement evidence reference agreements portably. You state the registry-resident and portable rule explicitly for Party; the equivalent sentence for state would settle it.

  • data@1.0.0 naming. Of your two alternatives I prefer templatedataextension names the mechanism, whereas the namespace holds exactly the two types templates extend.

Matt Roberts and others added 2 commits September 1, 2026 21:15
Responds to the review on #200.

Hash canonicalization is not defined by these models, so
agreementHash, documentHash, stateHash and AgreementReference's
agreementHash become optional. Two implementations hashing the same
agreement under different rules would both be conformant today, and
optional to required is the breaking direction, so the core models
leave the properties open and a deterministic profile fixes the
canonicalization and may require them. Where a hash is supplied its
crypto@1.0.0 Canonicalization should be populated, so a verifier can
tell which discipline produced it.

Clause.path is dropped. It restated the key of the
AgreementDocument.clauses map with nothing requiring the two to agree.
The key is now the single statement of a clause's instance path, and
records citing a clause from outside the map carry the path
themselves, as AgreementReference already does.

The data@1.0.0 namespace becomes templatedata@1.0.0. It holds exactly
the two types templates extend, and a generic namespace name is not
one this repository should claim for it.

TemplateReference.identifier becomes templateId, which names the
template rather than the reference. Identifier syntax remains deferred
to a profile.

AgreementState is documented as intentionally registry-resident: a
revision counter and a previous-state hash chain are only meaningful
where successive revisions resolve against one another, which is why
it holds a relationship rather than the portable AgreementReference
that obligations and settlement evidence use.

PartyRef is documented as deliberately carrying no role. A role is a
property of an act, not of a party, so it belongs on AgreementParty
and on attestations rather than on the portable party projection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPtNYorvvn7MQwTovLXp2r
Signed-off-by: Matt Roberts <matt@rbrts.uk>
Follows up the prototype migration findings raised on #200. Doc
comments only; no structural change.

The @template decorator belongs on the TemplateData subtype, never on
the agreement envelope. This is not a matter of taste: markdown-template
resolves clause and with blocks with getOwnProperty, which does not walk
supertypes, so {{#with data}} on an empty subclass of an envelope never
resolves and the template cannot render at all. Rooting the grammar at
the data removes the wrapper, and makes the division of labour explicit:
a template author writes data, and the runtime wraps it in a provenance
envelope the author never sees.

The clauses map records sub-template archives composed into a document,
not the document's clause structure. An inline clause block, written in
the parent grammar and composing no separate archive, gets no entry, and
a document whose clause blocks are all inline has no clauses map rather
than an empty one. This is why Clause.template is required.

Clause instance paths are rooted at the document's data rather than at
the document, so a clause bound to the paymentTerms property has the
path "paymentTerms" and never "data.paymentTerms".

Template data should name parties with party@1.0.0's PartyRef rather
than a relationship to Party. Data reaches contract logic as a
serialized value with no resolution step, where a relationship arrives
as an unresolvable "resource:...#me" string. Relationships to Party
belong on the envelope, where a registry can resolve them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPtNYorvvn7MQwTovLXp2r
Signed-off-by: Matt Roberts <matt@rbrts.uk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants