@softize/opus 8.6.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 (286) hide show
  1. package/CHANGELOG.md +1616 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -0
  4. package/bin/cli.mjs +528 -0
  5. package/bin/lib/check.mjs +307 -0
  6. package/bin/lib/components.mjs +151 -0
  7. package/bin/lib/create.mjs +208 -0
  8. package/bin/lib/db-check-runner.mjs +86 -0
  9. package/bin/lib/db-migrate-runner.mjs +89 -0
  10. package/bin/lib/db-scaffold-runner.mjs +84 -0
  11. package/bin/lib/db.mjs +261 -0
  12. package/bin/lib/docs-include.mjs +48 -0
  13. package/bin/lib/gen-dicts.mjs +134 -0
  14. package/bin/lib/gen-docs.mjs +288 -0
  15. package/bin/lib/gen-manifest.mjs +102 -0
  16. package/bin/lib/gen-openapi.mjs +195 -0
  17. package/bin/lib/gen-runner.mjs +472 -0
  18. package/bin/lib/gen-stubs.mjs +463 -0
  19. package/bin/lib/gen.mjs +311 -0
  20. package/bin/lib/init.mjs +514 -0
  21. package/bin/lib/introspect.mjs +107 -0
  22. package/bin/lib/mcp.mjs +85 -0
  23. package/bin/lib/postinstall.mjs +56 -0
  24. package/docs/chat-event-protocol.md +85 -0
  25. package/docs/code-style.md +16 -0
  26. package/docs/data-layer.md +246 -0
  27. package/docs/ownership-vs-shadcn-lock.md +102 -0
  28. package/docs/protocol.md +2053 -0
  29. package/docs/releasing.md +110 -0
  30. package/docs/shellnav.md +131 -0
  31. package/package.json +338 -0
  32. package/registry/hooks/hooks.json +26 -0
  33. package/registry/hooks/link-memory-on-start.mjs +46 -0
  34. package/registry/hooks/opus-check-on-stop.mjs +114 -0
  35. package/registry/skills/create-action/SKILL.md +49 -0
  36. package/registry/skills/create-action/scaffold.mjs +122 -0
  37. package/registry/templates/app/_gitignore +3 -0
  38. package/registry/templates/app/_npmrc +1 -0
  39. package/registry/templates/app/_opus/_gitignore +5 -0
  40. package/registry/templates/app/_prettierrc.json +6 -0
  41. package/registry/templates/app/index.html +13 -0
  42. package/registry/templates/app/opus.config.ts +16 -0
  43. package/registry/templates/app/package.json +43 -0
  44. package/registry/templates/app/pnpm-workspace.yaml +11 -0
  45. package/registry/templates/app/public/favicon.svg +4 -0
  46. package/registry/templates/app/src/App.tsx +37 -0
  47. package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
  48. package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
  49. package/registry/templates/app/src/domains/tasks/index.ts +13 -0
  50. package/registry/templates/app/src/index.css +18 -0
  51. package/registry/templates/app/src/main.tsx +25 -0
  52. package/registry/templates/app/tsconfig.json +20 -0
  53. package/registry/templates/app/vite.config.ts +46 -0
  54. package/registry/templates/monorepo/_gitignore +3 -0
  55. package/registry/templates/monorepo/_npmrc +1 -0
  56. package/registry/templates/monorepo/package.json +9 -0
  57. package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
  58. package/src/ai/ask.ts +64 -0
  59. package/src/ai/drivers/anthropic.ts +309 -0
  60. package/src/ai/index.ts +17 -0
  61. package/src/audit/drivers/console.ts +117 -0
  62. package/src/audit/drivers/pg.ts +172 -0
  63. package/src/audit/index.ts +51 -0
  64. package/src/auth/drivers/better-auth.ts +103 -0
  65. package/src/auth/drivers/jwt.ts +188 -0
  66. package/src/auth/index.ts +9 -0
  67. package/src/client/drivers/fetch.ts +202 -0
  68. package/src/client/index.ts +22 -0
  69. package/src/core/actions.ts +110 -0
  70. package/src/core/audit.ts +239 -0
  71. package/src/core/contracts.ts +137 -0
  72. package/src/core/domain.ts +310 -0
  73. package/src/core/errors.ts +181 -0
  74. package/src/core/index.ts +174 -0
  75. package/src/core/logical-type.ts +31 -0
  76. package/src/core/reactions.ts +81 -0
  77. package/src/core/runtime.ts +1167 -0
  78. package/src/core/schedules.ts +41 -0
  79. package/src/core/types.ts +1356 -0
  80. package/src/data/drivers/kysely.ts +389 -0
  81. package/src/data/index.ts +10 -0
  82. package/src/data/readonly-pool.ts +160 -0
  83. package/src/dsl/eval.ts +136 -0
  84. package/src/dsl/index.ts +29 -0
  85. package/src/dsl/kysely.ts +230 -0
  86. package/src/dsl/loads.ts +123 -0
  87. package/src/dsl/parser.ts +423 -0
  88. package/src/dsl/types.ts +113 -0
  89. package/src/events/drivers/mitt.ts +70 -0
  90. package/src/events/index.ts +9 -0
  91. package/src/log/drivers/pino.ts +57 -0
  92. package/src/log/index.ts +9 -0
  93. package/src/mcp/index.ts +62 -0
  94. package/src/queue/drivers/bullmq.ts +190 -0
  95. package/src/queue/index.ts +9 -0
  96. package/src/scheduler/drivers/node-cron.ts +93 -0
  97. package/src/scheduler/every.ts +45 -0
  98. package/src/scheduler/index.ts +9 -0
  99. package/src/schema/drivers/zod.ts +765 -0
  100. package/src/schema/entity.ts +439 -0
  101. package/src/schema/format/locale.ts +144 -0
  102. package/src/schema/index.ts +65 -0
  103. package/src/schema/openapi.ts +302 -0
  104. package/src/schema/scaffold.ts +160 -0
  105. package/src/server/drivers/fastify.ts +224 -0
  106. package/src/server/drivers/node.ts +386 -0
  107. package/src/server/index.ts +142 -0
  108. package/src/storage/drivers/fs.ts +90 -0
  109. package/src/storage/drivers/s3.ts +117 -0
  110. package/src/storage/index.ts +27 -0
  111. package/src/testing/fake.ts +298 -0
  112. package/src/testing/index.ts +324 -0
  113. package/src/ui/components/patterns/action-form-card.tsx +48 -0
  114. package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
  115. package/src/ui/components/patterns/app-shell.tsx +227 -0
  116. package/src/ui/components/patterns/confirm.tsx +226 -0
  117. package/src/ui/components/patterns/data-state.tsx +75 -0
  118. package/src/ui/components/patterns/form-dialog.tsx +64 -0
  119. package/src/ui/components/patterns/form.tsx +584 -0
  120. package/src/ui/components/patterns/list.tsx +1488 -0
  121. package/src/ui/components/patterns/page.tsx +46 -0
  122. package/src/ui/components/patterns/section-shell.tsx +246 -0
  123. package/src/ui/components/patterns/shell-nav.tsx +150 -0
  124. package/src/ui/components/patterns/sidebar.tsx +89 -0
  125. package/src/ui/components/patterns/split.tsx +93 -0
  126. package/src/ui/components/patterns/trigger.tsx +196 -0
  127. package/src/ui/components/patterns/view.tsx +84 -0
  128. package/src/ui/components/primitives/accordion.tsx +64 -0
  129. package/src/ui/components/primitives/alert-dialog.tsx +190 -0
  130. package/src/ui/components/primitives/alert.tsx +116 -0
  131. package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
  132. package/src/ui/components/primitives/avatar.tsx +107 -0
  133. package/src/ui/components/primitives/badge.tsx +37 -0
  134. package/src/ui/components/primitives/breadcrumb.tsx +109 -0
  135. package/src/ui/components/primitives/button-group.tsx +83 -0
  136. package/src/ui/components/primitives/button.tsx +102 -0
  137. package/src/ui/components/primitives/calendar.tsx +218 -0
  138. package/src/ui/components/primitives/card.tsx +56 -0
  139. package/src/ui/components/primitives/carousel.tsx +239 -0
  140. package/src/ui/components/primitives/chat.tsx +407 -0
  141. package/src/ui/components/primitives/checkbox.tsx +30 -0
  142. package/src/ui/components/primitives/collapsible.tsx +31 -0
  143. package/src/ui/components/primitives/command.tsx +182 -0
  144. package/src/ui/components/primitives/composer.tsx +121 -0
  145. package/src/ui/components/primitives/copyable.tsx +50 -0
  146. package/src/ui/components/primitives/dialog.tsx +147 -0
  147. package/src/ui/components/primitives/drawer.tsx +141 -0
  148. package/src/ui/components/primitives/empty.tsx +104 -0
  149. package/src/ui/components/primitives/field.tsx +246 -0
  150. package/src/ui/components/primitives/icon-picker.tsx +180 -0
  151. package/src/ui/components/primitives/input-group.tsx +168 -0
  152. package/src/ui/components/primitives/input-otp.tsx +75 -0
  153. package/src/ui/components/primitives/input.tsx +72 -0
  154. package/src/ui/components/primitives/item.tsx +193 -0
  155. package/src/ui/components/primitives/kbd.tsx +28 -0
  156. package/src/ui/components/primitives/label.tsx +22 -0
  157. package/src/ui/components/primitives/markdown.tsx +35 -0
  158. package/src/ui/components/primitives/menu.tsx +255 -0
  159. package/src/ui/components/primitives/pagination.tsx +127 -0
  160. package/src/ui/components/primitives/popover.tsx +87 -0
  161. package/src/ui/components/primitives/progress.tsx +29 -0
  162. package/src/ui/components/primitives/radio-group.tsx +43 -0
  163. package/src/ui/components/primitives/resizable.tsx +51 -0
  164. package/src/ui/components/primitives/scroll-area.tsx +56 -0
  165. package/src/ui/components/primitives/select.tsx +479 -0
  166. package/src/ui/components/primitives/separator.tsx +26 -0
  167. package/src/ui/components/primitives/skeleton.tsx +13 -0
  168. package/src/ui/components/primitives/slider.tsx +61 -0
  169. package/src/ui/components/primitives/sonner.tsx +46 -0
  170. package/src/ui/components/primitives/spinner.tsx +29 -0
  171. package/src/ui/components/primitives/switch.tsx +33 -0
  172. package/src/ui/components/primitives/table.tsx +114 -0
  173. package/src/ui/components/primitives/tabs.tsx +104 -0
  174. package/src/ui/components/primitives/textarea.tsx +18 -0
  175. package/src/ui/components/primitives/toggle-group.tsx +81 -0
  176. package/src/ui/components/primitives/toggle.tsx +45 -0
  177. package/src/ui/components/primitives/tooltip.tsx +55 -0
  178. package/src/ui/components/primitives/truncate.tsx +49 -0
  179. package/src/ui/docs/DocBrowser.tsx +90 -0
  180. package/src/ui/docs/changelog.tsx +80 -0
  181. package/src/ui/docs/content/accordion.md +86 -0
  182. package/src/ui/docs/content/action-form-card.md +24 -0
  183. package/src/ui/docs/content/action-form-dialog.md +30 -0
  184. package/src/ui/docs/content/action-form.md +125 -0
  185. package/src/ui/docs/content/action-list-dialog.md +68 -0
  186. package/src/ui/docs/content/action-list.md +194 -0
  187. package/src/ui/docs/content/action-trigger.md +72 -0
  188. package/src/ui/docs/content/action-view.md +47 -0
  189. package/src/ui/docs/content/actions.md +138 -0
  190. package/src/ui/docs/content/ai.md +112 -0
  191. package/src/ui/docs/content/alert-dialog.md +73 -0
  192. package/src/ui/docs/content/alert.md +69 -0
  193. package/src/ui/docs/content/app-shell.md +155 -0
  194. package/src/ui/docs/content/aspect-ratio.md +66 -0
  195. package/src/ui/docs/content/audit.md +84 -0
  196. package/src/ui/docs/content/auth.md +70 -0
  197. package/src/ui/docs/content/avatar.md +94 -0
  198. package/src/ui/docs/content/badge.md +48 -0
  199. package/src/ui/docs/content/breadcrumb.md +87 -0
  200. package/src/ui/docs/content/button-group.md +71 -0
  201. package/src/ui/docs/content/button.md +60 -0
  202. package/src/ui/docs/content/calendar.md +62 -0
  203. package/src/ui/docs/content/card.md +49 -0
  204. package/src/ui/docs/content/carousel.md +85 -0
  205. package/src/ui/docs/content/chat.md +69 -0
  206. package/src/ui/docs/content/checkbox.md +75 -0
  207. package/src/ui/docs/content/cli.md +58 -0
  208. package/src/ui/docs/content/collapsible.md +64 -0
  209. package/src/ui/docs/content/command.md +56 -0
  210. package/src/ui/docs/content/composer.md +50 -0
  211. package/src/ui/docs/content/confirm.md +120 -0
  212. package/src/ui/docs/content/copyable.md +30 -0
  213. package/src/ui/docs/content/customization.md +110 -0
  214. package/src/ui/docs/content/cycle.md +34 -0
  215. package/src/ui/docs/content/data-state.md +47 -0
  216. package/src/ui/docs/content/data.md +99 -0
  217. package/src/ui/docs/content/dialog.md +60 -0
  218. package/src/ui/docs/content/drawer.md +55 -0
  219. package/src/ui/docs/content/empty.md +66 -0
  220. package/src/ui/docs/content/events.md +61 -0
  221. package/src/ui/docs/content/field.md +58 -0
  222. package/src/ui/docs/content/getting-started.md +109 -0
  223. package/src/ui/docs/content/icon-picker.md +51 -0
  224. package/src/ui/docs/content/input-group.md +78 -0
  225. package/src/ui/docs/content/input-otp.md +72 -0
  226. package/src/ui/docs/content/input.md +78 -0
  227. package/src/ui/docs/content/item.md +84 -0
  228. package/src/ui/docs/content/kbd.md +62 -0
  229. package/src/ui/docs/content/label.md +32 -0
  230. package/src/ui/docs/content/log.md +55 -0
  231. package/src/ui/docs/content/markdown.md +41 -0
  232. package/src/ui/docs/content/mcp.md +44 -0
  233. package/src/ui/docs/content/menu.md +114 -0
  234. package/src/ui/docs/content/microcopy.md +83 -0
  235. package/src/ui/docs/content/page.md +34 -0
  236. package/src/ui/docs/content/pagination.md +99 -0
  237. package/src/ui/docs/content/popover.md +49 -0
  238. package/src/ui/docs/content/progress.md +69 -0
  239. package/src/ui/docs/content/queue.md +62 -0
  240. package/src/ui/docs/content/radio-group.md +77 -0
  241. package/src/ui/docs/content/resizable.md +86 -0
  242. package/src/ui/docs/content/router.md +56 -0
  243. package/src/ui/docs/content/runtime.md +77 -0
  244. package/src/ui/docs/content/scheduler.md +66 -0
  245. package/src/ui/docs/content/scroll-area.md +89 -0
  246. package/src/ui/docs/content/section-shell.md +121 -0
  247. package/src/ui/docs/content/select.md +342 -0
  248. package/src/ui/docs/content/separator.md +33 -0
  249. package/src/ui/docs/content/sidebar.md +38 -0
  250. package/src/ui/docs/content/skeleton.md +34 -0
  251. package/src/ui/docs/content/slider.md +64 -0
  252. package/src/ui/docs/content/spinner.md +37 -0
  253. package/src/ui/docs/content/split.md +33 -0
  254. package/src/ui/docs/content/storage.md +69 -0
  255. package/src/ui/docs/content/switch.md +69 -0
  256. package/src/ui/docs/content/table.md +102 -0
  257. package/src/ui/docs/content/tabs.md +94 -0
  258. package/src/ui/docs/content/testing.md +89 -0
  259. package/src/ui/docs/content/textarea.md +30 -0
  260. package/src/ui/docs/content/toast.md +67 -0
  261. package/src/ui/docs/content/toggle-group.md +81 -0
  262. package/src/ui/docs/content/toggle.md +72 -0
  263. package/src/ui/docs/content/tokens.md +171 -0
  264. package/src/ui/docs/content/tooltip.md +50 -0
  265. package/src/ui/docs/content/truncate.md +37 -0
  266. package/src/ui/docs/content/ui.md +40 -0
  267. package/src/ui/docs/content/upgrading.md +48 -0
  268. package/src/ui/docs/doc-client.tsx +214 -0
  269. package/src/ui/docs/doc.tsx +301 -0
  270. package/src/ui/docs/folder.tsx +149 -0
  271. package/src/ui/docs/index.ts +21 -0
  272. package/src/ui/docs/markdown.tsx +130 -0
  273. package/src/ui/docs/md-raw.d.ts +4 -0
  274. package/src/ui/docs/plugin.ts +104 -0
  275. package/src/ui/docs/registry.tsx +424 -0
  276. package/src/ui/docs/standalone.tsx +107 -0
  277. package/src/ui/drivers/react.tsx +627 -0
  278. package/src/ui/index.ts +92 -0
  279. package/src/ui/lib/cn.ts +10 -0
  280. package/src/ui/lib/zod-pt-br.ts +38 -0
  281. package/src/ui/meta.ts +412 -0
  282. package/src/ui/react.tsx +235 -0
  283. package/src/ui/router.ts +96 -0
  284. package/src/ui/theme.css +234 -0
  285. package/src/vite/design.ts +652 -0
  286. package/src/vite/index.ts +8 -0
