Skip to content

Independent R&D project · Cologne

Python and FastAPI

A modular monolith with typed boundaries, import contracts, an append-only trail in PostgreSQL and policy as data, with tested reference examples.

Non-normative

Companion version
1.0
Maps to COADF Core
2.2
Status
Current
Last reviewed
Profile version
1.0
Code examples
Illustrative reference examples

Architectural properties addressed

  • P-1A typed, validated boundary around the model call; in this reference implementation, an authoritative value exists only after verification by a named person.
  • P-4One audit identity beside the OpenTelemetry context; an append-only trail in PostgreSQL whose writes absorb an exact retry and refuse a conflicting one.
  • P-5The extraction method and the source location travel with every proposed value and survive serialisation.
  • P-6Import contracts and a publication fence, each with a proof of teeth.
  • P-7A port the domain owns, and an adapter that is the only module aware of the vendor.
  • P-8Decisions from versioned policy material, kept with their revision or refused.
  • P-2Only at the depth COADF publishes. The examples carry no confidence representation.
  • P-3Only at the depth COADF publishes: in this reference implementation, attribute-level verification by a named person, a rejected value left empty. When review is required, and how reviews are organised, are not shown.

Architectural intent

In a Python service the model client and the domain code are often written by the same people, in the same repository, in the same afternoon, which is how the probabilistic side and the authoritative side end up in one module calling each other directly. FastAPI and Pydantic make the boundary cheap to express as types and to validate at run time. They also have defaults that are right for a web API and wrong for a boundary: a Pydantic model ignores fields it does not declare and coerces compatible types unless strict mode is on, and FastAPI filters every response to its response model without complaint.

This profile shows one way to keep the COADF properties in a single Python service: a modular monolith with import contracts, PostgreSQL for the audit trail, OpenTelemetry for execution context, Open Policy Agent behind an interface for policy. None of these is required. Each is a place where the property is either kept or quietly lost.

Technology mapping

Python and FastAPI: Technology mapping
Architectural propertyPython and FastAPI
Boundary typeFrozen Pydantic models with extra="forbid" and strict=True; a distinct dataclass for verified values
Runtime validationmodel_validate_json on the raw reply; FastAPI request validation, answered with 422
Output contractAn explicit response_model that declares the provenance
Architecture ruleimport-linter forbidden contracts, run by lint-imports
Execution contextOpenTelemetry propagation (inject, extract); contextvars copied into threads
Audit trailPostgreSQL: insert and select only for the application role, triggers refusing rewrites, a unique key whose conflicts are compared before they count as retries
Standards isolationA typing.Protocol port; an httpx adapter that turns the vendor's HTTP outcomes into not found, unavailable and rejected
PolicyOPA over its REST API behind a small interface; the answer type-checked, never coerced, and the revision required on every decision
Publication fenceA scan of the built output with a pattern file kept outside it; a digest-checked proof of teeth

Reference pattern

A single package, refapp, in a synthetic maintenance domain: a model proposes the next service date and a component class from a technician's service report. The modules are the boundaries.

The modules of the reference package and the property each one holds
ModuleRoleMay not import
boundary.pyThe contract a model reply must meetnothing of the domain
inference.pyCalls a model; returns a Proposalrecords, audit, api
formats.pyDeterministic checks on a proposed valueadapters, HTTP clients
records.pyVerified values; the reviewed path inadapters, HTTP clients
api.pyThe HTTP surface of the boundary
tracing.pyExecution context and audit identity
audit.py, schema.sqlThe append-only trail
classification.pyThe port for an external serviceadapters, HTTP clients
adapters/The vendor, and only the vendor
policy.py, policy/Policy material, rule and client
fence.pyThe publication fence

P-1 · The boundary

Illustrative reference exampleA typed contract around a model call
Purpose
Give a model call a contract: what it may return, and what must come with what it returns. A deliberately partial reference boundary, built only from what COADF publishes at this level.
Architectural property
A probabilistic component returns a value together with how the value was obtained, and anything else is rejected at the boundary (P-1). The method travels with the value, which is what makes a later disclosure of machine extraction possible (P-5).
refapp/boundary.pypythonP-1 · P-5
"""Illustrative reference example: the typed boundary around a model call. A probabilistic component returns a value together with how it was obtained,never a bare value. Anything that does not parse into a Proposal is rejectedhere, before it reaches code that treats values as facts. Deliberately partial: there is no confidence field. COADF publishes that aconfidence accompanies the value, not how it is represented, so none is invented.""" from typing import Literal from pydantic import BaseModel, ConfigDict, Field # frozen: nothing downstream can edit a proposal in place.# extra="forbid": a field the contract does not name is an error, not ignored.# strict: no coercion, so "12" is not quietly accepted where an int is expected.STRICT = ConfigDict(frozen=True, extra="forbid", strict=True)  class Derivation(BaseModel):    model_config = STRICT     method: Literal["language-model"]    model: str = Field(min_length=1)  # the model identifier as deployed    prompt_revision: str = Field(min_length=1)  # the prompt template the call used    source_digest: str = Field(pattern=r"^[0-9a-f]{64}$")  # SHA-256 of the source read    source_start: int = Field(ge=0)  # where in the source the passage begins    source_end: int = Field(ge=0)  # and where it ends  class Proposal(BaseModel):    model_config = STRICT     attribute: Literal["next_service_due", "component_class"]    value: str = Field(min_length=1, max_length=64)    provenance: Derivation  def parse_model_reply(raw: str) -> Proposal:    """Parse and validate the model's JSON in one step, or raise ValidationError."""    return Proposal.model_validate_json(raw)

What it intentionally omits

  • Confidence, deliberately. COADF P-1 has a probabilistic component report a confidence with its value; that is the public architectural property. How confidence is expressed, assigned and used is a withheld implementation detail of P-2, which COADF publishes as a principle only. So this boundary carries the method and the provenance, has no confidence field, and invents no representation for it: it is partial on purpose, not complete.
  • The model call, its retries and the prompt. The boundary is the same whichever client produced the string.
  • Whether the value is right. A schema checks form; it cannot know whether a date is the correct one for this asset.
  • Storage. A Proposal is a message, not a record.

How to verify

Feed the parser replies that are almost right: an undeclared field, a number where the contract says text, a missing provenance, an attribute nobody asked for. Each must raise (tests/test_boundary.py). Then delete extra="forbid" in a scratch copy and watch the undeclared-field test fail.

Failure mode addressed

A model reply parsed with json.loads and read as a dictionary. An extra "verified": true, a coerced number or a missing source passes straight through, and the value becomes indistinguishable from data somebody checked.

