Skip to content

feat(runtime): add runtime 1.0 model - #198

Open
niallroche wants to merge 1 commit into
accordproject:mainfrom
niallroche:codex/runtime-1.0-model
Open

feat(runtime): add runtime 1.0 model#198
niallroche wants to merge 1 commit into
accordproject:mainfrom
niallroche:codex/runtime-1.0-model

Conversation

@niallroche

@niallroche niallroche commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Closes #N/A

Introduces org.accordproject.runtime@1.0.0 after publication of contract@1.0.0. The model keeps generic request and response semantics, gives runtime state stable identity and revision history, and moves reusable obligation vocabulary out of the runtime namespace.

Changes

  • Add versioned 1.0 Request and Response base transactions.
  • Replace the unidentified empty state with a concrete, identified State asset.
  • Bind each state snapshot to its governing contract@1.0.0.Contract.
  • Add revision, effective time, state hash and previous-state hash fields.
  • Remove Obligation from the new runtime namespace; new implementations use org.accordproject.obligation@1.0.0.
  • Preserve the existing unversioned and runtime@0.2.0 models unchanged for compatibility.

Flags

  • This PR must be merged only after contract@1.0.0 is merged and published because the models build resolves imports through models.accordproject.org.
  • stateHash is calculated over the canonical state representation excluding the stateHash property itself.
  • Canonical state serialization and transition validation rules must be documented before publication.
  • Removing the legacy runtime obligation from the 1.0 namespace is intentional. A migration mapping to the dedicated obligation model is still required.
  • The model remains syntactically compatible with Concerto 3 while being validated with Concerto 4.1.5.

Screenshots or Video

Not applicable. This PR contains a Concerto model change 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
  • Merging to main from niallroche:codex/runtime-1.0-model

Signed-off-by: Niall Roche <niall.roche@gmail.com>
@niallroche
niallroche requested review from dselman and mttrbrts August 19, 2026 13:39
*/
asset State identified by stateId {
o String stateId
--> Contract contract

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed recently that Devanshi was forced to convert all of our sample templates to Contract templates (from Clause templates) because of a similar change in Obligations.

This change mirrors the Obligation design, although collectively it effectively makes Clauses unable to have state and emit Obligations. That could be a sensible design (and today there isn't a technical difference between the two), however we should make this decision consciously.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — this should be a conscious decision.

One historical detail: runtime@0.2.0.Obligation already required --> Contract contract, so the Contract-only obligation constraint predates these PRs. What is new here is that State changes from an empty, unbound asset into one requiring the same Contract relationship.

My initial idea was to add clauseId to State, but that would not actually solve the problem you identified: a Clause template would still need a Contract instance to satisfy the required relationship.

I think the cleaner solution is to replace:

--> Contract contract

with:

o AgreementReference agreement

from contract@1.0.0. AgreementReference already carries the agreement id/hash, template provenance, and optional clauseId/clauseHash. That allows both Contract and Clause templates to create state without making Clause extend Contract, while retaining stronger provenance than an unqualified identifier.

The deliberate trade-off is replacing a resolved Concerto relationship with a portable value reference and repeating agreement/template provenance on each state snapshot. I think that is appropriate for state which must work without a Contract asset or registry, but would welcome your view

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be what I would propose

import org.accordproject.contract@1.0.0.AgreementReference from https://models.accordproject.org/accordproject/contract@1.0.0.cto
import org.accordproject.crypto@1.0.0.ContentHash from https://models.accordproject.org/crypto/crypto@1.0.0.cto

asset State identified by stateId {
  o String stateId
  o AgreementReference agreement
  o Long revision default=0 range=[0,]
  o DateTime effectiveAt
  o ContentHash stateHash optional
  o ContentHash previousStateHash optional
}

Note: the contract@1.0.0.Contract import is no longer needed and should be dropped.

--> Contract contract
o Long revision default=0 range=[0,]
o DateTime effectiveAt
o ContentHash stateHash

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting that this makes the Crypto namespace a runtime dependency for all templates.

No immediate concern, but it could force us to rev versions here if we bump minor versions of the crypto model.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. contract@1.0.0 already makes crypto a transitive dependency because its agreement and template provenance use ContentHash, so the direct runtime import makes that dependency visible rather than introducing it for the first time.

Because the crypto version is part of the namespace, publishing crypto@1.1.0 would not break an existing runtime@1.0.0; runtime would continue resolving crypto@1.0.0. However, adopting the newer crypto types would require a corresponding runtime revision, and consumers could otherwise encounter both crypto namespaces in the same model graph. So there is genuine adoption coupling even if there is no immediate breakage.

Your comment also exposes a more immediate issue: stateHash is mandatory while canonical state serialization is undefined. I propose making stateHash optional in the core runtime model. A deterministic-state profile can require it and define the representation, exclusions and canonicalization, potentially using JCS over Concerto JSON while excluding stateHash itself.

I would not promise to make it mandatory in a later minor revision, because changing an optional field to required would be breaking.

* predecessor when one exists. A concrete base state is retained so templates
* which need no additional properties can use it directly.
*
* Obligations are intentionally not defined in this namespace. New

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider adding the base Obligation type here (perhaps as abstract), but putting the concrete subclasses in a separate space? I'm trying to judge how much of the obligations namespace is needed for everyday use cases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did consider it, but my preference is still to keep the base Obligation outside runtime.

The 1.0 obligation is durable identified state rather than a runtime event. It carries legal lifecycle status, agreement provenance, revision, evidence/basis hashes and supersession information. Placing that base in runtime would mean future changes to the obligation lifecycle force runtime revisions, even for templates that never use obligations.

However, your everyday-use concern is valid. Concerto resolves the model file and namespace, so importing PaymentObligation effectively loads the complete obligation@1.0.0 model; describing the minimum as only the five directly used types understates that dependency surface.

I suggest keeping the independent obligation namespace, but revisiting the split in #196:

  • a minimal obligation core containing the abstract base, status and shared references;
  • concrete payment/performance/notification types in a separate types or profile namespace; and
  • transitions and fulfilment attempts in a lifecycle profile if we want the smallest possible everyday dependency.

That addresses the dependency concern without coupling obligation evolution to runtime.

@niallroche

Copy link
Copy Markdown
Contributor Author

Thanks for the review @mttrbrts — the three comments surfaced two proposed runtime changes and one obligation-scope question.

  • For Clause compatibility, adding clauseId beside a required Contract relationship would not be sufficient. I propose replacing --> Contract contract with the portable AgreementReference from feat(contract): add contract 1.0 model #197, which already supports optional clause provenance.
  • I propose making stateHash optional in the core model because its canonical computation is not yet normative. A deterministic-state profile can require it without making an underspecified field mandatory in runtime 1.0.
  • I still recommend keeping the base Obligation outside runtime, but agree that Codex/obligation 1.0 models #196's everyday import surface may be too broad. I suggest revisiting how its core types, concrete obligation types and lifecycle records are divided.

Details are in the inline replies.

mttrbrts added a commit that referenced this pull request Aug 27, 2026
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>
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