- Documentation
- /
- Contract
- /
- Phase 2 — Runtime Engine Scope
Phase 2 — Runtime Engine Scope
Phase 1 (built) renders the 16-model schema as an AI-Safe CRUD register: contracts,
parties, clauses, obligations, occurrences, notices, events, variations, claims,
breaches, evidence, approvals and tasks — browsable, with a dashboard. But the
states are static: the seed shows an obligation already overdue, a notice
already issued. Nothing is computed or transitioned by the system.
Phase 2 is the runtime that makes it live — computing deadlines, generating recurring occurrences, enforcing the state machines, running the scans and reacting to the rules the pack specifies. Same shape as the (built, cluster-proven) orchestrator engine, but single-app.
Everything below is drawn from the pack's workflows.yaml, rules.yaml, the DSL
actions {} / scheduled_jobs {} / derived {} blocks and the ContractPortfolio
dashboard. It is a scope, not a design; each workstream still needs the noted
decisions.
A. Derived-field engine
The DSL derived fields on Obligation are seeded statically today; Phase 2
recomputes them (the heart of "nothing lapses"):
| Field | Rule |
|---|---|
Obligation.days_to_due |
business-days between today and due_date |
Obligation.overdue |
due_date < today() AND status ∉ {satisfied, waived, closed} |
Obligation.due_soon |
0 ≤ days_to_due ≤ warning_days AND status active/pending |
Decisions: compute-on-read vs materialise-on-write vs recompute-on-tick;
ObligationOccurrence due dates likewise.
B. State machines & guarded actions
The pack defines two workflows plus the DSL actions {}. Today a user free-edits
status; Phase 2 exposes each transition as a guarded action that enforces the
guard, applies effects atomically and records the change.
| Workflow / action | Notable guards & effects |
|---|---|
| contract (draft→under_review→active→…→archived) | complete requires no open material obligations; role-gated |
| obligation (draft→pending→active→submitted→satisfied) | satisfy requires required evidence present + required approval complete; waive requires a waiver reference; active→overdue is automatic on deadline |
activate_contract |
requires under_review; sets status=active; emits contract.activated |
satisfy_obligation |
requires evidence linked; sets status=satisfied, satisfied_at=now(), satisfied_by=user; emits obligation.satisfied |
issue_notice |
requires approved; sets status=issued, issued_at=now(), notice_date=coalesce(...); emits notice.issued |
acknowledge_notice |
requires issued/received; sets acknowledged; emits notice.acknowledged |
Decisions: UI buttons vs API-only; role → PIN/RBAC mapping; guard blocks vs warns.
C. Scheduled jobs — the deadline & recurrence engine
The DSL scheduled_jobs {} are the headline background engine — a tick that:
obligation_deadline_scan(hourly) — flip obligations/occurrences todue_soon/overdueas dates pass (rules OBL-DUE-001 / OBL-OVERDUE-001).recurring_obligation_generator(daily) — generate the nextObligationOccurrencefrom each active obligation'srecurrence_rule(FREQ=MONTHLY;…) anddue_rule. This is what turns a monthly obligation into a rolling series of dated instances.notice_acknowledgement_scan(4-hourly) — chase issued notices past theiracknowledgement_due.contract_expiry_scan(daily) — warn on contracts nearingexpiry_date.
Decisions: cadence; business-calendar (AU_QLD) for business-day math (the
pack ships business_calendars.yaml); recurrence-rule parser (RRULE subset);
exception dedupe so a scan doesn't re-raise every tick.
D. Rules / cross-record automation
Reactive rules from rules.yaml:
- OBL-DUE-001 — obligation
due_soon→ set status, create a review Task assigned toaccountable_person_id, emitobligation.due_soon. - OBL-OVERDUE-001 — obligation past due → set
overdue, emitobligation.overdue, escalate to contract_manager/legal after 2 days. - NOTICE-DEADLINE-001 — a notice with a
required_by_datestill unsent → tiered warnings at 7 / 2 / 0 days (warning → high → critical), emitnotice.deadline_warning. - NOTICE-SERVICE-001 — before
issue, requirenotice_date,sender_party_id,recipient_party_id(service details).
The DSL also implies event-driven links: a ContractEvent flagged potential →
notice/claim/variation/breach could auto-create the downstream record or a task.
Decisions: event bus (in-app only vs emit to orchestrator); notify = email vs in-app; escalation targets.
E. Executive dashboard & KPIs
The DSL ContractPortfolio dashboard defines metrics Phase 1 renders generically;
Phase 2 wires them via the repo KPI framework (kpi_engine.py + kpis.py):
- Metrics: active contracts; obligations due in 30 days; overdue obligations; open notices; open claims; unresolved breaches.
- Panels: contracts at risk; upcoming deadlines; overdue items; recent notices; open variations.
How it would be built
- A per-app engine module (mirror
server/lib/orchestrator_engine/, single-app): compute pass (A), scheduler/tick with the four jobs (C), guarded actions (B), rules evaluator (D). Opt-in env flag, single worker. - KPIs (E) via the existing framework — independent of the engine, quick win.
- Business-calendar + RRULE underpin the deadline/recurrence engine.
- Tests per workstream (
test_orchestrator_flows.pyis the template).
Suggested sequencing
- KPIs / dashboard (E) — visible, no engine.
- Derived fields (A) — accurate
overdue/due_soonbadges. - Recurrence + deadline engine (C) — the core value; needs calendar + RRULE.
- State machines + guards (B) — governed transitions.
- Rules / automation (D) — reactive escalations and tasks.
Each slice is independently shippable and demoable. The orchestrator (built and run live this session) is the working template for the engine, scheduler and tests.