Ir para o conteúdo

Projeto independente de P&D · Colônia

Python e FastAPI

Um monólito modular com fronteiras tipadas, contratos de importação, uma trilha de auditoria somente de inserção no PostgreSQL e regras como dados, com exemplos de referência testados.

Não normativo

Versão do Companion
1.0
Corresponde ao COADF Core
2.2
Status
Atual
Última revisão
Versão do perfil
1.0
Exemplos de código
Exemplos de referência ilustrativos

Propriedades de arquitetura tratadas

  • P-1Uma fronteira tipada e validada em torno da chamada ao modelo; nesta implementação de referência, um valor com autoridade só existe depois da verificação por uma pessoa nomeada.
  • P-4Uma identidade de auditoria ao lado do contexto do OpenTelemetry; uma trilha somente de inserção no PostgreSQL cujas escritas absorvem uma nova tentativa idêntica e recusam uma conflitante.
  • P-5O método de extração e a localização na fonte acompanham cada valor proposto e sobrevivem à serialização.
  • P-6Contratos de importação e um fence de publicação, cada um com uma prova de dentes.
  • P-7Uma porta que pertence ao domínio, e um adaptador que é o único módulo que conhece o fornecedor.
  • P-8Decisões tomadas a partir de material de política versionado, guardadas com sua revisão ou recusadas.
  • P-2Apenas na profundidade que o COADF publica. Os exemplos não trazem nenhuma representação de confiança.
  • P-3Apenas na profundidade que o COADF publica: nesta implementação de referência, verificação no nível do atributo por uma pessoa nomeada, e um valor rejeitado fica vazio. Quando a revisão é exigida, e como as revisões são organizadas, não é mostrado.

Intenção de arquitetura

Em um serviço Python, o cliente do modelo e o código de domínio costumam ser escritos pelas mesmas pessoas, no mesmo repositório, na mesma tarde, e é assim que o lado probabilístico e o lado com autoridade acabam em um só módulo, chamando um ao outro diretamente. FastAPI e Pydantic tornam barato expressar a fronteira como tipos e validá-la em tempo de execução. Eles também têm padrões que são certos para uma API web e errados para uma fronteira: um modelo Pydantic ignora os campos que não declara e converte tipos compatíveis, a menos que o modo estrito esteja ativo, e o FastAPI filtra cada resposta pelo seu modelo de resposta sem reclamar.

Este perfil mostra uma forma de manter as propriedades do COADF em um único serviço Python: um monólito modular com contratos de importação, PostgreSQL para a trilha de auditoria, OpenTelemetry para o contexto de execução e Open Policy Agent atrás de uma interface para a política. Nenhum deles é exigido. Cada um é um lugar onde a propriedade ou é mantida ou se perde em silêncio.

Correspondência tecnológica

Python e FastAPI: Correspondência tecnológica
Propriedade de arquiteturaPython e FastAPI
Tipo de fronteiraModelos Pydantic congelados com extra="forbid" e strict=True; uma dataclass distinta para valores verificados
Validação em tempo de execuçãomodel_validate_json sobre a resposta bruta; validação de requisições do FastAPI, respondida com 422
Contrato de saídaUm response_model explícito que declara a procedência
Regra de arquiteturaContratos forbidden do import-linter, executados por lint-imports
Contexto de execuçãoPropagação do OpenTelemetry (inject, extract); contextvars copiadas para as threads
Trilha de auditoriaPostgreSQL: apenas inserção e seleção para o papel da aplicação, triggers que recusam reescritas, uma chave única cujos conflitos são comparados antes de contarem como novas tentativas
Isolamento de normasUma porta typing.Protocol; um adaptador httpx que transforma os resultados HTTP do fornecedor em não encontrado, indisponível e rejeitado
PolíticaOPA pela sua API REST, atrás de uma pequena interface; o tipo da resposta é verificado, nunca convertido, e a revisão da política é exigida em cada decisão
Fence de publicaçãoUma varredura da saída construída com um arquivo de padrões mantido fora dela; uma prova de dentes conferida por digest

Padrão de referência

Um único pacote, refapp, em um domínio sintético de manutenção: um modelo propõe a próxima data de serviço e uma classe de componente a partir do relatório de serviço de um técnico. Os módulos são as fronteiras.

Os módulos do pacote de referência e a propriedade que cada um mantém
MóduloPapelNão pode importar
boundary.pyO contrato que uma resposta do modelo precisa cumprirnada do domínio
inference.pyChama um modelo; devolve um Proposalrecords, audit, api
formats.pyVerificações determinísticas sobre um valor propostoadaptadores, clientes HTTP
records.pyValores verificados; a entrada pelo caminho revisadoadaptadores, clientes HTTP
api.pyA superfície HTTP da fronteira
tracing.pyContexto de execução e identidade de auditoria
audit.py, schema.sqlA trilha somente de inserção
classification.pyA porta para um serviço externoadaptadores, clientes HTTP
adapters/O fornecedor, e somente o fornecedor
policy.py, policy/Material de política, regra e cliente
fence.pyO fence de publicação

