# EIXO Engenharia Civil — contrato de solicitação de teste

## Finalidade e fontes

`site.json`, versão `1.1.0`, é a fonte canônica de textos, catálogo, CTAs, estados e regras. `brand.json` define identidade, voz, limites e decisões fictícias em `assumptions`. `copy.md` reproduz integralmente ambos os JSONs. A versão identifica este contrato próprio, não compatibilidade universal. `interest.payloadSchema` e `interest.recordSchema` são JSON Schema Draft 2020-12.

EIXO Engenharia Civil é uma marca fictícia de portfólio. O catálogo informativo organiza planejamento de obras, projetos e coordenação, acompanhamento técnico, reformas e adequações. Não há serviço profissional disponível, empresa registrada, equipe habilitada, experiência, prêmio, cliente, obra executada, preço, resultado ou prazo alegados. Endereço e horários são narrativos. Telefone comercial, WhatsApp, e-mail e Maps são `null`; não criar links externos de contato.

A conversão `engineering_request` registra apenas uma solicitação de escopo de TESTE. Não gera orçamento automático, visita, contratação, projeto, ART ou diagnóstico. O processo descreve solicitação, análise e proposta; só o registro fictício é executado. Informação geral não orienta execução real, cálculo, dimensionamento ou decisão de segurança. A pesquisa normativa não atesta conformidade ampla nem resolve situação individual.

## Página, catálogo e mídia

Âncoras únicas: `inicio`, `servicos`, `processo`, `escopo`, `empresa`, `duvidas`, `solicitacao`, `primeiro-passo`, `privacidade`. Hero, seis seções centrais, CTA final e privacidade fornecem essas âncoras. Catálogo `services` e `faq` são coleções, não seções duplicadas. Gestão em `/demo`; não há rotas de cases, galeria de obras ou páginas de projeto.

IDs de serviço: `planejamento`, `projetos_coordenacao`, `acompanhamento`, `reformas_adequacoes`. A opção `nao_sei` só pertence à solicitação e gestão, não cria um quinto serviço. Preços são `null`; não renderizar moeda, gratuidade ou valores a partir disso. `brand.location` e `brand.hours` fornecem endereço e horário com seus avisos ilustrativos.

CTAs de conversão: `action:open_request`, `href:#solicitacao`. CTA de serviço define `serviceId` do próprio cartão; genérico tem `serviceId:null` e preserva a escolha atual, inclusive Não sei se já escolhida. Não sei exige seleção explícita, nunca default silencioso. Foco vai para `request_heading`, distinto do ID da seção. Um CTA não registra nem altera destino remoto. Navegação e FAQ funcionam sem JavaScript; `ui.noJavascript` explica que o formulário não enviou nada.

`page.demoBanner` permanece legível em todas as rotas, inclusive `/demo`. Header, footer, legendas e formulário deixam a ficção evidente. Conteúdo principal fala de uso e planejamento. Helpers públicos usam linguagem humana; regras Unicode, UUIDs e fingerprint ficam no contrato, não em dicas de preenchimento.

`mediaPlan` limita a composição a três cenas próprias geradas: planejamento e coordenação, mais adequação opcional. IDs de mídia são referências, não arquivos disponíveis. Sem terceiros, obras reais ou licença alegada. Alts descrevem elementos físicos da composição e legendas indicam ilustração; revisar contra a imagem final. Sem pessoas por padrão; se presentes, adultos morenos ou brancos, roupa/PPE plausíveis, sem título profissional. Recortes não sugerem outras obras. Texto é composto em HTML. Sem mídia, usar `ui.imageUnavailable`. Este contrato não fixa paleta, geometria, tipografia ou DNA visual.

## Guard síncrono compartilhado

Antes de qualquer `await`, SHA-256, Web Lock ou rede, verificar e adquirir um guard síncrono compartilhado por formulário, cartões, gestão e controles remotos. Um `setState` de loading não protege handlers no mesmo tick. CTA genérico ou de serviço, select, edição, revisão, voltar, novo teste, fallback, cancelamento, exclusão, reset e mudança de modo/destino consultam o mesmo guard e pending. Durante guard/pending, nenhuma mutação ou edição é permitida; somente leitura/navegação e reconciliação controlada da tentativa original.

Capturar snapshot normalizado imutável dentro do guard. Ao esperar lock, anunciar `waitingLock`. No lock `eixoDemo:engineering-requests:v1`, reler registros, ledger e pending antes de mutar. Em `finally`, liberar guard temporário apenas após término verificável ou instalação do bloqueio durável de pending. Este bloqueio sobrevive à liberação, unmount e reload. Reconciliação pode adquirir o guard apesar de pending exclusivamente para resolver a mesma operação; não contornar bloqueios para outra ação.

