Skip to main content

ACTROVA

(era ESCOPO até 19/09/2026 — o pacote agora é actrova; escopo continua funcionando como atalho durante a transição.)

Autorize a ação. Prove o resultado.

Camada de assurance para agentes de IA que executam ações no mundo real.

A regra que atravessa cada peça desta biblioteca: nunca transformar ausência, silêncio ou tentativa em certeza.


O problema

Ferramenta de observabilidade te diz que reembolsar() devolveu 200.

Isso é o agente dizendo que fez. Não é o dinheiro tendo voltado.

observabilidade   o que aconteceu no código?
governança        quem podia fazer?
ACTROVA           ele tinha autoridade, ficou dentro do limite,
                  e o resultado foi REALMENTE produzido?

Os cinco conceitos, separados de propósito

INTENÇÃO      o que o agente QUER fazer          (antes)
VEREDITO      o que a política PERMITE           (antes)
EXECUÇÃO      o que a função RETORNOU            (durante)
VERIFICAÇÃO   o que a FONTE DE VERDADE confirma  (depois)
RECIBO        a evidência encadeada de tudo      (permanente)

Juntar EXECUÇÃO com VERIFICAÇÃO é o erro que este projeto existe para impedir.

Os quatro estados, e por que não são dois

PENDING        ainda não deu tempo de conferir
VERIFIED       consultei a fonte e está certo
FAILED         consultei a fonte e está ERRADO
UNVERIFIABLE   NÃO CONSEGUI consultar

⚠️ FAILED e UNVERIFIABLE são coisas radicalmente diferentes. Sistema que só tem "deu certo / deu errado" acaba inventando evidência que não tem — e é assim que agente destrói dado achando que está trabalhando.

Isso não é hipótese. É o incidente que originou o projeto: em ceo_agent.py:192 do Jarvis,

vendas = _vendas_por_fonte(dias)                    # {} quando o GraphQL erra
vk = vendas.get(fonte, {"vendas": 0, "comissao": 0.0})   # ← ausência vira zero
if vk["vendas"] > 0:   vd = "VENDE"
elif n >= min_posts:   vd = "MORTA"                 # ← 36 fontes de uma vez

Uma consulta falhou e 36 fontes de conteúdo foram desabilitadas porque "sem dado" foi lido como "vendeu zero".

Uso

from actrova import Escopo

escopo = Escopo(politicas="politicas", dados="dados")
escopo.registrar_verificador(MeuVerificador())

@escopo.guarda(agente="jarvis.ceo", acao="source.disable", alvos="fontes")
def podar_fontes(fontes):
    ...

O contrato, em YAML:

agente: jarvis.ceo
acao: source.disable
modo: observe            # não bloqueia nada — só registra

regras:
  - id: limite_absoluto
    se: {campo: quantidade, op: ">", valor: 50}
    entao: DENY
    motivo: acima de 50 não é decisão operacional, é incidente

  - id: lote_destrutivo
    se: {campo: quantidade, op: ">", valor: 5}
    entao: HOLD
    motivo: operação destrutiva em lote precisa de gente olhando

padrao: ALLOW

verificacao:
  verificador: jarvis.fontes
  tentativas: 3
  espera_segundos: [0, 5, 30]
  espera:
    desabilitadas: $quantidade

E o recibo que sai:

RECIBO      #1  a3c4825a1fb6
AGENTE      jarvis.ceo
INTENÇÃO    source.disable  (36 alvo(s))
CONTRATO    jarvis.ceo.source.disable@v1
REGRA       lote_destrutivo
DECISÃO     HOLD
MODO        observe
INVOCOU     SIM
EFEITO      UNKNOWN
VERIFICAÇÃO UNVERIFIABLE

a função foi invocada → sim, com 36 fontes o mundo mudou? → não se sabe o resultado foi provado → não e agora essas três coisas são campos diferentes

Um VERIFIED que não deixa ninguém concluir demais

Dizer "verificado" e parar ali é o jeito mais fácil de exagerar. O recibo de um reembolso sai assim:

ESTADO       VERIFIED
CONTAGEM     pedidos 1 · confirmados 1 · falhos 0 · incertos 0
COBERTURA    PARTIAL
  ⚠️ SEM COBERTURA  `cardholder.credit_received` — ninguém sabe avaliar isso