P-1 · A fronteira

Exemplo de referência ilustrativoUm contrato tipado em torno de uma chamada de modelo
Propósito
Dar a uma chamada de modelo um contrato: o que ela pode devolver e o que precisa acompanhar o que ela devolve. Uma fronteira de referência deliberadamente parcial, construída apenas com o que o COADF publica neste nível.
Propriedade de arquitetura
Um componente probabilístico devolve um valor junto com a forma como o valor foi obtido, e qualquer outra coisa é rejeitada na fronteira (P-1). O método viaja com o valor, e é isso que torna possível declarar mais tarde a extração automática (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)

O que ele omite de propósito

  • A confiança, deliberadamente. O P-1 do COADF faz um componente probabilístico informar uma confiança junto com seu valor; essa é a propriedade de arquitetura pública. Como a confiança é expressa, atribuída e usada é um detalhe de implementação reservado do P-2, que o COADF publica apenas como princípio. Por isso esta fronteira carrega o método e a procedência, não tem campo de confiança e não inventa nenhuma representação para ela: é parcial de propósito, não completa.
  • A chamada ao modelo, suas novas tentativas e o prompt. A fronteira é a mesma, seja qual for o cliente que produziu a string.
  • Se o valor está certo. Um esquema verifica a forma; ele não tem como saber se uma data é a correta para este ativo.
  • Armazenamento. Uma Proposal é uma mensagem, não um registro.

Como verificar

Alimentar o parser com respostas quase certas: um campo não declarado, um número onde o contrato pede texto, uma procedência ausente, um atributo que ninguém pediu. Cada uma precisa levantar uma exceção (tests/test_boundary.py). Depois, apagar extra="forbid" numa cópia descartável e ver o teste de campo não declarado falhar.

Modo de falha tratado

Uma resposta do modelo interpretada com json.loads e lida como dicionário. Um "verified": true a mais, um número convertido à força ou uma fonte ausente passam direto, e o valor fica indistinguível de um dado que alguém verificou.

Exemplo de referência ilustrativoVerificações determinísticas em torno de um valor estocástico
Propósito
Verificar o valor proposto com código que não depende em nada do modelo.
Propriedade de arquitetura
A validação determinística envolve a saída estocástica: o mesmo valor recebe sempre o mesmo veredito, e o veredito pode ser reproduzido só a partir do valor (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

O que ele omite de propósito

  • Verificações que precisam de outros registros ou de outras fontes. Este exemplo confronta um valor com as regras do seu próprio atributo, e com nada mais.
  • Formatos de data localizados. O contrato fixa uma única forma escrita de propósito.

Como verificar

Um valor que casa com o padrão mas não é uma data de calendário (2026-02-30) nunca é aceito como válido (tests/test_boundary.py).

Modo de falha tratado

Confiar que o modelo produziu um valor válido porque o prompt pediu um: a verificação que não foi escrita é a que falha em produção.

Exemplo de referência ilustrativoUm caminho revisado até os registros com autoridade
Propósito
Um caminho de referência conservador pelo qual um valor derivado de um modelo passa a ter autoridade: neste exemplo, todo valor desse tipo vai para uma pessoa com nome, que o examina ao lado de sua fonte e o aceita ou rejeita.
Propriedade de arquitetura
Um componente probabilístico não pode se tornar diretamente uma saída com autoridade (P-1). Nesta implementação de referência, um valor derivado de um modelo só chega a um registro por verificação humana, e um valor rejeitado deixa o atributo vazio (P-3, na profundidade que o COADF publica). É uma implementação, não uma topologia do COADF.
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)

O que ele omite de propósito

  • Quando a revisão é exigida. O COADF publica que a revisão é acionada pela confiança; a regra que decide isso não é publicada, e este exemplo não define nenhuma. Ele verifica todo valor derivado de um modelo, a escolha conservadora, que é também o que o fence público F-03 exige dos valores derivados de um modelo de linguagem.
  • Como as revisões são organizadas. O COADF não publica se as revisões entram em fila, seguem uma sequência, são atribuídas ou são acionadas, e este exemplo não pressupõe nada disso: o código trata uma proposta e uma decisão por vez.
  • Autenticação da pessoa revisora. reviewer aqui é uma string; num sistema real é uma identidade autenticada, verificada onde a decisão é recebida.
  • Proteção contra um desenvolvedor que construa VerifiedValue à mão. O tipo torna visível uma promoção acidental, o contrato de importação a torna impossível a partir do módulo de inferência, e a revisão de código cobre o resto.

