@softize/opus 9.0.8 → 9.1.0

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.
@@ -1,6 +1,6 @@
1
1
  ## Básico
2
2
 
3
- A caixa de escrever da casa: textarea numa pílula elevada (`rounded-card` + `border` + `shadow-sm`), **Enter** envia / **Shift+Enter** quebra linha, enviar dentro. É o composer do [Chat](/components/chat) extraído — use SOZINHO quando há entrada de texto mas não um chat (ex.: criar uma sessão). Controlado: o dono do texto é você.
3
+ A caixa de escrever da casa: textarea numa pílula elevada (`rounded-card` + `border` + `shadow-sm`), **Enter** envia / **Shift+Enter** quebra linha, enviar dentro. É o composer do [Chat](/components/chat) extraído — use SOZINHO quando há entrada de texto mas não um chat (ex.: criar uma sessão). Controlado: o dono do texto é você. Os callbacks opcionais `onHistoryPrevious` e `onHistoryNext` permitem que esse dono consuma **↑/↓**; sem eles, as setas mantêm o comportamento nativo da textarea.
4
4
 
5
5
  ```tsx preview col
6
6
  const [text, setText] = React.useState('')
@@ -0,0 +1,71 @@
1
+ ---
2
+ title: Observabilidade
3
+ ---
4
+
5
+ # Observabilidade
6
+
7
+ O core expõe uma porta vendor-neutral; o driver OpenTelemetry cria spans ativos para actions
8
+ e reactions sem escolher backend, exporter ou Collector.
9
+
10
+ ## Driver OpenTelemetry
11
+
12
+ Configure o SDK antes de criar o runtime. Exemplo com OTLP/HTTP apontando para um Collector:
13
+
14
+ ```ts
15
+ import { NodeSDK } from '@opentelemetry/sdk-node'
16
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
17
+ import { openTelemetryObservability } from '@softize/opus/observability/opentelemetry'
18
+ import { createRuntime } from '@softize/opus/core'
19
+
20
+ const sdk = new NodeSDK({
21
+ traceExporter: new OTLPTraceExporter({
22
+ url: 'http://otel-collector:4318/v1/traces',
23
+ }),
24
+ })
25
+ await sdk.start()
26
+
27
+ const runtime = createRuntime({
28
+ observability: openTelemetryObservability({
29
+ shutdown: () => sdk.shutdown(),
30
+ }),
31
+ })
32
+ ```
33
+
34
+ O app é dono do `NodeSDK`, sampling, resource (`service.name`), exporter, autenticação e
35
+ retry. A opção `shutdown` é explícita: sem ela, `runtime.dispose()` não desliga o provider
36
+ global. Um `Tracer` também pode ser injetado para testes ou providers não globais.
37
+
38
+ ## Semântica
39
+
40
+ - nomes de span: `opus.action <action>` e `opus.reaction <reaction>`;
41
+ - parent vem de `ContextBase.trace` ou do evento recebido;
42
+ - actions usam `ActionResult.ok` para status; reactions usam resolve/reject;
43
+ - somente atributos allowlisted de baixa cardinalidade são anexados;
44
+ - input, output, actor, tenant, request ID, error code/message e baggage não entram no span;
45
+ - `TraceContext` devolve apenas `traceId`, `spanId`, `traceFlags` e `traceState`.
46
+
47
+ ## Fronteira HTTP W3C
48
+
49
+ Os drivers Fastify e Node oferecem propagação explícita, desativada por default:
50
+
51
+ ```ts
52
+ fastifyServer({ app, traceContext: 'w3c' })
53
+ nodeServer({ traceContext: 'w3c' })
54
+ ```
55
+
56
+ O opt-in extrai um `traceparent` v00 válido (e `tracestate` limitado) para
57
+ `ContextBase.trace`. Depois da action, injeta na resposta o `traceparent` do contexto
58
+ efetivo retornado pelo runtime. `baggage` nunca é lido nem copiado. Headers inválidos são
59
+ ignorados; sem a opção, os drivers mantêm o comportamento anterior e não interpretam nem
60
+ emitem contexto de trace.
61
+
62
+ ## Limites
63
+
64
+ O driver OpenTelemetry não instrumenta HTTP automaticamente e não oferece backend APM.
65
+ Use o opt-in dos drivers Fastify/Node acima ou forneça `ContextBase.trace` na instrumentação
66
+ de outro transporte. A fronteira implementa somente `traceparent` v00 e uma validação
67
+ defensiva limitada de `tracestate`; não é um propagator OTel genérico.
68
+ Health do exporter/Collector só é reportado quando o app injeta `healthCheck`; o default
69
+ confirma apenas que a API do driver está configurada.
70
+ Sem um provider que produza spans válidos, o driver falha antes do callback e o runtime aplica
71
+ a degradação lenient — executa a action uma vez, sem trace — em vez de propagar IDs inválidos.
@@ -25,11 +25,14 @@ interface JobHandle<T = unknown> {
25
25
  data?: T
26
26
  error?: ActionError
27
27
  attempts: number
28
+ trace?: TraceContext
28
29
  }
29
30
  ```
30
31
 
31
- O core monta o `JobSpec` (action, input, ctx, retry/priority/timeout); o adapter persiste e
32
- processa.
32
+ `runtime.execute()` valida a chamada, cria o `JobSpec` e inclui o trace efetivo, request ID e
33
+ provenance original. O adapter persiste o envelope; o handle também expõe esse trace. No
34
+ worker, `runtime.executeJob()` revalida o envelope e executa um span filho, preservando a
35
+ cadeia request → enqueue → job sem aceitar baggage ou campos extras no carrier.
33
36
 