Illustrative reference exampleDeterministic checks around a stochastic value
Purpose
Check the proposed value with code that does not depend on the model at all.
Architectural property
Deterministic validation surrounds stochastic output: the same value always gets the same verdict, and the verdict can be reproduced from the value alone (P-1).
refapp/formats.pypythonP-1
"""Deterministic checks on a proposed value. They read the value, never the model.""" import refrom datetime import date from refapp.boundary import Proposal _CALENDAR_DATE = re.compile(r"\d{4}-\d{2}-\d{2}")  def check_format(proposal: Proposal) -> None:    """Raise ValueError when the value is not well formed for its attribute."""    if proposal.attribute == "next_service_due":        if not _CALENDAR_DATE.fullmatch(proposal.value):            raise ValueError("next_service_due must be written YYYY-MM-DD")        date.fromisoformat(proposal.value)  # and must be a real calendar date

What it intentionally omits

  • Checks that need other records or other sources. This example checks one value against its own attribute's rules and nothing else.
  • Localised date formats. The contract fixes one written form on purpose.

How to verify

A value that matches the pattern but is not a calendar date (2026-02-30) never verifies (tests/test_boundary.py).

Failure mode addressed

Trusting the model to have produced a valid value because the prompt asked for one: the check that is not written is the one that fails in production.

Illustrative reference exampleA reviewed path into authoritative records
Purpose
One conservative reference path by which a model-derived value becomes authoritative: in this example every such value goes to a named person, who looks at it beside its source and accepts or rejects it.
Architectural property
A probabilistic component cannot directly become an authoritative output (P-1). In this reference implementation a model-derived value reaches a record only through human verification, and a rejected value leaves the attribute empty (P-3, at the depth COADF publishes). It is one implementation, not a COADF topology.
refapp/records.pypythonP-1 · P-3
"""A reviewed path into authoritative records, one conservative reference. This example sends every model-derived value through the verification of anamed person. When a real system requires review is its own rule, which thisexample does not define; nor does it say how reviews are organised.""" from dataclasses import dataclassfrom datetime import datetime from refapp.boundary import Proposalfrom refapp.formats import check_format  @dataclass(frozen=True)class ReviewOutcome:    reviewer: str    accepted: bool    reason: str    decided_at: datetime  @dataclass(frozen=True)class VerifiedValue:    attribute: str    value: str    proposal: Proposal  # the provenance stays attached after verification    review: ReviewOutcome  def accept(proposal: Proposal, review: ReviewOutcome) -> VerifiedValue | None:    """Rejected means absent: the attribute stays empty and nothing is guessed."""    check_format(proposal)    if not review.reviewer.strip():        raise ValueError("a verification needs a named reviewer")    if not review.accepted:        return None    return VerifiedValue(proposal.attribute, proposal.value, proposal, review)

What it intentionally omits

  • When review is required. COADF publishes that review is triggered by confidence; the rule that decides it is not published, and this example defines none. It verifies every model-derived value, the conservative choice, which is also what the public fence F-03 requires of values derived by a language model.
  • How reviews are organised. Whether reviews are queued, ordered, assigned or triggered is not published by COADF and not implied here: the code takes one proposal and one decision at a time.
  • Authentication of the reviewer. reviewer is a string here; in a real system it is an authenticated identity, checked where the decision is received.
  • Protection against a developer building VerifiedValue by hand. The type makes an accidental promotion visible, the import contract makes it impossible from the inference module, and code review covers the rest.

How to verify

A rejected proposal returns None; an accepted one keeps its provenance; an empty reviewer name is refused (tests/test_boundary.py).

Failure mode addressed

A verified flag on the object the model produced, defaulting to true or set by whatever code saves the record. Inferred and verified values become the same thing in storage, and nothing downstream can tell them apart.

Illustrative reference exampleThe boundary as a rule the build enforces
Purpose
Turn the boundary from a box on a diagram into a check that fails the build.
Architectural property
The probabilistic module has no import path to authoritative records or to the audit trail, and domain code has none to vendor adapters or HTTP clients (P-1, P-7). The rule runs on every change (P-6).
pyproject.tomltomlP-1 · P-6 · P-7
# Illustrative reference example: architecture rules as data, run by `lint-imports`. [tool.importlinter]root_package = "refapp"include_external_packages = true [[tool.importlinter.contracts]]name = "The probabilistic side cannot reach authoritative records or the audit trail"type = "forbidden"source_modules = ["refapp.inference"]forbidden_modules = ["refapp.records", "refapp.audit", "refapp.api"] [[tool.importlinter.contracts]]name = "Domain code does not depend on vendor adapters or HTTP clients"type = "forbidden"source_modules = ["refapp.records", "refapp.formats", "refapp.classification"]forbidden_modules = ["refapp.adapters", "httpx"] [tool.pytest.ini_options]testpaths = ["tests"]pythonpath = ["."]

What it intentionally omits

  • Imports made by name at run time, such as importlib.import_module with a computed string. A static import contract checks the import statements it can read; a plugin loader needs a check of its own.
  • Data paths that do not go through imports: a shared database connection, a queue both sides can reach. The database grants and the network policy in the Cloud-Native profile address those.
  • A layered contract for the whole application. Two forbidden contracts are enough to show the mechanism.

How to verify

lint-imports reports both contracts kept. Append from refapp.records import accept to inference.py in a scratch copy: the first contract is reported broken and the command exits non-zero. Remove the line and it is kept again.

Failure mode addressed

The boundary exists as a box on an architecture diagram and nowhere else, until somebody needs a value from the other side in a hurry.

Illustrative reference exampleNegative tests: one planted defect each
Purpose
Verify the boundary by what it refuses, not only by what it accepts.
Architectural property
Each test plants one defect the boundary exists to stop, so deleting a constraint turns a test red.
tests/test_boundary.pypythonP-1 · P-6
"""The boundary refuses what it must refuse. Each test is one defect, planted.""" import jsonfrom datetime import UTC, datetime import pytestfrom pydantic import ValidationError from refapp.boundary import parse_model_replyfrom refapp.records import ReviewOutcome, accept SOURCE = "ab" * 32  # a well-formed SHA-256 hex digest, synthetic  def reply(**changes: object) -> str:    body: dict[str, object] = {        "attribute": "next_service_due",        "value": "2026-10-01",        "provenance": {            "method": "language-model",            "model": "example-model-2026-06",            "prompt_revision": "service-report-v3",            "source_digest": SOURCE,            "source_start": 118,            "source_end": 131,        },    }    body.update(changes)    return json.dumps(body)  def review(accepted: bool, reviewer: str = "j.doe") -> ReviewOutcome:    return ReviewOutcome(reviewer, accepted, "matches the report", datetime.now(UTC))  def test_a_well_formed_reply_parses() -> None:    assert parse_model_reply(reply()).provenance.method == "language-model"  @pytest.mark.parametrize(    "defect",    [        {"verified": True},  # the model claims more than it may        {"value": 20261001},  # a number where the contract says text        {"provenance": None},  # a bare value, with no provenance at all        {"attribute": "owner_name"},  # an attribute nobody asked for    ],)def test_the_boundary_rejects_it(defect: dict[str, object]) -> None:    with pytest.raises(ValidationError):        parse_model_reply(reply(**defect))  def test_a_rejected_proposal_leaves_the_attribute_empty() -> None:    assert accept(parse_model_reply(reply()), review(accepted=False)) is None  def test_verification_keeps_the_provenance() -> None:    verified = accept(parse_model_reply(reply()), review(accepted=True))    assert verified is not None and verified.proposal.provenance.source_digest == SOURCE  def test_a_value_that_is_not_a_calendar_date_never_verifies() -> None:    with pytest.raises(ValueError):        accept(parse_model_reply(reply(value="2026-02-30")), review(accepted=True))  def test_nobody_is_not_a_reviewer() -> None:    with pytest.raises(ValueError):        accept(parse_model_reply(reply()), review(accepted=True, reviewer=" "))