Como verificar

Uma proposta rejeitada devolve None; uma aceita mantém sua procedência; um nome de pessoa revisora vazio é recusado (tests/test_boundary.py).

Modo de falha tratado

Um flag verified no objeto que o modelo produziu, verdadeiro por padrão ou definido por qualquer código que salve o registro. Valores inferidos e verificados passam a ser a mesma coisa no armazenamento, e nada a jusante consegue distingui-los.

Exemplo de referência ilustrativoA fronteira como regra que o build impõe
Propósito
Transformar a fronteira, de uma caixa num diagrama, em uma verificação que faz o build falhar.
Propriedade de arquitetura
O módulo probabilístico não tem caminho de importação até os registros com autoridade nem até a trilha de auditoria, e o código de domínio não tem nenhum até os adaptadores de fornecedores ou os clientes HTTP (P-1, P-7). A regra roda a cada mudança (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 = ["."]

O que ele omite de propósito

  • Importações feitas por nome em tempo de execução, como importlib.import_module com uma string calculada. Um contrato de importação estático verifica as instruções de importação que consegue ler; um carregador de plugins precisa de uma verificação própria.
  • Caminhos de dados que não passam por importações: uma conexão de banco de dados compartilhada, uma fila que os dois lados alcançam. As permissões do banco de dados e a política de rede do perfil Cloud-native tratam desses caminhos.
  • Um contrato em camadas para a aplicação inteira. Dois contratos de proibição bastam para mostrar o mecanismo.

Como verificar

lint-imports informa os dois contratos como mantidos. Acrescentar from refapp.records import accept a inference.py numa cópia descartável: o primeiro contrato é informado como quebrado e o comando sai com código diferente de zero. Removida a linha, ele volta a ser mantido.

Modo de falha tratado

A fronteira existe como uma caixa num diagrama de arquitetura e em nenhum outro lugar, até alguém precisar às pressas de um valor do outro lado.

Exemplo de referência ilustrativoTestes negativos: um defeito plantado em cada um
Propósito
Verificar a fronteira pelo que ela recusa, não só pelo que ela aceita.
Propriedade de arquitetura
Cada teste planta um defeito que a fronteira existe para barrar, então apagar uma restrição deixa um teste vermelho.
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=" "))

O que ele omite de propósito

  • Entradas malformadas geradas. Uma lista curta de defeitos nomeados é mais fácil de revisar, e cada um documenta um modo de falha.
  • Qualquer coisa sobre a qualidade das respostas do modelo. A fronteira não torna um modelo correto.

Como verificar

Rodar pytest. A suíte só tem dentes se remover uma restrição fizer um teste falhar; o harness apaga extra="forbid" numa cópia descartável e o teste de campo não declarado falha.

Modo de falha tratado

Uma suíte que só alimenta o caminho feliz: todas as restrições poderiam ser apagadas e ela continuaria verde.

Exemplo de referência ilustrativoO modelo de resposta faz parte do contrato
Propósito
A superfície HTTP da fronteira: o que um worker de inferência pode enviar, e o que a tela da pessoa revisora recebe.
Propriedade de arquitetura
O FastAPI valida a requisição contra o modelo e filtra a resposta pelo modelo de resposta; por isso a procedência, incluindo a localização do trecho de que a pessoa revisora precisa para ver o valor ao lado de sua fonte, tem de ser declarada, ou nunca sai do serviço.
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]

O que ele omite de propósito

  • Autenticação e autorização dos dois endpoints.
  • Persistência. Um dicionário faz as vezes de armazenamento.
  • O endpoint de decisão pelo qual a pessoa revisora aceita ou rejeita; ver records.accept.
  • Qualquer ordem de apresentação. A lista volta na ordem de inserção. Não se trata de uma sequência de revisão: o COADF não publica como a sequência da revisão é definida, e nada aqui a define.

Como verificar

Enviar uma resposta com um campo extra verified: 422. Enviar uma resposta bem formada e listá-la: a procedência está na resposta. Depois, declarar um modelo de resposta sem a procedência: o mesmo objeto volta sem ela, e nada falha (tests/test_api.py reproduz isso).

Modo de falha tratado

Procedência perdida na serialização. Um modelo de resposta que omite a procedência a descarta em silêncio, e a tela de revisão mostra um valor sem nada com que compará-lo.

P-4 · O trace e a trilha

Exemplo de referência ilustrativoContexto de execução e identidade de auditoria, lado a lado
Propósito
Levar o contexto de execução através de uma mensagem e de uma thread, e levar a identidade de auditoria ao lado dele, nunca no lugar dele.
Propriedade de arquitetura
Uma única identidade rastreável liga as etapas de uma transação (P-4). O contexto do OpenTelemetry é correlacionado com ela por um atributo de span, e nunca a substitui.
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)