34
37
  ## Driver
35
38
 
@@ -52,10 +55,33 @@ const runtime = createRuntime({
52
55
  })
53
56
  ```
54
57
 
55
- A action declara `background: true` (+ retry/priority no contrato); chamá-la devolve um
58
+ A action declara `background: { enabled: true }` (+ retry/priority no contrato); chamá-la devolve um
56
59
  `JobHandle` em vez do resultado, e o cliente pola `status(jobId)` (ou `subscribe`) até
57
60
  `done`/`failed`.
58
61
 
62
+ ## Worker BullMQ
63
+
64
+ O app continua dono do `Worker` e da reidratação de autenticação. O processor chama
65
+ `executeJob()` — nunca `execute()`, que criaria outro job:
66
+
67
+ ```ts
68
+ new Worker('opus', async (job) => {
69
+ const spec = job.data as JobSpec
70
+ const ctx = await authContextForJob(spec.ctx)
71
+ const result = await runtime.executeJob(spec, ctx, {
72
+ report: (progress) => job.updateProgress(progress),
73
+ })
74
+ if (!result.ok) throw new Error(result.error.message)
75
+ return result.data
76
+ }, { connection })
77
+ ```
78
+
79
+ O runtime confirma que o actor reidratado corresponde a `spec.ctx.userId`; tenant, request,
80
+ trace e provenance vêm do envelope. O driver mapeia `attempts`, backoff nativo e prioridade.
81
+ Multiplicador exponencial diferente de 2 ou `maxMs` exige `backoffStrategy` custom do worker e
82
+ é rejeitado pelo driver em vez de ser silenciosamente ignorado. `timeout` permanece no
83
+ envelope para o worker aplicar, pois não é uma opção de execução do `Queue.add`.
84
+
59
85
  ## Limites (por enquanto)
60
86
 
61
87
  Driver hoje: `bullmq` (Redis). In-memory pra dev e outros backends (SQS, pg-boss…) entram
@@ -22,6 +22,7 @@ const runtime = createRuntime({
22
22
  data: kyselyData({ db }), // fornece ctx.db
23
23
  auth: makeAuthAdapter(), // resolve user/tenant/can
24
24
  audit: [pgAudit({ pool })], // trilha de auditoria (N sinks)
25
+ observability: makeObservability(), // span ativo + TraceContext vendor-neutral
25
26
  storage: fsStorage({ root }), // fornece ctx.storage (experimental)
26
27
  config: { env, i18n: { defaultLocale: 'pt-BR' } },
27
28
  })
@@ -42,6 +43,7 @@ await runtime.start()
42
43
  | `auth` | `user`/`tenantId`/`can` do contexto | `jwtAuth` — `…/auth/jwt` · `betterAuthSession` — `…/auth/better-auth` |
43
44
  | `audit` | trilha por execução de action | `pgAudit` — `…/audit/pg` · `consoleAudit` — `…/audit/console` |
44
45
  | `log` | `ctx.log` estruturado | `pinoLogger` — `@softize/opus/log/pino` |
46
+ | `observability` | span ativo + propagação de trace | porta no core; driver opt-in |
45
47
  | `eventBus` | `ctx.emit` + **reactions** | `mittEvents` — `@softize/opus/events/mitt` |
46
48
  | `queue` | jobs em background | `bullmqQueue` — `@softize/opus/queue/bullmq` |
47
49
  | `scheduler` | **schedules** (cron/intervalo) | `nodeCronScheduler` — `@softize/opus/scheduler/node-cron` |
@@ -60,7 +62,22 @@ Fora do runtime, mas parte do protocolo: o harness de teste (`runAction`/`testCo
60
62
 
61
63
  `user`/`tenantId`/`can` (auth) · `db` (data) · `log` (logger) · `emit` (eventBus) ·
62
64
  `storage` (storage) · `ai` (ai) · `provenance` (quem disparou: http, schedule,
63
- reaction…) · `meta`.
65
+ reaction…) · `trace` (quando configurado) · `meta`.
66
+
67
+ O core não depende de OpenTelemetry. O `ObservabilityAdapter` envolve actions e reactions
68
+ com `runInSpan`; o `TraceContext` efetivo também segue para resultado, audit e eventos. Os
69
+ envelopes de job recebem o trace da execução que enfileirou; `executeJob()` cria a execução
70
+ filha no worker sem reenfileirar a action.
71
+ Falha de instrumentação é lenient e nunca repete o handler.
72
+
73
+ O core não extrai headers HTTP. Os drivers Fastify e Node podem fornecer
74
+ `ContextBase.trace` com `traceContext: 'w3c'`; o opt-in é desativado por default. Outros
75
+ transportes continuam responsáveis por sua fronteira. Em audit, o contexto novo usa
76
+ `traceContext`; o campo `trace` legado continua disponível sem mudança.
77
+
78
+ Para actions, o callback observável resolve com `ActionResult`: `resultKind: 'action-result'`
79
+ indica que o driver deve inspecionar `result.ok`. Uma Promise resolvida com `ok:false` ainda
80
+ representa span de erro. Reactions usam `resultKind: 'void'` e rejeitam em falha.
64
81
 
65
82
  ## Reactions e schedules
66
83