Catalog PyPI
PACKAGE EXPLORERView in registry

actrova

PyPI

Autorize a ação. Prove o resultado. Assurance de execução para agentes de IA.

agent-toolsai-agentsassuranceaudit-loggovernancepolicytamper-evidentverification
Versions collected1
Direct dependencies4From release 0.0.5
Last observed20 Oct, 18:40 UTC

README

Version 0.0.5 documentation

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.

Mandar para a Actrova Cloud

O livro local é a primeira cópia. A Actrova Cloud guarda a segunda — uma que quem executou a ação não controla. Quem manda é o actrova enviar, num timer, nunca de dentro do agente: com a Cloud fora do ar o agente segue, e a próxima rodada manda o que ficou para trás.

# a credencial vem da Actrova (só escreve recibos, num livro só), por scp:
chmod 600 /etc/actrova/runtime.token

actrova enviar --livro escopo_dados/livro.jsonl --livro-id NOME-DO-LIVRO \
  --cloud https://api.actrova.com --token-arquivo /etc/actrova/runtime.token
# se o livro tem chave matriculada na Cloud: --chave caminho/da/chave.pem

Num timer do systemd (a cada minuto):

# /etc/systemd/system/actrova-enviar.service
[Service]
Type=oneshot
ExecStart=/caminho/do/venv/bin/actrova enviar --livro /caminho/livro.jsonl \
  --livro-id NOME-DO-LIVRO --cloud https://api.actrova.com \
  --token-arquivo /etc/actrova/runtime.token
# /etc/systemd/system/actrova-enviar.timer
[Timer]
OnBootSec=1min
OnUnitActiveSec=1min
[Install]
WantedBy=timers.target

Saída: 0 em dia · 2 🔥 achado (bifurcação, recibo recusado, livro local com número faltando ou repetido, redirecionamento — alguém precisa olhar) · 3 credencial recusada · 4 Cloud fora ou sobrecarregada (o timer tenta de novo) · 1 configuração. O token só vem de arquivo 600, a URL só https, e nenhuma saída mostra token ou corpo de recibo. Até onde a Cloud confirmou fica em <livro>.cloud.json; apagá-lo só faz reenviar (a Cloud responde que já tinha).

🔒 Redirecionamento nenhum é seguido. Um 3xx para o envio ali mesmo: o token só vai ao endereço do --cloud, nunca a outro — nem de https para http. Se a Cloud mudou de endereço, mude o --cloud.

🔥 Livro local com buraco não é "em dia". Antes de mandar, o enviar confere que o livro é #1, #2, #3… — inteiros a partir do 1, sem número faltando nem repetido. Se não for, não manda nada e sai 2: o livro foi copiado, restaurado ou editado, e quem decide o que vale é uma pessoa.

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.4 · 01/10/2026 — correções de segurança do actrova enviar, da revisão do Astra (30/09). Quem usa o enviar da 0.0.3 deve atualizar.

  • 🔒 Redirecionamento nenhum é seguido (ENV-R1). Na 0.0.3, uma resposta 3xx levava o token de escrita ao endereço do Location — outra origem, e até de https para http. Agora o 3xx para o envio (saída 2), sem mandar nada para lugar nenhum além do --cloud.
  • 🔥 Livro local com buraco não é "em dia" (ENV-R2, ENV-R2b). Na 0.0.3, um livro [#1, #3] mandava só o #1 e saía 0. Agora número faltando, repetido ou fora do domínio (0, negativo, não inteiro) no livro local para tudo antes de mandar (saída 2).

0.0.3 · 29/09/2026 — o runtime passa a mandar para a Actrova Cloud.

  • actrova enviar (actrova.replica): manda, em ordem, os recibos que a Cloud ainda não confirmou, exatamente como estão no livro; assina cada um quando o livro tem chave matriculada (--chave, extra [selo]). Para e avisa em bifurcação ou recibo recusado (saída 2), em credencial recusada (3); com a Cloud fora do ar, só adia (4). Rode num timer, nunca de dentro do agente — ver "Mandar para a Actrova Cloud".
  • 🔒 Token só de arquivo 600; URL só https (http só em 127.0.0.1); nenhuma saída mostra token ou corpo de recibo.
  • Sem dependência nova: o envio usa só a biblioteca padrão.

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.

Neste pacote, deliberadamente não existe: dashboard, login, multi-tenant, billing — isso é a Actrova Cloud, à parte, e este pacote nunca depende dela (enviar só fala HTTP). Ainda não existe: SDK TypeScript, landing page. Os conectores existem e são experimentais (Connector Contract v0).

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.0.59 Oct 20264

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
z-scriptz/escopo-runtime

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 18:40 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:6346cbf37734129b2eb48132d4d35a03d40cdcda2d4eb5c29089c07178a08e89