O que ele omite de propósito

  • Exporters e samplers; ver a configuração do Collector no perfil Cloud-native.
  • Um cliente de broker real. send é qualquer função que receba cabeçalhos e um corpo.
  • Onde o trace_id de auditoria é criado: na entrada, uma única vez, e em nenhum ponto a jusante.
  • Baggage. A identidade de auditoria viaja no corpo da mensagem, onde faz parte do contrato, e não na baggage, que a instrumentação repassa aos serviços a jusante, inclusive de terceiros.
  • O que a correlação expõe. O atributo de span copia o trace_id de auditoria para a telemetria, que tem seus próprios exporters, fornecedores, controles de acesso e retenção, em geral mais frouxos que os do repositório de auditoria. É preciso classificá-lo antes que seja exportado: uma referência opaca, nunca dado pessoal nem segredo. Onde um identificador de auditoria carrega significado, a correlação deve passar, em vez disso, por uma referência separada e não sensível.

Como verificar

Os spans do produtor e do consumidor compartilham um único trace distribuído, e o span do consumidor carrega a identidade de auditoria. Uma mensagem sem a identidade de auditoria levanta KeyError em vez de criar uma nova. Uma submissão simples a um pool de threads perde o contexto; o contexto copiado o mantém; asyncio.to_thread o mantém (tests/test_tracing.py).

Modo de falha tratado

O identificador de trace regenerado no meio do caminho: um consumidor que recorre a um identificador novo quando o campo falta, ou uma thread de trabalho que começa sem o contexto de quem a chamou, deixa duas metades que nenhuma consulta consegue juntar.

Exemplo de referência ilustrativoUma trilha de auditoria somente de inserção no PostgreSQL
Propósito
Uma trilha de auditoria com os nove campos que o COADF publica, armazenada de modo que a aplicação possa inserir e ler e o banco de dados recuse reescritas.
Propriedade de arquitetura
A trilha é somente de inserção, e uma correção é uma nova entrada (P-4). Uma etapa tem uma única identidade: uma nova tentativa exata é a mesma entrada, e a mesma identidade com outro conteúdo é recusada por 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();

O que ele omite de propósito

  • Evidência de adulteração. Privilégios e triggers são controles dentro da própria fronteira de confiança do banco de dados: um superusuário contorna as verificações de permissão, e o dono da tabela pode desativar seus triggers. Detectar uma reescrita feita por alguém com esses direitos exige um controle fora do banco de dados, que este exemplo não mostra.
  • Retenção, arquivamento e exportação.
  • O vocabulário de eventos. event_type é um texto obrigatório. Quais eventos uma aplicação registra é decisão de design dela, e fica deliberadamente fora deste exemplo.
  • Uma ordem entre entradas com o mesmo timestamp. A reconstrução ordena por timestamp, e timestamps iguais deliberadamente não são ordenados por este exemplo: nenhum campo publicado os ordena. Um único escritor por transação, ou uma ordem atribuída por ele, é uma decisão de armazenamento que vai além dos campos publicados.
  • A migração e o papel dono da tabela. A aplicação nunca se conecta com esse papel.

Como verificar

Contra um PostgreSQL real: o papel da aplicação não consegue atualizar, apagar nem truncar; o dono esbarra nos triggers; uma nova tentativa exata não acrescenta nada; a mesma identidade com outro conteúdo é recusada; uma correção acrescenta uma entrada; e, deliberadamente, o dono consegue desativar os triggers e apagar (tests/test_audit.py).

Modo de falha tratado

Uma correção escrita como UPDATE, que destrói o registro do que se tinha como verdadeiro antes, e a mesma perda por TRUNCATE, que não dispara trigger de DELETE algum.