Eventos `storage` atualizam lista, métricas e bloqueio entre abas. Sem Web Locks, anunciar `noWebLocks`, reler e deduplicar antes da escrita e recomendar uma aba, sem alegar atomicidade equivalente. Guard da instância não substitui lock, ledger e releitura. Invalidade síncrona sem efeito pode liberar guard e permitir correção.

## Campos e normalização

Payload tem exatamente nove propriedades obrigatórias no objeto, na ordem de `integration.contract.payloadFields`. Tipos não são coercíveis; extras são rejeitados. Etapa é opcional na UI, mas presente como `null` no objeto.

| Campo | Regra |
| --- | --- |
| `schemaVersion` | String constante `1.1.0`. |
| `brandId` | String constante `eixo-engenharia-civil-demo`. |
| `requestId` | UUID v4 minúsculo, gerado uma vez por tentativa. |
| `kind` | String constante `engineering_request`. |
| `serviceId` | `planejamento`, `projetos_coordenacao`, `acompanhamento`, `reformas_adequacoes`, `nao_sei`. |
| `stage` | `ideia`, `organizando`, `em_andamento` ou `null`. |
| `name` | String fictícia, trim → NFC → 2 a 80 pontos de código. |
| `phone` | String fictícia; entrada bruta até 20 pontos de código, depois remover não dígitos ASCII; resultado 10 ou 11 dígitos. |
| `demoAcknowledged` | Booleano constante `true`; checkbox desmarcado inicialmente. |

```json
{"schemaVersion":"1.1.0","brandId":"eixo-engenharia-civil-demo","requestId":"123e4567-e89b-42d3-a456-426614174000","kind":"engineering_request","serviceId":"nao_sei","stage":null,"name":"Pessoa de teste","phone":"00000000000","demoAcknowledged":true}
```

Nome: verificar string, rejeitar controles Unicode Cc e substitutos isolados antes de trim, normalizar NFC e medir pontos de código com `Array.from(value).length`; não unidades UTF-16 ou bytes. Sem quebras de linha. Telefone: verificar string, controles, substitutos e limite bruto antes de remover tudo fora de `[0-9]`. Não converter dígitos de outras escritas, truncar, adicionar `55` ou validar titularidade. `00000000000` é aceito para teste e não é contato. Servidor recebe telefone normalizado; revalida tipos, enums, constantes, trim/NFC e limites semânticos. JSON Schema não substitui essas verificações.

Etapa vazia vira `null`; Não sei não vira `null`. Etapa não abre campos condicionais. Cidade foi omitida. Sem endereço exato, documentos, planta, upload, descrição estrutural, CPF, orçamento numérico, medidas, urgência de segurança ou relato livre. Busca da gestão não é persistida no payload. O catálogo do servidor é cópia controlada destes IDs, nunca um catálogo fornecido pelo cliente.

## Persistência e idempotência locais

Padrão `demo_local`, endpoint vazio. Registros em `eixoDemo:engineering-requests:v1`; intenção no sufixo `:pending`; ledger no sufixo `:ledger`. Não ler ou apagar outros namespaces. Array de registros válidos: payload completo mais `recordId`, `createdAt`, `status:received|cancelled`, `cancelledAt`, `mode:demo_local|demo_remote`. `recordId` UUID distinto de `requestId`, vinculado permanentemente à tentativa. Timestamps ISO-8601 UTC; received exige cancelledAt null, cancelled exige timestamp. Tokens e capabilities nunca entram no registro.

Guard → validação/snapshot → lock → releitura de registros/ledger/pending → intenção durável → escrita → releitura/validação → finalização do ledger → confirmação. Ledger guarda marca, requestId, fingerprint, estado e resultado/ponteiro; recupera interrupção entre escritas. Mesmo nonce/payload retorna resultado anterior; nonce com outros dados é conflito sem mutação. Não deduplicar por nome ou telefone. Cancelamento não reativa por replay; exclusão individual conserva tombstone.

Sucesso local somente após escrita e releitura válida. Erro de quota usa `quotaError`, preservando dados e tentativa recuperável. Falha de storage não confirma registro. JSON ou schema corrompido é preservado, sem sobrescrita silenciosa; pending ilegível bloqueia mutações e limpeza até recuperação verificável. Interrupção local exige recuperar a mesma intenção pelo ledger/registro antes de outro ID.

Sem pending, reset confirmado pode apagar registros e ledger exclusivamente locais. Isso encerra a proteção de replay do histórico local; descartar retries locais antigos antes de reset. Não remove prova pendente remota, não chama reset remoto e não recria dados apagados. Exclusão de cópia remota mantém proteção de replay e não apaga remoto automaticamente.

