feat(agreement): 1.0 agreement, party and runtime models - #200
Conversation
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>
Findings from the prototype migration, and four proposed changescicero-template-library#528 migrates The duplication is a divergence from this PR, not a flaw in it
The cost shows up in the prototype's const paymentTerms = (clauses['paymentTerms']?.data as IPaymentTerms) ?? licenseData.paymentTerms;But it exposed a real gap in this PR. The
|
| 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
getOwnPropertyfailure — the@templateclass 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 noclausesmap 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
- Document the
@templateplacement rule: the renderable root is theTemplateDatasubtype, never the envelope. - State that
clausesrecords composed sub-template archives only; inline{{#clause}}blocks get no entry. - Pin the root of
Clause.path. The prototype used"data.paymentTerms"; this PR intends"paymentTerms". Moving@templateonto the data makes that unambiguous. - Note that template data should carry
PartyRefrather 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.
3a0f758 to
cbff6f2
Compare
🧩 Concerto Playground PreviewThe
All 7 changed models together: Preview combined Auto-generated from commit e8e0fb3. Updates automatically on new commits. |
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>
cbff6f2 to
34df2fd
Compare
…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>
|
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
This is the same point I raised about 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 consolidationCollapsing the three party representations is clearly right, and One point on signature:
I do not think it belongs on 3.
|
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>
The @template decorator is no longer how a template's renderable root is identified. Extending TemplateData is the declaration: a template's model holds exactly one concrete subtype of it, and that subtype is found by its parent type. The decorator restated something the inheritance already said, and nothing kept the two in agreement. Types the data is composed of stay ordinary concepts; only the root extends TemplateData. StateData is resolved the same way. The reason the root cannot be an agreement envelope is unchanged, and is stated without reference to the decorator: 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. 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>
…or alone accordproject/models#200 moved on since #946 was written: the base namespace for a template's own data model is now templatedata@1.0.0 (renamed from data@1.0.0), and it, not runtime@1.0.0, is where StateData lives. Template.STATE_TYPE follows the rename. The bigger change is how the template model itself is found. A template's model now declares exactly one concrete subtype of templatedata@1.0.0.TemplateData, and that subtype is the template model: extending TemplateData is the declaration, so no @template decorator is required to identify it. getTemplateModel() resolves this the same way getStateTypes() and friends already do, by reusing findConcreteSubclassNames(Template.TEMPLATE_DATA_TYPE): - exactly one concrete subtype is the template model - more than one is rejected with an error naming the offending types - none (the namespace predates this convention, or simply is not loaded) falls back to the legacy @template-decorator lookup, so existing decorated templates keep loading unchanged Fixtures that declare state now extend templatedata@1.0.0.StateData, and every reduced local copy of runtime@1.0.0 drops StateData in favour of a new reduced local copy of templatedata@1.0.0. Added test/data/no-template-decorator to cover the decorator-free path and reworked test/data/multiple-concepts to declare two concrete subtypes of TemplateData, covering the new ambiguity error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPtNYorvvn7MQwTovLXp2r Signed-off-by: Claude <noreply@anthropic.com>
The model design proposed in accordproject/models#200 has moved on substantially since this prototype was written. Bring it back in line: - No `@template` decorator, no envelope class. A template's model now declares exactly one concrete subtype of `templatedata@1.0.0.TemplateData`, and that subtype IS the template model (`CopyrightLicenseData`), carrying its variables directly. This also fixes the specific rendering gap the previous revision hit: TemplateMark's TypeVisitor resolves properties via `ClassDeclaration#getOwnProperty`, which doesn't walk supertypes, so an empty `TemplateModel extends AgreementDocument {}` could never resolve its inherited `data`. Rooting the grammar at the data itself removes the `{{#with data}}` wrapper and the problem it hit. - No `clauses` map. `copyright-license` has no sub-template archive -- `{{#clause paymentTerms}}` is an inline grammar block in this template's own grammar -- so `PaymentTerms` is simply a nested ordinary concept, addressed as a subtree of the data. This removes the data-duplication `logic.ts` previously had to paper over with `clauses['paymentTerms']?.data ?? licenseData.paymentTerms`. - `licensee`/`licensor` are `PartyRef` values (a portable embedded reference), not `--> Party` relationships. A relationship reaches `trigger()` as an unresolvable `"resource:...#me"` string with no registry to resolve it against; `resolveParty()` is gone along with the relationship. `--> Party` belongs on the envelope's `AgreementParty`, where a registry exists (see model/agreement.cto, which documents that wider envelope even though this template's own model has none to be composed onto). - Renamed the vendored `data@1.0.0` stand-in to `templatedata@1.0.0`, split into its own model/templatedata.cto per the current namespace split, and updated model/party.cto (adds `PartyRef`) and model/agreement.cto (drops `Clause.path`, adds `AgreementParty`, documents why `Clause`/`Clauses` are out of scope for this prototype) to match. Empirically, removing `@template` fixes rendering but trades it for a worse problem: the installed `@accordproject/cicero-core` (2.1.1, and the copy vendored inside `@accordproject/template-engine`) finds a template's root exclusively via the `@template` decorator, and `Template#validate()` -- called unconditionally by `Template.fromDirectory()` -- calls `getTemplateModel()` unconditionally too. So without the decorator this template cannot be *loaded* at all with today's toolchain, let alone drafted or triggered -- confirmed empirically (see README). The fix is accordproject/template-archive#946, which is not released. `@template` has deliberately not been added back -- that would paper over the exact gap this prototype exists to demonstrate. Instead, test/render.test.mjs gains an `expectedLoadFailures` set (a strict superset of `expectedFailures`, since a template here can't even load) covering this template in both the render and trigger describes, each `it.fails` naming template-archive#946, and the module-level template-loading probe now tolerates a known load failure without taking down the whole render-test file for every other template. npm run test --workspaces --if-present: 49/49 workspace test files pass (212 tests). npm run test:render: 114 passed, 5 expected fail (of 119), 0 unexpected failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPtNYorvvn7MQwTovLXp2r Signed-off-by: Claude <noreply@anthropic.com>
|
Great @mttrbrts. Does removing the decorator need a 'cicero-core' change? Your earlier analysis had 'findTemplateConcept' looking for exactly one non-abstract declaration carrying '@template'. If the supertype now carries that meaning, the lookup has to resolve by parent type instead. The engine-compatibility result was measured with the decorator in place, so I want to check whether this adds a second engine dependency alongside template-archive#946. it seems that it matters for the migration table, which is careful about which rows need one. |
|
Yes, you're right @niallroche. The template-archive prototype now includes the necessary changes too, accordproject/template-archive#946. Similarly, the prototype template migration in accordproject/cicero-template-library#528 aligns. I want to validate the sample template with a clause reference to a separate template, but otherwise think that we're pretty close. |
niallroche
left a comment
There was a problem hiding this comment.
Thanks Matt, that answers the decorator/root-resolution question for me. If template-archive#946 is carrying the TemplateData parent-type lookup with decorator fallback, then the engine dependency is clear and I am happy with that part of the story.
The remaining thing I would like to pin before we call this settled is the composed-template evidence. As I read it, #946 proves the engine can find/load the template data root and move to the 1.0 runtime shape, but it does not construct the runtime agreement envelope: AgreementDocument, documentHash, or the clauses provenance map.
Your note about validating a sample template with a clause reference to a separate template is exactly the missing evidence. The current migration text says composition is used by exactly 1 of 37 active templates today, citing copyright-license; but copyright-license is the inline {{#clause}} case, so it is useful migration coverage but not evidence for composed sub-template archive provenance. Could we either correct that count/wording, or add the separate-template clause sample and use that as the composition evidence?
One related sequencing question: if the envelope construction is not in #946, who owns the first implementation that wraps template data into AgreementDocument, populates documentHash, and builds the clauses map? That feels relevant to whether agreement@1.0.0 can publish ahead of implementation, given the HTTP import publish chain.
Separately, I can take the APAP/LCP L3/L4 follow-up for TermsAcceptance, evidence binding, and hash-discipline alignment as its own PR.
And yes, the extra point about Template.EMIT_TYPE = 'org.accordproject.obligation@1.0.0.Obligation' matters. It means obligation@1.0.0 is no longer off to the side: #946’s engine change depends on it as part of the 1.0 runtime shape, so your #196 becomes part of the practical publish chain.
Closes #N/A
Proposes the
1.0family 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
templatedata@1.0.0TemplateData,StateData^3.0.0party@1.0.0Party,PartyRef^3.0.0template@1.0.0TemplateReference,TemplateArtifact,TemplateArtifactRolecrypto^3.0.0agreement@1.0.0Agreement,AgreementDocument,Clause,Clauses,AgreementParty,AgreementReferencecrypto,templatedata,party,template^4.0.0runtime@1.0.0Request,Response,AgreementState,ClauseStatescrypto,templatedata,agreement^4.2.0obligation@1.0.0Obligationand its subtypes,ObligationTransition,FulfilmentAttemptcrypto,money,party,agreement^4.0.0obligation/settlement@1.0.0SettlementObligation,SettlementEvidence, and the evidence conceptscrypto,party,obligation^4.0.0The Concerto floors follow the map types: only
agreementandruntimedeclare one.runtimeneeds 4.2.0 specifically because both the declared value type ofClauseStatesand the concrete types an instance carries are imported from other namespaces.agreement@1.0.0replaces thecontract@1.0.0namespace proposed in #197.An agreement is a set of documents
A master agreement with schedules and confirmations is one agreement recorded across several instruments.
Agreementholds an ordered list ofAgreementDocument, 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.clausesis a map keyed by the TemplateMark instance path of each clause:payment, ordeliveries[2].inspection. A static clause tree cannot express this, because the number of instances is decided by the data — aClauseDefinitioninside aListBlockDefinitionyields one instance per element, and one inside aConditionalDefinitionmay yield none.The path is held only in the map key.
Clausecarries nopathproperty, so there is one statement of it and no invariant tying two copies together.The 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 — a document whose clause blocks are all inline has noclausesmap rather than an empty one. This is whyClause.templateis required.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
templatedata@1.0.0.TemplateDataholding its instantiated variables, and that subtype is carried inAgreementDocument.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. Instance paths are rooted at the data rather than at the document, so a clause bound to the
paymentTermsproperty has the pathpaymentTermsand neverdata.paymentTerms.The template root is found by its parent type
Extending
TemplateDatais what declares a template's data. A template's model holds exactly one concrete subtype of it, and that subtype is the renderable root. No@templatedecorator marks it: the supertype already carries that meaning, and an annotation restating it is a second statement of the same fact that nothing keeps in agreement with the first. Types the data is composed of stay ordinary concepts; only the root extendsTemplateData.StateDatais resolved the same way.The root must be the data and not an envelope, and that is a hard constraint rather than a preference:
markdown-templateresolves clause and with blocks withgetOwnProperty, which does not walk supertypes, so{{#with data}}on an empty subclass of an envelope never resolves. Plain variables resolve through inheritance; block constructs do not.Runtime state is one revisioned document per agreement
AgreementStateis a single identified, revisioned snapshot covering the agreement and every clause instance in it, withclauseStateskeyed by the same instance paths asAgreementDocument.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 leaverevisionandpreviousStateHashwithout a well-defined meaning.AgreementStateis registry-resident, and intentionally so. It holds a relationship to itsAgreementrather than the portableAgreementReferencethat obligations and settlement evidence use, because a revision counter and a previous-state hash chain are only meaningful where successive revisions resolve against one another. Records that must travel standalone reference an agreement portably; state does not travel, so it does not need to. This mirrors theParty/PartyRefdistinction.templatedata@1.0.0holds only what templates extendTemplateDataandStateDataare 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 aretemplatedata@1.0.0,runtime@0.2.0andmoney@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.0is independent of agreementsA 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.0imports only the hash types, so naming a template does not require taking on the agreement model.TemplateReference.templateIdnames the template rather than the reference. The type is deliberately not Concerto-identified, sotemplateIdcarries no identity semantics of its own, and its syntax remains deferred to a profile.One party representation
party@1.0.0definesParty, which is registry-resident, andPartyRef, 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 localPartyRef, signature'sSignatory).PartyRefdeliberately carries no role. A role is a property of an act, not of a party: the same party may sign in different roles on different documents. Role therefore belongs onAgreementPartyfor a party's role under an agreement, and on an attestation for the capacity in which a signature was given.Template data should name parties with
PartyRefrather than a relationship toParty. Data reaches contract logic as a serialized value with no resolution step, where a relationship arrives as an unresolvable"resource:...#me"string.Hashes are optional and their canonicalization is deferred
Agreement.agreementHash,AgreementDocument.documentHash,AgreementState.stateHashandAgreementReference.agreementHashare optional, and these models do not define how the bytes they commit to are produced. Two implementations hashing the same agreement under different canonicalization rules would both be conformant, so a hash is meaningful only under a profile that fixes those rules.The properties are optional rather than required because optional-to-required is the breaking direction and this family publishes permanently: a profile can tighten what the core model leaves open, and nothing can loosen it afterwards. Where a hash is supplied, its
crypto@1.0.0Canonicalizationshould be populated, so a verifier can tell which discipline produced it rather than having to assume one.CanonicalizationTypealready carriesRFC8785_JCS.Engine compatibility
Measured against unmodified
cicero-core@2.1.1andtemplate-engine@4.0.0.Composition renders. A concept that extends
templatedata@1.0.0.TemplateDatarather than an agreement asset, with nested clauses that are plain concepts, loads, validates and renders with no change to the render path:Neither half of the engine requires
ContractorClauseinheritance.{{#clause X}}resolves propertyXon the current model and recurses, and nothing in the render path depends on the envelope.Finding the template root needs a
cicero-corechange. That measurement was taken with@templatestill on the data concept, which is howfindTemplateConceptlocates the renderable root today: it looks for exactly one non-abstract declaration carrying the decorator. This design no longer uses it, so the root has to be resolved by parent type instead. Until that ships, a template written to this design has no root the published engine can find. Fixed by accordproject/template-archive#946.Concerto 4 is not a consumer-side constraint.
cicero-core@2.1.1andtemplate-engine@4.0.0both already bundleconcerto-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 anAgreementStatecarrying template-defined clause states fails to serialize, and on 4.2.0 the same instance round-trips with every key and$classpreserved (accordproject/concerto#1279).Adopting
runtime@1.0.0needs the same change.Templateresolves the runtime base types by fully-qualified name againstruntime@0.2.0, andgetType()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 anyruntime@1.0.0, including the conservative promotions in #198 and #199. Also fixed by accordproject/template-archive#946.Landing order
Publish order is a hard chain, since the build resolves imports over HTTP:
templatedata,party,template,agreement,obligationandobligation/settlementcan land and publish independently of the engine. Templates adopt the 1.0 data shape while keepingruntime@0.2.0forRequestandResponse.runtime@1.0.0should 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.0must 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": trueand are excluded from its index page byrun.js, so they are excluded here too.asset TemplateModel extends Contract|Clausebecomesconcept … extends TemplateData@templateis dropped; the root is found by itsTemplateDatasupertypeextends Clausehas no direct analogue; a standalone clause template becomes a document with dataextends runtime@0.2.0.Obligation(an event) becomes anobligation@1.0.0assetextends Statebecomesextends StateData, composed rather than subclassedextends Request/extends Response— unchangedEvery active template subclasses
ContractorClause, so the inheritance-to-composition change reaches all of them without exception. The edit itself is mechanical, but it is no longer one today's engine accepts unaided: dropping the decorator moves root resolution onto the supertype, so the whole migration is now gated on template-archive#946 rather than only the state and runtime parts of it. That is a real change in cost from an earlier revision of this PR, which measured the composition shape with the decorator still present.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.
Agreement.documentsholds reading order, and precedence is commonly its inverse.AgreementParty.roleis an open string.TemplateReference.templateIdis a name, a URI or a registry key, and how it is compared.TemplateReference.artifactsis populated, a profile must define the manifest canonicalization and require the list to agree withartifactManifestHash.Other notes
AgreementReference.templateis optional, where obligation's localAgreementRefrequiredtemplateIdandtemplateHash. An agreement recorded across several documents has no single template, so the stricter rule was not satisfiable.runtime@1.0.0drops the deprecatedruntime@0.2.0.Obligationevent. 21 templates subclass it;obligation@1.0.0is the destination.templatedata@1.0.0wasdata@1.0.0in an earlier revision.datawas too generic a namespace for this repository to claim for two concepts, and the name is free to change now and impossible to change after publication.Screenshots or Video
Not applicable. Concerto model changes only.
Related Issues
Author Checklist
--signoffoption of git commit.🤖 Generated with Claude Code
https://claude.ai/code/session_01NPtNYorvvn7MQwTovLXp2r