Metadata-Version: 2.4
Name: actrova
Version: 0.0.2
Summary: Autorize a ação. Prove o resultado. Assurance de execução para agentes de IA.
License-Expression: Apache-2.0
Project-URL: Repositorio, https://github.com/z-scriptz/escopo-runtime
Project-URL: Codigo, https://github.com/z-scriptz/escopo-runtime
Keywords: ai-agents,assurance,audit-log,verification,tamper-evident,policy,governance,agent-tools
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: System :: Logging
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyyaml>=5.4
Provides-Extra: selo
Requires-Dist: cryptography>=41; extra == "selo"
Provides-Extra: stripe
Requires-Dist: stripe>=15.6; extra == "stripe"
Provides-Extra: postgres
Requires-Dist: psycopg[binary]>=3.1; extra == "postgres"
Dynamic: license-file

# 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,

```python
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

```python
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:

```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:

```python
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:

```bash
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:

```bash
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

```bash
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).