## Revisão, estados e gestão

Dados → validação → revisão → operação → resultado verificável. Revisão exibe frente, etapa (Sem informação para null), nome/telefone fictícios e destino efetivo. Antes da intenção, editar é permitido. Invalid mantém campos e associa erros a resumo e controles. Loading, waitingLock, pending e reconciling não são sucesso. Cliques repetidos consultam guard. SuccessLocal/Remote exigem persistência verificada; cancelled não é nova recepção. Rejected depende de prova terminal; unverifiedRejection preserva pending. RemoteAcceptedLocalCacheError mantém bloqueio até recuperação da mesma tentativa.

Gestão `/demo` mostra somente registros válidos deste navegador. Métricas globais: total, recebidos, cancelados; total = recebidos + cancelados. Contagens por serviço usam recebidos de toda a base válida, incluindo Não sei e zeros. Filtros de serviço, status, etapa e busca combinam por AND; afetam apenas lista/contagem filtrada. `all` significa sem filtro; `unset` de etapa representa null e não pertence ao payload. Busca nome fictício ou código com trim/NFC e comparação sem distinção de caixa, até 80 pontos de código, sem buscar telefone. Base vazia e filtro sem resultado são distintos. Não calcular receita, progresso de obra, custos, clientes ou obras reais.

Consulta remota administrativa é privada e separada, sem somar conjunto remoto e cópia local. Falha de leitura não vira lista vazia. Storage inválido não gera métricas parciais apresentadas como total válido; informar corrupção até recuperação ou limpeza permitida.

Cancelar exige confirmação, muda received para cancelled com timestamp e é idempotente. Cancelled não volta a received. Enquanto servidor responder received a uma tentativa de cancelamento, usar `cancelStillReceived`, preservar operationId/pending e o estado anterior; isso não confirma cancelamento. Excluir cópia e reset exigem confirmação. Durante guard ou pending, todas as mutações ficam bloqueadas. Exclusão remota exige admin e confirmação, preserva ledger e não oferece reset público em lote.

Erros, operações e resultados usam região viva e texto, não só cor. Foco visível; diálogos devolvem foco ao acionador. Não injetar payload como HTML. O badge não cobre controles. Sem JS não simular envio.

## GAS opcional: destino e autorização

`PUBLIC_DEMO_GAS_ENDPOINT` começa vazio e `integration.gasEndpoint` é `""`. Configurar URL não prova serviço ativo. Remoto exige endpoint efetivo visível, escolha explícita de modo, aceite de demo, opt-in remoto separado desmarcado por padrão e autorização válida. Opt-in é condição da UI, não propriedade do payload. Alterar modo/destino antes de uma tentativa invalida opt-in anterior; durante pending é proibido. Não enviar automaticamente nem migrar registros locais. Sem endpoint, remoto indisponível e local acessível.

POST direto com JSON em `Content-Type:text/plain;charset=UTF-8`; não criar proxy extra. Resposta deve ser legível e validada no navegador. `no-cors`, corpo opaco, timeout, HTML, falha de parse ou HTTP 200 isolado deixam resultado desconhecido. CORS, Origin e Content-Type não são autorização; não alegar CORS arbitrariamente configurável no GAS. Health positivo não comprova criação, idempotência ou persistência externa.

Único `doGet` público: `?op=health` retorna exatamente `{ok,schemaVersion,brandId}`, sem PII, IDs, contagens, catálogo pessoal ou disponibilidade. Operações privadas via `doPost` autenticado. Envelope exato `{op,authToken,payload}`; extras são rejeitados. Token de usuário/admin e capability de cancelamento somente em RAM, nunca URL, storage, export, log, ZIP ou variável `PUBLIC_*`. Segredos de servidor em Script Properties ou armazenamento privado. Reload pede nova autorização, preservando intenção. Remover autorização da página não elimina pending.

Corpo máximo 8.192 bytes UTF-8; token/capability não vazios até 512 pontos de código, sem controles ou substitutos. Fingerprint com 64 caracteres hex minúsculos; UUIDs de tentativa/operação v4 minúsculos. Revalidar payload no servidor e neutralizar interpretação de fórmula em células Sheets ao gravar strings, preservando a representação canônica para comparação. Rate limit, retenção, rotação/revogação e permissões de usuário/admin pertencem ao operador. Autorização não depende apenas de UUID ou conhecimento da URL. Não registrar PII ou segredos em logs. O contrato não certifica segurança ou autenticação comercial.

## Operações e respostas