What it intentionally omits

  • Generated malformed input. A short list of named defects is easier to review, and each one documents a failure mode.
  • Anything about the quality of the model's answers. The boundary does not make a model right.

How to verify

Run pytest. The suite has teeth only if removing a constraint makes a test fail; the harness deletes extra="forbid" in a scratch copy and the undeclared-field test fails.

Failure mode addressed

A suite that only feeds the happy path: every constraint could be deleted and it would stay green.

Illustrative reference exampleThe response model is part of the contract
Purpose
The HTTP surface of the boundary: what an inference worker may post, and what a reviewer's screen receives.
Architectural property
FastAPI validates the request against the model and filters the response to the response model, so the provenance, including the passage location a reviewer needs in order to see the value beside its source, must be declared or it never leaves the service.
refapp/api.pypythonP-1 · P-5
"""The HTTP surface of the boundary. The response model is part of thecontract: a field it does not declare never leaves the service.""" from fastapi import FastAPI, HTTPExceptionfrom pydantic import BaseModel from refapp.boundary import Derivation, Proposal app = FastAPI()_pending: dict[str, list[Proposal]] = {}  # stand-in for real storage  class ProposalOut(BaseModel):    attribute: str    value: str    provenance: Derivation  # delete this line and every response loses it, silently  @app.post("/reports/{report_id}/proposals", status_code=201)def add_proposal(report_id: str, proposal: Proposal) -> None:    """The inference worker posts here. An undeclared field, say "verified", is a 422."""    _pending.setdefault(report_id, []).append(proposal)  @app.get("/reports/{report_id}/proposals", response_model=list[ProposalOut])def list_proposals(report_id: str) -> list[Proposal]:    """What a reviewer's screen needs: each value with the passage it came from."""    if report_id not in _pending:        raise HTTPException(status_code=404)    return _pending[report_id]

What it intentionally omits

  • Authentication and authorisation of both endpoints.
  • Persistence. A dictionary stands in for storage.
  • The decision endpoint through which a reviewer accepts or rejects; see records.accept.
  • Any order of presentation. The list comes back in insertion order, which is not a review order: COADF does not publish how review is ordered, and nothing here orders it.

How to verify

Post a reply with an extra verified field: 422. Post a well-formed one and list it: the provenance is in the response. Then declare a response model without the provenance: the same object comes back without it, and nothing fails (tests/test_api.py reproduces this).

Failure mode addressed

Provenance lost at serialisation. A response model that omits the provenance drops it silently, and the review screen shows a value with nothing to check it against.

P-4 · The trace and the trail

Illustrative reference exampleExecution context and audit identity, side by side
Purpose
Carry the execution context across a message and a thread, and carry the audit identity beside it, never instead of it.
Architectural property
One traceable identity connects the steps of a transaction (P-4). The OpenTelemetry context is correlated with it through a span attribute, and never substituted for it.
refapp/tracing.pypythonP-4
"""Two identifiers travel together and are never confused. The OpenTelemetry context describes one execution: a request, a message, ajob. The audit trace_id names one transaction across all of them, for as longas its records exist. They are correlated, never substituted for each other. Correlation copies the audit trace_id into telemetry, which has its ownexporters, vendors, access rules and retention. Keep it an opaque reference:never personal data, never a secret, never something that means more outside.""" import asyncioimport contextvarsfrom collections.abc import Callablefrom concurrent.futures import Executor, Futurefrom typing import Any from opentelemetry import propagate, trace tracer = trace.get_tracer("refapp")audit_trace_id: contextvars.ContextVar[str] = contextvars.ContextVar("audit_trace_id")  def publish(send: Callable[..., None], body: dict[str, Any]) -> None:    """Producer side: execution context in headers, audit identity in the body."""    headers: dict[str, str] = {}    propagate.inject(headers)  # W3C traceparent with the default propagators    send(headers=headers, body=body)  def consume(    headers: dict[str, str], body: dict[str, Any], work: Callable[[], None]) -> None:    """Consumer side: continue the execution, and require the audit identity."""    ctx = propagate.extract(headers)    token = audit_trace_id.set(body["trace_id"])  # a KeyError, never a freshly minted id    try:        with tracer.start_as_current_span("process-report", context=ctx) as span:            span.set_attribute("refapp.audit.trace_id", body["trace_id"])  # opaque reference            work()    finally:        audit_trace_id.reset(token)  async def run_blocking(fn: Callable[..., Any], *args: Any) -> Any:    return await asyncio.to_thread(fn, *args)  # documented to carry the context along  def submit_with_context(pool: Executor, fn: Callable[..., Any], *args: Any) -> Future[Any]:    ctx = contextvars.copy_context()  # pool.submit(fn) alone does not carry the caller's    return pool.submit(ctx.run, fn, *args)

What it intentionally omits

  • Exporters and samplers; see the Collector configuration in the Cloud-Native profile.
  • A real broker client. send is any function that takes headers and a body.
  • Where the audit trace_id is minted: at intake, once, and nowhere downstream.
  • Baggage. The audit identity travels in the message body, where it is part of the contract, rather than in baggage, which instrumentation passes on to downstream services, third parties included.
  • What the correlation exposes. The span attribute copies the audit trace_id into telemetry, which has its own exporters, vendors, access controls and retention, usually looser than the audit store's. Classify it before it is exported: an opaque reference, never personal data or a secret. Where an audit identifier carries meaning, correlate through a separate, non-sensitive reference instead.

How to verify

Producer and consumer spans share one distributed trace, and the consumer's span carries the audit identity. A message without the audit identity raises KeyError instead of minting a new one. A bare thread-pool submission loses the context; the copied context keeps it; asyncio.to_thread keeps it (tests/test_tracing.py).

Failure mode addressed

The trace identifier regenerated halfway through: a consumer that falls back to a fresh identifier when the field is missing, or a worker thread that starts without the caller's context, leaves two halves that no query can join.