Exemplo de referência ilustrativoInserção idempotente que recusa conflitos, reconstrução em uma consulta
Propósito
Tornar inofensiva uma nova tentativa e ruidosa uma escrita conflitante, e ler uma transação de volta em uma única consulta.
Propriedade de arquitetura
O timestamp é registrado quando a etapa roda, então uma nova tentativa reenvia a entrada idêntica. Num conflito, a entrada armazenada é comparada primeiro: o mesmo conteúdo é uma nova tentativa, outro conteúdo levanta IdempotencyCollision. A reconstrução é uma consulta por trace_id, ordenada por timestamp; timestamps iguais deliberadamente não são ordenados (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()

O que ele omite de propósito

  • A transação que grava a entrada junto com a mudança de estado que ela registra. Gravadas separadamente, uma pode existir sem a outra.
  • Exportação em JSON, que o P-4 também pede.
  • Gerenciamento de conexões, e qual papel a conexão usa.
  • Qual de duas entradas conflitantes está certa. O exemplo recusa a segunda e mantém a primeira; decidir entre elas é trabalho de alguém, não do banco de dados.
  • Escritores concorrentes da mesma etapa. O exemplo foi testado com um escritor por vez; sob concorrência, é preciso verificar o que o nível de isolamento permite que a leitura de comparação enxergue.

Como verificar

Inserir a mesma entrada duas vezes: True, depois False, e uma linha. Inserir a mesma identidade com uma saída diferente: IdempotencyCollision, e a entrada armazenada fica inalterada (tests/test_audit.py). Reconstruir com chain().

Modo de falha tratado

Novas tentativas que produzem eventos ambíguos, e conflitos que desaparecem: um timestamp tomado no momento da inserção faz de cada nova tentativa uma nova entrada, e um ON CONFLICT DO NOTHING puro descarta, sem erro, uma entrada diferente que por acaso tem a mesma chave.

Exemplo de referência ilustrativoVerificar a persistência, não os logs
Propósito
Verificar a propriedade somente de inserção contra a camada real que a impõe, e fixar a limitação como teste.
Propriedade de arquitetura
A propriedade é afirmada onde é imposta, no banco de dados; o que os controles não impedem também é um teste, para que não possa ser esquecido.
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") == []

O que ele omite de propósito

  • Um banco de dados na integração contínua. Os testes são pulados sem REFAPP_PG_DSN, e um teste pulado não é um teste que passou: o job que os roda precisa fornecer o banco de dados, ou relatar a verificação como não executada.

Como verificar

Rodar contra um PostgreSQL descartável: os seis testes passam, inclusive a nova tentativa conflitante que precisa falhar e o que prova que o dono consegue apagar.

Modo de falha tratado

Falsa imutabilidade: um trigger tomado como prova de que o histórico não pode mudar, quando o papel dono da tabela pode desligá-lo.

P-7 · A porta e o adaptador

Exemplo de referência ilustrativoUma porta que pertence ao domínio
Propósito
O lado do domínio de um serviço externo de classificação: uma porta, e os tipos por trás dela.
Propriedade de arquitetura
Vocabulários externos ficam atrás de uma porta que pertence ao domínio. Um serviço com falha deixa o registro menos informativo e nunca o interrompe, e o registro diz se o serviço estava indisponível ou se a integração está quebrada (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")

O que ele omite de propósito

  • O que uma resposta externa faz com a confiança. O P-7 do COADF diz que um serviço externo pode elevar a confiança de um atributo e nunca pode bloquear uma saída; como essa elevação funciona não é publicado. Aqui a resposta é registrada, com seu status, e nada mais.
  • Cache, e por quanto tempo uma licença permite guardar uma resposta externa.
  • O mapeamento dos códigos externos para os conceitos próprios do domínio, que num sistema real são dados versionados. Um novo fornecedor ou transporte fica no adaptador; uma nova versão do próprio esquema pode mudar o que os códigos significam, e então esse mapeamento muda também.
  • Quem é avisado. rejected não se resolve com novas tentativas, então precisa de um alerta de que unavailable talvez não precise.

Como verificar

Um dublê de teste satisfaz a porta; uma indisponibilidade registra unavailable e uma integração quebrada registra rejected (tests/test_classification.py). O contrato de importação mantém httpx e os adaptadores fora deste módulo.

Modo de falha tratado

Uma indisponibilidade remota que bloqueia processamento não relacionado: o domínio chama o fornecedor diretamente, de forma síncrona e sem timeout, e um incidente do fornecedor vira um incidente do próprio sistema.

Exemplo de referência ilustrativoOs nomes do fornecedor param no adaptador
Propósito
O único módulo que conhece o formato HTTP e os nomes do fornecedor.
Propriedade de arquitetura
classId e restricted do fornecedor são traduzidos para code e licensed do domínio, a versão do esquema é fixada em cada chamada, e os resultados HTTP do fornecedor viram três resultados de domínio: não encontrado, indisponível (transitório) e rejeitado (a integração está errada). O vocabulário HTTP do provedor não vira o do domínio (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)

O que ele omite de propósito

  • Novas tentativas, circuit breaking e limites de taxa.
  • Autenticação junto ao fornecedor.
  • Os próprios termos da licença, e qualquer conteúdo licenciado: este exemplo não contém nenhum.
  • Uma taxonomia mais fina. Três resultados bastam para separar uma indisponibilidade de uma integração quebrada; um adaptador real pode precisar de mais.

Como verificar

Com httpx.MockTransport: um 200 é traduzido; um 404 é None; um timeout, um erro de conexão, um 429 ou um 5xx é LookupUnavailable; um 400, um 401, um restricted não booleano, um campo ausente ou um corpo que não é JSON é LookupRejected; toda requisição carrega a versão fixada.

Modo de falha tratado

O SDK do fornecedor virando o modelo de domínio: suas classes aparecem em assinaturas do domínio, seus identificadores ganham significado interno, e uma mudança de licença ou de API obriga a reescrever código de domínio. Ou toda falha relatada como o mesmo unavailable, de modo que uma credencial revogada parece uma indisponibilidade e ninguém a corrige.

P-8 · Regras como dados

Exemplo de referência ilustrativoMaterial de política, com sua versão
Propósito
O material que uma regra lê: quais funcionalidades cada ambiente pode habilitar, e a versão deste material.
Propriedade de arquitetura
Regras são dados. Mudar o que é permitido muda este arquivo, não o motor e não a regra (P-8).
policy/data.jsonjsonP-8
{  "refapp": {    "revision": "2026-09-01.2",    "features": {      "staging": ["report-export", "beta-search"],      "production": ["report-export"]    }  }}

O que ele omite de propósito

  • Como o material de política é protegido no caminho até o motor e verificado na chegada. O COADF não publica essa parte do P-8, e este exemplo não mostra nada dela.
  • Como as versões são atribuídas. A própria documentação de bundles do OPA usa um hash de commit do Git como exemplo de versão. Nada neste exemplo impede que o material mude sob a mesma versão; é derivar a versão do commit ou do conteúdo que impede isso.

Como verificar

Permitir beta-search em production: os testes que fixam a resposta de produção falham, enquanto a regra fica intacta.

Modo de falha tratado

Regras duplicadas e fixadas no código de vários serviços, cada uma alterada no seu próprio ritmo, de modo que a mesma requisição é permitida num lugar e recusada em outro.

Exemplo de referência ilustrativoUma regra que responde com sua versão
Propósito
Uma regra que lê seu material a partir de dados e responde com a versão que produziu a resposta.
Propriedade de arquitetura
A decisão carrega a procedência da versão da regra (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,}

O que ele omite de propósito

  • Qualquer coisa sobre confiança, revisão, publicação ou autonomia. O assunto é deliberadamente genérico.
  • Bundles e logs de decisão, que no OPA podem carregar por conta própria uma versão do bundle. O revision explícito mantém este exemplo autocontido.
  • Se uma regra real deveria ter a recusa como padrão. Aqui o padrão é false; para cada regra real, essa é uma decisão de quem é dono dela.

Como verificar

opa test policy/ roda seis testes, inclusive uma chave de entrada com erro de grafia que resulta no valor padrão em vez de levantar um erro, e uma reexecução da mesma entrada sob a mesma versão.

Modo de falha tratado

Um recarregamento de regras que muda o significado de decisões históricas: sem a versão na decisão, um relatório calculado hoje não consegue dizer quais regras produziram as respostas do mês passado.

Exemplo de referência ilustrativoGuardar a decisão com sua versão, ou não guardá-la
Propósito
O lado da aplicação: consultar o motor por uma interface pequena e recusar qualquer resposta que não informe sua versão.
Propriedade de arquitetura
Uma decisão é guardada com a versão que a produziu, ou não é guardada (P-8). A resposta é interpretada com o mesmo rigor com que é guardada: allow precisa ser booleano e revision uma string não vazia, porque em Python bool("false") é True. O motor fica atrás de um adaptador, então o registro da decisão mantém seu formato se o motor mudar.
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)

O que ele omite de propósito

  • Cache de decisões. Um cache precisa usar como chave a versão, além da entrada.
  • Onde a decisão é registrada: na trilha de auditoria, como saída de uma etapa de política.
  • A recusa como regra universal. Recusar é o certo para este exemplo; quem é dono de cada decisão decide o que um motor indisponível significa para ela.

Como verificar

Contra um servidor OPA real, a versão volta e a mesma entrada recebe a mesma resposta. Contra um motor simulado, uma negação válida é uma decisão; allow enviado como a string "false", um allow ausente, uma versão vazia ou em branco, um resultado do tipo errado, um decision_id que não é string, um caminho indefinido (o OPA omite result), um corpo que não é JSON e um motor inalcançável levantam, cada um, PolicyUnavailable (tests/test_policy.py).

Modo de falha tratado

Um caminho de política indefinido lido como recusa, uma resposta malformada convertida à força na decisão oposta, ou uma resposta sem versão registrada mesmo assim: a decisão existe e não pode ser reconstruída.

P-6 · Um fence e sua prova de dentes

Exemplo de referência ilustrativoUm fence de publicação cuja lista nunca é publicada
Propósito
Pôr um fence na saída do build, com uma lista de padrões que fica fora da árvore publicada.
Propriedade de arquitetura
Um estado proibido na saída do build interrompe a publicação (P-6). Não ler nada não é passar, e um achado diz onde, não o quê.
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]))