| Operação | Campos exatos em payload | Autoridade |
| --- | --- | --- |
| `register_request` | Nove campos de `interest.payloadSchema` | Usuário autorizado |
| `request_status` | `brandId,requestId,fingerprint` | Dono da tentativa ou admin |
| `cancel_record` | `brandId,requestId,recordId,operationId,cancelCapability` | Dono autorizado com capability, ou admin com capability privada |
| `list_records` | `brandId` | Admin |
| `delete_record` | `brandId,recordId,operationId` | Admin e confirmação |

`operationId` é UUID estável por cancelamento/exclusão; retry usa o mesmo, nunca outro alvo. Capability vincula marca, tentativa e registro. Servidor calcula fingerprint da criação; cancelamento/status usam o original. Todas as respostas privadas incluem `schemaVersion,brandId,op,ok,outcome`. `outcome` é `received|cancelled|pending|rejected`. Criação/status também incluem `requestId,fingerprint`; cancelamento/exclusão incluem `operationId,recordId`. Validar correspondência exata com a operação e tentativa esperadas, incluindo payload completo do registro normalizado e seus metadados. `ok:true` sozinho não confirma nada.

Criação/status accepted retornam `ok:true,outcome:received,record` válido após persistência verificada, com `record.status:received`. Resultado já cancelado retorna `outcome:cancelled,record.status:cancelled`; não ressuscitar. Capability só em resposta privada, recuperável por status autorizado após reload e guardada em RAM. Pending retorna `ok:false,outcome:pending`. Resultado inválido mantém unknown, sem assumir rejeição.

Listagem admin concluída: `ok:true,outcome:received,records:[...]`; received significa leitura concluída, não novas solicitações. Validar todos os registros, não converter falha em lista vazia. Exclusão admin concluída: `ok:true,outcome:cancelled,deleted:true,recordId,operationId`; esse outcome encerra a operação, não cria solicitação cancelada. Ledger conserva tombstone. Status de tentativa anteriormente aceita e excluída retorna `outcome:cancelled,deleted:true,accepted:true`, IDs e fingerprint correspondentes, sem PII do registro removido. Interface confirma exclusão e nunca oferece fallback de não aceitação.

Rejeição definitiva de criação exige `ok:false,outcome:rejected,definitive:true,accepted:false,tombstoned:true`, código `VALIDATION|UNAUTHORIZED|NONCE_CONFLICT|REJECTED_FINAL`, marca/requestId/fingerprint correspondentes e ausência comprovada de registro dessa tentativa. Tombstone gravado antes da resposta. Sem essa prova, pending permanece. Falta de autorização, conflito com outro fingerprint ou not-found isolado não encerram a tentativa original. Um conflito não autoriza tombstone que destrua nonce já aceito ou pendente com outro payload. Rejeição sem identidade válida explica erro de entrada, mas não prova terminalidade de intenção enviada.

## Fingerprint, intenção durável e reconciliação

Normalizar e validar → gerar requestId uma vez → serializar JSON em ordem `integration.contract.payloadFields`, com null explícito e sem espaços → UTF-8 → SHA-256 hex minúsculo. Excluir credenciais, opt-in e metadados. Antes da primeira rede, gravar e reler intenção:

```text
{schemaVersion,brandId,op:"register_request",requestId,fingerprint,payload,
 mode:"demo_remote",endpoint,state:"pending",createdAt}
```

Payload, destino e identidade imutáveis; não iniciar rede sem intenção preservada. Reload, fechamento, retry e timeout conservam nonce. Pending bloqueia editar, nova tentativa, CTAs que alteram seleção, reset, delete, mudança de modo/endpoint e fallback. Pending ilegível nunca é apagado para destravar. Reconciliar com POST status autorizado ou repetir criação com os mesmos dados/nonce. Not-found sozinho mantém pending porque requisição anterior ainda pode concluir.

Servidor usa `LockService.getScriptLock()` com espera limitada e liberação em `finally`. Sob lock: ler ledger durável por marca+nonce → comparar fingerprint → gravar intenção antes do efeito → procurar registro por chave única → escrever no máximo uma vez → verificar registro → finalizar ledger antes de responder. Sheets não tem transação multiaba; recuperação repara interrupção entre linha e ledger pelo mesmo vínculo, sem duplicação. Ledger não pode ser só cache/RAM. Mesmo nonce/dados devolve resultado anterior; dados diferentes são conflito sem mutação. Rejeição terminal impede aceitação tardia; exclusão impede replay; cancelamento impede reativação. Retenção conserva proteções enquanto retries/replays forem possíveis.