@@ -0,0 +1,1356 @@
1
+ /**
2
+ * tbdlib — Core types
3
+ *
4
+ * Tipos centrais do protocolo. Source of truth: `/docs/protocol.md`.
5
+ * Qualquer divergência entre este arquivo e a doc é bug; alinhar a doc primeiro,
6
+ * depois ajustar o código.
7
+ *
8
+ * Organização (numerada igual ao protocolo quando aplicável):
9
+ * 1. Primitivos & shared
10
+ * 2. Erro (§3)
11
+ * 3. Result (§6)
12
+ * 4. Contexto (§4)
13
+ * 5. Audit (§5)
14
+ * 6. Eventos (§11)
15
+ * 7. Jobs / background (§10)
16
+ * 8. Tipos lógicos (§7)
17
+ * 9. Specs (FieldSpec, FilterSpec, ConfirmSpec, ...) (§2)
18
+ * 10. Action — discriminated union (§2)
19
+ * 11. Reaction (§12)
20
+ * 12. Adapters (§8)
21
+ * 13. RuntimeConfig (§14)
22
+ */
23
+
24
+ // =============================================================================
25
+ // 1. Primitivos & shared
26
+ // =============================================================================
27
+
28
+ /**
29
+ * Referência i18n. Opt-in.
30
+ * - `string` literal vira a si mesma (passthrough quando sem resolver).
31
+ * - `{ key, default }` resolve via `RuntimeConfig.i18n.resolver`, com fallback no `default`.
32
+ * Ver §13.
33
+ */
34
+ export type I18nRef = string | { key: string; default: string }
35
+
36
+ /**
37
+ * Função pra cancelar uma inscrição (event bus, queue listener, etc).
38
+ */
39
+ export type Unsubscribe = () => void
40
+
41
+ /**
42
+ * Logger genérico que mora no contexto. Implementado por `LoggerAdapter`.
43
+ * Detalhes: §8.
44
+ */
45
+ export interface Logger {
46
+ trace(msg: string, meta?: object): void
47
+ debug(msg: string, meta?: object): void
48
+ info(msg: string, meta?: object): void
49
+ warn(msg: string, meta?: object): void
50
+ error(msg: string, meta?: object): void
51
+ fatal(msg: string, meta?: object): void
52
+ /** Cria logger derivado com bindings pré-injetados (per-request, per-action). */
53
+ child(bindings: object): Logger
54
+ }
55
+
56
+ /**
57
+ * Path segment numa validation issue. Suporta tanto chave crua quanto wrapper.
58
+ * Espelha Standard Schema v1 spec.
59
+ */
60
+ export interface SchemaPathSegment {
61
+ readonly key: PropertyKey
62
+ }
63
+
64
+ export interface SchemaIssue {
65
+ readonly message: string
66
+ readonly path?: ReadonlyArray<PropertyKey | SchemaPathSegment> | undefined
67
+ }
68
+
69
+ export interface SchemaResultSuccess<TOutput> {
70
+ readonly value: TOutput
71
+ readonly issues?: undefined
72
+ }
73
+
74
+ export interface SchemaResultFailure {
75
+ readonly issues: ReadonlyArray<SchemaIssue>
76
+ }
77
+
78
+ export type SchemaResult<TOutput> = SchemaResultSuccess<TOutput> | SchemaResultFailure
79
+
80
+ /**
81
+ * Schema compatível com Standard Schema v1. Default vem de Zod (lib expõe `t.*`
82
+ * em `@softize/opus/core`), mas qualquer lib que implemente o spec funciona
83
+ * (Zod 3.24+, ArkType, Valibot via adapter).
84
+ *
85
+ * Mantemos o shape inline pra não acoplar core a `@standard-schema/spec`.
86
+ */
87
+ export interface Schema<TInput = unknown, TOutput = TInput> {
88
+ readonly '~standard': {
89
+ readonly version: 1
90
+ readonly vendor: string
91
+ readonly validate: (
92
+ value: unknown,
93
+ ) => SchemaResult<TOutput> | Promise<SchemaResult<TOutput>>
94
+ readonly types?: { readonly input: TInput; readonly output: TOutput } | undefined
95
+ }
96
+ }
97
+
98
+ /** Helper pra extrair o tipo de output de um Schema. */
99
+ export type InferOutput<S> = S extends Schema<unknown, infer Out> ? Out : never
100
+
101
+ /** Helper pra extrair o tipo de input de um Schema. */
102
+ export type InferInput<S> = S extends Schema<infer In, unknown> ? In : never
103
+
104
+ // =============================================================================
105
+ // 2. Erro (§3)
106
+ // =============================================================================
107
+
108
+ /**
109
+ * Categorias de erro com mapping HTTP default sugerido. Ver §3.
110
+ */
111
+ export type ErrorCategory =
112
+ | 'validation'
113
+ | 'authentication'
114
+ | 'authorization'
115
+ | 'not_found'
116
+ | 'conflict'
117
+ | 'rate_limit'
118
+ | 'dependency'
119
+ | 'internal'
120
+
121
+ export type Severity = 'warning' | 'error' | 'fatal'
122
+
123
+ /**
124
+ * Issue dentro de `ActionError.issues` — usado tipicamente em erros de validation
125
+ * pra reportar múltiplos campos inválidos numa única resposta.
126
+ */
127
+ export interface ValidationIssue {
128
+ path: string
129
+ code: string
130
+ message: string
131
+ i18nKey?: string
132
+ i18nParams?: Record<string, unknown>
133
+ }
134
+
135
+ /**
136
+ * Shape padronizado de erro emitido por qualquer action. Adapters de transporte
137
+ * serializam preservando estrutura. Ver §3.
138
+ */
139
+ export interface ActionError {
140
+ code: string
141
+ category: ErrorCategory
142
+ message: string
143
+ severity: Severity
144
+ retriable: boolean
145
+ i18nKey?: string
146
+ i18nParams?: Record<string, unknown>
147
+ field?: string
148
+ issues?: ValidationIssue[]
149
+ cause?: unknown
150
+ meta?: Record<string, unknown>
151
+ }
152
+
153
+ /**
154
+ * Catálogo declarativo de erros que uma action pode emitir.
155
+ * Drive OpenAPI/AI tools — não é checado em runtime.
156
+ */
157
+ export interface ErrorSpec {
158
+ code: string
159
+ category: ErrorCategory
160
+ description: string
161
+ }
162
+
163
+ // =============================================================================
164
+ // 3. Result (§6)
165
+ // =============================================================================
166
+
167
+ export interface ResultMeta {
168
+ actionId: string
169
+ action: string
170
+ durationMs: number
171
+ requestId?: string
172
+ cached?: boolean
173
+ }
174
+
175
+ /**
176
+ * Envelope discriminated union retornado de toda execução síncrona.
177
+ * Background actions retornam `BackgroundResult<T>`.
178
+ */
179
+ export type ActionResult<T> =
180
+ | { ok: true; data: T; meta: ResultMeta }
181
+ | { ok: false; error: ActionError; meta: ResultMeta }
182
+
183
+ export type BackgroundResult<T> =
184
+ | { ok: true; data: JobHandle<T>; meta: ResultMeta }
185
+ | { ok: false; error: ActionError; meta: ResultMeta }
186
+
187
+ /**
188
+ * Output de search actions: sempre paginado.
189
+ */
190
+ export interface Paginated<T> {
191
+ items: T[]
192
+ cursor: { next: string | null; prev?: string | null }
193
+ total?: number
194
+ }
195
+
196
+ // =============================================================================
197
+ // 4. Contexto (§4)
198
+ // =============================================================================
199
+
200
+ /**
201
+ * Origem (provenance) de uma execução. Identifica QUEM/O QUÊ disparou
202
+ * a action — humano, schedule, reaction, AI agent, integração externa.
203
+ *
204
+ * Discriminated union por `kind`. Provenance é populada pelo runtime ao
205
+ * receber a request/trigger; chega ao handler via `ctx.provenance` e
206
+ * é persistida no `AuditRecord.provenance`.
207
+ *
208
+ * Provenance chains: actions disparadas por `background` ou `reaction`
209
+ * preservam a `originalProvenance` permitindo rastrear a cadeia inteira
210
+ * até a origem humana/sistema.
211
+ */
212
+ export type Provenance =
213
+ | { kind: 'http'; userId: string | null; requestId?: string }
214
+ | { kind: 'schedule'; scheduleName: string }
215
+ | {
216
+ kind: 'reaction'
217
+ reactionName: string
218
+ sourceActionId: string
219
+ eventType: string
220
+ originalProvenance?: Provenance
221
+ }
222
+ | {
223
+ kind: 'background'
224
+ queueName: string
225
+ jobId: string
226
+ originalProvenance?: Provenance
227
+ }
228
+ | { kind: 'ai-agent'; agentId: string; model?: string; sessionId?: string }
229
+ | { kind: 'integration'; name: string; payload?: Record<string, unknown> }
230
+ | { kind: 'self-observation'; monitorName: string }
231
+ | { kind: 'system'; source: string }
232
+
233
+ /**
234
+ * Representação opaca do usuário autenticado. Shape exato é decidido pelo
235
+ * `AuthAdapter`. Core não opina além do `id`.
236
+ */
237
+ export interface User {
238
+ id: string
239
+ [key: string]: unknown
240
+ }
241
+
242
+ /**
243
+ * Função que checa permissão. Plugada pelo `AuthAdapter`.
244
+ * tbdlib não implementa RBAC/ABAC — apenas delega.
245
+ */
246
+ export type CanFn = (permission: string, resource?: unknown) => boolean | Promise<boolean>
247
+
248
+ /**
249
+ * Função que emite um `DomainEvent`. Disponível em `ActionContext.emit` e
250
+ * `ReactionContext.emit`. Plugada pelo `EventBusAdapter`.
251
+ */
252
+ export type EmitFn = <T = unknown>(
253
+ event: string,
254
+ data: T,
255
+ meta?: { correlation?: string; [k: string]: unknown },
256
+ ) => Promise<void>
257
+
258
+ /**
259
+ * Contexto entregue ao handler de action. Composto por adapters: auth fornece
260
+ * `user`/`tenantId`/`can`; data fornece `db`; logger fornece `log`; eventbus
261
+ * fornece `emit`; storage/ai fornecem `storage`/`ai` (null quando não configurados).
262
+ */
263
+ export interface ActionContext {
264
+ user: User | null
265
+ tenantId: string | null
266
+ can: CanFn
267
+ db: unknown
268
+ log: Logger
269
+ emit: EmitFn
270
+ storage: StorageAdapter | null
271
+ ai: BoundAi | null
272
+ provenance: Provenance
273
+ meta: Record<string, unknown>
274
+ }
275
+
276
+ /**
277
+ * Contexto entregue ao handler de reaction. Mais enxuto que `ActionContext`
278
+ * (sem `input`/`loaded`). Ver §12.
279
+ */
280
+ export interface ReactionContext {
281
+ user: User | null
282
+ tenantId: string | null
283
+ db: unknown
284
+ log: Logger
285
+ emit: EmitFn
286
+ storage: StorageAdapter | null
287
+ ai: BoundAi | null
288
+ provenance: Provenance
289
+ meta: Record<string, unknown>
290
+ }
291
+
292
+ // =============================================================================
293
+ // 5. Audit (§5)
294
+ // =============================================================================
295
+
296
+ /**
297
+ * Registro emitido após execução de action (sucesso ou falha).
298
+ * Persistido pelos `AuditSink` configurados. Ver §5.
299
+ */
300
+ export interface AuditRecord {
301
+ id: string
302
+ timestamp: string
303
+ action: string
304
+ /**
305
+ * O `kind` da action executada — sink que filtra leitura (list/view = ruído)
306
+ * decide pelo record, sem receber os DOMAINS por fora pra montar Set de nomes.
307
+ */
308
+ actionKind?: ActionDef['kind']
309
+ outcome: 'success' | 'error'
310
+ durationMs: number
311
+
312
+ /**
313
+ * Provenance estruturada — origem do trigger (humano, schedule, reaction,
314
+ * AI, etc). Permite rastreabilidade da cadeia de execução.
315
+ */
316
+ provenance: Provenance
317
+
318
+ /**
319
+ * Actor derivado de provenance, mantido pra retrocompat.
320
+ * Em provenance de tipo `http`/`ai-agent`/`integration`, `actor.id` é
321
+ * derivado; em system/schedule/reaction sem chain, `actor.id` é null.
322
+ * `meta` vem populado pelo runtime com `name`/`email` do user resolvido
323
+ * (quando presentes) — sink não precisa re-resolver id→nome.
324
+ */
325
+ actor: {
326
+ id: string | null
327
+ type?: 'user' | 'system' | 'integration'
328
+ meta?: Record<string, unknown>
329
+ }
330
+ tenant?: string | null
331
+
332
+ input: unknown
333
+ output?: unknown
334
+ error?: ActionError
335
+
336
+ severity: 'info' | 'warning' | 'error'
337
+ trace?: {
338
+ requestId?: string
339
+ parentActionId?: string
340
+ }
341
+ meta?: Record<string, unknown>
342
+ }
343
+
344
+ /**
345
+ * Configuração opcional de audit por action. Quando `audit: true` (default),
346
+ * captura tudo. Quando `audit: false`, desabilita. Quando objeto, customiza.
347
+ */
348
+ export interface AuditConfig {
349
+ fields?: string[]
350
+ redact?: string[]
351
+ severity?: 'info' | 'warning' | 'error'
352
+ sink?: string
353
+ output?: boolean | { fields?: string[]; redact?: string[] }
354
+ }
355
+
356
+ // =============================================================================
357
+ // 6. Eventos (§11)
358
+ // =============================================================================
359
+
360
+ /**
361
+ * Evento de domínio emitido pelo handler via `ctx.emit`. Shape preservado pelo
362
+ * `EventBusAdapter` ao publicar/distribuir.
363
+ */
364
+ export interface DomainEvent<T = unknown> {
365
+ type: string
366
+ id: string
367
+ timestamp: string
368
+ actor: { id: string | null; type?: 'user' | 'system' | 'integration' }
369
+ tenant?: string | null
370
+ data: T
371
+ source: {
372
+ action: string
373
+ actionId: string
374
+ correlation?: string
375
+ }
376
+ meta?: Record<string, unknown>
377
+ }
378
+
379
+ // =============================================================================
380
+ // 7. Jobs / background (§10)
381
+ // =============================================================================
382
+
383
+ export type JobStatus = 'queued' | 'running' | 'done' | 'failed' | 'cancelled'
384
+
385
+ /**
386
+ * Handle retornado por background action. Cliente pola ou subscribe pra
387
+ * acompanhar status até `done`/`failed`.
388
+ */
389
+ export interface JobHandle<T = unknown> {
390
+ jobId: string
391
+ action: string
392
+ status: JobStatus
393
+ progress?: { percent?: number; message?: string; data?: unknown }
394
+ data?: T
395
+ error?: ActionError
396
+ enqueuedAt: string
397
+ startedAt?: string
398
+ finishedAt?: string
399
+ attempts: number
400
+ }
401
+
402
+ /**
403
+ * Especificação enviada pra `QueueAdapter.enqueue`. Core constrói; adapter
404
+ * persiste e processa.
405
+ */
406
+ export interface JobSpec {
407
+ jobId: string
408
+ action: string
409
+ input: unknown
410
+ ctx: {
411
+ userId: string | null
412
+ tenantId: string | null
413
+ requestId?: string
414
+ }
415
+ config: {
416
+ queue?: string
417
+ priority?: 'high' | 'normal' | 'low'
418
+ retry?: { attempts: number; backoff?: BackoffSpec }
419
+ timeout?: number
420
+ }
421
+ }
422
+
423
+ export type BackoffSpec =
424
+ | { kind: 'fixed'; delayMs: number }
425
+ | { kind: 'exponential'; initialMs: number; multiplier?: number; maxMs?: number }
426
+
427
+ /**
428
+ * Config de execução em background, declarada na action.
429
+ * Apenas `simple`/`form` aceitam.
430
+ */
431
+ export interface BackgroundConfig {
432
+ enabled: true
433
+ queue?: string
434
+ priority?: 'high' | 'normal' | 'low'
435
+ retry?: { attempts: number; backoff?: BackoffSpec }
436
+ timeout?: number
437
+ progress?: boolean
438
+ }
439
+
440
+ /**
441
+ * Reporter injetado como 4º argumento do handler quando `background.progress: true`.
442
+ */
443
+ export interface ProgressReporter {
444
+ report(p: {
445
+ percent?: number
446
+ message?: string
447
+ data?: unknown
448
+ }): Promise<void> | void
449
+ }
450
+
451
+ // =============================================================================
452
+ // 8. Tipos lógicos (§7) — metadata anexada
453
+ // =============================================================================
454
+
455
+ /**
456
+ * Metadata anexada por `t.*` constructors. Adapters consultam pra mapear tipo
457
+ * lógico → coluna DB / widget UI / validação extra.
458
+ */
459
+ export interface LogicalTypeMeta {
460
+ logicalType: string
461
+ params?: Record<string, unknown>
462
+ }
463
+
464
+ // =============================================================================
465
+ // 9. Specs (§2)
466
+ // =============================================================================
467
+
468
+ export interface ConfirmSpec {
469
+ title: I18nRef
470
+ message?: I18nRef
471
+ destructive?: boolean
472
+ confirmLabel?: I18nRef
473
+ cancelLabel?: I18nRef
474
+ }
475
+
476
+ export interface RateLimitSpec {
477
+ window: number
478
+ max: number
479
+ key?: (ctx: ActionContext, input: unknown) => string
480
+ }
481
+
482
+ export interface AIConfig {
483
+ enabled?: boolean
484
+ description?: string
485
+ destructive?: boolean
486
+ requiresConfirmation?: boolean
487
+ }
488
+
489
+ export interface Example {
490
+ name: string
491
+ description?: string
492
+ input: unknown
493
+ output?: unknown
494
+ error?: unknown
495
+ }
496
+
497
+ export interface ActionMessages {
498
+ success?: I18nRef
499
+ error?: I18nRef
500
+ confirmation?: I18nRef
501
+ }
502
+
503
+ /**
504
+ * Origem de opções pra um campo (select/radio/lookup/autocomplete).
505
+ * Discriminated union por `kind`.
506
+ */
507
+ export type OptionsSpec =
508
+ | { kind: 'static'; items: Array<{ value: string; label: I18nRef }> }
509
+ | { kind: 'dictionary'; ref: string }
510
+ | { kind: 'lookup'; source: string; depends?: string[] }
511
+
512
+ /**
513
+ * Descrição de campo em `FormAction.fields`. Drive UI rendering + AI tool
514
+ * description per-field.
515
+ */
516
+ export interface FieldSpec {
517
+ label: I18nRef
518
+ placeholder?: I18nRef
519
+ hint?: I18nRef
520
+ /** Ajuda no hover/foco da label (ícone ⓘ + tooltip). `hint` = texto auxiliar SOB o campo;
521
+ * `help` = explicação mais longa, escondida atrás do ícone na label. */
522
+ help?: I18nRef
523
+
524
+ default?: unknown
525
+ mask?: string
526
+
527
+ depends?: string[]
528
+ showWhen?: (input: Record<string, unknown>) => boolean
529
+ requireWhen?: (input: Record<string, unknown>) => boolean
530
+
531
+ group?: string
532
+ order?: number
533
+
534
+ widget?: string
535
+ options?: OptionsSpec
536
+
537
+ aiDescription?: string
538
+ }
539
+
540
+ export type FilterOp =
541
+ | 'eq'
542
+ | 'neq'
543
+ | 'gt'
544
+ | 'gte'
545
+ | 'lt'
546
+ | 'lte'
547
+ | 'in'
548
+ | 'nin'
549
+ | 'contains'
550
+ | 'startsWith'
551
+ | 'endsWith'
552
+ | 'between'
553
+ | 'null'
554
+ | 'notNull'
555
+
556
+ /**
557
+ * Descrição de filtro em `ListAction.filters`. Mode `'server'` (default,
558
+ * filtra na query do DB) ou `'client'` (filtra no React adapter sobre
559
+ * `items` já carregados).
560
+ */
561
+ export interface FilterSpec {
562
+ label: I18nRef
563
+ placeholder?: I18nRef
564
+
565
+ type: 'text' | 'select' | 'lookup' | 'date' | 'number'
566
+ multiple?: boolean
567
+ mode?: 'server' | 'client'
568
+ operators?: FilterOp[]
569
+ path?: string
570
+
571
+ /** Filtro AVANÇADO: sai da barra inline e vai pro modal "Filtros" (com contador de
572
+ * ativos no botão). Os não-avançados rendem inline na toolbar da listagem. */
573
+ advanced?: boolean
574
+
575
+ section?: string
576
+ depends?: string[]
577
+
578
+ options?: OptionsSpec
579
+
580
+ aiDescription?: string
581
+ }
582
+
583
+ /**
584
+ * Coluna declarativa de uma `ListAction` — a UI (ActionList) deriva a tabela
585
+ * daqui; células custom entram POR CIMA na UI (prop `cells`, chave = key). É a base
586
+ * do futuro column picker (o usuário escolher colunas): a lista completa vive no
587
+ * contrato, `hidden` marca as que nascem fora.
588
+ */
589
+ export interface ListColumnSpec {
590
+ /** Chave do item (`Out`) que a coluna mostra. */
591
+ key: string
592
+ label: I18nRef
593
+ /** Render padrão: 'text' (default) · 'number' (alinha à direita) · 'date' (Intl) ·
594
+ * 'badge' (chip com o valor; dict/cores via célula custom por enquanto). */
595
+ type?: 'text' | 'number' | 'date' | 'badge'
596
+ /** Header clicável (asc ↔ desc). Convenção: a UI escreve `sort: '<key>:<dir>'` no
597
+ * input; o handler implementa o orderBy. */
598
+ sortable?: boolean
599
+ /** Coluna encolhe ao conteúdo (não expande). */
600
+ fit?: boolean
601
+ /** Nasce oculta (candidata ao column picker). */
602
+ hidden?: boolean
603
+ /** Formato quando `type: 'date'` (Intl.DateTimeFormatOptions). */
604
+ dateFormat?: Intl.DateTimeFormatOptions
605
+ }
606
+
607
+ export interface SortSpec {
608
+ field: string
609
+ dir: 'asc' | 'desc'
610
+ }
611
+
612
+ export interface PeriodSpec {
613
+ value: string
614
+ label: I18nRef
615
+ /** Preset inicial da listagem. Sem marcado, o PRIMEIRO da lista é o default —
616
+ * período é recorte obrigatório do caso de uso (não existe "sem período"). */
617
+ default?: boolean
618
+ }
619
+
620
+ export interface ExpandSpec {
621
+ description?: string
622
+ default?: boolean
623
+ }
624
+
625
+ // =============================================================================
626
+ // 10. Action — discriminated union (§2)
627
+ // =============================================================================
628
+
629
+ export type ActionKind = 'simple' | 'form' | 'list' | 'view'
630
+
631
+ export type AuthorizeFn<In, Loaded = Record<string, unknown>> = (
632
+ ctx: ActionContext,
633
+ input: In,
634
+ loaded?: Loaded,
635
+ ) => boolean | ActionError | Promise<boolean | ActionError>
636
+
637
+ /**
638
+ * Authorize aceita string DSL ou closure.
639
+ *
640
+ * - **String DSL**: avaliada pelo runtime contra um contexto formado por
641
+ * `{ user, input, ctx, ...loaded }`. Resultado coercido pra boolean.
642
+ * Ex: `'user.kind == "employee" OR ticket.assigneeId == user.id'`.
643
+ * - **Closure (`AuthorizeFn`)**: escape hatch pra lógica que a DSL não
644
+ * cobre (acesso a serviços externos, side effects, etc).
645
+ */
646
+ export type AuthorizeSpec<In, Loaded = Record<string, unknown>> =
647
+ | string
648
+ | AuthorizeFn<In, Loaded>
649
+
650
+ export type HandlerFn<In, Out, Loaded = Record<string, unknown>> = (
651
+ ctx: ActionContext,
652
+ input: In,
653
+ loaded?: Loaded,
654
+ progress?: ProgressReporter,
655
+ ) => Out | Promise<Out>
656
+
657
+ export type LoaderFn<In> = (ctx: ActionContext, input: In) => Promise<unknown>
658
+
659
+ /**
660
+ * Resolver registrado via `Runtime.registerLoaders`. Mapeia entidade DSL
661
+ * (ex: `'ticket'`) → função que recebe args já resolvidos do input mais o
662
+ * `ActionContext` e retorna a entidade carregada (ou null/undefined → not_found).
663
+ */
664
+ export type LoaderResolver = (
665
+ args: Record<string, unknown>,
666
+ ctx: ActionContext,
667
+ ) => Promise<unknown> | unknown
668
+
669
+ /**
670
+ * Spec individual de load:
671
+ *
672
+ * - **String DSL** (`'ticket(:id)'`): runtime parsa via `parseLoad`, resolve
673
+ * args contra o input e delega ao `LoaderResolver` registrado pra entidade.
674
+ * - **Closure (`LoaderFn`)**: escape hatch que recebe `(ctx, input)` direto.
675
+ */
676
+ export type LoadSpec<In> = string | LoaderFn<In>
677
+
678
+ /**
679
+ * Specs de loads de um action.
680
+ *
681
+ * - **Record nomeado** (`{ ticket: 'ticket(:id)', meta: async () => ... }`):
682
+ * chave = nome da propriedade em `loaded`; valor = string DSL ou closure.
683
+ * - **Array de strings** (`['ticket(:id)', 'shipment(:shipmentId)']`): cada
684
+ * string é parseada e a entidade vira a chave em `loaded` (ex: `loaded.ticket`).
685
+ */
686
+ export type LoadsSpec<In> = Record<string, LoadSpec<In>> | string[]
687
+
688
+ /**
689
+ * Forma legada: `Record<string, LoaderFn<In>>`. Alias mantido pra
690
+ * retrocompat — código existente que importa `LoadersSpec` continua valendo,
691
+ * mas `LoadsSpec` é o novo nome (e aceita também string DSL).
692
+ */
693
+ export type LoadersSpec<In> = Record<string, LoaderFn<In>>
694
+
695
+ /**
696
+ * Campos compartilhados por todos os kinds de action. Discriminator (`kind`)
697
+ * e campos kind-specific vivem nas variantes abaixo.
698
+ *
699
+ * Dois tipos de input, um schema:
700
+ *
701
+ * - **`In`** — o lado do FIO (o que o cliente envia, pré-parse). É o tipo
702
+ * que client/contracts/fields enxergam. Campo com `.default()` é opcional
703
+ * aqui.
704
+ * - **`ParsedIn`** — o lado PARSEADO (o output do schema de input, defaults
705
+ * aplicados). O runtime SEMPRE valida antes de executar, então `handler`,
706
+ * `mockHandler`, `authorize` e `loads` recebem `ParsedIn`.
707
+ *
708
+ * O default `ParsedIn = In` mantém retrocompat com anotações à mão de dois
709
+ * genéricos; `defineAction`/`defineContract` inferem os dois lados do schema.
710
+ */
711
+ export interface ActionBase<In, Out, ParsedIn = In> {
712
+ // — Identidade —
713
+ name: string
714
+ kind: ActionKind
715
+
716
+ // — Documentação / Application Interface —
717
+ label?: I18nRef
718
+ title?: I18nRef
719
+ summary?: string
720
+ description?: string
721
+ icon?: string
722
+ color?: 'primary' | 'success' | 'warning' | 'danger' | 'neutral' | string
723
+ messages?: ActionMessages
724
+ tags?: string[]
725
+ examples?: Example[]
726
+ errors?: ErrorSpec[]
727
+
728
+ // — Contrato —
729
+ input: Schema<In, ParsedIn>
730
+ output: Schema<Out>
731
+
732
+ // — Autorização (fail-closed default) —
733
+ public?: boolean
734
+ requires?: string | string[]
735
+ authorize?: AuthorizeSpec<ParsedIn>
736
+ loads?: LoadsSpec<ParsedIn>
737
+
738
+ // — Execução —
739
+ handler: HandlerFn<ParsedIn, Out>
740
+
741
+ /**
742
+ * Handler alternativo executado quando `process.env.OPUS_MODE === 'design'`.
743
+ * Use pra prototipar UI com dados realistas sem precisar do backend pronto.
744
+ *
745
+ * Em modo design: runtime tenta `mockHandler` primeiro; se ausente, cai
746
+ * no `handler` normal (permitindo testar pontualmente real ao lado de
747
+ * mocks).
748
+ *
749
+ * Em modo default (sem env): `mockHandler` é ignorado. Útil pra apagar
750
+ * em revisão ou manter pro Designer reusar.
751
+ *
752
+ * Pattern recomendado pra ações que só têm mock durante dev:
753
+ *
754
+ * ```ts
755
+ * defineAction({
756
+ * name: 'ticket.list',
757
+ * // ...
758
+ * mockHandler: async () => MOCK_TICKETS,
759
+ * handler: () => {
760
+ * throw new Error('handler real ainda não implementado — use OPUS_MODE=design')
761
+ * },
762
+ * })
763
+ * ```
764
+ */
765
+ mockHandler?: HandlerFn<ParsedIn, Out>
766
+
767
+ // — Comportamento —
768
+ audit?: boolean | AuditConfig
769
+ confirm?: ConfirmSpec
770
+ /** Avaliada no CLIENTE com o input do fio (pré-parse) — por isso `In`. */
771
+ invalidates?: string[] | ((input: In) => string[])
772
+ rateLimit?: RateLimitSpec
773
+ /** Server-side (pós-validação) — recebe o parseado. */
774
+ idempotency?: (input: ParsedIn) => string
775
+
776
+ // — Integração —
777
+ ai?: boolean | AIConfig
778
+ automatable?: boolean
779
+ internal?: boolean
780
+ successStatus?: number
781
+ }
782
+
783
+ export interface SimpleAction<In, Out, ParsedIn = In> extends ActionBase<In, Out, ParsedIn> {
784
+ kind: 'simple'
785
+ background?: BackgroundConfig
786
+ emits?: string[]
787
+ }
788
+
789
+ export interface FormAction<In extends Record<string, unknown>, Out, ParsedIn = In>
790
+ extends ActionBase<In, Out, ParsedIn> {
791
+ kind: 'form'
792
+ // Parcial: `fields` descreve as chaves RENDERÁVEIS do input — nem toda chave precisa
793
+ // de UI (ex.: `id` num update, ou campos derivados no servidor). O que não está aqui
794
+ // não é renderizado (mas segue no input, via defaultValues/submit).
795
+ fields: Partial<Record<keyof In & string, FieldSpec>>
796
+ background?: BackgroundConfig
797
+ emits?: string[]
798
+ }
799
+
800
+ export interface SearchFilters {
801
+ /** Colunas declarativas da listagem (a UI deriva a tabela; ver ListColumnSpec). */
802
+ columns?: ListColumnSpec[]
803
+ filters?: Record<string, FilterSpec>
804
+ sort?: { fields: string[]; default?: SortSpec[] }
805
+ paginate?: 'cursor' | 'offset' | false
806
+ text?: { fields: string[]; placeholder?: I18nRef }
807
+ periods?: PeriodSpec[]
808
+ /**
809
+ * Cláusula DSL aplicada **sempre** pelo repository do consumer (ex:
810
+ * `'NOT archived'`). Runtime NÃO executa SQL — quem consome a search
811
+ * compila via `compileWhere` do `@softize/opus/dsl` no momento de
812
+ * montar a query.
813
+ *
814
+ * Documentação que entra no manifest (próxima fase) e sinaliza intent
815
+ * de baseline imutável de filtragem.
816
+ */
817
+ where?: string
818
+ }
819
+
820
+ /**
821
+ * Em `ListAction`, `output` descreve o **item** (não o envelope).
822
+ * Handler retorna `Paginated<Out>`. Runtime não envelopa nem valida o
823
+ * envelope — handler é fonte de verdade.
824
+ */
825
+ export interface ListAction<In, Out, ParsedIn = In>
826
+ extends Omit<ActionBase<In, Out, ParsedIn>, 'handler' | 'mockHandler'> {
827
+ kind: 'list'
828
+ handler: (
829
+ ctx: ActionContext,
830
+ input: ParsedIn,
831
+ loaded?: Record<string, unknown>,
832
+ ) => Paginated<Out> | Promise<Paginated<Out>>
833
+ /**
834
+ * Mesma semântica de `ActionBase.mockHandler`, mas devolve `Paginated<Out>`
835
+ * (search retorna lista paginada, não item solto).
836
+ */
837
+ mockHandler?: (
838
+ ctx: ActionContext,
839
+ input: ParsedIn,
840
+ loaded?: Record<string, unknown>,
841
+ ) => Paginated<Out> | Promise<Paginated<Out>>
842
+ /** Colunas declarativas da listagem (a UI deriva a tabela; ver ListColumnSpec). */
843
+ columns?: ListColumnSpec[]
844
+ filters?: Record<string, FilterSpec>
845
+ sort?: { fields: string[]; default?: SortSpec[] }
846
+ paginate?: 'cursor' | 'offset' | false
847
+ text?: { fields: string[]; placeholder?: I18nRef }
848
+ periods?: PeriodSpec[]
849
+ /**
850
+ * Ver `SearchFilters.where`. Baseline DSL aplicado pelo repository
851
+ * via `compileWhere` (do `@softize/opus/dsl`). Runtime não executa SQL.
852
+ */
853
+ where?: string
854
+ }
855
+
856
+ export interface ViewAction<In, Out, ParsedIn = In> extends ActionBase<In, Out, ParsedIn> {
857
+ kind: 'view'
858
+ /** Entidades relacionadas projetáveis. View simples não projeta nada — omita. */
859
+ projection?: string[]
860
+ expand?: Record<string, ExpandSpec>
861
+ }
862
+
863
+ /**
864
+ * Union completa de actions. Usada em runtime/adapters como "qualquer action";
865
+ * pra inferência precisa do subtipo, use o tipo concreto (SimpleAction, etc).
866
+ *
867
+ * `any` é proposital aqui — union de tipos genéricos com `unknown` quebra
868
+ * narrowing (porque `unknown` é supertipo, não subtipo). `defineAction` é
869
+ * generic e preserva o tipo exato passado.
870
+ */
871
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
872
+ export type ActionDef =
873
+ | SimpleAction<any, any>
874
+ | FormAction<any, any>
875
+ | ListAction<any, any>
876
+ | ViewAction<any, any>
877
+
878
+ // =============================================================================
879
+ // 11. Reaction (§12)
880
+ // =============================================================================
881
+
882
+ export type ReactionHandlerFn<Event = unknown> = (
883
+ ctx: ReactionContext,
884
+ event: DomainEvent<Event>,
885
+ ) => Promise<void> | void
886
+
887
+ /**
888
+ * Declaração de listener de evento. Auto-registrada pelo `Runtime` no
889
+ * `EventBusAdapter` no `start()`. Ver §12.
890
+ */
891
+ export interface ReactionDef<Event = unknown> {
892
+ name: string
893
+ on: string | string[]
894
+
895
+ description?: string
896
+ tags?: string[]
897
+
898
+ handler: ReactionHandlerFn<Event>
899
+
900
+ retry?: { attempts: number; backoff?: BackoffSpec }
901
+ timeout?: number
902
+ dedup?: (event: DomainEvent<Event>) => string
903
+ concurrency?: number
904
+
905
+ authorize?: (
906
+ ctx: ReactionContext,
907
+ event: DomainEvent<Event>,
908
+ ) => boolean | Promise<boolean>
909
+ }
910
+
911
+ // =============================================================================
912
+ // 12. Adapters (§8)
913
+ // =============================================================================
914
+
915
+ export type AdapterKind =
916
+ | 'server'
917
+ | 'data'
918
+ | 'auth'
919
+ | 'audit'
920
+ | 'logger'
921
+ | 'queue'
922
+ | 'eventbus'
923
+ | 'scheduler'
924
+ | 'client'
925
+ | 'storage'
926
+ | 'ai'
927
+ | 'ui'
928
+ | 'schema'
929
+
930
+ /**
931
+ * Resultado de health check de um adapter (ou do runtime agregado).
932
+ */
933
+ export interface HealthStatus {
934
+ ok: boolean
935
+ details?: Record<string, unknown>
936
+ }
937
+
938
+ /**
939
+ * Interface base de adapter. Todos os adapters implementam `name`/`kind`;
940
+ * `init`/`dispose`/`healthCheck` são opcionais.
941
+ */
942
+ export interface Adapter {
943
+ name: string
944
+ kind: AdapterKind
945
+ init?: (runtime: RuntimeRef) => Promise<void> | void
946
+ dispose?: () => Promise<void> | void
947
+ healthCheck?: () => Promise<HealthStatus>
948
+ }
949
+
950
+ /**
951
+ * Contexto mínimo que o caller (server adapter, etc) entrega ao runtime
952
+ * ao invocar uma action. Auth adapter resolveContext() produz isso.
953
+ */
954
+ export interface ContextBase {
955
+ user: User | null
956
+ tenantId: string | null
957
+ can: CanFn
958
+ /**
959
+ * Provenance do trigger. Server adapter passa `{ kind: 'http', ... }`;
960
+ * schedule fires com `{ kind: 'schedule', ... }`; runtime preenche
961
+ * `{ kind: 'reaction', ... }` ao disparar reactions. Default
962
+ * `{ kind: 'system', source: 'unknown' }` se caller não passa.
963
+ */
964
+ provenance?: Provenance
965
+ meta?: Record<string, unknown>
966
+ requestId?: string
967
+ }
968
+
969
+ /**
970
+ * Referência ao runtime exposta pra adapters durante `init`.
971
+ * Adapters usam pra invocar actions e acessar auth/healthCheck.
972
+ *
973
+ * Mantemos as definições mínimas necessárias aqui (não a classe Runtime
974
+ * inteira) pra evitar dependência circular com `runtime.ts`.
975
+ */
976
+ export interface RuntimeRef {
977
+ readonly config: RuntimeConfig
978
+ readonly log: Logger
979
+ readonly auth: AuthAdapter | undefined
980
+ execute(
981
+ actionName: string,
982
+ input: unknown,
983
+ ctx: ContextBase,
984
+ ): Promise<ActionResult<unknown>>
985
+ healthCheck(): Promise<{
986
+ ok: boolean
987
+ adapters: Record<string, HealthStatus>
988
+ }>
989
+ }
990
+
991
+ // — Endpoint specs (server adapter) ——————————————————————————————————————————
992
+
993
+ export interface LogsEndpointSpec {
994
+ enabled: boolean
995
+ path?: string
996
+ auth: string | string[]
997
+ bufferSize?: number
998
+ source?: (query: LogQuery) => Promise<LogEntry[]>
999
+ }
1000
+
1001
+ export interface AuditEndpointSpec {
1002
+ enabled: boolean
1003
+ path?: string
1004
+ auth: string | string[]
1005
+ source?: (query: AuditQuery) => Promise<AuditRecord[]>
1006
+ }
1007
+
1008
+ export interface EndpointSpec {
1009
+ health?: boolean | { path?: string }
1010
+ ready?: boolean | { path?: string }
1011
+ openapi?: boolean | { path?: string; ui?: boolean }
1012
+ actions?: boolean | { path?: string; auth?: string | string[] }
1013
+ reactions?: boolean | { path?: string; auth?: string | string[] }
1014
+ logs?: boolean | LogsEndpointSpec
1015
+ audit?: boolean | AuditEndpointSpec
1016
+ }
1017
+
1018
+ export interface LogQuery {
1019
+ level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'
1020
+ since?: string
1021
+ until?: string
1022
+ action?: string
1023
+ tenant?: string
1024
+ requestId?: string
1025
+ userId?: string
1026
+ limit?: number
1027
+ cursor?: string
1028
+ }
1029
+
1030
+ export interface LogEntry {
1031
+ timestamp: string
1032
+ level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'
1033
+ msg: string
1034
+ meta?: Record<string, unknown>
1035
+ }
1036
+
1037
+ export interface AuditQuery {
1038
+ action?: string
1039
+ actorId?: string
1040
+ tenant?: string
1041
+ outcome?: 'success' | 'error'
1042
+ since?: string
1043
+ until?: string
1044
+ limit?: number
1045
+ cursor?: string
1046
+ }
1047
+
1048
+ // — Adapter interfaces específicas ————————————————————————————————————————————
1049
+
1050
+ export interface ServerAdapter extends Adapter {
1051
+ kind: 'server'
1052
+ mount(action: ActionDef): void
1053
+ mountEndpoints(spec: EndpointSpec): void
1054
+ }
1055
+
1056
+ export interface DataAdapter extends Adapter {
1057
+ kind: 'data'
1058
+ contextExtension(): { db: unknown }
1059
+ }
1060
+
1061
+ export interface AuthAdapter extends Adapter {
1062
+ kind: 'auth'
1063
+ resolveContext(req: unknown): Promise<{
1064
+ user: User | null
1065
+ tenantId?: string | null
1066
+ can: CanFn
1067
+ }>
1068
+ }
1069
+
1070
+ export interface AuditSink extends Adapter {
1071
+ kind: 'audit'
1072
+ emit(record: AuditRecord): Promise<void> | void
1073
+ }
1074
+
1075
+ export interface LoggerAdapter extends Adapter, Logger {
1076
+ kind: 'logger'
1077
+ }
1078
+
1079
+ export interface QueueAdapter extends Adapter {
1080
+ kind: 'queue'
1081
+ enqueue(spec: JobSpec): Promise<JobHandle>
1082
+ status(jobId: string): Promise<JobHandle | null>
1083
+ cancel(jobId: string): Promise<boolean>
1084
+ subscribe?(jobId: string, listener: (h: JobHandle) => void): Unsubscribe
1085
+ }
1086
+
1087
+ export interface EventBusAdapter extends Adapter {
1088
+ kind: 'eventbus'
1089
+ publish(event: DomainEvent): Promise<void> | void
1090
+ subscribe?(
1091
+ pattern: string,
1092
+ listener: (event: DomainEvent) => void,
1093
+ ): Unsubscribe
1094
+ }
1095
+
1096
+ /** Opções do put de storage. `contentType` viaja pro driver (S3 grava; fs anota). */
1097
+ export interface StoragePutOptions {
1098
+ contentType?: string
1099
+ }
1100
+
1101
+ /** Objeto lido do storage: bytes + o contentType quando o driver o conhece. */
1102
+ export interface StorageObject {
1103
+ data: Uint8Array
1104
+ contentType?: string
1105
+ }
1106
+
1107
+ /**
1108
+ * EXPERIMENTAL — storage de objetos (arquivos): a superfície MÍNIMA que backoffice
1109
+ * precisa (upload, download, remoção, URL de acesso). `key` é caminho lógico
1110
+ * (`clientes/123/contrato.pdf`) — o driver resolve onde mora. Nasceu sem consumidor
1111
+ * interno (jul/2026): a superfície cresce por reincidência, não por especulação
1112
+ * (streams, list e metadata ricos entram quando um caso real os cobrar).
1113
+ */
1114
+ export interface StorageAdapter extends Adapter {
1115
+ kind: 'storage'
1116
+ /** Grava (cria ou sobrescreve) o objeto. */
1117
+ put(key: string, data: Uint8Array, opts?: StoragePutOptions): Promise<void>
1118
+ /** Lê o objeto; null quando não existe. */
1119
+ get(key: string): Promise<StorageObject | null>
1120
+ /** Remove. Idempotente: remover o que não existe não é erro. */
1121
+ delete(key: string): Promise<void>
1122
+ /** URL de acesso (assinada/expirável no S3; `baseUrl` estática no fs). */
1123
+ url(key: string, opts?: { expiresInSeconds?: number }): Promise<string>
1124
+ }
1125
+
1126
+ /** Opções por chamada do `AiAdapter` — tudo tem default do driver. */
1127
+ export interface AiCompleteOptions {
1128
+ /** System prompt da chamada. */
1129
+ system?: string
1130
+ /** Modelo (id do provider); default do driver. */
1131
+ model?: string
1132
+ /** Teto de tokens de saída; default do driver. */
1133
+ maxTokens?: number
1134
+ temperature?: number
1135
+ }
1136
+
1137
+ /**
1138
+ * EXPERIMENTAL — capability de IA generativa: a superfície MÍNIMA que backoffice
1139
+ * precisa (completar texto e extrair dado estruturado). O diferencial do protocolo
1140
+ * é o `extract`: o MESMO `Schema` das actions vira o contrato da resposta do modelo
1141
+ * — o driver força saída estruturada e valida o retorno pelo schema antes de
1142
+ * devolver. Chega nos handlers via `ctx.ai` (null quando não configurado).
1143
+ * Superfície cresce por reincidência (streaming, chat multi-turn, tools livres
1144
+ * entram quando um caso real os cobrar).
1145
+ */
1146
+ export interface AiAdapter extends Adapter {
1147
+ kind: 'ai'
1148
+ /** Prompt de texto → resposta de texto. */
1149
+ complete(prompt: string, opts?: AiCompleteOptions): Promise<string>
1150
+ /** Prompt de texto → objeto VALIDADO pelo schema (saída estruturada). */
1151
+ extract<T>(prompt: string, schema: Schema<T>, opts?: AiCompleteOptions): Promise<T>
1152
+ /**
1153
+ * Loop AGÊNTICO (tool-use multi-turn): o modelo escolhe uma tool → o `execute` injetado
1154
+ * roda a action → o resultado volta pro modelo → repete até ele responder em texto (ou
1155
+ * bater `maxSteps`). `tools` e `execute` vêm de FORA — o driver NÃO conhece o registry
1156
+ * nem o runtime (DI); a cola que os monta a partir das actions `ai:enabled` mora no runtime.
1157
+ * OPCIONAL: driver sem `run` só não oferece o loop (complete/extract seguem).
1158
+ */
1159
+ run?(input: string | AiMessage[], opts: AiRunOptions): Promise<AiRunResult>
1160
+ /**
1161
+ * Variante em STREAMING do `run` — mesmo loop, emitindo `ChatEvent` conforme acontece
1162
+ * (tool em uso, texto incremental, fim). OPCIONAL e aditiva: driver sem `runStream`
1163
+ * segue válido; quem consome decide o modo pelo que o driver oferece.
1164
+ */
1165
+ runStream?(input: string | AiMessage[], opts: AiRunOptions): AsyncIterable<ChatEvent>
1166
+ }
1167
+
1168
+ /** Uma tool exposta ao modelo: nome da action + descrição + JSON Schema do input. */
1169
+ export interface AiTool {
1170
+ name: string
1171
+ description: string
1172
+ inputSchema: unknown
1173
+ }
1174
+
1175
+ /** Turno de conversa (histórico multi-turn do chat). */
1176
+ export interface AiMessage {
1177
+ role: 'user' | 'assistant'
1178
+ content: string
1179
+ }
1180
+
1181
+ /** Uma opção de resposta de uma pergunta de esclarecimento (`ask_user`). */
1182
+ export interface AskOption {
1183
+ label: string
1184
+ description?: string
1185
+ }
1186
+
1187
+ /** Uma pergunta que o agente faz À PESSOA no meio do turno, com opções. Espelha o
1188
+ * AskUserQuestion do Claude Code — mas servida por um round-trip REAL (ver `onAsk`), então
1189
+ * não volta vazia como o embutido faz em modo headless. */
1190
+ export interface AskQuestion {
1191
+ question: string
1192
+ header?: string
1193
+ multiSelect?: boolean
1194
+ options: AskOption[]
1195
+ }
1196
+
1197
+ /** Resposta de UMA pergunta: os rótulos escolhidos + um texto livre opcional. */
1198
+ export interface AskAnswer {
1199
+ header?: string
1200
+ selected: string[]
1201
+ text?: string
1202
+ }
1203
+
1204
+ export interface AiRunOptions extends AiCompleteOptions {
1205
+ /** Tools disponíveis (o runtime deriva das actions `ai:enabled`). */
1206
+ tools: AiTool[]
1207
+ /** Executa a tool escolhida (o runtime liga na action, com o contexto de quem chamou).
1208
+ * Devolve o resultado — ou um objeto de erro — que volta pro modelo. */
1209
+ execute: (name: string, input: unknown) => Promise<unknown> | unknown
1210
+ /** Teto de iterações do loop (default 8). */
1211
+ maxSteps?: number
1212
+ /** Round-trip de "perguntar à pessoa" (elicitação): se presente, o driver INTERCEPTA a tool
1213
+ * reservada `ask_user` (injetada automaticamente) e chama isto, devolvendo as respostas ao
1214
+ * modelo. AUSENTE = sem canal interativo: o driver RECUSA a `ask_user` com um erro acionável
1215
+ * (pergunte em texto e siga) em vez de deixar a chamada morrer no vazio — a pegadinha do
1216
+ * headless. Guarda QUALQUER consumidor por construção. */
1217
+ onAsk?: (questions: AskQuestion[]) => Promise<AskAnswer[]> | AskAnswer[]
1218
+ }
1219
+
1220
+ export interface AiRunResult {
1221
+ /** Resposta final em texto. */
1222
+ text: string
1223
+ /** Iterações que o loop rodou. */
1224
+ steps: number
1225
+ /** Tools chamadas (observability). */
1226
+ calls: Array<{ name: string; input: unknown }>
1227
+ }
1228
+
1229
+ /**
1230
+ * Evento de conversa — o protocolo que o loop agêntico em streaming emite e o `<Chat>`
1231
+ * consome (docs/chat-event-protocol.md). O mínimo que os casos reais cobraram:
1232
+ * texto incremental, tool em uso (dado cru; humanizar é apresentação), artefato
1233
+ * produzido na conversa e fim de turno. O request/response clássico é o caso
1234
+ * degenerado: `Promise<string>` ≡ um `text` + um `done`.
1235
+ */
1236
+ export type ChatEvent =
1237
+ | { type: 'text'; delta: string }
1238
+ | { type: 'tool'; name: string; detail?: string }
1239
+ | { type: 'artifact'; kind: string; ref: string; title?: string }
1240
+ | { type: 'done'; ok: boolean; error?: string }
1241
+
1242
+ /**
1243
+ * Adapter de IA já LIGADO ao runtime — o que chega em `ctx.ai`. O `run` sai com as tools
1244
+ * (actions `ai:enabled`) e o execute-como-usuário PRÉ-injetados; `complete`/`extract`
1245
+ * passam direto pro driver.
1246
+ */
1247
+ export interface BoundAi {
1248
+ complete(prompt: string, opts?: AiCompleteOptions): Promise<string>
1249
+ extract<T>(prompt: string, schema: Schema<T>, opts?: AiCompleteOptions): Promise<T>
1250
+ run(input: string | AiMessage[], opts?: BoundAiRunOptions): Promise<AiRunResult>
1251
+ /** Variante em streaming do `run` (mesmas tools/execute/confirm); exige driver com `runStream`. */
1252
+ runStream(input: string | AiMessage[], opts?: BoundAiRunOptions): AsyncIterable<ChatEvent>
1253
+ }
1254
+
1255
+ export interface BoundAiRunOptions extends AiCompleteOptions {
1256
+ maxSteps?: number
1257
+ /** Aprovação pras actions `destructive`/`requiresConfirmation`. Sem ela, essas actions
1258
+ * são RECUSADAS (o modelo avisa o usuário) — nada destrutivo sem o de-acordo. */
1259
+ confirm?: (call: { name: string; input: unknown }) => boolean | Promise<boolean>
1260
+ /** Elicitação: round-trip de perguntar à pessoa (ver `AiRunOptions.onAsk`). Passa direto
1261
+ * pro driver pelo spread de `runOptions` no bind. */
1262
+ onAsk?: (questions: AskQuestion[]) => Promise<AskAnswer[]> | AskAnswer[]
1263
+ }
1264
+
1265
+ /**
1266
+ * Schedule declarativo. Tempo dispara o trigger; runtime executa a action
1267
+ * referenciada com provenance `{ kind: 'schedule', scheduleName }`.
1268
+ *
1269
+ * Especifica timing via UM destes campos (exclusivo):
1270
+ * - `cron`: expressão cron padrão (5 ou 6 campos)
1271
+ * - `every`: intervalo simples ('1h', '30m', '15s', etc — ISO 8601 duration ou shorthand)
1272
+ *
1273
+ * Action referenciada deve existir no registry. Input dinâmico via função
1274
+ * (chamada toda vez que schedule dispara) ou estático.
1275
+ */
1276
+ export interface ScheduleDef {
1277
+ name: string
1278
+ action: string
1279
+
1280
+ /** Cron expression — ex: '0 9 * * *' (9am daily). */
1281
+ cron?: string
1282
+ /** Shorthand interval — ex: '1h', '30m', '15s'. */
1283
+ every?: string
1284
+ /** Timezone IANA — ex: 'America/Sao_Paulo'. Default: UTC. */
1285
+ timezone?: string
1286
+
1287
+ /** Input passado ao runtime.execute(); estático ou dinâmico. */
1288
+ input?: unknown | (() => unknown | Promise<unknown>)
1289
+
1290
+ /** Habilitação opt-in/out. Default true. */
1291
+ enabled?: boolean
1292
+
1293
+ /** Documentação. */
1294
+ description?: string
1295
+ tags?: string[]
1296
+ }
1297
+
1298
+ /**
1299
+ * Adapter que materializa schedules — registra triggers temporais e chama
1300
+ * o runtime quando o tempo bate.
1301
+ *
1302
+ * Drivers: `@softize/opus/scheduler/node-cron` (in-process), futuros: `/bullmq`
1303
+ * (Redis-backed repeatable jobs), `/temporal`, etc.
1304
+ */
1305
+ export interface SchedulerAdapter extends Adapter {
1306
+ kind: 'scheduler'
1307
+ /**
1308
+ * Registra um schedule e retorna função pra cancelar. Chamado pelo
1309
+ * runtime no `start()` pra cada schedule.
1310
+ *
1311
+ * Quando o tempo bater, o adapter chama `fire()` que internamente
1312
+ * invoca runtime.execute() com provenance schedule.
1313
+ */
1314
+ register(spec: ScheduleDef, fire: () => Promise<void> | void): Unsubscribe
1315
+ }
1316
+
1317
+ export interface ClientAdapter extends Adapter {
1318
+ kind: 'client'
1319
+ run<T>(action: ActionDef, input: unknown): Promise<ActionResult<T>>
1320
+ }
1321
+
1322
+ // =============================================================================
1323
+ // 13. RuntimeConfig (§14)
1324
+ // =============================================================================
1325
+
1326
+ /**
1327
+ * Behavior knobs do runtime. Recebe valores literais — core nunca lê `process.env`.
1328
+ * Defaults aplicados quando campo ausente. Ver §14.
1329
+ */
1330
+ export interface RuntimeConfig {
1331
+ // — Modos de falha —
1332
+ auditMode?: 'lenient' | 'strict'
1333
+ emitMode?: 'lenient' | 'strict'
1334
+
1335
+ // — Ambiente —
1336
+ env?: 'development' | 'production' | 'test'
1337
+ dev?: boolean
1338
+
1339
+ // — Validação —
1340
+ validateOutputInDev?: boolean
1341
+ warnUndeclaredEmits?: boolean
1342
+
1343
+ // — Resiliência —
1344
+ dedupCacheTtl?: number
1345
+ defaultRetry?: { attempts: number; backoff?: BackoffSpec }
1346
+
1347
+ // — i18n —
1348
+ i18n?: {
1349
+ resolver?: (ref: I18nRef, locale?: string) => string
1350
+ defaultLocale?: string
1351
+ }
1352
+
1353
+ // — Limites —
1354
+ maxConcurrentActions?: number
1355
+ maxConcurrentReactions?: number
1356
+ }