# VEREDA Advocacia — contrato de interesses de teste

## Fonte de conteúdo e finalidade

`site.json`, versão de contrato `1.1.0`, é a fonte canônica de textos, áreas, CTAs, campos, estados e regras. `brand.json` define identidade, voz, limites e dados narrativos. `copy.md` contém uma apresentação editorial e reproduções integrais dos dois JSONs. A versão identifica este contrato próprio; não declara compatibilidade com um schema de outro segmento. O payload e o registro têm JSON Schemas Draft 2020-12 embutidos em `interest.payloadSchema` e `interest.recordSchema`.

VEREDA Advocacia é uma identidade fictícia de portfólio. Não representa sociedade registrada, profissionais habilitados ou serviço jurídico disponível. Não há equipe nominal, inscrição OAB, título, formação, especialização, tempo de experiência, depoimento, resultado, cliente, preço ou gratuidade alegados. Endereço e horários são narrativos e explicitamente ilustrativos. Telefone comercial, WhatsApp, e-mail e Maps são `null`; não renderizar links de contato externo a partir desses valores.

A oferta é informativa: conhecer assuntos e organizar um primeiro contato sem expor detalhes de um caso. Contratos, civil, empresarial e imobiliário organizam relações pessoais, comerciais e patrimoniais. Podem se sobrepor; a escolha do visitante não classifica juridicamente uma situação. Não há aconselhamento individual, triagem de mérito, prescrição de conduta, promessa de resultado ou prazo de retorno.

A conversão `legal_inquiry` registra somente interesse de TESTE. Não cria consulta, agenda, atendimento, contratação ou caso jurídico. O processo descrito na página distingue localização do assunto, organização do contato e eventual análise individual em uma operação real; esta demo executa apenas o registro fictício.

## Composição e CTAs

Âncoras únicas: `inicio`, `areas`, `abordagem`, `escritorio`, `duvidas`, `contato`, `primeiro-passo`, `privacidade`. A gestão é `/demo`. `sections` contém as seções centrais; hero, CTA final e privacidade fornecem as demais âncoras. `areas` e `faq` são coleções referenciadas por `contentRef`, não seções duplicadas. `locationRef` e `hoursRef` apontam para `brand.json`.

CTAs de conversão: `href: #contato`, `action: open_inquiry`. Em uma área, `areaId` corresponde ao ID desse cartão. CTA genérico tem `areaId:null` e preserva a escolha atual. Nenhum CTA genérico define `nao_sei` automaticamente. A opção Não sei precisa de escolha explícita e não atribui área. Não alterar seleção, payload, modo ou destino quando houver pendência. Manter a âncora e levar foco ao título do contato com ID `contact_heading`, sem duplicar o ID da seção.

O aviso `page.demoBanner` permanece legível em todas as rotas, inclusive `/demo`, sem cobrir campos ou controles. As imagens têm legenda de contexto ilustrativo, não são prova de escritório real ou autoridade. Não usar retratos ou mídia de terceiros. O conteúdo não fixa cores, layout ou direção visual final.

Navegação e FAQ funcionam sem JavaScript; o formulário informa `ui.noJavascript` e não simula sucesso. Associar erros aos campos e a um resumo, anunciar validação/loading/resultado em região viva e preservar foco visível. Diálogos de cancelamento, exclusão e limpeza devolvem foco ao acionador. Estados são identificados por texto, não só por cor.

## Campos, normalização e payload

Todos os nove campos de `integration.contract.payloadFields` existem no payload, exatamente nessa ordem para o fingerprint. Campos extras são rejeitados. Tipos não são coercíveis: `true` não pode ser string ou número; enums não podem ser índices. Metadados do registro não entram no payload.

| Campo | Tipo e regra |
| --- | --- |
| `schemaVersion` | String constante `1.1.0`. |
| `brandId` | String constante `vereda-advocacia-demo`. |
| `requestId` | UUID v4 minúsculo gerado por `crypto.randomUUID()` uma vez por tentativa. |
| `kind` | String constante `legal_inquiry`. |
| `areaId` | String obrigatória: `contratos`, `civil`, `empresarial`, `imobiliario`, `nao_sei`. |
| `profile` | `pessoa`, `negocio` ou `null`; campo obrigatório no objeto, escolha opcional na interface. Vazio vira `null`. |
| `name` | String de teste, trim seguido de NFC, entre 2 e 80 pontos de código Unicode. |
| `phone` | Entrada fictícia com até 20 pontos de código antes de remover caracteres; payload contém 10 ou 11 dígitos ASCII, padrão `^[0-9]{10,11}$`. |
| `demoAcknowledged` | Booleano obrigatório `true`; checkbox começa desmarcado. |

