@orkastery/cli 0.5.1 → 0.5.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 (260) hide show
  1. package/adapters/claude-code/README.md +158 -0
  2. package/adapters/claude-code/agents/ork-master.md +2 -0
  3. package/adapters/claude-code/commands/master.md +8 -0
  4. package/adapters/claude-code/commands/ork.md +9 -0
  5. package/adapters/claude-code/mods/autoridade.json +56 -0
  6. package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
  7. package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
  8. package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
  9. package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
  10. package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
  11. package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
  12. package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
  13. package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
  14. package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
  15. package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
  16. package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
  17. package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
  18. package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
  19. package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
  20. package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
  21. package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
  22. package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
  23. package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
  24. package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
  25. package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
  26. package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
  27. package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
  28. package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
  29. package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
  30. package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
  31. package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
  32. package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
  33. package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
  34. package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
  35. package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
  36. package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
  37. package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
  38. package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
  39. package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
  40. package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
  41. package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
  42. package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
  43. package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
  44. package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
  45. package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
  46. package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
  47. package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
  48. package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
  49. package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
  50. package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
  51. package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
  52. package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
  53. package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
  54. package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
  55. package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
  56. package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
  57. package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
  58. package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
  59. package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
  60. package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
  61. package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
  62. package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
  63. package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
  64. package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
  65. package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
  66. package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
  67. package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
  68. package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
  69. package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
  70. package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
  71. package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
  72. package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
  73. package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
  74. package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
  75. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
  76. package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
  77. package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
  78. package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
  79. package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
  80. package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
  81. package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
  82. package/adapters/codex/skills/ork/SKILL.md +18 -3
  83. package/adapters/hermes/README.md +1 -0
  84. package/adapters/hermes/bin/ork-network-status.sh +10 -0
  85. package/adapters/hermes/hermes.plugin.json +1 -0
  86. package/adapters/hermes/hitl-ingress/__init__.py +2 -2
  87. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +7 -6
  88. package/adapters/openclaw/README.md +26 -5
  89. package/adapters/openclaw/dist/hitl-ingress.js +2 -2
  90. package/adapters/openclaw/dist/index.js +46 -11
  91. package/adapters/openclaw/openclaw.plugin.json +5 -1
  92. package/adapters/openclaw/package.json +2 -2
  93. package/adapters/openclaw/src/hitl-ingress.ts +2 -2
  94. package/adapters/openclaw/src/index.ts +49 -11
  95. package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
  96. package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
  97. package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
  98. package/assets/docs-en/produto/README.md +14 -0
  99. package/assets/docs-en/produto/_modelo-feature.md +62 -0
  100. package/assets/docs-en/roadmap/README.md +14 -0
  101. package/assets/docs-en/roadmap/_modelo-item.md +75 -0
  102. package/assets/orkmind_bridge.py +117 -9
  103. package/dist/adapters/claude-bg.js +116 -18
  104. package/dist/adapters/codex.js +14 -4
  105. package/dist/agents-md.js +24 -14
  106. package/dist/auditrun.js +22 -3
  107. package/dist/board.js +20 -17
  108. package/dist/branch-de-estado.js +88 -10
  109. package/dist/busca-semantica.js +52 -12
  110. package/dist/canarios-rm057.js +149 -0
  111. package/dist/canarios.js +2 -0
  112. package/dist/catalogo.js +33 -0
  113. package/dist/ci.js +21 -8
  114. package/dist/claims-conferir.js +342 -0
  115. package/dist/claims.js +41 -1
  116. package/dist/company-brain-client.js +36 -1
  117. package/dist/company-brain-context.js +158 -22
  118. package/dist/company-brain-journal.js +2 -1
  119. package/dist/company-brain-migration.js +36 -1
  120. package/dist/conducao-sentinela.js +388 -0
  121. package/dist/conducao-texto.js +12 -18
  122. package/dist/conducao.js +122 -30
  123. package/dist/conhecimento.js +490 -0
  124. package/dist/docs.js +70 -17
  125. package/dist/doctor-pulse-cron.js +349 -0
  126. package/dist/doctor-rede.js +150 -0
  127. package/dist/doctor.js +195 -111
  128. package/dist/entrega-pr.js +42 -29
  129. package/dist/escopo-escrita.js +10 -1
  130. package/dist/esgotamentos.js +181 -0
  131. package/dist/estado-thread.js +23 -1
  132. package/dist/experiencia-instalacao.js +45 -25
  133. package/dist/experiencia.js +12 -2
  134. package/dist/fabrica-batida.js +310 -0
  135. package/dist/fabrica-estado.js +52 -14
  136. package/dist/fabrica-publicar.js +9 -1
  137. package/dist/fechamento.js +20 -66
  138. package/dist/fila-pesada.js +444 -0
  139. package/dist/fix.js +3 -2
  140. package/dist/forja.js +31 -7
  141. package/dist/gates.js +3 -24
  142. package/dist/handoff.js +10 -6
  143. package/dist/hitl-contract.js +59 -4
  144. package/dist/hitl-curto.js +11 -6
  145. package/dist/hitl-gates.js +5 -0
  146. package/dist/hitl-local-receipt.js +2 -0
  147. package/dist/hitl-lote.js +5 -2
  148. package/dist/hitl-resumo.js +38 -3
  149. package/dist/hitl-tempo-parado.js +201 -0
  150. package/dist/hitl-texto-livre.js +5 -5
  151. package/dist/hitl.js +29 -33
  152. package/dist/horario.js +41 -7
  153. package/dist/hosts.js +104 -11
  154. package/dist/impedimento.js +299 -0
  155. package/dist/index.js +961 -542
  156. package/dist/indice-vetorial.js +87 -10
  157. package/dist/indice.js +9 -1
  158. package/dist/init.js +56 -43
  159. package/dist/instabilidade.js +144 -0
  160. package/dist/integracoes-locais.js +4 -0
  161. package/dist/intelligence-graph-cli.js +170 -16
  162. package/dist/intelligence-graph-contexto.js +254 -0
  163. package/dist/intelligence-graph-contract.js +2 -1
  164. package/dist/intelligence-graph-extract-md.js +56 -4
  165. package/dist/intelligence-graph-extract-ts.js +71 -3
  166. package/dist/intelligence-graph-parsers.js +6 -4
  167. package/dist/intelligence-graph-query.js +66 -19
  168. package/dist/leases.js +664 -87
  169. package/dist/ledger-stats.js +15 -1
  170. package/dist/ledger.js +18 -1
  171. package/dist/licoes.js +1 -0
  172. package/dist/liveness.js +6 -3
  173. package/dist/locale-ajuda.js +761 -0
  174. package/dist/locale-gerados.js +160 -0
  175. package/dist/locale-master.js +99 -0
  176. package/dist/locale-onboarding.js +92 -0
  177. package/dist/locale-phase.js +151 -0
  178. package/dist/locale-pulse.js +211 -0
  179. package/dist/locale-retry.js +385 -0
  180. package/dist/locale-roadmap.js +102 -0
  181. package/dist/locale-ship.js +494 -0
  182. package/dist/locale-thread.js +277 -0
  183. package/dist/locale-verify.js +125 -0
  184. package/dist/locale.js +714 -0
  185. package/dist/maestro-runtime.js +4 -1
  186. package/dist/manifest.js +149 -4
  187. package/dist/maquina.js +23 -4
  188. package/dist/master-digest.js +6 -1
  189. package/dist/master-nota.js +18 -11
  190. package/dist/master.js +145 -31
  191. package/dist/mcp-artifacts.js +43 -3
  192. package/dist/mcp-git.js +42 -8
  193. package/dist/mcp-grafo-worker.js +5 -3
  194. package/dist/mcp-grafo.js +55 -6
  195. package/dist/mcp-server.js +22 -3
  196. package/dist/mcp-ship.js +3 -1
  197. package/dist/mcp-verify.js +6 -1
  198. package/dist/memoria.js +48 -6
  199. package/dist/modos.js +3 -6
  200. package/dist/network-roadmap.js +196 -18
  201. package/dist/noctum-arco.js +911 -0
  202. package/dist/noctum-capacidade.js +141 -0
  203. package/dist/noctum-envelope.js +810 -0
  204. package/dist/noctum-execucao.js +304 -0
  205. package/dist/noctum-fila.js +280 -0
  206. package/dist/ocupacao.js +40 -5
  207. package/dist/onboarding.js +40 -21
  208. package/dist/orkmind.js +99 -7
  209. package/dist/orquestracao.js +24 -21
  210. package/dist/parado-no-condutor.js +219 -38
  211. package/dist/phase.js +160 -52
  212. package/dist/policies.js +56 -5
  213. package/dist/portfolio.js +8 -3
  214. package/dist/postura-local.js +234 -0
  215. package/dist/preflight.js +11 -10
  216. package/dist/procedencia.js +225 -0
  217. package/dist/projeto-alvo.js +132 -5
  218. package/dist/prompts.js +40 -1
  219. package/dist/prova-ativacao.js +75 -6
  220. package/dist/pulse-delivery.js +47 -4
  221. package/dist/pulse.js +98 -19
  222. package/dist/ratelimit.js +24 -6
  223. package/dist/recall.js +3 -1
  224. package/dist/redacao-saida.js +63 -0
  225. package/dist/rede-adesao.js +399 -0
  226. package/dist/rede-forja.js +289 -0
  227. package/dist/rede-projetos.js +239 -0
  228. package/dist/rede-status.js +337 -0
  229. package/dist/rede.js +980 -0
  230. package/dist/retry.js +291 -140
  231. package/dist/roadmap-reservas.js +20 -3
  232. package/dist/roadmap-status.js +150 -34
  233. package/dist/runtime-context.js +181 -3
  234. package/dist/runtime-profiles.js +22 -1
  235. package/dist/runtimes.js +2 -1
  236. package/dist/saida-segura.js +58 -0
  237. package/dist/session-events.js +7 -3
  238. package/dist/session-watcher-claude.js +120 -24
  239. package/dist/session-watcher.js +185 -33
  240. package/dist/sessoes-contas.js +187 -0
  241. package/dist/sessoes-inventario.js +6 -18
  242. package/dist/sessoes.js +83 -2
  243. package/dist/ship-publicar.js +800 -0
  244. package/dist/ship.js +157 -88
  245. package/dist/steal.js +77 -0
  246. package/dist/thread-close.js +3 -2
  247. package/dist/thread.js +299 -49
  248. package/dist/util.js +8 -1
  249. package/dist/verify.js +141 -52
  250. package/dist/vida-da-thread.js +254 -0
  251. package/dist/worktree-local.js +165 -0
  252. package/dist/worktree.js +25 -2
  253. package/dist/yaml.js +27 -8
  254. package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
  255. package/package.json +7 -3
  256. package/references/definition-of-done.md +1 -1
  257. package/schemas/code-artifact-graph.v1.schema.json +2 -1
  258. package/skills/README.md +1 -1
  259. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  260. package/skills/phases/master-metrics/SKILL.md +8 -3