QUANTO PROVA PROVIDER_STATE · SAME_SOURCE_REREAD · PROVISIONAL
  ⚠️ EXIGE RECONCILIAÇÃO — esta conclusão tem prazo e ninguém voltou a olhar
  NOTA       a Stripe confirma o REGISTRO do reembolso; a chegada do dinheiro
             ao portador do cartão NÃO foi verificada

Quatro perguntas, quatro respostas, nenhuma inflando a outra:

o que deveria acontecer?        intenção + efeito autorizado
o que conseguimos observar?     estado + contagem
quanto a evidência prova?       assurance
o que ficou FORA da cobertura?  coverage

assurance tem três eixos, e não vira um número. O que foi provado (o registro no provedor ou a consequência no destinatário), de onde veio a evidência (o próprio executor ou um terceiro), e se ainda pode mudar. Um reembolso da Stripe vai de succeeded para failed — eles fornecem cenário de teste para simular. nivel = 3 destruiria as três dimensões, pelo mesmo motivo que executed: true destruía invocação, efeito e verificação.

coverage é capacidade declarada, não resultado de execução. O contrato diz quais afirmações a intenção exige; o verificador diz quais sabe avaliar. Se a fonte cai, a cobertura não muda — o verificador continua sabendo o que sabia, só não estabeleceu nada naquela rodada. E as lacunas são nominais, nunca percentuais: 3 de 4 = 75% parece ótimo até alguém notar que a quarta era "o dinheiro chegou no cliente".

⚠️ Três coisas que nunca colapsam, e é aqui que este eixo se paga:

SEM COBERTURA   não existe quem avalie          → buraco de PRODUTO
UNVERIFIABLE    existe, e não deu nesta rodada  → problema de INFRA
FAILED          existe, consultou, e contradiz  → INCIDENTE

📌 E o melhor efeito é o que ninguém precisa pedir: quando alguém fortalece o contrato — acrescenta cardholder.credit_received às exigidas — o verificador passa a acusar a lacuna sozinho, sem uma linha alterada. A biblioteca diz, sem ninguém perguntar: o modelo de intenção ficou mais forte que a capacidade de prova.

⚠️ O que ainda NÃO existe: PROVISIONAL descreve que a conclusão tem prazo, e nada volta a olhar. A reconciliação é um mecanismo que não foi construído — o campo torna a dívida visível, não resolvida.

Decisões de projeto, e por quê

observe é o padrão. Ninguém coloca um fornecedor desconhecido no caminho crítico de uma operação que mexe com dinheiro. Em observe, a Actrova registra o veredito e não interrompe nada — rodando semanas assim se descobre quais políticas importam antes de deixar alguma parar a operação.

Nenhuma dependência de nuvem para executar. Contrato é arquivo local, avaliação é em processo, recibo é arquivo local. Se a internet cair — ou se a Actrova sumir amanhã — a decisão continua acontecendo na máquina do cliente. Mesmo desenho de OPA e Cedar: data plane local, control plane remoto.

Recibo é encadeado por hash. Log que o fornecedor consegue editar não é prova; é a palavra do fornecedor, formatada. Cada recibo carrega o hash do anterior, e alterar um do meio quebra todos os seguintes — detectável por qualquer um, sem confiar na gente.

⚠️ Tamper-evident, não tamper-proof. Quem tem acesso de escrita pode editar um recibo do meio e recalcular a cadeia inteira a partir dali; aí ela volta a fechar. A cadeia prova consistência interna e pega alteração, remoção, reordenação e corrupção — não pega reescrita completa.

Fechar isso exige assinar a cabeça da cadeia — actrova.selo, desde 22/09. Como cada recibo carrega o hash do anterior, assinar o hash do #500 é assinar os 500: reescrever o passado passa a exigir também forjar a assinatura.

🔥 E o que protege ali é a separação, não a criptografia. Com a chave no mesmo disco do livro, quem reescreve os recibos reassina a cabeça e o selo confere — isso é um TESTE, de propósito (teste_selo.py, caso 6). O selo vale o que valer a distância entre quem escreve e quem guarda a chave: KMS, HSM, outra máquina, ou o arquivo de selos publicado onde quem grava não alcança.