Após received/cancelled verificado, atualizar e reler cópia/ledger local antes de encerrar pending. Se cache falhar após aceitação remota, usar `remoteAcceptedLocalCacheError`, recuperar o mesmo registro e manter bloqueio; nunca criar novo. Deleted terminal remove cópia com ledger preservado e sem fallback. Rejeição validada não é sucesso: conservar tombstone e resolver intenção. Só depois oferecer escolha explícita de novo teste local, com NOVO nonce e ledger próprio, ou novo envio corrigido. Nunca transformar nonce rejeitado em recebido.

Cancelamento remoto prepara e relê `{schemaVersion,brandId,op:"cancel_record",requestId,recordId,operationId,fingerprint,mode,endpoint,state,createdAt}`, sem capability/token. Resultado desconhecido preserva status anterior e bloqueia mutações. Reload recupera capability por status privado e repete o mesmo operationId. Ledger de operação associa marca+operationId a tentativa/registro e conserva resultado. Exclusão remota usa intenção equivalente `op:delete_record`, com requestId/fingerprint vinculados ao registro conhecido na intenção e operationId estável; seu payload de rede segue a tabela. Não reutilizar operationId nem apagar ledger com registro.

## Downloads e limites

Recursos em `resources.links` possuem `enabled:false`. Regra: `enabled && arquivoExisteNoBuild`. Não exibir botão ativo para arquivo ausente ou desabilitado. Fonte ZIP, Code.gs, guia e contrato são recursos demonstrativos; não incluem storage, credenciais ou dados e não comprovam endpoint ativo. O código-fonte inclui somente artefatos públicos necessários, nunca segredos.

Este contrato descreve comportamento requerido; não atesta execução de GAS, persistência Sheets, entrega de mensagens, prontidão comercial, conformidade ampla ou prestação profissional. Uso real exige identidade/registro verificáveis, escopo verdadeiro, responsabilidade técnica cabível, conteúdo autorizado e avaliação específica. Não preencher esses requisitos com números fictícios.

## Referências primárias e limites editoriais

- [Lei 5.194/1966 — texto oficial](https://www.planalto.gov.br/ccivil_03/leis/l5194.htm): arts. 13–16 distinguem autoria/habilitação, identificação em documentos e placas de obra. **Inferência editorial:** não simular documento técnico, assinatura, título ou registro para dar autoridade à marca fictícia. Esses dispositivos não foram interpretados como regra universal de rodapé de toda página web.
- [Confea — Resolução 1.137/2023](https://normativos.confea.org.br/Ementas/Visualizar?id=76099): art. 2 relaciona ART à definição dos responsáveis técnicos por obras/serviços. **Inferência:** separar solicitação de teste de responsabilidade/documentação técnica; não emitir ART ou alegar que formulário equivale a registro profissional.
- [Confea — Resolução 1.160/2025](https://normativos.confea.org.br/Ementas/Visualizar?id=83001): alteração publicada da Resolução 1.137 sobre ART múltipla. **Limite:** não usar somente texto anterior como descrição integral do regime; este portfólio não ensina modalidade, emissão ou enquadramento de ART.
- [Crea-SP — Central de Dúvidas](https://www.creasp.org.br/central-de-duvidas/): explicações oficiais sobre ART, registro profissional/empresarial e acervo. **Inferência:** responsabilidade, atribuições e escopo reais precisam ser verificados; nenhuma credencial é transferida à EIXO. Conteúdo informativo não substitui atendimento do conselho.
- [Confea — consultas públicas](https://consultapublica.confea.org.br/): contém propostas de alteração normativa. **Limite:** consulta/anteprojeto não foi tratado como norma vigente ou certificado de atualização normativa integral.
- [HTB — site oficial de engenharia e construção](https://htb.com.br/): `https://www.htb.eng.br/` redireciona para este site; a página distingue soluções, pré-construção e contato. **Inferência editorial:** organizar frentes e etapas antes de convidar à conversa. Não importar obras, imagens, textos, estatísticas, clientes, garantias, geometria, equipe ou identidade.
- [Engeform — site oficial](https://www.engeform.com.br/): navegação distingue atuação, engenharia, institucional e contato. **Inferência editorial:** separar campos de atuação e apresentação da empresa. Não copiar portfólio, fotos, resultados, prêmios, equipes, contatos, slogan ou identidade. Observação editorial não equivale a captura DOM/DNA.

URLs pertencem às entidades e empresas e não indicam parceria, endosso ou direitos sobre materiais. A leitura é documental e limitada; não certifica conformidade profissional/publicitária ampla ou solução individual. Não criar registro fictício para preencher exigência real. A direção visual permanece independente destas inferências.