Illustrative reference exampleAn append-only audit trail in PostgreSQL
Purpose
An audit trail with the nine fields COADF publishes, stored so that the application can add and read and the database refuses rewrites.
Architectural property
The trail is append-only and a correction is a new entry (P-4). A step has one identity: an exact retry is the same entry, and the same identity with other content is refused by audit.py.
schema.sqlsqlP-4
-- Illustrative reference example: an append-only audit trail in PostgreSQL.-- The nine columns are the fields COADF publishes for its audit trail schema.-- The vocabulary of event_type belongs to the application and is deliberately-- outside this example: the lesson here is storage that refuses rewrites. CREATE TABLE audit_entries (    trace_id    text        NOT NULL,    "timestamp" timestamptz NOT NULL,  -- when the step happened, not when the row arrived    event_type  text        NOT NULL,    actor       jsonb       NOT NULL,  -- {"kind": "system" | "person" | "agent", "id": …}    input       jsonb       NOT NULL,  -- what the step was about: a reference, not a copy    output      jsonb,    decision    jsonb,    source_hash text,    immutable   boolean     NOT NULL DEFAULT true CHECK (immutable),    -- The identity of one step. A retry must match the stored entry exactly;    -- refapp/audit.py refuses a second entry with this identity and other content.    UNIQUE (trace_id, "timestamp", event_type, input)); -- The application connects as a role that can add and read, nothing else.CREATE ROLE trail_appender NOLOGIN;GRANT INSERT, SELECT ON audit_entries TO trail_appender; -- Defence in depth for sessions that do hold broader rights.CREATE FUNCTION audit_entries_refuse() RETURNS trigger LANGUAGE plpgsql AS $$BEGIN    RAISE EXCEPTION 'audit_entries is append-only: % refused', TG_OP;END $$; CREATE TRIGGER audit_entries_no_rewrite    BEFORE UPDATE OR DELETE ON audit_entries    FOR EACH ROW EXECUTE FUNCTION audit_entries_refuse(); -- TRUNCATE fires no DELETE trigger, so it needs a trigger of its own.CREATE TRIGGER audit_entries_no_truncate    BEFORE TRUNCATE ON audit_entries    FOR EACH STATEMENT EXECUTE FUNCTION audit_entries_refuse();

What it intentionally omits

  • Tamper evidence. Privileges and triggers are controls inside the database's own trust boundary: a superuser bypasses permission checks, and the table's owner can disable its triggers. Detecting a rewrite by somebody with those rights needs a control outside the database, which this example does not show.
  • Retention, archiving and export.
  • The event vocabulary. event_type is required text. Which events an application records is its own design, and deliberately outside this example.
  • An order among entries with the same timestamp. Reconstruction orders by timestamp, and equal timestamps are intentionally not ordered by this example: no published field orders them. A single writer per transaction, or an order assigned by one, is a storage decision beyond the published fields.
  • The migration and the role that owns the table. The application never connects as that role.

How to verify

Against a real PostgreSQL: the application role cannot update, delete or truncate; the owner meets the triggers; an exact retry adds nothing; the same identity with other content is refused; a correction adds an entry; and, deliberately, the owner can disable the triggers and delete (tests/test_audit.py).

Failure mode addressed

A correction written as an UPDATE, which destroys the record of what was believed before, and the same loss by TRUNCATE, which fires no DELETE trigger at all.

Illustrative reference exampleIdempotent append that refuses a conflict, one-query reconstruction
Purpose
Make a retry harmless and a conflicting write loud, and read one transaction back in a single query.
Architectural property
The timestamp is stamped when the step runs, so a retry resends the identical entry. On a conflict the stored entry is compared first: the same content is a retry, other content raises IdempotencyCollision. Reconstruction is one query by trace_id, ordered by timestamp; equal timestamps are intentionally not ordered (P-4).
refapp/audit.pypythonP-4
"""Append to the audit trail, and read one transaction back. The timestamp is taken when the step runs and travels with the entry, so aretry resends the identical row. The unique key makes that retry harmless; itmust not make a different entry disappear, so a conflict is compared withwhat is stored before it is called a retry.""" from dataclasses import dataclassfrom datetime import datetimefrom typing import Any from psycopg import Connectionfrom psycopg.types.json import Jsonb _INSERT = """    INSERT INTO audit_entries (trace_id, "timestamp", event_type, actor, input,                             output, decision, source_hash, immutable)    VALUES (%s, %s, %s, %s, %s, %s, %s, %s, true)    ON CONFLICT (trace_id, "timestamp", event_type, input) DO NOTHING"""_STORED = """    SELECT actor, output, decision, source_hash FROM audit_entries    WHERE trace_id = %s AND "timestamp" = %s AND event_type = %s AND input = %s"""  @dataclass(frozen=True)class AuditEntry:    trace_id: str    timestamp: datetime    event_type: str    actor: dict[str, Any]    input: dict[str, Any]    output: dict[str, Any] | None = None    decision: dict[str, Any] | None = None    source_hash: str | None = None  class IdempotencyCollision(Exception):    """Same step identity, different content: never a retry, never silent."""  def _json(value: dict[str, Any] | None) -> Jsonb | None:    return None if value is None else Jsonb(value)  def append(conn: Connection, entry: AuditEntry) -> bool:    """True if written; False if this exact entry is already there."""    identity = (entry.trace_id, entry.timestamp, entry.event_type, Jsonb(entry.input))    content = (entry.actor, entry.output, entry.decision, entry.source_hash)    with conn.cursor() as cur:        cur.execute(_INSERT, (entry.trace_id, entry.timestamp, entry.event_type,                              Jsonb(entry.actor), Jsonb(entry.input), _json(entry.output),                              _json(entry.decision), entry.source_hash))        if cur.rowcount == 1:            return True        cur.execute(_STORED, identity)        if cur.fetchone() != content:            raise IdempotencyCollision(f"{entry.trace_id}: {entry.event_type} differs")    return False  def chain(conn: Connection, trace_id: str) -> list[tuple[Any, ...]]:    """Every entry of one transaction, by timestamp. Entries with equal timestamps    are intentionally not ordered by this example: no published field orders them."""    with conn.cursor() as cur:        cur.execute('SELECT * FROM audit_entries WHERE trace_id = %s ORDER BY "timestamp"',                    (trace_id,))        return cur.fetchall()

What it intentionally omits

  • The transaction that writes the entry together with the state change it records. Written separately, one can exist without the other.
  • Export as JSON, which P-4 also asks for.
  • Connection management, and which role the connection uses.
  • Which of two conflicting entries is right. The example refuses the second and keeps the first; deciding between them is somebody's job, not the database's.
  • Concurrent writers of the same step. The example was tested one writer at a time; under concurrency, check what your isolation level lets the comparing read see.

How to verify

