Metadata-Version: 2.4
Name: guardiancontext
Version: 0.1.0
Summary: GuardianContext — memória, regras e coordenação para agentes (humanos e IA) que dividem um repositório: contexto que não se perde nem custa a janela inteira.
Author: IronStack
License: MIT
Keywords: agents,coordination,git,multi-agent,control-plane
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: PyYAML>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Provides-Extra: semantic
Requires-Dist: fastembed==0.5.1; extra == "semantic"
Provides-Extra: obsidian
Requires-Dist: watchdog>=3.0.0; extra == "obsidian"

# GuardianContext

> **Nome**: GuardianContext (decidido em 17/09/2026; antes "AgentNexusPlatform", nome já
> tomado por outro produto). Pacote `guardiancontext`, CLI `guardian`, env `GUARDIAN_*`,
> estado local em `.guardian/` e `~/.guardian/`. Marca da IronStack.
>
> **Status: em produção** — `https://api.ironstack.dev.br` (FastAPI + PostgreSQL/pgvector, Caddy/HTTPS),
> rodando a revisão de 17/09 com billing validado no sandbox da Asaas
> (`docs/trabalho/billing-sandbox-e2e-2026-09-17.md`). VM renomeada em 17/09
> (`/opt/guardiancontext`, containers `guardiancontext-*`, banco/usuário `guardiancontext`, env `GUARDIAN_*`).

Produto: transformar em SaaS o protocolo de coordenação multi-agente
(roteador + wiki por tópico + decisões + quadro de trabalho) validado dentro
do repositório **GymAi Pro** entre 06/09/2026 e 14/09/2026. Este documento é o
handoff — tudo que foi decidido numa sessão de ideação longa, pra continuar
aqui sem arrastar aquele histórico nem misturar com o desenvolvimento do app.

## Por que isto é um produto

O GymAi Pro roda com várias sessões de IA (Claude Code, Codex, Cursor, Kimi)
editando a mesma árvore git ao mesmo tempo. Isso já causou perda de commit,
furo de segurança (~40 min aberto) e horas de diagnóstico errado. A solução —
uma base de conhecimento em markdown versionada no git (router → wiki por
tópico → decisões → quadro de trabalho), mais um manual de sobrevivência pra
git compartilhado — funcionou bem o suficiente pra virar tese de produto: com
o volume de vídeos no YouTube propondo variações do mesmo problema (memória
persistente pra IA de código), esta solução, medida, saiu na frente.

Documentos de referência publicados (Claude Artifacts — acessíveis fora deste
projeto, independente de onde a conversa aconteceu):