⚠️ Por isso a frase continua sendo "toda alteração deixa marca", nunca "ninguém consegue alterar". O selo aumenta o custo da alteração; não a torna impossível.

O livro é a verdade; a fila de verificação é uma projeção. O recibo da ação carrega o plano inteiro — quem confere, contra qual asserção, quantas tentativas — então reconciliar() reconstrói a fila a partir do livro. Apagar fila.json, trocar de máquina ou restaurar um backup não faz a Actrova dizer "não há nada pendente" quando a verdade é "perdi a lista".

Um escritor por vez no livro. flock segurado do "ler o último recibo" até o "escrever o próximo". Sem isso, dois processos gravam com o mesmo prev e a cadeia passa a acusar adulteração onde houve concorrência — e cadeia que grita lobo perde o valor de evidência.

Verificação não altera o recibo da ação. Ela é deferida (reembolso demora a liquidar, ERP sincroniza em lote), então anexa-se um recibo novo apontando para o da ação pelo hash. Livro-razão, não linha editável.

Invocação, efeito e verificação são três campos. Um executed: true sozinho junta "a Actrova chamou o código" com "o mundo mudou", e as duas respostas divergem o tempo todo: a função que se absteve, a que quebrou no meio, a que devolveu 200 sem nada ter liquidado. invoked a Actrova sabe; effect ela só sabe quando a fonte de verdade confirma — até lá é UNKNOWN, e UNKNOWN escrito é melhor que true insinuado.

A aplicação pode declarar abstenção, nunca sucesso. Levantar SemEfeito é a função dizendo "fui chamada e de propósito não fiz nada" — abstenção ela conhece de dentro. Não existe contrapartida para declarar APPLIED: isso é a fonte de verdade quem diz.

O núcleo conhece estados; as integrações conhecem o mundo. A Actrova não implementa Stripe — implementa a gramática para você explicar como a Stripe prova alguma coisa, e depois confere se a explicação obedece às invariantes:

from actrova import conformar
print(conformar(MeuVerificadorStripe(), cenarios={...}).texto())

⚠️ A suíte exige o cenário fonte_fora e se recusa a certificar sem ele. De fora, um _consultar que devolve {} numa falha é indistinguível de um que devolve {} porque a fonte respondeu vazio — e essa confusão é o bug que originou o projeto. Certificado que passa sem testar isso dá confiança onde não há evidência.

O avaliador de política é andaime. OPA/Rego e AWS Cedar já resolvem isso, de graça e melhor. O produto da Actrova é provar o resultado, não decidir se pode. Avaliador é interface justamente para essa troca.

O seu verificador mente quando a fonte cai?

Uma linha, no seu código, sem instalar a Actrova em lugar nenhum:

python3 -m actrova conformar meu_modulo.py:MeuVerificador

Ele já diz o que dá para afirmar sem fixture nenhuma. Para responder a pergunta que decide tudo, monte os três cenários que só você consegue montar — porque só você sabe o que faz a sua fonte responder cada coisa:

python3 -m actrova exemplo > cenarios.py     # esqueleto comentado, edite
python3 -m actrova conformar meu_modulo.py:X --cenarios cenarios.py:CENARIOS

O resultado que interessa é este, e é o bug que originou o projeto:

  ❌ cenário `fonte_fora`
      a fonte está indisponível (rede caída, 500, timeout)
      → esperado um de UNVERIFIABLE, veio FAILED

    fonte_fora       REPROVADO — respondeu, e respondeu errado
    resultado        NÃO CONFORME — 1 invariante(s) violada(s)

Um except: return {} acabou de transformar "não consegui consultar" em "consultei e está errado". De fora, os dois são idênticos — e é por isso que a suíte exige esse cenário e se recusa a certificar sem ele.

Saídas: 0 conforme · 1 violou uma invariante · 2 sem certificado, porque não testado não é aprovado · 3 erro de uso. As três primeiras são diferentes de propósito: "ninguém perguntou" e "perguntamos e ele mentiu" não podem colapsar na sua CI, pelo mesmo motivo que não podem colapsar no recibo.

