@oondemand/create-central-oon 0.7.3 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +18 -14
  2. package/bin/cli.js +12 -2
  3. package/docs/AGENTS.md +12 -56
  4. package/docs/AGENT_WORKFLOW.md +8 -15
  5. package/docs/BACKEND_API.md +5 -5
  6. package/docs/BACKEND_DOMAIN_MANIFEST.md +1 -1
  7. package/docs/CAPABILITIES.md +11 -3
  8. package/docs/CHECKLIST_IMPLEMENTACAO.md +4 -5
  9. package/docs/CORE_UPGRADE.md +17 -14
  10. package/docs/ERRORS.md +1 -1
  11. package/docs/EXECUTABLE_EXAMPLE.md +42 -0
  12. package/docs/NATIVE_RESOURCES.md +94 -0
  13. package/docs/OON_APP_AGENT_GUIDE.md +26 -45
  14. package/docs/PDF_RENDERING.md +2 -3
  15. package/docs/PUBLIC_CONTRACTS.md +124 -0
  16. package/docs/RBAC_SECURITY.md +1 -1
  17. package/docs/ROUTES_HOOKS_WORKERS.md +19 -8
  18. package/docs/SECRETS.md +54 -0
  19. package/docs/TASK_EXAMPLES.md +134 -0
  20. package/docs/TASK_INDEX.md +36 -0
  21. package/docs/TESTING_CONFORMANCE.md +12 -19
  22. package/docs/TRANSACTIONAL_EMAIL.md +1 -1
  23. package/examples/first-journey/FIRST_JOURNEY.md +27 -0
  24. package/examples/first-journey/backend/central.domain.json +141 -0
  25. package/examples/first-journey/backend/central.process.json +105 -0
  26. package/examples/first-journey/backend/emails/confirmacao.json +12 -0
  27. package/examples/first-journey/backend/src/models/Documento.js +9 -0
  28. package/examples/first-journey/backend/src/models/Pessoa.js +6 -0
  29. package/examples/first-journey/backend/src/routes/integracao.js +23 -0
  30. package/examples/first-journey/backend/src/routes/jornada.js +10 -0
  31. package/examples/first-journey/backend/src/routes/recursos.js +26 -0
  32. package/examples/first-journey/backend/src/routes/segredos.js +27 -0
  33. package/examples/first-journey/backend/src/services/confirmacao.js +11 -0
  34. package/examples/first-journey/frontend/src/pages/JourneyPage.tsx +32 -0
  35. package/examples/first-journey/frontend/src/pages/NativeResourcesPage.tsx +50 -0
  36. package/examples/first-journey/frontend/src/pages/SecretsPage.tsx +31 -0
  37. package/package.json +3 -2
  38. package/schemas/central.app.schema.json +12 -0
  39. package/src/doctor.js +90 -0
  40. package/src/example.js +39 -0
  41. package/src/index.js +31 -16
  42. package/templates/_base/README.md +17 -46
  43. package/templates/_base/backend/package.json +1 -1
  44. package/templates/_base/frontend/package.json +1 -1
  45. package/templates/_base/frontend/src/app/app.tsx +2 -2
  46. package/templates/_base/frontend/src/pages/HomePage.tsx +3 -3
  47. package/templates/_base/package.json +2 -2
@@ -26,13 +26,12 @@ const {
26
26
  capabilities,
27
27
  defineRoutes,
28
28
  GenericError,
29
- requirePermission,
30
29
  } = require("@oondemand/oon-core-back");
31
30
 
