brick-contracts
Contracts: drafted from your approved templates and clause library, filled from the record rather than re-typed,
approved when they deviate, signed online with a record of who signed what and when, and tracked to their notice
deadlines and renewals. A brick on erp-platform (0.5.3); spec
modules/contracts in the blog repo (branch specs/hr-contracts). Every action is both an API route and an MCP
tool. Core — what SAP Ariba Contracts and S/4HANA Legal Content Management ship as standard; a customer's types,
clauses, templates and policies are configuration rows (a customer's wording is its own).
The signature is a simple electronic signature (SES) with its evidence attached. The module only ever says "signed electronically" — never advanced, qualified or anything more. A deed is never signed online.
What it does
| Story | How |
|---|---|
| CT-01 clause library | families with Preferred / Alternate / Fallback clauses; amend_clause cuts version N+1; a version a contract holds or an approved template pins is never changed in place ("version 1 is on 3 contracts, amend it into version 2"); a required clause cannot be retired |
| CT-02 types as rows | allowed business-partner roles, SIMPLE / DEED (a deed is saved esign_allowed = false), sensitivity, a retention rule and an approval policy with a mandatory fallback group |
| CT-03/04 templates | ordered slots (required / optional / alternative-of, with conditions) pinning clause_id@version; typed variables (text, date, money, rate, int, enum with aliases, bool) with source, bounds, defaults; DRAFT → APPROVED (not by its author) → RETIRED; an approved version is immutable |
| CT-05/06 drafting | draft_contract: the counterparty is ALWAYS a bp_id holding an allowed role (read through business-partner), sections are COPIES of clause versions, missing[] and deviations[] are computed; positions and overrides record their deviation level |
| CT-07 approval | a deviation opens an approval per group the policy names, bound to the draft fingerprint (sha256 over title, sections, variables, signers); an edit voids it; an owner cannot approve their own; after the timeout it belongs to the fallback group |
| CT-08 send | version N rendered to PDF and frozen with its sha256; refused in words for missing values, an open approval, a deed, one side named, the UK checks; one SIGN link per EXTERNAL signer issued in the same transaction; delivery by mail.send or, with no provider, a manual "copy the signing link" task (the link is never stored) |
| CT-09/10/53 the counterparty signs | through the platform's public door: the frozen words, the fingerprint, the consent text, how long the copy is kept; sign / decline / ask for changes; stale versions and out-of-order signing refused; single use; OPENED / CONSENTED / SIGNED with IP and user agent as fields |
| CT-17 our side signs | sign_as_us inside the app as the signer's own pool identity (INTERNAL, POOL_SESSION, the pool sub); no link exists for them |
| CT-31/32/13 evidence | append-only, hash-chained ct_signature_event (row_hash = sha256(prev_hash ‖ row)); the executed copy = the signed version's exact bytes + a certificate page (an incremental PDF update) with its own sha256; a broken chain or changed bytes refuse it |
| CT-12 signed elsewhere | an uploaded copy is the executed copy (UPLOADED); the contract is SIGNED_ELSEWHERE and its signers SIGNED_ELSEWHERE, never SIGNED |
| CT-14 amend / renew / terminate | each its own contract drafted from the original's frozen values; when signed the original is SUPERSEDED / TERMINATED; ADMINISTRATIVE changes are recorded without signing |
| CT-15/30 key dates and the clock | START, END, NOTICE_DEADLINE (end − notice, its own date), PROBATION_END, PRINCIPAL_STATEMENT_DUE, SDS_REVIEW; reminders 120/90/60/30 days (rows), once per slot; run_due chases, expires, escalates, finishes an owed executed copy |
| CT-33/34 derived state, retention | ACTIVE / EXPIRED derived from the term; term + 6 years, deeds 12, dead drafts after a short window; erasure keeps live evidence under the legal-claims exception |
| CT-35 the UK pack | ERA 1996 s.1 particulars required; the written statement "given" (delivered or opened) — warned on day one when it was not; IR35: an OUTSIDE determination by a medium/large client needs its SDS before send; CDPA s.90(3) |
| CT-50/51/54 capabilities | contracts.draft / send / status / fetch_executed for hr (and crm's adapter, outside this brick): contract_id derived from source_ref|type_code; the executed copy only to the ORIGINATOR (source_ref namespace, ct_consumer rows) |
Who
contract_admin everything; owner their contracts (an employment body needs contract:employment:read);
legal/finance/director approve (director is the fallback and signs for us); our_signatory signs; manager
reads all commercial contracts and only the existence of employment ones; hr_service/crm_service call the
capabilities; clock the tick. Permissions contract:* (tests/scenarios/roles.yaml).
Tables
All ct_, natural keys, schema.sql generated from infrastructure/tables.py (scripts/gen_schema.py). Contract
(ct_contract + variable, section, version, signer, executed_copy, approval, delivery, key_date, amendment, and the
append-only ct_signature_event), Library (ct_clause + version; ct_template + version, slot, variable_def),
ct_type, config rows (approval_policy, reminder_cadence, consent_text, retention_rule, settings, consumer,
config_version), bytes in ct_object_chunk, ct_retention_log, ct_tick_claim; the provider's
contracts_capability_claims/_log (named by the platform).
Run
python -m venv .venv && .venv/bin/pip install -e '.[test]' pypdf # needs erp-platform, brick-business-partner, brick-hr
.venv/bin/python -m pytest -q --tb=short # rules, doors, tier 1 (real hr + bp), scenarios
.venv/bin/python scripts/mutation_check.py # each key defect put back must fail its test
BUCKET=erp-platform-test DODIL_ORG=dodil .venv/bin/python scripts/live_check.py # the stories on DataK3
erp-scenarios run tests/scenarios/catalogue.py # SC-CT-01..25 + probes, in process
Environment: BUCKET, DODIL_SERVICE_ACCOUNT_ID/_SECRET (k3.editor), DODIL_USER_POOL, PUBLIC_LINK_BASE_URL
(the public app, where signing links point), BP_BASE_URL at tiers 2–3, CAPABILITY_MAIL_SEND to wire a mail
provider, CT_DEMO_CLOCK=enabled on a demo only. The signing pages are served by the composition's PUBLIC app
(erp-compose serve --public), never by this one.
Not in 0.1
Advanced or qualified signatures (the signature_provider extension point), inline change proposals, obligations
beyond dates, RFC 3161 timestamps, multi-language templates, a screen (H5). Bytes live in ct_object_chunk until the
platform has an object-store helper. See AGENTS.md for the known gaps.
License: Apache-2.0, see LICENSE