Append the same entry twice: True, then False, and one row. Append the same identity with a different output: IdempotencyCollision, and the stored entry is unchanged (tests/test_audit.py). Reconstruct with chain().

Failure mode addressed

Retries that produce ambiguous events, and conflicts that vanish: a timestamp taken at insert time makes every retry a new entry, and a bare ON CONFLICT DO NOTHING discards a different entry that happens to share the key, without an error.

Illustrative reference exampleVerify persistence, not logs
Purpose
Check the append-only property against the real enforcement layer, and pin the limitation as a test.
Architectural property
The property is asserted where it is enforced, in the database; what the controls do not stop is a test too, so it cannot be forgotten.
tests/test_audit.pypythonP-4 · P-6
"""Append-only, checked against a real PostgreSQL. Skipped without one. Set REFAPP_PG_DSN to a database this test may create a table in. The eventtypes are synthetic. The last test is deliberate: it shows what the controls doNOT stop.""" import osimport pathlibfrom datetime import UTC, datetime import psycopgimport pytestfrom psycopg import errors from refapp.audit import AuditEntry, IdempotencyCollision, append, chain DSN = os.environ.get("REFAPP_PG_DSN")pytestmark = pytest.mark.skipif(not DSN, reason="REFAPP_PG_DSN is not set")SCHEMA = (pathlib.Path(__file__).parent.parent / "schema.sql").read_text()  @pytest.fixturedef conn():    with psycopg.connect(DSN, autocommit=True) as owner:        owner.execute("DROP TABLE IF EXISTS audit_entries")        owner.execute("DROP FUNCTION IF EXISTS audit_entries_refuse")        owner.execute("DROP ROLE IF EXISTS trail_appender")        owner.execute(SCHEMA)        yield owner        owner.execute("DROP TABLE audit_entries")        owner.execute("DROP FUNCTION audit_entries_refuse")        owner.execute("DROP ROLE trail_appender")  def step(event_type: str, at: datetime, **fields: object) -> AuditEntry:    return AuditEntry(trace_id="tr-3", timestamp=at, event_type=event_type,                      actor={"kind": "system", "id": "intake-worker"},                      input={"report": "r-3"}, **fields)  def test_a_retry_of_the_same_step_is_the_same_entry(conn) -> None:    entry = step("intake", datetime(2026, 9, 1, 8, 0, tzinfo=UTC), source_hash="ab" * 32)    assert append(conn, entry) is True    assert append(conn, entry) is False  # the retry    assert len(chain(conn, "tr-3")) == 1  def test_same_identity_other_content_is_never_a_silent_retry(conn) -> None:    at = datetime(2026, 9, 1, 8, 0, tzinfo=UTC)    append(conn, step("decision", at, output={"next_service_due": "2026-10-01"}))    with pytest.raises(IdempotencyCollision):        append(conn, step("decision", at, output={"next_service_due": "2026-11-01"}))    [stored] = chain(conn, "tr-3")    assert stored[5] == {"next_service_due": "2026-10-01"}  # the first entry, unchanged  def test_a_correction_is_a_new_entry(conn) -> None:    first = datetime(2026, 9, 1, 8, 0, tzinfo=UTC)    append(conn, step("decision", first, output={"next_service_due": "2026-10-01"}))    corrected = {"event_type": "decision", "timestamp": first.isoformat()}    append(conn, AuditEntry(        trace_id="tr-3", timestamp=datetime(2026, 9, 2, 9, 30, tzinfo=UTC),        event_type="decision", actor={"kind": "person", "id": "j.doe"},        input={"attribute": "next_service_due", "corrects": corrected},        output={"next_service_due": "2026-11-01"},        decision={"action": "corrected", "reason": "the report was misread"}))    assert len(chain(conn, "tr-3")) == 2  # both stay; the later one supersedes  def test_the_application_role_cannot_rewrite_or_erase(conn) -> None:    append(conn, step("intake", datetime(2026, 9, 1, 8, 0, tzinfo=UTC)))    conn.execute("SET ROLE trail_appender")    try:        for statement in ("UPDATE audit_entries SET actor = '{}'", "DELETE FROM audit_entries",                          "TRUNCATE audit_entries"):            with pytest.raises(errors.InsufficientPrivilege):                conn.execute(statement)    finally:        conn.execute("RESET ROLE")  def test_even_the_owner_meets_the_triggers(conn) -> None:    append(conn, step("intake", datetime(2026, 9, 1, 8, 0, tzinfo=UTC)))    for statement in ("UPDATE audit_entries SET actor = '{}'", "DELETE FROM audit_entries",                      "TRUNCATE audit_entries"):        with pytest.raises(errors.RaiseException):            conn.execute(statement)  def test_what_the_controls_do_not_stop(conn) -> None:    """The owner can switch the triggers off. The app must never connect as owner."""    append(conn, step("intake", datetime(2026, 9, 1, 8, 0, tzinfo=UTC)))    conn.execute("ALTER TABLE audit_entries DISABLE TRIGGER USER")    conn.execute("DELETE FROM audit_entries")    assert chain(conn, "tr-3") == []

What it intentionally omits

  • A database in continuous integration. The tests skip without REFAPP_PG_DSN, and a skip is not a pass: the job that runs them must provide the database, or report the check as not run.

How to verify

Run against a scratch PostgreSQL: all six tests pass, including the conflicting retry that must fail and the one that proves the owner can delete.

Failure mode addressed

False immutability: a trigger taken as proof that history cannot change, when the role that owns the table can switch it off.

P-7 · The port and the adapter

Illustrative reference exampleA port the domain owns
Purpose
The domain's side of an external classification service: a port, and the types behind it.
Architectural property
External vocabularies stay behind a port the domain owns. A failing service makes the record less informative and never stops it, and the record says whether the service was unavailable or the integration is broken (P-7).
refapp/classification.pypythonP-7
"""A port for an external classification service, and the domain's use of it. The domain speaks only these types. It never sees the vendor's client, itsresponse shape or its identifiers, so a new vendor or a new transport is anadapter change. A new version of the scheme itself can change what a codemeans, and then the domain's mapping changes too.""" from dataclasses import dataclassfrom typing import Literal, Protocol  @dataclass(frozen=True)class Classification:    scheme: str  # which external scheme answered    version: str  # a code means something only within one version    code: str    licensed: bool  # licensed content is marked, so exports can keep it apart  class LookupUnavailable(Exception):    """Transient: a timeout, an outage, a server error. It may work later."""  class LookupRejected(Exception):    """Not transient: refused credentials, a bad request, an answer in a shape    nobody agreed to. Retrying does not help; somebody has to fix it."""  class ClassificationLookup(Protocol):    def lookup(self, component_class: str) -> Classification | None: ...  @dataclass(frozen=True)class Enrichment:    classification: Classification | None    status: Literal["found", "not-found", "unavailable", "rejected"]  def enrich(lookup: ClassificationLookup, component_class: str) -> Enrichment:    """A failing service makes the record less informative. It never stops the    record, and the status keeps an outage apart from a broken integration."""    try:        found = lookup.lookup(component_class)    except LookupUnavailable:        return Enrichment(None, "unavailable")    except LookupRejected:        return Enrichment(None, "rejected")    return Enrichment(found, "found" if found is not None else "not-found")