⚠️ E o relatório diz sozinho o que não afirma: quem escreveu a fixture do fonte_fora foi a mesma pessoa que escreveu o _consultar. Se ela supõe que a fonte levanta num 500 e a fonte devolve {} com HTTP 200, a conformidade é sobre uma fonte que não existe. Isso é um manifesto, não um selo.

Rodando

python3 teste_escopo.py      # 340 asserções — o núcleo
python3 teste_assegura.py    #  29 — os três eixos de quanto a prova prova
python3 teste_cobertura.py   #  34 — prova de quê, e o que ficou de fora
python3 teste_stripe.py      #  24 — o segundo domínio (pip install stripe)
python3 demonstracao.py      # o incidente das 36 fontes, de ponta a ponta

Só depende de pyyaml. E é python3 — em Ubuntu limpo python não existe.

⚠️ teste_stripe.py pula sem o pacote stripe — e pulado não é aprovado. A CI roda os 19 teste_*.py em Python 3.10–3.13 com o SDK instalado e reprova qualquer saída com PULADO.

Mudanças

⚠️ 0.0.x é experimental: a API e a semântica ainda mudam entre versões.

0.0.2 · 26/09/2026 — o trabalho de 21 a 26/09, que a 0.0.1 não levava.

  • Conectores (Connector Contract v0, EXPERIMENTAL) — actrova.conector e actrova.conectores: stripe (reembolso; succeeded só confirma com o valor autorizado informado), http (genérico: o código HTTP nunca vira veredito, e o que um 404 prova é declarado por quem integra) e postgres.
  • 🔒 pedir_urllib nasce endurecido pela inspeção de 26/09: o alvo — que vem dos ids que a ação do agente produziu — vai codificado como um segmento só, e ./.. dão UNVERIFIABLE sem consultar a fonte. Sem isso, fantasma/../ped_real lia outro recurso e um pedido inexistente saía VERIFIED. Caminho fixo vai na base, nunca no alvo; base só http/https.
  • Selo (actrova.selo, extra [selo]): assinatura da cabeça da cadeia, com o limite escrito — tamper-evident, não tamper-proof.
  • Reverificação de janela longa: uma ação com duas perguntas em aberto (PROVISORIO → RECONCILIADO), prometidas no próprio contrato YAML.
  • Composição: não observar nunca apaga uma observação (FALHOU > PARCIAL > VERIFICADO > PENDENTE > INVERIFICAVEL).
  • Alcance: capacidade × exercício — o recibo diz qual afirmação ficou sem resposta nesta rodada.
  • Correções: a fila não congela mais com um item envenenado; a trava confere que o flock exclui de verdade no sistema de arquivos; origem do contrato gravava UNVERIFIABLE no lugar do caminho do arquivo.
  • Build com setuptools>=83 (CVE-2026-59890).

0.0.1 · 21/09/2026 — primeira publicação.

Estado

v0. Prova uma coisa só, e prova: distinguir "o agente foi invocado" de "o mundo mudou" de "o resultado foi comprovado", num caso real.

Deliberadamente não existe ainda: dashboard, login, multi-tenant, billing, control plane em nuvem, SDK TypeScript, landing page. Os conectores existem e são experimentais (Connector Contract v0).

Release files for actrova 0.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for actrova 0.0.2
File Size Uploaded
actrova-0.0.2.tar.gz 162.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for actrova 0.0.2
File Interpreter ABI Platform
actrova-0.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 335.4 kB

Release files / actrova-0.0.2.tar.gz

Download URL actrova-0.0.2.tar.gz
Size 162.9 kB
Tags Source
SHA-256 checksum
How to use checksums
887612a5727e554e5411d8e515dfa1f8767fe96bce5dfc38894d6ddc5dc1c0ed
BLAKE2b-256 checksum
How to use checksums
a77b3a7f6a657b24b405e1b88086d8aaf782a4e0878ab18996e57069a433cf4c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / actrova-0.0.2-py3-none-any.whl

Download URL actrova-0.0.2-py3-none-any.whl
Size 172.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fdb23ac2133d8c46078e058a621b6707abb4b2d8e08923cb087471e3c313981b
BLAKE2b-256 checksum
How to use checksums
32fe58cb3819217fc6576a450ae85f3b48c7bbdf00efcd4d24f7db44f02c0a7e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.0.2 This release

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page