Exemplo válido:

```json
{
  "schemaVersion": "1.1.0",
  "brandId": "vereda-advocacia-demo",
  "requestId": "123e4567-e89b-42d3-a456-426614174000",
  "kind": "legal_inquiry",
  "areaId": "nao_sei",
  "profile": null,
  "name": "Pessoa de teste",
  "phone": "00000000000",
  "demoAcknowledged": true
}
```

Comprimento Unicode significa pontos de código, não unidades UTF-16 ou bytes. Em JavaScript, usar `Array.from(value).length`; para nome, medir após trim e NFC. Rejeitar controles Unicode Cc e substitutos isolados; não permitir quebra de linha. Para telefone, validar primeiro tipo, ausência de controles/substitutos e limite bruto de 20 pontos; depois remover tudo que não seja `[0-9]`. Dígitos de outras escritas não são convertidos em ASCII. Não prefixar `55`, não truncar e não validar titularidade ou capacidade de contato. O exemplo `00000000000` é intencionalmente aceito. O limite bruto é regra de entrada no cliente; o servidor só recebe e valida o telefone normalizado. O servidor revalida trim, NFC, pontos de código, tipos, constantes e enums. O Schema não substitui essas verificações semânticas.

Não há relato livre, e-mail, CPF, processo, documento, endereço do caso, informação de saúde, segredo ou upload. Perfil não abre novos campos. Os únicos textos livres são nome fictício e telefone fictício. Não transformar a busca da gestão em campo persistido do contato. O catálogo do servidor é uma cópia controlada do contrato, nunca uma lista enviada pelo cliente.

## Registro e persistência local

Padrão: `demo_local`, endpoint vazio. Namespaces exclusivos:

- Registros: `veredaDemo:legal-inquiries:v1`.
- Pendência: `veredaDemo:legal-inquiries:v1:pending`.
- Ledger e resultados terminais locais: `veredaDemo:legal-inquiries:v1:ledger`.

Não ler ou apagar namespaces de outros portfólios. Registros são um array de objetos válidos por `interest.recordSchema`, com payload completo e metadados gerados pelo storage: `recordId` UUID, `createdAt` ISO-8601 UTC, `status:received|cancelled`, `cancelledAt:null` quando recebido ou timestamp quando cancelado, `mode:demo_local|demo_remote`. `recordId` é distinto de `requestId`, mas está permanentemente vinculado à mesma marca e tentativa. Capabilities e tokens não entram nos registros.

Fluxo local: validar → adquirir Web Lock do namespace → reler registros/ledger/pendências → registrar intenção e chave idempotente → escrever registro → reler e validar → finalizar ledger → confirmar. O ledger local guarda `brandId`, `requestId`, fingerprint, estado e ponteiro/resultado; permite recuperar uma interrupção entre escritas. Mesmo nonce e mesmo payload devolvem o resultado anterior; nonce com payload diferente é conflito sem mutação. Nunca deduplicar por nome ou telefone. Exclusão individual conserva tombstone no ledger para impedir recriação por replay. Cancelamento não pode ser revertido por retry de criação.

Sucesso local só ocorre após escrita e releitura válida. Falha preserva campos, identificador e tentativa recuperável; não exibe sucesso. JSON corrompido ou schema inválido é preservado, não sobrescrito silenciosamente. Pendência ilegível bloqueia mutações até recuperação verificável. Sem pendência, uma limpeza confirmada pode remover registros e ledger exclusivamente locais. Uma limpeza não resolve estado remoto desconhecido. Sem Web Locks, informar o limite de atomicidade entre abas; reler e deduplicar antes de cada escrita e não alegar proteção contra concorrência equivalente. Eventos `storage` atualizam lista, contagens e bloqueios em todas as abas.

## Estados, revisão e gestão

Sem erros: `idle` → `review` → `loading` → resultado verificável. Validação inválida mantém formulário e mostra `invalid`; revisão mostra assunto, perfil (Sem informação para null), nome/telefone de teste e destino. Editar é permitido antes da intenção persistida. Cliques repetidos durante loading são bloqueados. Sucesso recebido e resultado cancelado são distintos; um retry cancelado não mostra uma nova recepção. `pending` e `reconciling` não são sucesso. `rejected` exige prova terminal; `unverifiedRejection` conserva pending. Falha de cache após confirmação remota usa `remoteAcceptedLocalCacheError`, sem gerar nova tentativa.