What it intentionally omits

  • What an external answer does to confidence. COADF P-7 says an external service may raise the confidence of an attribute and can never gate an output; how the raising works is not published. Here the answer is recorded, with its status, and nothing more.
  • Caching, and how long a licence allows an external answer to be kept.
  • The mapping from external codes to the domain's own concepts, which is versioned data in a real system. A new vendor or transport stays in the adapter; a new version of the scheme itself can change what codes mean, and then this mapping changes too.
  • Who is told. rejected does not heal by retrying, so it needs an alert that unavailable may not.

How to verify

A test double satisfies the port; an outage records unavailable and a broken integration records rejected (tests/test_classification.py). The import contract keeps httpx and the adapters out of this module.

Failure mode addressed

A remote outage that blocks unrelated processing: the domain calls the vendor directly, synchronously and without a timeout, and a vendor incident becomes your incident.

Illustrative reference exampleVendor names stop at the adapter
Purpose
The only module that knows the vendor's HTTP shape and the vendor's names.
Architectural property
The vendor's classId and restricted are translated into the domain's code and licensed, the scheme version is pinned on every call, and the vendor's HTTP outcomes become three domain outcomes: not found, unavailable (transient) and rejected (the integration is wrong). The provider's HTTP vocabulary does not become the domain's (P-7).
refapp/adapters/http_classification.pypythonP-7
"""The adapter: the only module that knows the vendor's HTTP shape.""" import httpx from refapp.classification import Classification, LookupRejected, LookupUnavailable  class HttpClassificationLookup:    def __init__(self, client: httpx.Client, scheme: str, version: str) -> None:        self._client = client  # base URL and timeout are set by whoever builds the client        self._scheme = scheme        self._version = version     def lookup(self, component_class: str) -> Classification | None:        try:            response = self._client.get(                f"/classes/{component_class}", params={"release": self._version}            )        except httpx.TransportError as exc:  # timeouts and connection failures            raise LookupUnavailable() from exc        if response.status_code == 404:            return None        if response.status_code == 429 or response.is_server_error:            raise LookupUnavailable()        if response.status_code != 200:  # our request or our credentials are wrong            raise LookupRejected(f"vendor refused the request: {response.status_code}")        return self._translate(response)     def _translate(self, response: httpx.Response) -> Classification:        """The vendor's names stop here, and so does any shape we did not agree to."""        try:            body = response.json()            code, restricted = body["classId"], body["restricted"]        except (ValueError, KeyError, TypeError) as exc:            raise LookupRejected("unexpected vendor response") from exc        if not isinstance(code, str) or not code or not isinstance(restricted, bool):            raise LookupRejected("unexpected vendor response")        return Classification(self._scheme, self._version, code, licensed=restricted)

What it intentionally omits

  • Retries, circuit breaking and rate limits.
  • Authentication to the vendor.
  • The licence terms themselves, and any licensed content: this example contains none.
  • A finer taxonomy. Three outcomes are enough to keep an outage apart from a broken integration; a real adapter may need more.

How to verify

With httpx.MockTransport: a 200 is translated; a 404 is None; a timeout, a connection error, a 429 or a 5xx is LookupUnavailable; a 400, a 401, a non-boolean restricted, a missing field or a body that is not JSON is LookupRejected; every request carries the pinned version.

Failure mode addressed

The vendor's SDK becoming the domain model: its classes appear in domain signatures, its identifiers become internal meaning, and a licence or API change means rewriting domain code. Or every failure reported as the same unavailable, so a revoked credential looks like an outage and nobody fixes it.

P-8 · Policy as data

Illustrative reference examplePolicy material, with its revision
Purpose
The material a rule reads: which features each environment may enable, and the revision of this material.
Architectural property
Rules are data. Changing what is allowed changes this file, not the engine and not the rule (P-8).
policy/data.jsonjsonP-8
{  "refapp": {    "revision": "2026-09-01.2",    "features": {      "staging": ["report-export", "beta-search"],      "production": ["report-export"]    }  }}

What it intentionally omits

  • How policy material is protected on its way to the engine and checked on arrival. COADF does not publish that part of P-8, and this example shows none of it.
  • How revisions are assigned. OPA's own bundle documentation uses a Git commit hash as its example revision. Nothing in this example stops the material changing under the same revision; deriving the revision from the commit or the content is what does.

How to verify

Allow beta-search in production: the tests that pin the production answer fail, while the rule is untouched.

Failure mode addressed

Rules duplicated and hard-coded in several services, each changed on its own schedule, so the same request is allowed in one place and refused in another.

Illustrative reference exampleA rule that answers with its revision
Purpose
A rule that reads its material from data, and answers with the revision that produced the answer.
Architectural property
The decision carries rule-version provenance (P-8).
policy/feature_access.regoregoP-8
# Illustrative reference example: which features an environment may enable.# The rule reads policy material from data; changing what is allowed is a data# change, and the rule below stays exactly as it is.package refapp.feature_access default allow := false allow if input.feature in data.refapp.features[input.environment] # The answer carries the revision of the material that produced it.decision := {	"allow": allow,	"revision": data.refapp.revision,}

What it intentionally omits

  • Anything about confidence, review, publication or autonomy. The subject is deliberately generic.
  • Bundles and decision logs, which in OPA can carry a bundle revision on their own. The explicit revision keeps this example self-contained.
  • Whether a real rule should default to refusal. Here the default is false; for each real rule that is a decision for its owner.

How to verify

opa test policy/ runs six tests, including a misspelt input key that falls to the default instead of raising an error, and a replay of the same input under the same revision.

Failure mode addressed

A rule reload that changes the meaning of historical decisions: without the revision on the decision, a report computed today cannot say which rules produced last month's answers.