32
31
  defineRoutes("/pdf", (router) => {
33
32
  router.private.post(
34
33
  "/render",
35
- requirePermission("pdf.render"),
34
+ { permission: "pdf.render" },
36
35
  async (req, res) => {
37
36
  if (typeof req.body?.html !== "string" || !req.body.html.trim()) {
38
37
  throw new GenericError("Informe o HTML.", {
@@ -55,7 +54,7 @@ defineRoutes("/pdf", (router) => {
55
54
  },
56
55
  {
57
56
  ...req.accessContext,
58
- correlationId: req.id,
57
+ correlationId: req.correlationId,
59
58
  environment: process.env.APP_ENVIRONMENT,
60
59
  signal: controller.signal,
61
60
  },
@@ -0,0 +1,124 @@
1
+ # Contratos públicos para a primeira jornada
2
+
3
+ Piso verificado: OonCore 0.7.4. Imports backend vêm de `@oondemand/oon-core-back`; frontend de `@oondemand/oon-core-front` e seus subpaths públicos. [Índice](TASK_INDEX.md) e [API completa](BACKEND_API.md).
4
+
5
+ ## Bootstrap e prefixos
6
+
7
+ - `npm run dev` na raiz usa o orquestrador oficial; `npm start --prefix backend` usa o CLI com o diretório do backend.
8
+ - No bootstrap programático, `await start({ cwd: caminhoAbsolutoDoBackend, listen: false })` devolve `{ app }`; com escuta, devolve `{ app, server }`. `createApp()` monta o Express a partir do registro existente, mas não carrega o domínio nem conecta o banco sozinho.
9
+ - `cwd` determina `central.config.js`, `central.domain.json`, `central.process.json` e `src/`. O App manifest é descoberto no backend ou em seu diretório pai.
10
+ - São autocarregados recursivamente arquivos `.js`/`.cjs` de `src/models`, `validations`, `triggers`, `routes`, `pipelines`, `documents` e `hooks`. Serviços e assets entram pelos imports dessas extensões. Não é preciso exportar uma função de registro: execute `defineRoutes` no módulo carregado.
11
+ - O bootstrap carrega domínio/processo antes dos módulos JS. Models usadas em um process manifest devem existir no domain manifest. Não duplique sua definição em JS.
12
+
13
+ | Camada | Exemplo da mesma operação |
14
+ |---|---|
15
+ | Registro no backend | `defineRoutes("/demo", ...)` + `router.private.post("/saudacao", options, handler)` |
16
+ | Backend direto | `POST /demo/saudacao` |
17
+ | URL pública / proxy Vite | `POST /api/demo/saudacao` |
18
+ | `useOonApi` com baseUrl `/api` | `http.post("/demo/saudacao", body)` |
19
+
20
+ O proxy existente remove `/api`; não registre esse prefixo novamente nem o duplique na chamada do cliente. Preserve a configuração fornecida pelo scaffold/delivery. Para diferenças entre checkout e imagem, consulte [APP_PACKAGING.md](APP_PACKAGING.md).
21
+
22
+ ## Rotas privadas
23
+
24
+ ```js
25
+ const { defineRoutes } = require("@oondemand/oon-core-back");
26
+
27
+ defineRoutes("/integracao", (router) => {
28
+ router.private.put("/credencial", {
29
+ permission: "integracao.config.write",
30
+ sensitiveBody: true,
31
+ }, async (req, res) => {
32
+ // Neste exemplo de assinatura, nenhuma credencial é persistida.
33
+ res.status(501).json({ message: "Implemente a persistência cifrada no App." });
34
+ });
35
+ });
36
+ ```
37
+
38
+ Assinatura: `router.private[METHOD](path, options, handler)`, com `get`, `post`, `put`, `patch`, `delete`. Há forma curta `(path, handler)`, porém ela não declara uma permissão de negócio; prefira opções explícitas.
39
+
40
+ - `permission: "dominio.acao"` é a forma recomendada. `permissions: ["a.read", "b.read"]` aceita **qualquer uma** das permissões; não significa exigir todas. Evite declarar ambos.
41
+ - `roles` é compatibilidade legada, não uma substituição da política RBAC declarada.
42
+ - `audit` configura auditoria de mutação quando necessária; não coloque dados secretos nela.
43
+ - **Não** passe `requirePermission(...)` como segundo argumento e o handler como terceiro. Antes da validação introduzida nesta entrega, uma função nessa posição era interpretada como o próprio handler, descartando o terceiro argumento. Na release __OONCORE_VERSION__, essa assinatura lança OON_ROUTE_REGISTRATION_INVALID no registro. O wrapper não é a assinatura variádica do Express. `requirePermission` é um middleware público para composições compatíveis, não uma opção de `router.private`.
44
+ - `router.public` não deve ser usado para evitar autenticação de uma operação privada.
45
+
46
+ Declare a permissão em `central.app.json` → `rbac.permissions` (array de strings) e conceda-a em `rbac.roles[].permissions`. Preserve os demais campos/papéis. O admin do scaffold já tem `permissions: ["*"]`; isso não dispensa a declaração de permissões nem o contexto autorizado no backend. Operações com dados próprios devem usar `req.accessContext` e os helpers de escopo documentados em [BACKEND_API.md](BACKEND_API.md).
47
+
48
+ ## Corpo sensível e erros
49
+
50
+ `sensitiveBody: true` em `post`, `put`, `patch` ou `delete` privado omite entrada e resposta no `requestLog` para o método/caminho, inclusive em erro. Não cifra banco, não mascara seletivamente campos, não cobre automaticamente CRUD, GET, console, logs de integração, auditoria ou payload de exceções. Credenciais pertencem a um armazenamento protegido do App ou a um contrato público disponível na release; nunca ao frontend/manifesto/URL.
51
+
52
+ Lance `new GenericError(mensagemSegura, { statusCode, code, details })`. O envelope de domínio com `code` é:
53
+
54
+ ```json
55
+ {
56
+ "message": "Informe um nome de até 80 caracteres.",
57
+ "error": {
58
+ "code": "DEMO_NAME_INVALID",
59
+ "message": "Informe um nome de até 80 caracteres.",
60
+ "requestId": "correlacao-quando-disponivel"
61
+ }
62
+ }
63
+ ```
64
+
65
+ `details` e `requestId` são opcionais. O Core não sanitiza arbitrariamente o conteúdo que o App coloca em `message`/`details`; normalize erros externos antes de lançá-los. Respostas legadas e recusas de auth podem conter somente `message`; 404 do proxy/Express pode ser HTML. Não trate erro de rede/404 como sucesso vazio. Veja [ERRORS.md](ERRORS.md).
66
+
67
+ ## Primeira jornada executável
68
+
69
+ No App gerado, adicione `"demo.execute"` a `rbac.permissions`. Mantenha o admin existente. Crie `backend/src/routes/demo.js`:
70
+
71
+ ```js
72
+ const { defineRoutes, GenericError } = require("@oondemand/oon-core-back");
73
+
74
+ defineRoutes("/demo", (router) => {
75
+ router.private.post("/saudacao", { permission: "demo.execute" }, async (req, res) => {
76
+ const nome = typeof req.body?.nome === "string" ? req.body.nome.trim() : "";
77
+ if (!nome || nome.length > 80) {
78
+ throw new GenericError("Informe um nome de até 80 caracteres.", {
79
+ statusCode: 422, code: "DEMO_NAME_INVALID",
80
+ });
81
+ }
82
+ res.json({ message: `Olá, ${nome}!` });
83
+ });
84
+ });
85
+ ```
86
+
87
+ Substitua `frontend/src/pages/HomePage.tsx`. A rota `/` e o item Início já são registrados pelo scaffold:
88
+
89
+ ```tsx
90
+ import { useState, type FormEvent } from "react";
91
+ import { useOonApi } from "@oondemand/oon-core-front";
92
+
93
+ export function HomePage() {
94
+ const { http } = useOonApi();
95
+ const [nome, setNome] = useState("");
96
+ const [resultado, setResultado] = useState("");
97
+ const [busy, setBusy] = useState(false);
98
+ async function submit(event: FormEvent) {
99
+ event.preventDefault();
100
+ setBusy(true);
101
+ try {
102
+ const response = await http.post("/demo/saudacao", { nome });
103
+ setResultado(response.data.message);
104
+ } catch (error: unknown) {
105
+ const response = (error as { response?: { status?: number; data?: {
106
+ message?: string; error?: { code?: string; message?: string };
107
+ } } }).response;
108
+ setResultado(response?.data?.error?.message || response?.data?.message
109
+ || `Não foi possível executar a ação (HTTP ${response?.status ?? "indisponível"}).`);
110
+ } finally { setBusy(false); }
111
+ }
112
+ return <form onSubmit={submit}>
113
+ <h1>Primeira jornada</h1>
114
+ <label>Nome <input value={nome} onChange={event => setNome(event.target.value)} /></label>
115
+ <button type="submit" disabled={busy}>Executar</button>
116
+ <p role="status">{resultado}</p>
117
+ </form>;
118
+ }
119
+ ```
120
+
121
+ Reinicie `npm run dev` após incluir o módulo, abra Início com admin e execute com nome válido e vazio (422). Selecione viewer, sem `demo.execute`, e confirme a recusa (403). O cliente do Core cuida de sessão e CSRF; não crie token fixo. A saudação é uma ação sem persistência; para um cadastro, siga [TASK_EXAMPLES.md](TASK_EXAMPLES.md). Estes blocos são exemplos documentais copiáveis, não uma suíte integrada de homologação do SDK.
122
+
123
+
124
+ A release __OONCORE_VERSION__ também diagnostica a raiz do App passada como cwd com OON_BACKEND_CWD_INVALID. Caminhos relativos são resolvidos antes do autoload. Use `create-central-oon example` em uma base limpa para instalar a jornada integrada descrita em [EXECUTABLE_EXAMPLE.md](EXECUTABLE_EXAMPLE.md).
@@ -46,7 +46,7 @@ Resposta:
46
46
  }
47
47
  ```
48
48
 
49
- Use esse endpoint para montar seletores e validar grants por App. Não mantenha códigos de perfil paralelos no frontend ou no Control Plane. O consumidor deve validar versão e App, bloquear redirects e destinos de rede privados, revalidar o perfil no backend e tratar catálogo inválido como indisponível. O endpoint usa cache público de cinco minutos e nunca expõe a lista de permissões; a autorização efetiva continua no App.
49
+ Use esse endpoint para montar seletores e validar grants por App. Não mantenha códigos de perfil paralelos no frontend ou no Control Plane. O consumidor deve validar versão e App, bloquear redirects e destinos de rede privados, revalidar o perfil no backend e tratar catálogo inválido como indisponível. O endpoint usa cache público de cinco minutos. Desde 0.7.1, pode incluir `commercialPermissions` com permissões funcionais declaradas; não publica wildcards literais nem namespaces operacionais reservados. Esse catálogo não concede autorização; a decisão efetiva continua no App.
50
50
 
51
51
  ## Checklist de segurança para Agents
52
52
 
@@ -1,11 +1,22 @@
1
- # Rotas, hooks, triggers e workers
1
+ # Rotas, hooks, triggers e execução de processos
2
2
 
3
- - `defineRoutes(basePath, router)`: registra rota específica do domínio. Proteja com auth já montada, `requirePermission`, validação e `req.accessContext`.
4
- - `defineValidation(model, fn)`: valida o estado consolidado antes de persistir.
5
- - `defineTrigger(model, fn)`: efeito de domínio rastreável associado à mutação.
6
- - Process manifest: transições, bindings, invariantes, recálculos e jobs transacionais.
7
- - Integration runtime: outbox/inbox, retry, lock, idempotência, histórico e webhook.
3
+ Para assinatura de `defineRoutes`, opções de `router.private`, bootstrap, prefixos, permissões, erros e `sensitiveBody`, use [PUBLIC_CONTRACTS.md](PUBLIC_CONTRACTS.md).
8
4
 
9
- Não crie processo web paralelo, registry local, timer genérico ou worker que replique o Core. Jobs devem ser idempotentes, limitar retry, sanitizar payload/erro e encerrar com o lifecycle do processo.
5
+ ## Existente e público
10
6
 
11
- No runtime local, workers e filas podem operar contra Mongo local, mas operações que exigem identidade da plataforma devem retornar `LOCAL_OPERATION_NOT_SUPPORTED`.
7
+ - `defineValidation(model, fn)`: valida estado consolidado antes de persistir.
8
+ - `defineTrigger(model, { before, after })`: registra efeitos de domínio associados à mutação. Não trate hooks como entrega exatamente uma vez de efeitos externos.
9
+ - `central.process.json`: transições, bindings, invariantes e recálculos suportados. [Contrato completo](BACKEND_PROCESS_MANIFEST.md).
10
+ - `capabilities.transactionalEmail`: envio e acompanhamento de e-mail, com fila própria. [Contrato completo](TRANSACTIONAL_EMAIL.md).
11
+
12
+ ## Interno e específico
13
+
14
+ Jobs de recálculo do processo e workers de e-mail atendem esses recursos; não são uma fila genérica durável para integração externa. `drainProcessJobs` não define enqueue/claim/retry de tarefas arbitrárias. Stores, transports e registries internos não são pontos de extensão de Apps.
15
+
16
+ ## Integração no App e proposta futura
17
+
18
+ Não há API pública genérica de “integration runtime” para outbox/inbox, locks, retry ou webhooks nesta release. Chamadas externas, mapeamentos, regras fiscais, estado de operação e recuperação de negócio ficam no App por rotas/serviços documentados. [Exemplo de consulta](TASK_EXAMPLES.md#integração).
19
+
20
+ Quando a jornada precisar de execução assíncrona durável, explicite persistência, idempotência, concorrência, retry limitado, diagnóstico sanitizado e lifecycle no App, ou acompanhe uma nova Capacidade em escopo separado. Não invente APIs do Core, não transforme um timer em garantia de durabilidade e não bloqueie funções independentes à espera da nova Capacidade.
21
+
22
+ O runtime local não fornece identidade operacional da Plataforma. Operações que a exigem continuam falhando fechado conforme [LOCAL_SECURITY_BOUNDARY.md](LOCAL_SECURITY_BOUNDARY.md).
@@ -0,0 +1,54 @@
1
+ # core.secrets V1
2
+
3
+ Contrato público a partir da release 0.7.6. Backend: `const { capabilities } = require("@oondemand/oon-core-back")`. Exclusivo para `single_tenant` + `dedicated`. Nenhum endpoint/CRUD automático expõe a coleção técnica. Adoção é opt-in; não migra e-mail nem Apps existentes.
4
+
5
+ ```json
6
+ {
7
+ "capabilities": ["core.secrets"],
8
+ "capabilitySettings": { "core.secrets": { "enabled": true } }
9
+ }
10
+ ```
11
+
12
+ Mescle a declaração no manifesto existente. Nunca coloque valores ou chave em manifesto, frontend, repositório ou imagem. O Publisher transmite somente a declaração. Core e executor precisam da release com V1; versão anterior deve recusar a publicação opt-in.
13
+
14
+ ```js
15
+ const options = { name: "omie", context: req.accessContext, correlationId: crypto.randomUUID() };
16
+ await capabilities.secrets.put({ ...options, value: { appKey, appSecret } });
17
+ const state = await capabilities.secrets.status(options);
18
+ await capabilities.secrets.use(options, async credential => {
19
+ // Serviço do App controla URL, timeout, redirects e sanitização do retorno.
20
+ await omie.call(credential);
21
+ });
22
+ await capabilities.secrets.remove(options);
23
+ ```
24
+
25
+ `context` é o contexto autorizado do Core; nunca copie identidade do body/header. Rotas do App exigem autenticação, permissão de negócio e `sensitiveBody: true`. A V1 verifica App, tenant e usuário; a autorização da operação é da rota. Não oferece execução anônima/sistema nem contorno de RBAC.
26
+
27
+ - Nome: `[a-z][a-z0-9._-]{0,63}`, definido pelo backend. Dois nomes são independentes.
28
+ - Valor: objeto JSON não vazio, até 16 KiB serializado. Substituição atômica do objeto inteiro; não há merge parcial, histórico ou compare-and-swap. Última gravação concluída prevalece.
29
+ - `put` retorna apenas `{state:"available"}`; valor incompatível exige `replace: true` explícito. Chave indisponível impede gravação inclusive com replace.
30
+ - `status` retorna `absent`, `available` ou `reentry_required`. `reason:key_unavailable`/`action:restore_provisioning` exige intervenção antes do recadastro; `incompatible_or_unreadable`/`replace` permite substituição autorizada. Erro de banco retorna erro sanitizado, nunca “ausente”.
31
+ - `use(options, callback)` aguarda o callback e **descarta seu retorno**. Erros do callback tornam-se `SECRETS_USE_FAILED`, sem mensagem/cause externa. Código do App deve decidir quais resultados sanitizados expor.
32
+ - `remove` é idempotente e não precisa decifrar o valor.
33
+ - Correlação opcional: 1–128 caracteres alfanuméricos, `:`, `_` ou `-`; gere no backend. Auditoria técnica registra tentativa de operação, ator, escopo, nome, correlação e instante, sem valor/envelope/chave. Não é confirmação transacional do efeito.
34
+
35
+ AES-256-GCM, chave aleatória de 32 bytes, IV novo de 12 bytes por gravação. Envelope v1 autenticado com AAD que inclui App, identidade canônica do runtime dedicado (appId + namespace), ambiente, tenant e nome. Uma chave por namespace/ambiente. As coleções `oon_secrets` e `oon_secret_audit` são técnicas, não registradas em CRUD/metadata/export.
36
+
37
+ `use` não é sandbox: código consumidor pode copiar/logar strings ou exfiltrá-las. Nunca registre payload, headers, erros de SDK externo ou resposta remota sem sanitização. Limpeza dos buffers não garante apagar todas as cópias de strings do heap. A criptografia protege valores em repouso separados da chave; um processo backend comprometido pode acessar os valores.
38
+
39
+ ## Provisionamento e recadastro
40
+
41
+ O helper de delivery cria Secret imutável `oon-core-secrets` por `create` atômico; concorrentes releem o vencedor. Erro de leitura não gera chave. A chave não passa em argumentos CLI, logs, contexto de publicação ou evidência. `secretKeyRef` monta somente OON_SECRETS_KEY do ambiente alvo, opcional para permitir que funções independentes iniciem mesmo se a chave desaparecer. Campos extras de um Secret restaurado não são injetados. Identidade/tenant não secretos permanecem no runtime base; nenhuma operação de segredo ignora a falta da chave. Um Secret marcador imutável `oon-core-secrets-initialized` guarda apenas o vínculo público e impede recriação silenciosa se a chave desaparecer. Não há serviço de cofre. O RBAC do Publisher recebe somente leitura nominal dos dois Secrets; criação de Secrets já existe.
42
+
43
+ Republicação, reinício e réplica reutilizam Secret/coleção. Promoção transporta declaração/imagem; provisiona chave independente no destino, sem copiar dados ou chave da origem.
44
+
45
+ 1. Para valor incompatível com chave íntegra: usuário autorizado informa o objeto completo e confirma `replace: true`.
46
+ 2. Se a chave estiver ausente: operador restaura o Secret correspondente, sem imprimir/exportar sua chave para diagnósticos. O provisionamento normal recusa perda ou corrupção.
47
+ 3. Sem chave recuperável: operador interrompe o runtime alvo, confirma identidade/ambiente e aceita a perda dos valores antigos. Remove **somente** o Secret e marcador daquele ambiente por procedimento operacional autorizado e reexecuta provisionamento. Depois republica/reinicia todas as réplicas e solicita recadastro explícito. Não há endpoint do App para trocar chave.
48
+ 4. Exclusão do namespace/ambiente ou restauração de banco sem a chave correspondente pode exigir recadastro. Não há recuperação, rotação ou migração transparente nesta V1.
49
+
50
+ ## Prova gerada e desenvolvimento local
51
+
52
+ `create-central-oon sample --no-install` seguido de `create-central-oon example` instala `/segredos`, menu, permissão `segredos.manage` e rotas públicas do SDK. O exemplo opt-in já declara a capacidade. Configure no **ambiente do backend**, persistindo de forma segura entre reinícios: `OON_SECRETS_KEY` (32 bytes base64 padrão), `OON_SECRETS_INSTANCE=local:sample`, `OON_SECRETS_TENANT=local:tenant`, `APP_ENVIRONMENT=desenvolvimento`. Nunca use a chave local na Plataforma.
53
+
54
+ `INTEGRACAO_URL` é endpoint HTTPS controlado pelo operador, que receberá POST do objeto de prova; não é implementação fiscal/Omie. Sem chave, outras páginas continuam operacionais. Use credenciais sintéticas no teste. A suíte HTTP gera o App, usa Core/Mongo reais e HTTPS de teste, cobre autorização, dois nomes, cifra, adulteração, recadastro, concorrência, erros/logs e reinício empacotado. Isso não substitui release npm nem homologação Dev pública.
@@ -0,0 +1,134 @@
1
+ # Exemplos por tarefa
2
+
3
+ Piso verificado: 0.7.4. Execute no App gerado seguindo [o roteiro](OON_APP_AGENT_GUIDE.md). Estes exemplos são blocos copiáveis para as tarefas; a suíte integrada e o exemplo instalável estão descritos em [EXECUTABLE_EXAMPLE.md](EXECUTABLE_EXAMPLE.md). Preserve o manifesto existente e acrescente apenas os campos indicados.
4
+
5
+ ## Cadastro e lista
6
+
7
+ Adicione `pessoas.read` e `pessoas.write` a `rbac.permissions` de `central.app.json`; use admin na POC. Crie `backend/src/models/Pessoa.js`:
8
+
9
+ ```js
10
+ const { defineModel, fields } = require("@oondemand/oon-core-back");
11
+
12
+ defineModel({
13
+ name: "Pessoa", singular: "pessoa", basePath: "/pessoas", scope: "tenant",
14
+ schema: { nome: fields.string({ required: true, label: "Nome" }) },
15
+ crud: { enabled: true, permissions: { read: "pessoas.read", write: "pessoas.write" } },
16
+ });
17
+ ```
18
+
19
+ Substitua `frontend/src/pages/HomePage.tsx`; a rota e o menu Início existentes tornam o cadastro acessível:
20
+
21
+ ```tsx
22
+ import { CoreCollection } from "@oondemand/oon-core-front";
23
+
24
+ export function HomePage() {
25
+ return <CoreCollection model="Pessoa" label="Pessoas" endpoint="/pessoas" />;
26
+ }
27
+ ```
28
+
29
+ Reinicie `npm run dev`. Em Início, crie uma pessoa, consulte a lista e recarregue para confirmar persistência. Nome ausente deve ser recusado; viewer sem as permissões não pode ler/gravar. O formulário é derivado da metadata. Não use esse CRUD para armazenar segredos: `sensitiveBody` de rota customizada não é aplicado aqui.
30
+
31
+ ## Esteira
32
+
33
+ Restrito ao App `single_tenant` com banco dedicado gerado pelo scaffold. O domain manifest desta versão não propaga `scope`/`tenancy` para a model: não use este exemplo como isolamento de linhas multi-tenant. Registre essa lacuna se precisar combinar processo declarativo com esse escopo; não invente campos de manifesto. Para cadastro com isolamento explícito, use `defineModel({ scope: "tenant", ... })` do exemplo anterior.
34
+
35
+ Este exemplo independente usa `backend/central.domain.json` (não crie outra model `Ticket` em JS):
36
+
37
+ ```json
38
+ {
39
+ "schemaVersion": 1, "name": "Exemplo de tickets",
40
+ "models": [{
41
+ "name": "Ticket", "singular": "ticket", "basePath": "/tickets",
42
+ "crud": { "enabled": true, "permissions": { "read": "tickets.read", "write": "tickets.write" } },
43
+ "fields": {
44
+ "titulo": { "kind": "string", "required": true },
45
+ "etapa": { "kind": "enum", "values": ["Aberto", "Concluido"], "default": "Aberto" }
46
+ }
47
+ }]
48
+ }
49
+ ```
50
+
51
+ Crie `backend/central.process.json`:
52
+
53
+ ```json
54
+ {
55
+ "schemaVersion": 1,
56
+ "models": {
57
+ "Ticket": { "workflow": {
58
+ "stageField": "etapa", "initialStages": ["Aberto"], "defaultStage": "Aberto",
59
+ "transitions": [{ "from": "Aberto", "to": "Concluido" }]
60
+ } }
61
+ }
62
+ }
63
+ ```
64
+
65
+ Declare `tickets.read` e `tickets.write` em `rbac.permissions` e use admin. Substitua `frontend/src/pages/HomePage.tsx`:
66
+
67
+ ```tsx
68
+ import { CorePipeline } from "@oondemand/oon-core-front";
69
+
70
+ export function HomePage() {
71
+ return <CorePipeline model="Ticket" label="Tickets" endpoint="/tickets"
72
+ titleField="titulo" stageField="etapa" create={{ enabled: true, defaultStage: "Aberto" }}
73
+ defaultActions={{ workStatus: false }}
74
+ stages={[{ id: "Aberto", label: "Aberto" }, { id: "Concluido", label: "Concluído" }]} />;
75
+ }
76
+ ```
77
+
78
+ Reinicie, crie um ticket em Aberto e mova para Concluído; o retorno para Aberto deve ser recusado pelo backend. A esteira não executa chamadas externas automaticamente. Consulte [BACKEND_PROCESS_MANIFEST.md](BACKEND_PROCESS_MANIFEST.md) para bindings/invariantes.
79
+
80
+ ## Integração
81
+
82
+ Não existe contrato genérico de “integration runtime” nesta release. Uma consulta externa pode usar uma rota privada e um serviço no App. Para executar o exemplo, configure `INTEGRACAO_URL` (endpoint HTTPS controlado por você) e `INTEGRACAO_TOKEN` no ambiente do backend; nunca versione o token. Ele usa Bearer, portanto adapte o mapeamento ao contrato do sistema externo.
83
+
84
+ Crie `backend/src/routes/integracao.js` e declare `integracao.test` em `rbac.permissions`:
85
+
86
+ ```js
87
+ const { defineRoutes, GenericError } = require("@oondemand/oon-core-back");
88
+
89
+ defineRoutes("/integracao", (router) => {
90
+ router.private.post("/testar", { permission: "integracao.test", sensitiveBody: true }, async (req, res) => {
91
+ const url = process.env.INTEGRACAO_URL;
92
+ const token = process.env.INTEGRACAO_TOKEN;
93
+ if (!url || !token) throw new GenericError("Configure a integração.", {
94
+ statusCode: 409, code: "INTEGRATION_NOT_CONFIGURED",
95
+ });
96
+ let target;
97
+ try { target = new URL(url); } catch { /* tratado abaixo */ }
98
+ if (!target || target.protocol !== "https:" || target.username || target.password) {
99
+ throw new GenericError("Configure um endpoint HTTPS sem credenciais na URL.", {
100
+ statusCode: 422, code: "INTEGRATION_URL_INVALID",
101
+ });
102
+ }
103
+ let response;
104
+ try {
105
+ response = await fetch(target, {
106
+ headers: { Authorization: `Bearer ${token}` },
107
+ signal: AbortSignal.timeout(10000), redirect: "error",
108
+ });
109
+ } catch {
110
+ throw new GenericError("O serviço externo não respondeu no prazo ou está indisponível.", {
111
+ statusCode: 502, code: "INTEGRATION_TRANSPORT_ERROR",
112
+ });
113
+ }
114
+ await response.body?.cancel();
115
+ if (!response.ok) throw new GenericError("O serviço externo recusou a consulta.", {
116
+ statusCode: 502, code: "INTEGRATION_REMOTE_REJECTED", details: { remoteStatus: response.status },
117
+ });
118
+ res.json({ message: "Consulta externa concluída." });
119
+ });
120
+ });
121
+ ```
122
+
123
+ Para uma página utilizável, reutilize o `HomePage` de [PUBLIC_CONTRACTS.md](PUBLIC_CONTRACTS.md) e troque a chamada por `http.post("/integracao/testar", {})`; o input nome pode ser removido. Reinicie e teste sucesso, configuração ausente e rejeição externa. O exemplo apenas consulta: não cadastra credenciais, não comprova uma operação fiscal nem oferece retry/persistência de jobs. Credenciais recebidas pela UI exigem armazenamento cifrado próprio e respostas sanitizadas.
124
+
125
+ ## Diagnóstico
126
+
127
+ Após publicar, consulte (substitua o host pelo endereço real de Dev):
128
+
129
+ ```bash
130
+ curl --fail-with-body https://SEU-APP-DEV/api/health/ready
131
+ curl --fail-with-body https://SEU-APP-DEV/api/health/version
132
+ ```
133
+
134
+ Para uma ação privada, reproduza pela página com `useOonApi` autenticado e registre método, caminho, status, código e correlação sem segredos. Confira se `/api` foi acrescentado apenas na URL pública, se o módulo está em `backend/src/routes` e se o commit é o publicado. HTML 404 indica investigar rota/proxy/empacotamento; não é prova de rejeição da API externa. Readiness não prova execução do handler de domínio.
@@ -0,0 +1,36 @@
1
+ # Índice por intenção
2
+
3
+ Documentação distribuída da release **__OONCORE_VERSION__**. Escolha a linha da tarefa; não é necessário ler todos os guias.
4
+
5
+ **Versão mínima:** 0.7.4 é o piso verificado dos exemplos deste roteiro, não uma alegação de que cada API surgiu nessa versão. PDF e e-mail têm contrato dedicado desde 0.6.0. Use versões coordenadas dos três pacotes e não presuma compatibilidade com versões anteriores ao piso indicado.
6
+
7
+ | Quero… | API/export público | Mínima do roteiro | Exemplo executável / referência completa | Limite principal |
8
+ |---|---|---|---|---|
9
+ | Cadastrar e listar | back: `defineModel`, `fields`; front: `CoreCollection` | 0.7.4 | [Cadastro e lista](TASK_EXAMPLES.md#cadastro-e-lista); [metadata/CRUD](METADATA_CRUD_UI.md), [domínio](BACKEND_DOMAIN_MANIFEST.md) | CRUD valida escopo; consultas próprias também devem usar contexto autorizado |
10
+ | Criar formulário | front: `useOonApi`, `CoreCollection` com formulário nativo | 0.7.4 | [Formulário e ação](PUBLIC_CONTRACTS.md#primeira-jornada-executável); [frontend](FRONTEND_API.md), [modal](DETAIL_MODAL_AND_RELATED_GRIDS.md) | Validação e permissão da UI não substituem backend |
11
+ | Registrar rota privada | back: `defineRoutes`, `GenericError` | 0.7.4 | [Rota privada](PUBLIC_CONTRACTS.md#rotas-privadas); [backend](BACKEND_API.md) | Opções como segundo argumento, handler como terceiro; prefixo interno sem `/api` |
12
+ | Executar ação | front: `useOonApi`; back: `defineRoutes` | 0.7.4 | [Formulário e ação](PUBLIC_CONTRACTS.md#primeira-jornada-executável); [rotas](ROUTES_HOOKS_WORKERS.md) | Valide permissão, entrada e efeitos no backend; retry de efeito externo é decisão do App |
13
+ | Modelar esteira | `central.process.json`, back: `loadProcessManifest`; front: `CorePipeline` | 0.7.4 | [Esteira mínima](TASK_EXAMPLES.md#esteira); [processo](BACKEND_PROCESS_MANIFEST.md), [UI](COLLECTIONS_AND_PIPELINES.md) | Transições/invariantes não são fila genérica de integração |
14
+ | Integrar sistema externo | back: `defineRoutes`, `GenericError`; serviço do App; Node `fetch` (não é API OonCore) | 0.7.4 | [Integração](TASK_EXAMPLES.md#integração); [contratos e segredo](PUBLIC_CONTRACTS.md) | Mapeamento, timeout, credenciais e recuperação de negócio pertencem ao App |
15
+ | Armazenar credenciais | back: `capabilities.secrets.put/status/use/remove` | 0.7.6 | [Segredos V1](SECRETS.md); `/segredos` no exemplo | Apenas single_tenant dedicado; chave separada e responsabilidade de sanitização do consumidor |
16
+ | Gerar PDF | back: `capabilities.pdf.render`; front: `useOonApi` | 0.6.0 | [Exemplo back/front e referência](PDF_RENDERING.md) | Depende de capability resolvida; limites de entrada/saída e timeout no guia |
17
+ | Enviar e-mail | back: `capabilities.transactionalEmail.send`; front: `CoreTransactionalEmail` | 0.6.0 | [Exemplo de template, envio e painel](TRANSACTIONAL_EMAIL.md) | Sem anexos; `accepted` não confirma entrega; fila exclusiva de e-mail |
18
+ | Diagnosticar | `GenericError`, HTTP `/health/ready`, `/health/version` | 0.7.4 | [Comandos](TASK_EXAMPLES.md#diagnóstico); [erros](ERRORS.md), [troubleshooting](TROUBLESHOOTING.md) | 404 HTML não é envelope de domínio; não colar segredos no diagnóstico |
19
+ | Atualizar | CLI `create-central-oon docs sync/check` | 0.7.4 | [Comandos e referência](CORE_UPGRADE.md) | Atualize três pacotes, lockfiles e cache oficial; não edite `.ooncore` à mão |
20
+
21
+ ## Estado dos recursos
22
+
23
+ - **Existente e público:** exports/contratos acima, CRUD/metadata, validações/fórmulas, manifesto de processo, auditoria, PDF e e-mail. [CAPABILITIES.md](CAPABILITIES.md) detalha os limites por ambiente.
24
+ - **Interno, não consumível como contrato de integração:** stores, transports, registries internos, worker de e-mail e jobs de recálculo do processo. Mesmo funções exportadas para o processo não constituem uma API genérica de enqueue/claim/retry.
25
+ - **Proposta futura separada:** execução durável genérica de integrações, diagnóstico padronizado operação → tentativa → troca e evolução de segredos. A existência de uma issue não disponibiliza uma API; não use `core.secrets` sem contrato publicado na versão instalada.
26
+
27
+ A Plataforma homologada fornece identidade, acesso e delivery; o App não assume essa autoridade. Lacunas têm tratamento localizado conforme [AGENTS.md](AGENTS.md).
28
+
29
+
30
+ ## Jornada integrada e diagnóstico
31
+
32
+ Na release __OONCORE_VERSION__, `create-central-oon example` instala o exemplo opt-in em uma base limpa; `create-central-oon doctor` diagnostica versões, lockfiles, manifestos, cache e erros de empacotamento. Veja [EXECUTABLE_EXAMPLE.md](EXECUTABLE_EXAMPLE.md).
33
+
34
+ ## Aplicação dos recursos nativos
35
+
36
+ A página `/recursos` do exemplo demonstra fórmulas, validações, bindings, invariantes, documentos, dashboard e PDF. O serviço de e-mail é opt-in. Consulte [NATIVE_RESOURCES.md](NATIVE_RESOURCES.md) para o roteiro, helpers de escopo e limites reais de isolamento, auditoria e execução.
@@ -1,30 +1,23 @@
1
1
  # Testes e conformidade
2
2
 
3
- ## Gates do projeto consumidor
3
+ ## App consumidor
4
4
 
5
5
  ```bash
6
- npm run ooncore:docs:check
7
- npm run ooncore:conformance
8
6
  npm run check
9
- npm test
7
+ npm run build --prefix frontend
10
8
  ```
11
9
 
12
- ## Runtime local
10
+ No scaffold, `check` executa `doctor`, que inclui docs check e conformance, versões instaladas, lockfiles e diagnóstico de composição. Rode também os testes relevantes declarados pelo App; a base neutra não cria um script raiz `test`. Prove a jornada com acesso permitido/recusado, validação de entrada, resultado e persistência quando aplicável. Não duplique verificações sem uma mudança ou risco concreto.
13
11
 
14
- Comprove:
12
+ A homologação global da Plataforma e os testes de segurança do runtime pertencem ao SDK/Plataforma. O App preserva os contratos e comprova sua integração específica. Se alterar uma fronteira de segurança, cubra explicitamente essa mudança.
15
13
 
16
- - `127.0.0.1` e `localhost` funcionam;
17
- - `0.0.0.0`, IP de LAN, Host/Origin externos e proxy falham;
18
- - nenhuma chamada alcança a plataforma;
19
- - cookies existem e bearer não aparece no `localStorage`;
20
- - bootstraps de navegador e automação não podem ser reutilizados nem invalidar um ao outro;
21
- - expiração bloqueia a sessão e reiniciar o processo não amplia o prazo;
22
- - Apps multi-tenant recebem somente o contexto virtual fixo `local:tenant`;
23
- - perfis fora do manifesto falham;
24
- - CRUD, metadata, dashboards, processos e integrações locais funcionam;
25
- - `operationalRequestHeaders` falha fechado;
26
- - apagar o Mongo local inicia novo prazo, como limitação documentada.
14
+ ## Manutenção do SDK
27
15
 
28
- ## Documentação
16
+ Alterações no runtime exigem as provas pertinentes: loopback, sessão/CSRF, expiração, perfis, isolamento, recusa de identidade operacional local e ausência de chamadas à Plataforma. Esses cenários não são pré-requisito repetido para cada página de um App.
29
17
 
30
- `docs --check` valida fonte, versão, schema, entrada `AGENTS.md`, hashes dos documentos e entrypoints, além da presença de todos os arquivos. O tarball npm deve conter docs, templates e schemas. Exemplos devem compilar ou executar em CI.
18
+ ## Documentação distribuída
19
+
20
+ `create-central-oon docs check` valida fonte, versão, schema, entrada, hashes e arquivos. `docs sync` regenera o cache a partir da fonte do pacote. O tarball deve conter docs, templates e schemas. Verifique links e exemplos alterados; build, publicação e homologação são evidências diferentes.
21
+
22
+
23
+ No monorepo, `npm run check:authoring` executa os testes do gerador uma vez e verifica typecheck/build da base com o exemplo integrado. Execute após o build do SDK frontend. A suíte backend inclui HTTP real e MongoDB descartável para a mesma jornada; não simula router. No CI essas entradas substituem os checks duplicados.
@@ -64,7 +64,7 @@ async function enviarConfirmacao({ email, nome, idempotencyKey }, req) {
64
64
  to: email,
65
65
  variables: { nome },
66
66
  idempotencyKey,
67
- correlationId: req.id,
67
+ correlationId: req.correlationId,
68
68
  maxAttempts: 5,
69
69
  },
70
70
  {
@@ -0,0 +1,27 @@
1
+ # Primeira jornada executável
2
+
3
+ Exemplo opt-in instalado depois da base oficial. O comando exige routes.tsx/navigation.ts originais e recusa colisões antes de escrever. Não é scaffold alternativo nem migração de App existente.
4
+
5
+ 1. Instale as dependências e configure MongoDB como replica set (as invariantes exigem transações; um Mongo standalone não atende este exemplo ampliado).
6
+ 2. Execute `npm run dev` e abra **Primeira jornada** no menu. Selecione admin no banner local.
7
+ 3. Envie o formulário e valide nome vazio; cadastre uma Pessoa, consulte a lista e recarregue.
8
+ 4. Crie um Ticket em Aberto e mova para Concluído. O backend recusa voltar para Aberto.
9
+ 5. Configure `INTEGRACAO_URL` (HTTPS controlado por você) e `INTEGRACAO_TOKEN` exclusivamente no ambiente backend. Reinicie e clique em Testar integração externa. Nada é enviado à Plataforma pelo exemplo local.
10
+ 6. Selecione viewer: as operações devem retornar 403. A página continua visível para demonstrar o diagnóstico da recusa; permissão de menu não substitui a autorização backend.
11
+ 7. Reinicie o App e confira os registros. Execute `npm run check` e `npm run build --prefix frontend`.
12
+
13
+ Pessoa usa escopo tenant explícito. Ticket declarativo limita-se a single_tenant dedicado: nesta versão o domain manifest não propaga escopo de linha; não reutilize essa model em multi_tenant.
14
+
15
+ A ação de processo usa o PATCH do CRUD e a transição do process manifest; não implementa worker ou integração fiscal. A rota externa só consulta o destino configurado, não aceita URL/token da UI, não repete efeitos externos e devolve apenas mensagem/status sanitizados. `sensitiveBody` omite request/response no requestLog; não cifra banco. Este exemplo não cadastra nem persiste credenciais.
16
+
17
+ Com baseURL `/api`, `http.post('/integracao/testar')` chama publicamente `/api/integracao/testar`; o proxy existente remove `/api` e o Core recebe `/integracao/testar`. Não duplique o prefixo. O bootstrap programático deve apontar `cwd` para backend/; somente .js/.cjs dos diretórios documentados são descobertos.
18
+
19
+ A suíte do SDK testa o mesmo código com HTTP real, sessão/CSRF, MongoDB descartável, HTTPS de teste, recusa de acesso, persistência após reinício e estrutura de runtime backend/ + central.app.json. Isso não equivale à homologação pública do emissor nem ao teste de uma imagem Docker final.
20
+
21
+ ## Recursos nativos
22
+
23
+ Abra **Recursos nativos** no menu e siga `.ooncore/docs/NATIVE_RESOURCES.md`: Contratos → Parcelas → Documentos → Indicadores → PDF. Contrato/Parcela também são restritos ao banco single-tenant dedicado. PDF requer capability resolvida; e-mail é opt-in, com serviço e template distribuídos, sem envio ao instalar. As models declarativas deste exemplo dependem do banco dedicado; declare escopo explícito quando necessário.
24
+
25
+ ## Segredos opt-in
26
+
27
+ O exemplo declara core.secrets e adiciona `/segredos` com dois nomes e credenciais em objeto. Siga `.ooncore/docs/SECRETS.md` para configurar a chave local, salvar/consultar/utilizar/substituir/remover e os limites do callback. Sem provisionamento, as outras jornadas continuam disponíveis.