"""The use cases — what contract admins, owners, approvers, our signatories, the counterparty's signers (through the
public door), calling modules and the clock can do, each declared ONCE with @CT.use_case and served as an API route
AND an MCP tool.
`caller` is the first parameter of every one; row visibility is decided inside the use case from it (support.View),
so an agent sees exactly what its person sees. Refusals are Refused(status, reason): a broken rule 409, invalid input
422, a row the caller may not see 404 — never 403, which would confirm it exists.
The modules, by area (each imports CT from here and registers its use cases):
library keep_contract_type, keep_clause, amend_clause, retire_clause, keep_template, approve_template,
retire_template, keep_settings, install_contract_pack, clause_library, show_template, show_settings
drafting draft_contract, edit_draft, set_variables, choose_position, override_section, add_signer,
edit_signer, request_approval, decide_approval, approvals_queue
signing send_for_signature, recall, reissue_link, copy_signing_link, mark_delivered_by_hand, open_as_us,
sign_as_us, decline_as_us, to_sign — and the public commands the door calls (open_for_signing,
consent_and_sign, decline, request_changes; public_pages.py)
lifecycle upload_signed_elsewhere, cancel, amend_contract, renew_contract, terminate_contract,
record_administrative_change, complete_key_date, run_due (the tick), purge_by_retention
queries list_contracts, show_contract, show_versions, download_version, show_certificate, verify_evidence,
key_dates, contract_status, fetch_executed
capabilities contracts.draft / send / status / fetch_executed, PROVIDED to other bricks (hr; crm's adapter)
"""
import os
from contextlib import contextmanager
from erp_platform.bricks import Brick
VERSION = "0.2.0"
CT = Brick("contracts", namespace="contract", version=VERSION, requires=("mail.send",), ids_as_strings=True, instructions=(
"Contracts drafted from approved templates and a clause library (Preferred / Alternate / Fallback positions), "
"filled from typed variables rather than re-typed, approved when they deviate (the approval binds to the draft's "
"fingerprint; an edit voids it), frozen as version N with its sha256 at send, and signed with a SIMPLE electronic "
"signature: the counterparty through a public link, our side inside the app as their own sign-in. Every signature "
"event is append-only and hash-chained; the executed copy is the signed version plus a certificate page with its "
"own sha256. The module only ever says 'signed electronically'. A deed is never signed online: it is signed on "
"paper and the executed copy uploaded (SIGNED_ELSEWHERE). The counterparty is always a business partner (bp_id). "
"contract_id is 'ct-' + 16 hex derived from source_ref|type_code. Employment contracts (they hold pay) show their "
"body only to contract:employment:read; everyone else who may see them sees state and key dates."))
@contextmanager
def _repository_default():
from erp_platform import db
from brick_contracts.infrastructure.tables import ContractsRepository
with db.engine.begin() as conn:
yield ContractsRepository(conn)
# The unit of work: ONE connection, one transaction. Tests and the scenario harness swap it for SQLite.
repository = _repository_default
class DemoClockOnLive(RuntimeError):
pass
# The demo clock: the route exists only when CT_DEMO_CLOCK=enabled, and the app refuses to start with it on a live
# deployment (DODIL_ENV=live|prod). Mount it on ONE brick of a composition (hr mounts its own).
if os.environ.get("CT_DEMO_CLOCK", "").strip().lower() == "enabled":
if os.environ.get("DODIL_ENV", "").strip().lower() in ("live", "prod"):
raise DemoClockOnLive("CT_DEMO_CLOCK=enabled on a live deployment (DODIL_ENV=live|prod): refusing to start")
from erp_platform import democlock
democlock.mount(CT)
from brick_contracts.application import (capabilities, drafting, library, lifecycle, public_pages, # noqa: E402,F401
queries, signing)
__all__ = ["CT", "repository", "VERSION"]