Illustrative reference exampleKeep the decision with its revision, or do not keep it
Purpose
The application's side: ask the engine through a small interface, and refuse any answer that does not name its revision.
Architectural property
A decision is kept with the revision that produced it, or it is not kept (P-8). The answer is parsed as strictly as it is kept: allow must be a boolean and revision a non-empty string, because in Python bool("false") is True. The engine sits behind an adapter, so the decision record keeps its shape if the engine changes.
refapp/policy.pypythonP-8
"""Ask a policy engine, and keep the answer together with its revision. The application depends on this small interface, not on the engine. SwapOpen Policy Agent for rule tables or a decision service and only the adapterchanges; the decision record keeps its shape.""" from dataclasses import dataclassfrom typing import Any import httpx  @dataclass(frozen=True)class PolicyDecision:    allow: bool    revision: str  # which policy material produced this answer    decision_id: str | None  # present when the engine keeps decision logs  class PolicyUnavailable(Exception):    """No usable decision. What that means is the decision owner's call; for    this feature-access example the caller refuses the feature."""  class OpaFeatureAccess:    _PATH = "/v1/data/refapp/feature_access/decision"     def __init__(self, client: httpx.Client) -> None:        self._client = client     def decide(self, environment: str, feature: str) -> PolicyDecision:        try:            response = self._client.post(                self._PATH, json={"input": {"environment": environment, "feature": feature}}            )            response.raise_for_status()            body = response.json()        except (httpx.HTTPError, ValueError) as exc:  # unreachable, refused, not JSON            raise PolicyUnavailable("no answer from the policy engine") from exc        return _decision(body)  def _decision(body: Any) -> PolicyDecision:    """Types are checked, never coerced: bool("false") is True."""    result = body.get("result") if isinstance(body, dict) else None    if result is None:  # OPA omits "result" when the path is undefined        raise PolicyUnavailable("undefined: no decision, which is not a denial")    if not isinstance(result, dict):        raise PolicyUnavailable("a result in an unexpected shape")    allow, revision = result.get("allow"), result.get("revision")    decision_id = body.get("decision_id")    if not isinstance(allow, bool):        raise PolicyUnavailable("allow is not a boolean")    if not isinstance(revision, str) or not revision.strip():        raise PolicyUnavailable("no decision that names its revision")    if decision_id is not None and not isinstance(decision_id, str):        raise PolicyUnavailable("decision_id is not a string")    return PolicyDecision(allow, revision, decision_id)

What it intentionally omits

  • Caching of decisions. A cache has to key on the revision as well as on the input.
  • Where the decision is recorded: in the audit trail, as the output of a policy step.
  • Refusal as a universal rule. Refusing is right for this example; the owner of each decision decides what an unavailable engine means for it.

How to verify

Against a real OPA server, the revision comes back and the same input answers the same way. Against a mocked engine, a valid denial is a decision; allow sent as the string "false", a missing allow, an empty or blank revision, a result of the wrong type, a non-string decision_id, an undefined path (OPA omits result), a body that is not JSON and an unreachable engine each raise PolicyUnavailable (tests/test_policy.py).

Failure mode addressed

An undefined policy path read as a refusal, a malformed answer coerced into the opposite decision, or an answer without a revision recorded anyway: the decision exists and cannot be reconstructed.

P-6 · A fence, and its proof of teeth

Illustrative reference exampleA publication fence whose list is never published
Purpose
Fence the built output, with a pattern list that stays outside the published tree.
Architectural property
A forbidden state in the built output stops the publication (P-6). Nothing scanned is not a pass, and a finding says where, not what.
refapp/fence.pypythonP-6
"""A publication fence over a built site. The patterns it matches are read from a file kept outside the build output:a list of what is watched is a map of what is protected, so the list itselfis never published and a finding reports where, not what.""" import pathlibimport reimport sys SCANNED_SUFFIXES = {".html", ".txt", ".json", ".js", ".xml", ".svg"}  def load_patterns(pattern_file: pathlib.Path) -> list[re.Pattern[str]]:    lines = pattern_file.read_text(encoding="utf-8").splitlines()    kept = [line for line in lines if line and not line.startswith("#")]    return [re.compile(line, re.IGNORECASE) for line in kept]  def scan(build_dir: pathlib.Path, patterns: list[re.Pattern[str]]) -> tuple[int, list[str]]:    scanned, findings = 0, []    for path in sorted(build_dir.rglob("*")):        if not path.is_file() or path.suffix not in SCANNED_SUFFIXES:            continue        scanned += 1        text = path.read_text(encoding="utf-8", errors="replace")        for number, line in enumerate(text.splitlines(), start=1):            if any(p.search(line) for p in patterns):                findings.append(f"{path.relative_to(build_dir)}:{number}")    return scanned, findings  def main(build_dir: str, pattern_file: str) -> int:    build = pathlib.Path(build_dir).resolve()    patterns_path = pathlib.Path(pattern_file).resolve()    if build in patterns_path.parents:        print("fence: the pattern file is inside the build output", file=sys.stderr)        return 2    patterns = load_patterns(patterns_path)    scanned, findings = scan(build, patterns)    if not patterns or scanned == 0:  # nothing checked is not the same as nothing found        print(f"fence: {len(patterns)} patterns, {scanned} files: refusing", file=sys.stderr)        return 2    for finding in findings:        print(f"fence: match at {finding}", file=sys.stderr)    print(f"fence: {scanned} files, {len(findings)} findings", file=sys.stderr)    return 1 if findings else 0  if __name__ == "__main__":    sys.exit(main(*sys.argv[1:3]))

What it intentionally omits

  • The patterns. The example ships a synthetic sentinel only. A real pattern list is a map of what is protected, which is why COADF P-6 does not publish its own.
  • Semantic review. A text fence cannot see a mechanism expressed through control flow, renamed identifiers or a diagram's geometry. It is necessary and not sufficient.
  • Binary formats, and content fetched at run time.

How to verify

The proof of teeth in tests/test_fence.py.

Failure mode addressed

A fence that passes because it read nothing: an empty pattern file, a build directory with no files, or a pattern file that has ended up inside the build output.

Illustrative reference exampleProof of teeth
Purpose
Plant a synthetic sentinel, watch the real entry point fail, restore the exact bytes, watch it pass.
Architectural property
A fence earns its place only when it has been watched failing on the real path (P-6).
tests/test_fence.pypythonP-6
"""Proof of teeth for the publication fence: plant a synthetic sentinel, watchthe real entry point fail, restore the exact bytes, watch it pass.""" import hashlib from refapp.fence import main # Not a real term of anything. It exists only to be caught.SENTINEL = "PLANTED-EXAMPLE-TOKEN-7F3Q"  def test_the_fence_has_teeth(tmp_path) -> None:    site = tmp_path / "site"    site.mkdir()    page = site / "index.html"    page.write_text("<main><p>Ordinary published copy.</p></main>", encoding="utf-8")    patterns = tmp_path / "patterns.txt"    patterns.write_text(SENTINEL + "\n", encoding="utf-8")    original = page.read_bytes()     assert main(str(site), str(patterns)) == 0     page.write_bytes(original + f"<p>{SENTINEL}</p>".encode())  # plant    assert main(str(site), str(patterns)) == 1     page.write_bytes(original)  # restore the exact bytes, and prove it    assert hashlib.sha256(page.read_bytes()).digest() == hashlib.sha256(original).digest()    assert main(str(site), str(patterns)) == 0  def test_nothing_scanned_is_not_a_pass(tmp_path) -> None:    (tmp_path / "empty-site").mkdir()    patterns = tmp_path / "patterns.txt"    patterns.write_text(SENTINEL + "\n", encoding="utf-8")    assert main(str(tmp_path / "empty-site"), str(patterns)) == 2  def test_a_pattern_file_inside_the_build_is_refused(tmp_path) -> None:    site = tmp_path / "site"    site.mkdir()    (site / "index.html").write_text("<p>copy</p>", encoding="utf-8")    leaked = site / "patterns.txt"    leaked.write_text(SENTINEL + "\n", encoding="utf-8")    assert main(str(site), str(leaked)) == 2