`/demo` mostra registros deste navegador. A consulta remota administrativa é uma visão separada, privada e explicitamente identificada; não misturar conjuntos ou contar cópia local e registro remoto duas vezes. A demo local não possui autenticação comercial e não deve receber dados reais.

Métricas globais: total válido, recebidos e cancelados. Contagem por área inclui somente recebidos de todo o conjunto válido e mostra os cinco valores, inclusive Não sei e zeros. Filtros de área, estado, perfil e busca combinam com AND e alteram somente a lista e sua contagem filtrada. Busca compara nome fictício ou código com trim, NFC e comparação sem distinção entre maiúsculas/minúsculas; não altera o payload. `all` significa sem filtro; `unset` de perfil significa `null`, não é enum do payload. Não exibir receita, ocupação, agenda, clientes, vitórias ou casos julgados. Diferenciar base vazia de filtros sem correspondência.

Cancelar pede confirmação e muda recebido para cancelado com timestamp; repetir é idempotente. Cancelado não volta a recebido. Excluir cópia e reset pedem confirmação; apagam apenas dados locais. Nenhuma exclusão, reset ou outra mutação é permitida enquanto uma criação/cancelamento estiver pendente nesse namespace. Excluir localmente um registro remoto mantém proteção de replay e nunca chama exclusão remota automaticamente. Exclusão remota é operação administrativa separada, com confirmação e ledger durável preservado. Não existe reset remoto público ou limpeza remota em lote.

## GAS opcional: configuração, destino e autorização

`PUBLIC_DEMO_GAS_ENDPOINT` começa vazio; `integration.gasEndpoint` é `""`. Endpoint configurado não comprova serviço ativo. Modo remoto exige escolha explícita, destino efetivo visível, `remoteOptIn` desmarcado por padrão, aceite de demo e autorização válida. Aceite remoto é condição da interface, não campo adicional do payload. Não enviar automaticamente, migrar registros locais ou trocar modo/destino sem decisão. Sem endpoint, remoto indisponível e local acessível.

GAS direto usa POST JSON com `Content-Type: text/plain;charset=UTF-8`. Não é necessário proxy adicional para este contrato. A resposta precisa ser legível no navegador e coerente; testar o transporte real. `no-cors`, resposta opaca, HTML, timeout, erro de parse ou HTTP 200 isolado não confirmam nada. Origin/CORS e Content-Type não autorizam usuários; não alegar CORS arbitrariamente configurável no GAS.

Único GET público: `?op=health`, retorna somente `{ok, schemaVersion, brandId}`, sem PII, IDs, métricas ou disponibilidade. Todas as operações privadas, inclusive listagem administrativa, usam POST autenticado. Não incluir tokens ou capacidades em URL/query string. Credenciais de usuário/admin e `cancelCapability` ficam somente em RAM; não em localStorage, sessionStorage, export, logs, ZIP ou variáveis `PUBLIC_*`. Segredos do servidor ficam em Script Properties ou armazenamento privado. Reload exige nova autorização, mas preserva o payload e o código pendentes. Limpar token de RAM não limpa pendência.

Limite do corpo: 8.192 bytes UTF-8. Token e capability: strings não vazias até 512 pontos de código, sem controles; fingerprint: 64 dígitos hex minúsculos. `brandId` é constante; UUIDs de operações seguem UUID v4 minúsculo. Reject extra properties em todos os envelopes e payloads. Rate limit e retenção devem ser configurados no operador antes de admitir tráfego remoto; nunca confiar em limite apenas no navegador. Não registrar dados pessoais ou segredos em logs. Este mecanismo demonstra autorização privada; não certifica autenticação ou segurança comercial.

## Operações e envelopes

Envelope privado exato: `{op, authToken, payload}`. Campos de `payload` por operação:

| Operação | Campos exatos | Autoridade |
| --- | --- | --- |
| `register_inquiry` | Os nove campos de `interest.payloadSchema` | Token de usuário autorizado. |
| `request_status` | `brandId, requestId, fingerprint` | Dono da tentativa ou admin; não basta conhecer o UUID. |
| `cancel_record` | `brandId, requestId, recordId, operationId, cancelCapability` | Autorização e capability vinculada ao registro. |
| `list_records` | `brandId` | Admin; nenhuma listagem GET pública. |
| `delete_record` | `brandId, recordId, operationId` | Admin e confirmação explícita. |

`operationId` é UUID estável por tentativa de cancelamento/exclusão. Não usar nonce novo a cada retry. O servidor calcula o fingerprint da criação; status consulta o fingerprint original. Cancelamento valida o vínculo marca/requestId/recordId/capability; a resposta usa fingerprint do registro original e operationId correspondente.

