ACTROVA
(era ESCOPO até 19/09/2026 — o pacote agora é
actrova;escopocontinua 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.conectoreactrova.conectores:stripe(reembolso;succeededsó 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) epostgres. - 🔒
pedir_urllibnasce 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ãoUNVERIFIABLEsem consultar a fonte. Sem isso,fantasma/../ped_reallia outro recurso e um pedido inexistente saíaVERIFIED. Caminho fixo vai nabase, nunca no alvo;basesó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
travaconfere que oflockexclui de verdade no sistema de arquivos;origemdo contrato gravavaUNVERIFIABLEno 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)
| File | Size | Uploaded | |
|---|---|---|---|
| actrova-0.0.2.tar.gz | 162.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|