1. Executive intent
Deal Engine is the deal terms + computation platform inside the UTA Client Framework. It standardizes how deal terms are represented, validated, computed, audited, searched, and integrated with Client Processing and departmental systems.
This spec is implementation-oriented: it describes what to build, how the services work, how data flows, how correctness is enforced, and what the interfaces are. It is designed to be handed to coding agents to build the system incrementally without losing architectural integrity.
Business outcomes this spec is designed to achieve
- Deal term breakdown and analysis: represent deals as structured clauses and components so we can compare, aggregate, forecast, and explain deal economics across departments and time.
- Contingent compensation tracking: watch external/internal events and surface obligations as they become due, reducing missed revenue and manual “spreadsheet chasing.”
- Faster, safer booking-to-cash: provide CP with deterministic obligations/deltas so invoicing and payments are timely, auditable, and resilient to late-arriving changes.
- Lower operational cost: reduce re-keying and ad hoc integrations by standardizing schemas, interfaces, and templates, and by automating settlement math and reporting.
- Evolvability without breakage: version models and primitives so we can improve deal understanding over time without breaking historical deals or CP baselines.
- Legacy continuity: ingest open legacy deals quickly with traceable provenance so CP can rely on obligations for long-running deals immediately.
Key requirements added in this revision:
- Legacy deals that are still “open” (expecting revenue or generating payments) must be queryable as deals immediately.
- Legacy systems have consistent unique identifiers, enabling idempotent bulk loads.
- Client Processing must be able to rely on Deal Engine obligations for historical open deals, including long-running “overall deals” spanning 2–20 years.
1.1 How to read this spec
This document is long because it is meant to be agent-ready and production survivable.
Recommended reading order:
- Sections 1–3: intent, glossary, key flows (what we are building and why)
- Section 5: standard interfaces and performance (how apps integrate; what must be fast)
- Sections 6–7: correctness, changes, events/evidence (how we avoid breaking finance)
- Sections 7–9: legacy ingestion and CP reliance for long-running deals
- Appendices: Deal Foundry UX, DSL grammar, testing, fixture packs
If you are implementing:
- Apps/CRMs: focus on Section 5 and the app dev guide appendix.
- Runtime/compute: focus on determinism, money/FX, canonicalization, testing appendices.
- Evidence/contingencies: focus on the watch/evidence sections and DLQ.
- Authoring: focus on Appendix B/D/G/H plus the Claude Opus integration notes below.
Quick links
- Glossary
- Key flows
- Assumptions
- Standard interface and performance
- Determinism, FX, money
- Events, evidence, watches
- Legacy ingestion
- Deal Foundry UX appendix
- Codegen IR and pipeline appendices
1.2 Glossary and definitions (normative)
| Term | Meaning |
|---|---|
| Amendment | a forward-looking change in terms after Client Processing has started (effective now/future). |
| Bundled schema | the $ref-resolved JSON Schema used for validation/rendering/codegen (Appendix K). Schemas use JSON Schema draft 2020-12, supporting both Core and Validation vocabularies. |
| Calculation catalog | the published list of metrics/outputs for a model version (keys, types, dependency hints) used by UIs and docs. |
| Canonical JSON (JCS) | a deterministic JSON serialization used for hashing/signing and cross-language determinism.[1] |
| Clause library pattern | a reusable, versioned authoring-time pattern built from primitives (template + schema fragment + payment term scaffold + computation specs) that accelerates modeling and improves consistency (Appendix O). |
| Clause primitive | a minimal clause building block: simple text, guarantee, or contingent. |
| Clause provenance | metadata recorded in a model clause showing which pattern seeded it (sourcePatternId, sourcePatternHash) and whether it diverged (isCustomized). |
| ComputationSpec | the canonical intent definition of a metric/output (type + description + dependencies + invariants/examples). It is not executable code. |
| Compute snapshot | immutable computed output for a deal revision and evidence set; replayable by hash. |
| Contract template / Contract Preview | parameterized contract text (and preview rendering) bound to schema paths and clause templates, enabling contract/PDF generation from instance data. |
| Correction | a retroactive fix where the previous snapshot was factually wrong (data entry error or model bug fix). |
| Dead-letter queue (DLQ) | operational holding area for events/observations/jobs that failed and require retry/remediation. |
| Deal instance | a persisted deal record (with immutable revisions) that conforms to a model version. |
| Deal model | a versioned definition of a deal type (inputs, clauses, templates, computation catalog, workflow mapping), stored in the Registry. |
| Deal program | the service that implements a deal type. Deal programs may choose their own storage strategy but must expose the standard interface (Appendix M2.1). |
| Deal type | the named family of deals a model describes (e.g., touring_calcs_v1), implemented by one deal program. |
| Delta | a computed difference between two snapshots (classified as amendment or correction) used by CP to reconcile changes safely. |
| Draft | an ephemeral authoring or entry workspace used for rapid iteration; TTL’d; no CP side effects. |
| Evaluation job | an authoring-time verification run (workbench/scenario/fixtures) executed in a sandbox (local or Sprites), producing results/diffs. |
| Evaluator bundle | LLM-generated TypeScript code used only inside authoring sandboxes to verify calculations against fixtures (B2.5). Not deployed to production. |
| Evidence adapter | a component that ingests facts from a source system and produces normalized observations. |
| Evidence observation | a time-stamped, typed fact (internal or external) used to evaluate contingencies (e.g., box office, AGP, FX rate). |
| Fixture | one test case for a model version: inputs (+ optional evidence) and expected outputs/obligations. |
| Fixture pack | a portable bundle of fixtures (plus optional templates/schema/model metadata) used in Deal Foundry and CI to enforce correctness. |
| Model Registry | the catalog of deal types and versions, including discovery data like serviceBaseUrl for the deal program (no facade). |
| Model version | an immutable version identifier for a deal model (e.g., 1.0.0). New versions are published for changes. |
| Obligation | a finance-relevant “line item” representing money due (or money movement rules), including obligationKey (stable identity), amount/currency, due date, counterparty, allocations, posting hints. Obligations are what Client Processing consumes. |
| Pattern ID | the stable identifier for a clause library pattern (e.g., lib:touring/payout/versus_net@1). |
| Payment term | a structure defining when/how a clause obligation becomes due (advance, balance, installments, recurring), including triggers and due-date rules. |
| Primitive catalog | a platform-maintained, versioned set of allowed building blocks and semantics a model can use (clause kinds, payment term primitives, schedule primitives, evidence/watch primitives, policy refs like rounding/currency rules). primitive_catalog_version pins a model to a specific catalog version so older models remain stable and replayable. |
| SAGA[2] | A distributed transaction coordination pattern (choreography/orchestration) used by consumers/integration layers (e.g., Client Processing) to apply multi-step changes safely with retries and compensations. Deal Engine does not orchestrate consumers. [2:1][3] |
| Schedule | a rule that produces a series of due dates/periods (e.g., quarterly statements with lag), used by payment terms. |
| Standard interface | the uniform API surface generated/applied across all deal programs so callers do not learn domain-specific APIs. |
| Tool-use loop | an LLM orchestration pattern where the model requests tool calls and the application executes them and returns results until the model produces a final answer.[4][5] |
| Transactional Outbox | a reliability pattern that ensures DB updates and emitted events cannot diverge by writing both within one DB transaction and publishing asynchronously.[6][7] |
| Watch | an operational record instructing the system to keep a deal’s event conditions up to date (subscribe/poll/timer), with lifecycle controls (expiry/dormancy). |
Workflow state (workflowState) | the standardized per-deal-type workflow label used by all consuming apps for that deal type (strict enum). Workflow mapping: the model-defined mapping from workflowState → canonical Deal Engine state (draft/negotiating/closed/cancelled) plus optional cp_ready. |
1.3 Key flows (overview)
1.3.1 Authoring (modeler workflow)
- Upload/paste examples (including contract excerpts) and run document extraction (docpacks).
- Clause Inventory: use the LLM to propose the clause families and variants commonly found in this deal domain (financial + non-financial), then confirm/curate them.
- Output:
clause_inventory.json(domain checklist + patterns), used to drive schema and contract coverage.
- Output:
- Define or refine input JSON Schema (form preview).
- compose from Schema Components Library using
$refwhere possible - ensure the schema can capture both financial and non-financial clauses well enough to reconstruct the contract (touring and other domains).
- compose from Schema Components Library using
- Define computations (metrics/outputs), payment terms, events/evidence hooks.
- Contract Preview: generate a contract-like template and bind it to schema paths and clause inventory entries; render from sample payloads/fixtures to verify coverage.
- Generate fixtures and run them; inspect diffs and explain traces; use the Calculation Workbench to test metrics and promote runs into fixtures.
- Review/approve; publish model version; optional canary.
1.3.2 Interactive deal entry (apps/CRMs)
- Create draft → patch inputs as user types → validate frequently → compute what-if occasionally.
- Commit draft → creates Deal + revision + snapshot; registers watches only on commit.
1.3.3 Evidence → recompute → notify
- Evidence ingested → matched to watches → compute requested → snapshot computed → obligations changed event emitted.
- CP subscribes to changes and pulls updated obligations/deltas.
1.3.4 Changes after CP starts (amendment vs correction)
- CP pins baseline snapshot.
- Deal changes create new revision/snapshot.
- Delta is computed against CP baseline; classified as
amendmentorcorrection. - CP applies adjustments/restatements and acknowledges; Deal Engine records applied baseline.
1.4 Claude Opus integration (authoring UX overview)
The Deal Foundry is designed to be LLM-assisted but validator-driven. Claude Opus drafts artifacts; the system validates, compiles, tests, and asks follow-ups only when needed.
High-level UX pattern:
- A right-hand “Assistant” panel runs a structured flow:
- Extract intent from description + selected contract excerpts (docpack)
- Propose/update input schema
- Propose/update model (canonical JSON + optional DSL)
- Generate fixtures (and expand around boundaries)
- Run fixtures and summarize failures with links to explain traces
- The user can “accept”, “edit”, or “regenerate” each artifact independently.
See Appendix I for the detailed Claude tool loop, prompt structure, and mock screens.
2. Scope and boundaries
Business value: Define what Deal Engine is responsible for (and what it is not) so teams don’t rebuild logic in every app and we avoid scope creep that delays business impact.
2.1 In scope
- Deal Model lifecycle (authoring, validation, compilation, testing, versioning, rollout)
- Deal Instance lifecycle (create/update/revise, validation, compute snapshots, explainability)
- Event + Evidence (contingent tracking: observation ingestion, trigger evaluation, timers, recompute workflow)
- Registry (deal types, model versions, compiled artifacts, primitive catalog versions)
- Deterministic runtime (compile + execute models; obligations/schedules/allocations)
- Legacy ingestion (bulk load, idempotent upserts, tiered fidelity upgrades, provenance)
- Standard integration contracts:
- Departmental systems ↔ Deal Engine
- Client Processing ↔ Deal Engine
- Analytics ↔ Deal Engine (read models + CDC)
- Security (AuthN, AuthZ, audit logging, sensitive data handling)
- Observability (metrics, tracing, replay, backtesting determinism)
2.2 Explicitly out of scope (for now)
- “Full” contract document generation (downstream systems can render using model metadata)
- A monolithic “enterprise CRM”
- Fully automated contract ingestion/extraction (can be added later; not required to build the platform)
3. Primary architectural decisions
Business value: Lock the few architectural decisions that protect long-term business outcomes: comparability across deals, safe change over time, and predictable integration with CP.
3.1 Model vs Instance separation
3.1.1 Concurrency control (optimistic locking)
Deal instances must prevent silent overwrites when multiple users/systems update terms concurrently.
Approach: optimistic locking using a monotonic deal.version counter.
- Each deal has
version(bigint) incremented whenever a new revision is created. - Update requests must include
If-Match: {version}(or a request fieldexpectedVersion). - If the provided version does not match the current version, the API returns 409 Conflict with the current version and (optionally) a diff-friendly summary.
Implementation note (Postgres):
- Updates must use a conditional update:
WHERE id = $id AND version = $expected. - If 0 rows are updated, return conflict.
UI behavior on conflict:
- show a diff between “your changes” and “latest”
- allow merge/override/abandon
- retry with the new
If-Matchvalue
This concurrency control is required for both human edits and legacy ingestion upgrades that create revisions.
Deal Model (definition):
- Versioned definition of a deal type: schema + behavior
- Compiles to runtime artifacts
Deal Instance (data):
- A specific deal record that conforms to a model version
- Has revisions over time and computed snapshots
This separation is enforced in:
- storage (distinct tables)
- APIs (distinct endpoints)
- auth policies (model authoring ≠ deal editing)
- tooling (fixtures attach to models, not to instances)
3.1.2 Model vs instance hygiene (non-negotiable)
Deal Engine succeeds only if we keep a hard boundary between:
- Deal model: the algorithmic shape and the contract vocabulary for a deal type (schema + computations + payment term structures + renderable templates).
- Deal instance: the negotiated values for a specific deal (amounts, percentages, caps, dates, tier tables, expense lines, freeform clause text overrides).
If we blur this boundary, we end up “modeling instances” (one-off micro-models), which destroys:
- cross-deal comparability
- governability (fixtures can’t keep up)
- maintainability (every deal becomes a special case)
Rules of thumb
No negotiated numbers in the model
- Never hardcode values like “90%”, “$50,000”, “2,758 tickets”, “$2.1M”.
- Those must be instance inputs (with optional defaults in schema if truly standard).
Templates must be parameterized
- Contract language in
templatemust reference schema paths (e.g.,{{terms.artistPct}}%), not literal negotiated values.
- Contract language in
Models define structures, instances provide values
- Models define:
- which clauses exist and their types (guarantee/contingent/text)
- how computations combine inputs
- how payment terms split and schedule obligations
- Instances provide:
- amounts, dates, percents, tier tables, expense line items
- Models define:
Domain constants are allowed, but must be explicit
- If something is truly fixed across all deals of a type (rare), treat it as a “domain constant” in a primitive catalog/policy reference, not a magic number in a model.
- Example: canonical rounding policy identifiers, standard currency precision rules.
Schema defaults are OK; hardcoding is not
artistPctmay default to 0.90 in schema, but the model still treats it as a parameter.
Deal Foundry lint checks (required)
Deal Foundry must flag:
- hardcoded numeric literals in templates that look like negotiated values
- use of constant percents/amounts in computations where an input should exist
- models that cannot represent multiple negotiated variants of the same deal family
Review checklist for model publish
Before publishing a model version, reviewers should be able to answer “yes” to:
- Can two different deals with different negotiated values use this same model?
- Is every deal-specific number captured in inputs (not embedded in the model)?
- Does the contract preview render from instance data (fixtures) without hidden constants?
3.1.3 Computations are specifications (not an embedded programming language)
In the DealModel DSL, computations do not embed an executable language. Instead, each metric/output is a ComputationSpec:
type,display,description- dependency hints (
depends_on_inputs,depends_on_metrics) - invariants and examples
Correctness is enforced by:
- fixture packs (inputs → expected outputs/obligations)
- invariants/property checks (optional)
- authoring-time evaluation jobs (B2.4) using sandboxed evaluator code generated by an LLM (B2.5)
This preserves domain boundaries:
- calling systems send data only
- deal programs compute
- authoring validates via tests, not a homegrown language
3.2 LLM-first authoring, deterministic runtime
The LLM helps users author models, but:
- the canonical model format is machine-validated
- the runtime is deterministic
- every model version must ship with fixtures
- publish requires review gates
3.3 Legacy ingestion is first-class
Legacy systems data is not “import and pray.” We need:
- idempotent bulk ingestion with stable unique identifiers
- provenance to explain where each field came from
- tiered fidelity:
- get open legacy deals queryable fast
- progressively upgrade semantics to compute trustworthy obligations
4. Assumptions (defaults to make this buildable)
Business value: Make the build executable by stating defaults up front—reducing ambiguity and rework that would delay benefits like faster cash and fewer manual touches.
| Assumption | Default | Why it matters |
|---|---|---|
| JSON Schema dialect | All input and validation schemas MUST use JSON Schema draft 2020-12 ($schema: "https://json-schema.org/draft/2020-12/schema"). Deal Foundry supports both Core and Validation vocabularies (and the standard 2020-12 dialect meta-schema), so schemas may be structural-only (core) or include validation keywords (validation). | Standardizes validation and tooling across all deal types; prevents schema drift across systems. |
| Implementation language | Service-local choice (Go, TypeScript/Node.js, Java, etc.), as long as the service honors the contracts and invariants in this spec. | Lets teams choose the best language while still producing interoperable services via the standard interface. |
| Runtime (default) | Node.js (LTS) for finance-critical services in the reference implementation (Runtime, Deal API, Evidence). Go is equally acceptable for these services if the decimal/canonicalization rules are implemented exactly. | Provides a known-good reference implementation for finance-grade correctness; other languages must match determinism rules. |
| API style | REST + OpenAPI for internal service communication; GraphQL Federation for client-facing composition (optional in v1) | Keeps integration simple and consistent; OpenAPI makes it easy to generate clients and test. |
| API Gateway | Azure API Management (APIM) is the default gateway for Deal Engine APIs. | Centralizes authn/authz enforcement and routing; reduces per-service security plumbing. |
| Database | Postgres (primary), JSONB for flexible instance payloads | Supports immutable revisions + flexible payloads while remaining queryable and auditable. |
| Message bus | NATS JetStream (assumed subjects); Kafka is acceptable with analogous topics | Enables reliable projections, watches, and async workflows without tight coupling. |
| AuthN | Entra ID JWT validation at gateway; service-to-service with mTLS or signed tokens | Ensures users and services are authenticated consistently across the platform. |
| AuthZ | Centralized authorization uses AWS Verified Permissions with the Cedar policy language. Policy evaluation may occur in Azure API Management (gateway) and/or in services via a shared client (see Section 5.4). | Ensures policy decisions are centralized and auditable; reduces bespoke permission logic. |
| Observability | Datadog for APM tracing, logs, metrics, and dashboards. OpenTelemetry is acceptable as an instrumentation layer if exported to Datadog. | Makes correctness and performance measurable; critical for finance-grade operations. |
| Migrations | SQL migrations (no ORM) | Ensures reproducible deployment and repeatable environments; avoids ORM drift. |
| Canonicalization + hashing | all services must share the same canonical JSON + hashing spec for fingerprints, obligation keys, and audit chaining (see Appendix F) | Guarantees deterministic fingerprints and tamper-evident audit trails across languages. |
4.1 GraphQL Federation (optional but recommended for client-facing integration)
Deal Engine is built as multiple services with distinct domains (deals, models, evidence, legacy, authoring). Departmental systems often need composed views (deal + obligations + evidence + legacy confidence) in a single request.
We will support GraphQL Federation as an optional composition layer at the API Gateway:
- The gateway hosts a federation router (e.g., Apollo Router or equivalent).
- Each service can expose a subgraph schema:
deals(Deal API): Deal, Revision, Snapshot, Obligation, Amendment, ProcessingStatemodels(Registry): DealType, ModelVersion, PrimitiveCatalog, Migrationevidence(Event service): Observation, Watch, ContingencyState, EvidenceSourceHealthlegacy(Ingestion): LegacyDealLink, SourceSystem, QualityScoreauthoring(Authoring): Draft, Comment, Approval, Document, Docpack
REST remains the contract of record for:
- service-to-service internal calls
- webhook endpoints
- bulk ingestion
- administrative operations where GraphQL is unnecessary
Federation enables:
- less over/under-fetching for UI
- a stable “one API” surface for departmental systems
- controlled extension (
@extends) by departmental subgraphs
We will generate TypeScript clients from GraphQL schema for UI usage, while keeping OpenAPI clients for service-to-service calls.
5. Services: what we build
Business value: Specify the concrete services and APIs that deliver business value: standard interfaces for all apps, searchable deals, and obligations CP can trust.
5.1 Required services
In plain terms: these are the building blocks of Deal Engine and Deal Foundry. Each service has a narrow job so we can scale, audit, and evolve safely.
| Service | What it is | Where it’s detailed |
|---|---|---|
| Model Registry service | Catalog of deal types/models/versions/artifacts; discovery with serviceBaseUrl; primitive catalog; approvals/migrations. models, versions, artifacts, approvals, migrations, primitive catalog | Section 5.5, Section 9 |
| Deal API service | Standard deal instance lifecycle: drafts, commits, revisions, snapshots, obligations, CP handshake endpoints. Deal Instance lifecycle + compute entrypoints + CP handshake | Section 5.5, Section 6.4 |
| Authoring service | Deal Foundry backend: drafting, clause library, schema bundling, fixtures, evaluator jobs, publishing. LLM-guided model drafting + fixtures + model cards + contract docpacks | Appendix B |
| Evaluation Worker (authoring) | Runs workbench/scenario/fixture evaluation jobs in an isolated sandbox (local or Sprites). Job-scoped; disposable; posts results back to Deal Foundry. | Appendix B |
| Clause Library service | Stores and serves versioned clause patterns (templates + schema fragments + payment scaffolds + computation specs) and supports provenance/rebase. | Appendix O |
| Event/Evidence service | Observations, watches, timers, matching, recompute triggers, notification emission. observations + watch evaluation + timers + recompute orchestration | Section 7.8 |
| Scheduler/Jobs service | Reliable timers/polling/backfills for evidence watches and periodic tasks; HA job execution. HA timers + polling + backfills; PG-backed job queue or Temporal | Section 7.8, Appendix B2.4 |
| Projection/Query service | Materialized read models for search, dashboards, analytics; outbox/CDC ingestion. materialized read models for search + dashboards + analytics | Section 5.3 |
| Legacy Ingestion service | Bulk imports from legacy systems, mapping/provenance, fidelity upgrades, migration reports. bulk loads + staging + mapping versions + fidelity upgrades | Section 7.6 |
| API Gateway | Front door for all external callers; authn/authz enforcement, routing, rate limits, optional federation. | Section 4, Section 5.4 |
| AuthZ service | Policy evaluation via AWS Verified Permissions (Cedar), decision logging, obligations/claims. policy evaluation + decision logging | Section 5.4 |
| Audit subsystem | Tamper-evident append-only audit chain; verification jobs; compliance reporting. append-only log with integrity protection + verification | Section 5.1.1, Appendix F |
| Operations subsystem | DLQ, remediation UI, replays/backfills, health checks, runbooks. Dead Letter Queue + remediation dashboard + backfills | Appendix B |
| Runtime service | Shared compile/validation utilities used by authoring/codegen; deterministic helper libraries; explain traces. compile + evaluate + explain tracing; deterministic execution | Appendix L, Appendix M |
| Compute Cache layer | Speeds interactive compute and repeated snapshot reads; cache-aside for expensive computations. L1 in-process + optional distributed cache; snapshot cache-aside | Section 5.6 |
Reading order: the sections that follow (5.2 → 5.1a → 5.5 → 5.6 → 5.4 → 5.7 → 5.3) track the core service flow for discovery → deal APIs → performance → authorization → workflow → query.
Note: “Compute Cache layer,” “Audit integrity,” and “DLQ” may start as modules within existing services (Runtime/Audit/Projection) and be split into dedicated services if/when scale requires.
5.1.1 Audit log integrity (tamper-evident)
Because Deal Engine supports finance-grade operations, the audit log must be tamper-evident.
Approach:
- Hash chaining: each audit entry stores
prev_hashandentry_hash. - Periodic anchoring: write checkpoints to immutable external storage (Object Lock / immutable blob).
- Verification: background job validates chain and recent checkpoints.
Audit entry fields (additions):
sequence_num(monotonic)prev_hashentry_hash
Checkpoint table: de_audit_checkpoint
sequence_numentry_hashexternal_refsignature(HSM-backed if available)
Verification job:
- recompute hashes for sampled ranges hourly
- alert + freeze writes if integrity check fails (policy-controlled response)
This provides forensic confidence without turning the audit log into a separate blockchain project.
- Evaluation Worker (authoring): runs workbench/scenario/fixture evaluation jobs in an isolated sandbox (local or Sprites). Job-scoped; disposable; stores results back to Deal Foundry.
5.2 High-level diagram
flowchart LR
subgraph Legacy[Legacy Systems]
L1[Legacy A]
L2[Legacy B]
L3[... x9]
end
subgraph Dept[Departmental Systems]
DS1[Dept apps / CRMs]
end
subgraph DE[Deal Engine Platform]
GW[API Gateway]
DEAL[Deal API]
REG[Model Registry]
RT[Runtime]
AUTHR[LLM Authoring]
EVT[Event + Evidence]
PROJ[Projection/Query]
LEG[Legacy Ingestion]
AUTHZ[AuthZ Policy]
AUD[Audit Log]
DB[(Postgres)]
BUS[(Message Bus)]
end
subgraph ClientProc[Client Processing]
CP[Client Processing]
end
DS1 --> GW --> DEAL
DEAL --> AUTHZ
DEAL --> RT
DEAL --> DB
DEAL <--> CP
AUTHR --> REG
REG --> DB
RT --> DB
EVT <--> BUS
EVT --> DB
RT <--> BUS
DEAL <--> BUS
LEG --> DB
L1 --> LEG
L2 --> LEG
L3 --> LEG
PROJ --> DB
PROJ --> CP
PROJ --> AUD5.1a Authoritative Data integration and contract header requirements
Business value: eliminates duplicate party/address/contact data entry across systems, reduces invoice/payment errors, and enables reliable contract/PDF generation from deal data.
UTA’s Authoritative Data system is the system of record for Party identifiers (people and organizations) and their roles and relationships within the party domain.【turn5file1†UTA_DATA_MODEL_BRIEFING.md†L32-L39】 It is explicitly out of scope for “deal” or “contract” domains, but it provides the authoritative identifiers and relationship context that Deal Engine depends on.【turn5file1†UTA_DATA_MODEL_BRIEFING.md†L37-L39】
5.1a.1 What every deal instance must carry
Every deal instance MUST include a contract header payload sufficient to:
- identify the client the deal is done on behalf of, and
- identify the buyer(s) who are contracting/paying, and
- render standard header information in a contract/PDF (addresses, contacts, payment/invoice instructions).
Client and buyer are Parties in Authoritative Data (a Party is a person or organization).【turn5file1†UTA_DATA_MODEL_BRIEFING.md†L52-L59】
- Client: usually a single client Party (person or organization).
- Buyer(s): one or more buyer Parties (organization in most cases; multiple buyers are supported).
- Contacts: buyer contacts and external team roles may be modeled in Authoritative Data using assignment/appointment structures and role categories (e.g., buyer contacts with job roles; “Notices Contact” on external teams).【turn5file4†UTA_DATA_MODEL_BRIEFING.md†L5-L12】【turn5file7†UTA_DATA_MODEL_BRIEFING.md†L32-L35】【turn5file15†UTA_DATA_MODEL_BRIEFING.md†L47-L54】
- Addresses: are Party data nodes connected via
HAS_ADDRESSand are subject to normalization and verification gates.【turn5file0†UTA_DATA_MODEL_BRIEFING.md†L4-L13】
5.1a.2 Storage rule: reference + snapshot
To support historically correct contract generation, deals must not rely solely on “look up the latest party data at render time,” because names, contacts, and addresses can change.
Therefore the contract header supports two layers:
- Authoritative references (required): Party IDs and (optionally) address/contact IDs.
- Snapshots (optional but recommended): name/address/contact values captured at deal revision time for reproducible rendering and audit.
5.1a.3 Schema library extensions (recommended)
Deal Foundry’s schema library SHOULD include reusable components for contract headers:
PartyRef:partyId(Authoritative Data UUID)- optional
role/roleSubType(e.g., Buyer subType Touring/Brand, tracked in Auth Data)【turn5file9†UTA_DATA_MODEL_BRIEFING.md†L22-L25】 - optional
displayNameSnapshot
AddressRef:- either
addressId(if available) or(partyId, addressType) - optional normalized
addressSnapshotfields (street/locality/region/postal/country)
- either
ContactRef:contactPartyId(a Party with Contact role) and optional job role / assignment context (buyer contacts)【turn5file4†UTA_DATA_MODEL_BRIEFING.md†L5-L12】- optional
emailSnapshot/phoneSnapshot
ContractHeader:client: PartyRefbuyers: PartyRef[]billTo,payTo,noticeTo(AddressRef/ContactRef, as required)- freeform
instructions(remittance / invoicing notes)
Deal types can embed this component in their inputs.schema at a standard location, e.g.:
header.clientheader.buyers[]header.contacts[]header.addresses[]header.instructions
5.1a.4 Validation and integration points
Deal programs and Deal Foundry MUST validate:
- provided Party IDs exist in Authoritative Data (client has Client role, buyer has Buyer role).
- address and contact references (if provided) are consistent with the referenced party.
Where Authoritative Data provides stricter constraints (e.g., address types, verification policies), Deal Engine should follow them rather than invent its own constraints.【turn5file0†UTA_DATA_MODEL_BRIEFING.md†L7-L13】
Pragmatic note:
- Deal Engine does not become a party system. It stores references and optional snapshots.
- Resolution of references into snapshots can be done by Deal Foundry at authoring time or by the deal program at commit time (configurable), but the persisted revision must remain immutable.
5.5 Deal program standard interface (required for all deal types)
Deal types are encapsulated as deal programs (independent services). To keep departmental systems and Client Processing integrations consistent, every deal program must implement the same standard interface.
Deal Engine may expose a single “facade” API that routes to the appropriate deal program, but the semantics live in the deal program.
5.5.1 Interface goals
- Support rapid, interactive deal entry (many calls while users type).
- Enable “what-if” computations without persistence.
- Provide schemas and metadata for UI generation and validation.
- Provide deterministic compute outputs, obligations, deltas, and explain traces.
- Support versioned models and safe evolution (including amendments vs corrections).
5.5.2 Core concepts in the interface
- Model version: immutable deal model definition with a semantic version.
- Draft: optional ephemeral “workspace” object for multi-step editing without creating a Deal record.
- Deal: persisted instance with immutable revisions.
- Snapshot: deterministic compute result for a revision and evidence set.
- Mode:
ephemeral: no side effects, no writes, no watch registration, no domain event emissionpersistent: creates revisions/snapshots and triggers watches/events as needed
5.5.3 Required endpoints (REST)
A) Model discovery and schema
GET /models- list supported deal models and versions (registry-backed)
GET /models/{dealType}/versionsGET /models/{dealType}/versions/{version}- returns model card + compiled artifacts hash + compatibility classification
GET /models/{dealType}/versions/{version}/input-schema- JSON Schema for deal instance inputs
GET /models/{dealType}/versions/{version}/ui-schema(optional)- UI hints (field order, labels, help text, grouping)
GET /models/{dealType}/versions/{version}/calculations- list available outputs/metrics and obligation shapes
GET /models/{dealType}/versions/{version}/projections- projection hints and supported query fields
Calculation Catalog (required compiled artifact)
The /calculations endpoint must return a Calculation Catalog derived from the model’s computations block (metrics/outputs). This is not user-authored separately and must never be “mocked” or invented by clients.
The compiler/runtime must emit this catalog as part of compiled_artifacts so that:
- Deal Foundry can populate dropdowns (Workbenches) from real calculations,
- UIs can auto-render minimal mock-input forms (dependency-driven),
- fixtures can be generated/expanded around the correct dependencies,
- codegen can bind calculations to stable keys.
Minimum response shape (conceptual):
calculations[]each with:calculationKey(stable identifier)kind(metric|output)type(number|money|bool|string|object)displayName,description(optional)dependencies:inputPaths[](FieldPaths)evidenceKeys[]calculationDeps[](other metrics used)
- optional policy metadata:
currencyPolicyRef,roundingPolicyRef,unit
Hard rule: Deal Foundry Workbench must populate the calculation list from this catalog. Any “random” calculations not present in the model are a bug.
B) Draft support (optional but recommended for UX)
Drafts are ephemeral resources to support multi-step entry with server-side validation and caching, without persisting a deal.
POST /drafts- body:
{ dealType, modelVersion?, subjectRefs? }
- body:
GET /drafts/{draftId}PATCH /drafts/{draftId}- body: partial input updates; returns validation status
POST /drafts/{draftId}/validatePOST /drafts/{draftId}/compute- body:
{ mode: "ephemeral", asOf?, evidenceOverrides? }
- body:
POST /drafts/{draftId}/commit- creates a persisted Deal (see commit flow below)
Draft lifecycle:
- drafts expire by TTL (default 24 hours) and do not create watches or durable snapshots
- drafts are auditable (who created) but do not store full snapshots unless explicitly configured
C) Deal lifecycle (persistent)
POST /deals- body:
{ dealType, modelVersion?, subjectRefs, inputs }
- body:
GET /deals/{dealId}PATCH /deals/{dealId}- creates a new immutable revision; uses optimistic locking
GET /deals/{dealId}/revisionsGET /deals/{dealId}/revisions/{rev}
D) Compute, explain, and what-if (ephemeral mode required)
All deal programs must support ephemeral compute. This is a first-class execution mode.
POST /compute- body:
{ mode: "ephemeral" | "persistent", dealId?, dealType?, modelVersion?, subjectRefs?, inputs?, evidenceOverrides?, asOf? }
- behavior:
ephemeral: no writes; returns compute result + would-create metadatapersistent: writes snapshot (and optionally revision if inputs provided), emits events, registers watches
- body:
POST /whatif- alias for
POST /computewithmode=ephemeral
- alias for
GET /snapshots/{snapshotId}GET /deals/{dealId}/snapshots?revision=&asOf=...GET /snapshots/{snapshotId}/explain- returns explain trace (or trace ref)
Ephemeral compute response must include:
outputs,obligations,contingencies,explainRef|explainrequiredEvidence[](e.g., FX rate needed)wouldCreateWatches[](eventName, matchSpec, expiresAt)fingerprint(deterministic compute fingerprint)- optional
commitPayload(see 5.5.4)
E) Obligations and deltas (CP-facing)
GET /deals/{dealId}/obligations?asOf=...GET /deals/{dealId}/obligations/delta?fromSnapshot=...&toSnapshot=...- response includes
classification(amendment|correction) andreasonCode
- response includes
POST /deals/{dealId}/processing/start- CP pins baseline snapshot
POST /deals/{dealId}/payments/ackPOST /deals/{dealId}/invoices/ack(optional if CP needs it)
F) Evidence (read-only in deal program; write in Evidence service)
Deal programs do not ingest evidence directly. They may read observations via Evidence service or accept overrides in requests.
POST /computeacceptsevidenceOverrides(ephemeral)- persistent mode compute may request watch registration via Event service (as an internal call), but does not itself poll vendors.
5.5.4 Commit flow for ephemeral computations (no accidental persistence)
To prevent accidental persistence:
- Ephemeral compute returns
commitPayload(optional) that contains:- dealType + modelVersion
- subjectRefs
- canonicalized inputs
- evidence binding summary (not full evidence)
- fingerprint
- Client commits via:
POST /commit(orPOST /drafts/{id}/commit)- must include an
Idempotency-Key
Server behavior:
- validates payload against schema/constraints
- creates Deal + initial revision
- computes and stores snapshot
- registers watches (if any)
- emits domain events
Ephemeral mode must never register watches or write durable snapshots.
5.5.5 GraphQL subgraph shape (optional)
If using GraphQL federation:
Query.deal(id),Query.deals(filter),Query.model(dealType, version)Mutation.createDraft,Mutation.updateDraft,Mutation.computeWhatIf,Mutation.commitDraft- all mutations call the same REST handlers internally
5.6 Performance and latency expectations (interactive deal entry)
Deal entry is an interactive UX: the client will make frequent calls for validation and compute as the user types. The architecture must be designed for low-latency, high-read workloads.
5.6.1 Performance targets (v1)
Targets assume “hot path” (model artifacts cached, no cold storage):
- Schema fetch (
/input-schema,/calculations): P95 < 50ms - Validation (
/validate): P95 < 100ms - Ephemeral compute (
/compute mode=ephemeral): P95 < 200ms for typical deals; P99 < 500ms - Persistent compute (revision commit + snapshot + outbox): P95 < 400ms
- Search via projections: P95 < 200ms
5.6.2 Architectural implications
To hit these targets:
- Compiled artifacts caching
- cache compiled model artifacts by
(dealType, version, model_hash)in memory - warm cache on service startup for active/canary versions
- cache compiled model artifacts by
- Compute caching
- leverage deterministic fingerprint cache (L1 + optional Redis)
- especially for “user typing” loops where inputs fluctuate slightly
- Fast validation
- JSON schema validation must be local (compiled validator), not remote
- static lint/semantic checks should be incremental where possible
- Avoid synchronous remote calls
- ephem compute should not block on external evidence polling
- evidence overrides can be provided by client
- missing evidence should be returned as
requiredEvidencerather than slow lookup
- Outbox and projections are async
- persistent commit writes outbox in-transaction, but projection updates are async
- DB strategy
- Postgres with proper indexes; partition large snapshot/trace tables
- consider read replicas for read-heavy endpoints (snapshots, search)
- Concurrency
- optimistic locking for deal updates prevents race-induced retries
- Backpressure
- implement per-user/per-deal rate limiting at gateway
- protect runtime with circuit breakers and load shedding for extreme cases
5.6.3 Client UX strategies (recommended)
- Use drafts for rapid incremental validation
- Use debounce (150–300ms) on compute calls
- For heavy compute, return 202 + job id (rare; keep synchronous in v1)
- Prefer
validatecalls on every change; compute less frequently unless required
5.6.4 Benchmarking and SLO enforcement
- Add synthetic benchmarks for:
- typical touring deal compute
- overall deal compute (long schedules)
- large obligation sets
- CI should fail if regression exceeds thresholds.
- Production SLOs:
- error rate < 0.1% for compute
- latency targets above
- watch evaluation backlog bounded (Band 2)
5.4 Centralized authorization integration contract (out of scope platform; in scope integration)
This integration assumes the enterprise authorization platform is AWS Verified Permissions using the Cedar policy language.
UTA is standardizing on a Centralized Authorization service with:
- uniform identity construction (a composite identity derived from Entra + Authoritative Data),
- RBAC + ABAC + ReBAC policy support (notably the “Desks” inheritance model),
- auditability and “single pane of glass” observability,
- minimal query-time cost and minimal developer friction via shared SDKs / GraphQL helpers.
Deal Engine does not implement its own authorization platform. It integrates with the centralized service via a strict contract.
5.4.1 Identity context (AuthN vs AuthZ)
- AuthN (who you are) is handled at the API Gateway via Entra JWT validation.
- The gateway (or an Identity Service) constructs a composite identity payload (principal) that includes:
- user/service id
- roles and groups
- department and desk membership (including temporary coverage)
- job title and location attributes
- any Authoritative Data-derived relationships required for ReBAC
Deal Engine services must treat this composite identity as the authoritative input to AuthZ decisions.
5.4.2 Resource model: what Deal Engine authorizes
Deal Engine must define a stable set of resources and actions for policy evaluation.
Resources (minimum):
deal,deal_revision,deal_snapshot,obligation,obligation_delta,amendment,processing_statedeal_type,model_version,model_migration,primitive_catalogobservation,event_watch,contingency_state,evidence_source,canonical_source_schema,source_adapter_versionlegacy_raw_record,legacy_deal_link,legacy_transform_run,quality_scoreauthoring_draft,authoring_document,docpack,authoring_comment,authoring_approvaldead_letter
Actions (examples):
read,write,create_revision,compute,publish_model,approve_model,activate_modelingest_legacy,upgrade_legacy_fidelity,run_transformingest_evidence,override_evidence,pause_watch,resume_watch,replay_watchapply_amendment,apply_correctionremediate_dlq,replay_compute,rebuild_projection
The exact policy logic lives in the centralized AuthZ service. Deal Engine is responsible for using consistent resource/action names and supplying required attributes.
5.4.3 Attributes Deal Engine must supply for ABAC/ReBAC
For each authorization request, Deal Engine must supply “resource attributes” sufficient for policy evaluation:
Deal attributes:
dept_scopestatusdeal_typemodel_versionsubject_refs(client ids, agent ids, engagement ids, buyer ids)- sensitivity tags (e.g., “financial”, “legal”, “personnel”, “client” categories)
Model/registry attributes:
- owners
- status (draft/approved/canary/active/deprecated)
- compatibility classification (compatible / requires_migration / deprecated_only)
Evidence/watch attributes:
sourcetrust levelsource_type(automated/manual)priorityexpires_at/status(active/dormant/paused)- whether the action affects obligations (e.g., “override evidence”)
Legacy attributes:
- source system
- fidelity tier and quality score
- whether CP reliance is enabled
Document attributes (authoring):
- redaction status (redacted/unredacted)
- retention policy
These attributes enable centralized policies to express:
- desk inheritance (“assistants inherit access based on desk membership and temporary coverage”)
- client-team membership based access
- “lock down” special clients
- category-based restrictions (financial/legal/etc.)
5.4.4 Authorization decision response (allow/deny + obligations)
Deal Engine must handle more than allow/deny. The AuthZ service may return obligations such as:
- field masking (e.g., redact sensitive amounts/bank details)
- row-level filters (restrict to subset of subject refs)
- “break glass” requirements (extra audit, justification text)
- caching TTL hints
Deal Engine must implement:
- response shaping / masking based on obligations
- audit logging of applied obligations (see 5.4.6)
5.4.5 Where to call AuthZ (integration points)
AuthZ must be evaluated at the boundaries where data leaves Deal Engine or state changes:
Deal API
- read deal / search deals
- create revision, change status
- compute/what-if/explain
- fetch obligations, deltas, snapshots
- CP acknowledgements and processing-start pin
- amendment/correction apply/ack flows
Registry
- register model version, upload fixtures
- approve/canary/activate/deprecate versions
- register primitive catalogs and evidence canonical schemas/adapters
- publish migrations and run migrations
Evidence / Watches
- ingest observations (especially manual overrides)
- pause/resume watches
- view watch health and evidence source health
- replay watches
Legacy
- ingest raw records
- run transforms and upgrades
- manage legacy→deal links (including split mapping)
- view quality scores
Authoring
- upload/view documents, docpacks
- generate/regenerate drafts
- comment/approve/publish
Operations
- DLQ view and remediation actions
- replay/backfill/projection rebuilds
5.4.6 Decision logging and audit correlation
Every state-changing request must record:
authz_decision_id(from AuthZ service)policy_version- principal id + actor type (user/service)
- resource/action
- any obligations applied (mask profile id, etc.)
These values must be written to de_audit_log.metadata to support centralized investigations and compliance audits.
5.4.7 Caching and performance posture
Centralized AuthZ requires “minimal query-time cost.” Deal Engine should:
- Use the provided SDK/client abstraction (or build one shared internal client library) to minimize developer friction.
- Cache positive AuthZ decisions with short TTL where appropriate (read-heavy endpoints), but:
- never cache write decisions for long TTL
- treat desk membership and temporary coverage as potentially fast-changing (short TTLs)
- Support “no-cache” flags for sensitive operations (manual evidence override, DLQ remediation, model activation).
5.4.8 Service principals and background jobs
Background processes (pollers, timers, projection workers, migration runners, legacy loaders) must authenticate as service principals with scoped permissions.
Rules:
- background jobs do not bypass AuthZ
- if a job acts “on behalf of” a user, preserve both identities (initiator + service executor) in audit logs
5.4.9 Secondary exposure points (warehouse/projections)
Warehousing and projections create secondary exposure points. Deal Engine must ensure:
- projection/query endpoints are protected by AuthZ just like primary reads
- if data is replicated to a warehouse, the serving layer must enforce equal-or-stricter policies, or the warehouse must do so natively
This prevents inconsistent exposure across systems.
5.4.10 Where policy is evaluated (Gateway vs service)
Authorization decisions can be evaluated in either (or both) places:
Gateway enforcement (Azure API Management)
- APIM validates Entra JWTs and calls the authorization service (AWS Verified Permissions) to get an allow/deny decision and obligations.
- APIM forwards the request to Deal Engine services only if allowed.
- APIM propagates
authzDecisionId,policyVersion, and any obligations via headers for audit correlation.
Service-side enforcement
- Services call the authorization service directly using a shared client library.
- This is required for:
- internal service-to-service calls
- background jobs (pollers, timers, projection workers, migrations)
- operations actions (DLQ remediation) not routed via APIM
Recommended posture: start with APIM enforcement for user-facing APIs and keep service-side checks for background/worker flows. In all cases, audit logs must record the decision id and policy version.
5.7 Deal workflow state (standardized per deal type) vs Deal Engine canonical states
Business value: Standardizing workflowState per deal type prevents re-keying and translation errors across apps, while still giving CP a small, stable lifecycle signal.
Departmental systems (CRMs and workflow apps) have rich, domain-specific workflow states (e.g., “submitted to buyer”, “offer out”, “counter”, “hold”, “booked”). For Deal Engine, we standardize these workflow states per deal type: all systems that use a given deal model share the same workflow state vocabulary.
Most workflow states are not relevant to Client Processing (CP). CP cares about a small set of canonical Deal Engine states and CP readiness gates. However, calling systems must be able to store and retrieve the workflow state alongside a deal.
This section defines a simple, strict mapping strategy:
- Calling systems send one string
workflowState. - The deal model defines:
- the allowed workflow states (strict enum)
- a mapping from each workflow state to Deal Engine canonical state
- optional
cp_readyhints
5.7.1 Design goals
- Maintain a small set of canonical Deal Engine states that are stable across domains and drive CP.
- Preserve domain workflow states as standardized per deal type (shared across consuming systems).
- Enforce strict validation: apps can only send workflow states defined by the model version.
- Provide deterministic, auditable mapping from workflow state → canonical state.
- Support search/reporting by canonical status and workflow state.
- Avoid “consumer coupling”: Deal Engine does not hardcode departmental workflow enums in code; they live in the model.
5.7.2 Canonical Deal Engine states (small, stable)
Deal Engine maintains only states required for:
- high-level lifecycle control, and
- CP gating and obligations semantics.
Minimum canonical lifecycle:
draft(not ready for CP; incomplete inputs likely)negotiating(terms in flux; still not CP-ready)closed(commercially agreed; CP may act once gated)cancelled(will not proceed)
CP processing lifecycle remains separate (baseline pinning, amendments/corrections, applied snapshots).
5.7.3 Workflow state is an input field (strict enum)
Each deal model version defines the allowed workflow states using the DSL workflow block:
workflow {
allowed { DRAFT; OFFER_OUT; COUNTER; HOLD; BOOKED; CANCELLED; }
mapping {
DRAFT -> draft;
OFFER_OUT -> negotiating;
COUNTER -> negotiating;
HOLD -> negotiating;
BOOKED -> closed cp_ready: true;
CANCELLED -> cancelled;
}
}Compilation behavior:
- The compiler emits the workflow mapping as part of
compiled_artifacts. - The compiler also injects a strict enum into the instance input schema:
"workflowState": { "type": "string", "enum": ["DRAFT","OFFER_OUT","COUNTER","HOLD","BOOKED","CANCELLED"] }5.7.4 How canonical status is derived (authoritative vs suggested)
Deal Engine stores:
workflowState(as provided; validated by schema)canonical_status_suggested(derived from workflow mapping)
Rules:
- The mapping defines
canonical_status_suggesteddeterministically fromworkflowState. - The canonical
deal.statusmay be set explicitly by authorized actors (rare). If set explicitly, it wins and must be audited with a reason code. - In the normal path,
deal.statusis set from the suggestion at commit time and may be updated asworkflowStatechanges.
5.7.5 CP readiness: “closed” is necessary but not sufficient
Client Processing should start only when:
canonical_status_suggested == "closed"(or explicitdeal.status == closed), AND- required validation gates pass (model constraints satisfied), AND
- a CP readiness signal is present.
CP readiness signal:
- may be derived from mapping (
cp_ready: trueon certain workflow states) - may be set explicitly by a finance/ops action if needed (audited)
This ensures CP does not need to understand departmental workflows.
5.7.6 API integration points (standard interface)
Deal Programs (or the Deal Engine facade) must expose:
PATCH /deals/{dealId}- payload includes
workflowStateas part of normal deal input updates (schema-validated)
- payload includes
GET /deals/{dealId}- returns:
workflowStatecanonical_status_suggestedcp_ready_suggested
- returns:
GET /models/{dealType}/versions/{version}/workflow- returns the workflow definition from compiled artifacts:
- allowed states
- mapping table
- cp_ready hints
- used by UIs to render workflow pickers correctly
- returns the workflow definition from compiled artifacts:
5.7.7 Projections and search
Projection service indexes:
workflowStatecanonical_status_suggestedcp_ready_suggested
This supports filtering:
- “all closed deals”
- “all deals in OFFER_OUT”
- “deals ready for CP”
5.7.8 Tenet-safe guardrails
- No workflow meanings are hardcoded in Deal Engine code.
- Workflow vocabulary and mapping are model-versioned and auditable.
- Strict enum validation prevents silent drift between systems.
- If workflow states change, publish a new model version (and migrate if needed).
This approach keeps Deal Engine generic while giving applications a consistent workflow contract.
5.3 Projection architecture (outbox now; CDC option for scale)
Projections power search, dashboards, and analytics. We support two synchronization approaches:
5.3.1 Baseline: Outbox-driven projections (v1)
- Write path emits domain events via outbox (transactional).
- Projection workers subscribe and update projection tables.
This is simpler to ship early and matches the event-driven design already in the spec.
5.3.2 Optional: CDC-driven projections (Debezium/WAL)
As scale grows, you may prefer Change Data Capture (CDC) from Postgres WAL:
Postgres WAL → Debezium → Message Bus → Projection Workers → Read Stores
Captured tables typically include:
- deals, revisions, snapshots
- contingency state
- processing/amendment state
- legacy link + quality scores
CDC guarantees ordered delivery per transaction and reduces the need for every service to emit bespoke events.
Admin operations:
- rebuild projection from scratch
- pause/resume
- backfill a time range
Metrics:
- projection lag
- processed events
- error rate
6. Versioning: how change flows without breaking existing data
Business value: Ensure we can change models and fix mistakes without breaking existing open deals—critical for finance correctness and long-running overall deals.
The versioning strategy is intentionally concrete and enforceable.
6.1 What is versioned
- Model versions:
deal_type@version - Primitive catalog versions: semantics of operations/types used by models
- Ingestion mapping versions: legacy-to-canonical transformations
- Compute snapshots: immutable outputs tied to a revision + model hash + evidence set
6.2 How model versions apply to deals
Each deal instance has:
deal.model_version_id(the current bound model version)- immutable
deal_revisions.inputs
Invariant: A deal is always computable using the model version it is bound to, forever.
A) Default: “old deals stay on old models”
When you publish touring@1.2.0, you do not automatically change deals on touring@1.1.0.
- Old deals remain readable and computable.
- New deals use the newest active version (unless explicitly pinned).
A.1) Canary deployments for new deals (safe rollout)
Even with fixtures passing, a new model version can behave unexpectedly with real-world inputs. To reduce blast radius, the Registry supports canary rollout stages:
Rollout stages:
draft(not usable)canary(usable only for allow-listed departments/users/systems or a traffic percentage)active(default for new deals)deprecated(no new deals; existing deals continue)retired(no new deals; existing deals should migrate or be closed)
Registry stores canary configuration (when stage is canary):
allowedDepartments[]allowedUsers[]allowedSystems[]trafficPercent(0–100)startedAt,expiresAt
Version selection algorithm (Deal creation):
- If the caller requests an explicit version and is authorized → use it.
- If a canary version exists and the request matches canary criteria (or traffic percent) → use canary.
- Otherwise → use active.
Promotion gate (canary → active) requires:
- zero critical compute errors above threshold
- obligation variance review (optional: compare outputs vs previous active on sample deals)
- explicit approval by finance + platform owner
This is especially important for models that affect obligations.
This is usually correct: old contracts don’t change retroactively.
B) Migration: explicit, audited, test-backed
Migration is required only when you decide existing open deals must adopt the new semantics.
Mechanics:
- Registry stores a migration spec from
v_old -> v_new:migrateInputs(oldInputs) -> newInputs- optionally
migrateEvidenceKeys(oldKeys) -> newKeys
- Migration is executed by a Migration Runner (batch job/service):
- for each deal:
- create a new immutable revision with migrated inputs
- update
deal.model_version_id = v_new - recompute snapshots
- record audit entries
- for each deal:
- Old revisions remain and can still be replayed using old model hashes.
C) Compatibility classification (enforced by registry gates)
Every model version publish declares:
compatibility = compatible- adding optional inputs with defaults
- adding new outputs not affecting obligations
- adding projection hints
compatibility = requires_migration- any change that can affect obligations, allocations, schedules, event semantics, rounding/FX
compatibility = deprecated_only- model is being retired; no new deals allowed
Registry refuses activation unless compatibility and required migration spec are present.
6.3 How compute determinism is preserved across time
A compute snapshot is determined by:
model_hash(content hash of canonical model doc + compiled artifacts)primitive_catalog_versiondeal_revision.inputsevidence_set_hash(observations used)as_oftimestamp (time context for schedule generation)
This enables replay/backtesting for finance-grade auditability.
6.3.2 FX determinism (currency strategy; no live rates in runtime)
The Runtime must never fetch “live” FX rates during computation. FX must be inside the deterministic boundary: deal inputs or evidence observations.
Scenario A: Fixed contract rate (preferred when explicit)
- Deal inputs specify a fixed rate (e.g.,
1 GBP = 1.25 USD) as part ofdeal_revision.inputs. - Snapshots are deterministic because the rate is an input.
Scenario B: Spot/dated rate (when required)
- The model references an FX rate via evidence, e.g.
evidence("fx.GBP.USD.2026-03-01"). - The Event/Evidence service ingests FX rate observations (from a configured internal or external source).
- The snapshot stores the specific observation ids and rate values used in its evidence set.
- Replay uses the stored observation, not current market rates.
Rules:
- Money addition/subtraction requires same currency; otherwise the model must explicitly convert.
- Conversions must specify:
sourceCurrency,targetCurrencyraterateAsOfDate(or explicit observation id)
Compute fingerprint includes FX: FX observations are part of evidence_set_hash. Changing FX inputs/observations yields a new snapshot (never mutating old ones).
Implementation note:
- Maintain a logical evidence schema for FX:
fx_rate@v1with fields{ base, quote, rate, asOfDate }(see Evidence Source Adapters in 7.8.2b).
6.3.1 Snapshot storage tiers and compaction (performance over 20 years)
Long-running deals (2–20 years) can accumulate many compute snapshots. We must preserve auditability while keeping storage and query performance bounded.
Approach: store snapshots in tiers based on age and operational relevance.
Tiers (defaults; configurable):
- Hot (0–90 days): keep all snapshots in Postgres for fast access.
- Warm (90 days–2 years): keep all revision-triggered snapshots + one daily snapshot; partition Postgres by month.
- Cold (2+ years): keep metadata in Postgres; move snapshot bodies to object storage (Parquet/JSON) with
storage_ref. - Archive (7+ years): move cold bodies to archive storage; restore-on-demand.
Retention rules (never violate):
- keep all snapshots referenced by:
- CP invoice/payment acknowledgements
- contingency state changes
- amendments applied
- keep all snapshots created by:
- deal revisions
- model migrations
Compaction job (daily):
- Identify eligible snapshots per tier policy.
- For cold/archive: serialize to object storage, record
storage_ref. - Optionally delete large JSON body from Postgres while retaining:
- keys, hashes, timestamps, and
storage_ref.
- keys, hashes, timestamps, and
Query behavior:
- Hot/Warm: direct Postgres.
- Cold: lazy-load from storage when requested (may return 202 + polling token for large loads).
- Archive: require explicit restore request.
This keeps the system usable for 20-year obligations without giving up audit correctness.
6.4 Deal amendments after Client Processing has started
Clarification: Deal Engine publishes snapshots/deltas and optional acknowledgements; consumers (e.g., Client Processing) may implement a SAGA[2:2] to apply those changes safely. Deal Engine does not coordinate consumer workflows.
This is the required architecture for “terms changed after CP is already invoicing / paying.”
6.4.1 The invariant we must preserve
Once Client Processing (CP) has started acting on a deal, we must preserve:
- immutability of history (what CP acted on must remain replayable)
- stable obligation identity (CP needs stable keys to reconcile invoices/payments)
- no silent retroactive changes to obligations that were already invoiced/posted/paid
- explicit deltas (what changed, why, effective when)
The platform handles this with revisions + snapshots + deltas.
6.4.2 Baseline snapshot (“CP pin”)
When CP begins processing a deal (e.g., first invoice is created, or first payment is applied), CP must pin a baseline snapshot:
- CP calls:
POST /deals/{dealId}/processing/start- body:
{ snapshotId, startedAt, cpCaseId? }
- body:
- Deal Engine records:
processing_started_atprocessing_baseline_snapshot_id
From this point on, changes to terms are treated as amendments, not ordinary edits.
6.4.3 Amendment workflow (distributed saga)
Amendments cross system boundaries (Deal Engine ↔ Client Processing). We implement this as a distributed saga to prevent divergence under partial failures.
Each step is idempotent and has a compensating action where needed:
| Step | Action | Compensating Action |
|---|---|---|
| 1. Propose | Create revision with amendment=true | Mark revision abandoned |
| 2. Compute | Generate new snapshot | none (snapshots immutable) |
| 3. Delta | Compute delta package | none (derived) |
| 4. Publish | Emit deal.amendment_ready + deal.obligations_delta_ready | retry publish / mark publication_failed |
| 5. CP Reserve | CP reserves/locks invoice actions | CP releases reservation |
| 6. CP Apply | CP applies adjustments | CP reverses adjustments |
| 7. Acknowledge | DE records applied baseline | saga remains incomplete until ack stored |
6.4.3.1 Saga coordinator
Deal Engine stores saga progress and retries/compensates deterministically.
Table:
de_amendment_saga
id uuid pkamendment_id uuid fk de_amendment(id)current_step text not nullstep_history jsonb not null(array of{step,status,at,details})status text not null(running|completed|compensating|failed|paused)timeout_at timestamptz not nullretry_count int not null default 0created_at timestamptz not nullupdated_at timestamptz not null
Timeout/retry policy (defaults):
- per-step timeout: 5 minutes
- retries: 3 with exponential backoff
- on repeated failure: move to compensation and/or pause + alert ops
6.4.3.2 Idempotency keys
Every step uses an idempotency key:
{amendmentId}:{stepName}:{attemptNumber}
CP and Deal Engine must:
- detect duplicates
- return the original result if already applied
This makes retries safe.
6.4.3.3 How CP and DE stay consistent
- Deal Engine never assumes CP accounting mechanics; it provides deltas.
- CP applies adjustments and then acknowledges.
- If ack fails, saga retries the ack step; CP remains idempotent using keys.
- DLQ captures repeated failures for operations remediation (see DLQ section).
6.4.4 Stable obligation keys (how CP reconciles safely)
6.4.3b Correction workflow (retroactive fixes; distinct from amendments)
A Correction means the previously-computed snapshot was factually incorrect (data entry error, mapping error, or a confirmed model bug). Corrections must not be treated as “terms changed,” because CP may trigger legal/process workflows (e.g., contract amendment artifacts) if we misuse the concept.
A correction is therefore a distinct change classification with distinct event names and recommended CP handling.
Examples:
- Guarantee amount was entered as 1,000,000 instead of 100,000.
- A legacy mapping mis-read a milestone date.
- A model bug misapplied rounding and is now fixed.
Correction invariants
- Corrections may be retroactive (effective date in the past).
- Corrections are computed against the same baseline snapshot CP used (restatement semantics).
- CP should treat corrections as void/reissue or restatement (implementation-specific), not as ordinary “credit/debit memo because terms changed.”
Correction flow
- Correction detected
- User creates a new deal revision or applies a model migration with
changeClassification=correction - Must include
reasonCode(see below)
- User creates a new deal revision or applies a model migration with
- Snapshot computed
- Runtime computes the new snapshot (same as normal)
- Delta computed vs CP baseline
- Delta is computed against the CP baseline snapshot (or current applied baseline), not merely “last snapshot”
- Notification
- Deal Engine emits
deal.correction_readyanddeal.obligations_delta_readywithclassification=correction
- Deal Engine emits
- CP handling
- CP pulls delta and treats it as a restatement/void-reissue workflow as appropriate
- Acknowledgement
- CP acknowledges the correction application; Deal Engine updates applied baseline
Reason codes
terms_change(amendment)data_entry_error(correction)model_bug_fix(correction)legacy_mapping_fix(correction)
The classification and reasonCode are required fields in the delta contract (see 6.4.5).
Every obligation line item produced by the runtime must include a stable obligationKey that is consistent across recomputations.
Recommended construction (deterministic):
obligationKey = hash(modelClauseId + scheduleOccurrenceId + counterpartyRef + currency + allocationGroupId?)
Where:
modelClauseIdis stable within a model version (clause or primitive id)scheduleOccurrenceIdis the occurrence identity (e.g., installment #3, milestone “delivery”, period “2027-Q1”)
This ensures that:
- when the amount/date changes, CP can see it as “same obligationKey, new terms”
- when a new obligation appears, it has a new obligationKey
- when an obligation is removed, it is explicitly “cancelled” via delta semantics
6.4.5 Delta package schema (the contract between Deal Engine and CP)
Deal Engine must expose a delta endpoint:
GET /deals/{dealId}/obligations/delta?fromSnapshot={id}&toSnapshot={id}
Response includes:
classification(amendment|correction)reasonCode(terms_change|data_entry_error|model_bug_fix|legacy_mapping_fix)added[]obligations (new keys)changed[]:obligationKeyfromto
cancelled[]:obligationKeyreason(e.g., superseded by amendment; window expired)
effectiveAt(date/time from which CP should treat the delta as active)amendmentIdexplainRefs(optional pointers to traces for “why did it change?”)
Important: Deal Engine does not assume CP’s accounting mechanics. CP chooses whether a change is handled by:
- credit memo / debit memo,
- invoice cancellation + reissue,
- or schedule update for future items.
6.4.6 Protecting already-posted activity (no retroactive mutation)
Deal Engine must track CP acknowledgements:
- CP sends back invoice/payment actions referencing
obligationKeyandsnapshotId:POST /deals/{dealId}/payments/ack(already specified)POST /deals/{dealId}/invoices/ack(add if needed)
Once CP has invoiced/posted/paid an obligationKey for a baseline snapshot:
- Deal Engine may still compute new snapshots, but must not pretend the past didn’t happen.
- If an amendment reduces an already-invoiced amount, that becomes an adjustment recommendation in the delta package (CP will credit).
- If an amendment increases an already-invoiced amount, that becomes a debit recommendation.
This keeps a clean ledger: history remains; adjustments move forward.
6.4.7 Events emitted (for automation)
When an amendment delta is ready:
deal.amendment_ready:dealId,amendmentId,fromSnapshotId,toSnapshotId,effectiveAt
deal.obligations_delta_ready:- same ids + summary counts (added/changed/cancelled) When a correction delta is ready:
deal.correction_ready:dealId,correctionId(amendmentId),fromSnapshotId,toSnapshotId,effectiveAt,reasonCode
CP subscribes and then pulls details via API.
Reliability requirement:
- use an outbox pattern so DB commits and events cannot diverge.
6.4.8 Storage additions (minimal)
Add:
de_processing_state
deal_id uuid pkprocessing_started_at timestamptz not nullbaseline_snapshot_id uuid not nullcurrent_applied_snapshot_id uuid not nullupdated_at timestamptz not null
de_amendment
id uuid pkdeal_id uuid fkfrom_snapshot_id uuid not nullto_snapshot_id uuid not nulleffective_at timestamptz not nullstatus text not null(proposed/ready/applied/rejected)created_by text not nullcreated_at timestamptz not nullapplied_at timestamptz
6.4.9 Why this is “simple and elegant”
- The core write-path never changes: revisions + snapshots remain immutable.
- CP integration stays stable: CP always pulls obligations/snapshots, and for changes it pulls a delta.
- We avoid “distributed editing”: Deal Engine owns terms; CP owns accounting actions.
- Auditability is automatic: every step is versioned, replayable, explainable.
6.5 Compute result caching (performance + CP responsiveness)
The platform already defines a deterministic compute fingerprint: model_hash + primitive_catalog_version + canonical(inputs) + evidence_set_hash + as_of.
We use this to implement cache-aside snapshot reuse.
6.5.1 Cache key
cache_key = sha256(model_hash + primitive_catalog_version + sha256(canonical_json(inputs)) + evidence_set_hash + as_of_bucket)
Where as_of_bucket is configurable:
- default: exact timestamp
- optionally: truncated to day for certain deal types (if allowed by semantics)
6.5.2 Cache tiers
- L1: in-process LRU (per runtime instance)
- ~1000 entries, 5-minute TTL
- L2: distributed cache (Redis)
- compressed snapshots, 24-hour TTL
- L3: Postgres snapshot store (authoritative, immutable)
6.5.3 Invalidation triggers
- new deal revision created
- new observation ingested affecting the deal
- model migration applied
- manual invalidation (admin)
6.5.4 Cache-aside flow
For GET /deals/{id}/obligations?asOf=...:
- Build fingerprint and
cache_key. - Check L1 → L2 → L3.
- On miss:
- compute snapshot deterministically
- write to L3
- populate L2 and L1
- Return snapshot with
cache_hitmetadata.
6.5.5 Metrics
7. Legacy ingestion: how we load nine systems without chaos
Business value: Bring open legacy deals into the new world quickly so CP can rely on obligations now, while allowing fidelity to improve incrementally.
7.1 Goals
- Open legacy deals must become queryable as deals immediately (even if fidelity is low).
- Client Processing must rely on obligations for open historical deals; those deals must be upgraded to sufficient fidelity.
- Ingestion must be idempotent using stable legacy identifiers.
- Every loaded field must have provenance (source system + record + mapping version).
7.2 Tiered fidelity strategy
Tier 0 — Reference-only (“queryable now”)
For open deals we can’t fully interpret yet:
- create a Deal Engine deal instance using a legacy placeholder model
- store:
- legacy ids, key references, minimal amounts/dates if known
- link to source artifacts (PDF, record link, export row)
- obligations may be empty or “unknown”
- deal is still searchable, linkable, and trackable
Tier 1 — Normalized core economics (“operationally useful”)
Map legacy data to a canonical “core economics” model:
- currency
- guaranteed amounts + due dates if known
- high-level splits if available
- basic contingency flags / evidence keys
- enough to produce obligations that CP can use for many workflows
Tier 2 — Full semantic modeling (“finance-grade for complex deals”)
For selected domains (e.g., long-running overall deals):
- map to a full semantic deal type
- capture schedules, caps, tiers, and triggers accurately
- integrate evidence pipelines required to compute contingents
Rule: CP reliance requires Tier 1+ at minimum. Tier 0 is allowed only when CP does not need obligations from that deal yet.
7.2.1 Data quality scoring (continuous measure)
Tiering (0/1/2) is necessary, but not sufficient: legacy data quality is continuous. We compute a quality score 0.0–1.0 per deal to drive prioritization and CP reliance decisions.
Dimensions (default weights; configurable per deal type):
- Identity completeness (0.15): client/counterparty refs resolved
- Amount completeness (0.20): guaranteed amounts present + validated
- Schedule completeness (0.20): payment dates/milestones specified
- Allocation completeness (0.15): splits/parties defined
- Evidence linkage (0.15): contingent events have sources configured
- Validation pass rate (0.15): % of model constraints satisfied
Storage:
- Extend
de_legacy_deal_link:quality_score decimal(4,3) not null default 0.0quality_dimensions jsonb not null default '{}'quality_computed_at timestamptz
- Add
de_quality_score_historyfor trend analysis:deal_id,score,dimensions,computed_at,triggered_by
Thresholds:
cp_reliance_minimum(default 0.70)auto_upgrade_threshold(default 0.85)alert_degradation_threshold(default 0.10 score drop)
APIs:
GET /deals/{id}/qualityGET /legacy/quality/distribution?sourceSystem=...POST /legacy/quality/recompute(batch)
7.3 “Legacy placeholder” models: what they are
Legacy placeholder deal models exist to make deals queryable quickly without pretending semantics we don’t have.
Examples:
legacy_systemA_deal_v1legacy_systemB_booking_v1
These models:
- have a stable input schema matching the legacy export shape (or a cleaned subset)
- do not attempt complex computations
- produce projection fields:
- “known amounts”
- “next expected payment date (if any)”
- “confidence level” / “fidelity tier”
This allows open legacy deals to be present as first-class deals immediately.
7.4 Upgrading fidelity over time
Upgrading a legacy deal is implemented as one of:
- Revision upgrade within same model version
- same model; improved data completeness; create new revision with richer inputs
- Model upgrade
- move from legacy placeholder model → core economics model → full semantic model
- uses the same migration mechanism described above (audited, deterministic)
The system supports “incremental archaeology” while keeping strong audit trails.
7.5 Legacy Ingestion service — what to build
Responsibilities
- accept raw legacy exports and store them immutably
- run mapping/transformation pipelines (versioned)
- resolve identities (client/buyer/engagement refs) against authoritative data
- upsert into Deal Engine via internal APIs:
- create deals if not exists
- create revisions if changed
- optionally bind to specific model versions
- produce reconciliation reports and error queues
Key properties
- Idempotent by
(source_system, legacy_deal_id, legacy_record_hash) - Repeatable: rerun pipelines as mappings improve without duplicating deals
- Auditable: every load links to a mapping version and raw record id
7.6 Legacy ingestion and progressive modernization
Business value: Bring open legacy deals into the new world quickly so Client Processing can rely on obligations now, while allowing fidelity to improve incrementally.
7.6.1 The guiding principle
We do not try to “upgrade” legacy deals into a higher‑fidelity modern schema during initial migration. That typically fails (missing fields, undocumented semantics) and delays business impact.
Instead, we treat each legacy source format as its own legacy deal type model:
- We model the legacy format as-is (schema + templates + computation catalog) so deals become queryable immediately.
- We preserve provenance (source system + legacy ids + raw fields).
- We compute only what we can compute deterministically and validate via fixtures.
- We later incrementally “modernize” or enrich data where there is business value.
This gives us immediate benefits:
- search and aggregation across all deals (including legacy)
- unified obligation view for CP for open deals
- consistent IDs and traceability
7.6.2 What gets migrated first
Phase 1: “Queryable legacy deals”
- ingest deals that are still open or can still generate revenue/payments (including long-running overall deals)
- map legacy identifiers into Deal Engine stable ids
- load raw fields with minimal transformation
- generate baseline obligations where feasible
Phase 2: “Fidelity upgrades”
- selectively map high-value fields into modern structured components
- add richer templates and better computation specs
- backfill evidence/watch hooks where appropriate
7.6.3 Legacy deal type modeling
For each legacy system (nine sources), create a deal type model:
deal_type:legacy_<system>_<domain>_v1inputs.schema(draft 2020-12):- captures the legacy record shape (including weakly-typed fields if needed)
- includes provenance fields (source system, legacy ids, timestamps)
- includes optional “normalized” fields for cross-system search (party ids, dates, currency)
clause templates:
- render a “legacy deal memo” or “legacy record view” from the raw fields
- include a provenance block in the rendered output
ComputationSpec catalog:
- list the outputs the legacy system can support (often fewer than modern models)
- include invariants and fixtures for known calculations
7.6.4 Identity and provenance (required)
Each migrated legacy deal must record:
source_system_idlegacy_deal_id(stable unique id from source)- optional additional legacy keys (booking id, contract id, etc.)
legacy_record_hash(hash of raw payload for audit)- mapping to new Deal Engine
deal_id
Split/Merge reality (edge cases):
- allow 1 legacy record → multiple Deal Engine deals (split children)
- allow multiple legacy records → one Deal Engine deal (rare; explicitly modeled)
- model this with a link table:
(source_system_id, legacy_deal_id, deal_id, relationship_type)
7.6.5 Migration mechanism (bulk load)
Introduce a dedicated migration pipeline (can be a service or batch job):
- Extract from legacy system → canonical export files
- Transform minimally into the legacy deal type
inputspayload - Load via a bulk endpoint:
POST /migration/bulk/deals(job-based, idempotent)
- Validate:
- schema validation (2020-12)
- required provenance fields
- referential integrity for parties (as available)
- Produce:
- persisted deal revisions + baseline snapshots
- optional obligations (if computation specs exist)
- Emit migration report:
- counts, errors, skipped items, unresolved references
7.6.6 Making legacy deals immediately useful
For open legacy deals, the goal is:
- Deal programs serve the standard interface even for legacy deal types.
- Downstream consumers can query by:
- deal type, parties, dates, status, currency
- known outputs (even if partial)
- CP can rely on obligations where available, and track “unknown/unsupported” areas explicitly.
If a legacy model cannot compute obligations reliably:
- mark the obligation capability as unsupported for that deal type/version
- still allow CP to link the deal and store manual obligations if needed (audited), while modernization proceeds.
7.6.7 Fidelity levels (explicit)
Each legacy deal type should declare a fidelity level:
- L0 Raw: raw fields only, templates only, no computations
- L1 Calculated: deterministic outputs supported with fixtures
- L2 Obligations: obligations and deltas supported for CP reliance
- L3 Modernized: mapped to modern clause patterns and richer analytics
This keeps expectations realistic and avoids “magic conversions.”
7.7 Bulk load APIs (internal)
POST /legacy/ingest/raw
store raw record(s) (batch)POST /legacy/transform/run
run mapping pipeline for a source system + mapping versionGET /legacy/transform/runs/{runId}
status + statsGET /legacy/errors?runId=...
remediation queuePOST /legacy/upgrade
attempt to upgrade fidelity for a set of deals (0→1, 1→2) with explicit rules
7.8 Contingency tracking and event watching (detailed architecture)
This section specifies the exact approach for tracking contingent compensation triggers (“events”), evaluating them, and notifying Deal Engine and Client Processing when a contingency becomes active/fulfilled.
7.8.1 Core concepts
- Event definition: a model-declared definition of a condition that can change state over time (e.g., “box office exceeded $X”, “season picked up”, “artist delivered masters”).
- Observation: a time-stamped piece of evidence (internal or external) that provides a value used by event evaluation.
- Watch: a persistent, operational instruction to “keep this deal’s event up to date” (subscribe/poll/timer), including matching rules.
- Contingency state: the computed state for an event on a deal:
open → activated → fulfilled(orunmet/expired/disputed).
7.8.2 Event types we must support
A) Internal events (push)
Derived from UTA systems or Deal Engine itself:
- milestone dates reached (contract date, delivery due date, renewal option deadline)
- deliverable acceptance (internal approval)
- payment received / invoice sent (from Client Processing)
- status transitions (deal closed, engagement cancelled)
B) External events (push or pull)
Derived from third parties or public data:
- box office totals, streaming hours, ratings
- sports stats (yards, games played)
- chart ranks, ticket sales thresholds
- brand campaign KPIs
Important: external events must never be fetched directly inside the model runtime. The runtime consumes observations only.
7.8.2b Evidence Source Adapters (schema versioning and decoupling)
External payload schemas change over time. Models must not bind directly to vendor payload field paths.
Approach: models bind to logical canonical source schemas, and platform engineering maintains adapters from vendor payloads to canonical schemas.
- Canonical source schema (Registry-managed)
- Example:
box_office_stats@v1 - Fields:
totalGrossUsd,ticketsSold,reportingDate,movieId
- Example:
- Source adapter version
- Example:
comscore_api@2026-04 -> box_office_stats@v1 - Transforms raw payload to canonical schema; handles renames (e.g.,
gross_usd→totalGrossUsd)
- Example:
- Model definition
- Uses
source: box_office_stats(logical), not the vendor name.
- Uses
Operationally:
- Evidence ingestion stores raw payload (for audit) and canonical payload (for computation).
- Watches and match_specs target canonical fields.
- Adapters are versioned and can be rolled forward without changing immutable model versions.
Data model additions (minimal):
de_canonical_source_schema:name(e.g.,box_office_stats)version(e.g.,v1)schema jsonb(JSON Schema for canonical payload)
de_source_adapter_version:
7.8.3 Model-level event specification (what the model must declare)
Each model version may declare events. For contingent compensation, the model must include:
name(stable identifier)type(bool|number|money|string|date)source(evidence source name)selector/match(how observations correlate to this deal)condition(typed expression evaluated over inputs + observations)effects(what changes when the event becomes true: activates clause(s), generates obligations, changes outputs)
Canonical model doc shape (event definition)
events:
- name: box_office_threshold_met
type: bool
source: comscore_boxoffice
match:
kind: bySubjectRef
subjectKey: projectId # from deal.subject_refs
evidenceField: movieId # field in observation payload
condition:
op: gte
left: { op: evidence, key: "comscore.boxoffice.totalGrossUsd" }
right: { op: input, path: "contingent.thresholdUsd" }
effects:
activateClauses: ["backend_bonus_clause"]This forces the authoring workflow (LLM or humans) to define how we match evidence to deals, which is the most common real-world failure mode.
7.8.4 Operational architecture
Business value: This automation is what reduces missed contingent revenue and manual follow‑ups, while keeping an auditable trail finance can trust.
In short: when a deal model includes contingent terms (“pay X if Y happens”), the deal program registers watches that describe what evidence it needs and how to match it (e.g., “boxOffice.total for filmId=…”, “show.settled for showId=…”, “option_exercised for dealId=…”). An Evidence Service ingests observations from internal systems or external adapters (plus manual overrides), normalizes them into canonical keys, and matches them to active watches. When a matching observation arrives (or a timer fires), the deal program recomputes the affected deal revision (or marks a contingent clause as activated), producing a new snapshot and an obligation delta; it then emits an event that Client Processing can consume (or poll) to update invoices/payments. Watches have lifecycle controls (expiry, dormancy) to avoid “zombie polling,” and every observation and resulting recompute is auditable (source, timestamp, decision id).
At runtime, contingent tracking is implemented by the Event + Evidence service, a Watch Index, and a Compute Orchestrator.
sequenceDiagram
participant Source as Evidence Source
participant EVT as Evidence Service
participant DB as Postgres
participant RT as Runtime/Compute Worker
participant DEAL as Deal API (or compute job)
participant CP as Client Processing
participant BUS as Message Bus
Source->>EVT: Observation (push webhook / batch / poll result)
EVT->>DB: Store observation (immutable) + dedupe
EVT->>DB: Find watches affected (dealId/eventName)
EVT->>BUS: publish compute.requested(dealId, observationId)
BUS->>RT: compute.requested
RT->>DB: Load deal current revision + relevant observations
RT->>DB: Evaluate event condition(s) + compute snapshot
RT->>DB: Persist contingency_state + snapshot + explain
RT->>BUS: publish contingency.state_changed + deal.obligations_changed
BUS->>CP: notify (subscribe)
CP->>DEAL: fetch updated obligations (pull-by-notification)Pattern: notify via bus → CP pulls the latest obligation set via API.
This avoids large payload pushes and keeps CP in control of read timing.
7.8.5 Watches: how we “keep events up to date”
A watch is created when:
- a deal revision is created, and
- the model includes one or more contingent events, and
- the deal is in a status where contingencies matter (typically
negotiating|closedwhile revenue may occur).
New table: de_event_watch
id uuid pkdeal_id uuid fkmodel_version_id uuid fkevent_name text not nullsource_id uuid fk de_evidence_source(id)match_spec jsonb not null(thematchblock from model, compiled)polling_spec jsonb(optional: if source is pull-based)timer_spec jsonb(optional: if date-driven triggers are needed)status text not null(active/paused/closed)created_at timestamptz not nullupdated_at timestamptz not null- unique
(deal_id, model_version_id, event_name)
Watch lifecycle, expiration, and pruning (zombie watch prevention)
Watches must not poll forever for dormant or ended deals.
- Auto-expiration: each watch must have
expires_at, derived from:- the deal’s modeled
term_end_date(or equivalent), plus - a model-defined
tail_period(grace period for late evidence)
- the deal’s modeled
- Dormancy: if a watch remains in
unmet(or “no change”) for N consecutive evaluation cycles, it transitions todormantand polling stops.- default N is source/model specific (e.g., 12 monthly cycles)
- Wake-up: a new deal revision, model migration, or manual intervention can reactivate a dormant watch.
Implementation:
- extend
de_event_watchfields:expires_at timestamptzdormancy_count int not null default 0last_evaluated_at timestamptzstatusincludesactive|paused|dormant|closed
- scheduler runs a daily pruning job:
- close watches past
expires_at - move long-dormant watches to
closed(with audit) if permitted by policy
- close watches past
Watches are (re)materialized when:
- a deal revision changes relevant subject refs
- a deal migrates to a new model version
- an event definition changes in a new model version (new watch set)
7.8.6 Evidence ingestion modes
A) Push (preferred)
- Signed webhooks from trusted sources
- Internal events emitted by CP or other UTA systems
Endpoints:
POST /evidence/observations(internal)POST /evidence/webhooks/{sourceName}(external)
B) Pull (polling)
For sources that do not push:
- a poller worker runs per
polling_spec - results are written as observations (same path as push)
Polling requires:
- rate limiting
- retry/backoff
- source auth + rotation
- durable checkpoints (cursor/lastSeen)
B.1 Circuit breaker for external evidence sources (failure isolation)
External evidence sources can be slow or unavailable. We isolate failures using a circuit breaker per source:
States:
- Closed: normal operation
- Open: fail fast; skip outbound calls
- Half-open: allow a probe request to test recovery
Trip policy (defaults):
- open after 5 consecutive failures
- stay open for 60 seconds then half-open
- success closes; failure re-opens
Storage table: de_evidence_source_health
source_id uuid pkcircuit_state text(closed/open/half_open)failure_count intlast_failure_at,last_success_atopened_atupdated_at
Behavior:
- polling workers skip open circuits (emit metrics)
- push webhooks are still accepted (circuit breaker applies to outbound polling)
- watches are marked
source_unavailable(distinct from paused) - compute:
- if evidence optional: proceed but flag snapshot “missing evidence”
- if evidence required: snapshot returns
evidence_source_unavailableand remains pending
Admin:
GET /evidence/sources/{id}/healthPOST /evidence/sources/{id}/health/reset
C) Timers (date-driven)
For schedule milestones and deadlines:
- watches may include
timer_spec - a timer worker creates observations at the correct times (internal source)
7.8.6b Manual evidence overrides (business-correct superseding)
Sometimes automated feeds are “technically correct” but business-wrong or disputed. Authorized users must be able to insert a manual observation that supersedes automated ones without changing models or code.
Mechanism:
- Insert into
de_event_observationwith:source_type = manualpriority = highevidence_refexplaining rationale
- Resolution rule (runtime/evaluation):
- Higher priority wins (manual > automated)
- Newest wins within same priority
- If multiple conflicting manual observations exist, mark contingency
disputedand require resolution (policy)
Data model additions:
- extend
de_event_observation:source_type text not null default 'automated'(automated|manual)priority int not null default 0(manual override uses 100)supersedes_observation_id uuid nullcreated_by text null(for manual)
AuthZ:
- manual overrides require elevated permission (
evidence.override) - all overrides are audited and visible in the explain trace
APIs:
POST /evidence/observations/manual
7.8.7 Matching observations to deals (the “which deal does this belong to?” problem)
Since you have stable identifiers, matching should be deterministic.
Matching strategy (in order):
- Explicit deal_id on observation (internal sources often can provide this)
- Watch match_spec (preferred for external sources)
- Legacy id mapping (during migration/ingestion): observation provides legacy ids; map to deal_id via
de_legacy_deal_link - Fallback resolver (rare): deterministic lookup by subject refs (projectId, engagementId)
If no match is found:
- store observation with
deal_id = null - route to remediation queue (do not drop data)
7.8.8 Event evaluation and state transitions
Event evaluation occurs after each new observation affecting a watch:
- load current inputs (latest revision)
- collect relevant observations for this watch (by deal_id + event_name + source + match keys)
- evaluate model’s
condition(typed expression) - update
de_contingency_state
Recommended state machine:
open(not yet active)activated(condition became true; clause(s) now payable/trackable)fulfilled(payout satisfied OR obligation posted/paid; definition per model)unmet(condition determined false at end-of-window)expired(past eligibility window)disputed(manual hold; blocks downstream automation)
Important: many contingencies depend on windows (“in first 12 months”). The model must declare window semantics so expired/unmet is computable.
7.8.9 Compute orchestration and idempotency
The Event service never performs financial computations itself. It schedules compute.
Compute request message:
- subject:
compute.requested - body:
dealIdreason = evidence_ingested | timer_fired | deal_revised | manual_recomputeobservationId(optional)asOf
Idempotency:
- compute worker derives
evidence_set_hash - writes snapshot with unique key
(deal_id, revision, as_of, evidence_set_hash) - retries are safe
7.8.10 Notifications: how CP and other systems learn “something happened”
When a contingency state changes or obligations change, the runtime emits:
contingency.state_changed- includes
dealId,eventName,oldState,newState,observationId,snapshotId,modelHash
- includes
deal.obligations_changed- includes
dealId,snapshotId, summary counts (no large payload),effectiveAsOf
- includes
Consumers:
- Client Processing subscribes to
deal.obligations_changedand pulls obligations via:GET /deals/{dealId}/obligations?asOf=...
- Departmental systems can subscribe similarly for UI notifications.
- Projection service subscribes to update dashboards.
Reliability:
- use an outbox pattern in Deal Engine services:
- write DB changes + outbox row in same transaction
- publisher drains outbox to bus
- prevents “DB committed but event lost”
7.8.11 Operational controls (must-have)
Event/Evidence service must support:
- pausing/resuming watches (e.g., disputed deals)
- replaying observations (recompute from a point in time)
- viewing watch health:
- last observation time
- next poll time
- backlog size
- error rate per source
A simple admin API:
GET /evidence/watches?filters...POST /evidence/watches/{id}/pausePOST /evidence/watches/{id}/resumePOST /evidence/watches/{id}/replay?from=...
7.8.12 What this enables for “overall deals” (2–20 years)
For long-running deals:
- watches remain active for years
- timers handle recurring obligations and renewal deadlines
- external evidence sources can periodically update performance metrics
- CP is notified whenever obligations become newly due or contingent bonuses activate
This architecture is designed to run continuously and auditably over multi-year horizons.
7.9 How CP relies on historical open deals (the “overall deal” case)
Long-running deals introduce three requirements:
- Obligations can span years
schedules must generate future obligations and support updates - Contingencies can depend on external evidence over years
evidence sources must be durable and auditable - Deal changes occur mid-term
revisions + recompute must preserve history and current truth
Implementation specifics:
- Overall deals should be Tier 2 semantic models.
- The model must express:
- term windows
- periodic obligations
- caps/floors
- renewal options (as inputs + events)
- triggers like “season pickup,” “minimum compensation escalator,” etc.
- Event service must support:
- timers (for periodic due dates)
- external evidence observations (renewal, performance metrics)
- CP uses:
GET /deals/{id}/obligations?asOf=...to retrieve what to invoice/post nextPOST /deals/{id}/payments/ackto feed receipts and allocations back for balance tracking
7.10 Dead Letter Queue (DLQ) and remediation workflow
No critical pipeline in Deal Engine may “log and drop” failures. Unprocessable items must be captured for retry and human remediation.
7.10.1 DLQ sources
- Legacy ingestion transforms
- Evidence observations (no match, invalid payload)
- Poller jobs (source failures beyond retry policy)
- Compute jobs (model error, timeout, resource exhaustion)
- Watch evaluations (condition errors)
- Amendment sagas (step failures/timeouts)
7.10.2 DLQ storage
Table: de_dead_letter
id uuid pksource_service text not nullitem_type text not nullitem_id text(original id if available)payload jsonb not nullerror_code text not nullerror_message text not nullerror_details jsonbretry_count int not null default 0max_retries int not null default 3status text not null(pending|retrying|remediated|discarded|escalated)remediation_action text(retry|transform|manual|discard)remediation_notes textremediated_by textremediated_at timestamptzcreated_at timestamptz not nullexpires_at timestamptz(auto-archive)
7.10.3 Retry policy
- exponential backoff (1m, 5m, 30m… configurable)
- idempotency keys ensure safe replays
- after
max_retries, item becomesescalatedand alerts ops
7.10.4 DLQ operations UI
The Deal Foundry includes an Operations → Dead Letters page (see Appendix B) with:
- filters by source/type/status/error code
- bulk retry
- inspect payload + error
- optional “transform and retry” for certain types (guarded; audited)
8. Data model (Postgres) – core tables (unchanged but referenced)
Business value: Provide a durable data model that supports auditability, reporting, and deterministic recomputation—foundational for trust and compliance.
(Registry, deal instance, evidence, audit tables are as previously specified; see the technical spec version prior to this revision.)
If you want, this section can be expanded to re-list every table, but for brevity we focus on the new versioning + legacy additions here.
9. Registry migrations: storage additions
Business value: Describe how to evolve registry/storage safely so discovery and deployment keep working as we add deal types and versions.
Add to registry:
de_model_migration
id uuid pkdeal_type_id uuid fkfrom_version_id uuid fk de_model_version(id)to_version_id uuid fk de_model_version(id)migration_spec jsonb not null- includes:
- input transforms
- evidence key remaps (optional)
- projection remaps (optional)
- includes:
fixtures jsonb not null(migration test cases)created_at timestamptz not null- unique
(from_version_id, to_version_id)
de_model_version additions
compatibility text not null(compatible/requires_migration/deprecated_only)requires_migration boolean not null default false
4.2 Implementation priorities and phased adoption (so agents don’t boil the ocean)
This system has a large surface area. To avoid building everything at once, implement features in priority bands with clear “exit criteria” for each band. Later bands are optional until scale/complexity requires them.
Band 0 — Foundation invariants (must exist first)
Goal: enforce determinism, immutability, and versioning from day one.
In plain terms: Build the minimum platform so models can be defined, compiled, computed deterministically, and audited—before we ship workflow and integrations.
Build:
- Canonical model schema + primitive catalog v1 (types + ops), including Money + FX determinism rules
- Model Registry (store model versions + artifacts + fixtures; approval gates)
- Runtime (compile → IR; evaluate; explain traces)
- Deal API (deal, revisions, compute, obligations endpoint)
- Outbox publisher module (transactional events) + minimal bus wiring
- Language-agnostic compliance suite (canonical JSON + hashing + decimals + obligationKey + fingerprint test vectors)
- Basic AuthN/AuthZ enforcement + audit log
Exit criteria:
- One deal type runs end-to-end with fixtures passing
- Snapshots reproducible by hash (replay test passes)
- Deal revisions are immutable and conflict-safe (optimistic locking)
Band 1 — Make it usable (authoring MVP + projections)
Goal: enable real iteration of models and basic enterprise search.
In plain terms: Ship Deal Foundry so modelers can build schemas, computations, fixtures, and previews; and app teams can discover models and search deals.
Build:
- Authoring Service MVP (draft → generate → validate → compile → fixtures → submit)
- Deal Foundry MVP:
- Model Library
- New Draft Wizard
- Draft Workspace (Model, Fixtures, Results)
- Review & Approvals (minimal)
- Projection tables + projector worker (outbox-driven)
- Search endpoint backed by projections
Exit criteria:
- A modeler can produce a new model version with fixtures and publish (approved + active)
- Departmental system can search deals and fetch obligations in < 500ms P95 (hot path)
Band 2 — Events & contingencies (operational engine)
Goal: reliably track contingent compensation triggers and change obligations over time.
In plain terms: Add the event/evidence machinery so contingent terms can activate over time and obligations update automatically.
Build:
- Evidence ingestion (push + minimal polling)
- Watch index +
de_event_watch - Contingency evaluation + recompute orchestration
- Notifications (
deal.obligations_changed) + CP pull pattern - Circuit breaker for polling sources
- DLQ v1 (store, retry, ops view optional)
Exit criteria:
- One contingent trigger type works end-to-end (observation → state change → obligation delta)
- Watch health is visible and failures do not drop on the floor (DLQ captures)
Band 3 — Client Processing reliance + amendments (finance-grade)
Goal: CP can safely act on obligations and handle term changes.
In plain terms: Make the platform safe for money movement: CP pins baselines, receives deltas, applies amendments/corrections, and acknowledges invoices/payments.
Build:
- CP processing start “pin” + baseline snapshot
- Stable
obligationKeygeneration - Obligation delta endpoint
- Amendment workflow + saga coordinator (idempotent steps)
- Correction workflow (restatement semantics distinct from amendment)
- Payment/invoice acknowledgements
- Snapshot retention rules (CP-referenced snapshots never compacted)
Exit criteria:
- CP can run an open historical deal from obligations, apply a payment, and see balances update
- An amendment produces a delta and CP can apply adjustments without losing audit trail
Band 4 — Legacy ingestion at scale (nine systems)
Goal: ingest open legacy deals fast and upgrade fidelity without duplication.
In plain terms: Load historical open deals quickly and upgrade fidelity so CP can rely on obligations for long-running deals.
Build:
- Staging tables (
de_legacy_raw_record, mapping versions, transform runs) - Tier 0 placeholder models per legacy system
- Tier 1 core economics model(s)
- Upgrade workflow 0→1 and 1→2 (revision upgrades + migrations)
- Data quality scoring + dashboards
Exit criteria:
- Open deals from all nine systems are queryable as deals (Tier 0)
- Target set of open historical deals needed by CP meet quality threshold and are Tier 1/2
Band 5 — Scale and hardening (only when needed)
These are optional until load/complexity forces them:
- L2 distributed compute cache (Redis)
- CDC-driven projections (Debezium/WAL)
- Snapshot compaction to object storage tiers
- GraphQL federation for UI composition
- Advanced sandbox testing and regression suites (broaden)
Exit criteria:
- Chosen items are justified by concrete metrics (latency, lag, storage, developer productivity)
Guardrail: “No skipping bands”
Agents should not implement Band 3+ features before Band 0–2 invariants and plumbing exist. A partial saga or partial event pipeline without determinism and outbox/DLQ semantics will produce an un-debuggable system.
10. Implementation notes for coding agents
Business value: Give coding agents the practical implementation notes needed to ship value incrementally without accidentally violating core tenets.
10.1 “Don’t break old deals” checklist
- never delete model versions or primitive catalog versions
- never mutate deal revisions or snapshots
- enforce compatibility classification at publish time
- migration writes a new revision + updates deal’s bound model version
- ensure compute snapshot keys include model hash + evidence set hash
10.2 “Load nine systems” checklist
- build staging tables and idempotent raw record ingestion first
- ingest open deals first so they are queryable early
- build Tier 0 legacy placeholder models first (one per legacy system)
- then build Tier 1 core economics model(s) and upgrade the deals CP needs
- keep mapping versions and raw record hashes so reruns produce revisions, not duplicates
Appendix: quick answers to your two questions
A) How do updated models avoid breaking old data?
- Every deal is pinned to a model version.
- New understanding ships as a new model version.
- Old deals stay pinned, or you explicitly migrate them, creating a new revision and audit trail.
B) How do we load legacy deals from nine systems?
- Use a Legacy Ingestion service with staging + mapping versions + idempotency.
- Load open deals immediately as Tier 0 placeholder deals.
- Upgrade to Tier 1/2 where CP needs obligations, especially long-running overall deals.
Appendix B: Deal Foundry UI and workflow requirements (build spec)
This appendix specifies the end-to-end authoring system that coding agents can implement without guessing: backend draft persistence, UI pages, roles, review/approval workflow, regeneration behavior, and acceptance criteria.
B1. Product: “Deal Foundry”
A secure internal web app used by modelers and reviewers to create and publish Deal Model versions.
Primary users
- Modeler (Sales Ops / Business Affairs / PM): drafts models, supplies examples, reviews outputs, iterates.
- Reviewer (Finance + Business owner): reviews model card, semantics, fixture outcomes; requests changes/approves.
- Publisher (Platform/Finance admin): activates versions, sets rollout flags, manages migrations.
Non-goals
- This console is not for day-to-day deal entry (departmental systems do that).
- This console is not a full BPM system; it implements a minimal review/approval workflow sufficient for audit.
UI tech defaults (recommended)
- React + Next.js (app router), no server components for app logic
- shadcn/ui components
- TanStack React Query for data fetching/caching
- OpenAPI-generated TS client for services
- Entra ID authentication (MSAL) with JWT to API Gateway
(If your internal standard differs, keep the page contracts and states.)
B2. Draft lifecycle model (what persists)
The Authoring Service previously described can be implemented “stateless” with the LLM, but the UI needs persistent draft state. Implement drafts as first-class entities.
Draft entity (backend)
Create a table:
de_authoring_draft
id uuid pkdeal_type_name text not null(ordeal_type_idif already created)target_version text(proposed version)status text not nulldraftvalidatingready_for_reviewchanges_requestedapprovedsubmittedarchived
primitive_catalog_version text not nulldescription text not null(natural language intent)examples jsonb not null(metadata + storage pointers)constraints jsonb(required outputs, CP mapping needs, policies)generated_model_source jsonb(canonical model doc)generated_model_card jsonbgenerated_fixtures jsonb(list or pointers)generated_dsl text(optional)generated_code_ref text(optional pointer)validation_report jsonb(schema validation + lint)compile_report jsonbfixture_report jsonb(results per fixture + diffs)diff_report jsonb(vs prior version, if any)created_by text not nullcreated_at timestamptz not nullupdated_at timestamptz not null
Comments + approvals (backend)
Add:
de_authoring_comment
id uuid pkdraft_id uuid fkauthor text not nullcreated_at timestamptz not nullscope text not null(model_card / model_source / fixture / general)path text(optional JSON pointer, e.g.,/primitives/clauses/0)comment text not nullresolved boolean not null default false
de_authoring_approval
id uuid pkdraft_id uuid fkapprover text not nullrole text not null(business_owner / finance_owner / platform_owner)decision text not null(approved / rejected)note textdecided_at timestamptz not null
Status transitions (enforced)
draft → validatingwhen generation/validation beginsvalidating → ready_for_reviewwhen:- schema + lint pass
- compile pass
- fixtures pass (or pass threshold, configurable)
ready_for_review → changes_requestedwhen reviewer requests changesready_for_review → approvedwhen required approvals completeapproved → submittedwhen registry submission succeeds
Locking rule:
- when
ready_for_review, edits create a new “draft iteration” (either new draft id or incrementdraft_revision) so approvals refer to a specific artifact set.
B2.4 Evaluation jobs and sandbox execution (fixtures, workbench, scenario)
Business value: Fast, isolated verification keeps modeling accurate without turning Deal Foundry into a programming language IDE or pushing computation back into calling apps.
Deal Foundry must validate models and run computations during authoring (fixtures, calculation workbench, scenario runner). Because authoring may execute LLM-generated code fragments or generated evaluators, execution must be isolated and easy to clean up.
B2.4.1 Design goal (in plain terms)
Treat execution as job-scoped and disposable:
- Do not associate long-lived sandboxes with a specific modeler session.
- Do not keep persistent state in the sandbox.
- Run authoring verification as evaluation jobs that can be queued, cancelled, retried, and audited.
B2.4.2 Evaluation job types
All authoring-time execution is expressed as an evaluation_job:
workbench— compute one selected calculation/output with mocked inputs/evidencescenario— compute all outputs/obligations for one payload (“what-if”)fixtures— run a fixture suite and produce pass/fail diffs
B2.4.3 Inputs and outputs
Job inputs (data only):
draftId,modelHash,schemaBundleHash,asOf- compiled artifacts (typed IR / evaluator bundle) referenced by hash
- payload(s):
- one payload for
workbench/scenario - N fixtures for
fixtures
- one payload for
- evidence overrides (optional)
- execution limits: timeout, max memory, max fixtures per batch
Job outputs:
- computed outputs/obligations
- diffs vs expected (fixtures)
- warnings/errors with source spans (best-effort)
- explain trace references (if enabled)
- timing metrics
B2.4.4 Execution backends
Deal Foundry supports an execution backend abstraction:
- Local sandbox backend (default for development)
- runs evaluator in a restricted local sandbox
- Sprites backend (recommended for stronger isolation)
- uses Fly.io Sprites (or similar) to run evaluator code in an ephemeral sandbox
Backends must enforce:
- no network
- no filesystem writes (or write to ephemeral tmp only)
- CPU/memory/time limits
- deterministic inputs only (no wall-clock unless passed as
asOf)
B2.4.5 Sprites backend pattern (job-scoped, disposable)
If using Sprites:
- Authoring Service creates an
evaluation_jobrecord and persists artifacts to blob storage (by hash). - Worker pulls the job, fetches artifacts, and creates one sprite per job (or per shard for large fixture suites).
- Worker copies artifacts + payload(s) into the sprite and runs evaluation.
- Worker retrieves results and stores them in the job record.
- Worker terminates the sprite immediately (or relies on short TTL) and marks job complete.
Cleanup policy:
- every job has a TTL (e.g., 30 minutes)
- every sprite is tagged with
jobId - a reaper deletes sprites older than TTL or in terminal job states
B2.4.6 Why this is safe and manageable
- Sprites are tied to jobs, not people or sessions.
- State is stored in Deal Foundry (job records), not inside sandboxes.
- Cleanup is deterministic (TTL + jobId tagging).
- Users get fast feedback without sacrificing isolation.
B2.5 LLM-generated evaluator code (TypeScript) for authoring-time verification
Deal Foundry may validate computations during authoring by generating ephemeral evaluator code (TypeScript) using an LLM (Claude Opus). This evaluator runs only inside sandbox execution (local sandbox or Sprites) as part of an evaluation job.
Non-negotiable: evaluator code is authoring-only and is never deployed as a production deal program. Production deal programs are generated/implemented separately via codegen (Appendix M) and must pass fixture packs.
B2.5.1 Why we generate evaluator code
- Allows a semi-technical model author to confirm calculations early (“desk calc” verification) without requiring a full production implementation.
- Supports rapid iteration in Deal Foundry: Workbench + fixtures + contract preview.
- Keeps calling systems dumb: no upstream computation required.
B2.5.2 Two evaluator modes (both required)
- Per-model evaluator (preferred for fixtures + contract preview)
- A single module computes all model outputs (and optionally obligations) for one payload.
- Used by
scenarioandfixturesevaluation jobs.
- Per-calculation evaluator (preferred for Workbench)
- A module that computes exactly one selected calculation/output.
- Used by
workbenchevaluation jobs to provide fast feedback.
Deal Foundry may implement per-calculation by:
- generating a separate evaluator, OR
- compiling/executing the per-model evaluator with a
calculationKeyfilter.
B2.5.3 Evaluator bundle contract (LLM output; strict)
The LLM must return a JSON object with:
language:"typescript"mode:"model" | "calculation"exports: list of exported functionsfiles: a map of filename → file contents (TypeScript)
Required entrypoints:
Per-model
computeModel(ctx): Result
Per-calculation
computeCalc(ctx, calculationKey): CalcResult
Where ctx is fixed:
type EvalContext = {
inputs: unknown; // validated against bundled schema
evidence: Record<string, unknown>; // explicit evidence overrides / observations
asOf: string; // ISO date string
};
type Result = {
outputs: Record<string, unknown>;
obligations?: unknown[];
trace?: unknown; // optional explain data
};
type CalcResult = {
key: string;
value: unknown;
deps?: { inputPaths?: string[]; evidenceKeys?: string[]; calcDeps?: string[] };
trace?: unknown;
};Hard safety rules for evaluator code:
- no imports except allowlisted
@deal-foundry/eval-stdlib(optional) - no filesystem access
- no network access (no fetch, sockets)
- no timers
- no dynamic eval (
eval,new Function) - pure functions: output depends only on
ctx
B2.5.4 LLM prompt inputs (what Deal Foundry provides)
Deal Foundry supplies:
- bundled input schema (
bundled.input.schema.json) - workflow definition (optional)
- calculation catalog (keys/types/deps)
- clause templates (optional; for trace labels)
- fixture payloads and expected outputs for a small subset (for iterative repair)
- policy docs (rounding, currency rules)
B2.5.5 Iterative generation loop (test-driven)
The Authoring Service runs a test-driven loop:
- Generate evaluator bundle (LLM)
- Run sandbox evaluation against a small fixture subset
- If failures: return diffs + trace snippets to the LLM and regenerate
- Stop when required fixtures pass (or author accepts warnings; audited)
All iterations are recorded:
- prompt hash
- code hash
- fixture results
- acceptance decision
B2.5.6 What is persisted
Persist only:
- evaluator bundle hash and metadata (not required to store raw code permanently)
- last successful evaluator bundle for the draft (optional cache)
- fixture results and diffs
Published artifacts remain:
- model JSON, IR, fixture packs, and reports.
B2.6 Evaluation Worker (Bun) and Sprites execution protocol (concrete)
Evaluation jobs should not be executed by the browser or directly by the Deal Foundry UI. Instead:
- UI → Authoring Service (Deal Foundry)
- Authoring Service → Evaluation Worker
- Evaluation Worker → Sprites API (optional backend)
B2.6.1 Worker responsibilities
A Bun-based worker service:
- polls for queued evaluation jobs (or receives push notifications)
- materializes job inputs (artifacts + payloads) from blob storage
- creates and runs a sandbox (local or Sprite)
- stores results back to Authoring Service
- enforces TTL cleanup and cancellation
- emits Datadog telemetry
B2.6.2 Sprite execution layout
Inside a sprite, the worker places:
/work/job.json
/work/evaluator/
evaluator.ts
runner.ts
/work/out/results.jsonThe worker executes a single command, e.g.:
bun run /work/evaluator/runner.ts /work/job.json /work/out/results.json
Runner contract:
- reads job.json
- imports evaluator.ts
- executes computeModel or computeCalc
- writes results.json (outputs, diffs, trace refs)
B2.6.3 Networking model (keep it simple)
Deal Foundry UI never talks to sprites directly.
- Worker talks to Sprites API
- Worker returns results to Authoring Service
- UI polls
GET /authoring/eval-jobs/{jobId}and.../results
B2.6.4 Cleanup and observability
- Every sprite is tagged with
jobId - TTL default: 30 minutes
- Reaper deletes orphaned sprites
- Datadog metrics: job duration, fixture throughput, failure counts, sandbox timeouts/OOM
B2.6.5 Security posture
- No outbound network from sandboxes
- Artifacts read-only; results are only output channel
- Evaluator code linted for forbidden patterns before execution
B3. Deal Foundry pages (UI requirements)
B3.1 Model Library
Purpose: discover deal types and existing versions; start a new draft.
Features:
- list deal types with:
- active version
- latest draft/approved versions
- owners
- status (active/deprecated)
- quick actions:
- “New version”
- “View versions”
- filters: department/owner/status/search
Acceptance criteria:
- can open an existing model version (read-only)
- can start a new draft from a selected deal type/version baseline
B3.2 New Draft Wizard
Purpose: gather intent + examples + constraints.
Step 1: Deal type + target version
- choose existing deal type or create new
- pick baseline version (optional)
- propose target version (auto-suggest patch/minor based on baseline)
Step 2: Natural language description
- large text area with guidance prompts:
- “What is guaranteed vs contingent?”
- “Payment schedule patterns”
- “Allocation/splits”
- “Eligibility windows”
- “Evidence sources / how to observe triggers”
Step 3: Examples
- upload redacted documents or paste excerpts
- attach structured example bullets
Step 4: Constraints
- required outputs checklist (org default + overrides)
- CP mapping needs (posting hints requirements)
- primitives catalog version (default latest)
Step 5: Generate
- starts
POST /authoring/draftsthenPOST /authoring/drafts/{id}/generate
Acceptance criteria:
- wizard produces a draft id and transitions to Draft Workspace
- examples are stored and referenced, not embedded as massive blobs in DB
B3.3 Draft Workspace (core)
Tabbed workspace:
Tab: Summary (Model Card)
- render
generated_model_cardwith sections:- intent summary
- assumptions
- edge cases
- what is not modeled
- mapping notes for CP
- inline comments per section
- “Regenerate model card” action (does NOT regenerate model unless requested)
Acceptance criteria:
- reviewers can comment and resolve comments
- modeler can regenerate model card without losing prior comment history (comments scoped to paths)
Tab: Model
Two synchronized views:
- canonical JSON (authoritative)
- optional DSL view (if generated)
- schema-aware editor with validation feedback
- “Regenerate model” action with options:
- regenerate from scratch
- regenerate preserving certain blocks (inputs/clauses/events)
DSL correctness requirement (non-negotiable):
- If Deal Foundry displays or edits DSL, it must be the DealModel DSL defined in Appendix C (ANTLR grammar). It must not invent a different “def … = …” language.
- The Compile action must produce compile errors/warnings plus compiled artifacts hash and the Calculation Catalog derived from
computations. - The “Calculations” list shown in the UI must come from compile output (or
/models/.../calculations) and match the model’s computations exactly.
How the compiled DSL parser is used during authoring (required):
Deal Foundry embeds the ANTLR4-generated parser for the DealModel DSL (structural syntax only).
On every save/validate/compile:
- Parse DSL → AST (fail fast on syntax errors with line/column spans)
- Transform AST → canonical model JSON (structural blocks, templates, payment terms, workflow mapping)
- For each embedded CEL expression string, run CEL parse + type-check against bundled input schema and known types (Money/Decimal/Date).
- Emit compile artifacts:
model.json(canonical)ir.codegen.v1.json(typed IR for codegen)calculation_catalog.json+dependency_index.json- schema bundle + refs used
- Run lint gates (model vs instance hygiene, workflow mapping coverage, schema/model consistency)
The parser is strictly an authoring-time validator and transformer. Runtime services consume published artifacts and do not parse DSL.
Recommended UX:
- Provide a “DSL Reference” sheet that shows supported keywords and a small example in our syntax.
Acceptance criteria:
- edits trigger re-validation and compile
- validation report visible (errors/warnings)
Tab: Workflow (deal-level workflowState mapping)
Purpose: define the standardized workflowState vocabulary for this deal type and map each workflowState to Deal Engine canonical state. This is a deal-level global that applies across the entire model (not per-clause).
Calling applications send a single string workflowState. Strict validation is enforced via the input schema enum generated from this mapping.
This is authored as the DSL block:
workflow {
allowed { DRAFT; OFFER_OUT; COUNTER; HOLD; BOOKED; CANCELLED; }
mapping {
DRAFT -> draft;
OFFER_OUT -> negotiating;
COUNTER -> negotiating;
HOLD -> negotiating;
BOOKED -> closed cp_ready: true;
CANCELLED -> cancelled;
}
}UI requirements
- Allowed states editor:
- add/remove/reorder states
- enforce uniqueness
- show “strict enum” warning: apps must only send these values
- Mapping table:
- one row per allowed state
- dropdown: canonical target (draft/negotiating/closed/cancelled)
- toggle:
cp_ready(default false) - lint: block publish if any allowed state lacks a mapping
- Live preview:
- show generated DSL snippet for the workflow block
- show generated JSON Schema snippet for
workflowStateenum
- Validation:
- warn if a state maps to
closedbutcp_ready != true - warn if
cp_ready=trueon a non-closed mapping
- warn if a state maps to
- Diff support:
- show diff vs baseline version (added/removed states; mapping changes)
Acceptance criteria:
- author can create the allowed state list and mapping in < 5 minutes
- on “Apply”, Deal Foundry updates:
- DSL (or canonical model JSON) to include/refresh the workflow block
- input schema to include/refresh the
workflowStateenum
- compile output exposes the workflow definition via:
GET /models/{dealType}/versions/{version}/workflow
Tab: Clause Inventory (domain checklist and coverage driver)
Purpose: help a semi-technical author reason about what clauses exist in a deal domain (TV writer, touring, endorsement, etc.) based on interviews and sample artifacts, and convert that understanding into a curated, versioned checklist that drives schema and contract reconstruction.
This is a product/knowledge layer that sits above primitives:
- Primitives answer “what can we represent?”
- Clause Inventory answers “what do people actually put in contracts in this domain, and what should we capture?”
Features:
Insert from Clause Library: picking a pattern creates a clause with
sourcePatternId+sourcePatternHashprovenance and marks it customized if edited.LLM-proposed inventory from selected docpack blocks and author notes:
- clause families (financial + non-financial)
- typical variants (e.g., tiered bonus, capped participation)
- required vs optional
- “structured vs freeform” recommendation
Author curation
- merge duplicates, rename for clarity, tag by category
- mark “must be reconstructable in contract” for touring and other domains
Links to schema components
- each inventory item can link to a Schema Library component
$ref(or indicate “freeform text”)
- each inventory item can link to a Schema Library component
Coverage view
- % of inventory covered by input schema
- missing fields required to represent a clause family
- contract preview binding coverage (see Contract Preview)
Outputs:
clause_inventory.jsonstored with the draft and included in fixture pack exports.- the inventory does not become “every clause ever”; it is a curated domain profile plus an escape hatch for one-off text clauses.
Acceptance criteria:
- author can produce a clause inventory for a domain in under 30–60 minutes using artifacts + curation
- coverage indicators guide the author to missing schema fields before publish
Tab: Schema (instance inputs; first-class authoring artifact)
Purpose: define and validate the deal instance input schema that downstream apps (CRMs, CP, tools) will use to create and update deals. This is a primary product of authoring, not a derived afterthought.
Features:
Real JSON editor: use
lovasoa/jsonjoy-builder(tree-based JSON builder) for editinginput.schema.jsonandui.schema.jsonwith validation and JSON-pointer context.JSON Schema editor (authoritative)
- schema validation (Draft 2020-12 or newer as standardized)
- lint rules (required fields, enums, numeric ranges, date formats)
- schema formatting + diffs vs baseline version
Form preview
- render the schema as a form using UI schema hints when available
- show field groups, labels, help text, required markers
Sample payload generator
- generate:
- minimal valid payload
- “typical” payload
- edge-case payloads (boundary values)
- generate:
Schema-to-model consistency checks
- highlight model references to missing fields
- highlight unused schema fields
- ensure required fields exist for:
- schedules/payment terms (dates)
- FX/money conversions (currency, fx context)
- event matching (subject refs / IDs)
Auto-propose schema changes
- when user adds a clause/payment term/schedule/event:
- propose needed schema fields (e.g.,
guaranteeAmount,taxRate,shows[])
- propose needed schema fields (e.g.,
- allow user to accept/reject proposals; store decisions in draft history
- when user adds a clause/payment term/schedule/event:
Acceptance criteria:
- modeler can edit schema, see validation errors inline, and preview the resulting form
- changes are diffable vs the prior model version
- fixture generator can consume schema to propose test inputs
- publish gates fail if schema/model references are inconsistent
Schema Components Library (reusable schema composition)
Deal Foundry must expose a Schema Components Library so modelers can compose input schemas from approved, reusable building blocks instead of re-inventing fields per model.
Why:
- improves cross-deal comparability (same “Money” shape everywhere)
- reduces UI drift and speeds modeling
- improves migration safety and compatibility checks
Requirements:
- searchable catalog of components (domain-neutral + domain-specific)
- insert by reference (
$ref) into the draft input schema - show component version and change notes
- warn when mixing incompatible component versions
- “normalize schema” helper:
- suggests replacing inline objects with library refs
- flags near-duplicates (“Money” defined differently than standard)
$ref handling and schema bundling (required)
Deal Foundry must support reusable schema components via $ref, but editors do not resolve refs. Resolution happens in the Authoring Service via a controlled bundling step.
Principle: author schemas with $ref; validate and generate fixtures/forms using a bundled schema with refs resolved.
Allowed $ref forms:
- Local refs within a schema document:
#/$defs/Money - Schema Library refs (preferred):
de://schema-library/<component>@<version>#/<json-pointer>- example:
de://schema-library/lib/common@1.0.0#/Money
- example:
Disallowed (security):
- arbitrary
http(s)://...refs - file system refs outside approved stores
UI requirements:
- Show two views:
- Authored Schema (as edited; contains
$ref) - Bundled Schema (resolved; read-only)
- Authored Schema (as edited; contains
- Provide a “Validate & Bundle” action that:
- resolves refs
- reports unresolved refs with JSON pointer locations
- shows which components/versions were used
- “Copy $ref” actions in Schema Library must generate the
de://schema-library/...form.
Bundling rules (Authoring Service):
- resolve
$refonly through:- the Schema Components Library, and/or
- local
$defswithin the authored schema
- produce a deterministic bundle:
- stable ordering
- no network calls
- record
refs_used[]with component names + versions + hashes
- cache bundles by
sha256(canonical(authoredSchema) + refsUsedHashes)(Appendix F/JCS)
Tooling note:
- In the Node reference implementation, use a resolver/dereferencer (e.g.,
@apidevtools/json-schema-ref-parser) to bundle schemas, but restrict its resolvers to the schema library and local refs only.[8][9]
Tab: Fixtures
- list fixtures with pass/fail badges
- open a fixture:
- inputs
- evidence set
- expected outputs/obligations
- actual outputs/obligations
- structured diff viewer
- actions:
- regenerate fixtures only
- add/edit a fixture manually (power-user)
- mark fixture as required/optional (policy controlled)
Acceptance criteria:
- can run fixtures and see results within the UI
- failing fixture shows exact diff, not just “failed”
Tab: Calculation Workbench (mock values → instant results → promote to fixture)
Purpose: give modelers a tight feedback loop for defining metrics/outputs and immediately testing them against mock values—without constructing full fixtures manually.
This is the fastest way to validate formulas like NBOR, divider tax, split points, versus logic, bonus tiers, and royalty statement math.
Data sources:
- calculations dropdown: Calculation Catalog (
/models/.../calculationsor compile output) - dependency form:
dependencies.inputPaths+dependencies.evidenceKeys
Features:
- Select calculation
- dropdown list of metrics/outputs defined by the model (calculation key, type, description)
- shows dependencies (referenced inputs/evidence) and required evidence keys
- Mock inputs panel
- mode A: “Load from fixture” (pick an existing fixture’s inputs/evidence)
- mode B: “Mock just dependencies” (auto-detect referenced fields and render a minimal form)
- supports money/FX: enforce currency selection and allow injecting FX observations
- Run
- computes the selected metric/output and returns:
- result value
- intermediate values (dependency breakdown)
- warnings (missing evidence, currency mismatch, rounding)
- explain trace link/reference
- computes the selected metric/output and returns:
- Promote
- “Create fixture from this run”:
- generates a new fixture with:
- inputs (full)
- evidence (optional)
- expected.outputs for the selected metric/output
- optional expected.obligations if requested
- generates a new fixture with:
- “Generate boundary variants”:
- creates variants around thresholds/caps/tier edges (Appendix G)
- “Create fixture from this run”:
Acceptance criteria:
- modeler can pick any metric/output and see its value update instantly as they edit mock inputs
- can promote a workbench run into a fixture with one click and then re-run the full fixture suite
- workbench runs are ephemeral (no deal persistence) but are audited
Tab: Compile/Test Results
- compile output summary:
- generated artifacts hashes
- projection hints
- event definitions list
- fixture summary:
- pass rate
- coverage checklist (caps/tiers/rounding/events/windows)
Acceptance criteria:
- shows a “ready for review” banner only when publish gates are satisfied
Tab: Contract Preview (reconstructable contract rendering)
Purpose: verify that the input schema and clause inventory can reproduce a contract-like document from deal data. This is critical for touring and any domain where downstream systems need to generate contracts or “deal memos.”
Features:
- Template editor
- markdown (or doc template) editor with placeholders bound to schema paths and clause inventory items
- “insert binding” picker (schema field, clause item, computed output)
- Rendered preview
- render a contract-like document from:
- a selected fixture, or
- a scenario/workbench payload
- render a contract-like document from:
- Coverage indicators
- missing bindings (placeholders not wired)
- schema fields not used
- clause inventory items not represented
- Export
- export template + a sample payload for downstream contract generation services
Acceptance criteria:
- author can render a reasonable contract preview for a touring fixture from captured inputs and clause texts
- missing-field/binding warnings are visible before publish
Tab: Diff vs baseline
If baselined from an earlier version:
- structured diff of canonical model doc
- diff of outputs/obligation schemas
- compatibility assessment suggestion (compatible vs requires migration)
Acceptance criteria:
- highlights any breaking changes (field removals, obligation semantics changes)
Tab: Sandbox Testing (pre-production validation)
Purpose: test a draft model against production-like data without affecting production.
Data sources:
- Synthetic generator (fixtures + variations)
- Production snapshot (PII-redacted; refreshed weekly)
- User-uploaded cases (CSV/JSON)
Execution:
- select N deals relevant to the deal type
- run compute with draft model
- compare against baseline (prior active model) when upgrading
- produce variance report:
- number of deals changed
- obligations affected
- total amount variance
- new/removed obligations
Regression suites:
- save a sandbox run as a named regression suite
- auto-run on model changes
- block publish if suite fails (configurable)
Acceptance criteria:
- modeler can run against 100+ deals and see a clear variance report
- per-deal drilldown includes explain trace
- no production data is modified
B3.4 Review & Approvals
Purpose: coordinate sign-off.
Features:
- show required approvers by role (config)
- show comment threads and unresolved count
- buttons:
- “Request changes”
- “Approve”
- audit: record who approved what and when
Acceptance criteria:
- cannot approve if there are unresolved “blocking” comments (configurable)
- cannot move to approved unless required approvers completed
B3.5 Publish / Activate
Purpose: submit to Registry and optionally activate.
Inputs:
- compatibility classification:
- compatible / requires_migration / deprecated_only
- if requires_migration:
- attach migration spec (generated or edited)
- attach migration fixtures
- rollout controls:
- allow-listed departments/systems
- activation date/time (optional)
- “default for new deals” toggle
Actions:
- “Submit to Registry” (creates registry draft version)
- “Activate” (if authorized and gates met)
Acceptance criteria:
- activation fails fast if fixtures are missing or migration required but absent
- on success, Registry version is visible in Model Library within 1 minute
B3.6 Primitive Catalog Browser (read-only v1)
Purpose: help modelers understand allowed building blocks.
Features:
- list primitives and ops (money/schedule/allocation/evidence)
- examples + type signatures
- “policy notes” (e.g., rounding rules)
Acceptance criteria:
- modeler can copy an op example into model editor
B4. Deal Foundry APIs (concrete)
Implement these in the Authoring Service (or Authoring + Registry combined for MVP).
Drafts
POST /authoring/drafts- body:
{ dealTypeName, baselineVersion?, targetVersion?, description, examples[], constraints, primitiveCatalogVersion }
- body:
GET /authoring/drafts/{draftId}GET /authoring/drafts?filters...PATCH /authoring/drafts/{draftId}- update description/examples/constraints
POST /authoring/drafts/{draftId}/generate- starts LLM chain + validation; async job
POST /authoring/drafts/{draftId}/regenerate- body:
{ scope: "model|fixtures|model_card|all", preserve?: {...} }
- body:
POST /authoring/drafts/{draftId}/run-fixtures
Evaluation Jobs (authoring-time execution)
Evaluator generation (LLM-assisted; authoring-only)
POST /authoring/drafts/{draftId}/evaluator/generate- body:
{ "mode":"model"|"calculation", "calculationKey"?: "walkout", "fixtureSampleIds"?: [...]} - returns
{ evaluatorBundleHash, jobId? } - implementation may enqueue an evaluation job for verification
- body:
GET /authoring/drafts/{draftId}/evaluator- returns metadata for the latest evaluator bundle (hash, mode, lastVerifiedAt, coverage)
DELETE /authoring/drafts/{draftId}/evaluator- deletes the cached evaluator bundle for the draft (no effect on published artifacts)
POST /authoring/drafts/{draftId}/eval-jobs- creates an evaluation job of type
workbench|scenario|fixtures - body (examples):
- workbench:
{ "type":"workbench", "calculationKey":"walkout", "payload":{...}, "evidenceOverrides":{...}, "asOf":"YYYY-MM-DD" } - scenario:
{ "type":"scenario", "payload":{...}, "evidenceOverrides":{...}, "asOf":"YYYY-MM-DD" } - fixtures:
{ "type":"fixtures", "fixtureIds":[...], "asOf":"YYYY-MM-DD", "shard": { "size": 25 } }
- workbench:
- returns
{ jobId }
- creates an evaluation job of type
GET /authoring/eval-jobs/{jobId}- returns job status:
queued|running|succeeded|failed|cancelledplus timestamps
- returns job status:
GET /authoring/eval-jobs/{jobId}/results- returns results (outputs/obligations/diffs/explainRefs) when complete
POST /authoring/eval-jobs/{jobId}/cancel- cancels a queued/running job and triggers backend cleanup
Backend notes:
Local sandbox backend is acceptable for dev.
Sprites backend is recommended for strong isolation for LLM-generated evaluator code.
POST /authoring/drafts/{draftId}/workbench/run- body:
{ calculationKey, inputs?, evidenceOverrides?, asOf? } - runs the selected metric/output ephemerally and returns:
- result, dependency breakdown, warnings, explainRef
- body:
POST /authoring/drafts/{draftId}/workbench/promote- body:
{ runId, fixtureName, includeObligations?: boolean } - creates a fixture from a workbench run (explicit user confirmation + audit)
- body:
POST /authoring/drafts/{draftId}/fixtures/import- imports a fixture pack (zip or JSON) into the draft
- options:
mode=merge|replacevalidateOnly=true|false
GET /authoring/drafts/{draftId}/fixtures/export- exports the draft’s fixtures (and optional schema/model/docpack refs) as a fixture pack
POST /authoring/drafts/{draftId}/fixtures/promote- promotes “actual results” from a run into
expectedvalues (requires explicit user confirmation + audit)
- promotes “actual results” from a run into
GET /authoring/drafts/{draftId}/schemaGET /authoring/drafts/{draftId}/clause-inventoryGET /authoring/drafts/{draftId}/workflow- returns the draft workflow definition (allowed states + mapping + cp_ready flags)
PUT /authoring/drafts/{draftId}/workflow- updates the workflow definition; server validates:
- unique allowed states
- mapping coverage
- canonical targets are valid
- triggers regeneration of
workflowStateenum in input schema
- updates the workflow definition; server validates:
POST /authoring/drafts/{draftId}/workflow/generate- uses description + clause inventory + (optional) docpacks to propose a first-pass workflow mapping
- returns the current
clause_inventory.jsonfor the draft
PUT /authoring/drafts/{draftId}/clause-inventory- updates the inventory (LLM-proposed patches or manual edits)
POST /authoring/drafts/{draftId}/clause-inventory/generate- uses selected docpack blocks + author notes to propose an inventory draft
POST /authoring/drafts/{draftId}/contract-preview/render- body:
{ templateId?, templateMarkdown?, payloadSource: fixture|workbench|scenario, payloadRef } - returns rendered preview + coverage warnings
- body:
GET /authoring/drafts/{draftId}/contract-preview/templatePUT /authoring/drafts/{draftId}/contract-preview/template- returns current draft input JSON Schema + ui-schema hints (if any)
PUT /authoring/drafts/{draftId}/schema- updates the draft input schema (authoritative)
POST /authoring/drafts/{draftId}/schema/validatePOST /authoring/drafts/{draftId}/schema/bundle- resolves
$ref(schema library + local defs only) and returns:bundledSchema(fully resolved)refsUsed[](component@version + hash)bundleHasherrors[](unresolved refs with JSON pointers)
- resolves
GET /authoring/drafts/{draftId}/schema/bundle- returns the latest cached bundle for the draft (if available)
- validates schema and returns lint + compatibility warnings
POST /authoring/drafts/{draftId}/schema/sample- generates sample payloads: minimal/typical/edge-case
POST /authoring/drafts/{draftId}/submit- submits to Registry as a draft model version
Comments & approvals
POST /authoring/drafts/{draftId}/commentsPATCH /authoring/comments/{commentId}(resolve/unresolve)POST /authoring/drafts/{draftId}/approvals{ role, decision, note }
Jobs (async)
GET /authoring/jobs/{jobId}returns status/progress/logs
B5. Acceptance criteria checklist (minimum viable authoring system)
The authoring system is “done” for v1 when:
- A modeler can produce a draft model from natural language and examples.
- The system validates schema + primitive constraints and compiles deterministically.
- The system generates fixtures, runs them, and displays diffs in UI.
- Reviewers can comment inline, request changes, and approve with audit trail.
- A publisher can submit and activate a model version in the Registry with compatibility classification.
- Activated versions are consumable by Deal API and Runtime.
- Publishing blocks if schema/model references are inconsistent (missing fields, unused required fields, invalid types).
- The system prevents publishing models that would change obligations without:
- declaring
requires_migration, and - providing migration specs + migration fixtures.
- declaring
B6. Suggested “v1 shortcuts” that don’t harm architecture
- Store fixtures and explain traces in Postgres JSONB initially; migrate to object storage later if needed.
- Implement “draft iteration” by creating a new draft row linked via
parent_draft_id. - Start with 2 required approver roles: business_owner + finance_owner; add platform_owner later.
- Limit generated code to a thin wrapper around shared runtime in v1.
B7. UI integration with Registry and Runtime
The console should not call Runtime directly except via Authoring Service orchestration.
Flow:
- UI → Authoring Service (generate/validate/compile/run fixtures)
- Authoring Service → Registry (submit)
- Registry → Runtime/Deal API (serve compiled artifacts)
This ensures a single place enforces publish gates and audit logging.
Appendix C: Optional Deal Model DSL (ANTLR grammar)
The canonical model JSON document is the source of truth. The following DSL is an optional authoring surface that compiles into canonical model JSON.
Authoring-only DSL rule: The DealModel DSL is an authoring surface used in Deal Foundry for humans and review diffs. Published model versions produce canonical artifacts (model JSON, bundled schema, codegen IR, catalogs, fixture packs). Deal programs MUST NOT depend on the DSL parser at runtime.
// DealModel.g4 — Deal Engine model-definition DSL (optional authoring surface)
// Purpose: Human-friendly syntax that compiles to the canonical model JSON document.
// Notes:
// - This DSL is NOT used for deal instances.
// - The compiler must enforce: primitive-catalog constraints, determinism, fixture requirements.
// - The canonical model JSON is the source of truth; this DSL is a "skin".
grammar DealModel;
// ========================= PARSER RULES =========================
model
: modelHeader statement* EOF
;
modelHeader
: MODEL LBRACE DEALTYPE COLON (TEXT | STRING) COMMA VERSION COLON SEMVER
(COMMA PRIMITIVES_VERSION COLON SEMVER)? // optional primitive catalog version
RBRACE
;
statement
: inputsBlock
| overridesBlock
| selectorsBlock
| constraintsBlock
| schedulesBlock
| allocationsBlock
| workflowBlock
| computationsBlock
| variableDecl
| operatorDef
| eventDef
| clauseDef
| blockDef
| projectionsBlock
| typeDef
;
// -------- Inputs / overrides / selectors --------
inputsBlock
: INPUTS LBRACE (
SCHEMA COLON (TRIPLE_STRING | STRING)
| REF COLON STRING
) RBRACE
;
overridesBlock
: OVERRIDES LBRACE TEMPLATES COLON TRIPLE_STRING RBRACE
;
selectorsBlock
: SELECTORS LBRACE selectorEntry+ RBRACE
;
selectorEntry
: ID COLON (STRING | TEXT) SEMI?
;
// -------- Constraints --------
constraintsBlock
: CONSTRAINTS LBRACE ruleDef+ RBRACE
;
ruleDef
: RULE ID COLON boolExpr (SEVERITY COLON severityLevel)? SEMI?
;
severityLevel
: ERROR
| WARNING
;
// -------- Schedules --------
schedulesBlock
: SCHEDULES LBRACE scheduleDef+ RBRACE
;
scheduleDef
: SCHEDULE LBRACE
NAME COLON ID
KIND COLON scheduleKind
(ON COLON expr)? // one_time
(START COLON expr)? (END COLON expr)? // periodic/installments window
(COUNT COLON expr)? // installments count
(EVERY COLON expr)? (UNIT COLON timeUnit)? // periodic cadence
(MILESTONES COLON TRIPLE_STRING)? // JSON/YAML list of milestones (compiler validates)
RBRACE
;
scheduleKind
: ONE_TIME
| INSTALLMENTS
| PERIODIC
| MILESTONE
;
timeUnit
: DAYS
| WEEKS
| MONTHS
| YEARS
;
// -------- Allocations --------
allocationsBlock
: ALLOCATIONS LBRACE allocationDef+ RBRACE
;
allocationDef
: ALLOCATION LBRACE
NAME COLON ID
KIND COLON allocationKind
TO COLON allocationTarget+
RBRACE
;
allocationKind
: SPLIT_PERCENT
| SPLIT_FIXED
| TIER_TABLE
| WATERFALL
;
allocationTarget
: LBRACE
PARTY COLON ID
(PERCENTKW COLON expr)?
(AMOUNTKW COLON expr)?
(ROLE COLON (STRING | TEXT))?
RBRACE
;
// -------- Workflow States (new) --------
//
// Standardized external workflow state for this deal type.
// Calling applications send a single string `workflowState` whose allowed values are defined here.
// The compiler enforces strict mapping coverage and generates the input JSON Schema enum.
//
// Mapping target is a Deal Engine canonical state (draft/negotiating/closed/cancelled)
// with optional cp_ready hint.
//
// Example:
//
// workflow {
// allowed { DRAFT; OFFER_OUT; BOOKED; CANCELLED; }
// mapping {
// DRAFT -> draft;
// OFFER_OUT -> negotiating;
// BOOKED -> closed cp_ready: true;
// CANCELLED -> cancelled;
// }
// }
workflowBlock
: WORKFLOW LBRACE workflowAllowed workflowMapping RBRACE
;
workflowAllowed
: ALLOWED LBRACE allowedState+ RBRACE
;
allowedState
: (ID | STRING) SEMI?
;
workflowMapping
: MAPPING LBRACE workflowMapEntry+ RBRACE
;
workflowMapEntry
: (ID | STRING) ARROW canonicalState (CP_READY COLON (TRUE | FALSE))? SEMI?
;
canonicalState
: CANON_DRAFT
| CANON_NEGOTIATING
| CANON_CLOSED
| CANON_CANCELLED
;
// -------- Computations / variables --------
computationsBlock: COMPUTATIONS LBRACE computationEntry+ RBRACE;
computationEntry
: metricSpec
| outputSpec
;
metricSpec
: METRIC ID LBRACE computationField+ RBRACE
;
outputSpec
: OUTPUT ID LBRACE computationField+ RBRACE
;
computationField
: TYPEKW COLON computationValueType SEMI?
| DISPLAY COLON (TEXT | STRING) SEMI?
| DESCRIPTION COLON (TEXT | STRING | TRIPLE_STRING) SEMI?
| DEPENDS_ON_INPUTS COLON stringList SEMI?
| DEPENDS_ON_METRICS COLON stringList SEMI?
| INVARIANTS COLON stringList SEMI?
| EXAMPLES COLON TRIPLE_STRING SEMI?
;
computationValueType
: NUMBER_T
| MONEY_T
| INT_T
| BOOL_T
| STRING_T
| OBJECT_T
;
stringList
: LBRACK (stringAtom (COMMA stringAtom)*)? RBRACK
;
stringAtom
: STRING
| ID
;
metricDef
: METRIC ID ASSIGN expr SEMI?
;
outputDef
: OUTPUT ID ASSIGN expr SEMI?
;
variableDecl
: VAR ID (ASSIGN expr)? SEMI?
;
funcCall
: (ID | AMOUNTKW | MONEYKW | PERCENTFN | EVIDENCEKW | CLAUSE_AMOUNTKW) LPAREN (argList)? RPAREN
;
argList
: expr (COMMA expr)*
;
// Boolean expressions (constraints/events/term triggers)
boolExpr
: boolExpr ANDAND boolExpr
| boolExpr OROR boolExpr
| NOT boolExpr
| TRUE
| FALSE
| expr CMP expr
| LPAREN boolExpr RPAREN
;
// -------- Operators --------
operatorDef
: OPERATOR LBRACE
NAME COLON ID
DISPLAY COLON (TEXT | STRING)
TYPEKW COLON operatorType
RBRACE
;
operatorType
: AND
| OR
;
// -------- Events --------
eventDef
: EVENT LBRACE
NAME COLON ID
DESCRIPTION COLON (TEXT | STRING)
SOURCE COLON (TEXT | STRING)
(MATCH COLON (TRIPLE_STRING | STRING))?
CONDITION COLON boolExpr
RBRACE
;
// -------- Payment Terms (new) --------
paymentTermsBlock
: PAYMENT_TERMS LBRACE paymentTermDef+ RBRACE
;
paymentTermDef
: TERM LBRACE
NAME COLON ID
(DESCRIPTION COLON (TEXT | STRING))?
(INITIAL_STATUS COLON termStatus)?
(PAYMENT_TYPE COLON paymentType)?
AMOUNTKW COLON termAmountSpec
(TRIGGER COLON boolExpr)?
DUE_DATE COLON dueDateSpec
(PAYMENT_METHOD COLON paymentMethod)?
(DESTINATION_REF COLON (ID | STRING))?
(SPECIAL_CONDITIONS COLON (TRIPLE_STRING | STRING))?
RBRACE
;
termStatus
: PENDING
| SCHEDULED
| READY_TO_PAY
| PAID
| CANCELLED
;
paymentType
: LUMP_SUM
| INSTALLMENT
| RECURRING
| HOLDBACK
| ADVANCE
;
paymentMethod
: ACH
| CHECK
| WIRE
| CASH
| OTHER
;
termAmountSpec
: FIXED expr
| PERCENTAGE expr
| CALCULATED expr
;
dueDateSpec
: ABSOLUTE DATE
| RELATIVE LBRACE TRIGGER COLON boolExpr COMMA OFFSET_DAYS COLON expr RBRACE
| BY_SCHEDULE ID
| RULE_BASED (TRIPLE_STRING | STRING)
;
// -------- Clauses / Blocks / Type --------
clauseDef
: CLAUSE LBRACE
NAME COLON ID
KIND COLON clauseKind
DESCRIPTION COLON (TEXT | STRING)
(WHEN COLON ID)?
(AMOUNTKW COLON expr)?
(PAYABLE COLON payableSpec)?
(BY_SCHEDULE COLON ID)?
(ALLOCATE COLON ID)?
(paymentTermsBlock)?
(TEMPLATE COLON TRIPLE_STRING)?
RBRACE
;
clauseKind
: SIMPLE
| GUARANTEE
| CONTINGENT
;
payableSpec
: ON ID
| BY_SCHEDULE ID
;
// Clause Blocks
blockDef
: BLOCK LBRACE
NAME COLON ID
EXPR COLON clauseExpr
(WHEN COLON ID)?
(TEMPLATE COLON TRIPLE_STRING)?
RBRACE
;
clauseExpr
: clauseTerm (ID clauseTerm)*
;
clauseTerm
: ID
| LPAREN clauseExpr RPAREN
;
// Projections (read-model hints)
projectionsBlock
: PROJECTIONS LBRACE projectionField+ RBRACE
;
projectionField
: FIELD (STRING | TEXT) (AS ID)? SEMI?
;
// Deal Type (composition root)
typeDef
: TYPEKW LBRACE
NAME COLON ID
COMPOSE COLON clauseExpr
(TEMPLATE COLON TRIPLE_STRING)?
RBRACE
;
// ========================= LEXER RULES =========================
// Header keywords
MODEL: 'model';
DEALTYPE: 'deal_type';
VERSION: 'version';
PRIMITIVES_VERSION: 'primitive_catalog_version';
// Primitives / blocks
INPUTS: 'inputs';
OVERRIDES: 'overrides';
SELECTORS: 'selectors';
CONSTRAINTS: 'constraints';
SCHEDULES: 'schedules';
SCHEDULE: 'schedule';
ALLOCATIONS: 'allocations';
ALLOCATION: 'allocation';
COMPUTATIONS: 'computations';
VAR: 'var';
OPERATOR: 'operator';
EVENT: 'event';
CLAUSE: 'clause';
BLOCK: 'block';
TYPEKW: 'type';
PROJECTIONS: 'projections';
// Workflow states
WORKFLOW: 'workflow';
ALLOWED: 'allowed';
MAPPING: 'mapping';
CP_READY: 'cp_ready';
// Canonical Deal Engine states (targets for mapping)
CANON_DRAFT: 'draft';
CANON_NEGOTIATING: 'negotiating';
CANON_CLOSED: 'closed';
CANON_CANCELLED: 'cancelled';
// Payment terms
PAYMENT_TERMS: 'payment_terms';
TERM: 'term';
INITIAL_STATUS: 'initial_status';
PAYMENT_TYPE: 'payment_type';
DUE_DATE: 'due_date';
TRIGGER: 'trigger';
PAYMENT_METHOD: 'payment_method';
DESTINATION_REF: 'payment_destination_input_ref';
SPECIAL_CONDITIONS: 'special_conditions';
FIXED: 'fixed';
PERCENTAGE: 'percentage';
CALCULATED: 'calculated';
ABSOLUTE: 'absolute';
RELATIVE: 'relative';
RULE_BASED: 'rule_based';
OFFSET_DAYS: 'offset_days';
PENDING: 'pending';
SCHEDULED: 'scheduled';
READY_TO_PAY: 'ready_to_pay';
PAID: 'paid';
CANCELLED: 'cancelled';
LUMP_SUM: 'lump_sum';
INSTALLMENT: 'installment';
RECURRING: 'recurring';
HOLDBACK: 'holdback';
ADVANCE: 'advance';
ACH: 'ach';
CHECK: 'check';
WIRE: 'wire';
CASH: 'cash';
OTHER: 'other';
// Fields
SCHEMA: 'schema';
REF: 'ref';
TEMPLATES: 'templates';
NAME: 'name';
DISPLAY: 'display';
DESCRIPTION: 'description';
DEPENDS_ON_INPUTS: 'depends_on_inputs';
DEPENDS_ON_METRICS: 'depends_on_metrics';
INVARIANTS: 'invariants';
EXAMPLES: 'examples';
NUMBER_T: 'number';
MONEY_T: 'money';
INT_T: 'int';
BOOL_T: 'bool';
STRING_T: 'string';
OBJECT_T: 'object';
KIND: 'kind';
WHEN: 'when';
AMOUNTKW: 'amount';
PAYABLE: 'payable';
ON: 'on';
BY_SCHEDULE: 'by_schedule';
CONDITION: 'condition';
EXPR: 'expr';
COMPOSE: 'compose';
TEMPLATE: 'template';
SOURCE: 'source';
MATCH: 'match';
FIELD: 'field';
AS: 'as';
SEVERITY: 'severity';
RULE: 'rule';
START: 'start';
END: 'end';
COUNT: 'count';
EVERY: 'every';
UNIT: 'unit';
MILESTONES: 'milestones';
TO: 'to';
PARTY: 'party';
ROLE: 'role';
// Clause kinds
SIMPLE: 'simple';
GUARANTEE: 'guarantee';
CONTINGENT: 'contingent';
// Schedule kinds
ONE_TIME: 'one_time';
INSTALLMENTS: 'installments';
PERIODIC: 'periodic';
MILESTONE: 'milestone';
// Allocation kinds
SPLIT_PERCENT: 'split_percent';
SPLIT_FIXED: 'split_fixed';
TIER_TABLE: 'tier_table';
WATERFALL: 'waterfall';
// Time units
DAYS: 'days';
WEEKS: 'weeks';
MONTHS: 'months';
YEARS: 'years';
// Computations
METRIC: 'metric';
OUTPUT: 'output';
// Severity
ERROR: 'error';
WARNING: 'warning';
// Logical
ANDAND: '&&';
OROR: '||';
NOT: '!';
TRUE: 'true';
FALSE: 'false';
// Operators
IN: 'in';
AND: 'and';
OR: 'or';
// Intrinsics
MONEYKW: 'money';
PERCENTFN: 'percent';
EVIDENCEKW: 'evidence';
CLAUSE_AMOUNTKW: 'clause_amount';
// Punctuation
LBRACE: '{';
RBRACE: '}';
COLON: ':';
COMMA: ',';
SEMI: ';';
ASSIGN: '=';
LPAREN: '(';
RPAREN: ')';
ARROW: '->';
// Math / compare
ADD: '+';
SUB: '-';
MUL: '*';
DIV: '/';
CMP: '==' | '!=' | '>=' | '<=' | '>' | '<';
// Identifiers
ID: [a-zA-Z_][a-zA-Z_0-9]*;
// Semantic version
SEMVER: INT '.' INT '.' INT ( '-' PRERELEASE)? ( '+' BUILD)?;
fragment INT: '0' | [1-9][0-9]*;
fragment PRERELEASE: IDENT ('.' IDENT)*;
fragment BUILD: IDENT ('.' IDENT)*;
fragment IDENT: [0-9A-Za-z-]+;
// Dates (must appear before TEXT so "2025-12-15" lexes as DATE, not TEXT)
DATE: [0-9]{4} '-' [0-9]{2} '-' [0-9]{2};
// Numbers
NUMBER: [0-9]+ ('.' [0-9]+)?;
// Strings
TRIPLE_STRING: '"""' ( . | '\r' | '\n')*? '"""';
STRING: '"' ( '\\' . | ~["\\\r\n])* '"';
TEXT: (
~[{}(),:="\r\n \u00A0\u1680\u2000-\u200A\u202F\u205F\u3000]
)+;
// Whitespace & Comments
fragment UNI_WS_CHAR:
'\u0009'
| '\u000B'
| '\u000C'
| '\u0020'
| '\u00A0'
| '\u1680'
| '\u2000' ..'\u200A'
| '\u202F'
| '\u205F'
| '\u3000';
WS: (UNI_WS_CHAR | '\r' | '\n')+ -> skip;
HASH_COMMENT: '#' ~[\r\n]* -> skip;
LINE_COMMENT: '//' ~[\r\n]* -> skip;
BLOCK_COMMENT: '/*' .*? '*/' -> skip;Appendix D: Using sample contracts in authoring (document ingestion + understanding without SaaS)
Yes: the Authoring system must accept sample contracts (PDF/DOCX/text) as inputs to the model drafting process. This appendix specifies how to do that safely and effectively without requiring third-party SaaS document tools.
D1. What “sample contracts as inputs” means
The authoring workflow should support attaching documents to a draft, then using those documents to:
- extract candidate clauses, definitions, and schedules
- find payment triggers and contingent terms
- identify key variables to become model inputs
- generate test fixtures that reflect real-world patterns
Important: contracts are inputs to model authoring, not to runtime computation.
D2. Document security and storage
D2.1 Document policy
- Prefer redacted contracts as inputs whenever possible.
- If unredacted documents are required:
- store them encrypted at rest
- restrict access via AuthZ policies
- log access in the audit log
- allow per-document retention rules
D2.2 Document store (build)
Implement a small internal “Document Store” module/service (can live inside Authoring Service for MVP):
Table:
de_authoring_document
id uuid pkdraft_id uuid fk de_authoring_draft(id)filename text not nullcontent_type text not null(application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document, text/plain)size_bytes bigint not nullsha256 text not null(dedupe)storage_ref text not null(file path or object storage key)redaction_status text not null(unknown/redacted/unredacted)uploaded_by text not nulluploaded_at timestamptz not null
For MVP you can store binaries on disk with storage_ref pointing to a path; for production use object storage.
D3. Document understanding pipeline (no SaaS)
This is the recommended on-prem / self-hostable pipeline:
- Ingest (store file + metadata)
- Extract text + layout (PDF/DOCX parsing)
- OCR fallback (only if scanned images)
- Structure detection
- headings/sections
- clause numbering
- tables (payment schedules, tier tables)
- Chunking
- chunk by clause/section
- preserve page/line provenance
- LLM-assisted synthesis
- summarize clauses
- propose variables + triggers + schedules
- map to primitives
- Human selection
- modeler selects which extracted clauses to use
- Generate model + fixtures + model card
D3.1 Extraction libraries (self-hostable)
PDFs (text + layout)
- PyMuPDF (fitz): fast text extraction with coordinates (layout-aware)
- pdfplumber: good for text + tables with layout cues
- pypdf: basic extraction; good for metadata
DOCX
- python-docx: reliable DOCX text extraction + paragraph structure
OCR (for scanned PDFs)
- Tesseract OCR (self-hosted)
- Optional: OpenCV preprocessing (deskew/denoise) to improve OCR quality
Tables
- camelot (PDF tables; works well for lattice tables)
- tabula-java (also effective; requires JVM)
D3.2 Output format: “docpack”
All downstream steps (LLM prompts, UI display) should consume a single normalized JSON structure:
{
"documentId": "uuid",
"source": {"filename": "...", "pages": 42},
"blocks": [
{
"blockId": "uuid",
"type": "clause",
"title": "Backend Bonus",
"number": "3.2(b)",
"text": "…",
"provenance": {"pageStart": 12, "pageEnd": 13, "bbox": [[x1,y1,x2,y2]]}
}
],
"tables": [
{
"tableId": "uuid",
"caption": "Payment Schedule",
"rows": [["Milestone","Amount","Due"], ["On signing","$100,000","…"]],
"provenance": {"page": 7}
}
],
"definitions": [
{"term": "Net Profits", "definition": "...", "provenance": {"page": 3}}
]
}This can be produced deterministically without SaaS.
D4. Authoring UI additions (required)
Add to Draft Workspace:
D4.1 Tab: Documents
upload documents (PDF/DOCX/TXT)
show extraction status:
- extracted text OK
- OCR required
- tables detected
viewer:
- page thumbnails
- extracted clause list
- click clause → show text + provenance (page)
“Select excerpts” feature:
- modeler selects clauses/snippets and tags them:
- guarantee
- schedule
- contingent trigger
- allocation/split
- definitions
- modeler selects clauses/snippets and tags them:
“Generate from selection” action:
- feed only selected excerpts into the LLM chain to reduce noise and improve accuracy
Human-in-the-loop correction:
- allow the user to edit extracted clause text (especially OCR)
- allow re-chunking (merge/split blocks)
- mark blocks as “ignore” or “authoritative”
- re-run extraction on demand and preserve edit history Acceptance criteria:
modeler can upload a PDF, see extracted clause blocks, and include selected blocks in generation.
D5. Authoring Service API additions (required)
Documents
POST /authoring/drafts/{draftId}/documents(multipart upload)GET /authoring/drafts/{draftId}/documentsGET /authoring/documents/{documentId}POST /authoring/documents/{documentId}/extract- triggers extraction pipeline → produces docpack JSON
GET /authoring/documents/{documentId}/docpack
Generation using documents
Extend generation request to allow:
POST /authoring/drafts/{draftId}/generate- body includes either:
useDocuments: true(use all docpacks)- or
selectedBlockIds: [...](preferred) - or
excerpts: [...](manual paste)
- body includes either:
D6. LLM prompt strategy for contract-informed authoring
Use a two-stage approach:
Clause classification + variable harvesting
- input: docpack blocks (selected)
- output:
- candidate variables (inputs) with types
- candidate schedules (dates, cadence)
- candidate contingencies (conditions, windows)
- candidate allocation patterns
Model synthesis
- input: the structured harvest output + primitive catalog
- output: canonical model doc + fixtures + model card
This reduces hallucination risk versus giving the LLM raw PDFs.
D7. Practical quality measures (so it works in real life)
- Prefer layout-aware extraction (coordinates) so clause boundaries are accurate.
- Chunk by clause numbering where possible (regex + heading detection).
- Keep provenance on every block so humans can validate quickly.
- Never rely on OCR if text exists; OCR is slower and noisier.
- Maintain a “bad extraction” remediation path:
- allow manual paste of key clauses
- allow manual fixture authoring
D8. Why this satisfies the “no third-party SaaS” constraint
Everything here can be run:
- inside your infra
- using open-source libraries for parsing/OCR/tables
- with your chosen LLM deployment (self-hosted model or an enterprise-approved hosted LLM)
No external document SaaS is required for extraction, structure, or storage.
Appendix E: Primitive semantics (Money and FX)
This appendix makes “Money” behavior explicit so all services (compiler, runtime, fixtures) agree.
E1. Money type
Structure:
{ amount: Decimal, currency: ISO_4217 }
Representation rules:
amountmust be stored as a decimal string in JSON (never a binary float).- Runtime uses a decimal library (arbitrary precision) and rounds only at explicit boundaries.
E2. Money operations
add(Money, Money) -> Money- throws error if currencies differ
sub(Money, Money) -> Money- throws error if currencies differ
mul(Money, Percent|Number) -> Moneyround(Money, policy) -> Moneyconvert(Money, targetCurrency, rate) -> Money- returns new Money in
targetCurrency - rate must be supplied by input or evidence (see 6.3.2)
- conversion must record:
- base/quote
- rate value
- as-of date
- evidence observation id (if from evidence)
- returns new Money in
E3. FX rate primitive
FX rate structure:
{ base: ISO_4217, quote: ISO_4217, rate: Decimal, asOfDate: YYYY-MM-DD }
Allowed sources:
- internal finance-approved provider
- manual override (with audit + permissions)
Determinism requirement:
- FX rates must be present in the snapshot evidence set or in inputs; never fetched at compute time.
Appendix F: Language-agnostic correctness contracts (canonical JSON, hashing, decimals)
This appendix defines the cross-language invariants that must hold if services are implemented in different languages (Go, Node.js, etc.). Coding agents must treat this as normative.
F1. Canonical JSON (normalization)
Many keys in Deal Engine depend on deterministic hashing of structured inputs (e.g., compute fingerprints, obligationKey derivation inputs, audit hash chaining metadata). This requires a single canonical JSON representation.
F1.1 Canonicalization rules
Given an input JSON value, canonicalization produces a UTF-8 string:
- Object key ordering
- Sort object keys lexicographically by Unicode code points (bytewise).
- No insignificant whitespace
- No extra spaces, newlines, or indentation.
- Stable string escaping
- Use JSON standard escapes; do not escape characters unnecessarily.
- Arrays preserved
- Array element order is preserved exactly.
- Numbers
- Do not encode binary floats in canonical JSON for money/FX.
- Where numeric values are part of deterministic keys, represent decimals as strings (see F3).
- Null/booleans
- Standard JSON encodings.
F1.2 Recommendation: adopt RFC 8785 (JCS)
If feasible, implement JSON Canonicalization Scheme (JCS, RFC 8785) for canonicalization. If not, implement the above rules and standardize test vectors in the repo.
F1.3 Test vectors (required)
Provide a shared corpus of canonicalization fixtures:
- input JSON
- expected canonical string
- expected sha256 digest
All languages must pass these tests.
F2. Hashing conventions
F2.1 Hash algorithm
- SHA-256 over UTF-8 bytes of canonicalized strings.
F2.2 Encoding
- Hex lowercase encoding for stored hashes.
F2.3 Where hashes are used
model_hashevidence_set_hash- compute cache keys
- obligationKey components hashing
- audit log
entry_hashchain - legacy raw record
payload_hash
F3. Decimal and money determinism (no floats)
F3.1 Decimal representation in JSON
- Money amounts, FX rates, and any finance-critical numeric must be represented as decimal strings in JSON:
"100000.00"not100000.00(number type)
- This avoids language-specific float encoding differences.
F3.2 Decimal arithmetic
All finance-critical arithmetic must use arbitrary-precision decimal libraries:
- Go:
shopspring/decimal(or equivalent) - Node.js:
decimal.js(or equivalent)
F3.3 Rounding
Rounding must be explicit and versioned (primitive catalog / policy):
- rounding mode (half-up, bankers, etc.)
- precision (cents, mills, etc.)
- rounding boundary (per operation vs end-of-compute)
Fixtures must cover rounding behavior to prevent cross-language drift.
F4. Obligation key determinism (cross-language)
Obligation keys must be derived from canonical inputs:
Recommended:
- build a small JSON object with the fields that define obligation identity:
modelClauseIdscheduleOccurrenceIdcounterpartyRefcurrencyallocationGroupId(if identity depends on it)
- canonicalize → sha256 → hex
This avoids subtle string concatenation differences across languages.
F5. Compute fingerprint determinism (cross-language)
Compute fingerprint inputs:
model_hashprimitive_catalog_version- canonicalized
deal_revision.inputs evidence_set_hash- canonicalized
asOftimestamp format (ISO-8601 UTC)
The canonical timestamp format is:
YYYY-MM-DDTHH:MM:SS.sssZ(UTC, always with milliseconds)
F6. Golden compliance suite (required)
Create a shared compliance suite repo folder:
compliance/canonicalization/*.jsoncompliance/money_rounding/*.jsoncompliance/obligation_keys/*.jsoncompliance/compute_fingerprints/*.json
Every service language implementation must run this suite in CI. This is the mechanism that makes the architecture truly language-agnostic without losing finance-grade correctness.
Appendix G: Model validation and testing strategy (authoring-time + runtime + generated code)
This appendix closes the gap on how users know a model is correct and how we automatically test both compiled models and any generated deal program/service code.
G1. What “correct” means (test oracle problem)
A deal model is “correct” when:
- It matches the intended business semantics for a deal type.
- It produces stable, deterministic outputs (same inputs/evidence ⇒ same outputs).
- It produces obligations/schedules/allocations that reconcile with Client Processing expectations.
- It behaves reasonably across edge cases (rounding, caps, tier boundaries, missing evidence windows).
- It remains correct as the model evolves (regression safety).
Because “correctness” often lacks a single oracle, we use multiple complementary test oracles:
- golden fixtures (example-based)
- invariants/properties (property-based)
- differential checks (compare against baseline model or reference spreadsheet)
- metamorphic tests (inputs change → predictable relationship holds)
Deal Foundry includes a Calculation Workbench tab that lets modelers test individual metrics/outputs with mock values and promote runs into fixtures.
G2. Authoring-time validation (“while users are describing the deal”)
Yes: we should validate continuously during authoring, not only at publish time.
G2.1 Validation loop in the Deal Foundry
Add a persistent “Validation” panel in Draft Workspace that runs on every meaningful change:
- Schema validation
- canonical model doc conforms to canonical schema
- input schema compiles and is internally consistent
- Primitive-catalog lint
- unknown ops/types rejected
- money operations enforce currency rules
- disallow nondeterministic calls
- Static semantic checks (compiler)
- unused inputs
- unreachable clauses / dead events
- missing required projection fields (org policy)
- schedule feasibility (no negative dates; required anchor dates exist)
- allocation feasibility (percents sum to 1; waterfall constraints coherent)
- Constraint satisfiability hints
- detect constraints that can never be satisfied (simple SAT-style checks where possible)
- Event/watch validity
- every contingent event has source + match spec
- evidence keys exist in canonical source schema (adapter layer)
- Obligation identity stability preview
- show computed
obligationKeyformula inputs and confirm they are stable identifiers
- show computed
UI: show errors/warnings grouped by:
- schema
- compilation
- semantics
- policy/gov gates
G2.2 Partial (“incremental”) model construction
When using LLM-driven authoring, the service should build the model in stages and validate after each:
- Stage A: inputs schema + core outputs
- Stage B: guarantees + schedules
- Stage C: allocations/splits
- Stage D: contingencies (events + windows)
- Stage E: projections + CP posting hints
The authoring tool can ask clarifying questions only when validation requires it (e.g., “what is the term end date field for watch expiry?”).
G2.3 Live scenario runner (interactive)
Provide a “Scenario Runner” tab:
- user enters a small input JSON payload (or selects a fixture)
- optionally enters evidence observations (including FX rates)
- runtime computes outputs + obligations
- UI shows:
- outputs
- obligation schedule
- explain trace with step-by-step values
This is how business users build confidence.
G3. Auto-generating tests (fixtures) for compiled models
G3.1 Fixture generation sources
Fixtures should be generated from multiple sources:
- From user-provided examples
- sample contracts/term sheets
- contract docpacks (Appendix D)
- known deal spreadsheets
- From the model structure
- boundary cases at tier thresholds and caps/floors
- schedule edges (first/last installment, leap months)
- currency conversions (fixed vs evidence FX)
- From invariant templates
- allocation sums
- payout monotonicity with key drivers
- guarantee vs versus/plus logic invariants
G3.2 Minimum fixture requirements (publish gate)
For any model version eligible for canary or active:
- at least 15 fixtures
- must include:
- 2 “happy path” fixtures
- 3 boundary fixtures (tier edges, caps/floors, max/min)
- 3 schedule fixtures (multiple due dates)
- 3 allocation fixtures (splits/waterfalls if present)
- 2 contingency fixtures (activate + not activate)
- 2 rounding/decimal fixtures
These are enforced by the Registry publish gate (Band 1/2).
G3.3 Programmatic fixture expansion (“fuzz around edges”)
After the LLM produces initial fixtures, the Authoring Service expands them programmatically:
- choose key numeric parameters
- generate variants around boundary points:
x-ε,x,x+ε - vary tax rate near 0, near typical, and high but plausible
- vary artist percentage
- vary expenses up/down
This catches off-by-one and threshold mistakes.
G4. Property-based tests (invariants)
These tests do not need expected numeric outputs; they verify relationships that must always hold.
Examples (touring/box office style deals):
- NBOR:
NBOR = GBOR - (SalesTax + FacilityFees)- invariant:
NBOR <= GBOR
- invariant:
- Divider tax:
Tax = Price - Price/(1 + TaxRate)- invariant: if
TaxRate = 0,Tax = 0 - invariant:
0 <= Tax <= Price
- invariant: if
- Versus (net):
Payout = max(Guarantee, (NBOR - Expenses) * Artist%)- invariant:
Payout >= Guarantee - monotonic: increasing expenses should not increase payout (holding others constant)
- monotonic: increasing NBOR should not decrease payout
- invariant:
- Allocations:
- sum(allocation amounts) == gross amount (within rounding tolerance)
- Schedule:
- due dates non-decreasing
- amounts non-negative
Property tests are especially valuable when migrating legacy data where expected outputs are unknown.
G5. Differential / regression testing (against baseline)
When creating a new model version from a baseline:
- Run the draft model against a sandbox corpus (Appendix B: Sandbox Testing).
- Compare obligation deltas and key outputs vs the prior active model.
Publish gate:
- if obligations change materially and the change is not explicitly classified (
requires_migration,correction), block publish.
This prevents “surprise money changes.”
G6. Spreadsheet-backed golden tests (recommended for touring)
For deals with well-established spreadsheet formulas (like the attached touring/box-office formulas), support importing spreadsheet-style golden cases.
Mechanism:
- upload CSV containing:
- named inputs (GBOR, taxRate, facilityFees, expenses, guarantee, artistPct, tier tables)
- expected outputs (NBOR, Tax, SplitPoint, Payout)
- Authoring Service converts rows into fixtures automatically.
This yields a strong oracle with minimal effort from business users.
Example: touring/box-office fixture (illustrative)
Inputs:
GBOR = 1,000,000salesTax = 80,000facilityFees = 20,000expenses = 300,000guarantee = 200,000artistPct = 0.85taxRate = 0.10
Expected:
NBOR = 900,000dividerTax = 1,000,000 - (1,000,000 / 1.10) = 90,909.0909...versusNetPayout = max(200,000, (900,000 - 300,000) * 0.85) = 510,000
The fixture runner should use Decimal arithmetic and explicit rounding rules for display vs posting.
G7. Testing generated deal programs/services
If Deal Engine generates a “deal program/service” (thin wrapper or specialized runtime), we must guarantee it produces the same results as the compiled model artifacts.
G7.1 Golden fixture conformance (required)
- Generated code must run the same fixture suite.
- CI gate: generated service outputs must match:
- outputs
- obligations
- contingency state
- obligationKeys
- If there is drift, publishing is blocked.
G7.2 Cross-language compliance suite (ties to Appendix F)
Generated code must also pass:
- canonical JSON / hashing test vectors
- decimal money rounding test vectors
- obligationKey and compute fingerprint tests
This ensures Go vs Node implementations do not diverge.
G7.3 Explain trace parity
Generated services must return explain traces compatible with the platform:
- node types and fields standardized
- include observation ids for evidence and FX
- include rounding/FX policy references
If the generated service calls the shared runtime, trace parity is automatic.
G8. Where this fits in the phased plan
- Band 0: compliance suite + primitive unit tests + deterministic replay tests
- Band 1: authoring-time validation panel + fixtures runner UI
- Band 2: contingency fixtures (activate/not) + evidence adapters and overrides tests
- Band 3: amendment/correction delta tests against pinned CP baselines
- Band 5: large sandbox corpuses and regression suites at scale
Appendix H: Fixture Packs (import/export) for model authoring and CI
Fixtures already exist in the system as individual test cases attached to model versions. A fixture pack makes fixtures portable so teams can:
- seed authoring drafts with known good cases (endorsement/touring/movie examples)
- run model regression in CI without custom scripts
- share test suites across departments
- bootstrap expected outputs from spreadsheets safely
H1. Fixture pack goals
- Portable: can be shared as a single artifact (zip or JSON).
- Deterministic: includes everything needed to run fixtures (inputs + evidence + expected).
- Auditable: import and “promote actual → expected” are logged.
- Extensible: supports adding documents/docpacks and schema/model refs without breaking older packs.
H2. Fixture pack formats
Two supported encodings:
H2.1 fixturepack.json (single file)
Used for lightweight packs and APIs.
Top-level shape:
dealTypemodelVersion(optional; may be blank for drafts)primitiveCatalogVersion(optional)inputSchema(optional; may be omitted if provided separately)fixtures[]:nameinputs(JSON)evidence(JSON, optional)expected.outputs(JSON)expected.obligations(JSON)expected.contingencies(JSON, optional)notes(optional)
documents[](optional; references only, or embedded if small)metadata(owner, createdAt, source)
H2.2 Zip bundle (recommended for real usage)
Used for larger packs and to include documents.
Folder convention:
fixturepack/
draft.json # optional: seed draft info (deal type, description, constraints)
input.schema.json # optional: instance input schema
ui.schema.json # optional: UI hints
model.json # optional: canonical model doc (draft stage)
dsl.dealmodel # optional: DSL source
fixtures/
001_name.json # each includes inputs/evidence/expected
002_name.json
documents/
contract_1.pdf
term_sheet.docx
docpacks/
contract_1.docpack.json
README.mdH3. Import/export semantics
H3.1 Import modes
merge(default): add new fixtures; overwrite by fixture name if samereplace: replace the draft’s fixture set entirelyvalidateOnly: parse + validate + report issues without persisting
Validation performed on import:
- input schema validates inputs
- expected outputs/obligations validate against model’s published shapes (if model present)
- evidence schema validates (if evidence keys are known)
H3.2 Export contents
Draft export may include:
- fixtures
- input schema + UI schema
- model doc / DSL source (optional)
- docpack refs (optional)
Registry export (model version export) includes:
- fixtures + expected results
- model version hash + primitive catalog version
- projection hints + calculation list
H4. Promotion workflow (“actual → expected”) with safeguards
Early in a model’s life, you may import fixtures that have inputs but incomplete expected values. The system may allow promotion:
- Run fixtures and compute actual outputs/obligations.
- For each fixture:
- show a diff (expected vs actual)
- require a human to click “Promote actual to expected”
- Record audit entry:
- who promoted
- when
- which model hash
- before/after snapshot of expected values
Rule: promotion is allowed only for drafts (not for active model versions) unless explicitly enabled for controlled workflows.
H5. APIs (authoring and registry)
Authoring Service (draft stage)
POST /authoring/drafts/{draftId}/fixtures/importGET /authoring/drafts/{draftId}/fixtures/exportPOST /authoring/drafts/{draftId}/fixtures/promote
Registry (model version stage; admin/publisher)
POST /deal-types/{dealType}/versions/{version}/fixtures/importGET /deal-types/{dealType}/versions/{version}/fixtures/export
H6. Why fixture packs matter for your pilot deals
Your endorsement/touring/movie examples can be shipped as fixture packs:
- seed the draft with schema + starter fixtures
- let modelers iterate in the authoring console
- run packs in CI as golden regressions for each model version
Appendix I: Claude Opus integration for Authoring (tool loop + UX mock + generated artifacts)
This appendix makes the Claude integration concrete: how the UI and Authoring Service orchestrate Claude Opus, what tools exist, what prompts look like, and what artifacts are produced.
I1. Components
- Deal Foundry (UI): collects user intent, documents, schema edits; displays diffs, fixtures, traces.
- Authoring Service (API): owns draft persistence, validation, compile/run-fixtures, and Claude orchestration.
- Claude Opus: drafts artifacts in structured JSON with tool calls for extraction/validation/test.
- Runtime (compiler/evaluator): compiles models to IR and runs fixtures deterministically.
- Document pipeline: produces
docpackJSON from PDFs/DOCX (Appendix D). - Registry: receives submitted model versions + fixtures + model cards.
I1.1 Schema Components Library (shared, versioned)
Deal Engine and Deal Foundry use a Schema Components Library to compose input JSON Schemas by reference.
- Components are versioned and immutable (
lib/common@1.0.0,lib/touring@1.2.0, etc.). - Draft schemas should reference components via
$refrather than copy/paste definitions. - The Authoring Service must be able to bundle schemas for evaluation:
- resolve
$refto a fully expanded schema for validation and fixture generation - record the component versions in compiled artifacts for determinism and replay
- resolve
Minimal API (registry-owned or dedicated module):
GET /schema-library/componentsGET /schema-library/components/{name}/versionsGET /schema-library/components/{name}/versions/{version}
Authoring integration:
- Deal Foundry’s Schema tab browses components and inserts
$refs. - The Authoring Service validates that referenced component versions exist and are allowed.
I2. UX mock (high-level wireframe)
A suggested Draft Workspace layout (ASCII):
┌─────────────────────────────────────────────────────────────────────────────┐
│ Deal Engine Deal Foundry | Draft: touring_show_v1 | Status: draft │
├───────────────┬───────────────────────────────────────────┬─────────────────┤
│ Left Nav │ Main Workspace │ Assistant │
│ │ │ (Claude Opus) │
│ • Summary │ [Tab: Schema] [Tab: Model] [Tab: Fixtures]│ │
│ • Documents │ │ Step 3/5 │
│ • Schema │ JSON Schema Editor Form Preview │ ✓ Extract intent │
│ • Model │ ┌──────────────┐ ┌───────────────┐ │ ✓ Propose schema │
│ • Fixtures │ | schema.json | | rendered form | │ ▶ Propose model │
│ • Results │ └──────────────┘ └───────────────┘ │ │
│ • Diff │ │ Findings: │
│ │ Buttons: Validate | Generate Samples │ - Missing field │
│ │ │ settlementClose│
│ │ │ Actions: │
│ │ │ [Regenerate] │
│ │ │ [Ask Question] │
│ │ │ [Apply Patch] │
├───────────────┴───────────────────────────────────────────┴─────────────────┤
│ Console: validation errors | fixture run status | links to explain traces │
└─────────────────────────────────────────────────────────────────────────────┘I3. Authoring flow (Claude tool loop)
The Authoring Service runs Claude Opus in a tool-use loop.[5:1]
I3.1 Tools exposed to Claude
Claude should not write directly to the DB. It should request tool actions that the Authoring Service executes.
Example tool set (conceptual):
get_draft(draftId)→ returns draft summary + current artifactsget_docpack(documentId|blockIds)→ returns structured excerpts (selected blocks only)validate_json_schema(schema)→ returns schema validation + lint warningscompile_model(modelJson)→ returns compiled artifacts or errorsrun_fixtures(modelJson, fixtures)→ returns pass/fail and diffssuggest_fixture_variants(fixtures)→ returns boundary-expanded fixturesdiff_artifacts(old, new)→ returns structured diff summaryapply_patch(target, jsonPatch)→ applies a JSON Patch to draft artifacts (guarded)ask_user(question)→ returns the user’s response (only when required)
I3.2 Orchestration sequence
sequenceDiagram
participant UI as Deal Foundry
participant AS as Authoring Service
participant C as Claude Opus
participant RT as Runtime (compile/eval)
participant DP as Doc Pipeline
UI->>AS: create/update draft + upload docs
AS->>DP: extract docpack (async)
UI->>AS: "Generate" (with selected doc blocks)
AS->>C: messages + tools + draft context
C->>AS: tool_use(get_docpack)
AS->>C: docpack blocks (selected)
C->>AS: tool_use(propose_schema)
AS->>AS: validate_json_schema + lint
AS->>C: schema validation result
C->>AS: tool_use(propose_model)
AS->>RT: compile_model
RT->>AS: compile report + artifacts hash
AS->>C: compile result
C->>AS: tool_use(generate_fixtures)
AS->>RT: run_fixtures
RT->>AS: pass/fail + diffs + explain refs
AS->>C: fixture results
C->>AS: final: summary + suggested patches
AS->>UI: updated draft artifacts + resultsI4. Prompting approach (specific)
Use a multi-step, schema-constrained prompting strategy. Each step outputs strict JSON.
I4.1 System prompt (core constraints)
- “You are authoring a Deal Engine model.”
- “Do not invent new primitives; only use the primitive catalog.”
- “Never hardcode parties or real client names.”
- “All money values are decimal strings and include currency.”
- “If a required fact is missing, ask exactly one question.”
I4.2 Step prompts (recommended)
- Extract: from description + docpack blocks, output:
- deal intent summary
- list of candidate clauses
- candidate inputs
- candidate events/evidence keys
- Schema: propose/update input JSON Schema + UI schema hints
- Model: propose canonical model JSON (and optional DSL) referencing schema fields
- Fixtures: generate initial fixtures + expected outputs
- Expand: generate boundary variants around tiers/caps/rounding
- Validate: interpret compile/fixture failures and propose minimal patches
Claude tool-use docs: see Anthropic tool-use overview and implementation guidance.[4:1][5:2]
I5. What is generated (precisely)
When authoring generates a model version draft, the system produces:
Required artifacts
model.json— canonical model document (source of truth)input.schema.json— JSON Schema for instance inputs (source of truth for UIs)ui.schema.json— optional UI hints (labels/groups/order)model_card.json— assumptions, edge cases, mapping notes, what is not modeledfixtures/— a set of fixtures:inputs.jsonevidence.json(optional)expected.outputs.jsonexpected.obligations.jsonexpected.contingencies.json(optional)
fixturepack.zip— portable bundle (Appendix H)compiled_artifacts.json— compiled IR + projection hints + calculation list + hashescompile_report.json— validation/lint/compile errors/warningsfixture_report.json— pass/fail + diffs + explain refsclause_inventory.json— curated domain clause checklist (financial + non-financial), with coverage tags and schema refscontract.template.md— contract preview template bound to schema paths and clause inventory itemsworkflow_definition.json— allowed workflow states + mapping to canonical states (+ cp_ready hints)
Optional artifacts
dsl.dealmodel— DSL source (human-friendly; compiles tomodel.json)migration_spec.json— if version is breaking, migration transform + migration fixturesgenerated_service_stub/— thin wrapper service code (optional)
Audit artifacts (internal)
prompt_trace.json— tool calls and messages (for debugging), access-controlled
I6. Safety and UX: how users gain confidence
The UX must make correctness visible:
- Schema → form preview → sample payloads
- Fixtures → expected vs actual diffs
- Explain traces for any failing fixture
- “Publish readiness” checklist that blocks canary/active until gates pass
I7. References
- Claude tool use overview and implementation guidance.[4:2][5:3]
- Anthropic engineering note on advanced tool use (optional reading).[10]
Appendix J: Calculation Catalog and dependency metadata (normative)
Deal Foundry features like Calculation Workbench, fixture expansion, and schema/model consistency checks rely on compiler-emitted metadata.
J1. Calculation Catalog (required)
The compiler/runtime must emit:
calculation_catalog.version(string)calculation_catalog.calculations[]
Each calculation:
calculationKey(string, stable within model version)kind(metric|output)type(number|money|bool|string|object)displayName(optional)description(optional)dependencies:inputPaths[](FieldPaths; dot-separated)evidenceKeys[]calculationDeps[](other calculationKeys)
- optional:
unitcurrencyPolicyRefroundingPolicyRef
J2. Dependency breakdown output (workbench/run-fixtures)
For a single evaluation, the runtime should be able to return:
- result value
- a breakdown list:
- intermediate node id
- op name
- inputs and outputs
- provenance (input path or observation id)
- explain trace reference (or inline trace)
This is used by:
- Deal Foundry Workbench
- failing fixture diffs
- audit/explain endpoints
J3. JSON builder requirement (Deal Foundry)
Deal Foundry must use a real JSON builder/editor for:
input.schema.json(JSON Schema)ui.schema.json- canonical model JSON (source of truth)
Recommended library:
lovasoa/jsonjoy-builder(tree-based editor)
The UI should still provide a raw JSON textarea toggle for power users, but the builder is the default.
Appendix K: $ref resolution and schema bundling (normative)
This appendix defines how Deal Foundry and the Authoring Service handle JSON Schema $ref in a secure, deterministic way.
Dialect requirement: All schemas in this system use JSON Schema draft 2020-12. The Authoring Service must validate schemas against the 2020-12 meta-schema and preserve the declared $schema during bundling.
Core vs Validation vocabularies: Deal Foundry must support schemas that use only the Core vocabulary (structural/reference-only) as well as schemas that use the Validation vocabulary (constraints like type, enum, minimum, etc.).
K1. Why bundling is required
- Editors (including jsonjoy-builder) can create
$ref, but they do not reliably resolve external refs. - Downstream uses (form preview, validation, fixture generation) require a fully resolved schema.
- Deterministic builds require that ref resolution is:
- offline (no network)
- version-pinned
- hashable and replayable.
K2. Reference URI scheme
Schema Library refs use this form:
de://schema-library/<component>@<version>#/<json-pointer>
Examples:
de://schema-library/lib/common@1.0.0#/Moneyde://schema-library/lib/touring@1.2.0#/TicketTier
K3. Bundling algorithm (Authoring Service)
Inputs:
authoredSchema(may contain$ref)- schema library component store (versioned)
Steps:
- Validate that all
$refURIs are either:- local refs (
#/...), or - schema-library refs (
de://schema-library/...)
- local refs (
- Resolve schema-library refs by fetching the referenced component version from the library.
- Produce a bundled schema with all refs inlined.
- Record
refsUsed[]with:- component name
- version
- component hash
- Produce
bundleHash = sha256(canonical_json(bundledSchema))(Appendix F).
Outputs:
bundledSchemarefsUsed[]bundleHash- errors with JSON pointer paths
K4. Runtime and determinism
- The runtime validates instance payloads using the bundled schema, not the authored schema.
- Model versions must store:
- authored schema (for readability)
- bundled schema (for determinism)
- refsUsed and bundleHash
K5. Security posture
- Disallow network refs by default (
http(s)://). - Disallow file system refs outside approved stores.
- Enforce allow-listing: only the schema library and local defs.
K6. Further reading
Appendix L: Codegen IR v1 (normative) — compiled model representation for generated deal programs
This appendix defines the canonical intermediate representation (IR) that Deal Foundry produces at compile time and that codegen consumes to generate actual executable code for:
- calculations (metrics/outputs)
- payment term schedule generation
- obligation generation scaffolding
Important: This IR is not executed directly at runtime. It is a stable, versioned, language-agnostic input to code generators.
CEL note: Expressions originate as CEL text in the model. The compiler parses and type-checks CEL and emits the typed AST in this IR. Calculations may include sourceCel for debugging.
L1. Goals
- Deterministic: no wall-clock, no network, no randomness.
- Stable: versioned schema; backward compatible evolution.
- Language-agnostic: supports TS/Go/Rust/Elixir generators.
- Explicit dependencies: calculation catalog + input paths + evidence keys.
- Generates real code: expressions compile to code (not TODO stubs).
L2. IR file(s)
A model version compile produces:
ir.codegen.v1.json— the codegen IR (this appendix)calculation_catalog.json— derived view for UIs (Appendix J)workflow_definition.json— allowed workflow states + mapping (if present)bundled.input.schema.json— bundled schema with$refresolved (Appendix K)fixturepack.zip— portable test pack (Appendix H)
L3. Top-level IR shape (conceptual)
{
"irVersion": "codegen.ir.v1",
"dealType": "touring_show_v2",
"modelVersion": "2.0.0",
"primitiveCatalogVersion": "1.0.0",
"hashes": {
"modelHash": "sha256...",
"schemaBundleHash": "sha256...",
"irHash": "sha256..."
},
"inputs": {
"fieldIndex": [
{ "path": "gbor", "type": "number" },
{ "path": "guarantee", "type": "money" }
]
},
"workflow": {
"allowed": ["DRAFT","BOOKED"],
"mapping": [
{ "external": "DRAFT", "canonical": "draft", "cpReady": false },
{ "external": "BOOKED", "canonical": "closed", "cpReady": true }
]
},
"calculations": [ ... ],
"schedules": [ ... ],
"clauses": [ ... ],
"projections": { "fields": [ ... ] }
}L4. Expression AST (operator set for code generation)
Expressions are represented as a small, typed AST. Code generators must emit code for every supported operator.
L4.1 Core operator nodes
const— literal constantinput— read an input path (path)evidence— read an evidence key (key)calc_ref— reference another calculation by key- arithmetic:
add,sub,mul,div,min,max - boolean:
and,or,not - comparisons:
eq,ne,gt,gte,lt,lte - conditionals:
if(cond,then,else) - rounding:
round(policy ref) - money:
money(amount,currency),money_add,money_sub,money_mul,money_convert - collections (bounded):
count,sum_field,sum_money_field,min_field,max_field(FieldPath strings; see app dev guide)
L4.2 Node encoding (example)
{ "op": "max", "args": [
{ "op": "input", "path": "guarantee" },
{ "op": "mul", "args": [
{ "op": "sub", "args": [
{ "op": "calc_ref", "key": "nbor" },
{ "op": "input", "path": "expenses" }
]},
{ "op": "input", "path": "artistPct" }
]}
]}L4.3 Money and decimals
Money and finance-critical numbers MUST be represented and computed using decimals (no floats). Recommended libraries:
Rounding policies are referenced by id (policy catalog), and fixtures must cover rounding boundaries.
L5. Calculations block (codegen input)
Each calculation entry:
key(stable)kind:metric|outputtype:number|money|bool|string|objectexpr: AST nodedeps:inputPaths[]evidenceKeys[]calculationDeps[]
Codegen output:
- a pure function per calculation:
compute_<key>(ctx) -> type - a registry of calculation metadata for
/calculations
L6. Schedules and payment terms (codegen input)
L6.1 Schedule definitions
IR schedule entries normalize schedule generation:
idkind:one_time|installments|periodic|milestoneparams: normalized fields (start/end/count/every/unit/milestones)anchorInputs: which input paths are required
Codegen output:
generateSchedule_<id>(inputs, asOf) -> occurrences[]
L6.2 Payment terms
Payment terms are compiled under each clause:
termKeyamountSpec:fixed(expr)percentage(expr; percent of clause_amount)calculated(expr)
dueDateSpec:absolute(date expr)relative(trigger expr + offset_days expr)by_schedule(schedule id)rule_based(normalized rule object)
- optional:
trigger(bool expr) - optional:
method,destinationRef,specialConditions
Codegen output:
- a function that generates obligation occurrences for the clause:
- evaluate clause amount
- split into term occurrences
- compute due dates
- emit obligation records with stable keys
L7. Clauses and obligations (codegen input)
Each clause includes:
id,kind,descriptionamountExpr(AST)paymentTerms[]- optional: allocation id reference (if present)
Codegen output:
computeClause_<id>(ctx) -> ClauseResultgenerateObligations_<id>(ctx) -> obligations[]
L8. Deterministic key generation
The IR must include sufficient stable ids so codegen can produce:
obligationKey(stable identity) from clause id + occurrence identity + counterparty/currency rules- compute fingerprint keys (Appendix F)
L9. Schema and $ref bundling
The IR references the bundled input schema hash and records refsUsed[] (Appendix K). Codegen uses the bundled schema to generate:
- input types
- validators
- form metadata hints (optional)
Appendix M: Code generation pipeline (normative) — from IR to a deal program service
This appendix specifies exactly how codegen works.
M1. Inputs to codegen
Codegen consumes a published model version bundle:
ir.codegen.v1.json(Appendix L)bundled.input.schema.json+ui.schema.json(if present)calculation_catalog.json(Appendix J)workflow_definition.json(if present)fixturepack.zip(Appendix H)openapi.contract.yaml(standard interface; Section 5.5)
M2. Outputs of codegen
Codegen generates a repository (or package) containing:
Generated (do not edit)
/generated/types/— types from schema (inputs/outputs/obligations)/generated/validators/— schema validators/generated/calculations/— executable functions for metrics/outputs/generated/schedules/— executable schedule generators/generated/payment_terms/— executable payment term generators/generated/workflow/— workflow mapping catalog and helpers/generated/openapi/— OpenAPI handlers stubs for schema/calculations endpoints/generated/tests/— fixturepack runner + snapshot tests
Handwritten extension points (safe to edit)
/src/storage/— storage adapter interface + default in-memory impl/src/service/— HTTP server wiring, auth hooks, datadog hooks/src/extensions/— domain-specific hooks (optional)/src/custom/— manual overrides where needed
Rule: regen must never overwrite /src/**.
M2.1 Standard interface generation (mandatory; no per-deal custom APIs)
A key platform invariant is that all deal programs expose the same API surface, so departmental systems and analytics do not need to learn per-domain APIs.
Therefore, codegen MUST generate:
- OpenAPI spec (
/generated/openapi/openapi.yaml) for the standard interface - Route handlers and request/response types for that interface
- The schema/calculation/workflow discovery endpoints, derived from IR artifacts
- Stub implementations only where domain-specific storage/side-effects are required (ports)
Hard rule: deal program maintainers must not change the external contract surface. Custom endpoints are allowed only behind a feature flag and must not be required by calling applications.
Endpoints generated (minimum):
- Model discovery:
/models,/models/{dealType}/versions,/models/{dealType}/versions/{version},/input-schema,/ui-schema,/calculations,/workflow - Drafts:
/draftscreate/patch/validate/compute/commit - Deals:
/dealscreate/get/patch (revision),/revisions,/snapshots - Compute:
/compute(ephemeral|persistent),/snapshots/{id}/explain - CP:
/obligations,/obligations/delta,/processing/start,/payments/ack,/invoices/ack(optional)
Where generated code calls into handwritten code:
- storage adapters (DealStore/EvidenceStore)
- event/evidence registration hooks (watch creation) if enabled
- any explicit extension hooks
This gives the enterprise a uniform integration surface while still allowing per-deal program implementation autonomy.
M3. Language targets
Codegen must support at least:
- TypeScript (Node.js LTS)
- Go
- Rust
- Elixir
Each language target must provide:
- decimal money library integration (Appendix L4.3)
- canonical JSON/hashing compliance hooks (Appendix F)
- test harness integration (fixturepack runner)
M4. Execution model
Generated calculation and schedule code must be:
- deterministic (no time/network/random)
- pure functions of
(inputs, evidence, asOf) - instrumented (timing spans) but not side-effectful
M5. How maintainers customize storage
Generated code defines storage ports:
DealStore(revisions/snapshots)EvidenceStore(read-only observations)ProjectionEmitter(optional)
Maintainers implement these ports for their chosen DB. Deal Engine remains compatible because the standard interface does not require a specific storage backend.
M6. Testing and conformance
Every generated deal program must include tests that run in CI:
- Fixture pack conformance:
- inputs/evidence → expected outputs/obligations
- Compliance suite (Appendix F):
- canonical JSON/hashing test vectors
- decimal rounding test vectors
- obligationKey determinism tests
- Calculation catalog parity:
- calculations exposed by
/calculationsmust match IR catalog
- calculations exposed by
If generated code is edited manually (in allowed folders), tests must still pass.
M7. When codegen runs
- During authoring: Deal Foundry can generate a “preview service stub” for local testing.
- During publish: the Registry stores IR + artifacts; a CI pipeline can run codegen and publish a deployable service artifact.
- During upgrades: codegen runs again for new model versions.
M9. Service discovery (no facade; how callers find deal programs)
Because we do not rely on a single facade, calling systems need a consistent way to locate the correct deal program endpoint for a deal type/model version.
Approach:
- The Model Registry is the source of truth for:
- which deal types exist
- which model versions are active/canary/deprecated
- the service endpoint (base URL) for the deal program that implements the deal type
- Deal programs register themselves (or are registered by deployment pipelines) with:
dealTypeserviceBaseUrl- environment (dev/prod)
- supported model versions (optional; usually “all published versions of this deal type”)
Client discovery flow:
- App calls Registry:
GET /deal-types/{dealType}(or equivalent) - Registry returns:
- active version
- canary rules (if applicable)
serviceBaseUrl
- App calls the deal program standard interface at that base URL.
Implementation notes:
- Keep discovery cached client-side with short TTL.
- In enterprise setups, APIM can front multiple deal program backends behind consistent routing rules, but the caller should not be required to know internal topology.
M8. Further reading
- Code generation patterns and separating generated vs handwritten code.[15]
Appendix O: Clause Library patterns (normative) and provenance in models
Deal Foundry uses a Clause Library to accelerate modeling and improve consistency. The Clause Library is not a separate runtime system; it is an authoring-time catalog of reusable patterns built from clause primitives (kinds, payment terms, templates, schedules, computation specs).
This appendix defines:
- what a clause library item is
- required identifiers (pattern IDs)
- what gets stamped into the model when a pattern is used
- how customization and drift are tracked
O1. Clause library item (pattern) definition
A clause library item is a versioned pattern. Minimum fields:
patternId(stable, globally unique string)- example:
lib:endorsement/payment/option_two_tranche@1
- example:
title(human-friendly name)domain(touring/endorsement/film/common)clauseKind(simple|guarantee|contingent)description(short)template(parameterized contract language)inputSchemaFragment(JSON Schema fragment to be merged into the model’s input schema)computationSpecs(optional list of ComputationSpec blocks this pattern expects)paymentTermsScaffold(optional; term definitions with placeholders)eventsNeeded(optional; evidence keys / internal milestone events)policyRefs(optional; rounding/currency/date policies)
Pattern IDs must be immutable: a new version (e.g., @2) is created when the meaning changes.
O2. Pattern ID conventions
Recommended format:
lib:<domain>/<family>/<name>@<major>
Examples:
lib:touring/ticketing/tier_table@1lib:touring/payout/versus_net@1lib:endorsement/payment/base_fee_installments@1lib:film/backend/boxoffice_ladder@2
O3. What gets written into the model when a pattern is used
When an author inserts a clause pattern, Deal Foundry instantiates a model clause. The model clause has its own stable identity, and also carries provenance.
Required provenance fields per clause:
clauseId(stable within the model; used by traces/obligation keys)sourcePatternId(pattern that seeded this clause)sourcePatternHash(hash of the pattern definition as used at insertion time)customizationmetadata:isCustomizedboolean- optional
patchSummary(human-readable) - optional
patches(JSON Patch list) for structured diffs
This allows:
- auditability (“what pattern did this come from?”)
- rebase/upgrade workflows (“update to pattern @2”)
- drift detection (“this clause no longer matches the library pattern”)
O4. Model DSL representation (authoring-only)
Deal Foundry should store provenance in the canonical model JSON. In DSL, this can be represented as an optional field inside a clause block:
clause {
name: option_term_fee
kind: contingent
source: "lib:endorsement/payment/option_two_tranche@1"
source_hash: "sha256:..."
description: "Fee payable if option exercised"
template: \"\"\" ... \"\"\"
}If the DSL grammar does not include source/source_hash tokens, Deal Foundry may still store provenance only in canonical model JSON and show it in the UI.
O5. Clause Library service surface (authoring-time)
Deal Foundry requires a catalog API (could be part of Deal Foundry backend):
GET /clause-library/patterns?domain=touringGET /clause-library/patterns/{patternId}GET /clause-library/patterns/{patternId}/versions
Insertion and tracking:
- Deal Foundry records provenance on insert.
- Deal Foundry can show “Rebase to latest version” (optional feature).
O6. Customization and rebasing
Customization happens when the author edits the instantiated clause (template wording, payment term fields, schema fragments).
Policy:
- any edit sets
isCustomized=true - rebasing is a deliberate action:
- compute diff between current clause and new pattern version
- apply only safe patches automatically
- require review for conflicts
O7. Why this matters
Without stable pattern IDs and provenance:
- model diffs become noisy and untraceable
- consistency across domains erodes
- analytics and contract generation drift
With this approach:
- authors move faster (start from known patterns)
- maintainers can upgrade patterns safely
- the enterprise can measure reuse and standardize semantics incrementally
O8. Normative JSON format (pattern bundles)
Clause library patterns MUST be stored as JSON documents that validate against:
de://contracts/authoring/clause-library-pattern.schema.json
O8.1 Pattern schema (draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "de://contracts/authoring/clause-library-pattern.schema.json",
"title": "Clause Library Pattern (Deal Foundry)",
"type": "object",
"required": [
"patternId",
"title",
"domain",
"version",
"clauseKind",
"template",
"inputSchemaFragment",
"paymentTermsScaffold",
"computationSpecs",
"hash"
],
"properties": {
"patternId": {
"type": "string",
"pattern": "^lib:[a-z0-9_-]+\\/[a-z0-9_-]+\\/[a-z0-9_-]+@[\\d]+$"
},
"title": {
"type": "string"
},
"domain": {
"type": "string"
},
"version": {
"type": "string"
},
"clauseKind": {
"type": "string",
"enum": [
"simple",
"guarantee",
"contingent"
]
},
"description": {
"type": [
"string",
"null"
]
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"template": {
"type": "object",
"required": [
"markdown"
],
"properties": {
"markdown": {
"type": "string"
},
"bindingSyntax": {
"type": "string",
"enum": [
"mustache"
],
"default": "mustache"
}
},
"additionalProperties": false
},
"inputSchemaFragment": {
"type": "object",
"required": [
"mergePath",
"schema"
],
"properties": {
"mergePath": {
"type": "string"
},
"schema": {
"type": "object"
}
},
"additionalProperties": false
},
"paymentTermsScaffold": {
"type": "object",
"required": [
"terms"
],
"properties": {
"terms": {
"type": "array",
"items": {
"type": "object",
"required": [
"termKey",
"payment_type",
"amountSpec",
"dueDateSpec"
],
"properties": {
"termKey": {
"type": "string"
},
"payment_type": {
"type": "string"
},
"amountSpec": {
"type": "object"
},
"dueDateSpec": {
"type": "object"
},
"triggerSpec": {
"type": [
"object",
"null"
]
},
"payment_method": {
"type": [
"string",
"null"
]
},
"payment_destination_input_ref": {
"type": [
"string",
"null"
]
},
"special_conditions": {
"type": [
"object",
"null"
]
}
},
"additionalProperties": true
}
}
},
"additionalProperties": false
},
"computationSpecs": {
"type": "array",
"items": {
"type": "object",
"required": [
"key",
"kind",
"valueType",
"description"
],
"properties": {
"key": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"metric",
"output"
]
},
"valueType": {
"type": "string",
"enum": [
"money",
"number",
"int",
"bool",
"string",
"object"
]
},
"display": {
"type": [
"string",
"null"
]
},
"description": {
"type": "string"
},
"depends_on_inputs": {
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"depends_on_metrics": {
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"invariants": {
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"examples": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
}
},
"eventsNeeded": {
"type": "array",
"items": {
"type": "object"
},
"default": []
},
"policyRefs": {
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"hash": {
"type": "string"
},
"createdAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"createdBy": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
}O8.2 Example pattern: endorsement option two-tranche
{
"patternId": "lib:endorsement/payment/option_two_tranche@1",
"title": "Option Term Fee \u2013 Two Tranches",
"domain": "endorsement",
"version": "1.0.0",
"clauseKind": "contingent",
"description": "Contingent option fee split into two tranches with separate due dates and configurable tranche percentages.",
"tags": [
"option",
"payment_terms",
"endorsement"
],
"template": {
"markdown": "Option Term Fee. If option exercised, Brand shall pay Talent {{financial.optionTermFee.amount.amount}} {{currency}}.\n{{financial.optionTermFee.tranchePct1}} due {{financial.optionTermFee.tranche1DueDate}} and {{financial.optionTermFee.tranchePct2}} due {{financial.optionTermFee.tranche2DueDate}}.\n",
"bindingSyntax": "mustache"
},
"inputSchemaFragment": {
"mergePath": "/properties/financial/properties",
"schema": {
"optionTermFee": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "boolean",
"default": false
},
"amount": {
"$ref": "de://schema-library/lib/common@1.0.0#/$defs/Money"
},
"optionStartDate": {
"type": [
"string",
"null"
],
"format": "date"
},
"tranchePct1": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.5
},
"tranchePct2": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.5
},
"tranche1DueDate": {
"type": [
"string",
"null"
],
"format": "date"
},
"tranche2DueDate": {
"type": [
"string",
"null"
],
"format": "date"
}
},
"additionalProperties": false
}
}
},
"paymentTermsScaffold": {
"terms": [
{
"termKey": "tranche_1",
"payment_type": "lump_sum",
"amountSpec": {
"type": "percentage",
"path": "financial.optionTermFee.tranchePct1"
},
"dueDateSpec": {
"type": "absolute",
"path": "financial.optionTermFee.tranche1DueDate"
},
"payment_method": "wire",
"payment_destination_input_ref": "PAYMENT_PROFILE_INPUT"
},
{
"termKey": "tranche_2",
"payment_type": "lump_sum",
"amountSpec": {
"type": "percentage",
"path": "financial.optionTermFee.tranchePct2"
},
"dueDateSpec": {
"type": "absolute",
"path": "financial.optionTermFee.tranche2DueDate"
},
"payment_method": "wire",
"payment_destination_input_ref": "PAYMENT_PROFILE_INPUT"
}
]
},
"computationSpecs": [
{
"key": "option_fee_total",
"kind": "output",
"valueType": "money",
"display": "Option Term Fee Total",
"description": "Option fee amount when option is exercised; otherwise zero.",
"depends_on_inputs": [
"financial.optionTermFee.enabled",
"financial.optionTermFee.amount"
],
"depends_on_metrics": [],
"invariants": [],
"examples": null
}
],
"eventsNeeded": [
{
"name": "option_exercised",
"kind": "input_or_evidence",
"key": "financial.optionTermFee.enabled"
}
],
"policyRefs": [],
"hash": "sha256:PLACEHOLDER"
}O8.3 Example pattern: touring versus net with bonus
{
"patternId": "lib:touring/payout/versus_net_with_bonus@1",
"title": "Guarantee vs Net % + Ticket Bonus + Commission",
"domain": "touring",
"version": "1.0.0",
"clauseKind": "guarantee",
"description": "Standard touring settlement pattern: versus net variant with per-paid-ticket bonus and commission subtraction.",
"tags": [
"touring",
"versus",
"bonus",
"commission"
],
"template": {
"markdown": "Artist shall receive a GUARANTEE of ${{terms.guarantee.amount.amount}} {{currency}} VERSUS {{terms.artistPct}} of net base after deductions/expenses, whichever is greater, PLUS a bonus of ${{terms.bonusPerPaidTicket.amount.amount}} per paid ticket up to {{terms.bonusCapTickets}} tickets. Less commission of {{terms.commissionPct}}.\n",
"bindingSyntax": "mustache"
},
"inputSchemaFragment": {
"mergePath": "/properties/terms/properties",
"schema": {
"guarantee": {
"$ref": "de://schema-library/lib/common@1.0.0#/$defs/Money"
},
"artistPct": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"bonusPerPaidTicket": {
"$ref": "de://schema-library/lib/common@1.0.0#/$defs/Money"
},
"bonusCapTickets": {
"type": "integer",
"minimum": 0
},
"commissionPct": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.0
}
}
},
"paymentTermsScaffold": {
"terms": [
{
"termKey": "settlement_balance",
"payment_type": "installment",
"amountSpec": {
"type": "calculated",
"expr": "clause_amount()"
},
"dueDateSpec": {
"type": "relative",
"trigger": "evidence('show.settled')==true",
"offset_days": 0
},
"payment_method": "ach",
"payment_destination_input_ref": "PAYMENT_PROFILE_INPUT"
}
]
},
"computationSpecs": [
{
"key": "walkout",
"kind": "output",
"valueType": "money",
"display": "Walkout",
"description": "Net to artist after applying versus base, ticket bonus, and commission.",
"depends_on_inputs": [
"terms.guarantee",
"terms.artistPct",
"terms.bonusPerPaidTicket",
"terms.bonusCapTickets",
"terms.commissionPct"
],
"depends_on_metrics": [
"gbor",
"salesTax",
"nbor",
"payoutBase",
"ticketBonus"
],
"invariants": [
"walkout >= 0"
],
"examples": null
}
],
"eventsNeeded": [
{
"name": "show_settled",
"kind": "evidence",
"key": "show.settled"
}
],
"policyRefs": [],
"hash": "sha256:PLACEHOLDER"
}O9. Deterministic merge rules (schema + scaffolds)
When inserting a pattern into a draft/model, Deal Foundry MUST apply deterministic rules:
- Schema merge
- Navigate to
inputSchemaFragment.mergePathin the model’sinputs.schema. - Merge object properties:
- If a property does not exist: add it.
- If a property exists and is identical: keep it.
- If a property exists and differs: mark a conflict and require author resolution.
$defsmerging:- identical: keep one
- differs: conflict
- ComputationSpec merge
- Add missing specs by
key. - If key exists and differs: author resolves (or keep model and mark customized).
- Payment term scaffold merge
- Insert term scaffolds into the instantiated clause.
- Placeholders remain unresolved until a deal instance is provided.
- Provenance stamping
- Stamp
sourcePatternIdandsourcePatternHash = sha256(JCS(pattern))and setisCustomized=false.
- Customization detection
- Any edit sets
isCustomized=true.
O10. Rebase rules (pattern upgrades)
Rebasing to a newer pattern version:
- Diff current instantiated clause vs freshly instantiated clause from new pattern.
- Apply safe patches automatically (template wording changes with same placeholders; added optional schema props).
- Require manual resolution for meaning changes (required fields, triggers, due rules).
O11. Authoring usage workflow (super explicit)
- Author selects a pattern in Deal Foundry (Clause Inventory → Insert from Clause Library).
- Deal Foundry validates the pattern JSON, applies deterministic merges (O9), and instantiates a model clause with stable
clauseIdand provenance. - Author edits clause as needed; customization is tracked.
- Author runs fixtures/evaluation jobs to confirm behavior.
- Publish model; provenance remains for audit and reuse analytics.
https://learn.microsoft.com/en-us/azure/architecture/patterns/saga ↩︎
https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview ↩︎ ↩︎ ↩︎
https://platform.claude.com/docs/en/agents-and-tools/tool-use/implement-tool-use ↩︎ ↩︎ ↩︎ ↩︎
https://microservices.io/patterns/data/transactional-outbox.html ↩︎
https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/transactional-outbox.html ↩︎
https://json-schema.org/understanding-json-schema/structuring.html#ref ↩︎ ↩︎