From d58145ff69c350cea948506bc0ab5f2d948d2ace Mon Sep 17 00:00:00 2001 From: rafaelscosta Date: Mon, 18 May 2026 15:28:09 -0300 Subject: [PATCH] =?UTF-8?q?docs(pro-ux):=20EPIC-PRO-UX-ERRORS=20+=202=20st?= =?UTF-8?q?ories=20=E2=80=94=20CLI=20error=20UX=20overhaul=20[Story=20PRO-?= =?UTF-8?q?UX.1]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Materializes EPIC-PRO-UX-ERRORS with 2 stories elaborated to Ready status (21 ACs total, 23 tasks executable, 13 points). Anchor incident: Robert (costa.wanderl@gmail.com, 2026-05-18) — bateu HTTP 403 cru em "npx -y -p @aiox-squads/core@latest aiox install" porque license-server JÁ retornava error.code SEAT_LIMIT_EXCEEDED em envelope estruturado, mas o CLI client ignorava o body da response e mostrava apenas o status HTTP. Aluno gastou ~30min em comandos client-side que não tinham conserto. EPIC scope: aiox-pro-cli bridge layer que consome envelope estendido (produzido por aios-license-server EPIC-PRO-16) e instancia AIOXError via Pro-specific ErrorRegistry estendendo defaultErrorRegistry (reuso da infra de EPIC-AIOX-ERROR-GOVERNANCE, no parallel error system). Stories: - STORY-PRO-UX.1 (8pts, Ready, blocked_by aios-license-server PRO-16.1) — Create .aiox-core/core/errors/pro-error-registry.js com 5 definitions + packages/aiox-pro-cli/src/error-bridge.js com parseEnvelopeToAIOXError e fallback 3-tier (message_pt > registry.userMessage > server message). - STORY-PRO-UX.2 (5pts, Ready, blocked_by PRO-UX.1) — PT-BR strings finais + conditional render por recovery_hint + OS-aware cleanup (PowerShell vs bash detection, fix do bug observado no caso Robert) + snapshot tests. Constraint arquitetural crítica: categorias do license-server (auth/install/ rate/system) MAPEAM para ErrorCategory existente (PERMISSION/NETWORK/ EXTERNAL_EXECUTOR/UNKNOWN) — NAO inventar categorias novas (Object.freeze em .aiox-core/core/errors/constants.js). Decisao Architect 2026-05-18. Code naming: codes Pro-specific NAO usam prefixo AIOX_ (espelham ErrorCodes do aios-license-server). Aceito pelo regex /^[A-Z0-9_]+$/ em ErrorRegistry._normalizeDefinition. Companion: aios-license-server EPIC-PRO-16 (server envelope contract). Architect G1 review APPROVED 2026-05-18. Files added with -f flag (docs/stories/ is gitignored at repo root, but tracked epic dirs follow same convention as epic-error-governance, epic-123, etc — force-added). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../epic-pro-ux-errors/EPIC-PRO-UX-ERRORS.md | 162 ++++++++++ ...RY-PRO-UX.1-CLI-BRIDGE-AND-PRO-REGISTRY.md | 298 ++++++++++++++++++ ...ORY-PRO-UX.2-PT-BR-MAPPING-AND-RECOVERY.md | 274 ++++++++++++++++ 3 files changed, 734 insertions(+) create mode 100644 docs/stories/epic-pro-ux-errors/EPIC-PRO-UX-ERRORS.md create mode 100644 docs/stories/epic-pro-ux-errors/STORY-PRO-UX.1-CLI-BRIDGE-AND-PRO-REGISTRY.md create mode 100644 docs/stories/epic-pro-ux-errors/STORY-PRO-UX.2-PT-BR-MAPPING-AND-RECOVERY.md diff --git a/docs/stories/epic-pro-ux-errors/EPIC-PRO-UX-ERRORS.md b/docs/stories/epic-pro-ux-errors/EPIC-PRO-UX-ERRORS.md new file mode 100644 index 0000000000..0d7b907874 --- /dev/null +++ b/docs/stories/epic-pro-ux-errors/EPIC-PRO-UX-ERRORS.md @@ -0,0 +1,162 @@ +# Epic PRO-UX-ERRORS: AIOX Pro CLI Error UX Bridge + +## Metadata + +| Campo | Valor | +|-------|-------| +| Epic ID | PRO-UX-ERRORS | +| Status | Draft | +| Created | 2026-05-18 | +| Owner | rafael@utidasideias.com.br | +| Points | 13 | +| Priority | P1 | +| Parent | EPIC-AIOX-ERROR-GOVERNANCE (provides AIOXError + ErrorRegistry — done) | +| Companion | aios-license-server/docs/epics/EPIC-PRO-16-ERROR-ENVELOPE-EXTENSION.md (server-side, paired) | + +## Objetivo + +Refatorar o `aiox-pro-cli` (em `packages/aiox-pro-cli/`) para consumir o envelope `ApiError` estendido pelo EPIC-PRO-16, instanciar `AIOXError` via um Pro-specific registry que estende `defaultErrorRegistry`, e renderizar UX clara em PT-BR com recovery condicional — eliminando o problema observado no caso Robert (2026-05-18, costa.wanderl@gmail.com) onde HTTP codes crus levavam alunos a horas de tentativa-erro client-side. + +Este EPIC é a **outra metade** do par cross-repo com `EPIC-PRO-16-ERROR-ENVELOPE-EXTENSION`. Server entrega contrato estendido; este EPIC entrega o consumer. + +## Escopo + +- Reuso de `AIOXError`, `ErrorRegistry`, `normalizeError`, `serializeError` (já em `.aiox-core/core/errors/`) +- Criação de Pro-specific registry (`pro-error-registry.js`) que estende `defaultErrorRegistry` com 5 definitions (1 por code do top-5 do EPIC-PRO-16) +- Mapping das categorias do license-server (`auth/install/rate/system`) para as categorias **EXISTENTES E CONGELADAS** em `aiox-core/.aiox-core/core/errors/constants.js`: + - License-server `auth` → `ErrorCategory.PERMISSION` + - License-server `rate` → `ErrorCategory.NETWORK` + - License-server `install` → `ErrorCategory.EXTERNAL_EXECUTOR` + - License-server `system` → `ErrorCategory.UNKNOWN` +- Bridge layer no CLI: consome envelope, monta `AIOXError`, exibe `userMessage` (PT-BR) + `recovery` (array de passos) + `support_code` técnico no rodapé +- Recovery conditional actions baseado em `recovery_hint`: + - `wait_and_retry` → contador visual + - `contact_support_*` → exibe `support_code` + canal + - `retry_install_cache_clean` → executa cleanup OS-aware (PowerShell vs bash detection) +- Fallback gracioso para envelopes legacy (sem campos novos) — CLI funciona mesmo se o server release atrasar + +## Fora de Escopo + +- Refatorar TODOS os erros do CLI — apenas os 5 do top-5 (mesmos do EPIC-PRO-16) +- i18n (apenas PT-BR neste MVP) +- Telemetria de error rates client-side (provavelmente útil, mas EPIC-168 observability owns) +- Migration de erros não-Pro do CLI (escopo restrito ao fluxo Pro install + activate) +- Mudanças na UI/UX gráfica do dashboard — escopo é CLI text-only + +## Stories + +| Story | Título | Pts | Status | +|-------|--------|-----|--------| +| PRO-UX.1 | Pro CLI bridge — envelope consumer + pro-error-registry + AIOXError instantiation | 8 | Draft — blocked by aios-license-server/STORY-PRO-16.1 | +| PRO-UX.2 | PT-BR registry definitions + recovery conditional actions + OS-aware cleanup | 5 | Draft — blocked by PRO-UX.1 | + +**Total: 13pts.** + +## Architecture Bridge + +``` +┌─────────────────────────────────────────┐ +│ aios-license-server (EPIC-PRO-16) │ +│ ApiError envelope: │ +│ { error: { code, message, message_pt, │ +│ recovery_hint, support_code }}│ +└─────────────────┬───────────────────────┘ + │ HTTP response + ▼ +┌─────────────────────────────────────────┐ +│ aiox-pro-cli (THIS EPIC) │ +│ 1. Parse envelope │ +│ 2. Lookup code in proErrorRegistry │ +│ 3. new AIOXError({code, userMessage, │ +│ recovery, metadata: { support_code }})│ +│ 4. Render UX condicional por recovery │ +└─────────────────────────────────────────┘ +``` + +`proErrorRegistry` estende `defaultErrorRegistry` (em `.aiox-core/core/errors/error-registry.js`), respeitando todas as invariantes: +- Códigos únicos (no overlap com defaults) +- Categorias do `ErrorCategory` congelado (mapping acima) +- `userMessage` = string PT-BR (não `message_pt` paralelo) +- `recovery` = array de strings (não `recovery_hint` string única) +- `recovery_hint` do envelope vira chave de UX render no CLI, não campo da `AIOXError` + +## Mapping: envelope → AIOXError + +| Envelope field (server) | AIOXError prop (client) | Conversão | +|---|---|---| +| `error.code` | `code` | direto (sem prefixo `AIOX_`) | +| `error.message_pt` | `userMessage` | direto se presente, fallback para registry definition | +| `error.recovery_hint` | NÃO mapeia direto — usado pra escolher `recovery` array do registry | switch case no consumer | +| `error.support_code` | `metadata.support_code` | direto | +| `error.message` | `metadata.serverMessage` (EN técnico, oculto do aluno) | direto | +| `error.details` | `metadata.serverDetails` | direto | +| http_status | `metadata.httpStatus` | direto | + +`retryable` é derivado da Registry definition (declarativo), NÃO de `recovery_hint`. + +## Backward Compat + +Fallback gracioso: se o envelope NÃO contém `message_pt`/`recovery_hint`/`support_code` (server release atrasou), o CLI: +1. Consulta `proErrorRegistry.lookup(code)` para obter `userMessage` local +2. Não exibe `support_code` (aluno reporta via email padrão) +3. Recovery default = array genérico ("Tente de novo em 5 min. Se persistir, contate o suporte.") + +## Anchor Incident + +**Robert / costa.wanderl@gmail.com / 2026-05-18 13:14 UTC** — bateu `HTTP 403` em `npx -y -p @aiox-squads/core@latest aiox install`. CLI mostrou: + +``` +✗ Falha na ativação: HTTP 403 +✗ HTTP 403 +i Preciso de ajuda? Execute: npx -y @aiox-squads/aiox-pro-cli@latest recover +``` + +Causa real: `SEAT_LIMIT_EXCEEDED` (server retornou `error.code` mas CLI ignorou). Aluno gastou ~30min rodando `find/rm` em PowerShell (comandos bash sugeridos por nós em chat — outro problema). Operador resolveu via `/pro-ops reset-seats`. + +Pós-EPIC esperado: + +``` +✗ Você já ativou em todas as 3 máquinas permitidas. + +Para resolver: + 1. Pegue o código de suporte abaixo + 2. Cole no chat com o suporte + 3. Após confirmação, rode o comando de instalação novamente + +Código de suporte: 20260518T191234Z-a1b2c3d4 +Suporte: https://suporte.aiox.dev + +(detalhes técnicos: SEAT_LIMIT_EXCEEDED — HTTP 403) +``` + +Tempo aluno: 0min de tentativa errada. Tempo operador: 30s (1 lookup via support_code). + +## Stories File List + +| File | Type | +|------|------| +| `docs/stories/epic-pro-ux-errors/EPIC-PRO-UX-ERRORS.md` | This epic | +| `docs/stories/epic-pro-ux-errors/STORY-PRO-UX.1-CLI-BRIDGE-AND-PRO-REGISTRY.md` | Story | +| `docs/stories/epic-pro-ux-errors/STORY-PRO-UX.2-PT-BR-MAPPING-AND-RECOVERY.md` | Story | +| `.aiox-core/core/errors/pro-error-registry.js` (created in PRO-UX.1) | Implementation | +| `packages/aiox-pro-cli/src/error-bridge.js` (created in PRO-UX.1) | Implementation | + +## Dependencies + +- ✅ `EPIC-AIOX-ERROR-GOVERNANCE` (Done) — `AIOXError` + `ErrorRegistry` infrastructure +- ⚠️ `aios-license-server/EPIC-PRO-16` (Draft, this session) — server contract MUST land first OR CLI ships com fallback only (degraded UX até server pronto) +- ⚠️ `STORY-PRO-13.6 PRO-ARTIFACT-SIGNED-URL` (in aios-license-server) — touches install path, coordenar para evitar merge conflict + +## Human Gates + +| Gate | Quem decide | Quando | +|---|---|---| +| G1 — ADR do envelope contract | @architect | Provided by EPIC-PRO-16 (server-side gate) | +| G2 — Threat model PT-BR strings | @dev + Rafael | Antes de PRO-UX.2 mergear | +| G3 — Final PT-BR copy review | Rafael (quality bar) | Antes de PRO-UX.2 mergear | +| G4 — Client release pós-server | @devops | Antes do `aiox-pro-cli` major bump | + +## Success Metrics + +- **Pre:** caso Robert (2026-05-18) tipo: aluno gasta 30min, operador 5min, total 1 ticket / 4-5 round-trips +- **Post:** mesmo caso = aluno 0min de tentativa errada, operador 30s, 1 round-trip diff --git a/docs/stories/epic-pro-ux-errors/STORY-PRO-UX.1-CLI-BRIDGE-AND-PRO-REGISTRY.md b/docs/stories/epic-pro-ux-errors/STORY-PRO-UX.1-CLI-BRIDGE-AND-PRO-REGISTRY.md new file mode 100644 index 0000000000..a1b5259c9c --- /dev/null +++ b/docs/stories/epic-pro-ux-errors/STORY-PRO-UX.1-CLI-BRIDGE-AND-PRO-REGISTRY.md @@ -0,0 +1,298 @@ +# Story PRO-UX.1: Pro CLI Bridge — envelope consumer + pro-error-registry + AIOXError instantiation + +## Metadata + +| Campo | Valor | +|-------|-------| +| Story ID | PRO-UX.1 | +| Epic | PRO-UX-ERRORS | +| Status | Ready | +| Executor | @dev | +| Quality Gate | @qa | +| Points | 8 | +| Priority | P1 | +| Created | 2026-05-18 | +| Blocked By | aios-license-server/STORY-PRO-16.1 (envelope schema) | +| Blocks | PRO-UX.2 | + +## Objetivo + +Implementar a bridge layer no `aiox-pro-cli` que consome o envelope `ApiError` estendido (do EPIC-PRO-16) e instancia `AIOXError` via um Pro-specific registry, reusando a infraestrutura canônica do `.aiox-core/core/errors/` sem duplicação. Esta story entrega o consumer técnico; PT-BR strings finais e recovery actions condicionais ficam em PRO-UX.2. + +## Context + +A infraestrutura canônica de erros do `aiox-core` existe desde EPIC-AIOX-ERROR-GOVERNANCE (Done, 2026-05): +- `AIOXError` em `.aiox-core/core/errors/aiox-error.js` — campos `code`, `category`, `severity`, `retryable`, `userMessage`, `recovery` (array), `metadata`, `exitCode` +- `ErrorRegistry` em `.aiox-core/core/errors/error-registry.js` — taxonomy de codes com lookup seguro +- `defaultErrorRegistry` populado com `CORE_ERROR_DEFINITIONS` (configuration, validation, filesystem, network, etc — 11 categorias congeladas) + +Esta story estende a infra com codes Pro-specific e a usa do CLI Pro, sem mexer no core. + +References: +- Parent ADR: `docs/architecture/adr/ADR-ERROR-GOVERNANCE-CONTRACT.md` +- AIOXError source: `.aiox-core/core/errors/aiox-error.js` +- ErrorRegistry source: `.aiox-core/core/errors/error-registry.js` +- Categories (congeladas): `.aiox-core/core/errors/constants.js:1-13` +- Server contract (companion): `aios-license-server/docs/stories/STORY-PRO-16.1-...` +- CLI target: `packages/aiox-pro-cli/src/` + +## Acceptance Criteria + +- **AC1:** **Pre-gate** — `aios-license-server/STORY-PRO-16.1` deve estar `Done`. Confirmar via: + - Endpoint staging do license-server retorna `{ error: { code, message, message_pt?, recovery_hint?, support_code?, details? } }` com campos novos opcionais quando aplicável + - ADR `aios-license-server/docs/decisions/PRO-16-error-envelope-extension.md` está `Accepted` + - Se PRO-16.2/16.3 ainda não mergearam, esta story funciona com fallback gracioso (AC7) + +- **AC2:** `aiox-core/.aiox-core/core/errors/pro-error-registry.js` criado exportando `proErrorRegistry` (instância de `ErrorRegistry`) + `PRO_ERROR_DEFINITIONS` (array). Skeleton: + ```js + const { ErrorRegistry } = require('./error-registry'); + const { ErrorCategory, ErrorSeverity } = require('./constants'); + + const PRO_ERROR_DEFINITIONS = [ + { + code: 'SEAT_LIMIT_EXCEEDED', + category: ErrorCategory.PERMISSION, + severity: ErrorSeverity.ERROR, + retryable: false, + userMessage: 'PLACEHOLDER — final em STORY-PRO-UX.2', + recovery: ['PLACEHOLDER'], + exitCode: 13, + }, + { + code: 'NOT_A_BUYER', + category: ErrorCategory.PERMISSION, + severity: ErrorSeverity.ERROR, + retryable: false, + userMessage: 'PLACEHOLDER', + recovery: ['PLACEHOLDER'], + exitCode: 13, + }, + { + code: 'REVOKED_KEY', + category: ErrorCategory.PERMISSION, + severity: ErrorSeverity.ERROR, + retryable: false, + userMessage: 'PLACEHOLDER', + recovery: ['PLACEHOLDER'], + exitCode: 13, + }, + { + code: 'RATE_LIMITED', + category: ErrorCategory.NETWORK, + severity: ErrorSeverity.WARNING, + retryable: true, + userMessage: 'PLACEHOLDER', + recovery: ['PLACEHOLDER'], + }, + { + code: 'PRO_ARTIFACT_UNAVAILABLE', + category: ErrorCategory.EXTERNAL_EXECUTOR, + severity: ErrorSeverity.ERROR, + retryable: true, + userMessage: 'PLACEHOLDER', + recovery: ['PLACEHOLDER'], + }, + ]; + + const proErrorRegistry = new ErrorRegistry(PRO_ERROR_DEFINITIONS); + + module.exports = { proErrorRegistry, PRO_ERROR_DEFINITIONS }; + ``` + Placeholders preenchidos com PT-BR final em PRO-UX.2. + +- **AC3:** Categorias usadas em `PRO_ERROR_DEFINITIONS` confirmadas como pertencentes ao `ErrorCategory` existente em `.aiox-core/core/errors/constants.js`: + - `SEAT_LIMIT_EXCEEDED` → `PERMISSION` + - `NOT_A_BUYER` → `PERMISSION` + - `REVOKED_KEY` → `PERMISSION` + - `RATE_LIMITED` → `NETWORK` + - `PRO_ARTIFACT_UNAVAILABLE` → `EXTERNAL_EXECUTOR` + + Implementação NÃO importa `ErrorCategory.AUTH` ou similar (não existe). Teste a verificar esse mapping (AC7). + +- **AC4:** `proErrorRegistry` instância passa `assertUnique()` test e `list()` retorna exatamente 5 entries. Constructor NÃO joga erro (nenhum code do top-5 colide com codes do `CORE_ERROR_DEFINITIONS` do `defaultErrorRegistry`). + +- **AC5:** `packages/aiox-pro-cli/src/error-bridge.js` criado exportando `parseEnvelopeToAIOXError(envelope, options)`: + ```js + const { AIOXError } = require('../../../.aiox-core/core/errors'); + const { proErrorRegistry } = require('../../../.aiox-core/core/errors/pro-error-registry'); + const { defaultErrorRegistry } = require('../../../.aiox-core/core/errors/error-registry'); + + /** + * Converte envelope HTTP do license-server em AIOXError estruturado. + * Fallback 3-tier: + * 1. envelope.error.message_pt (server populou) + * 2. registryDefinition.userMessage (PT-BR local) + * 3. envelope.error.message (server EN técnico) + */ + function parseEnvelopeToAIOXError(envelope, options = {}) { + const httpStatus = options.httpStatus; + const errorBody = envelope && envelope.error; + + if (!errorBody || !errorBody.code) { + // Envelope malformado — fallback to UNKNOWN + return new AIOXError('Erro inesperado ao consultar o servidor.', { + code: 'AIOX_UNKNOWN_ERROR', + metadata: { httpStatus, malformedEnvelope: true }, + }); + } + + const code = errorBody.code; + // Tier 1: Pro registry + let definition = proErrorRegistry.has(code) ? proErrorRegistry.lookup(code) : null; + // Tier 2: default registry + if (!definition) { + definition = defaultErrorRegistry.has(code) ? defaultErrorRegistry.lookup(code) : null; + } + // Tier 3: unknown fallback + if (!definition) { + definition = defaultErrorRegistry.lookup('AIOX_UNKNOWN_ERROR'); + } + + const userMessage = + errorBody.message_pt || + definition.userMessage || + errorBody.message || + 'Erro inesperado.'; + + return new AIOXError(userMessage, { + code, + category: definition.category, + severity: definition.severity, + retryable: definition.retryable, + recovery: definition.recovery, + exitCode: definition.exitCode, + metadata: { + support_code: errorBody.support_code, + recovery_hint: errorBody.recovery_hint, + serverMessage: errorBody.message, + serverDetails: errorBody.details, + httpStatus, + }, + }); + } + + module.exports = { parseEnvelopeToAIOXError }; + ``` + +- **AC6:** CLI entry points `packages/aiox-pro-cli/bin/aiox-pro.js` e `packages/aiox-pro-cli/src/recover.js` (paths confirmados via `ls packages/aiox-pro-cli/{bin,src}`) capturam responses HTTP do license-server e passam pelo `parseEnvelopeToAIOXError` antes de exibir output. ESCOPO: as chamadas que recebem 4xx/5xx do license-server. + +- **AC7:** Tests em `packages/aiox-pro-cli/src/__tests__/error-bridge.test.js`: + - `test('parseEnvelopeToAIOXError: envelope completo com support_code retorna AIOXError com tudo populated')` — fixture `SEAT_LIMIT_EXCEEDED` com todos os campos novos + - `test('parseEnvelopeToAIOXError: envelope legacy sem campos novos usa registry userMessage')` — fixture só com `code` + `message` (server pre-PRO-16.2) + - `test('parseEnvelopeToAIOXError: code desconhecido cai no AIOX_UNKNOWN_ERROR')` — fixture com `code: 'FOO_BAR'` + - `test('parseEnvelopeToAIOXError: envelope malformado retorna AIOXError default')` — fixture `null`, `{}`, `{ error: null }` + - `test('parseEnvelopeToAIOXError: 3-tier fallback: message_pt > registry.userMessage > message')` — 3 fixtures cobrindo cada tier + - `test('proErrorRegistry: assertUnique passa')` — sanity check + - `test('proErrorRegistry: list retorna 5 entries com categories válidas')` — confirma AC3 + - `test('proErrorRegistry + defaultErrorRegistry: nenhum overlap de codes')` — iterar codes e verificar disjoint sets + +- **AC8:** Tests rodam via `npm test` no scope de `aiox-core`. `npm run lint` + `npm run typecheck` passam. + +- **AC9:** Integration test (STRETCH GOAL — pode ser mock se staging não acessível) em `packages/aiox-pro-cli/src/__tests__/error-bridge-integration.test.js`: + - Mock HTTP fetch retornando envelope realista de `SEAT_LIMIT_EXCEEDED` + - Assertar que CLI flow renderiza `userMessage` PT-BR (placeholder OK) ao invés de "HTTP 403" + - Marcar como `test.skip` se for muito custoso — não bloqueia merge + +## Tasks + +- [ ] T1: Confirmar pre-gate AC1 — verificar que `aios-license-server/src/lib/errors.ts` no main/develop contém o tipo `RecoveryHint` e os campos novos em `ApiError` +- [ ] T2: Criar `.aiox-core/core/errors/pro-error-registry.js` com `PRO_ERROR_DEFINITIONS` skeleton (AC2) +- [ ] T3: Confirmar mapeamento de categorias (AC3) abrindo `.aiox-core/core/errors/constants.js` e validando que `PERMISSION`, `NETWORK`, `EXTERNAL_EXECUTOR` existem +- [ ] T4: Criar `packages/aiox-pro-cli/src/error-bridge.js` com `parseEnvelopeToAIOXError` (AC5) +- [ ] T5: Identificar TODAS as chamadas HTTP a license-server em `aiox-pro-cli` (grep por `fetch`/`axios`/`http`) e wrappar com `parseEnvelopeToAIOXError` (AC6) — não é refactor amplo, só os fluxos de install/activate +- [ ] T6: Implementar tests em `__tests__/error-bridge.test.js` (AC7) — 8 tests +- [ ] T7: Adicionar entity registry entry para `.aiox-core/core/errors/pro-error-registry.js` (rodar `node .aiox-core/core/ids/registry-updater.js --files .aiox-core/core/errors/pro-error-registry.js`) — segue padrão de STORY-AIOX-ERROR.1 +- [ ] T8: `npm run lint` + `npm run typecheck` + `npm test` passa (AC8) +- [ ] T9: (Stretch) Integration test mock (AC9) +- [ ] T10: Atualizar `.aiox-core/install-manifest.yaml` se hook regenerar — confirmar diff é benigno antes de commit (segue pattern STORY-AIOX-ERROR.1) +- [ ] T11: Atualizar Dev Agent Record + Change Log + +## Dev Notes + +### Files de referência + +- AIOXError class: `aiox-core/.aiox-core/core/errors/aiox-error.js` +- ErrorRegistry class: `aiox-core/.aiox-core/core/errors/error-registry.js` +- Constants congeladas: `aiox-core/.aiox-core/core/errors/constants.js` +- Core registry definitions: `aiox-core/.aiox-core/core/errors/constants.js:24-130` (`CORE_ERROR_DEFINITIONS`) +- CLI entry points: `aiox-core/packages/aiox-pro-cli/bin/aiox-pro.js` + `aiox-core/packages/aiox-pro-cli/src/recover.js` +- Server envelope shape (cross-repo reference): `aios-license-server/src/lib/errors.ts` +- Predecessor story: `aiox-core/docs/stories/epic-error-governance/STORY-AIOX-ERROR.1-CORE-CONTRACT.md` + +### Code naming convention + +- Codes Pro-specific NÃO usam prefixo `AIOX_` — eles espelham `ErrorCodes` do license-server (`SEAT_LIMIT_EXCEEDED`, etc). O regex de validation em `ErrorRegistry._normalizeDefinition` (`/^[A-Z0-9_]+$/`) aceita. +- Decision documentada no ADR-ERROR-GOVERNANCE-CONTRACT amendment futuro (ou no header comment do `pro-error-registry.js`). + +### Anti-patterns / NÃO fazer + +- **NÃO** criar `ErrorCategory.AUTH`, `INSTALL`, `RATE`, `SYSTEM` — eles NÃO existem no Object.freeze. Mapear pra existentes. +- **NÃO** modificar `aiox-error.js`, `error-registry.js`, `constants.js`, `serializer.js`, `utils.js` — INFRA congelada +- **NÃO** wrappar TODAS as chamadas HTTP do CLI — apenas os fluxos install/activate +- **NÃO** popular `recovery` arrays com strings PT-BR finais aqui — placeholders ficam até PRO-UX.2 +- **NÃO** renderizar erro no terminal ainda — esta story é bridge layer. Renderização condicional vai em PRO-UX.2 + +### Gotchas + +- `proErrorRegistry` é uma instância separada de `defaultErrorRegistry`. Lookup tier order é manual (`parseEnvelopeToAIOXError` faz check em sequência). Não tentem mergear em um single registry — semantic separation é importante. +- AIOXError construct path resolve via require relativo do `aiox-pro-cli` package. Confirmar `../../../.aiox-core/core/errors` (3 levels up de `packages/aiox-pro-cli/src/`). Se filesystem layout mudou desde 2026-05-18, ajustar paths. +- Status PRO-16.2/16.3 do license-server NÃO bloqueia esta story totalmente — fallback gracioso garante CLI funciona com envelope legacy. PR pode mergear antes do server. + +### CodeRabbit Integration + +Pre-merge focus: +- Path resolution correctness (require paths relativos) +- Test coverage de fallback paths +- No breaking change em CLI commands existentes + +## Dev Notes + +### Mapping de categorias (CRÍTICO — não inventar novas) + +Categorias do license-server (conceitos lógicos do server side) → categorias existentes em `aiox-core/.aiox-core/core/errors/constants.js`: + +| Server concept | `ErrorCategory.*` to use | +|---|---| +| `SEAT_LIMIT_EXCEEDED` (license auth says "no") | `PERMISSION` | +| `NOT_A_BUYER` (license auth says "no") | `PERMISSION` | +| `REVOKED_KEY` (license auth says "no") | `PERMISSION` | +| `RATE_LIMITED` (throttling) | `NETWORK` | +| `PRO_ARTIFACT_UNAVAILABLE` (npm/tarball fetch fail) | `EXTERNAL_EXECUTOR` | + +`AUTH`, `INSTALL`, `RATE`, `SYSTEM` NÃO existem no `ErrorCategory` congelado. Adicionar nova categoria requer **amendment formal ao ADR-ERROR-GOVERNANCE-CONTRACT** — fora do escopo deste EPIC. + +### Code naming convention + +`proErrorRegistry` aceita codes **sem prefixo `AIOX_`** (ex.: `SEAT_LIMIT_EXCEEDED`) porque eles espelham os codes do license-server (`ErrorCodes` em `src/lib/errors.ts`). O regex de validação no `ErrorRegistry._normalizeDefinition` é `/^[A-Z0-9_]+$/` — sem requirement de prefixo. `SEAT_LIMIT_EXCEEDED` passa. + +Mas: documentar no ADR (PRO-16.1) que codes Pro-specific NÃO usam prefixo `AIOX_` e que essa decisão é deliberada (preserva paridade com server-side ErrorCodes). + +### `recovery_hint` consumption + +`recovery_hint` (string única do envelope) NÃO vira campo da `AIOXError`. Vai para `metadata.recovery_hint` e é consumido pela camada de render (PRO-UX.2) para escolher qual array `recovery` exibir e qual action condicional disparar (wait_and_retry contador / retry_install_cache_clean cleanup / contact_support_* exibe support_code). + +### Definitions placeholder (texto final em PRO-UX.2) + +```js +// .aiox-core/core/errors/pro-error-registry.js +const { ErrorRegistry } = require('./error-registry'); +const { ErrorCategory, ErrorSeverity } = require('./constants'); + +const PRO_ERROR_DEFINITIONS = [ + { + code: 'SEAT_LIMIT_EXCEEDED', + category: ErrorCategory.PERMISSION, + severity: ErrorSeverity.ERROR, + retryable: false, + userMessage: 'TBD by PRO-UX.2', + recovery: ['TBD by PRO-UX.2'], + exitCode: 13, + }, + // ... 4 outras +]; + +const proErrorRegistry = new ErrorRegistry(PRO_ERROR_DEFINITIONS); +module.exports = { proErrorRegistry, PRO_ERROR_DEFINITIONS }; +``` + +PRO-UX.2 preenche `userMessage` PT-BR e `recovery` arrays. Esta story (PRO-UX.1) entrega skeleton + bridge wiring. diff --git a/docs/stories/epic-pro-ux-errors/STORY-PRO-UX.2-PT-BR-MAPPING-AND-RECOVERY.md b/docs/stories/epic-pro-ux-errors/STORY-PRO-UX.2-PT-BR-MAPPING-AND-RECOVERY.md new file mode 100644 index 0000000000..e213e01abc --- /dev/null +++ b/docs/stories/epic-pro-ux-errors/STORY-PRO-UX.2-PT-BR-MAPPING-AND-RECOVERY.md @@ -0,0 +1,274 @@ +# Story PRO-UX.2: PT-BR Registry Definitions + Recovery Conditional Actions + OS-aware Cleanup + +## Metadata + +| Campo | Valor | +|-------|-------| +| Story ID | PRO-UX.2 | +| Epic | PRO-UX-ERRORS | +| Status | Ready | +| Executor | @dev | +| Quality Gate | @qa | +| Points | 5 | +| Priority | P1 | +| Created | 2026-05-18 | +| Blocked By | PRO-UX.1 (registry skeleton + bridge layer) | + +## Objetivo + +Preencher os 5 entries do `PRO_ERROR_DEFINITIONS` com `userMessage` (PT-BR) e `recovery` arrays finais, implementar render conditional baseado em `recovery_hint`, e entregar cleanup OS-aware para o caso `retry_install_cache_clean` (PowerShell vs bash detection). + +## Context + +PRO-UX.1 entregou a bridge técnica e o registry skeleton. Esta story preenche o conteúdo PT-BR final (revisado pelo Rafael em G3) e ativa as recovery actions condicionais que diferem por `recovery_hint`. + +Caso anchor (Robert, 2026-05-18) tinha um problema duplo: (1) mensagem opaca [resolvido em PRO-UX.1] e (2) o "recovery" sugerido pelo CLI era genérico (`npx -y @aiox-squads/aiox-pro-cli@latest recover`) e levou o aluno a rodar comandos bash em PowerShell, agravando o problema. Esta story garante que cada `recovery_hint` dispara o caminho correto **e** OS-aware. + +References: +- `STORY-PRO-UX.1-CLI-BRIDGE-AND-PRO-REGISTRY.md` (predecessor) +- `EPIC-PRO-UX-ERRORS.md` (parent — mapping table de UX final) +- `.aiox-core/core/errors/pro-error-registry.js` (created in PRO-UX.1) +- `packages/aiox-pro-cli/src/error-bridge.js` (created in PRO-UX.1) + +## Acceptance Criteria + +- **AC1:** **Pre-gate** — `PRO-UX.1` deve estar `Done`. Confirmar via: + - `.aiox-core/core/errors/pro-error-registry.js` existe com 5 definitions + - `packages/aiox-pro-cli/src/error-bridge.js` existe com `parseEnvelopeToAIOXError` + - Tests da PRO-UX.1 passam + +- **AC2:** Os 5 entries de `PRO_ERROR_DEFINITIONS` em `.aiox-core/core/errors/pro-error-registry.js` têm `userMessage` PT-BR FINAL (não placeholder). Strings DEVEM passar por G3 (Rafael copy review) antes do merge: + + | Code | `userMessage` (proposta inicial — Rafael revisa em G3) | + |---|---| + | `SEAT_LIMIT_EXCEEDED` | "Você já ativou em todas as 3 máquinas permitidas." | + | `NOT_A_BUYER` | "Sua licença Pro não está ativa." | + | `REVOKED_KEY` | "Sua licença Pro foi revogada." | + | `RATE_LIMITED` | "Muitas tentativas em sequência." | + | `PRO_ARTIFACT_UNAVAILABLE` | "Erro ao baixar componente Pro." | + + **Gate humano:** PR fica blocked merge até Rafael aprovar strings finais em PR comment (G3 documented). + +- **AC3:** Os 5 entries têm `recovery` array (passos PT-BR acionáveis). Proposta inicial: + + | Code | `recovery` array (Rafael aprova em G3) | + |---|---| + | `SEAT_LIMIT_EXCEEDED` | `["Pegue o código de suporte abaixo", "Cole no chat com o suporte", "Após confirmação, rode o comando de instalação novamente"]` | + | `NOT_A_BUYER` | `["Pegue o código de suporte abaixo", "Cole no chat com o suporte", "Aguarde verificação da sua compra"]` | + | `REVOKED_KEY` | `["Pegue o código de suporte abaixo", "Cole no chat com o suporte", "Aguarde retorno do financeiro"]` | + | `RATE_LIMITED` | `["Aguarde 5 minutos", "Tente o comando novamente"]` | + | `PRO_ARTIFACT_UNAVAILABLE` | `["Aguarde 5 minutos (servidor pode estar reiniciando)", "Rode \`aiox install --recover-cache\` para limpar cache local", "Tente novamente"]` | + +- **AC4:** Render layer criada em `packages/aiox-pro-cli/src/render-error.js` exportando `renderError(aioxError)`: + - Recebe `AIOXError` (vindo de `parseEnvelopeToAIOXError`) + - Emite output para `process.stderr` ou via writer abstraction + - Formato EXATO: + ``` + ✗ {userMessage} + + Para resolver: + 1. {recovery[0]} + 2. {recovery[1]} + ... + + Código de suporte: {metadata.support_code} ← omitido se metadata.support_code é undefined + Suporte: https://suporte.aiox.dev ← omitido se recovery_hint não começa com "contact_support_" + + ({code} — HTTP {metadata.httpStatus}) ← rodapé técnico, sempre visível + ``` + - Encoding PT-BR preservado (acentos UTF-8) + - Exit code = `aioxError.exitCode` se definido, senão `1` + +- **AC5:** Recovery conditional actions implementadas em `packages/aiox-pro-cli/src/recovery-actions.js`: + ```js + /** + * Dispatch recovery action baseado em recovery_hint do envelope. + * Retorna boolean indicando se ação foi disparada (true) ou se UX é apenas informativa (false). + */ + function executeRecoveryAction(recoveryHint, context) { + switch (recoveryHint) { + case 'wait_and_retry': + return showRetryCountdown(context.waitSeconds || 300); // 5min default + case 'retry_install_cache_clean': + return executeCacheCleanup(); // OS-aware + case 'contact_support_seat_reset': + case 'contact_support_grant': + case 'contact_support_billing': + return false; // No automated action — only support_code visibility + default: + return false; + } + } + ``` + +- **AC6:** OS-aware cleanup em `executeCacheCleanup()`: + ```js + function getCacheCleanCommands() { + if (process.platform === 'win32') { + return [ + // PowerShell — Robert era Windows + `Get-ChildItem -Path . -Recurse -Filter "pro" -Directory -ErrorAction SilentlyContinue | Where-Object { $_.FullName -match "node_modules\\\\@aiox-squads\\\\pro$" } | Remove-Item -Recurse -Force`, + `Remove-Item -Recurse -Force $env:USERPROFILE\\.npm\\_npx -ErrorAction SilentlyContinue`, + ]; + } + // darwin / linux + return [ + `find . -maxdepth 5 -path "*/node_modules/@aiox-squads/pro" -type d 2>/dev/null -exec rm -rf {} + 2>/dev/null`, + `rm -rf ~/.npm/_npx 2>/dev/null`, + ]; + } + ``` + CLI executa commands via `child_process.execSync` ou similar, com timeout e error capture. Fail-safe: se cleanup falha, ainda mostra "Tente novamente em alguns minutos" sem crashar. + +- **AC7:** Tests em `packages/aiox-pro-cli/src/__tests__/render-error.test.js` (snapshot tests): + - 5 golden files em `__tests__/__snapshots__/render-error-.snap` cobrindo cada um dos 5 codes + - Snapshot cobre output exato (incluindo whitespace) renderizado pela `renderError` + - Snapshot test específico para caso Robert: envelope `SEAT_LIMIT_EXCEEDED` + `support_code: "20260518T193411Z-a1b2c3d4"` → output matching EPIC anchor section + +- **AC8:** Tests cross-platform em `packages/aiox-pro-cli/src/__tests__/recovery-actions.test.js`: + - Mock `process.platform` para `'win32'`, `'darwin'`, `'linux'` + - Validar que `getCacheCleanCommands()` retorna PowerShell em win32, bash nos outros + - Validar shape dos comandos (não a execução real — execução é mocked via `child_process` stub) + +- **AC9:** Threat model PT-BR validation (G2): Strings dos 5 codes auth (`NOT_A_BUYER`, `REVOKED_KEY`, `SEAT_LIMIT_EXCEEDED`) não habilitam account enumeration em probe não autenticado. Comentário inline no `pro-error-registry.js` justificando paridade de tom. + +- **AC10:** `npm test`, `npm run lint`, `npm run typecheck` passam. + +- **AC11:** PR fica blocked até Rafael aprovar strings finais (G3) E threat model (G2). Aprovações documentadas em PR comments. + +- **AC12:** EPIC-PRO-UX-ERRORS anchor section atualizada (no `EPIC-PRO-UX-ERRORS.md`): substituir "Pós-EPIC esperado" pelo output FINAL renderizado (após snapshot tests passarem). Story toca o file do EPIC para sincronizar. + +## Tasks + +- [ ] T1: Confirmar pre-gate AC1 +- [ ] T2: Substituir placeholders nos 5 entries de `PRO_ERROR_DEFINITIONS` com `userMessage` + `recovery` PT-BR propostos (AC2, AC3) +- [ ] T3: Criar `packages/aiox-pro-cli/src/render-error.js` com `renderError` (AC4) — começar com fixture-driven dev +- [ ] T4: Criar `packages/aiox-pro-cli/src/recovery-actions.js` com `executeRecoveryAction` + `executeCacheCleanup` + `showRetryCountdown` (AC5, AC6) +- [ ] T5: Wire renderError nos CLI entry points (`bin/aiox-pro.js`, `src/recover.js`) — substituir output cru de erros +- [ ] T6: Tests snapshot em `__tests__/render-error.test.js` (AC7) — gerar golden files inicial, congelar +- [ ] T7: Tests cross-platform em `__tests__/recovery-actions.test.js` (AC8) +- [ ] T8: Adicionar comentários inline justificando threat model paridade (AC9) +- [ ] T9: Solicitar G2 review do @dev (threat model) e G3 review do Rafael (copy) +- [ ] T10: Atualizar `EPIC-PRO-UX-ERRORS.md` anchor section com output final (AC12) +- [ ] T11: `npm test` + `npm run lint` + `npm run typecheck` passam (AC10) +- [ ] T12: Atualizar Dev Agent Record + Change Log + +## Dev Notes + +### Render output reference + +Output esperado para caso Robert (anchor): +``` +✗ Você já ativou em todas as 3 máquinas permitidas. + +Para resolver: + 1. Pegue o código de suporte abaixo + 2. Cole no chat com o suporte + 3. Após confirmação, rode o comando de instalação novamente + +Código de suporte: 20260518T193411Z-a1b2c3d4 +Suporte: https://suporte.aiox.dev + +(SEAT_LIMIT_EXCEEDED — HTTP 403) +``` + +Output esperado para `RATE_LIMITED` (sem support_code, sem link suporte): +``` +✗ Muitas tentativas em sequência. + +Para resolver: + 1. Aguarde 5 minutos + 2. Tente o comando novamente + +(RATE_LIMITED — HTTP 429) +``` + +### Anti-patterns / NÃO fazer + +- **NÃO** mexer em `error-bridge.js` (PRO-UX.1 owns) — esta story consome a `AIOXError` produzida lá +- **NÃO** mudar shape do envelope HTTP — server contract owned por PRO-16.x +- **NÃO** acessar `process.stdout` diretamente em render — usar writer abstraction pra testabilidade +- **NÃO** usar libs de cor (chalk, kleur) sem confirmar que CLI já depende delas +- **NÃO** popular telemetria de error rates aqui — EPIC-168 owns +- **NÃO** strip os comments PT-BR de placeholders sem confirmar G3 aprovou + +### Gotchas + +- Linha "Código de suporte: ..." só aparece quando `metadata.support_code` está presente (envelope estendido pós-PRO-16.3 merge). Pre-merge do PRO-16.3, linha some — esse é o fallback gracioso de PRO-UX.1. +- Linha "Suporte: https://..." só aparece quando `recovery_hint.startsWith('contact_support_')`. Outros hints (`wait_and_retry`, `retry_install_cache_clean`) NÃO mostram link de suporte (auto-recovery). +- OS-aware cleanup commands em Windows usam barra invertida dupla escaped pela TypeScript string. Cuidado com `\\\\` em fontes JS — quatro backslashes no source = duas no runtime string = um literal backslash no PowerShell. +- Robust UTF-8: Node ≥18 lida com PT-BR encoding default; mas se houver lint de spell-check no repo, garantir whitelist de palavras PT (suporte, código, etc). + +### CodeRabbit Integration + +Pre-merge focus: +- UI/UX consistency: output formatting matches mockup em EPIC anchor +- Cross-platform correctness: PowerShell vs bash commands shape válido +- PT-BR accent preservation (críticos: máquinas, código, não, ativação) +- Snapshot test stability (snapshots não dependem de timestamps ou random) + +## Dev Agent Record + +_(Preenchido por @dev)_ + +### Debug Log + +### Completion Notes + +### File List + +## Change Log + +_(Preenchido por @dev)_ + +## Dev Notes + +### PT-BR strings (proposta — Rafael revisa em G3) + +| Code | `userMessage` | `recovery` (array) | +|---|---|---| +| `SEAT_LIMIT_EXCEEDED` | "Você já ativou em todas as 3 máquinas permitidas." | ["Pegue o código de suporte abaixo", "Cole no chat com o suporte", "Após confirmação, rode o comando de instalação novamente"] | +| `NOT_A_BUYER` | "Sua licença Pro não está ativa." | ["Pegue o código de suporte abaixo", "Cole no chat com o suporte", "Aguarde verificação da sua compra"] | +| `REVOKED_KEY` | "Sua licença Pro foi revogada." | ["Pegue o código de suporte abaixo", "Cole no chat com o suporte", "Aguarde retorno do financeiro"] | +| `RATE_LIMITED` | "Muitas tentativas em sequência." | ["Aguarde 5 minutos", "Tente o comando novamente"] | +| `PRO_ARTIFACT_UNAVAILABLE` | "Erro ao baixar componente Pro." | ["Aguarde 5 minutos (servidor pode estar reiniciando)", "Rode `aiox install --recover-cache` para limpar cache local", "Tente novamente"] | + +### OS-aware cleanup (caso `retry_install_cache_clean`) + +```js +// packages/aiox-pro-cli/src/recovery-actions.js (created here) +function getCacheCleanCommands() { + if (process.platform === 'win32') { + return [ + `Get-ChildItem -Path . -Recurse -Filter "pro" -Directory -ErrorAction SilentlyContinue | Where-Object { $_.FullName -match "node_modules\\\\@aiox-squads\\\\pro$" } | Remove-Item -Recurse -Force`, + `Remove-Item -Recurse -Force $env:USERPROFILE\\.npm\\_npx -ErrorAction SilentlyContinue` + ]; + } + return [ + `find . -maxdepth 5 -path "*/node_modules/@aiox-squads/pro" -type d 2>/dev/null -exec rm -rf {} + 2>/dev/null`, + `rm -rf ~/.npm/_npx 2>/dev/null` + ]; +} +``` + +Aluno em Windows recebe PowerShell-compatible (não os `find/rm` bash que quebraram o Robert). + +### Render layout (proposta — UX validation em G3) + +``` +✗ Você já ativou em todas as 3 máquinas permitidas. + +Para resolver: + 1. Pegue o código de suporte abaixo + 2. Cole no chat com o suporte + 3. Após confirmação, rode o comando de instalação novamente + +Código de suporte: 20260518T191234Z-a1b2c3d4 +Suporte: https://suporte.aiox.dev + +(SEAT_LIMIT_EXCEEDED — HTTP 403) +``` + +### Telemetria opcional + +Telemetria de `recovery_hint` adoption (quantos alunos usaram `--recover-cache` vs ignoraram) é nice-to-have, mas fora deste MVP. EPIC-168 observability owns o canal.