Today a Plan only carries a templateHash — a hash reference to an off-chain document. That satisfies tamper-evidence but is not machine-enforceable: the orchestrator can't tell whether an execution context satisfies the terms without a human reading the underlying PDF.
This PR lifts the governing-terms object into first-class structured data so commit/abort/unwind can be evaluated automatically.
evaluateClauses() / evaluateCommit() / evaluateAbort() — run clause arrays against a context, return per-clause results without short-circuiting.
buildIssueInstrumentObligation() — derives a sensible default obligation from an issueInstrument step's InstrumentTerms. Bakes in the UCP 600 / URDG 758 rule that MT760 is irrevocable, so unwind applies only when payment failed AFTER instrument dispatch (amendment H / §4.1).
Why a self-contained evaluator
PR P (Rules Engine) ships a more general DSL that this evaluator is a subset of. Keeping a local copy lets PR S be merged independently of PR P — the Condition shape is deliberately identical so clauses authored for one run on the other. Post-merge cleanup can consolidate the two on whichever order they land.
Verification
npx tsc --noEmit clean.
Full jest suite: 8 suites, 100/100 passing (obligation tests contribute 20).
Coverage spans: canonicalize invariance, SHA-256 stability, validation (ISO-4217, hex hash, required fields), evaluator primitives, combinators, path resolution, commit success + failure attribution, abort firing on active exception, buildIssueInstrumentObligation binding template hash + governing law, non-throwing error surfacing for bad regex.
Closes gap-analysis v2 §4.1 partial (Legal / Obligation Layer).
Today a Plan only carries a `templateHash` — a hash reference to an off-chain document. That satisfies tamper-evidence but is not machine-enforceable: the orchestrator can't tell whether an execution context *satisfies* the terms without a human reading the underlying PDF.
This PR lifts the governing-terms object into first-class structured data so commit/abort/unwind can be evaluated automatically.
## What
- `services/obligations/types.ts` — `ObligationTerms` schema: `Consideration`, `validIssuance[]`, `validPayment[]`, `commit[]`, `abort[]`, `unwind[]`, `AuthorizedParticipant[]` (role set matches PR M's SoD: coordinator / approver / releaser / validator / exception_manager / operator), `GoverningDocument[]` (keyed by `templateRef` + SHA-256 `templateHash`, reusing the convention from existing `InstrumentTerms`).
- `services/obligations/evaluator.ts` — closed-operator condition engine (`eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `not_in`, `exists`, `matches`, `length_gte`, `length_lte`) + `all`/`any`/`not` combinators + dotted-and-indexed path resolution (e.g. `plan.steps[1].type`). No `eval`, no code execution, deterministic.
- `services/obligations/index.ts` — public surface:
- `canonicalize()` — lexicographic key-sort JSON encoding.
- `hashObligationTerms()` — SHA-256 of canonical JSON, hex, no `0x` prefix.
- `validateObligationTerms()` — cheap structural check (ISO-4217 currency, hex hash, required fields, non-empty `governingDocuments`).
- `evaluateClauses()` / `evaluateCommit()` / `evaluateAbort()` — run clause arrays against a context, return per-clause results without short-circuiting.
- `buildIssueInstrumentObligation()` — derives a sensible default obligation from an `issueInstrument` step's `InstrumentTerms`. Bakes in the UCP 600 / URDG 758 rule that **MT760 is irrevocable, so unwind applies only when payment failed AFTER instrument dispatch** (amendment H / §4.1).
## Why a self-contained evaluator
PR P (Rules Engine) ships a more general DSL that this evaluator is a subset of. Keeping a local copy lets PR S be merged independently of PR P — the `Condition` shape is deliberately identical so clauses authored for one run on the other. Post-merge cleanup can consolidate the two on whichever order they land.
## Verification
- `npx tsc --noEmit` clean.
- Full jest suite: **8 suites, 100/100 passing** (obligation tests contribute 20).
- Coverage spans: canonicalize invariance, SHA-256 stability, validation (ISO-4217, hex hash, required fields), evaluator primitives, combinators, path resolution, commit success + failure attribution, abort firing on active exception, `buildIssueInstrumentObligation` binding template hash + governing law, non-throwing error surfacing for bad regex.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes gap-analysis v2 §4.1 partial (Legal / Obligation Layer).
Today a Plan only carries a
templateHash— a hash reference to an off-chain document. That satisfies tamper-evidence but is not machine-enforceable: the orchestrator can't tell whether an execution context satisfies the terms without a human reading the underlying PDF.This PR lifts the governing-terms object into first-class structured data so commit/abort/unwind can be evaluated automatically.
What
services/obligations/types.ts—ObligationTermsschema:Consideration,validIssuance[],validPayment[],commit[],abort[],unwind[],AuthorizedParticipant[](role set matches PR M's SoD: coordinator / approver / releaser / validator / exception_manager / operator),GoverningDocument[](keyed bytemplateRef+ SHA-256templateHash, reusing the convention from existingInstrumentTerms).services/obligations/evaluator.ts— closed-operator condition engine (eq,neq,gt,gte,lt,lte,in,not_in,exists,matches,length_gte,length_lte) +all/any/notcombinators + dotted-and-indexed path resolution (e.g.plan.steps[1].type). Noeval, no code execution, deterministic.services/obligations/index.ts— public surface:canonicalize()— lexicographic key-sort JSON encoding.hashObligationTerms()— SHA-256 of canonical JSON, hex, no0xprefix.validateObligationTerms()— cheap structural check (ISO-4217 currency, hex hash, required fields, non-emptygoverningDocuments).evaluateClauses()/evaluateCommit()/evaluateAbort()— run clause arrays against a context, return per-clause results without short-circuiting.buildIssueInstrumentObligation()— derives a sensible default obligation from anissueInstrumentstep'sInstrumentTerms. Bakes in the UCP 600 / URDG 758 rule that MT760 is irrevocable, so unwind applies only when payment failed AFTER instrument dispatch (amendment H / §4.1).Why a self-contained evaluator
PR P (Rules Engine) ships a more general DSL that this evaluator is a subset of. Keeping a local copy lets PR S be merged independently of PR P — the
Conditionshape is deliberately identical so clauses authored for one run on the other. Post-merge cleanup can consolidate the two on whichever order they land.Verification
npx tsc --noEmitclean.buildIssueInstrumentObligationbinding template hash + governing law, non-throwing error surfacing for bad regex.Closes gap-analysis v2 §4.1 partial (Legal / Obligation Layer) — today a Plan only carries a templateHash (hash-reference to an off-chain document). This PR lifts the governing-terms object into structured, machine-enforceable data. - services/obligations/types.ts — ObligationTerms schema: Consideration, validIssuance[], validPayment[], commit[], abort[], unwind[], AuthorizedParticipant[] (role set matches PR M SoD), GoverningDocument[] (keyed by templateRef + SHA-256 templateHash that reuses the existing InstrumentTerms convention). - services/obligations/evaluator.ts — closed-operator condition engine (eq, neq, gt, gte, lt, lte, in, not_in, exists, matches, length_gte, length_lte) + all/any/not combinators, dotted + indexed path resolution (e.g. plan.steps[1].type). No eval, no code execution, deterministic. - services/obligations/index.ts — public surface: canonicalize(), hashObligationTerms(), validateObligationTerms(), evaluateClauses(), evaluateCommit(), evaluateAbort(), buildIssueInstrumentObligation() (derives a sensible default obligation from an issueInstrument step's InstrumentTerms — binds commit, abort, unwind, validIssuance, validPayment clauses that reflect UCP 600 / URDG 758 semantics, including the "MT760 is irrevocable so unwind only applies when payment failed AFTER instrument dispatch" rule from amendment H/§4.1). - tests/unit/obligations.test.ts — 20 tests covering: * canonicalize() key-sorting invariance + array preservation * SHA-256 hash stability and sensitivity to mutation * validateObligationTerms() (shape, ISO-4217 currency, hex hash, authorizedParticipants role required, non-empty docs) * evaluator primitives (eq/gt/lt/in/matches/length_*) * all/any/not combinators * dotted + indexed path resolution * evaluateCommit ok-true + failure attribution * evaluateAbort firing on an active exception * buildIssueInstrumentObligation binding the template hash + governingLaw into governingDocuments * non-throwing error surfacing on bad regex - Full suite: 8 suites, 100/100 passing. tsc --noEmit clean.