@@ -0,0 +1,810 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ID_DO_TRABALHO = exports.ID_DO_ARCO = exports.RecusaDoArco = exports.MOTIVOS_DE_RECUSA = exports.CANAIS_DO_RELATORIO = exports.UNIDADES = exports.EXCLUSOES_FIXAS = exports.PERMISSOES = exports.JANELA_MAXIMA_H = exports.JANELA_MINIMA_H = exports.MAXIMO_DE_SESSOES_DA_MAQUINA = exports.TETO_DOS_PEDIDOS_DO_ARCO = exports.TETO_DO_PEDIDO_DO_TRABALHO = exports.MAXIMO_DE_TRABALHOS = exports.CONTRATO_ENVELOPE_V2 = exports.CONTRATO_ENVELOPE = void 0;
4
+ exports.nomeDaBranch = nomeDaBranch;
5
+ exports.exclusaoDaAcao = exclusaoDaAcao;
6
+ exports.shaDoPedido = shaDoPedido;
7
+ exports.envelopeComTrabalhos = envelopeComTrabalhos;
8
+ exports.validarPedidoDeAbertura = validarPedidoDeAbertura;
9
+ exports.canonico = canonico;
10
+ exports.hashDoEnvelope = hashDoEnvelope;
11
+ exports.recusaDeExclusao = recusaDeExclusao;
12
+ exports.avaliarEfeito = avaliarEfeito;
13
+ exports.avaliarJanela = avaliarJanela;
14
+ exports.resumoDoEnvelope = resumoDoEnvelope;
15
+ /**
16
+ * RM-058, fatia N1: o contrato do envelope de autorização de um arco #Noctum.
17
+ *
18
+ * O envelope é o que o dono autoriza ANTES do arco: a janela, os destinos e o que pode em cada
19
+ * um, o orçamento com unidade, o horário e o canal do relatório da manhã. As exclusões não são
20
+ * dele: são fixas, ficam guardadas no envelope para o registro e nenhum pedido as amplia.
21
+ *
22
+ * Nada aqui inventa valor do dono. Campo ausente vira recusa que lista tudo o que falta, de uma
23
+ * vez, e teto ilimitado é recusado sempre (D3 da ork-rm058n1). O módulo é puro: não lê disco nem
24
+ * relógio. Quem persiste, autentica a origem e confere antes do efeito é `noctum-arco.ts`.
25
+ *
26
+ * Nada aqui liga o modo: `#Noctum` continua fora de `MODOS_LEGADOS`.
27
+ *
28
+ * RM-058, fatia N2 (D1 da ork-rm058n2): a `v2` acrescenta o que a `v1` deixou para uma versão nova, a lista
29
+ * fechada de trabalhos do arco e os limites de concorrência. A `v1` segue valendo como estava, byte a byte, e
30
+ * um arco autorizado nela não despacha nada.
31
+ */
32
+ const node_crypto_1 = require("node:crypto");
33
+ const horario_1 = require("./horario");
34
+ const runtime_profiles_1 = require("./runtime-profiles");
35
+ exports.CONTRATO_ENVELOPE = 'ork.noctum-envelope/v1';
36
+ exports.CONTRATO_ENVELOPE_V2 = 'ork.noctum-envelope/v2';
37
+ /**
38
+ * D1 da ork-rm058n2: a lista do arco é fechada e curta. O dono lê cada pedido inteiro no resumo que autoriza (A7 do
39
+ * CHECK), então o pedido é texto curto: cada um e a soma deles cabem numa leitura.
40
+ */
41
+ exports.MAXIMO_DE_TRABALHOS = 30;
42
+ exports.TETO_DO_PEDIDO_DO_TRABALHO = 4 * 1024;
43
+ exports.TETO_DOS_PEDIDOS_DO_ARCO = 16 * 1024;
44
+ /** Mais sessões que isto numa máquina não limita nada: é a ausência do limite escrita como número. */
45
+ exports.MAXIMO_DE_SESSOES_DA_MAQUINA = 64;
46
+ /** D3 do dono (12/10/2026): a janela do arco fica entre 6 e 12 horas. */
47
+ exports.JANELA_MINIMA_H = 6;
48
+ exports.JANELA_MAXIMA_H = 12;
49
+ /** D1 do dono: push, PR e merge, este só com todas as provas no SHA exato (N5). */
50
+ exports.PERMISSOES = ['push', 'pr', 'merge'];
51
+ /** Nunca entram em envelope nenhum, e por isso não são configuráveis (D6 da ork-rm058n1). */
52
+ exports.EXCLUSOES_FIXAS = ['tag-e-publicacao', 'producao', 'segredos', 'acao-destrutiva', 'repositorio-publico-novo'];
53
+ /**
54
+ * Os nomes com que uma ação excluída pode chegar, num pedido de abertura ou antes de um efeito.
55
+ * Ação fora de `PERMISSOES` que não está aqui também é recusada; este mapa só dá o motivo certo.
56
+ */
57
+ const EXCLUSAO_POR_NOME = Object.freeze({
58
+ 'tag-e-publicacao': 'tag-e-publicacao', tag: 'tag-e-publicacao', publicacao: 'tag-e-publicacao',
59
+ publicar: 'tag-e-publicacao', release: 'tag-e-publicacao', 'publicar-pacote': 'tag-e-publicacao',
60
+ 'publicar-site': 'tag-e-publicacao',
61
+ producao: 'producao', deploy: 'producao', 'deploy-producao': 'producao', 'atualizar-producao': 'producao',
62
+ segredos: 'segredos', segredo: 'segredos', 'ler-segredo': 'segredos', credencial: 'segredos',
63
+ 'acao-destrutiva': 'acao-destrutiva', destrutiva: 'acao-destrutiva', 'force-push': 'acao-destrutiva',
64
+ apagar: 'acao-destrutiva', 'apagar-branch': 'acao-destrutiva', 'reescrever-historico': 'acao-destrutiva',
65
+ 'repositorio-publico-novo': 'repositorio-publico-novo', 'criar-repositorio': 'repositorio-publico-novo',
66
+ 'criar-repositorio-publico': 'repositorio-publico-novo',
67
+ });
68
+ /** Por que cada exclusão fica fora, na frase que a recusa mostra. */
69
+ const RAZAO_DA_EXCLUSAO = Object.freeze({
70
+ 'tag-e-publicacao': 'tag e publicação de versão, pacote ou site nunca entram em envelope',
71
+ producao: 'produção exige autorização explícita própria e fluxo próprio, que não são o envelope',
72
+ segredos: 'acesso ou circulação de segredos nunca entram em envelope',
73
+ 'acao-destrutiva': 'ação destrutiva nunca entra em envelope',
74
+ 'repositorio-publico-novo': 'criar repositório público nunca entra em envelope',
75
+ });
76
+ /** D3 da ork-rm058n1: lista fechada. Assinatura não é uso ilimitado, e unidade sem nome não mede nada. */
77
+ exports.UNIDADES = ['sessoes', 'minutos-de-sessao', 'tokens', 'percentual-da-cota'];
78
+ /** Contadas em inteiros. Acima do inteiro seguro do JavaScript, o teto deixa de limitar e vira ilimitado na prática. */
79
+ const UNIDADES_INTEIRAS = ['sessoes', 'minutos-de-sessao', 'tokens'];
80
+ /** Os destinos do relatório da manhã que a página da RM-058 nomeia. */
81
+ exports.CANAIS_DO_RELATORIO = ['terminal', 'telegram', 'openclaw'];
82
+ /** D2 da ork-rm058n1: tipo fechado próprio, fora de `MotivoGate`, porque a N1 não abre gate. */
83
+ exports.MOTIVOS_DE_RECUSA = [
84
+ 'noctum.envelope.valores-ausentes', 'noctum.envelope.teto-ilimitado', 'noctum.envelope.acao-excluida',
85
+ 'noctum.envelope.invalido', 'noctum.origem.sem-prova', 'noctum.efeito.acao-excluida',
86
+ 'noctum.efeito.sem-autorizacao', 'noctum.efeito.hash-antigo', 'noctum.efeito.revogado',
87
+ 'noctum.efeito.expirado', 'noctum.efeito.fora-da-janela', 'noctum.efeito.fora-do-destino',
88
+ 'noctum.estado.adulterado',
89
+ // RM-058 N2 (D11 e D12 da ork-rm058n2): o despacho fora da lista, conta ou runtime do envelope, e o orçamento.
90
+ 'noctum.despacho.fora-do-envelope', 'noctum.orcamento.esgotado', 'noctum.orcamento.sem-medicao',
91
+ ];
92
+ class RecusaDoArco extends Error {
93
+ motivo;
94
+ constructor(motivo, detalhe) {
95
+ super(`${motivo}: ${detalhe}`);
96
+ this.motivo = motivo;
97
+ this.name = 'RecusaDoArco';
98
+ }
99
+ }
100
+ exports.RecusaDoArco = RecusaDoArco;
101
+ exports.ID_DO_ARCO = /^[a-z0-9][a-z0-9-]{0,62}$/;
102
+ exports.ID_DO_TRABALHO = /^[a-z0-9][a-z0-9-]{0,31}$/;
103
+ const ITEM_DO_ROADMAP = /^RM-\d{3}$/;
104
+ const ID_DE_THREAD = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/;
105
+ const ID_DE_CONTA = /^[a-z0-9][a-z0-9._-]{0,62}$/i;
106
+ const REPOSITORIO = /^[A-Za-z0-9_.-]{1,100}\/[A-Za-z0-9_.-]{1,100}$/;
107
+ const BRANCH = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,199}$/;
108
+ const DATA_HORA = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:\d{2})$/;
109
+ const HORARIO = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
110
+ const PALAVRAS_DE_ILIMITADO = /ilimitad|unlimited|infinit|sem[ -]?(?:teto|limite)/i;
111
+ const objeto = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
112
+ /**
113
+ * O nome curto de uma branch, nas regras de `git check-ref-format --branch`, ou nada. Ref completa
114
+ * (`refs/heads/main`), forma curta de outra ref (`heads/`, `tags/`, `remotes/`) e `HEAD` nunca são
115
+ * destino: com o texto cru, um push em `refs/heads/main` passaria pela recusa do push direto na base
116
+ * e `refs/tags/v1` criaria tag, que é exclusão fixa.
117
+ */
118
+ function nomeDaBranch(v) {
119
+ if (typeof v !== 'string' || !BRANCH.test(v))
120
+ return undefined;
121
+ const minusculo = v.toLowerCase();
122
+ if (/^(?:refs|heads|tags|remotes)\//.test(minusculo) || minusculo === 'head' || v.includes('..') || v.includes('//') ||
123
+ /(?:^|\/)\./.test(v) || /\.lock(?:\/|$)/.test(v) || v.endsWith('/') || v.endsWith('.'))
124
+ return undefined;
125
+ return v;
126
+ }
127
+ const ausente = (v) => v === undefined || v === null || v === '';
128
+ const sha256 = (s) => (0, node_crypto_1.createHash)('sha256').update(s).digest('hex');
129
+ /** A exclusão que um nome de ação representa, quando representa. */
130
+ function exclusaoDaAcao(acao) {
131
+ if (typeof acao !== 'string')
132
+ return undefined;
133
+ const nome = acao.trim().toLowerCase();
134
+ // `constructor` e `__proto__` não são exclusão: a busca é só nas chaves próprias do mapa.
135
+ return Object.hasOwn(EXCLUSAO_POR_NOME, nome) ? EXCLUSAO_POR_NOME[nome] : undefined;
136
+ }
137
+ function chavesConhecidas(v, aceitas, onde, p) {
138
+ for (const k of Object.keys(v)) {
139
+ if (k === 'exclusoes' && onde === 'pedido')
140
+ p.invalidos.push('exclusoes: são fixas do núcleo, o pedido não as declara');
141
+ else if (!aceitas.includes(k))
142
+ p.invalidos.push(`${onde}.${k}: campo desconhecido`);
143
+ }
144
+ }
145
+ function lerTeto(v, onde, p) {
146
+ if (ausente(v)) {
147
+ p.faltas.push(`${onde} (valor e unidade)`);
148
+ return undefined;
149
+ }
150
+ if (!objeto(v)) {
151
+ p.invalidos.push(`${onde}: precisa ser { valor, unidade }`);
152
+ return undefined;
153
+ }
154
+ chavesConhecidas(v, ['valor', 'unidade'], onde, p);
155
+ let valor;
156
+ if (ausente(v.valor))
157
+ p.faltas.push(`${onde}.valor`);
158
+ else if (typeof v.valor === 'number') {
159
+ // Zero e negativo viram "sem limite" em muita configuração; aqui eles não viram nada.
160
+ if (!Number.isFinite(v.valor) || v.valor <= 0)
161
+ p.ilimitados.push(`${onde}.valor (${String(v.valor)})`);
162
+ else
163
+ valor = v.valor;
164
+ }
165
+ else if (typeof v.valor === 'string' && PALAVRAS_DE_ILIMITADO.test(v.valor))
166
+ p.ilimitados.push(`${onde}.valor ("${v.valor}")`);
167
+ else
168
+ p.invalidos.push(`${onde}.valor: precisa ser número finito maior que zero`);
169
+ let unidade;
170
+ if (ausente(v.unidade))
171
+ p.faltas.push(`${onde}.unidade`);
172
+ else if (!exports.UNIDADES.includes(v.unidade))
173
+ p.invalidos.push(`${onde}.unidade: use ${exports.UNIDADES.join(', ')}`);
174
+ else
175
+ unidade = v.unidade;
176
+ if (unidade === 'percentual-da-cota' && valor !== undefined && valor > 100) {
177
+ p.invalidos.push(`${onde}.valor: percentual da cota vai até 100`);
178
+ valor = undefined;
179
+ }
180
+ if (unidade !== undefined && UNIDADES_INTEIRAS.includes(unidade) && valor !== undefined) {
181
+ if (valor > Number.MAX_SAFE_INTEGER)
182
+ p.ilimitados.push(`${onde}.valor (${String(valor)}, acima do inteiro seguro)`);
183
+ else if (!Number.isInteger(valor))
184
+ p.invalidos.push(`${onde}.valor: ${unidade} se conta em inteiros`);
185
+ if (!Number.isSafeInteger(valor))
186
+ valor = undefined;
187
+ }
188
+ return valor !== undefined && unidade !== undefined ? { valor, unidade } : undefined;
189
+ }
190
+ function lerJanela(v, p) {
191
+ if (ausente(v)) {
192
+ p.faltas.push('janela.inicio', 'janela.fim', 'janela.fuso');
193
+ return undefined;
194
+ }
195
+ if (!objeto(v)) {
196
+ p.invalidos.push('janela: precisa ser { inicio, fim, fuso }');
197
+ return undefined;
198
+ }
199
+ chavesConhecidas(v, ['inicio', 'fim', 'fuso'], 'janela', p);
200
+ const instante = (campo) => {
201
+ const x = v[campo];
202
+ if (ausente(x)) {
203
+ p.faltas.push(`janela.${campo}`);
204
+ return undefined;
205
+ }
206
+ if (typeof x !== 'string' || !DATA_HORA.test(x) || !Number.isFinite(Date.parse(x))) {
207
+ p.invalidos.push(`janela.${campo}: data e hora com deslocamento explícito, como 2026-10-12T22:00:00-03:00`);
208
+ return undefined;
209
+ }
210
+ return x;
211
+ };
212
+ const inicio = instante('inicio'), fim = instante('fim');
213
+ let fuso;
214
+ if (ausente(v.fuso))
215
+ p.faltas.push('janela.fuso');
216
+ else if (!(fuso = (0, horario_1.normalizarFuso)(v.fuso)) || !(fuso === 'UTC' || /^[A-Za-z]+(?:\/[A-Za-z0-9_+-]+)+$/.test(fuso))) {
217
+ p.invalidos.push(`janela.fuso: fuso IANA, como ${horario_1.FUSO_DE_BRASILIA}`);
218
+ fuso = undefined;
219
+ }
220
+ if (inicio && fim) {
221
+ const horas = (Date.parse(fim) - Date.parse(inicio)) / 3600000;
222
+ if (!(horas >= exports.JANELA_MINIMA_H && horas <= exports.JANELA_MAXIMA_H)) {
223
+ p.invalidos.push(`janela: o arco dura de ${exports.JANELA_MINIMA_H} a ${exports.JANELA_MAXIMA_H} h (D3 do dono); este pedido dá ${Math.round(horas * 100) / 100} h`);
224
+ }
225
+ }
226
+ return inicio && fim && fuso ? { inicio, fim, fuso } : undefined;
227
+ }
228
+ function lerDestinos(v, p) {
229
+ if (ausente(v) || (Array.isArray(v) && v.length === 0)) {
230
+ p.faltas.push('destinos (ao menos um repositório, a base e as permissões)');
231
+ return undefined;
232
+ }
233
+ if (!Array.isArray(v)) {
234
+ p.invalidos.push('destinos: precisa ser lista');
235
+ return undefined;
236
+ }
237
+ const destinos = [];
238
+ const vistos = new Set();
239
+ v.forEach((d, i) => {
240
+ const onde = `destinos[${i}]`;
241
+ if (!objeto(d)) {
242
+ p.invalidos.push(`${onde}: precisa ser { repositorio, base, permissoes }`);
243
+ return;
244
+ }
245
+ chavesConhecidas(d, ['repositorio', 'base', 'permissoes'], onde, p);
246
+ let ok = true;
247
+ if (ausente(d.repositorio)) {
248
+ p.faltas.push(`${onde}.repositorio`);
249
+ ok = false;
250
+ }
251
+ else if (typeof d.repositorio !== 'string' || !REPOSITORIO.test(d.repositorio) || d.repositorio.includes('..')) {
252
+ p.invalidos.push(`${onde}.repositorio: dono/nome de um repositório que já existe`);
253
+ ok = false;
254
+ }
255
+ else if (vistos.has(d.repositorio.toLowerCase())) {
256
+ p.invalidos.push(`${onde}.repositorio: repetido`);
257
+ ok = false;
258
+ }
259
+ else
260
+ vistos.add(d.repositorio.toLowerCase());
261
+ const base = nomeDaBranch(d.base);
262
+ if (ausente(d.base)) {
263
+ p.faltas.push(`${onde}.base`);
264
+ ok = false;
265
+ }
266
+ else if (base === undefined) {
267
+ p.invalidos.push(`${onde}.base: nome curto de branch, sem refs/, heads/, tags/ ou HEAD`);
268
+ ok = false;
269
+ }
270
+ const permissoes = [];
271
+ if (ausente(d.permissoes) || (Array.isArray(d.permissoes) && d.permissoes.length === 0)) {
272
+ p.faltas.push(`${onde}.permissoes`);
273
+ ok = false;
274
+ }
275
+ else if (!Array.isArray(d.permissoes)) {
276
+ p.invalidos.push(`${onde}.permissoes: precisa ser lista`);
277
+ ok = false;
278
+ }
279
+ else {
280
+ for (const x of d.permissoes) {
281
+ const exclusao = exclusaoDaAcao(x);
282
+ if (exclusao) {
283
+ p.excluidas.push(`${onde}.permissoes: ${String(x)} (${RAZAO_DA_EXCLUSAO[exclusao]})`);
284
+ ok = false;
285
+ }
286
+ else if (!exports.PERMISSOES.includes(x)) {
287
+ p.invalidos.push(`${onde}.permissoes: ${String(x)} fora de ${exports.PERMISSOES.join(', ')}`);
288
+ ok = false;
289
+ }
290
+ else if (!permissoes.includes(x))
291
+ permissoes.push(x);
292
+ }
293
+ }
294
+ if (ok)
295
+ destinos.push({ repositorio: d.repositorio, base: base,
296
+ permissoes: exports.PERMISSOES.filter(x => permissoes.includes(x)) });
297
+ });
298
+ return destinos.length === v.length ? destinos.sort((a, b) => a.repositorio.localeCompare(b.repositorio, 'en')) : undefined;
299
+ }
300
+ function lerSubtetos(v, chave, onde, aceito, explicacao, p) {
301
+ if (ausente(v) || (Array.isArray(v) && v.length === 0)) {
302
+ p.faltas.push(`${onde} (ao menos um subteto por ${chave})`);
303
+ return undefined;
304
+ }
305
+ if (!Array.isArray(v)) {
306
+ p.invalidos.push(`${onde}: precisa ser lista`);
307
+ return undefined;
308
+ }
309
+ const itens = [];
310
+ const vistos = new Set();
311
+ v.forEach((s, i) => {
312
+ const aqui = `${onde}[${i}]`;
313
+ if (!objeto(s)) {
314
+ p.invalidos.push(`${aqui}: precisa ser { ${chave}, teto }`);
315
+ return;
316
+ }
317
+ chavesConhecidas(s, [chave, 'teto'], aqui, p);
318
+ const nome = s[chave];
319
+ let ok = true;
320
+ if (ausente(nome)) {
321
+ p.faltas.push(`${aqui}.${chave}`);
322
+ ok = false;
323
+ }
324
+ else if (typeof nome !== 'string' || !aceito(nome)) {
325
+ p.invalidos.push(`${aqui}.${chave}: ${explicacao}`);
326
+ ok = false;
327
+ }
328
+ else if (vistos.has(nome.toLowerCase())) {
329
+ p.invalidos.push(`${aqui}.${chave}: repetido`);
330
+ ok = false;
331
+ }
332
+ else
333
+ vistos.add(nome.toLowerCase());
334
+ const teto = lerTeto(s.teto, `${aqui}.teto`, p);
335
+ if (ok && teto)
336
+ itens.push({ [chave]: nome, teto });
337
+ });
338
+ return itens.length === v.length ? itens.sort((a, b) => a[chave].localeCompare(b[chave], 'en')) : undefined;
339
+ }
340
+ function lerOrcamento(v, p) {
341
+ if (ausente(v)) {
342
+ p.faltas.push('orcamento.arco (teto do arco: valor e unidade)', 'orcamento.porConta (subteto por conta)', 'orcamento.porRuntime (subteto por runtime)');
343
+ return undefined;
344
+ }
345
+ if (!objeto(v)) {
346
+ p.invalidos.push('orcamento: precisa ser { arco, porConta, porRuntime }');
347
+ return undefined;
348
+ }
349
+ chavesConhecidas(v, ['arco', 'porConta', 'porRuntime'], 'orcamento', p);
350
+ const arco = lerTeto(v.arco, 'orcamento.arco', p);
351
+ const porConta = lerSubtetos(v.porConta, 'conta', 'orcamento.porConta', x => ID_DE_CONTA.test(x), 'id de perfil de conta já autenticado', p);
352
+ const porRuntime = lerSubtetos(v.porRuntime, 'runtime', 'orcamento.porRuntime', x => runtime_profiles_1.RUNTIMES_COM_PERFIL.includes(x), `runtime de assinatura: ${runtime_profiles_1.RUNTIMES_COM_PERFIL.join(', ')}`, p);
353
+ // Subteto acima do teto do arco, na mesma unidade, não limita nada: é teto do arco disfarçado.
354
+ for (const [nome, teto] of [...(porConta ?? []).map(s => [`conta ${s.conta}`, s.teto]),
355
+ ...(porRuntime ?? []).map(s => [`runtime ${s.runtime}`, s.teto])]) {
356
+ if (arco && teto.unidade === arco.unidade && teto.valor > arco.valor) {
357
+ p.invalidos.push(`orcamento: o subteto da ${nome} passa do teto do arco na mesma unidade`);
358
+ }
359
+ }
360
+ return arco && porConta && porRuntime ? { arco, porConta, porRuntime } : undefined;
361
+ }
362
+ function lerRelatorio(v, p) {
363
+ if (ausente(v)) {
364
+ p.faltas.push('relatorio.horario (HH:MM da manhã)', 'relatorio.canal');
365
+ return undefined;
366
+ }
367
+ if (!objeto(v)) {
368
+ p.invalidos.push('relatorio: precisa ser { horario, canal }');
369
+ return undefined;
370
+ }
371
+ chavesConhecidas(v, ['horario', 'canal'], 'relatorio', p);
372
+ let horario, canal;
373
+ if (ausente(v.horario))
374
+ p.faltas.push('relatorio.horario (HH:MM da manhã)');
375
+ else if (typeof v.horario !== 'string' || !HORARIO.test(v.horario))
376
+ p.invalidos.push('relatorio.horario: HH:MM, no fuso da janela');
377
+ else
378
+ horario = v.horario;
379
+ if (ausente(v.canal))
380
+ p.faltas.push('relatorio.canal');
381
+ else if (!exports.CANAIS_DO_RELATORIO.includes(v.canal))
382
+ p.invalidos.push(`relatorio.canal: use ${exports.CANAIS_DO_RELATORIO.join(', ')}`);
383
+ else
384
+ canal = v.canal;
385
+ return horario && canal ? { horario, canal } : undefined;
386
+ }
387
+ /** O sha256 do pedido de um trabalho, o mesmo que a criação da thread guarda (`prepararPedido`). */
388
+ function shaDoPedido(texto) {
389
+ return sha256(texto.trim());
390
+ }
391
+ /**
392
+ * Os caracteres de formato (marcas de direção, largura zero, BOM, hífen suave e as tags do Unicode, que carregam texto
393
+ * que a tela não mostra e o modelo lê) e os de uso privado.
394
+ */
395
+ const FORMATO_OU_PRIVADO = /[\p{Cf}\p{Co}]/u;
396
+ /** Letras e marcas que não desenham nada: os preenchedores do Hangul, o juntador de grafemas e as vogais mudas do khmer. */
397
+ const SEM_DESENHO = new Set([0x115f, 0x1160, 0x3164, 0xffa0, 0x34f, 0x17b4, 0x17b5]);
398
+ const seletorDeVariacao = (cp) => cp >= 0xfe00 && cp <= 0xfe0f;
399
+ /** O que pode levar o seletor de texto ou de emoji: o pictograma e a base da tecla (dígito, `#` e `*`). */
400
+ const BASE_DE_EMOJI = /^[\p{Extended_Pictographic}0-9#*]$/u;
401
+ /**
402
+ * A7 do CHECK da ork-rm058n2, e o resto que a rodada 2 achou: o caractere que esconde ou reordena texto na tela de quem
403
+ * autoriza. Os controles (fora o tab e a quebra de linha), os separadores de linha do Unicode, os de formato e de uso
404
+ * privado, os que não desenham nada, os seletores de variação suplementares e o seletor fora do emoji: só o de texto ou de
405
+ * emoji (U+FE0E, U+FE0F), um só, logo depois de um pictograma ou da base da tecla. Seletores em série carregariam texto
406
+ * escondido, 4 bits por seletor (S2 da rodada 3). Pelo código e pela categoria, sem escapes no fonte.
407
+ */
408
+ function caractereQueEsconde(texto) {
409
+ let anterior = '';
410
+ for (const c of texto) {
411
+ const cp = c.codePointAt(0);
412
+ if ((cp < 0x20 && cp !== 0x09 && cp !== 0x0a) || (cp >= 0x7f && cp <= 0x9f) || cp === 0x2028 || cp === 0x2029 ||
413
+ FORMATO_OU_PRIVADO.test(c) || SEM_DESENHO.has(cp) || (cp >= 0xe0100 && cp <= 0xe01ef) ||
414
+ (seletorDeVariacao(cp) && !((cp === 0xfe0e || cp === 0xfe0f) && BASE_DE_EMOJI.test(anterior))))
415
+ return cp;
416
+ anterior = c;
417
+ }
418
+ return null;
419
+ }
420
+ const codigoDoCaractere = (cp) => `U+${cp.toString(16).toUpperCase().padStart(4, '0')}`;
421
+ /**
422
+ * D1 e D2 da ork-rm058n2: a lista fechada de trabalhos. A ordem da lista é do dono e entra no hash, porque a
423
+ * posição desempata a fila (D3); a ordem dentro de `dependeDe` não muda nada e é normalizada.
424
+ */
425
+ function lerTrabalhos(v, p) {
426
+ if (ausente(v) || (Array.isArray(v) && v.length === 0)) {
427
+ p.faltas.push('trabalhos (a lista fechada do arco: id, item, prioridade, dependeDe e thread ou pedido)');
428
+ return undefined;
429
+ }
430
+ if (!Array.isArray(v)) {
431
+ p.invalidos.push('trabalhos: precisa ser lista');
432
+ return undefined;
433
+ }
434
+ if (v.length > exports.MAXIMO_DE_TRABALHOS) {
435
+ p.invalidos.push(`trabalhos: até ${exports.MAXIMO_DE_TRABALHOS} por arco`);
436
+ return undefined;
437
+ }
438
+ const lidos = [];
439
+ const ids = new Set(), threads = new Set(), pedidos = new Set();
440
+ v.forEach((t, i) => {
441
+ const onde = `trabalhos[${i}]`;
442
+ if (!objeto(t)) {
443
+ p.invalidos.push(`${onde}: precisa ser { id, item, prioridade, dependeDe, thread ou pedido }`);
444
+ return;
445
+ }
446
+ chavesConhecidas(t, ['id', 'item', 'prioridade', 'dependeDe', 'thread', 'pedido'], onde, p);
447
+ let ok = true;
448
+ if (ausente(t.id)) {
449
+ p.faltas.push(`${onde}.id`);
450
+ ok = false;
451
+ }
452
+ else if (typeof t.id !== 'string' || !exports.ID_DO_TRABALHO.test(t.id)) {
453
+ p.invalidos.push(`${onde}.id: letras minúsculas, dígitos e hífen, até 32`);
454
+ ok = false;
455
+ }
456
+ else if (ids.has(t.id)) {
457
+ p.invalidos.push(`${onde}.id: repetido`);
458
+ ok = false;
459
+ }
460
+ else
461
+ ids.add(t.id);
462
+ if (ausente(t.item)) {
463
+ p.faltas.push(`${onde}.item`);
464
+ ok = false;
465
+ }
466
+ else if (typeof t.item !== 'string' || !ITEM_DO_ROADMAP.test(t.item)) {
467
+ p.invalidos.push(`${onde}.item: item do roadmap, como RM-058`);
468
+ ok = false;
469
+ }
470
+ if (ausente(t.prioridade)) {
471
+ p.faltas.push(`${onde}.prioridade`);
472
+ ok = false;
473
+ }
474
+ else if (typeof t.prioridade !== 'number' || !Number.isSafeInteger(t.prioridade) || t.prioridade < 1) {
475
+ p.invalidos.push(`${onde}.prioridade: inteiro a partir de 1 (o 1 vem primeiro)`);
476
+ ok = false;
477
+ }
478
+ // Nem a lista vazia é presumida: o dono diz que o trabalho não depende de nada.
479
+ if (t.dependeDe === undefined || t.dependeDe === null) {
480
+ p.faltas.push(`${onde}.dependeDe (lista, vazia quando não depende de nada)`);
481
+ ok = false;
482
+ }
483
+ else if (!Array.isArray(t.dependeDe) || t.dependeDe.some(d => typeof d !== 'string' || !exports.ID_DO_TRABALHO.test(d))) {
484
+ p.invalidos.push(`${onde}.dependeDe: lista de ids de trabalhos deste arco`);
485
+ ok = false;
486
+ }
487
+ else if (new Set(t.dependeDe).size !== t.dependeDe.length) {
488
+ p.invalidos.push(`${onde}.dependeDe: id repetido`);
489
+ ok = false;
490
+ }
491
+ const comThread = !ausente(t.thread), comPedido = !ausente(t.pedido);
492
+ if (!comThread && !comPedido) {
493
+ p.faltas.push(`${onde}.thread ou ${onde}.pedido`);
494
+ ok = false;
495
+ }
496
+ else if (comThread && comPedido) {
497
+ p.invalidos.push(`${onde}: thread ou pedido, nunca os dois`);
498
+ ok = false;
499
+ }
500
+ else if (comThread) {
501
+ if (typeof t.thread !== 'string' || !ID_DE_THREAD.test(t.thread)) {
502
+ p.invalidos.push(`${onde}.thread: id de uma thread que já existe`);
503
+ ok = false;
504
+ }
505
+ else if (threads.has(t.thread)) {
506
+ p.invalidos.push(`${onde}.thread: a mesma thread em dois trabalhos`);
507
+ ok = false;
508
+ }
509
+ else
510
+ threads.add(t.thread);
511
+ }
512
+ else if (typeof t.pedido !== 'string' || !t.pedido.trim()) {
513
+ p.invalidos.push(`${onde}.pedido: o texto do dono para a thread nova`);
514
+ ok = false;
515
+ }
516
+ else if (Buffer.byteLength(t.pedido.trim(), 'utf8') > exports.TETO_DO_PEDIDO_DO_TRABALHO) {
517
+ p.invalidos.push(`${onde}.pedido: até ${exports.TETO_DO_PEDIDO_DO_TRABALHO} bytes`);
518
+ ok = false;
519
+ }
520
+ else if (caractereQueEsconde(t.pedido) !== null) {
521
+ p.invalidos.push(`${onde}.pedido: o caractere ${codigoDoCaractere(caractereQueEsconde(t.pedido))} esconde ou reordena texto na tela; ` +
522
+ 'o pedido é texto, com tab e quebra de linha');
523
+ ok = false;
524
+ }
525
+ else if (typeof t.item === 'string' && pedidos.has(`${t.item}|${shaDoPedido(t.pedido)}`)) {
526
+ // O vínculo da thread aberta é o item mais o sha256 do pedido: dois trabalhos iguais seriam a mesma thread.
527
+ p.invalidos.push(`${onde}.pedido: o mesmo item e o mesmo pedido em dois trabalhos`);
528
+ ok = false;
529
+ }
530
+ else if (typeof t.item === 'string')
531
+ pedidos.add(`${t.item}|${shaDoPedido(t.pedido)}`);
532
+ if (ok) {
533
+ lidos.push({ id: t.id, item: t.item, prioridade: t.prioridade,
534
+ dependeDe: [...t.dependeDe].sort(),
535
+ ...(comThread ? { thread: t.thread } : { pedido: t.pedido.trim() }) });
536
+ }
537
+ });
538
+ if (lidos.length !== v.length)
539
+ return undefined;
540
+ const somaDosPedidos = lidos.reduce((n, t) => n + (t.pedido ? Buffer.byteLength(t.pedido, 'utf8') : 0), 0);
541
+ if (somaDosPedidos > exports.TETO_DOS_PEDIDOS_DO_ARCO) {
542
+ p.invalidos.push(`trabalhos: os pedidos somam ${somaDosPedidos} bytes; até ${exports.TETO_DOS_PEDIDOS_DO_ARCO} por arco`);
543
+ return undefined;
544
+ }
545
+ for (const t of lidos) {
546
+ for (const d of t.dependeDe) {
547
+ if (d === t.id)
548
+ p.invalidos.push(`trabalho ${t.id}: depende de si mesmo`);
549
+ else if (!ids.has(d))
550
+ p.invalidos.push(`trabalho ${t.id}: depende de ${d}, que não está na lista do arco`);
551
+ }
552
+ }
553
+ // Ciclo: nenhum trabalho do ciclo chegaria a ser entregue, e a fila ficaria bloqueada até o fim da janela.
554
+ const porId = new Map(lidos.map(t => [t.id, t]));
555
+ const estado = new Map();
556
+ const ciclos = new Set();
557
+ const visitar = (id, caminho) => {
558
+ if (estado.get(id) === 'feito')
559
+ return;
560
+ if (estado.get(id) === 'visitando') {
561
+ ciclos.add([...caminho.slice(caminho.indexOf(id)), id].join(' -> '));
562
+ return;
563
+ }
564
+ estado.set(id, 'visitando');
565
+ for (const d of porId.get(id)?.dependeDe ?? [])
566
+ if (porId.has(d) && d !== id)
567
+ visitar(d, [...caminho, id]);
568
+ estado.set(id, 'feito');
569
+ };
570
+ for (const t of lidos)
571
+ visitar(t.id, []);
572
+ for (const c of ciclos)
573
+ p.invalidos.push(`trabalhos: dependência em ciclo (${c})`);
574
+ return lidos;
575
+ }
576
+ const inteiroDeSessoes = (v, minimo) => typeof v === 'number' && Number.isSafeInteger(v) && v >= minimo && v <= exports.MAXIMO_DE_SESSOES_DA_MAQUINA;
577
+ /**
578
+ * D7 da ork-rm058n2: os limites de sessões ao mesmo tempo. Cada conta e cada runtime do orçamento tem o seu, e
579
+ * nenhum outro entra: a conta e o runtime que o arco pode usar são os que o dono pôs no envelope.
580
+ */
581
+ function lerCapacidade(v, orcamento, p) {
582
+ if (ausente(v)) {
583
+ p.faltas.push('capacidade.maquina (sessões ao mesmo tempo nesta máquina)', 'capacidade.porConta (sessões por conta)', 'capacidade.porRuntime (sessões por runtime)', 'capacidade.revisao (slots que só o CHECK usa)');
584
+ return undefined;
585
+ }
586
+ if (!objeto(v)) {
587
+ p.invalidos.push('capacidade: precisa ser { maquina, porConta, porRuntime, revisao }');
588
+ return undefined;
589
+ }
590
+ chavesConhecidas(v, ['maquina', 'porConta', 'porRuntime', 'revisao'], 'capacidade', p);
591
+ let maquina, revisao;
592
+ if (ausente(v.maquina))
593
+ p.faltas.push('capacidade.maquina (sessões ao mesmo tempo nesta máquina)');
594
+ else if (!inteiroDeSessoes(v.maquina, 1))
595
+ p.invalidos.push(`capacidade.maquina: inteiro de 1 a ${exports.MAXIMO_DE_SESSOES_DA_MAQUINA}`);
596
+ else
597
+ maquina = v.maquina;
598
+ if (v.revisao === undefined || v.revisao === null)
599
+ p.faltas.push('capacidade.revisao (slots que só o CHECK usa; 0 é um valor)');
600
+ else if (!inteiroDeSessoes(v.revisao, 0))
601
+ p.invalidos.push('capacidade.revisao: inteiro a partir de 0');
602
+ else if (maquina !== undefined && v.revisao >= maquina) {
603
+ p.invalidos.push('capacidade.revisao: precisa deixar ao menos um slot da máquina para a execução');
604
+ }
605
+ else
606
+ revisao = v.revisao;
607
+ const lerLimites = (bruto, chave, onde, doOrcamento) => {
608
+ if (ausente(bruto) || (Array.isArray(bruto) && bruto.length === 0)) {
609
+ p.faltas.push(`${onde} (sessões por ${chave})`);
610
+ return undefined;
611
+ }
612
+ if (!Array.isArray(bruto)) {
613
+ p.invalidos.push(`${onde}: precisa ser lista`);
614
+ return undefined;
615
+ }
616
+ const itens = [];
617
+ const vistos = new Set();
618
+ bruto.forEach((s, i) => {
619
+ const aqui = `${onde}[${i}]`;
620
+ if (!objeto(s)) {
621
+ p.invalidos.push(`${aqui}: precisa ser { ${chave}, sessoes }`);
622
+ return;
623
+ }
624
+ chavesConhecidas(s, [chave, 'sessoes'], aqui, p);
625
+ const nome = s[chave];
626
+ let ok = true;
627
+ if (ausente(nome)) {
628
+ p.faltas.push(`${aqui}.${chave}`);
629
+ ok = false;
630
+ }
631
+ else if (typeof nome !== 'string') {
632
+ p.invalidos.push(`${aqui}.${chave}: texto`);
633
+ ok = false;
634
+ }
635
+ else if (vistos.has(nome.toLowerCase())) {
636
+ p.invalidos.push(`${aqui}.${chave}: repetido`);
637
+ ok = false;
638
+ }
639
+ else if (doOrcamento && !doOrcamento.includes(nome)) {
640
+ p.invalidos.push(`${aqui}.${chave}: ${nome} não está no orçamento, e só entra aqui quem tem teto`);
641
+ ok = false;
642
+ }
643
+ else
644
+ vistos.add(nome.toLowerCase());
645
+ if (ausente(s.sessoes)) {
646
+ p.faltas.push(`${aqui}.sessoes`);
647
+ ok = false;
648
+ }
649
+ else if (!inteiroDeSessoes(s.sessoes, 1)) {
650
+ p.invalidos.push(`${aqui}.sessoes: inteiro de 1 a ${exports.MAXIMO_DE_SESSOES_DA_MAQUINA}`);
651
+ ok = false;
652
+ }
653
+ else if (maquina !== undefined && s.sessoes > maquina) {
654
+ // Limite acima da máquina não limita nada: é o limite da máquina disfarçado, como o subteto acima do teto.
655
+ p.invalidos.push(`${aqui}.sessoes: passa do limite da máquina`);
656
+ ok = false;
657
+ }
658
+ if (ok)
659
+ itens.push({ [chave]: nome, sessoes: s.sessoes });
660
+ });
661
+ for (const falta of (doOrcamento ?? []).filter(n => !vistos.has(n.toLowerCase())))
662
+ p.faltas.push(`${onde} (${falta}, que está no orçamento)`);
663
+ return itens.length === bruto.length ? itens.sort((a, b) => a[chave].localeCompare(b[chave], 'en')) : undefined;
664
+ };
665
+ const porConta = lerLimites(v.porConta, 'conta', 'capacidade.porConta', orcamento?.porConta.map(s => s.conta));
666
+ const porRuntime = lerLimites(v.porRuntime, 'runtime', 'capacidade.porRuntime', orcamento?.porRuntime.map(s => s.runtime));
667
+ return maquina !== undefined && revisao !== undefined && porConta && porRuntime ? { maquina, porConta, porRuntime, revisao } : undefined;
668
+ }
669
+ /** O conteúdo traz a lista e os limites da `v2`? Só ele despacha (D1 da ork-rm058n2). */
670
+ function envelopeComTrabalhos(c) {
671
+ return c.contrato === exports.CONTRATO_ENVELOPE_V2 && Array.isArray(c.trabalhos) && !!c.capacidade;
672
+ }
673
+ /**
674
+ * Valida o pedido de abertura e devolve o conteúdo normalizado que o dono autoriza.
675
+ *
676
+ * Junta todos os problemas antes de recusar, para o erro dizer de uma vez o que falta. O motivo
677
+ * tipado segue a gravidade: ação excluída, depois teto ilimitado, depois valor ausente, depois
678
+ * forma inválida. A mensagem leva todos, não só o do motivo.
679
+ */
680
+ function validarPedidoDeAbertura(v) {
681
+ const p = { faltas: [], ilimitados: [], excluidas: [], invalidos: [] };
682
+ if (!objeto(v))
683
+ throw new RecusaDoArco('noctum.envelope.invalido', 'o pedido de abertura precisa ser um objeto');
684
+ // Na `v1`, `trabalhos` e `capacidade` continuam campo desconhecido, como antes da N2.
685
+ const v2 = v.contrato === exports.CONTRATO_ENVELOPE_V2;
686
+ chavesConhecidas(v, ['contrato', 'arco', 'janela', 'destinos', 'orcamento', 'relatorio', ...(v2 ? ['trabalhos', 'capacidade'] : [])], 'pedido', p);
687
+ if (v.contrato !== exports.CONTRATO_ENVELOPE && !v2)
688
+ p.invalidos.push(`contrato: precisa ser ${exports.CONTRATO_ENVELOPE} ou ${exports.CONTRATO_ENVELOPE_V2}`);
689
+ let arco;
690
+ if (ausente(v.arco))
691
+ p.faltas.push('arco (identificador do arco)');
692
+ else if (typeof v.arco !== 'string' || !exports.ID_DO_ARCO.test(v.arco))
693
+ p.invalidos.push('arco: letras minúsculas, dígitos e hífen, até 63');
694
+ else
695
+ arco = v.arco;
696
+ const janela = lerJanela(v.janela, p);
697
+ const destinos = lerDestinos(v.destinos, p);
698
+ const orcamento = lerOrcamento(v.orcamento, p);
699
+ const relatorio = lerRelatorio(v.relatorio, p);
700
+ const trabalhos = v2 ? lerTrabalhos(v.trabalhos, p) : undefined;
701
+ const capacidade = v2 ? lerCapacidade(v.capacidade, orcamento, p) : undefined;
702
+ const partes = [
703
+ p.excluidas.length ? `ação excluída: ${p.excluidas.join('; ')}` : '',
704
+ p.ilimitados.length ? `teto ilimitado não é aceito: ${p.ilimitados.join('; ')}` : '',
705
+ p.faltas.length ? `o pedido precisa trazer, do dono: ${p.faltas.join('; ')}` : '',
706
+ p.invalidos.length ? `inválido: ${p.invalidos.join('; ')}` : '',
707
+ ].filter(Boolean);
708
+ if (partes.length) {
709
+ const motivo = p.excluidas.length ? 'noctum.envelope.acao-excluida'
710
+ : p.ilimitados.length ? 'noctum.envelope.teto-ilimitado'
711
+ : p.faltas.length ? 'noctum.envelope.valores-ausentes' : 'noctum.envelope.invalido';
712
+ throw new RecusaDoArco(motivo, `${partes.join(' | ')}. Nenhum valor do dono é presumido.`);
713
+ }
714
+ return { contrato: v2 ? exports.CONTRATO_ENVELOPE_V2 : exports.CONTRATO_ENVELOPE, arco: arco, janela: janela, destinos: destinos,
715
+ orcamento: orcamento, relatorio: relatorio, exclusoes: [...exports.EXCLUSOES_FIXAS],
716
+ ...(v2 ? { trabalhos: trabalhos, capacidade: capacidade } : {}) };
717
+ }
718
+ /** JSON com as chaves em ordem: o mesmo conteúdo dá o mesmo hash, qualquer que seja a ordem de entrada. */
719
+ function canonico(v) {
720
+ const ordenar = (x) => Array.isArray(x) ? x.map(ordenar)
721
+ : objeto(x) ? Object.fromEntries(Object.keys(x).sort().map(k => [k, ordenar(x[k])])) : x;
722
+ return JSON.stringify(ordenar(v));
723
+ }
724
+ function hashDoEnvelope(conteudo) {
725
+ return sha256(canonico(conteudo));
726
+ }
727
+ /** A recusa de uma ação excluída, antes de olhar estado nenhum. */
728
+ function recusaDeExclusao(acao) {
729
+ const exclusao = exclusaoDaAcao(acao);
730
+ return exclusao ? new RecusaDoArco('noctum.efeito.acao-excluida', `${String(acao)}: ${RAZAO_DA_EXCLUSAO[exclusao]}`) : undefined;
731
+ }
732
+ /**
733
+ * Janela e destino de um efeito contra o conteúdo autorizado, no instante dado.
734
+ *
735
+ * Lança a recusa tipada; não devolve nada quando o efeito cabe. O `merge` que passa aqui ainda
736
+ * depende das provas no SHA exato, que são da N5: este contrato é necessário, não suficiente.
737
+ */
738
+ function avaliarEfeito(conteudo, efeito, quando) {
739
+ const excluida = recusaDeExclusao(efeito.acao);
740
+ if (excluida)
741
+ throw excluida;
742
+ avaliarJanela(conteudo, quando);
743
+ if (!exports.PERMISSOES.includes(efeito.acao)) {
744
+ throw new RecusaDoArco('noctum.efeito.fora-do-destino', `ação ${String(efeito.acao)} fora de ${exports.PERMISSOES.join(', ')}`);
745
+ }
746
+ const destino = conteudo.destinos.find(d => d.repositorio.toLowerCase() === String(efeito.repositorio).toLowerCase());
747
+ if (!destino)
748
+ throw new RecusaDoArco('noctum.efeito.fora-do-destino', `repositório ${String(efeito.repositorio)} fora do envelope`);
749
+ if (!destino.permissoes.includes(efeito.acao)) {
750
+ throw new RecusaDoArco('noctum.efeito.fora-do-destino', `${efeito.acao} não autorizado em ${destino.repositorio}`);
751
+ }
752
+ const branch = nomeDaBranch(efeito.branch);
753
+ if (branch === undefined) {
754
+ throw new RecusaDoArco('noctum.efeito.fora-do-destino', 'branch inválida: nome curto de branch, nunca ref completa, tag ou HEAD');
755
+ }
756
+ // Sem caixa: `MAIN` e `main` colidem em sistema de arquivos sem caixa, e o push direto na base fica fora de qualquer forma.
757
+ if (efeito.acao === 'push' && branch.toLowerCase() === destino.base.toLowerCase()) {
758
+ throw new RecusaDoArco('noctum.efeito.fora-do-destino', `push direto na base ${destino.base} fica fora do envelope`);
759
+ }
760
+ if (efeito.acao !== 'push' && branch !== destino.base) {
761
+ throw new RecusaDoArco('noctum.efeito.fora-do-destino', `${efeito.acao} só para a base ${destino.base}`);
762
+ }
763
+ }
764
+ /** Só a janela: o instante precisa estar entre o início e o fim, e nada renova o fim. Lança a recusa tipada. */
765
+ function avaliarJanela(conteudo, quando) {
766
+ const agora = Date.parse(quando);
767
+ if (!Number.isFinite(agora))
768
+ throw new RecusaDoArco('noctum.efeito.fora-da-janela', 'instante da conferência inválido');
769
+ if (agora < Date.parse(conteudo.janela.inicio)) {
770
+ throw new RecusaDoArco('noctum.efeito.fora-da-janela', `a janela só começa em ${conteudo.janela.inicio}`);
771
+ }
772
+ if (agora >= Date.parse(conteudo.janela.fim)) {
773
+ throw new RecusaDoArco('noctum.efeito.expirado', `a janela terminou em ${conteudo.janela.fim}; nada a renova`);
774
+ }
775
+ }
776
+ /** O que o dono lê antes de autorizar: uma linha por item, com o hash inteiro no fim. */
777
+ function resumoDoEnvelope(conteudo, envelopeSha256) {
778
+ const teto = (t) => `${t.valor} ${t.unidade}`;
779
+ return [
780
+ `Arco ${conteudo.arco}: de ${conteudo.janela.inicio} a ${conteudo.janela.fim} (${conteudo.janela.fuso})`,
781
+ ...conteudo.destinos.map(d => `Destino ${d.repositorio}, base ${d.base}: ${d.permissoes.join(', ')}`),
782
+ `Teto do arco: ${teto(conteudo.orcamento.arco)}`,
783
+ ...conteudo.orcamento.porConta.map(s => `Subteto da conta ${s.conta}: ${teto(s.teto)}`),
784
+ ...conteudo.orcamento.porRuntime.map(s => `Subteto do runtime ${s.runtime}: ${teto(s.teto)}`),
785
+ `Relatório da manhã: ${conteudo.relatorio.horario} (${conteudo.janela.fuso}) por ${conteudo.relatorio.canal}`,
786
+ ...(envelopeComTrabalhos(conteudo) ? linhasDaV2(conteudo) : []),
787
+ `Nunca entram: ${conteudo.exclusoes.join(', ')}`,
788
+ `Hash do envelope: ${envelopeSha256}`,
789
+ ];
790
+ }
791
+ /**
792
+ * O que a `v2` acrescenta ao resumo: cada trabalho, na ordem do dono, e os limites. O pedido vai inteiro, uma linha do
793
+ * resumo por linha dele (A7 do CHECK): o dono autoriza só o que leu.
794
+ */
795
+ function linhasDaV2(c) {
796
+ return [
797
+ ...c.trabalhos.flatMap(t => {
798
+ const cabeca = `Trabalho ${t.id} (${t.item}), prioridade ${t.prioridade}` +
799
+ `${t.dependeDe.length ? `, depois de ${t.dependeDe.join(', ')}` : ''}: `;
800
+ if (t.thread)
801
+ return [`${cabeca}thread ${t.thread}`];
802
+ const linhas = t.pedido.split('\n');
803
+ return [`${cabeca}abre uma thread com o pedido abaixo (${linhas.length} linha(s), sha256 ${shaDoPedido(t.pedido).slice(0, 12)})`,
804
+ ...linhas.map(l => ` | ${l}`)];
805
+ }),
806
+ `Sessões ao mesmo tempo: ${c.capacidade.maquina} na máquina, ${c.capacidade.revisao} delas só para revisão`,
807
+ ...c.capacidade.porConta.map(s => `Sessões ao mesmo tempo na conta ${s.conta}: ${s.sessoes}`),
808
+ ...c.capacidade.porRuntime.map(s => `Sessões ao mesmo tempo no runtime ${s.runtime}: ${s.sessoes}`),
809
+ ];
810
+ }