feat(agreement): 1.0 agreement, party and runtime models (north star) - #200
feat(agreement): 1.0 agreement, party and runtime models (north star)#200mttrbrts wants to merge 1 commit into
Conversation
Proposes org.accordproject.agreement@1.0.0 as the successor to the contract@1.0.0 namespace, with the party and runtime models it depends on and the obligation models updated to share its types. 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. - 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. - Carry template data by composition in TemplateData rather than by subclassing the agreement envelope. - 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. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Matt Roberts <code@rbrts.uk>
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.
Closes #N/A
Proposes
org.accordproject.agreement@1.0.0as the successor to thecontract@1.0.0namespace in #197, together with the party and runtime models it depends on, and updates the obligation models in #196 to share its types.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 together rather than one PR at a time. It breaks every existing template. See #199 for a companion PR that promotes the same namespaces to 1.0 without breaking anything, and the migration cost table below for what the gap between them actually is.
Changes
agreement@1.0.0replacescontract@1.0.0. An agreement is a set of documents rather than a single instrument, so a master agreement with schedules and confirmations is expressible.party@1.0.0definesPartyand its portablePartyRefprojection, so the 1.0 family has one party representation instead of the three it has today (--> Party, obligation'sPartyRef, signature'sSignatory).payment,deliveries[2].inspection) rather than by a static tree. AClauseDefinitionnested in aListBlockDefinitionyields one instance per data element, and one nested in aConditionalDefinitionmay yield none, so the instance count is data-driven and a static tree cannot express it. CiceroMark remains authoritative for structure; this model records provenance over it.TemplateDatarather than by subclassing the agreement envelope.obligation@1.0.0drops its localPartyRefandAgreementReffor the shared types.Flags
obligation@1.0.0needs^4.0.0even though it declares no map itself.ClauseStateis a workaround, not a modelling decision. The map would otherwise takeStateDatadirectly. A map value whose concrete type is imported from another namespace cannot be (de)serialized — see fix(concerto-core): serialize maps with cross-namespace concept values concerto#1279, open and unreleased as of 4.1.5 — and a template'sStateDatasubtype always is. Collapse it once that ships.party→agreement→runtime→obligation→obligation/settlement.contract@1.0.0must never be published if this direction is taken. Model URLs are permanent, and a dead namespace is worse than the rename.AgreementReference.templateis optional, where obligation's localAgreementRefrequiredtemplateIdandtemplateHash. An agreement recorded across several documents has no single template, so the stricter rule was not satisfiable.Migration cost for existing templates
Measured against the 37 active templates in
accordproject/cicero-template-library. The library's other 20 templates carry"archived": truein theirpackage.jsonand are excluded from its index page byrun.js, so they are excluded here too.asset TemplateModel extends Contract|Clausebecomesconcept … extends TemplateDataextends 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 with no exceptions.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.Screenshots or Video
Not applicable. Concerto model changes only.
Related Issues
Author Checklist
--signoffoption of git commit.🤖 Generated with Claude Code