Nesta página
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
| Propriedade de arquitetura | Python e FastAPI |
|---|---|
| Tipo de fronteira | Modelos Pydantic congelados com extra="forbid" e strict=True; uma dataclass distinta para valores verificados |
| Validação em tempo de execução | model_validate_json sobre a resposta bruta; validação de requisições do FastAPI, respondida com 422 |
| Contrato de saída | Um response_model explícito que declara a procedência |
| Regra de arquitetura | Contratos forbidden do import-linter, executados por lint-imports |
| Contexto de execução | Propagação do OpenTelemetry (inject, extract); contextvars copiadas para as threads |
| Trilha de auditoria | PostgreSQL: 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 normas | Uma porta typing.Protocol; um adaptador httpx que transforma os resultados HTTP do fornecedor em não encontrado, indisponível e rejeitado |
| Política | OPA 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ção | Uma 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.
| Módulo | Papel | Não pode importar |
|---|---|---|
boundary.py | O contrato que uma resposta do modelo precisa cumprir | nada do domínio |
inference.py | Chama um modelo; devolve um Proposal | records, audit, api |
formats.py | Verificações determinísticas sobre um valor proposto | adaptadores, clientes HTTP |
records.py | Valores verificados; a entrada pelo caminho revisado | adaptadores, clientes HTTP |
api.py | A superfície HTTP da fronteira | |
tracing.py | Contexto de execução e identidade de auditoria | |
audit.py, schema.sql | A trilha somente de inserção | |
classification.py | A porta para um serviço externo | adaptadores, clientes HTTP |
adapters/ | O fornecedor, e somente o fornecedor | |
policy.py, policy/ | Material de política, regra e cliente | |
fence.py | O fence de publicação |
P-1 · A fronteira
- 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).
"""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.
- 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).
"""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 dateO 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.
- 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.
"""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.
revieweraqui é 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.
- 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).
# 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_modulecom 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.
Apoia-se em
- 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.
"""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.
- 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.
"""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
- 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.
"""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_idde 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_idde 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.
Apoia-se em
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
- 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.
-- 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.
Apoia-se em
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
- 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 portrace_id, ordenada por timestamp; timestamps iguais deliberadamente não são ordenados (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()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.
- 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.
"""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
- 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).
"""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.
rejectednão se resolve com novas tentativas, então precisa de um alerta de queunavailabletalvez 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.
- Propósito
- O único módulo que conhece o formato HTTP e os nomes do fornecedor.
- Propriedade de arquitetura
classIderestricteddo fornecedor são traduzidos paracodeelicenseddo 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).
"""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
- 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).
{ "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.
- 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).
# 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
revisionexplí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.
- 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:
allowprecisa ser booleano erevisionuma string não vazia, porque em Pythonbool("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.
"""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
- 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ê.
"""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.
- 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).
"""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)) == 2O 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
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 deextra="forbid"e do modo estrito, e de testes para os dois.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.
model_constructem um caminho crítico de desempenhoEle constrói um modelo sem validação. Introduzido por velocidade, torna-se o caminho que contorna a fronteira.
Contexto que para em um pool de threads
asyncio.to_threadpropaga o contexto atual; uma submissão direta a um executor não propaga, erun_in_executortambém não, a menos que quem chama copie o contexto antes, como o próprioto_threadfaz. Os spans e a identidade de auditoria então desaparecem justamente no ponto em que o trabalho é mais lento.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.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.
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.
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.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:
pytestexecuta 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-importsmantém os dois contratos.Prova de dentes: Plantar
from refapp.records import accepteminference.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
allowcombool()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
- Pydantic: ConfigDict.extra · documentação oficial · Pydantic 2.13 · Verificado em 2026-09-11
- Pydantic: Strict mode · documentação oficial · Pydantic 2.13 · Verificado em 2026-09-11
- Pydantic: Models: faux-immutability · documentação oficial · Pydantic 2.13 · Verificado em 2026-09-11
- Pydantic: Performance: model_validate_json · documentação oficial · Pydantic 2.13 · Verificado em 2026-09-11
- Pydantic: Models: creating models without validation · documentação oficial · Pydantic 2.13 · Verificado em 2026-09-11
- FastAPI: Handling errors: request validation · documentação oficial · FastAPI 0.141 · Verificado em 2026-09-11
- FastAPI: Response model: return type and data filtering · documentação oficial · FastAPI 0.141 · Verificado em 2026-09-11
- FastAPI: Background tasks · documentação oficial · FastAPI 0.141 · Verificado em 2026-09-11
- import-linter: Contract types · documentação oficial · import-linter 2.15 · Verificado em 2026-09-11
- Python Software Foundation: asyncio: Task and to_thread · documentação da linguagem · Python 3.14 · Verificado em 2026-09-11
- CPython: Lib/asyncio/threads.py · repositório do projeto · CPython 3.14.7 · Verificado em 2026-09-11
- Python Software Foundation: typing.Protocol and runtime_checkable · documentação da linguagem · Python 3.14 · Verificado em 2026-09-11
- OpenTelemetry: SDK environment variables: OTEL_PROPAGATORS · especificação · Specification 1.60.0 · Verificado em 2026-09-11
- OpenTelemetry: Context propagation · documentação oficial · Verificado em 2026-09-11
- OpenTelemetry: Baggage: security considerations · documentação oficial · Verificado em 2026-09-11
- OpenTelemetry Python: opentelemetry-api: runtime context (source) · repositório do projeto · opentelemetry-api 1.44.0 · Verificado em 2026-09-11
- W3C: Trace Context, the traceparent header · especificação · W3C Recommendation, Level 1 · Verificado em 2026-09-11
- PostgreSQL Global Development Group: Privileges · documentação oficial · PostgreSQL 18 · Verificado em 2026-09-11
- PostgreSQL Global Development Group: TRUNCATE · documentação oficial · PostgreSQL 18 · Verificado em 2026-09-11
- PostgreSQL Global Development Group: CREATE TRIGGER · documentação oficial · PostgreSQL 18 · Verificado em 2026-09-11
- PostgreSQL Global Development Group: Trigger functions in PL/pgSQL · documentação oficial · PostgreSQL 18 · Verificado em 2026-09-11
- PostgreSQL Global Development Group: Role attributes · documentação oficial · PostgreSQL 18 · Verificado em 2026-09-11
- PostgreSQL Global Development Group: ALTER TABLE: DISABLE TRIGGER · documentação oficial · PostgreSQL 18 · Verificado em 2026-09-11
- PostgreSQL Global Development Group: INSERT: ON CONFLICT · documentação oficial · PostgreSQL 18 · Verificado em 2026-09-11
- PostgreSQL Global Development Group: JSON types: jsonb indexing · documentação oficial · PostgreSQL 18 · Verificado em 2026-09-11
- PostgreSQL Global Development Group: Date/time types · documentação oficial · PostgreSQL 18 · Verificado em 2026-09-11
- Open Policy Agent: Open Policy Agent: introduction · documentação oficial · OPA 1.20 · Verificado em 2026-09-11
- Open Policy Agent: Bundles: bundle file format · documentação oficial · OPA 1.20 · Verificado em 2026-09-11
- Open Policy Agent: Policy testing · documentação oficial · OPA 1.20 · Verificado em 2026-09-11
- Open Policy Agent: Upgrading to OPA 1.0 · documentação oficial · OPA 1.20 · Verificado em 2026-09-11
- Open Policy Agent: Decision logs · documentação oficial · OPA 1.20 · Verificado em 2026-09-11
- Open Policy Agent: REST API: get a document with input · documentação oficial · OPA 1.20 · Verificado em 2026-09-11