O que ele omite de propósito

  • Os padrões. O exemplo traz apenas um sentinela sintético. Uma lista real de padrões é um mapa do que está protegido, e é por isso que o P-6 do COADF não publica a sua.
  • Revisão semântica. Um fence de texto não enxerga um mecanismo expresso por fluxo de controle, identificadores renomeados ou a geometria de um diagrama. Ele é necessário e não suficiente.
  • Formatos binários, e conteúdo buscado em tempo de execução.

Como verificar

A prova de dentes em tests/test_fence.py.

Modo de falha tratado

Um fence que passa porque não leu nada: um arquivo de padrões vazio, um diretório de build sem arquivos, ou um arquivo de padrões que foi parar dentro da saída do build.

Exemplo de referência ilustrativoProva de dentes
Propósito
Plantar um sentinela sintético, ver o ponto de entrada real falhar, restaurar os bytes exatos, ver passar.
Propriedade de arquitetura
Um fence só ganha seu lugar depois de ter sido visto falhando no caminho real (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

O que ele omite de propósito

  • Vocabulário protegido real. O sentinela é sintético e não significa nada.
  • Qualquer afirmação de que um fence que passa torna a saída segura. A prova mostra que o caminho do guardião funciona, não que a revisão semântica esteja completa.

Como verificar

Rodá-lo. Depois, fazer scan retornar cedo numa cópia descartável e vê-lo falhar.

Modo de falha tratado

Uma restauração que não é exata. Restaurar de memória, ou a partir da ponta de um branch, pode alterar ou perder conteúdo em silêncio; comparar o digest antes e depois é o que prova que o fixture voltou.

Modos de falha

  1. Os padrões do Pydantic tratados como fronteira

    Por padrão, um modelo ignora os campos que não declara e, fora do modo estrito, converte valores compatíveis. Para uma API, isso é tolerância; para uma fronteira, significa que uma resposta que afirma "verified": true, ou um número enviado como texto, passa sem deixar rastro. Os modelos de fronteira precisam de extra="forbid" e do modo estrito, e de testes para os dois.

  2. Um modelo de resposta que descarta a procedência

    O FastAPI filtra cada resposta pelo seu modelo de resposta. Um modelo de resposta escrito para a tela, sem os campos de procedência, os remove de todas as respostas, e a pessoa revisora vê um valor sem fonte.

  3. model_construct em um caminho crítico de desempenho

    Ele constrói um modelo sem validação. Introduzido por velocidade, torna-se o caminho que contorna a fronteira.

  4. Contexto que para em um pool de threads

    asyncio.to_thread propaga o contexto atual; uma submissão direta a um executor não propaga, e run_in_executor também não, a menos que quem chama copie o contexto antes, como o próprio to_thread faz. Os spans e a identidade de auditoria então desaparecem justamente no ponto em que o trabalho é mais lento.

  5. Uma default factory que cria identificadores de trace

    Field(default_factory=uuid4) em um modelo de mensagem parece inofensivo e transforma cada identificador ausente em um novo, sem relação com nada. Depois da entrada, a identidade de auditoria é obrigatória e nunca recebe um valor padrão.

  6. Escritas de auditoria adiadas para depois da resposta

    As background tasks do FastAPI rodam depois que a resposta é devolvida. Uma entrada de auditoria escrita ali pode se perder junto com o processo quando o cliente já foi informado de que a etapa teve sucesso. Ela deve ser escrita na mesma transação que a mudança.

  7. Um único modelo ORM para propostas e registros

    Uma coluna de status na entidade que todos leem. No dia em que alguém esquece um filtro, propostas aparecem como fatos.

  8. Um protocolo tomado por verificação em tempo de execução

    Um typing.Protocol é um contrato estático; uma verificação em tempo de execução contra ele só confirma que os métodos existem. O comportamento é o que os testes de contrato verificam.

  9. Testes pulados, lidos como testes que passam

    Um teste de banco de dados que é pulado quando falta a string de conexão transforma a falta de um banco de dados na CI em uma execução verde. É preciso contar o que de fato executou e fazer o job falhar quando os testes de auditoria não rodaram.

Verificação

  • Teste unitário

    Passa quando: pytest executa os testes negativos da fronteira: campo não declarado, tipo errado, procedência ausente, atributo inesperado, data impossível, revisão rejeitada.

    Prova de dentes: Apagar extra="forbid" em uma cópia descartável: o teste do campo não declarado falha. O harness faz isso a cada execução.

  • Teste de arquitetura

    Passa quando: lint-imports mantém os dois contratos.

    Prova de dentes: Plantar from refapp.records import accept em inference.py: o contrato é quebrado e o comando termina com código diferente de zero.

  • Teste de contrato

    Passa quando: A API recusa campos não declarados com 422, e suas respostas carregam a procedência.

    Prova de dentes: Um modelo de resposta sem o campo de procedência devolve o objeto sem ele; o teste que afirma sua presença falha.

  • Teste de integração

    Passa quando: Contra um PostgreSQL real: o papel da aplicação não consegue reescrever a trilha, o proprietário esbarra nos triggers, novas tentativas idênticas são absorvidas, uma nova tentativa conflitante é recusada, correções são acrescentadas.

    Prova de dentes: O último teste de auditoria desativa os triggers como proprietário e apaga, provando o que os controles não impedem.

  • Teste de integração

    Passa quando: Contra um servidor OPA real, a decisão carrega a revisão da política e se repete de forma idêntica; contra um motor simulado, respostas sem revisão ou com o tipo errado são recusadas.

    Prova de dentes: Converter allow com bool() em vez de verificar seu tipo: o teste que envia a string "false" falha. O harness faz isso a cada execução.

  • Teste de ponta a ponta

    Passa quando: O fence de publicação passa na saída construída.

    Prova de dentes: Um sentinela sintético é plantado, o fence falha, os bytes exatos são restaurados e comparados, e o fence passa.

Realizações alternativas

  • Outras bibliotecas de validação. attrs com cattrs, ou msgspec, dão a mesma fronteira com outras escolhas de compromisso entre velocidade e rigor. A propriedade é o tipo estrito, validado e portador de procedência, não a biblioteca.
  • Outros frameworks. Os serializers do Django REST framework ou o Litestar expressam a mesma fronteira. O comportamento de filtragem da saída varia de framework para framework; é preciso testá-lo no framework em uso.
  • Imutabilidade no nível da aplicação (um repositório somente de inserção, sem método de atualização) no lugar de triggers do banco de dados, onde o banco é compartilhado ou os triggers não podem ser gerenciados. Mais fraca diante de um segundo cliente, e mais simples de operar.
  • Tabelas de regras no PostgreSQL em vez do OPA, onde as regras são poucas e a equipe já é dona do banco de dados. A exigência da revisão da política é a mesma.

Escolhas de compromisso

  • O rigor custa atrito. Modelos estritos rejeitam entradas que modelos tolerantes teriam consertado, e cada rejeição exige uma decisão. O modo estrito também é mais frouxo para entrada JSON do que para objetos Python: tipos de data aceitam strings mesmo no modo estrito.
  • Dois identificadores custam atenção. Quem desenvolve precisa saber qual dos dois tem diante de si. Convém dar a eles nomes diferentes no código, como fazem os exemplos, e nunca usar um para preencher o outro.
  • Somente inserção imposto pelo banco de dados amarra a solução aos recursos desse banco. Triggers e grants são, aqui, específicos do PostgreSQL; um segundo banco de dados precisa do seu próprio equivalente, e dos seus próprios testes.
  • Um motor de políticas atrás de HTTP acrescenta um salto de rede e um modo de falha. O cliente precisa decidir o que um motor inacessível significa, para cada decisão.

Limitações

  • Os exemplos são ilustrativos, pequenos e sintéticos. Eles deixam de fora autenticação, autorização, migrações, pool de conexões, acesso assíncrono ao banco de dados e deploy.
  • Foram testados no ambiente listado acima, em 11 de setembro de 2026, e em nenhum outro.
  • O processo único mostrado é uma das topologias possíveis. O perfil Cloud-native mostra o que muda quando o lado probabilístico é implantado separadamente.
  • Nada aqui mostra como a confiança é representada, quando a revisão é exigida ou como a revisão é organizada; o COADF não publica essas partes de P-2 e P-3.

O que este perfil não estabelece

Seguir este perfil não estabelece conformidade regulatória, certificação nem avaliação da conformidade, e nada aqui é exigido pelo COADF. Executar estes exemplos estabelece que estes exemplos foram executados.

Ambiente de referência testado

  • Python 3.12.13 and 3.14.4 · cada teste de exemplo executado nas duas versões
  • FastAPI 0.141.1 · com Starlette 1.6.0 e seu cliente de teste
  • Pydantic 2.13.5
  • httpx 0.28.1 · incluindo seu transporte simulado
  • OpenTelemetry API and SDK 1.44.0 · exportador de spans em memória
  • import-linter 2.15 · contratos mantidos, e quebrados por uma importação plantada
  • psycopg 3.3.5
  • PostgreSQL 18.6 · um cluster local descartável; os testes de auditoria rodam contra ele
  • Open Policy Agent 1.20.2 · opa test, opa check --strict e um servidor em execução para o teste do cliente
  • pytest 9.1.1

Fontes

COADF Engineering Companion 1.0 · não normativo · corresponde ao COADF Core 2.2

Direitos de publicação reservados. Nenhuma licença pública é concedida, no momento, para o COADF Engineering Companion 1.0 nem para seus exemplos de referência.

Status de propriedade intelectual e de publicação