On this page
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
| Architectural property | Python and FastAPI |
|---|---|
| Boundary type | Frozen Pydantic models with extra="forbid" and strict=True; a distinct dataclass for verified values |
| Runtime validation | model_validate_json on the raw reply; FastAPI request validation, answered with 422 |
| Output contract | An explicit response_model that declares the provenance |
| Architecture rule | import-linter forbidden contracts, run by lint-imports |
| Execution context | OpenTelemetry propagation (inject, extract); contextvars copied into threads |
| Audit trail | PostgreSQL: insert and select only for the application role, triggers refusing rewrites, a unique key whose conflicts are compared before they count as retries |
| Standards isolation | A typing.Protocol port; an httpx adapter that turns the vendor's HTTP outcomes into not found, unavailable and rejected |
| Policy | OPA over its REST API behind a small interface; the answer type-checked, never coerced, and the revision required on every decision |
| Publication fence | A 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.
| Module | Role | May not import |
|---|---|---|
boundary.py | The contract a model reply must meet | nothing of the domain |
inference.py | Calls a model; returns a Proposal | records, audit, api |
formats.py | Deterministic checks on a proposed value | adapters, HTTP clients |
records.py | Verified values; the reviewed path in | adapters, HTTP clients |
api.py | The HTTP surface of the boundary | |
tracing.py | Execution context and audit identity | |
audit.py, schema.sql | The append-only trail | |
classification.py | The port for an external service | adapters, HTTP clients |
adapters/ | The vendor, and only the vendor | |
policy.py, policy/ | Policy material, rule and client | |
fence.py | The publication fence |
P-1 · The boundary
- 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).
"""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
Proposalis 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.
- 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).
"""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 dateWhat 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.
- 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.
"""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.
revieweris a string here; in a real system it is an authenticated identity, checked where the decision is received. - Protection against a developer building
VerifiedValueby 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.
- 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).
# 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_modulewith 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.
Relies on
- 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.
"""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.
- 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.
"""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
- 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.
"""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.
sendis any function that takes headers and a body. - Where the audit
trace_idis 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_idinto 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.
Relies on
W3C: Trace Context, the traceparent header · OpenTelemetry: SDK environment variables: OTEL_PROPAGATORS · OpenTelemetry: Context propagation · OpenTelemetry: Baggage: security considerations · OpenTelemetry Python: opentelemetry-api: runtime context (source) · Python Software Foundation: asyncio: Task and to_thread · CPython: Lib/asyncio/threads.py
- 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.
-- 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_typeis 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.
Relies on
PostgreSQL Global Development Group: Privileges · PostgreSQL Global Development Group: TRUNCATE · PostgreSQL Global Development Group: CREATE TRIGGER · PostgreSQL Global Development Group: Trigger functions in PL/pgSQL · PostgreSQL Global Development Group: Role attributes · PostgreSQL Global Development Group: ALTER TABLE: DISABLE TRIGGER · PostgreSQL Global Development Group: JSON types: jsonb indexing · PostgreSQL Global Development Group: Date/time types
- 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 bytrace_id, ordered by timestamp; equal timestamps are intentionally not ordered (P-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.
- 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.
"""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
- 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).
"""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.
rejecteddoes not heal by retrying, so it needs an alert thatunavailablemay 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.
- Purpose
- The only module that knows the vendor's HTTP shape and the vendor's names.
- Architectural property
- The vendor's
classIdandrestrictedare translated into the domain'scodeandlicensed, 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).
"""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
- 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).
{ "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.
- 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).
# 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
revisionkeeps 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.
- 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:
allowmust be a boolean andrevisiona non-empty string, because in Pythonbool("false")isTrue. The engine sits behind an adapter, so the decision record keeps its shape if the engine changes.
"""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
- 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.
"""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.
- 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).
"""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)) == 2What 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
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. Setextra="forbid"and strict mode on boundary models, and test both.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.
model_constructin a hot pathIt builds a model without validation. Introduced for speed, it becomes the path that skips the boundary.
Context that stops at a thread pool
asyncio.to_threadpropagates the current context; a bare executor submission does not, and neither doesrun_in_executorunless the caller copies the context first, asto_threaditself does. Spans and the audit identity then vanish in the one place work is slowest.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.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.
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.
A protocol taken as a runtime check
A
typing.Protocolis a static contract; a runtime check against it verifies only that the methods exist. Behaviour is what the contract tests check.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:
pytestruns 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-importskeeps both contracts.Proof of teeth: Plant
from refapp.records import acceptininference.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
allowwithbool()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
- Pydantic: ConfigDict.extra · official documentation · Pydantic 2.13 · Checked on 2026-09-11
- Pydantic: Strict mode · official documentation · Pydantic 2.13 · Checked on 2026-09-11
- Pydantic: Models: faux-immutability · official documentation · Pydantic 2.13 · Checked on 2026-09-11
- Pydantic: Performance: model_validate_json · official documentation · Pydantic 2.13 · Checked on 2026-09-11
- Pydantic: Models: creating models without validation · official documentation · Pydantic 2.13 · Checked on 2026-09-11
- FastAPI: Handling errors: request validation · official documentation · FastAPI 0.141 · Checked on 2026-09-11
- FastAPI: Response model: return type and data filtering · official documentation · FastAPI 0.141 · Checked on 2026-09-11
- FastAPI: Background tasks · official documentation · FastAPI 0.141 · Checked on 2026-09-11
- import-linter: Contract types · official documentation · import-linter 2.15 · Checked on 2026-09-11
- Python Software Foundation: asyncio: Task and to_thread · language documentation · Python 3.14 · Checked on 2026-09-11
- CPython: Lib/asyncio/threads.py · project repository · CPython 3.14.7 · Checked on 2026-09-11
- Python Software Foundation: typing.Protocol and runtime_checkable · language documentation · Python 3.14 · Checked on 2026-09-11
- OpenTelemetry: SDK environment variables: OTEL_PROPAGATORS · specification · Specification 1.60.0 · Checked on 2026-09-11
- OpenTelemetry: Context propagation · official documentation · Checked on 2026-09-11
- OpenTelemetry: Baggage: security considerations · official documentation · Checked on 2026-09-11
- OpenTelemetry Python: opentelemetry-api: runtime context (source) · project repository · opentelemetry-api 1.44.0 · Checked on 2026-09-11
- W3C: Trace Context, the traceparent header · specification · W3C Recommendation, Level 1 · Checked on 2026-09-11
- PostgreSQL Global Development Group: Privileges · official documentation · PostgreSQL 18 · Checked on 2026-09-11
- PostgreSQL Global Development Group: TRUNCATE · official documentation · PostgreSQL 18 · Checked on 2026-09-11
- PostgreSQL Global Development Group: CREATE TRIGGER · official documentation · PostgreSQL 18 · Checked on 2026-09-11
- PostgreSQL Global Development Group: Trigger functions in PL/pgSQL · official documentation · PostgreSQL 18 · Checked on 2026-09-11
- PostgreSQL Global Development Group: Role attributes · official documentation · PostgreSQL 18 · Checked on 2026-09-11
- PostgreSQL Global Development Group: ALTER TABLE: DISABLE TRIGGER · official documentation · PostgreSQL 18 · Checked on 2026-09-11
- PostgreSQL Global Development Group: INSERT: ON CONFLICT · official documentation · PostgreSQL 18 · Checked on 2026-09-11
- PostgreSQL Global Development Group: JSON types: jsonb indexing · official documentation · PostgreSQL 18 · Checked on 2026-09-11
- PostgreSQL Global Development Group: Date/time types · official documentation · PostgreSQL 18 · Checked on 2026-09-11
- Open Policy Agent: Open Policy Agent: introduction · official documentation · OPA 1.20 · Checked on 2026-09-11
- Open Policy Agent: Bundles: bundle file format · official documentation · OPA 1.20 · Checked on 2026-09-11
- Open Policy Agent: Policy testing · official documentation · OPA 1.20 · Checked on 2026-09-11
- Open Policy Agent: Upgrading to OPA 1.0 · official documentation · OPA 1.20 · Checked on 2026-09-11
- Open Policy Agent: Decision logs · official documentation · OPA 1.20 · Checked on 2026-09-11
- Open Policy Agent: REST API: get a document with input · official documentation · OPA 1.20 · Checked on 2026-09-11