Todas as respostas privadas incluem `schemaVersion, brandId, op, ok, outcome`. `outcome` pertence a `received|cancelled|pending|rejected`. Respostas ligadas a tentativa incluem `requestId` e `fingerprint`; cancelamento/exclusão incluem `operationId` e `recordId`. Criação/status recebidos ou cancelados incluem `record` válido. Para resposta `received`, `record.status` deve ser `received`; `cancelled` exige `record.status:cancelled`. Todo ID, marca, versão e fingerprint devem corresponder à tentativa e ao registro. Não basta `ok:true`.

Nova aceitação retorna `ok:true, outcome:received` somente após persistência servidor verificada; capability de cancelamento aparece apenas na resposta privada. Status autorizado pode recuperar capability em RAM após reload. Retry de registro cancelado retorna `outcome:cancelled`, sem ressuscitar. Cancelamento recebido pelo servidor só confirma `outcome:cancelled` após gravação durável. Resposta pending usa `ok:false` e mantém bloqueios. Resultado inválido é desconhecido, não rejeição definitiva.

Listagem administrativa concluída usa `ok:true, outcome:received, records:[...]`; received descreve conclusão da leitura, não novos registros. Validar todos os records. Exclusão administrativa concluída usa `ok:true, outcome:cancelled, deleted:true, recordId, operationId`; cancelled descreve encerramento da operação, não um registro de atendimento. Falhas dessas operações não são convertidas em dados vazios ou sucesso. Exclusão remota mantém tombstone; um registro excluído não é recriado por replay. Status posterior de uma tentativa antes recebida e agora excluída retorna prova terminal `outcome:cancelled, deleted:true, accepted:true`, IDs e fingerprint correspondentes, sem PII do registro removido. A interface mostra exclusão confirmada, não oferece fallback de não aceitação.

Rejeição definitiva de criação: `ok:false, outcome:rejected, definitive:true, accepted:false, tombstoned:true`, código `VALIDATION|UNAUTHORIZED|NONCE_CONFLICT|REJECTED_FINAL`, marca/nonce/fingerprint correspondentes e ausência comprovada de registro para aquela tentativa. O servidor precisa gravar tombstone antes de responder. Sem essa prova, conservar pending. Falta de autorização, conflito com outro fingerprint ou not-found isolado não encerra a tentativa original; obter reconciliação autorizada. Uma rejeição sem IDs válidos pode explicar erro de entrada, mas nunca prova terminalidade de uma intenção já enviada.

## Fingerprint, ledger e recuperação

Normalizar campos → validar → gerar UUID de tentativa → construir JSON em ordem `payloadFields`, sem espaços e com null explícito → codificar UTF-8 → SHA-256 hex minúsculo. Fingerprint exclui token, opt-in da UI e metadados. Antes do primeiro POST, persistir e reler no namespace pending:

```text
{schemaVersion, brandId, op:"register_inquiry", requestId, fingerprint,
 payload, mode:"demo_remote", endpoint, state:"pending", createdAt}
```

Esse objeto tem payload exato e nenhuma credencial. Não iniciar rede se a pendência não puder ser preservada. Ela guarda destino e dados imutáveis; reload, fechamento, timeout e retry não geram novo nonce. Não editar payload, mudar endpoint, excluir registros, resetar, mudar modo ou fazer fallback enquanto o resultado for desconhecido. Pendência ilegível conserva bloqueios; não apagá-la para tentar novamente.

No GAS, `LockService.getScriptLock()` com espera limitada e liberação em `finally`. Sob lock, consultar ledger durável por `brandId+requestId`, comparar fingerprint, gravar intenção antes do efeito e persistir resultado consistente antes de responder. Sheets não oferece transação multiaba: uma recuperação precisa buscar o registro pela chave única e reparar ledger após interrupção entre inserção e finalização, sem segunda linha. Ledger não pode ser só cache/RAM. Mesmo nonce/payload retorna resultado original; mesmo nonce/payload diferente é conflito sem mutação do original. Tombstone de rejeição impede aceitação tardia. Tombstone de exclusão impede recriação. Retenção nunca remove proteção enquanto retries/replays daquela chave puderem ocorrer.

Reconciliar por status privado autenticado ou repetir a criação com o mesmo nonce/payload/fingerprint. Not-found sozinho mantém pending, pois uma chamada anterior pode concluir. Rejeição com prova terminal nunca é sucesso; resolver a intenção preservando o tombstone e os dados para correção. Só então oferecer escolha explícita de novo registro local, com NOVO nonce e ledger próprio. Não converter a tentativa rejeitada em received. Novo envio corrigido também exige resolução anterior e nonce novo.