What it intentionally omits

  • Real protected vocabulary. The sentinel is synthetic and means nothing.
  • Any claim that a passing fence makes the output safe. The proof shows that the guard path works, not that semantic review is complete.

How to verify

Run it. Then make scan return early in a scratch copy and watch it fail.

Failure mode addressed

A restore that is not exact. Restoring from memory, or from a branch head, can silently change or lose content; comparing the digest before and after is what proves the fixture came back.

Failure modes

  1. Pydantic's defaults treated as a boundary

    By default a model ignores fields it does not declare, and outside strict mode it coerces compatible values. For an API that is forgiving; for a boundary it means a reply that claims "verified": true, or a number sent as text, passes without a trace. Set extra="forbid" and strict mode on boundary models, and test both.

  2. A response model that drops the provenance

    FastAPI filters every response to its response model. A response model written for the screen, without the provenance fields, strips them from every answer, and the reviewer sees a value with no source.

  3. model_construct in a hot path

    It builds a model without validation. Introduced for speed, it becomes the path that skips the boundary.

  4. Context that stops at a thread pool

    asyncio.to_thread propagates the current context; a bare executor submission does not, and neither does run_in_executor unless the caller copies the context first, as to_thread itself does. Spans and the audit identity then vanish in the one place work is slowest.

  5. A default factory that mints trace identifiers

    Field(default_factory=uuid4) on a message model looks harmless and turns every missing identifier into a new, unrelated one. Downstream of intake, the audit identity is required and never defaulted.

  6. Audit writes deferred until after the response

    FastAPI's background tasks run after the response is returned. An audit entry written there can be lost with the process after the client has already been told the step succeeded. Write it in the same transaction as the change.

  7. One ORM model for proposals and records

    A status column on the entity everybody reads. The day somebody forgets a filter, proposals appear as facts.

  8. A protocol taken as a runtime check

    A typing.Protocol is a static contract; a runtime check against it verifies only that the methods exist. Behaviour is what the contract tests check.

  9. Tests that skip, read as tests that pass

    A database test that skips without a connection string turns a missing database in CI into a green run. Count what executed, and fail the job when the audit tests did not run.

Verification

  • Unit test

    Passes when: pytest runs the negative boundary tests: undeclared field, wrong type, missing provenance, unexpected attribute, impossible date, rejected review.

    Proof of teeth: Delete extra="forbid" in a scratch copy: the undeclared-field test fails. The harness does this on every run.

  • Architecture test

    Passes when: lint-imports keeps both contracts.

    Proof of teeth: Plant from refapp.records import accept in inference.py: the contract is broken and the command exits non-zero.

  • Contract test

    Passes when: The API refuses undeclared fields with 422, and its responses carry the provenance.

    Proof of teeth: A response model without the provenance field returns the object without it; the test that asserts its presence fails.

  • Integration test

    Passes when: Against a real PostgreSQL: the application role cannot rewrite the trail, the owner meets the triggers, exact retries are absorbed, a conflicting retry is refused, corrections append.

    Proof of teeth: The last audit test disables the triggers as the owner and deletes, proving what the controls do not stop.

  • Integration test

    Passes when: Against a real OPA server, the decision carries its revision and replays identically; against a mocked engine, answers without a revision or of the wrong type are refused.

    Proof of teeth: Coerce allow with bool() instead of checking its type: the test that sends the string "false" fails. The harness does this on every run.

  • End-to-end test

    Passes when: The publication fence passes on the built output.

    Proof of teeth: A synthetic sentinel is planted, the fence fails, the exact bytes are restored and compared, and the fence passes.

Alternative realizations

  • Other validation libraries. attrs with cattrs, or msgspec, give the same boundary with different trade-offs in speed and strictness. The property is the strict, validated, provenance-carrying type, not the library.
  • Other frameworks. Django REST framework serialisers or Litestar express the same boundary. The output-filtering behaviour differs per framework; test it in yours.
  • Application-level immutability (an insert-only repository, no update method) in place of database triggers, where the database is shared or the triggers cannot be managed. Weaker against a second client, and simpler to operate.
  • Rule tables in PostgreSQL instead of OPA, where the rules are few and the team already owns the database. The revision requirement is the same.

Trade-offs

  • Strictness costs friction. Strict models reject inputs that lax ones would have repaired, and every rejection needs a decision. Strict mode is also looser for JSON input than for Python objects: date types accept strings even in strict mode.
  • Two identifiers cost attention. Every developer has to know which one they are looking at. Name them differently in code, as the examples do, and never use one to fill the other.
  • Database-enforced append-only ties you to the database's features. Triggers and grants are specific to PostgreSQL here; a second database needs its own equivalent, and its own tests.
  • A policy engine behind HTTP adds a network hop and a failure mode. The client has to decide what an unreachable engine means, for every decision.

Limitations

  • The examples are illustrative, small and synthetic. They leave out authentication, authorisation, migrations, connection pooling, asynchronous database access and deployment.
  • They were tested in the environment listed above, on 11 September 2026, and on nothing else.
  • The single process shown is one topology. The Cloud-Native profile shows what changes when the probabilistic side is deployed separately.
  • Nothing here shows how confidence is represented, when review is required, or how review is organised; COADF does not publish those parts of P-2 and P-3.

What this profile does not establish

Following this profile does not establish regulatory compliance, certification or conformity assessment, and nothing here is required by COADF. Running these examples establishes that these examples ran.

Tested reference environment

  • Python 3.12.13 and 3.14.4 · every example test run on both
  • FastAPI 0.141.1 · with Starlette 1.6.0 and its test client
  • Pydantic 2.13.5
  • httpx 0.28.1 · including its mock transport
  • OpenTelemetry API and SDK 1.44.0 · in-memory span exporter
  • import-linter 2.15 · contracts kept, and broken by a planted import
  • psycopg 3.3.5
  • PostgreSQL 18.6 · a local scratch cluster; the audit tests run against it
  • Open Policy Agent 1.20.2 · opa test, opa check --strict, and a live server for the client test
  • pytest 9.1.1

Sources

COADF Engineering Companion 1.0 · non-normative · maps to COADF Core 2.2

Publication rights reserved. No public licence is granted for the COADF Engineering Companion 1.0 or its reference examples at this time.

IP and publication status