- **[Kit de Coordenação Multi-Agente](https://claude.ai/code/artifact/b1604567-bed3-4241-acc1-f4b43ecf4689)** — os dois prompts vendáveis: o Instalador (bootstrap da estrutura em qualquer repo) e o Auditor (disciplina recorrente, o que vira assinatura).
- **[Infra do Kit de Coordenação](https://claude.ai/artifact/EieUEVezZeYAQkFoYkTHdV)** — topologia Oracle (quente) × Locaweb (fria), schema MySQL completo, plano de implementação em 7 fases, funil de onboarding do cliente.

## Decisões já tomadas (não reabrir sem motivo novo)

1. **Não centralizar o artefato de coordenação em si.** `router.md`/`wiki/`/`decisoes/`/quadro continuam sendo arquivo, na árvore do cliente — é isso que dá zero-setup, diff em PR e funcionamento offline. Avaliamos e descartamos mover isso pra MySQL (recriaria o erro do "ai-memory"/Akita: segunda fonte de verdade divergindo do git).
2. **O que centraliza**: biblioteca de padrões curada pela empresa (não pelo cliente), dashboard de auditoria hospedado, licenciamento. Nunca o conteúdo bruto do cliente (incidente, decisão, segredo de arquitetura).
3. **Modelo de acesso ao repo do cliente**: Agente instalado (CLI local) primeiro, GitHub App como possível "modo enterprise" depois — não o contrário.
4. **Licenciamento nunca por chamada de ferramenta.** Validação 1×/sessão ou 1×/dia, com cache e graça offline. Isto é lição paga: o hook `PreToolUse` do graphify custava ~400ms por chamada de Bash/Grep/Read/Glob, aplicado à árvore inteira — medido e revertido em 13/09/2026 no próprio GymAi Pro.
5. **Banco: PostgreSQL (SaaS) + SQLite (agente local).** Revogado o MySQL. O PostgreSQL é a base única do servidor — ACID, JSONB compacto, FTS nativo (`tsvector`) e busca vetorial via `pgvector` para recuperação top-k (minimiza tokens), migrations via Alembic, replication/arquivamento. O SQLite (FTS5 + sqlite-vec) é apenas cache/índice offline do agente local, derivado do Git — nunca uma segunda fonte de verdade. Tabelas desenhadas por padrão de acesso, não por entidade pura.
6. **Topologia**: tudo que responde uma sessão em andamento (API, Redis, PostgreSQL primário) fica na VM Oracle, como mais um serviço no `docker-compose.yml` que já roda `hermes-agent`/`hermes-support`. Sem arquivo frio em fornecedor separado: o PostgreSQL é a base única, e o arquivamento/retenção sai de replicação e particionamento na própria instância — não de uma segunda infraestrutura com deploy manual.
7. **Funil**: cadastro → instalação local (funciona offline, nunca bloqueia) → primeiro valor imediato (auditoria roda na hora da instalação, dashboard não nasce vazio) → loop recorrente (hook de git ou cron do cliente) → conversão free→pago via Asaas (gatilho: 2º repositório) → equipe (licença por repositório instalado, não por pessoa). **Trial de 14 dias revogado em 20/09/2026** (`decisoes/2026-09-20-plano-free-um-repositorio.md`): a instalação nasce no plano `free`, sem prazo, limitado a 1 repositório por conta; o gatilho por tempo saiu.
8. **Locaweb como arquivo frio: REVOGADA em 14/09/2026.** A decisão de usar a Locaweb como destino de arquivo frio (deploy manual cPanel/FTP, nginx/PHP na frente, risco de `413` silencioso e `REMOTE_ADDR`/`HTTPS` não confiáveis) foi abandonada. Substituto definido: PostgreSQL (SaaS) + SQLite (agente local), conforme a decisão #5.

## Números medidos que sustentam o pitch (não são estimativa)

- `TRABALHO_EM_ANDAMENTO.md` do GymAi Pro chegou a **245 KB (~66 mil tokens) em 14 dias** — 43% de uma janela de 200k gastos antes de abrir um arquivo de código. Reestruturado (quadro = stub de 6 linhas, história em `docs/trabalho/<slug>.md`) para **20 KB (~5,6k tokens)**, 11,8× menor.
- Hook `PreToolUse` do graphify: **~400ms por chamada** de ferramenta, medido, removido.
- Graphify (grafo de código) no mesmo repo: **144,3× de redução de token** por consulta, medido via `graphify benchmark`.
- Divergência normal entre `main` local e `origin/main` num repo com múltiplas sessões de IA cherry-pickando cada uma o próprio commit: **83 vs. 71 commits**, quase tudo mesmo conteúdo com SHA diferente — isso é o comportamento esperado do fluxo, não bug (lição aprendida depois de eu mesma ler errado da primeira vez).

## Em aberto — próximos passos de ideação/execução

- Nome final do produto (este README usa "GuardianContext" como nome da pasta/projeto; não foi validado como nome comercial).
- Piloto: usar o próprio GymAi Pro como conta zero, comparando os números acima como caso de referência real.

## Estado da implementação (Fases 1–4 — núcleo local + backend + licenciamento + produção)

**Implementado, testado e deployado em produção** (`https://api.ironstack.dev.br`).

### Núcleo local (`guardiancontext` — pacote Python instalável)

Control plane Git-native. `pip install -e .` expõe o comando `guardiancontext`:

- `guardian init [--mode shared|branches]` — bootstrap idempotente da estrutura (router, quadro, wiki, decisões, claims, handoffs, histórico) e do modo de trabalho (ver "Modos de trabalho").
- `guardian doctor` — valida a estrutura; falha com exit≠0 se houver erro. Reporta o modo e se o hook está instalado.
- `guardian hook install|uninstall` — hook `pre-commit` (ver "Hook de pre-commit").
- `guardian audit` — auditoria determinística (sem LLM): arquivos ausentes, quadro inchado, claims expirados/conflitantes, handoffs abandonados, links quebrados.
- `guardian claim --actor X <escopo...>` — reserva de escopo de arquivos com TTL; **recusa sobreposição** de outro ator.
- `guardian release <id>` — libera um claim.
- `guardian handoff create/accept` — handoff com **aceite único** (segundo accept é recusado).
- `guardian index rebuild/search` — índice SQLite híbrido (FTS5 + vetores) derivado dos markdown. **Rebuild incremental por sha256**: só re-embedda páginas novas/alteradas e expurga as removidas (8 páginas sem mudança → 0.2s; antes ~5s). `--semantic` força o modelo; `--lexical` desativa; default é automático. Busca combina FTS5 + cosine com **RRF** (Reciprocal Rank Fusion).
- `guardian configure --api-url ... --api-key ...` — configura a conexão com o servidor SaaS.
- `guardian sync-patterns` — sincroniza os patterns do índice local com o servidor. **Privacidade (decisão #2)**: envia apenas slug/título/sha256/embedding — nunca o corpo dos documentos. Upsert idempotente por sha256 no servidor (`repository_patterns`), com remoção de páginas deletadas.
- `guardian report [--actor X]` — roda a auditoria e envia o relatório (métricas apenas) ao servidor; **offline-safe** (enfileira se o servidor estiver fora).
- `guardian embed <texto>` — gera embedding local de 384 dimensões. Dois modos: **semântico** (`--semantic`, usa `paraphrase-multilingual-MiniLM-L12-v2` via `fastembed`, 100% local, funciona bem em português) ou **hashing determinístico** (`--hashing`, sem modelo externo). Auto-detecta o melhor disponível.
- `guardian license [--force]` — consulta o estado da licença (validação 1×/dia, com cache e graça offline — decisão #4).
- `guardian contribute <arquivo.md> [--yes] [--tags a,b]` — propõe uma decisão/regra deste repo à biblioteca curada. Única saída de corpo de documento (decisão #2): anonimiza localmente, **mostra o texto inteiro e pede confirmação** (sem TTY exige `--yes`), envia com embedding gerado no mesmo recorte do índice. Sem fila offline. O `report` avisa quando há `decisoes/`/`wiki/` novas ou alteradas desde o anterior (`.guardian/last_report.json`) — só aponta o comando, nunca envia.
- `guardian proposals list [--status pending|approved|rejected|all] [--json]` / `show <id> [--json]` / `approve <id> [--slug s] [--tags a,b]` / `reject <id> --reason "..."` — o lado do **curador** do laço de aprendizado: lista a fila (default pendentes; id, status, slug, repo/data, tamanho do corpo), mostra título/tags/corpo completo (já anonimizado), aprova (só metadados editáveis — slug e tags; o corpo sobe como foi lido; imprime o slug do pattern publicado) ou rejeita com motivo obrigatório. Usa a `api_url` do `configure`, mas o **token admin vem só de `GUARDIAN_ADMIN_TOKEN`** (nunca gravado em config, nunca impresso); sem a variável, sai com erro. 401/404/409 viram mensagens legíveis, sem traceback.

Testes: `pytest tests/` — 97 testes (59 + 21 de modos/hook + 17 de `proposals`) (os de embedding semântico pulam se `fastembed` não estiver instalado). Embedding oficial: `fastembed==0.5.1` (pinado); vetores de outra versão do fastembed **não são comparáveis** (0.5.1 × 0.8.0 deram cosseno 0,565 para o mesmo texto).

### Modos de trabalho

Várias sessões de IA num repositório trabalham de um de dois jeitos, e o que é
normal num é sinal de erro no outro. O modo é declarado em `router.md` (linha
`Modo: shared` ou `Modo: branches`) — versionado, portanto visível para todas as
sessões — e lido de lá pelo `doctor` e pelo hook. Não fica em `.guardian/config.json`
porque isso é local a uma working copy.

- **`shared`** (default): várias sessões editam e comitam a **mesma working copy**.
  `init` grava o `wiki/como-trabalhar.md` completo — as regras de árvore compartilhada
  nascidas de incidente (nunca `git add -A`, nunca `stash`/`restore .`, branch+log antes
  de cada commit, cópia entre worktrees apaga, etc.).
- **`branches`**: cada sessão numa worktree/branch própria; a principal só recebe merge.
  `init --mode branches` grava uma versão enxuta: sincronize a principal antes de criar a
  branch, uma branch por sessão/tarefa (`guardian claim` continua avisando o escopo), nunca
  copie arquivo entre worktrees (cherry-pick), merge só por PR ou fast-forward, nunca
  `--force` na principal, claims/handoffs/quadro continuam sendo o canal entre sessões.
  As regras que independem do modo ficam ("verde não prova completude", "está na principal
  se prova por conteúdo").

`guardian init` sem `--mode` respeita o modo que o `router.md` já declara; com `--mode`
troca a linha do router (migração). O `como-trabalhar.md` de um repo já instalado **nunca**
é sobrescrito — trocar de modo depois é editar esse arquivo à mão. `guardian doctor`
imprime `[MODO] …` e avisa se o router não declara modo.

### Hook de pre-commit

`guardian hook install` grava um `pre-commit` (em `.git/hooks/`, ou onde `core.hooksPath`
apontar) de poucas linhas de sh POSIX — roda no Git Bash do Windows e em Linux/macOS — que
chama `guardian hook run pre-commit`. O trabalho fica em Python. A cada commit:

1. Imprime `[guardian] branch=<x> head=<sha7> arquivos=<n>` — a regra "branch + log antes
   de cada commit" cumprida pelo hook, não pela memória do agente.
2. **Recusa** se algum arquivo em staging está num claim **ativo de outro ator**. O ator da
   sessão vem de `GUARDIAN_ACTOR` (recomendado: uma variável por sessão), de
   `guardian hook run pre-commit --actor X` ou de `actor` em `.guardian/config.json`. Sem
   ator, avisa e não bloqueia por claim.
3. No modo `shared`, **recusa staging largo**: mais de N arquivos (default 25;
   `guardian hook install --max-files N`, `GUARDIAN_HOOK_MAX_FILES` ou `hook_max_files` na
   config) **e** nenhum claim do próprio ator cobre todos eles — o sinal de `git add -A`
   numa árvore compartilhada. No modo `branches` isso é normal e não é checado.

O escape é o do git, `git commit --no-verify`; não existe outro. Instalar duas vezes não
duplica (o bloco fica entre marcadores); um `pre-commit` de outra origem que seja shell
script recebe o bloco no fim e `uninstall` remove só o bloco; se não for shell (python,
node…), a instalação recusa e diz o que fazer.

### Backend (FastAPI + PostgreSQL + pgvector + FTS + Alembic)

- `backend/app/` — API em FastAPI, SQLAlchemy 2.0 + psycopg.
- Modelos: `users`, `repositories`, `audit_events`, `api_keys`, `licenses`, `billing_events`, `patterns` (biblioteca curada com `embedding vector(384)` + `search_vector tsvector`), `pattern_proposals` (fila de propostas do `guardian contribute`).
- Busca full-text em português (`to_tsvector`/`websearch_to_tsquery`) e vetorial (`cosine_distance` via pgvector) sobre os padrões curados.
- Migrations Alembic (inclui `CREATE EXTENSION vector`).
- **Autenticação (Fase 2):** endpoints admin protegidos por `GUARDIAN_ADMIN_TOKEN` (fail-closed se vazio); endpoints do agente protegidos por API key (`api_keys`, SHA-256). Instalação via `POST /api/v1/installations` emite a chave uma única vez.
- **Licenciamento (Fase 3):** a instalação nasce no plano **`free`** — licença `ativo`, sem prazo, **1 repositório por conta** (`POST /installations` e `/installations/full` respondem 402 no segundo; plano criado sob demanda pelo código, sem migration). Segundo repositório exige assinatura ativa em plano pago via `/checkout` — vale então o `max_repositories` do plano (`None` = ilimitado). Repositórios `internal` não contam. Validação 1×/sessão ou 1×/dia (`GET /api/v1/license`), com cache e graça offline no agente (decisão #4). Licença por repositório (decisão #7). Licenças `trial` antigas continuam vencendo na data.
- **Licença interna (sem cobrança):** `POST /repositories/{id}/license/grant` (admin) → plano `internal`, ativa sem prazo, fora do Asaas. Para os projetos do próprio dono e cortesias.
- **Billing Asaas (Fase 3):** webhook `POST /api/v1/billing/webhook` autenticado pelo "Token de autenticação" do painel (header `asaas-access-token`, comparação em tempo constante, 503 sem `GUARDIAN_ASAAS_WEBHOOK_TOKEN`). A Asaas não assina o body. Idempotente por `id` do evento; responde 200 a todo payload válido (evento irrelevante → `ignored`), porque fora de 2xx a Asaas reenvia. Casa a licença por `payment.subscription`/`subscription.id` contra `asaas_subscription_id`. Mapeamento em `backend/app/services/asaas.py`. **Ainda não validado com um evento real** — o webhook no painel está inativo até o deploy.
- **Dashboard (Fase 3):** `GET /dashboard` (HTML) + `GET /api/v1/dashboard/summary` (métricas apenas — repositórios, usuários, auditorias, licenças por status, auditorias recentes; nunca conteúdo bruto).
- `docker compose up -d db` sobe `pgvector/pgvector:pg16`; `docker compose up api` sobe a API.
- **Login JWT:** `GUARDIAN_JWT_SECRET` próprio (separado do admin token), fail-closed; senha em bcrypt; admin token comparado em tempo constante. `POST /installations/full` e a leitura da biblioteca curada (`GET /patterns`, `/patterns/search`, `/patterns/similar`) exigem admin token ou API key.
- Testes: `backend/.venv/Scripts/python -m pytest backend/tests/` (precisa de Postgres + `GUARDIAN_JWT_SECRET`) — 46 passam. CI em `.github/workflows/ci.yml`: suíte do CLI, uma head no Alembic, migrations num banco vazio e no estado de produção, suíte do backend.

### O que ainda NÃO existe (fora do corte até a Fase 4)

- GitHub App (modo enterprise) — ainda só o agente local (decisão #3).
- Publicação no PyPI (pacote validado: build + twine check + smoke em venv limpo; conta criada, token salvo em `guardiancontext.pypirc`; falta `twine upload` com credenciais do usuário).

## Laço de aprendizado — o produto aprende com quem usa (20/09/2026)

A biblioteca curada cresce com decisões reais dos repositórios instalados, sem quebrar a decisão #2: o `report` compara `decisoes/` e `wiki/` com o report anterior e **só avisa** (`guardian contribute <caminho>`); o dono roda o comando, lê o texto já anonimizado e confirma. O servidor recebe em `POST /api/v1/patterns/proposals` (API key do repo), **roda o anonimizador de novo** e recusa com 422 se sobrar identificador; a proposta fica `pending` em `pattern_proposals`. Um admin lista (`GET /patterns/proposals?status=pending`), lê (`GET /patterns/proposals/{id}`), aprova (`POST .../{id}/approve`, com edições opcionais — vira `Pattern` com FTS e o vetor da proposta; a resposta traz `pattern_slug`) ou rejeita (`POST .../{id}/reject`, nota obrigatória) — pelo CLI, `guardian proposals list|show|approve|reject` com `GUARDIAN_ADMIN_TOKEN` no ambiente. Lista de identificadores proibidos em `guardiancontext/anonymizer.py` (cópia verificada por teste em `backend/app/services/anonymizer.py`). Registro: `docs/trabalho/laco-de-aprendizado-2026-09-20.md`.

## Produção (Fase 4 — deploy + webhook + instalação)

**Deployado e validado em produção** na VM Oracle (Ubuntu ARM64):

- **`https://api.ironstack.dev.br`** — API FastAPI + Postgres/pgvector via `docker compose`, atrás do Caddy (HTTPS/Let's Encrypt automático, `caddy reload` sem downtime). Subdomínio `api.ironstack.dev.br` aponta para a VM via DNS A na Locaweb.
- **Webhook Asaas cadastrado no painel, inativo**: URL `POST /api/v1/billing/webhook`, API v3, envio não sequencial, token de autenticação gerado. Ativar só depois do deploy do handler novo (o anterior exigia HMAC, que a Asaas não envia — recusaria 100% dos eventos).
- **Instalação de produção criada**: repo `GuardianContext` (id=1), owner `rogerio@ironstack.dev.br` (id=1), API key `gc_…` emitida via `POST /api/v1/installations`.
- **CLI local conectado**: `guardian doctor` → 0 erros; `guardian index rebuild` → 8 páginas indexadas, embeddings semânticos funcionando (`paraphrase-multilingual-MiniLM-L12-v2` via `fastembed`, 100% local); `guardian index search "webhook HMAC validation"` → top-k com snippets e similaridade cosine.
- **Configuração do CLI**: `guardian configure --api-url ... --api-key ...` grava `api_url` em `<repo>/.guardian/config.json` e a **API key em `~/.guardian/config.json`** (0600) — nunca dentro do repositório; chave que apareça no config do repo é ignorada. Precedência: `GUARDIAN_API_KEY`/`GUARDIAN_API_URL` no ambiente > `~/.guardian` > repo. `GUARDIAN_HOME` redireciona o diretório do usuário (testes/CI).

O guia operacional completo (deploy Docker, variáveis, setup admin, instalação do
agente, publicação no PyPI, webhook Asaas, backup, modelo de ameaças) está em
[docs/runbook-producao.md](./docs/runbook-producao.md).

## Como retomar uma sessão aqui

Leia este README primeiro — ele substitui a necessidade de reler a conversa
original (que ficou no repositório GymAi Pro, sessão longa de 06 a 14/09/2026).
Os dois artefatos linkados têm o conteúdo completo de prompts/schema/plano; não
precisam ser recriados.