Se servidor confirmou mas cache local falhou, manter pending e mostrar `remoteAcceptedLocalCacheError`. Recuperar o mesmo registro por status e concluir a cópia local antes de liberar bloqueios; não duplicar. Resultados terminais received/cancelled só encerram pending após atualização local consistente. Resposta deleted terminal apaga a cópia local com ledger preservado, sem fallback.

Antes de cancelar remotamente, persistir/reler `{schemaVersion,brandId,op:"cancel_record",requestId,recordId,operationId,fingerprint,mode,endpoint,state,createdAt}`. Fingerprint é o da criação; não armazenar capability/token. Reload recupera capability por status privado e repete o mesmo operationId. Cancelamento desconhecido não muda status local e mantém bloqueios. Ledger de cancelamento usa marca+operationId, vincula requestId/recordId e impedirá reativação tardia. Aplicar o mesmo preparo durável à exclusão remota administrativa, com `op:"delete_record"` e operationId; excluir somente depois de resposta coerente. Não reutilizar operationId para alvo diferente.

## Recursos e limites operacionais

`resources.links` começa integralmente com `enabled:false`. Renderizar somente quando `enabled && arquivoExisteNoBuild`. Arquivo ausente ou desabilitado não recebe botão ativo, link quebrado ou promessa de download. ZIP, exemplo GAS, guia e contrato são recursos demonstrativos; não comprovam endpoint ativo. Nenhum export inclui dados armazenados, tokens ou segredos.

O contrato especifica comportamento; não atesta execução de um endpoint real, persistência externa, entrega de mensagens, autenticação comercial, conformidade jurídica ampla ou operação profissional. Não alterar essas afirmações apenas por health positivo. Uso profissional real exige identidade e registro verificáveis, conteúdo verdadeiro e avaliação específica; não preencher números OAB com valores fictícios.

## Referências primárias e limites editoriais

Consulta documental de referência: 7 de outubro de 2026. URLs pertencem às entidades/autores e não indicam vínculo com a VEREDA.

- [OAB — Provimento 205/2021](https://www.oab.org.br/leisnormas/legislacao/provimentos/205-2021): arts. 1–6 orientam veracidade, sobriedade e limites de divulgação. **Inferência editorial:** áreas descritivas, sem preços, gratuidade, resultados, comparação, títulos ou símbolos da OAB. Não é parecer de conformidade.
- [OAB — Código de Ética e Disciplina, Resolução 02/2015](https://www.oab.org.br/leisnormas/legislacao/resolucoes/02-2015): arts. 39–44 tratam de publicidade informativa e identificação profissional. **Limite:** a demo sem registro não é modelo pronto de publicidade para escritório real; não inventar inscrição para preencher esse requisito.
- [OAB — revisão do marketing jurídico](https://www.oab.org.br/noticia/63703/atualizacao-do-provimento-do-marketing-juridico-entra-na-fase-final-apos-encontro-com-presidentes-dos-teds): notícia de dezembro de 2025 descreve debate de atualização. A consulta ao texto e pesquisas no domínio oficial não localizaram ato publicado que altere ou substitua o 205/2021. Isso é um limite da pesquisa, não prova absoluta de inexistência. Propostas não foram tratadas como norma vigente.
- [OAB — notícias da Corregedoria](https://corregedoria.oab.org.br/home/Noticias): a chamada de aprovação de “novo provimento” encontrada na busca é datada de 15/07/2021 na página, não de 2026. Não usar data de indexação como vigência.
- [Mattos Filho — Contratos e desenvolvimento de projetos](https://www.mattosfilho.com.br/area-atuacao/contratos-e-desenvolvimento-de-projetos/): página oficial distingue área e temas de atuação. **Inferência editorial:** separar catálogo e organização do contato. Não transferir escopo industrial, equipe, clientes, prêmios, claims, textos ou mídia à VEREDA.
- [Demarest — Contratos comerciais e negociações](https://www.demarest.com.br/areas-de-atuacao/contratos-comerciais-e-negociacoes/): referência oficial de organização por prática. **Inferência editorial:** rótulos objetivos e temas concretos, sem importar especialização, credenciais, capacidade operacional ou casos. Não copiar obras, fotos, identidade ou copy.

As referências sustentam escolhas editoriais e limites, não equivalência entre escritórios, autorização para exercer advocacia ou direitos sobre materiais. Todas as informações próprias fictícias estão declaradas em `brand.assumptions`.
