@orkastery/cli 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (502) hide show
  1. package/adapters/README.md +18 -0
  2. package/adapters/claude-code/.claude-plugin/plugin.json +1 -0
  3. package/adapters/claude-code/README.md +195 -5
  4. package/adapters/claude-code/agents/ork-check.md +1 -0
  5. package/adapters/claude-code/agents/ork-go.md +1 -0
  6. package/adapters/claude-code/agents/ork-goal.md +1 -0
  7. package/adapters/claude-code/agents/ork-master.md +1 -0
  8. package/adapters/claude-code/agents/ork-plan.md +1 -0
  9. package/adapters/claude-code/agents/ork-ship.md +1 -0
  10. package/adapters/claude-code/hooks/hooks.json +2 -1
  11. package/adapters/claude-code/hooks/ork-guard.js +15 -1
  12. package/adapters/claude-code/hooks/ork-resposta.js +91 -0
  13. package/adapters/claude-code/hooks/ork-sensor.js +18 -2
  14. package/adapters/claude-code/hooks/quota-signal.js +14 -0
  15. package/adapters/claude-code/mods/autoridade.json +56 -0
  16. package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
  17. package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
  18. package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
  19. package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
  20. package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
  21. package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
  22. package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
  23. package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
  24. package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
  25. package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
  26. package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
  27. package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
  28. package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
  29. package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
  30. package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
  31. package/adapters/claude-code/mods/ork-mensageiro/.claude-plugin/plugin.json +14 -0
  32. package/adapters/claude-code/mods/ork-mensageiro/hooks/caixa.ts +215 -0
  33. package/adapters/claude-code/mods/ork-mensageiro/hooks/hooks.json +3 -0
  34. package/adapters/claude-code/mods/ork-mensageiro/hooks/painel.tsx +165 -0
  35. package/adapters/claude-code/mods/ork-mensageiro/hooks/recepcao.ts +256 -0
  36. package/adapters/claude-code/mods/ork-mensageiro/hooks/register.tsx +481 -0
  37. package/adapters/claude-code/mods/ork-mensageiro/orkastery-mod.json +5 -0
  38. package/adapters/claude-code/mods/ork-mensageiro/types/index.d.ts +85 -0
  39. package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
  40. package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
  41. package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
  42. package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
  43. package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
  44. package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
  45. package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
  46. package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
  47. package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
  48. package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
  49. package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
  50. package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +121 -0
  51. package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
  52. package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
  53. package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
  54. package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
  55. package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
  56. package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
  57. package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
  58. package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
  59. package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
  60. package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
  61. package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
  62. package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
  63. package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
  64. package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
  65. package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
  66. package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
  67. package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
  68. package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
  69. package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
  70. package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
  71. package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
  72. package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
  73. package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
  74. package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
  75. package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
  76. package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
  77. package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
  78. package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
  79. package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
  80. package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
  81. package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
  82. package/adapters/claude-code/mods/ork-ritual/.claude-plugin/plugin.json +14 -0
  83. package/adapters/claude-code/mods/ork-ritual/hooks/hooks.json +3 -0
  84. package/adapters/claude-code/mods/ork-ritual/hooks/nota.ts +88 -0
  85. package/adapters/claude-code/mods/ork-ritual/hooks/register.tsx +494 -0
  86. package/adapters/claude-code/mods/ork-ritual/hooks/replay.tsx +271 -0
  87. package/adapters/claude-code/mods/ork-ritual/hooks/ritual.ts +218 -0
  88. package/adapters/claude-code/mods/ork-ritual/orkastery-mod.json +5 -0
  89. package/adapters/claude-code/mods/ork-ritual/types/index.d.ts +106 -0
  90. package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
  91. package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
  92. package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
  93. package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
  94. package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
  95. package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
  96. package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
  97. package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
  98. package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
  99. package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
  100. package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
  101. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
  102. package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
  103. package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
  104. package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
  105. package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
  106. package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
  107. package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
  108. package/adapters/codex/skills/ork/SKILL.md +2 -0
  109. package/adapters/hermes/README.md +64 -1
  110. package/adapters/hermes/bin/ork-conhecimento-colecoes.sh +14 -0
  111. package/adapters/hermes/bin/ork-conhecimento-listar.sh +15 -0
  112. package/adapters/hermes/bin/ork-conhecimento-mostrar.sh +16 -0
  113. package/adapters/hermes/bin/ork-conhecimento-verificar.sh +15 -0
  114. package/adapters/hermes/bin/ork-vigia-batida.py +127 -0
  115. package/adapters/hermes/hermes.plugin.json +4 -0
  116. package/adapters/hermes/hitl-ingress/__init__.py +101 -8
  117. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +3 -3
  118. package/adapters/openclaw/README.md +83 -10
  119. package/adapters/openclaw/bin/ork-vigia-batida.js +86 -0
  120. package/adapters/openclaw/dist/hitl-ingress.js +106 -9
  121. package/adapters/openclaw/dist/index.js +86 -0
  122. package/adapters/openclaw/openclaw.plugin.json +34 -4
  123. package/adapters/openclaw/package.json +2 -2
  124. package/adapters/openclaw/src/hitl-ingress.ts +103 -9
  125. package/adapters/openclaw/src/index.ts +94 -0
  126. package/assets/adapters-en/claude-code/README.md +403 -0
  127. package/assets/adapters-en/claude-code/agents/ork-check.md +33 -0
  128. package/assets/adapters-en/claude-code/agents/ork-go.md +33 -0
  129. package/assets/adapters-en/claude-code/agents/ork-goal.md +34 -0
  130. package/assets/adapters-en/claude-code/agents/ork-master.md +35 -0
  131. package/assets/adapters-en/claude-code/agents/ork-plan.md +33 -0
  132. package/assets/adapters-en/claude-code/agents/ork-ship.md +33 -0
  133. package/assets/adapters-en/claude-code/commands/check.md +46 -0
  134. package/assets/adapters-en/claude-code/commands/go.md +46 -0
  135. package/assets/adapters-en/claude-code/commands/goal.md +55 -0
  136. package/assets/adapters-en/claude-code/commands/master.md +54 -0
  137. package/assets/adapters-en/claude-code/commands/onboarding.md +22 -0
  138. package/assets/adapters-en/claude-code/commands/ork.md +126 -0
  139. package/assets/adapters-en/claude-code/commands/plan.md +47 -0
  140. package/assets/adapters-en/claude-code/commands/ship.md +45 -0
  141. package/assets/adapters-en/codex/skills/ork/SKILL.md +85 -0
  142. package/assets/adapters-en/hermes/README.md +273 -0
  143. package/assets/adapters-en/hermes/skills/orkastery-devmaster/SKILL.md +169 -0
  144. package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
  145. package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
  146. package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
  147. package/assets/docs-en/produto/README.md +14 -0
  148. package/assets/docs-en/produto/_modelo-feature.md +62 -0
  149. package/assets/docs-en/roadmap/README.md +14 -0
  150. package/assets/docs-en/roadmap/_modelo-item.md +75 -0
  151. package/assets/references-en/README.md +25 -0
  152. package/assets/references-en/code-review-axes.md +82 -0
  153. package/assets/references-en/definition-of-done.md +74 -0
  154. package/assets/references-en/performance-checklist.md +53 -0
  155. package/assets/references-en/security-checklist.md +101 -0
  156. package/assets/references-en/testing-patterns.md +51 -0
  157. package/assets/security-scan/defaults/.golangci.yml +2 -0
  158. package/assets/security-scan/defaults/eslint.config.mjs +11 -0
  159. package/assets/security-scan/defaults/gitleaks.toml +3 -0
  160. package/assets/security-scan/defaults/ruff.toml +1 -0
  161. package/assets/security-scan/tools-agil.json +87 -0
  162. package/assets/security-scan/tools.json +625 -0
  163. package/assets/skills-en/core/onboarding/SKILL.md +58 -0
  164. package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +119 -0
  165. package/assets/skills-en/core/thread-state/SKILL.md +88 -0
  166. package/assets/skills-en/governance/decision-triage/SKILL.md +65 -0
  167. package/assets/skills-en/governance/narrative-guardian/SKILL.md +66 -0
  168. package/assets/skills-en/governance/roadmap-keeper/SKILL.md +60 -0
  169. package/assets/skills-en/governance/scope-check-capability-map/SKILL.md +71 -0
  170. package/assets/skills-en/observability/thread-tracing/SKILL.md +63 -0
  171. package/assets/skills-en/phases/check-quality/SKILL.md +103 -0
  172. package/assets/skills-en/phases/go-implementation/SKILL.md +78 -0
  173. package/assets/skills-en/phases/goal-definition/SKILL.md +69 -0
  174. package/assets/skills-en/phases/master-metrics/SKILL.md +72 -0
  175. package/assets/skills-en/phases/plan-specification/SKILL.md +67 -0
  176. package/assets/skills-en/phases/security-remediation/SKILL.md +70 -0
  177. package/assets/skills-en/phases/ship-release/SKILL.md +71 -0
  178. package/assets/skills-en/reviewers/code-reviewer/SKILL.md +61 -0
  179. package/assets/skills-en/reviewers/security-auditor/SKILL.md +93 -0
  180. package/assets/skills-en/reviewers/test-engineer/SKILL.md +66 -0
  181. package/assets/skills-en/reviewers/web-performance-auditor/SKILL.md +63 -0
  182. package/dist/adapters/claude-bg.js +193 -15
  183. package/dist/adapters/codex-controller-worker.js +12 -5
  184. package/dist/adapters/codex-events.js +16 -4
  185. package/dist/adapters/codex.js +13 -2
  186. package/dist/agents-md.js +24 -15
  187. package/dist/atualizar-branch.js +245 -0
  188. package/dist/auditoria.js +13 -11
  189. package/dist/auditrun.js +68 -48
  190. package/dist/autorizacao-duravel.js +327 -0
  191. package/dist/board.js +98 -43
  192. package/dist/branch-de-estado.js +39 -9
  193. package/dist/busca-semantica.js +7 -5
  194. package/dist/catalogo.js +35 -1
  195. package/dist/changelog-reconciliacao.js +290 -0
  196. package/dist/check-agil.js +500 -0
  197. package/dist/check-modo.js +41 -0
  198. package/dist/check-paralelo.js +183 -0
  199. package/dist/ci.js +255 -46
  200. package/dist/ciclos.js +8 -6
  201. package/dist/claim-lint.js +12 -8
  202. package/dist/claims-conferir.js +350 -0
  203. package/dist/claims.js +13 -10
  204. package/dist/company-brain-client.js +45 -3
  205. package/dist/company-brain-journal.js +2 -1
  206. package/dist/company-brain-migration.js +47 -6
  207. package/dist/conducao-batida.js +759 -0
  208. package/dist/conducao-sentinela.js +405 -0
  209. package/dist/conducao-texto.js +12 -18
  210. package/dist/conducao.js +206 -59
  211. package/dist/conhecimento-consulta.js +270 -0
  212. package/dist/conhecimento.js +514 -0
  213. package/dist/conta-esgotada.js +127 -0
  214. package/dist/dado-privado.js +816 -0
  215. package/dist/decisao-autonoma.js +5 -4
  216. package/dist/demo.js +14 -12
  217. package/dist/dependencias-da-worktree.js +25 -0
  218. package/dist/divida.js +38 -36
  219. package/dist/docs.js +103 -82
  220. package/dist/doctor-forja.js +345 -0
  221. package/dist/doctor-pulse-cron.js +294 -27
  222. package/dist/doctor-rede.js +18 -17
  223. package/dist/doctor.js +278 -116
  224. package/dist/dsn-da-memoria.js +417 -0
  225. package/dist/entrega-por-mod.js +503 -0
  226. package/dist/entrega-pr.js +158 -57
  227. package/dist/escopo-escrita.js +10 -1
  228. package/dist/esgotamentos.js +13 -11
  229. package/dist/estado-thread.js +42 -17
  230. package/dist/evalrunner.js +35 -32
  231. package/dist/experiencia-instalacao.js +45 -25
  232. package/dist/fabrica-batida.js +323 -0
  233. package/dist/fabrica-estado.js +49 -16
  234. package/dist/fabrica-publicar.js +3 -1
  235. package/dist/fila-pesada.js +444 -0
  236. package/dist/fix.js +24 -21
  237. package/dist/forja/adaptador.js +6 -0
  238. package/dist/forja/ambiente-de-ci.js +27 -0
  239. package/dist/forja/api.js +256 -0
  240. package/dist/forja/cache.js +89 -0
  241. package/dist/forja/cli.js +60 -0
  242. package/dist/forja/contrato.js +9 -0
  243. package/dist/forja/endereco.js +43 -0
  244. package/dist/forja/estado.js +22 -0
  245. package/dist/forja/executor.js +73 -0
  246. package/dist/forja/github/ci.js +95 -0
  247. package/dist/forja/github/dono-da-rede.js +18 -0
  248. package/dist/forja/github/metadados.js +15 -0
  249. package/dist/forja/github/pedidos.js +200 -0
  250. package/dist/forja/gitlab/ci.js +141 -0
  251. package/dist/forja/gitlab/consultas.js +59 -0
  252. package/dist/forja/gitlab/dono-da-rede.js +43 -0
  253. package/dist/forja/gitlab/metadados.js +15 -0
  254. package/dist/forja/gitlab/pedidos-abertos.js +158 -0
  255. package/dist/forja/gitlab/pedidos.js +141 -0
  256. package/dist/forja/produtor.js +32 -0
  257. package/dist/forja/registro.js +31 -0
  258. package/dist/forja/resolver.js +121 -0
  259. package/dist/forja/texto.js +13 -0
  260. package/dist/forja/versao.js +71 -0
  261. package/dist/forja.js +124 -81
  262. package/dist/gates.js +3 -28
  263. package/dist/handoff.js +33 -19
  264. package/dist/hitl-contract.js +46 -43
  265. package/dist/hitl-curto.js +37 -25
  266. package/dist/hitl-gates.js +13 -11
  267. package/dist/hitl-local-receipt.js +53 -0
  268. package/dist/hitl-local.js +11 -2
  269. package/dist/hitl-lock.js +5 -11
  270. package/dist/hitl-public-receipt.js +11 -0
  271. package/dist/hitl-resumo.js +72 -3
  272. package/dist/hitl-sessions.js +62 -9
  273. package/dist/hitl-tempo-parado.js +4 -4
  274. package/dist/hitl-texto-livre.js +7 -4
  275. package/dist/hitl.js +63 -64
  276. package/dist/horario.js +30 -7
  277. package/dist/hosts.js +155 -134
  278. package/dist/impedimento.js +108 -14
  279. package/dist/index.js +1421 -1019
  280. package/dist/indice-vetorial.js +14 -12
  281. package/dist/indice.js +7 -1
  282. package/dist/init.js +54 -49
  283. package/dist/instabilidade.js +144 -0
  284. package/dist/integracoes-locais.js +4 -0
  285. package/dist/intelligence-graph-cli.js +213 -88
  286. package/dist/intelligence-graph-contexto.js +56 -17
  287. package/dist/intelligence-graph-contract.js +2 -1
  288. package/dist/intelligence-graph-extract-md.js +56 -4
  289. package/dist/intelligence-graph-extract-ts.js +71 -3
  290. package/dist/intelligence-graph-query.js +96 -34
  291. package/dist/leases.js +19 -18
  292. package/dist/ledger-stats.js +17 -16
  293. package/dist/ledger.js +46 -2
  294. package/dist/licoes.js +12 -11
  295. package/dist/liveness.js +6 -3
  296. package/dist/locale-accounts.js +220 -0
  297. package/dist/locale-adapter.js +291 -0
  298. package/dist/locale-ajuda.js +833 -0
  299. package/dist/locale-audit.js +199 -0
  300. package/dist/locale-claims.js +213 -0
  301. package/dist/locale-conducao.js +138 -0
  302. package/dist/locale-conhecimento.js +181 -0
  303. package/dist/locale-decisao.js +205 -0
  304. package/dist/locale-docs.js +230 -0
  305. package/dist/locale-estado.js +101 -0
  306. package/dist/locale-forja.js +290 -0
  307. package/dist/locale-gerados.js +165 -0
  308. package/dist/locale-grafo.js +129 -0
  309. package/dist/locale-master.js +182 -0
  310. package/dist/locale-network.js +524 -0
  311. package/dist/locale-onboarding.js +92 -0
  312. package/dist/locale-operacao-restante.js +931 -0
  313. package/dist/locale-phase.js +151 -0
  314. package/dist/locale-projetos.js +100 -0
  315. package/dist/locale-prompts.js +194 -0
  316. package/dist/locale-pulse.js +349 -0
  317. package/dist/locale-reservas.js +87 -0
  318. package/dist/locale-retry.js +524 -0
  319. package/dist/locale-roadmap.js +102 -0
  320. package/dist/locale-security-scan.js +69 -0
  321. package/dist/locale-setup.js +192 -0
  322. package/dist/locale-ship.js +597 -0
  323. package/dist/locale-thread.js +421 -0
  324. package/dist/locale-verify.js +201 -0
  325. package/dist/locale-worktree.js +246 -0
  326. package/dist/locale.js +1258 -0
  327. package/dist/maestro-runtime.js +22 -3
  328. package/dist/manifest.js +415 -13
  329. package/dist/maquina-ociosa.js +211 -0
  330. package/dist/maquina.js +31 -7
  331. package/dist/master-digest.js +6 -1
  332. package/dist/master-nota-local.js +200 -0
  333. package/dist/master-nota.js +14 -8
  334. package/dist/master.js +139 -65
  335. package/dist/mcp-artifacts.js +11 -2
  336. package/dist/mcp-conhecimento.js +52 -0
  337. package/dist/mcp-experiencia.js +26 -5
  338. package/dist/mcp-git.js +23 -6
  339. package/dist/mcp-grafo.js +7 -4
  340. package/dist/mcp-install.js +73 -8
  341. package/dist/mcp-noctum.js +101 -0
  342. package/dist/mcp-server.js +90 -16
  343. package/dist/mcp-ship-transporte.js +977 -0
  344. package/dist/mcp-ship.js +71 -25
  345. package/dist/mcp-transportes.js +17 -0
  346. package/dist/mcp-verify.js +6 -1
  347. package/dist/memoria.js +63 -58
  348. package/dist/modos-migracao.js +8 -6
  349. package/dist/modos.js +20 -24
  350. package/dist/network-roadmap.js +90 -51
  351. package/dist/noctum-aceite.js +789 -0
  352. package/dist/noctum-arco.js +1418 -0
  353. package/dist/noctum-capacidade.js +141 -0
  354. package/dist/noctum-changelog.js +399 -0
  355. package/dist/noctum-cli.js +482 -0
  356. package/dist/noctum-coordenacao.js +1225 -0
  357. package/dist/noctum-entrega.js +1169 -0
  358. package/dist/noctum-envelope.js +1046 -0
  359. package/dist/noctum-execucao.js +376 -0
  360. package/dist/noctum-fechamento.js +405 -0
  361. package/dist/noctum-fila.js +314 -0
  362. package/dist/noctum-guarda.js +256 -0
  363. package/dist/noctum-integracao.js +507 -0
  364. package/dist/noctum-maquinas.js +383 -0
  365. package/dist/noctum-medicao.js +450 -0
  366. package/dist/noctum-modo.js +362 -0
  367. package/dist/noctum-pergunta-canal.js +96 -0
  368. package/dist/noctum-pergunta.js +115 -0
  369. package/dist/noctum-provas.js +838 -0
  370. package/dist/noctum-reconciliacao.js +591 -0
  371. package/dist/noctum-registro.js +215 -0
  372. package/dist/noctum-relatorio.js +827 -0
  373. package/dist/noctum-resposta.js +207 -0
  374. package/dist/noctum-sessoes-em-voo.js +93 -0
  375. package/dist/noctum-supervisao.js +649 -0
  376. package/dist/noctum-terminal.js +459 -0
  377. package/dist/noctum-transporte.js +655 -0
  378. package/dist/noctum-vigia.js +639 -0
  379. package/dist/ocupacao.js +32 -3
  380. package/dist/onboarding.js +56 -26
  381. package/dist/orkmind.js +48 -25
  382. package/dist/orquestracao.js +44 -47
  383. package/dist/parado-no-condutor.js +174 -127
  384. package/dist/phase.js +144 -63
  385. package/dist/policies.js +37 -4
  386. package/dist/portfolio.js +4 -2
  387. package/dist/postura-local.js +235 -0
  388. package/dist/preflight.js +11 -10
  389. package/dist/procedencia.js +220 -0
  390. package/dist/projeto-alvo.js +186 -24
  391. package/dist/prompts.js +140 -42
  392. package/dist/prova-minima.js +5 -6
  393. package/dist/pulse-cadencia.js +5 -4
  394. package/dist/pulse-consentimento.js +53 -1
  395. package/dist/pulse-delivery.js +151 -24
  396. package/dist/pulse-resposta.js +368 -4
  397. package/dist/pulse.js +188 -22
  398. package/dist/ratelimit.js +16 -6
  399. package/dist/recall.js +14 -13
  400. package/dist/redacao-saida.js +63 -0
  401. package/dist/rede-adesao.js +13 -8
  402. package/dist/rede-forja.js +108 -24
  403. package/dist/rede-status.js +66 -47
  404. package/dist/rede.js +86 -46
  405. package/dist/retomada-checkpoint.js +101 -0
  406. package/dist/retry.js +451 -212
  407. package/dist/roadmap-reservas.js +45 -28
  408. package/dist/roadmap-status.js +129 -33
  409. package/dist/runtime-ambiente.js +5 -0
  410. package/dist/runtime-context.js +284 -32
  411. package/dist/runtime-escada.js +423 -0
  412. package/dist/runtime-profiles.js +103 -46
  413. package/dist/runtimes.js +2 -1
  414. package/dist/saida-segura.js +3 -2
  415. package/dist/security-scan/agil.js +355 -0
  416. package/dist/security-scan/cli.js +76 -0
  417. package/dist/security-scan/compare.js +149 -0
  418. package/dist/security-scan/config.js +93 -0
  419. package/dist/security-scan/detect.js +97 -0
  420. package/dist/security-scan/executor.js +188 -0
  421. package/dist/security-scan/guard.js +180 -0
  422. package/dist/security-scan/imagem-de-lint.js +96 -0
  423. package/dist/security-scan/normalize/actionlint.js +12 -0
  424. package/dist/security-scan/normalize/eslint.js +12 -0
  425. package/dist/security-scan/normalize/gitleaks.js +13 -0
  426. package/dist/security-scan/normalize/golangci-lint.js +13 -0
  427. package/dist/security-scan/normalize/govulncheck.js +36 -0
  428. package/dist/security-scan/normalize/index.js +25 -0
  429. package/dist/security-scan/normalize/npm-audit.js +30 -0
  430. package/dist/security-scan/normalize/osv-scanner.js +32 -0
  431. package/dist/security-scan/normalize/oxlint.js +51 -0
  432. package/dist/security-scan/normalize/pip-audit.js +16 -0
  433. package/dist/security-scan/normalize/ruff.js +12 -0
  434. package/dist/security-scan/normalize/semgrep.js +20 -0
  435. package/dist/security-scan/normalize/trivy.js +33 -0
  436. package/dist/security-scan/normalize/util.js +204 -0
  437. package/dist/security-scan/normalize/zizmor.js +19 -0
  438. package/dist/security-scan/report.js +156 -0
  439. package/dist/security-scan/run.js +448 -0
  440. package/dist/security-scan/tools.js +62 -0
  441. package/dist/security-scan/trabalhador.js +74 -0
  442. package/dist/security-scan/types.js +15 -0
  443. package/dist/session-events.js +26 -5
  444. package/dist/session-watcher-claude.js +81 -20
  445. package/dist/session-watcher.js +340 -42
  446. package/dist/sessoes-contas.js +10 -1
  447. package/dist/sessoes-inventario.js +1 -1
  448. package/dist/sessoes.js +25 -23
  449. package/dist/setup.js +59 -58
  450. package/dist/ship-publicar.js +971 -0
  451. package/dist/ship.js +243 -97
  452. package/dist/steal.js +77 -0
  453. package/dist/superficie.js +14 -11
  454. package/dist/thread-close.js +12 -11
  455. package/dist/thread.js +274 -100
  456. package/dist/tokens.js +15 -11
  457. package/dist/trabalho-thread.js +297 -0
  458. package/dist/transporte-do-canal.js +189 -0
  459. package/dist/types.js +12 -1
  460. package/dist/util.js +13 -3
  461. package/dist/varredura-acoes.js +507 -0
  462. package/dist/verify.js +608 -76
  463. package/dist/vida-da-thread.js +254 -0
  464. package/dist/worktree-local.js +167 -0
  465. package/dist/worktree.js +99 -83
  466. package/dist/yaml.js +27 -8
  467. package/eval/casos/check-quality.json +77 -0
  468. package/eval/casos/security-auditor.json +148 -0
  469. package/eval/casos/security-remediation.json +161 -0
  470. package/eval/fixtures/security-scan/README.md +8 -0
  471. package/eval/fixtures/security-scan/go/.github/workflows/ci.yml +12 -0
  472. package/eval/fixtures/security-scan/go/Dockerfile +4 -0
  473. package/eval/fixtures/security-scan/go/README.md +4 -0
  474. package/eval/fixtures/security-scan/go/config.go +4 -0
  475. package/eval/fixtures/security-scan/go/go.mod +5 -0
  476. package/eval/fixtures/security-scan/go/go.sum +2 -0
  477. package/eval/fixtures/security-scan/go/main.go +31 -0
  478. package/eval/fixtures/security-scan/js/.github/workflows/ci.yml +12 -0
  479. package/eval/fixtures/security-scan/js/Dockerfile +3 -0
  480. package/eval/fixtures/security-scan/js/README.md +4 -0
  481. package/eval/fixtures/security-scan/js/package-lock.json +844 -0
  482. package/eval/fixtures/security-scan/js/package.json +10 -0
  483. package/eval/fixtures/security-scan/js/src/config.js +4 -0
  484. package/eval/fixtures/security-scan/js/src/server.js +17 -0
  485. package/eval/fixtures/security-scan/js/src/util.ts +4 -0
  486. package/eval/fixtures/security-scan/js/tsconfig.json +4 -0
  487. package/eval/fixtures/security-scan/python/.github/workflows/ci.yml +12 -0
  488. package/eval/fixtures/security-scan/python/Dockerfile +3 -0
  489. package/eval/fixtures/security-scan/python/README.md +4 -0
  490. package/eval/fixtures/security-scan/python/app/__init__.py +0 -0
  491. package/eval/fixtures/security-scan/python/app/config.py +2 -0
  492. package/eval/fixtures/security-scan/python/app/main.py +19 -0
  493. package/eval/fixtures/security-scan/python/requirements.txt +1 -0
  494. package/package.json +9 -4
  495. package/references/security-checklist.md +3 -3
  496. package/schemas/code-artifact-graph.v1.schema.json +2 -1
  497. package/skills/README.md +1 -0
  498. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  499. package/skills/phases/check-quality/SKILL.md +16 -0
  500. package/skills/phases/go-implementation/SKILL.md +6 -0
  501. package/skills/phases/security-remediation/SKILL.md +70 -0
  502. package/skills/reviewers/security-auditor/SKILL.md +25 -0
@@ -0,0 +1,1418 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.MOTIVOS_DE_ENCERRAMENTO = exports.PRAZO_PARA_AUTORIZAR_MIN = exports.TEXTO_DE_REVOGACAO = exports.textoDeAutorizacao = exports.enderecoDoArco = exports.ALVO_DO_NOCTUM = exports.CONTRATO_ARCO = void 0;
37
+ exports.contextoDoArco = contextoDoArco;
38
+ exports.hashDaRevogacao = hashDaRevogacao;
39
+ exports.proporEnvelope = proporEnvelope;
40
+ exports.codigosDosArcos = codigosDosArcos;
41
+ exports.pedirRevogacao = pedirRevogacao;
42
+ exports.perguntasDoArco = perguntasDoArco;
43
+ exports.autorizarPeloIngresso = autorizarPeloIngresso;
44
+ exports.revogarPeloIngresso = revogarPeloIngresso;
45
+ exports.autorizarPelaResposta = autorizarPelaResposta;
46
+ exports.revogarPelaResposta = revogarPelaResposta;
47
+ exports.criarDialogoDoArco = criarDialogoDoArco;
48
+ exports.idDaDecisao = idDaDecisao;
49
+ exports.enfileirarDecisao = enfileirarDecisao;
50
+ exports.lerArco = lerArco;
51
+ exports.lerArcoComProposta = lerArcoComProposta;
52
+ exports.conferirAntesDoEfeito = conferirAntesDoEfeito;
53
+ exports.reservarDespacho = reservarDespacho;
54
+ exports.encerrarArco = encerrarArco;
55
+ exports.concluirDespacho = concluirDespacho;
56
+ exports.versoesAutorizadas = versoesAutorizadas;
57
+ /**
58
+ * RM-058, fatia N1: o estado do arco #Noctum e o ingresso do envelope.
59
+ *
60
+ * O estado mora num log append-only em `.orkastery/noctum/arcos/<arco>/arco.jsonl`, na raiz do
61
+ * estado. Cada linha leva o sha256 da anterior e um MAC de subchave derivada da chave local do
62
+ * núcleo (`.orkastery/private`, a mesma fronteira do recibo local do HITL), e linha trocada reprova
63
+ * toda leitura. Essa chave fica legível para qualquer processo com o mesmo usuário do sistema, que é
64
+ * o caso dos agentes da fábrica: o MAC do log não os impede de escrever uma linha. Uma linha que se diz
65
+ * do Telegram ou do callback nativo ainda precisa do recibo do canal, que a conferência antes do efeito
66
+ * relê e que a chave local não produz. Uma linha que se diz do diálogo do host não tem recibo do canal:
67
+ * a fronteira dela é a conta do sistema, como no recibo local do HITL, e com a chave local ela passa.
68
+ * Truncar o log também segue possível, como no ledger (D5 da ork-rm058n1, limites ditos na página).
69
+ *
70
+ * Proposta não autoriza nada. O envelope só passa a valer por ingresso com prova de origem do
71
+ * dono, pelo mesmo caminho do HITL: o envelope assinado do Telegram ou do callback nativo, que
72
+ * `autenticarResposta` confere no endereço `noctum` / `arco:<arco>` (D1), ou o diálogo nativo
73
+ * do host, por callback de elicitação que o servidor fornece. Argumento de ferramenta nunca é
74
+ * resposta.
75
+ *
76
+ * Nada aqui guarda estado em memória: toda leitura relê o disco e todo veredito usa o instante
77
+ * dado. Por isso revogação e expiração valem na chamada seguinte, e restart não renova nada.
78
+ */
79
+ const fs = __importStar(require("node:fs"));
80
+ const path = __importStar(require("node:path"));
81
+ const node_crypto_1 = require("node:crypto");
82
+ const estado_thread_1 = require("./estado-thread");
83
+ const horario_1 = require("./horario");
84
+ const hitl_canais_1 = require("./hitl-canais");
85
+ const hitl_gates_1 = require("./hitl-gates");
86
+ const hitl_ingress_receipt_1 = require("./hitl-ingress-receipt");
87
+ const hitl_contract_1 = require("./hitl-contract");
88
+ const hitl_lock_1 = require("./hitl-lock");
89
+ const hitl_local_receipt_1 = require("./hitl-local-receipt");
90
+ const hitl_native_1 = require("./hitl-native");
91
+ const hitl_public_receipt_1 = require("./hitl-public-receipt");
92
+ const noctum_envelope_1 = require("./noctum-envelope");
93
+ const noctum_pergunta_1 = require("./noctum-pergunta");
94
+ const pulse_consentimento_1 = require("./pulse-consentimento");
95
+ const thread_1 = require("./thread");
96
+ exports.CONTRATO_ARCO = 'ork.noctum-arco/v1';
97
+ /**
98
+ * D1 da ork-rm058n1: o endereço que o ingresso assina, nas posições de thread e de pedido. O
99
+ * `:` não cabe no identificador que `/ork gate` e `/ork session` aceitam, e o pulse assina outro
100
+ * alvo: nenhum envelope de gate, de sessão ou do pulse serve de prova aqui, nem o contrário.
101
+ */
102
+ exports.ALVO_DO_NOCTUM = 'noctum';
103
+ const enderecoDoArco = (arco) => `arco:${arco}`;
104
+ exports.enderecoDoArco = enderecoDoArco;
105
+ /** O texto que o dono assina. Fechado: qualquer outro não autoriza nem revoga. */
106
+ const textoDeAutorizacao = (envelopeSha256) => `autorizar ${envelopeSha256}`;
107
+ exports.textoDeAutorizacao = textoDeAutorizacao;
108
+ exports.TEXTO_DE_REVOGACAO = 'revogar';
109
+ /** Uma hora para autorizar a proposta, como o pedido HITL. Vencida, ela não autoriza mais nada. */
110
+ exports.PRAZO_PARA_AUTORIZAR_MIN = 60;
111
+ /** O diálogo do host espera no máximo isto, e nunca além do prazo da proposta. */
112
+ const ESPERA_DO_DIALOGO_MS = 300000;
113
+ /**
114
+ * RM-058 N6 (D2 e D8 da ork-rm058n6): por que o arco fechou. O fechamento é ato do núcleo, não do dono: ele só registra que
115
+ * a hora, o teto ou a fila acabaram, e que a revogação do dono já foi tratada. RM-058, integração (D7 da ork-rm058integra):
116
+ * `supervisao` é a parada da supervisão da N4 (o supervisor que falhou, o teto, a violação de custo ou o ambiente do
117
+ * despacho), que o fechamento lê do log dela depois do revogado e da hora e antes do custo.
118
+ */
119
+ exports.MOTIVOS_DE_ENCERRAMENTO = ['revogado', 'hora', 'custo', 'fila-vazia', 'supervisao'];
120
+ const ZERO = '0'.repeat(64);
121
+ const ARQUIVO = 'arco.jsonl';
122
+ const sha = (s) => (0, node_crypto_1.createHash)('sha256').update(s).digest('hex');
123
+ const adulterado = (detalhe) => new noctum_envelope_1.RecusaDoArco('noctum.estado.adulterado', `${detalhe}; o estado do arco exige inspeção`);
124
+ const semProva = (detalhe) => new noctum_envelope_1.RecusaDoArco('noctum.origem.sem-prova', detalhe);
125
+ const objeto = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
126
+ const ID_DE_THREAD = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/;
127
+ /** Subchave própria do arco: o mesmo segredo do recibo local, separado por domínio. */
128
+ function chaveDoArco(raiz, criar) {
129
+ return (0, node_crypto_1.createHmac)('sha256', (0, hitl_local_receipt_1.chaveLocal)(raiz, criar)).update(exports.CONTRATO_ARCO).digest();
130
+ }
131
+ function dirDoArco(raiz, arco) {
132
+ if (typeof arco !== 'string' || !noctum_envelope_1.ID_DO_ARCO.test(arco))
133
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', 'identificador de arco inválido');
134
+ const principal = (0, estado_thread_1.raizDoEstado)(raiz);
135
+ (0, estado_thread_1.exigirEstadoSemLink)(principal, ['.orkastery', 'noctum', 'arcos', arco]);
136
+ return path.join(principal, '.orkastery', 'noctum', 'arcos', arco);
137
+ }
138
+ /** O contexto que o callback nativo amarra, por arco e versão (ou zero, na revogação). */
139
+ function contextoDoArco(arco, versao) {
140
+ return sha(JSON.stringify([exports.CONTRATO_ARCO, arco, versao]));
141
+ }
142
+ /** O `pedidoSha256` que o callback nativo assina para revogar: não é hash de envelope nenhum. */
143
+ function hashDaRevogacao(arco) {
144
+ return sha(JSON.stringify(['ork.noctum-revogacao/v1', arco]));
145
+ }
146
+ /**
147
+ * Lê e confere o log inteiro. Linha sem quebra no fim está sendo gravada: fica de fora e não
148
+ * dá autoridade a ninguém. Qualquer outra divergência (MAC, cadeia, sequência, arco) reprova.
149
+ */
150
+ function lerLog(raiz, arco) {
151
+ const file = path.join(dirDoArco(raiz, arco), ARQUIVO);
152
+ let bruto;
153
+ try {
154
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
155
+ try {
156
+ const st = fs.fstatSync(fd);
157
+ if (!st.isFile() || st.nlink !== 1 || st.uid !== process.getuid?.() || (st.mode & 0o077) !== 0) {
158
+ throw adulterado('log do arco não é arquivo privado do núcleo');
159
+ }
160
+ bruto = fs.readFileSync(fd);
161
+ }
162
+ finally {
163
+ fs.closeSync(fd);
164
+ }
165
+ }
166
+ catch (e) {
167
+ if (e.code === 'ENOENT') {
168
+ return { linhas: [], ultimaLinha: null, terminaEmQuebra: true, bytesCompletos: 0, bytesLidos: 0 };
169
+ }
170
+ if (e instanceof noctum_envelope_1.RecusaDoArco)
171
+ throw e;
172
+ throw adulterado('log do arco ilegível');
173
+ }
174
+ // Em bytes, não em caracteres: cauda que corta um caractere de vários bytes não desalinha os tamanhos.
175
+ const fim = bruto.lastIndexOf(0x0a) + 1;
176
+ const textos = bruto.subarray(0, fim).toString('utf8').split('\n').slice(0, -1);
177
+ const tamanhos = { terminaEmQuebra: fim === bruto.length, bytesCompletos: fim, bytesLidos: bruto.length };
178
+ if (!textos.length)
179
+ return { linhas: [], ultimaLinha: null, ...tamanhos };
180
+ let chave;
181
+ try {
182
+ chave = chaveDoArco(raiz, false);
183
+ }
184
+ catch {
185
+ throw adulterado('chave do núcleo ausente para conferir o log');
186
+ }
187
+ let anterior = ZERO;
188
+ const linhas = textos.map((texto, i) => {
189
+ let l;
190
+ try {
191
+ l = JSON.parse(texto);
192
+ }
193
+ catch {
194
+ throw adulterado(`linha ${i + 1} não é JSON`);
195
+ }
196
+ if (!objeto(l) || Object.keys(l).sort().join(',') !== 'dados,mac' || typeof l.mac !== 'string' || !/^[a-f0-9]{64}$/.test(l.mac)) {
197
+ throw adulterado(`linha ${i + 1} fora do formato`);
198
+ }
199
+ const esperado = (0, node_crypto_1.createHmac)('sha256', chave).update(JSON.stringify(l.dados)).digest();
200
+ if (!(0, node_crypto_1.timingSafeEqual)(Buffer.from(l.mac, 'hex'), esperado))
201
+ throw adulterado(`linha ${i + 1} com MAC que não confere`);
202
+ const d = l.dados;
203
+ if (d.contrato !== exports.CONTRATO_ARCO || d.arco !== arco || d.seq !== i + 1 || d.anterior !== anterior) {
204
+ throw adulterado(`linha ${i + 1} fora da cadeia`);
205
+ }
206
+ anterior = sha(texto);
207
+ return d;
208
+ });
209
+ return { linhas, ultimaLinha: textos.at(-1), ...tamanhos };
210
+ }
211
+ /**
212
+ * Grava uma linha. Só roda sob o lock do arco, depois de `lerLog`.
213
+ *
214
+ * Sob o lock, cauda sem quebra de linha é escrita abortada (queda, disco cheio) ou lixo: nunca deu
215
+ * autoridade a ninguém, e é descartada antes da linha nova. Sem isso, uma cauda rasgada impediria a
216
+ * revogação do dono. Escrita curta volta o arquivo ao tamanho de antes e é acusada: o chamador nunca
217
+ * recebe sucesso de uma linha que não ficou inteira no disco.
218
+ */
219
+ function anexar(raiz, arco, log, evento, quando) {
220
+ const dados = { contrato: exports.CONTRATO_ARCO, arco, seq: log.linhas.length + 1,
221
+ anterior: log.ultimaLinha === null ? ZERO : sha(log.ultimaLinha), em: quando, evento };
222
+ const mac = (0, node_crypto_1.createHmac)('sha256', chaveDoArco(raiz, true)).update(JSON.stringify(dados)).digest('hex');
223
+ const linha = Buffer.from(JSON.stringify({ dados, mac }) + '\n', 'utf8');
224
+ const file = path.join(dirDoArco(raiz, arco), ARQUIVO);
225
+ const fd = fs.openSync(file, fs.constants.O_WRONLY | fs.constants.O_APPEND | fs.constants.O_CREAT | fs.constants.O_NOFOLLOW, 0o600);
226
+ try {
227
+ if (fs.fstatSync(fd).size !== log.bytesLidos)
228
+ throw adulterado('o log mudou entre a leitura e a escrita sob o lock');
229
+ if (log.bytesLidos !== log.bytesCompletos)
230
+ fs.ftruncateSync(fd, log.bytesCompletos);
231
+ const escritos = fs.writeSync(fd, linha);
232
+ if (escritos !== linha.length) {
233
+ fs.ftruncateSync(fd, log.bytesCompletos);
234
+ fs.fsyncSync(fd);
235
+ throw adulterado(`escrita curta no log (${escritos} de ${linha.length} bytes); nada foi gravado`);
236
+ }
237
+ fs.fsyncSync(fd);
238
+ }
239
+ finally {
240
+ fs.closeSync(fd);
241
+ }
242
+ }
243
+ /** Tentativas de 100 ms pelo lock: a revogação não perde para um escritor que segura o arco por um instante. */
244
+ const TENTATIVAS_DO_LOCK = 30;
245
+ function dirParaLock(raiz, arco, criar) {
246
+ const dir = dirDoArco(raiz, arco);
247
+ if (criar)
248
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
249
+ else if (!fs.existsSync(dir))
250
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.sem-autorizacao', `arco ${arco} sem proposta`);
251
+ return dir;
252
+ }
253
+ /** Só a disputa pelo kernel espera; qualquer outra falha, inclusive a recusa de dentro, sobe na hora. */
254
+ const disputaDoLock = (e) => e instanceof Error && e.message.startsWith('HITL ocupado; exclusão do kernel');
255
+ /**
256
+ * `esperar` na revogação, o ato de segurança, e na reserva e no desfecho do despacho (N2), que são passos curtos de quem
257
+ * já tem a vez de despachar; o resto falha na hora, sem prender o processo.
258
+ */
259
+ function comLockDoArco(raiz, arco, criar, executar, esperar = false) {
260
+ const dir = dirParaLock(raiz, arco, criar);
261
+ for (let tentativa = 1;; tentativa++) {
262
+ try {
263
+ return (0, hitl_lock_1.comLockInspecionavel)(dir, 'noctum-arco', executar);
264
+ }
265
+ catch (e) {
266
+ if (!esperar || tentativa >= TENTATIVAS_DO_LOCK || !disputaDoLock(e))
267
+ throw e;
268
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
269
+ }
270
+ }
271
+ }
272
+ /** A mesma espera, sem prender o event loop: é a que o diálogo usa dentro do servidor MCP. */
273
+ async function comLockDoArcoAssincrono(raiz, arco, executar) {
274
+ const dir = dirParaLock(raiz, arco, false);
275
+ for (let tentativa = 1;; tentativa++) {
276
+ try {
277
+ return (0, hitl_lock_1.comLockInspecionavel)(dir, 'noctum-arco', executar);
278
+ }
279
+ catch (e) {
280
+ if (tentativa >= TENTATIVAS_DO_LOCK || !disputaDoLock(e))
281
+ throw e;
282
+ await new Promise(r => setTimeout(r, 100));
283
+ }
284
+ }
285
+ }
286
+ /** A identidade de uma prova do dono: cada uma vale para um ato só. */
287
+ function chaveDaProva(i) {
288
+ if (i.origem === 'mcp-local')
289
+ return `mcp-local:${i.solicitacao}`;
290
+ if (i.origem === 'terminal')
291
+ return `terminal:${i.solicitacao}`;
292
+ return `${i.origem}:${i.mensagem}`;
293
+ }
294
+ function derivar(linhas) {
295
+ const d = { propostas: new Map(), ultima: null, autorizacao: null, revogacao: null, decisoes: [], usadas: new Set(),
296
+ despachos: new Map(), encerramento: null, revogacoesPedidas: [] };
297
+ const usar = (i) => {
298
+ const prova = chaveDaProva(i);
299
+ // Uma prova do dono vale para um ato só: reaproveitada noutra linha, o log foi emendado à mão.
300
+ if (d.usadas.has(prova))
301
+ throw adulterado('a mesma prova do dono aparece em dois atos do log');
302
+ d.usadas.add(prova);
303
+ };
304
+ for (const { evento: e, seq, em } of linhas) {
305
+ if (e.tipo === 'despacho_reservado') {
306
+ d.despachos.set(seq, { ...e.reserva, seq, reservadoEm: em, desfecho: 'pendente', threadDespachada: null, sessionId: null,
307
+ idempotente: false, motivo: null, concluidoEm: null });
308
+ continue;
309
+ }
310
+ if (e.tipo === 'despacho_concluido' || e.tipo === 'despacho_recusado') {
311
+ const r = d.despachos.get(e.reserva);
312
+ // Desfecho sem reserva, ou um segundo desfecho, só existe num log emendado à mão.
313
+ if (!r || r.desfecho !== 'pendente')
314
+ throw adulterado(`desfecho da reserva ${e.reserva} sem reserva pendente`);
315
+ if (e.tipo === 'despacho_concluido')
316
+ Object.assign(r, { desfecho: 'concluido', threadDespachada: e.thread, sessionId: e.sessionId,
317
+ idempotente: e.idempotente, concluidoEm: em });
318
+ else
319
+ Object.assign(r, { desfecho: 'recusado', motivo: e.motivo, concluidoEm: em });
320
+ continue;
321
+ }
322
+ if (e.tipo === 'encerrado') {
323
+ // O fechamento é um só e vem depois da autorização: outra linha assim só existe num log emendado à mão.
324
+ if (!d.autorizacao || d.encerramento)
325
+ throw adulterado('encerramento sem autorização ou repetido');
326
+ d.encerramento = { motivo: e.motivo, detalhe: e.detalhe, em, versao: d.autorizacao.versao, envelopeSha256: d.autorizacao.envelopeSha256 };
327
+ continue;
328
+ }
329
+ if (e.tipo === 'revogacao_pedida') {
330
+ // Pergunta de revogação sem autorização antes dela só existe num log emendado à mão.
331
+ if (!d.autorizacao)
332
+ throw adulterado('pergunta de revogação sem autorização');
333
+ d.revogacoesPedidas.push({ codigo: e.codigo, prazo: e.prazo, em });
334
+ continue;
335
+ }
336
+ if (e.tipo === 'proposto') {
337
+ const p = { versao: e.versao, envelopeSha256: e.envelopeSha256, conteudo: e.conteudo, prazo: e.prazo, em,
338
+ ...(typeof e.codigo === 'string' ? { codigo: e.codigo } : {}) };
339
+ if ((0, noctum_envelope_1.hashDoEnvelope)(p.conteudo) !== p.envelopeSha256)
340
+ throw adulterado(`proposta ${p.versao} com hash que não confere`);
341
+ d.propostas.set(p.versao, p);
342
+ d.ultima = p;
343
+ }
344
+ else if (e.tipo === 'autorizado') {
345
+ if (d.propostas.get(e.versao)?.envelopeSha256 !== e.envelopeSha256)
346
+ throw adulterado(`autorização da versão ${e.versao} sem proposta`);
347
+ if (d.encerramento)
348
+ throw adulterado(`autorização da versão ${e.versao} depois do encerramento`);
349
+ d.autorizacao = { versao: e.versao, envelopeSha256: e.envelopeSha256, ingresso: e.ingresso };
350
+ usar(e.ingresso);
351
+ }
352
+ else if (e.tipo === 'revogado') {
353
+ d.revogacao = e.ingresso;
354
+ usar(e.ingresso);
355
+ }
356
+ else if (e.tipo === 'decisao_enfileirada')
357
+ d.decisoes.push(e.decisao);
358
+ }
359
+ return d;
360
+ }
361
+ function situacao(d, quando) {
362
+ if (d.revogacao)
363
+ return 'revogado';
364
+ if (!d.autorizacao)
365
+ return 'proposto';
366
+ if (d.encerramento)
367
+ return 'encerrado';
368
+ const janela = d.propostas.get(d.autorizacao.versao).conteudo.janela;
369
+ const agora = Date.parse(quando);
370
+ if (agora < Date.parse(janela.inicio))
371
+ return 'agendado';
372
+ return agora >= Date.parse(janela.fim) ? 'expirado' : 'vigente';
373
+ }
374
+ function estadoDe(arco, d, quando) {
375
+ const vigente = d.autorizacao ? d.propostas.get(d.autorizacao.versao) : null;
376
+ const pendente = d.ultima && !d.revogacao && !d.encerramento && d.ultima.versao !== d.autorizacao?.versao &&
377
+ Date.parse(quando) < Date.parse(d.ultima.prazo) ? d.ultima : null;
378
+ const sit = situacao(d, quando);
379
+ // RM-057 (resposta curta): só a última pergunta de revogação espera o dono, e só com o arco ainda de pé.
380
+ const rev = d.revogacoesPedidas.at(-1);
381
+ const revogacaoPedida = rev && (sit === 'vigente' || sit === 'agendado') && Date.parse(quando) < Date.parse(rev.prazo)
382
+ ? { codigo: rev.codigo, prazo: rev.prazo, pedidaEm: rev.em } : null;
383
+ return { contrato: exports.CONTRATO_ARCO, arco, situacao: sit, versao: vigente?.versao ?? null,
384
+ envelopeSha256: vigente?.envelopeSha256 ?? null, conteudo: vigente?.conteudo ?? null,
385
+ autorizadoPor: d.autorizacao?.ingresso ?? null, revogadoPor: d.revogacao,
386
+ propostaPendente: pendente ? { versao: pendente.versao, envelopeSha256: pendente.envelopeSha256, prazo: pendente.prazo,
387
+ ...(pendente.codigo ? { codigo: pendente.codigo } : {}) } : null,
388
+ decisoes: d.decisoes, despachos: [...d.despachos.values()], consumo: consumoDe(d), encerramento: d.encerramento, revogacaoPedida };
389
+ }
390
+ /** D12 da ork-rm058n2: a reserva consome uma sessão até ser recusada; a concluída e a pendente contam. */
391
+ function consumoDe(d) {
392
+ const c = { arco: 0, porConta: {}, porRuntime: {} };
393
+ for (const r of d.despachos.values()) {
394
+ if (r.desfecho === 'recusado')
395
+ continue;
396
+ c.arco += 1;
397
+ c.porConta[r.conta] = (c.porConta[r.conta] ?? 0) + 1;
398
+ c.porRuntime[r.runtime] = (c.porRuntime[r.runtime] ?? 0) + 1;
399
+ }
400
+ return c;
401
+ }
402
+ /**
403
+ * Grava a proposta de um envelope. Não autoriza nada: quem autoriza é o dono, pelo ingresso.
404
+ *
405
+ * A mesma proposta repetida devolve a que está pendente. Proposta nova sobre um envelope já
406
+ * autorizado é emenda: o vigente continua valendo até o dono autorizar a nova versão.
407
+ */
408
+ function proporEnvelope(raiz, pedido, quando = new Date().toISOString(), emUso = []) {
409
+ const conteudo = (0, noctum_envelope_1.validarPedidoDeAbertura)(pedido);
410
+ const envelopeSha256 = (0, noctum_envelope_1.hashDoEnvelope)(conteudo);
411
+ if (!(Date.parse(quando) < Date.parse(conteudo.janela.fim))) {
412
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', `a janela pedida terminou em ${conteudo.janela.fim}`);
413
+ }
414
+ return comLockDoArco(raiz, conteudo.arco, true, () => {
415
+ const log = lerLog(raiz, conteudo.arco), d = derivar(log.linhas);
416
+ if (d.revogacao)
417
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.revogado', 'arco revogado é terminal; abra outro arco');
418
+ if (d.encerramento)
419
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.encerrado', `arco encerrado (${d.encerramento.motivo}) é terminal; abra outro arco`);
420
+ const resumo = (0, noctum_envelope_1.resumoDoEnvelope)(conteudo, envelopeSha256);
421
+ const pendente = estadoDe(conteudo.arco, d, quando).propostaPendente;
422
+ if (pendente?.envelopeSha256 === envelopeSha256)
423
+ return { arco: conteudo.arco, ...pendente, resumo };
424
+ if (d.autorizacao?.envelopeSha256 === envelopeSha256) {
425
+ const vigente = d.propostas.get(d.autorizacao.versao);
426
+ return { arco: conteudo.arco, versao: vigente.versao, envelopeSha256, prazo: vigente.prazo, resumo };
427
+ }
428
+ exigirEmendaSemProrrogar(d, conteudo, quando);
429
+ const versao = (d.ultima?.versao ?? 0) + 1;
430
+ const prazo = new Date(Date.parse(quando) + exports.PRAZO_PARA_AUTORIZAR_MIN * 60000).toISOString();
431
+ // RM-057 (resposta curta, D2): a pergunta nasce com a proposta, com um código que nenhuma outra pergunta usa agora.
432
+ const codigo = (0, pulse_consentimento_1.gerarCodigo)([...emUso, ...codigosDosArcos(raiz)]);
433
+ anexar(raiz, conteudo.arco, log, { tipo: 'proposto', versao, envelopeSha256, conteudo, prazo, codigo }, quando);
434
+ return { arco: conteudo.arco, versao, envelopeSha256, prazo, resumo, codigo };
435
+ });
436
+ }
437
+ /**
438
+ * RM-057 (resposta curta): os códigos das perguntas de todos os arcos desta raiz, para um código novo nunca repetir um
439
+ * deles. Arco que não se lê não contribui (a resolução por código recusa o ambíguo de qualquer jeito).
440
+ */
441
+ function codigosDosArcos(raiz) {
442
+ let nomes;
443
+ try {
444
+ nomes = fs.readdirSync(path.join((0, estado_thread_1.raizDoEstado)(raiz), '.orkastery', 'noctum', 'arcos')).filter(n => noctum_envelope_1.ID_DO_ARCO.test(n));
445
+ }
446
+ catch {
447
+ return [];
448
+ }
449
+ const codigos = [];
450
+ for (const arco of nomes) {
451
+ try {
452
+ const d = derivar(lerLog(raiz, arco).linhas);
453
+ for (const p of d.propostas.values())
454
+ if (p.codigo)
455
+ codigos.push(p.codigo);
456
+ for (const r of d.revogacoesPedidas)
457
+ codigos.push(r.codigo);
458
+ }
459
+ catch { /* arco ilegível não empresta código */ }
460
+ }
461
+ return codigos;
462
+ }
463
+ /**
464
+ * RM-057 (resposta curta, D10 da ork-rm057respost): abre a pergunta de revogação ao dono. Não revoga nada: quem revoga é
465
+ * a resposta dele, pela prova do canal ou do terminal. A pergunta que ainda espera o dono é devolvida, em vez de outra.
466
+ */
467
+ function pedirRevogacao(raiz, arco, quando = new Date().toISOString(), emUso = []) {
468
+ return comLockDoArco(raiz, arco, false, () => {
469
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
470
+ if (d.revogacao)
471
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.revogado', `o arco ${arco} já foi revogado`);
472
+ if (d.encerramento)
473
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.encerrado', `arco ${arco} encerrado (${d.encerramento.motivo}) é terminal`);
474
+ if (!d.autorizacao) {
475
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.sem-autorizacao', `o arco ${arco} nunca foi autorizado: não há o que revogar, e a proposta vence sozinha`);
476
+ }
477
+ const estado = estadoDe(arco, d, quando);
478
+ if (estado.situacao === 'expirado')
479
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.expirado', `o arco ${arco} já terminou; não há o que revogar`);
480
+ const base = { arco, versao: d.autorizacao.versao, envelopeSha256: d.autorizacao.envelopeSha256 };
481
+ if (estado.revogacaoPedida)
482
+ return { ...base, codigo: estado.revogacaoPedida.codigo, prazo: estado.revogacaoPedida.prazo, nova: false };
483
+ const codigo = (0, pulse_consentimento_1.gerarCodigo)([...emUso, ...codigosDosArcos(raiz)]);
484
+ const prazo = new Date(Date.parse(quando) + exports.PRAZO_PARA_AUTORIZAR_MIN * 60000).toISOString();
485
+ anexar(raiz, arco, log, { tipo: 'revogacao_pedida', codigo, prazo }, quando);
486
+ return { ...base, codigo, prazo, nova: true };
487
+ });
488
+ }
489
+ /**
490
+ * RM-057 (resposta curta): as perguntas do arco ao dono, derivadas do log a cada leitura, com a situação de cada uma.
491
+ * Só a `pendente` aceita resposta; as outras existem para a resposta atrasada ouvir o porquê, nunca para valer.
492
+ */
493
+ function perguntasDoArco(raiz, arco, quando = new Date().toISOString()) {
494
+ const d = derivar(lerLog(raiz, arco).linhas);
495
+ const estado = estadoDe(arco, d, quando);
496
+ const terminal = d.revogacao !== null || d.encerramento !== null;
497
+ const perguntas = [];
498
+ for (const p of d.propostas.values()) {
499
+ if (!p.codigo)
500
+ continue;
501
+ const situacao = d.autorizacao?.versao === p.versao ? 'respondida'
502
+ : d.ultima?.versao !== p.versao ? 'substituida' : terminal ? 'arco-terminal'
503
+ : estado.propostaPendente?.versao === p.versao && Date.parse(quando) < Date.parse(p.conteudo.janela.fim) ? 'pendente' : 'vencida';
504
+ perguntas.push({ contrato: noctum_pergunta_1.CONTRATO_PERGUNTA_DO_ARCO, arco, ato: 'autorizar', codigo: p.codigo, versao: p.versao,
505
+ envelopeSha256: p.envelopeSha256, criadaEm: p.em, prazo: p.prazo, conteudo: p.conteudo, situacao });
506
+ }
507
+ const vigente = d.autorizacao ? d.propostas.get(d.autorizacao.versao) ?? null : null;
508
+ d.revogacoesPedidas.forEach((r, i) => {
509
+ const situacao = d.revogacao ? 'respondida' : i < d.revogacoesPedidas.length - 1 ? 'substituida'
510
+ : d.encerramento ? 'arco-terminal' : estado.revogacaoPedida?.codigo === r.codigo ? 'pendente' : 'vencida';
511
+ perguntas.push({ contrato: noctum_pergunta_1.CONTRATO_PERGUNTA_DO_ARCO, arco, ato: 'revogar', codigo: r.codigo, versao: vigente?.versao ?? null,
512
+ envelopeSha256: vigente?.envelopeSha256 ?? null, criadaEm: r.em, prazo: r.prazo, conteudo: vigente?.conteudo ?? null, situacao });
513
+ });
514
+ return perguntas;
515
+ }
516
+ /**
517
+ * D7: emenda é versão nova com prova nova, mas não prorroga nada. Ela mantém o início, não passa do
518
+ * fim do envelope autorizado e não reabre arco que já terminou: arco novo é outro arco.
519
+ */
520
+ function exigirEmendaSemProrrogar(d, conteudo, quando) {
521
+ if (!d.autorizacao)
522
+ return;
523
+ const janela = d.propostas.get(d.autorizacao.versao).conteudo.janela;
524
+ if (!(Date.parse(quando) < Date.parse(janela.fim))) {
525
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.expirado', `o arco terminou em ${janela.fim}; emenda não reabre arco, abra outro`);
526
+ }
527
+ if (Date.parse(conteudo.janela.inicio) !== Date.parse(janela.inicio) || Date.parse(conteudo.janela.fim) > Date.parse(janela.fim)) {
528
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', 'emenda mantém o início e não prorroga o fim do envelope autorizado');
529
+ }
530
+ // RM-058 N3 (A4 da revisão da ork-rm058n3): a emenda não troca o contrato entre a `v3` e as outras. O consumo e as sessões de
531
+ // uma máquina só não estão no ramo de coordenação, e a soma entre máquinas os perderia; o arco entre máquinas nasce assim.
532
+ const antes = d.propostas.get(d.autorizacao.versao).conteudo.contrato;
533
+ if ((antes === noctum_envelope_1.CONTRATO_ENVELOPE_V3) !== (conteudo.contrato === noctum_envelope_1.CONTRATO_ENVELOPE_V3)) {
534
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', `a emenda não troca o contrato de ${antes} para ${conteudo.contrato}: ` +
535
+ 'o arco entre máquinas começa como tal, e o de uma máquina segue assim; abra outro arco');
536
+ }
537
+ }
538
+ /** A proposta que o dono pode autorizar agora, ou a recusa tipada de por que não. */
539
+ function propostaAutorizavel(d, envelopeSha256, quando) {
540
+ if (d.revogacao)
541
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.revogado', 'arco revogado é terminal; abra outro arco');
542
+ if (d.encerramento)
543
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.encerrado', `arco encerrado (${d.encerramento.motivo}) é terminal; abra outro arco`);
544
+ if (!d.ultima)
545
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.sem-autorizacao', 'arco sem proposta');
546
+ if (d.ultima.envelopeSha256 !== envelopeSha256) {
547
+ const conhecida = [...d.propostas.values()].some(p => p.envelopeSha256 === envelopeSha256);
548
+ throw new noctum_envelope_1.RecusaDoArco(conhecida ? 'noctum.efeito.hash-antigo' : 'noctum.envelope.invalido', conhecida ? `a proposta mais nova é ${d.ultima.envelopeSha256}` : 'hash sem proposta neste arco');
549
+ }
550
+ if (!(Date.parse(quando) < Date.parse(d.ultima.prazo))) {
551
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', `proposta vencida em ${(0, horario_1.formatarDataHora)(d.ultima.prazo)}; proponha de novo`);
552
+ }
553
+ if (!(Date.parse(quando) < Date.parse(d.ultima.conteudo.janela.fim))) {
554
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.expirado', `a janela terminou em ${d.ultima.conteudo.janela.fim}`);
555
+ }
556
+ if (d.autorizacao?.versao !== d.ultima.versao)
557
+ exigirEmendaSemProrrogar(d, d.ultima.conteudo, quando);
558
+ return d.ultima;
559
+ }
560
+ /**
561
+ * A prova de origem do envelope assinado, pela mesma `autenticarResposta` do HITL: chave do canal,
562
+ * allowlists, conta homologada e janela de 60 s. O texto assinado precisa ser exatamente o ato.
563
+ * O callback nativo ainda amarra o hash do pedido, o contexto e o prazo no próprio corpo.
564
+ */
565
+ function provarEnvelope(arco, texto, envelope, quando, amarra) {
566
+ if (!objeto(envelope) || (envelope.origem !== 'telegram' && envelope.origem !== 'native')) {
567
+ throw semProva('o ingresso precisa ser o envelope assinado do Telegram ou do callback nativo do host');
568
+ }
569
+ try {
570
+ (0, hitl_gates_1.autenticarResposta)(exports.ALVO_DO_NOCTUM, (0, exports.enderecoDoArco)(arco), envelope, quando);
571
+ }
572
+ catch (e) {
573
+ throw semProva(`ingresso não autenticado (${e.message})`);
574
+ }
575
+ if (typeof envelope.resposta !== 'string' || envelope.resposta.trim() !== texto) {
576
+ throw semProva(`o texto assinado não é "${texto}"`);
577
+ }
578
+ const base = { canal: envelope.canal ?? null, conta: envelope.conta ?? null, por: envelope.por,
579
+ mensagem: envelope.mensagem, recebidoEm: envelope.recebidoEm, recibo: sha(envelope.prova) };
580
+ if (envelope.origem === 'telegram')
581
+ return { origem: 'telegram', ...base };
582
+ const native = hitl_native_1.nativeContextSchema.parse(envelope.native);
583
+ if (native.pedidoSha256 !== amarra.pedidoSha256 || native.context !== amarra.contexto ||
584
+ (amarra.prazo !== undefined && native.expiresAt !== amarra.prazo)) {
585
+ throw semProva('o callback nativo assinou outro pedido, contexto ou prazo');
586
+ }
587
+ return { origem: 'native', ...base, native: { ...native } };
588
+ }
589
+ /** O corpo do recibo durável: o que o ingresso provou e para qual ato, sem o texto assinado. */
590
+ function dadosDoRecibo(arco, ato, envelopeSha256, versao, i) {
591
+ return { contrato: 'ork.noctum-ingresso/v1', arco, endereco: (0, exports.enderecoDoArco)(arco), ato, envelopeSha256, versao,
592
+ origem: i.origem, canal: i.canal, conta: i.conta, por: i.por, mensagem: i.mensagem, recebidoEm: i.recebidoEm,
593
+ recibo: i.recibo, ...(i.origem === 'native' ? { native: i.native } : {}),
594
+ // RM-057 (resposta curta, D7): a pergunta e o endereço que o gateway assinou entram no MAC do canal.
595
+ ...(i.origem === 'telegram' && i.pergunta ? { pergunta: i.pergunta } : {}) };
596
+ }
597
+ function lerPrivado(file) {
598
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
599
+ try {
600
+ const st = fs.fstatSync(fd);
601
+ if (!st.isFile() || st.nlink !== 1 || st.uid !== process.getuid?.() || (st.mode & 0o077) !== 0) {
602
+ throw adulterado('recibo do ingresso não é arquivo privado do núcleo');
603
+ }
604
+ return fs.readFileSync(fd);
605
+ }
606
+ finally {
607
+ fs.closeSync(fd);
608
+ }
609
+ }
610
+ const nomeDoRecibo = (i) => `${sha(i.mensagem)}.json`;
611
+ /**
612
+ * O recibo durável do envelope assinado, no formato que o HITL assina e publica: `dados` com o MAC
613
+ * da chave do canal e, ao lado, o `.public` que deixa reconferir sem a chave. É ele que a conferência
614
+ * antes do efeito relê (como o FX2 faz no gate): quem tem só a chave local do núcleo não o forja.
615
+ */
616
+ function gravarRecibo(raiz, arco, ato, envelopeSha256, versao, i) {
617
+ const dir = path.join(dirDoArco(raiz, arco), 'ingresso');
618
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
619
+ const nome = nomeDoRecibo(i), file = path.join(dir, nome);
620
+ const canal = i.canal === null ? null : (0, hitl_canais_1.parseCanal)(i.canal);
621
+ const dados = dadosDoRecibo(arco, ato, envelopeSha256, versao, i);
622
+ const chave = i.origem === 'native' ? (0, hitl_native_1.nativeKey)(String(canal)) : (0, hitl_ingress_receipt_1.chaveDoIngresso)(canal);
623
+ const bytes = JSON.stringify({ dados, assinatura: (0, node_crypto_1.createHmac)('sha256', chave).update(JSON.stringify(dados)).digest('hex') });
624
+ const temp = path.join(dir, `${(0, node_crypto_1.randomUUID)()}.tmp`);
625
+ const fd = fs.openSync(temp, 'wx', 0o600);
626
+ try {
627
+ fs.writeFileSync(fd, bytes);
628
+ fs.fsyncSync(fd);
629
+ }
630
+ finally {
631
+ fs.closeSync(fd);
632
+ }
633
+ try {
634
+ fs.linkSync(temp, file);
635
+ }
636
+ catch (e) {
637
+ if (e.code !== 'EEXIST')
638
+ throw e;
639
+ }
640
+ finally {
641
+ fs.unlinkSync(temp);
642
+ }
643
+ if (lerPrivado(file).toString('utf8') !== bytes)
644
+ throw adulterado('recibo do ingresso já existe com outro conteúdo');
645
+ (0, hitl_public_receipt_1.signPublicReceipt)(file, canal, i.origem === 'native');
646
+ return { evidencia: `.orkastery/noctum/arcos/${arco}/ingresso/${nome}`, evidenciaSha256: sha(bytes) };
647
+ }
648
+ /**
649
+ * Reconfere o recibo durável do envelope assinado, com a chave do canal quando o processo a tem, ou
650
+ * com o verificador público herdado (`ORK_RECEIPT_VERIFIERS`). Chave presente e inválida reprova; sem
651
+ * chave e sem verificador, a prova não pode ser reconferida aqui.
652
+ */
653
+ function reciboConfere(raiz, arco, ato, envelopeSha256, versao, i) {
654
+ if (i.origem === 'mcp-local')
655
+ return 'ok';
656
+ if (i.origem === 'terminal')
657
+ return reciboDoTerminalConfere(raiz, arco, ato, envelopeSha256, versao, i);
658
+ try {
659
+ const nome = nomeDoRecibo(i);
660
+ if (i.evidencia !== `.orkastery/noctum/arcos/${arco}/ingresso/${nome}`)
661
+ return 'falhou';
662
+ const file = path.join(dirDoArco(raiz, arco), 'ingresso', nome);
663
+ const bytes = lerPrivado(file);
664
+ if (sha(bytes) !== i.evidenciaSha256)
665
+ return 'falhou';
666
+ const v = JSON.parse(bytes.toString('utf8'));
667
+ const { evidencia: _evidencia, evidenciaSha256: _sha, ...assinado } = i;
668
+ if (!objeto(v) || Object.keys(v).sort().join(',') !== 'assinatura,dados' || typeof v.assinatura !== 'string' ||
669
+ !/^[a-f0-9]{64}$/.test(v.assinatura) || (0, noctum_envelope_1.canonico)(v.dados) !== (0, noctum_envelope_1.canonico)(dadosDoRecibo(arco, ato, envelopeSha256, versao, assinado))) {
670
+ return 'falhou';
671
+ }
672
+ const native = i.origem === 'native';
673
+ const canal = i.canal === null ? null : (0, hitl_canais_1.parseCanal)(i.canal);
674
+ if (i.canal !== null && canal !== i.canal)
675
+ return 'falhou';
676
+ const variavel = native ? `ORK_HITL_NATIVE_KEY_${String(canal).toUpperCase()}` : (0, hitl_canais_1.variavelDaChaveDoCanal)(canal);
677
+ if (process.env[variavel] !== undefined) {
678
+ // A conta e o vínculo de agora, como no gate: depois de uma troca, o que a conta antiga autorizou não vale.
679
+ if (i.origem === 'native') {
680
+ if (Object.entries((0, hitl_native_1.nativeBinding)(String(canal))).some(([k, v]) => i.native[k] !== v))
681
+ return 'falhou';
682
+ }
683
+ else
684
+ (0, hitl_ingress_receipt_1.contaDoEnvelope)(canal, i.conta ?? undefined);
685
+ const chave = native ? (0, hitl_native_1.nativeKey)(String(canal)) : (0, hitl_ingress_receipt_1.chaveDoIngresso)(canal);
686
+ return (0, node_crypto_1.timingSafeEqual)(Buffer.from(v.assinatura, 'hex'), (0, node_crypto_1.createHmac)('sha256', chave).update(JSON.stringify(v.dados)).digest())
687
+ ? 'ok' : 'falhou';
688
+ }
689
+ const autoridade = (0, hitl_public_receipt_1.publicReceiptAuthority)(canal, native);
690
+ if (!autoridade)
691
+ return 'sem-verificador';
692
+ let vinculo = i.conta;
693
+ if (i.origem === 'native') {
694
+ const { messageId: _m, context: _c, pedidoSha256: _p, expiresAt: _e, ...identidade } = i.native;
695
+ vinculo = identidade;
696
+ }
697
+ return (0, hitl_public_receipt_1.publicBindingMatches)(autoridade, vinculo) && (0, hitl_public_receipt_1.verifyPublicReceipt)(file, bytes, autoridade) ? 'ok' : 'falhou';
698
+ }
699
+ catch {
700
+ return 'falhou';
701
+ }
702
+ }
703
+ /** Relê o disco depois de gravar: o ato só volta com sucesso quando está no log, inteiro. */
704
+ function confirmado(raiz, arco, quando, vale, ato) {
705
+ const log = lerLog(raiz, arco), estado = estadoDe(arco, derivar(log.linhas), quando);
706
+ if (!log.terminaEmQuebra || !vale(estado))
707
+ throw adulterado(`a ${ato} não ficou gravada no log`);
708
+ return estado;
709
+ }
710
+ /**
711
+ * Autoriza a proposta pendente pelo envelope assinado do dono. Sem prova, nada muda. `info.repetida` diz, decidido sob
712
+ * o lock do arco, que o ato já valia antes desta mensagem (o gateway não confirma de novo ao dono).
713
+ */
714
+ function autorizarPeloIngresso(raiz, arco, envelopeSha256, envelope, quando = new Date().toISOString(), info) {
715
+ return comLockDoArco(raiz, arco, false, () => {
716
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
717
+ const versao = d.ultima?.envelopeSha256 === envelopeSha256 ? d.ultima.versao : 0;
718
+ const provado = provarEnvelope(arco, (0, exports.textoDeAutorizacao)(envelopeSha256), envelope, quando, { pedidoSha256: envelopeSha256, contexto: contextoDoArco(arco, versao), prazo: d.ultima?.prazo });
719
+ // A mesma autorização repetida devolve o que já vale; no arco que já acabou, a recusa tipada chega ao gateway, que
720
+ // responde ao dono, em vez de um ok mudo (RM-058 N6).
721
+ if (d.autorizacao?.envelopeSha256 === envelopeSha256 && !d.revogacao) {
722
+ const atual = estadoDe(arco, d, quando);
723
+ if (atual.situacao === 'encerrado') {
724
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.encerrado', `arco ${arco} encerrado (${d.encerramento?.motivo ?? 'fechado'}) é terminal; abra outro arco`);
725
+ }
726
+ if (atual.situacao === 'expirado') {
727
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.expirado', `o arco ${arco} terminou em ${atual.conteudo?.janela.fim ?? 'o fim da janela'}; abra outro arco`);
728
+ }
729
+ if (info)
730
+ info.repetida = true;
731
+ return atual;
732
+ }
733
+ if (d.usadas.has(`${provado.origem}:${provado.mensagem}`))
734
+ throw semProva('esta mensagem já provou outro ato neste arco');
735
+ const proposta = propostaAutorizavel(d, envelopeSha256, quando);
736
+ const ingresso = { ...provado, ...gravarRecibo(raiz, arco, 'autorizar', envelopeSha256, proposta.versao, provado) };
737
+ anexar(raiz, arco, log, { tipo: 'autorizado', versao: proposta.versao, envelopeSha256, ingresso }, quando);
738
+ return confirmado(raiz, arco, quando, e => e.envelopeSha256 === envelopeSha256 && e.situacao !== 'revogado', 'autorização');
739
+ });
740
+ }
741
+ /** Revoga o arco pelo envelope assinado do dono. Terminal: nenhum efeito passa depois (D7). `info.repetida`: já valia. */
742
+ function revogarPeloIngresso(raiz, arco, envelope, quando = new Date().toISOString(), info) {
743
+ return comLockDoArco(raiz, arco, false, () => {
744
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
745
+ const provado = provarEnvelope(arco, exports.TEXTO_DE_REVOGACAO, envelope, quando, { pedidoSha256: hashDaRevogacao(arco), contexto: contextoDoArco(arco, 0) });
746
+ if (d.revogacao) {
747
+ if (info)
748
+ info.repetida = true;
749
+ return estadoDe(arco, d, quando);
750
+ }
751
+ if (d.usadas.has(`${provado.origem}:${provado.mensagem}`))
752
+ throw semProva('esta mensagem já provou outro ato neste arco');
753
+ // O recibo da revogação é auditoria e nunca é reconferido: falha nele não segura o ato de segurança, e o motivo fica na linha.
754
+ let recibo;
755
+ try {
756
+ recibo = gravarRecibo(raiz, arco, 'revogar', null, null, provado);
757
+ }
758
+ // Só o motivo tipado: a mensagem crua do erro pode trazer caminho da máquina, e o relatório da manhã lê esta linha.
759
+ catch (e) {
760
+ recibo = { evidencia: '', evidenciaSha256: '', semRecibo: e instanceof noctum_envelope_1.RecusaDoArco ? e.motivo : 'noctum.recibo.nao-gravado' };
761
+ }
762
+ const ingresso = { ...provado, ...recibo };
763
+ anexar(raiz, arco, log, { tipo: 'revogado', ingresso }, quando);
764
+ return confirmado(raiz, arco, quando, e => e.situacao === 'revogado', 'revogação');
765
+ }, true);
766
+ }
767
+ // ---------------------------------------------------------------------------
768
+ // RM-057 (resposta curta, ork-rm057respost): os atos do dono pela resposta curta à pergunta do arco.
769
+ // ---------------------------------------------------------------------------
770
+ /** O domínio da subchave local que assina o recibo do terminal: o mesmo segredo do recibo local, separado por domínio. */
771
+ const DOMINIO_DO_RECIBO_DO_TERMINAL = 'ork.noctum-terminal/v1';
772
+ /** A resposta curta já lida pelo núcleo: o código da pergunta, a letra e o hash do texto do dono. */
773
+ function validarRespostaAPergunta(r) {
774
+ if (!objeto(r) || typeof r.codigo !== 'string' || !pulse_consentimento_1.CODIGO_CURTO.test(r.codigo) || !['a', 'b', 'c'].includes(String(r.letra)) ||
775
+ typeof r.respostaSha256 !== 'string' || !/^[a-f0-9]{64}$/.test(r.respostaSha256) ||
776
+ (r.numero !== undefined && (!Number.isInteger(r.numero) || r.numero < 1 || r.numero > 99))) {
777
+ throw semProva('a resposta curta chegou malformada');
778
+ }
779
+ return { codigo: r.codigo, letra: r.letra, respostaSha256: r.respostaSha256,
780
+ ...(r.numero !== undefined ? { numero: r.numero } : {}) };
781
+ }
782
+ /**
783
+ * A prova da resposta curta. Pelo canal, a MESMA `autenticarResposta` do HITL, no endereço do pulse que o gateway assinou
784
+ * (`resposta`, ou `resposta:<código>` no reply), no instante do envelope e no de agora, com o texto assinado igual ao que o
785
+ * núcleo leu. Pelo terminal, o atestado da origem da sessão, conferido por `noctum-terminal.ts` (D5): a fronteira dele é
786
+ * a conta do sistema, como a do diálogo nativo.
787
+ */
788
+ function provarRespostaCurta(prova, quando) {
789
+ if (!objeto(prova))
790
+ throw semProva('a resposta curta precisa de prova');
791
+ const pergunta = validarRespostaAPergunta(prova.pergunta);
792
+ if (prova.via === 'pulse') {
793
+ const e = prova.envelope;
794
+ if (!objeto(e) || e.origem !== 'telegram')
795
+ throw semProva('a resposta curta pelo canal precisa do envelope assinado do Telegram');
796
+ // GO-FIX 1 (achado 4): o endereço assinado é o do pulse ou o do reply à MESMA pergunta, `resposta:<código dela>:<id>`;
797
+ // o recibo nunca diz uma pergunta e autoriza outra.
798
+ const ref = typeof prova.endereco === 'string' && prova.endereco.startsWith(`${pulse_consentimento_1.ENDERECO_DA_RESPOSTA}:`)
799
+ ? (0, pulse_consentimento_1.lerReferenciaDoReply)(prova.endereco.slice(pulse_consentimento_1.ENDERECO_DA_RESPOSTA.length + 1)) : null;
800
+ if (prova.endereco !== pulse_consentimento_1.ENDERECO_DA_RESPOSTA && ref?.codigo !== pergunta.codigo) {
801
+ throw semProva('o endereço assinado não é o do pulse nem o do reply a esta pergunta');
802
+ }
803
+ try {
804
+ (0, hitl_gates_1.autenticarResposta)(pulse_consentimento_1.ALVO_DO_PULSE, prova.endereco, e, e.recebidoEm);
805
+ (0, hitl_gates_1.autenticarResposta)(pulse_consentimento_1.ALVO_DO_PULSE, prova.endereco, e, quando);
806
+ }
807
+ catch (x) {
808
+ throw semProva(`ingresso não autenticado (${x.message})`);
809
+ }
810
+ if (typeof e.resposta !== 'string' || sha(e.resposta) !== pergunta.respostaSha256) {
811
+ throw semProva('o texto assinado não é a resposta que o núcleo leu');
812
+ }
813
+ return { origem: 'telegram', canal: e.canal ?? null, conta: e.conta ?? null, por: e.por, mensagem: e.mensagem,
814
+ recebidoEm: e.recebidoEm, recibo: sha(e.prova), pergunta: { ...pergunta, enderecoAssinado: { alvo: pulse_consentimento_1.ALVO_DO_PULSE, endereco: prova.endereco } } };
815
+ }
816
+ if (prova.via === 'terminal') {
817
+ const a = prova.atestado;
818
+ const idade = objeto(a) ? Date.parse(quando) - Date.parse(String(a.recebidoEm)) : NaN;
819
+ if (!objeto(a) || a.host !== 'claude-code' || typeof a.sessao !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(a.sessao) ||
820
+ (a.entrypoint !== null && (typeof a.entrypoint !== 'string' || a.entrypoint.length > 64)) || !Array.isArray(a.conferencias) ||
821
+ !a.conferencias.length || a.conferencias.some(c => typeof c !== 'string' || c.length > 80) ||
822
+ !Number.isFinite(idade) || idade < 0 || idade > 60000) {
823
+ throw semProva('o atestado do terminal chegou malformado ou fora da janela de 60 s');
824
+ }
825
+ return { origem: 'terminal', host: 'claude-code', sessao: a.sessao, entrypoint: a.entrypoint, conferencias: [...a.conferencias],
826
+ recebidoEm: a.recebidoEm, solicitacao: (0, node_crypto_1.randomUUID)(), por: 'terminal:claude-code', pergunta };
827
+ }
828
+ throw semProva('a resposta curta chegou por uma via desconhecida');
829
+ }
830
+ /** A subchave do recibo do terminal. */
831
+ function chaveDoTerminal(raiz, criar) {
832
+ return (0, node_crypto_1.createHmac)('sha256', (0, hitl_local_receipt_1.chaveLocal)(raiz, criar)).update(DOMINIO_DO_RECIBO_DO_TERMINAL).digest();
833
+ }
834
+ const nomeDoReciboDoTerminal = (i) => `${sha(`terminal:${i.solicitacao}`)}.json`;
835
+ function dadosDoReciboDoTerminal(arco, ato, envelopeSha256, versao, i) {
836
+ return { contrato: 'ork.noctum-ingresso/v1', arco, endereco: (0, exports.enderecoDoArco)(arco), ato, envelopeSha256, versao, origem: 'terminal',
837
+ host: i.host, sessao: i.sessao, entrypoint: i.entrypoint, conferencias: i.conferencias, solicitacao: i.solicitacao, por: i.por,
838
+ recebidoEm: i.recebidoEm, pergunta: i.pergunta };
839
+ }
840
+ /** O recibo do terminal, com a subchave local: a conferência antes do efeito o relê, como relê o do canal. */
841
+ function gravarReciboDoTerminal(raiz, arco, ato, envelopeSha256, versao, i) {
842
+ const dir = path.join(dirDoArco(raiz, arco), 'ingresso');
843
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
844
+ const nome = nomeDoReciboDoTerminal(i), file = path.join(dir, nome);
845
+ const dados = dadosDoReciboDoTerminal(arco, ato, envelopeSha256, versao, i);
846
+ const bytes = JSON.stringify({ dados, assinatura: (0, node_crypto_1.createHmac)('sha256', chaveDoTerminal(raiz, true)).update(JSON.stringify(dados)).digest('hex') });
847
+ const temp = path.join(dir, `${(0, node_crypto_1.randomUUID)()}.tmp`);
848
+ const fd = fs.openSync(temp, 'wx', 0o600);
849
+ try {
850
+ fs.writeFileSync(fd, bytes);
851
+ fs.fsyncSync(fd);
852
+ }
853
+ finally {
854
+ fs.closeSync(fd);
855
+ }
856
+ try {
857
+ fs.linkSync(temp, file);
858
+ }
859
+ catch (e) {
860
+ if (e.code !== 'EEXIST')
861
+ throw e;
862
+ }
863
+ finally {
864
+ fs.unlinkSync(temp);
865
+ }
866
+ if (lerPrivado(file).toString('utf8') !== bytes)
867
+ throw adulterado('recibo do terminal já existe com outro conteúdo');
868
+ return { evidencia: `.orkastery/noctum/arcos/${arco}/ingresso/${nome}`, evidenciaSha256: sha(bytes) };
869
+ }
870
+ /** Reconfere o recibo do terminal: o arquivo privado, o hash, os dados iguais aos da linha e o MAC da subchave local. */
871
+ function reciboDoTerminalConfere(raiz, arco, ato, envelopeSha256, versao, i) {
872
+ try {
873
+ const nome = nomeDoReciboDoTerminal(i);
874
+ if (i.evidencia !== `.orkastery/noctum/arcos/${arco}/ingresso/${nome}`)
875
+ return 'falhou';
876
+ const bytes = lerPrivado(path.join(dirDoArco(raiz, arco), 'ingresso', nome));
877
+ if (sha(bytes) !== i.evidenciaSha256)
878
+ return 'falhou';
879
+ const v = JSON.parse(bytes.toString('utf8'));
880
+ const { evidencia: _evidencia, evidenciaSha256: _sha, semRecibo: _sem, ...atestado } = i;
881
+ if (!objeto(v) || Object.keys(v).sort().join(',') !== 'assinatura,dados' || typeof v.assinatura !== 'string' ||
882
+ !/^[a-f0-9]{64}$/.test(v.assinatura) || (0, noctum_envelope_1.canonico)(v.dados) !== (0, noctum_envelope_1.canonico)(dadosDoReciboDoTerminal(arco, ato, envelopeSha256, versao, atestado))) {
883
+ return 'falhou';
884
+ }
885
+ return (0, node_crypto_1.timingSafeEqual)(Buffer.from(v.assinatura, 'hex'), (0, node_crypto_1.createHmac)('sha256', chaveDoTerminal(raiz, false)).update(JSON.stringify(v.dados)).digest())
886
+ ? 'ok' : 'falhou';
887
+ }
888
+ catch {
889
+ return 'falhou';
890
+ }
891
+ }
892
+ /** GO-FIX 1 (achado 6 da ork-rm057respost): a resposta não é de antes da pergunta; a mensagem velha não responde à nova. */
893
+ function exigirDepoisDaPergunta(provado, criadaEm) {
894
+ if (!(Date.parse(provado.recebidoEm) >= Date.parse(criadaEm)))
895
+ throw semProva('a mensagem é de antes da pergunta que ela diz responder');
896
+ }
897
+ /**
898
+ * GO-FIX 1 (achado 6 da ork-rm057respost): uma mensagem assinada do dono responde a UMA pergunta. A conferência de "uma
899
+ * prova, um ato" do log vale por arco; esta vale para todos: a mensagem é reivindicada num arquivo privado por mensagem,
900
+ * criado de uma vez (link exclusivo) antes do ato. A mesma mensagem de novo, para a mesma pergunta e o mesmo ato, segue ao
901
+ * caminho de sempre (repetida); para outra pergunta, de qualquer arco, é recusada antes de gravar, mesmo dentro dos 60 s
902
+ * da assinatura (a reentrega do gateway depois de reiniciar, ou um processo que reapresente o envelope).
903
+ */
904
+ function reivindicarMensagem(raiz, chave, alvo, quando) {
905
+ const principal = (0, estado_thread_1.raizDoEstado)(raiz);
906
+ (0, estado_thread_1.exigirEstadoSemLink)(principal, ['.orkastery', 'noctum', 'mensagens-usadas']);
907
+ const dir = path.join(principal, '.orkastery', 'noctum', 'mensagens-usadas');
908
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
909
+ const file = path.join(dir, `${sha(chave)}.json`);
910
+ const dados = JSON.stringify({ contrato: 'ork.noctum-mensagem-usada/v1', mensagemSha256: sha(chave), ...alvo, em: quando });
911
+ const temp = path.join(dir, `${(0, node_crypto_1.randomUUID)()}.tmp`);
912
+ const fd = fs.openSync(temp, 'wx', 0o600);
913
+ try {
914
+ fs.writeFileSync(fd, dados);
915
+ fs.fsyncSync(fd);
916
+ }
917
+ finally {
918
+ fs.closeSync(fd);
919
+ }
920
+ try {
921
+ fs.linkSync(temp, file);
922
+ return;
923
+ }
924
+ catch (e) {
925
+ if (e.code !== 'EEXIST')
926
+ throw e;
927
+ }
928
+ finally {
929
+ fs.unlinkSync(temp);
930
+ }
931
+ let antes;
932
+ try {
933
+ antes = JSON.parse(lerPrivado(file).toString('utf8'));
934
+ }
935
+ catch {
936
+ throw semProva('o registro desta mensagem não se lê; nada foi gravado');
937
+ }
938
+ if (!objeto(antes) || antes.arco !== alvo.arco || antes.codigo !== alvo.codigo || antes.ato !== alvo.ato) {
939
+ throw semProva('esta mensagem já respondeu a outra pergunta; mande outra');
940
+ }
941
+ }
942
+ /** O ingresso do dono com o recibo durável da via (o do canal ou o do terminal). */
943
+ function comRecibo(raiz, arco, ato, envelopeSha256, versao, provado) {
944
+ return provado.origem === 'terminal'
945
+ ? { ...provado, ...gravarReciboDoTerminal(raiz, arco, ato, envelopeSha256, versao, provado) }
946
+ : { ...provado, ...gravarRecibo(raiz, arco, ato, envelopeSha256, versao, provado) };
947
+ }
948
+ /**
949
+ * RM-057 (resposta curta): autoriza a proposta pela resposta curta do dono à pergunta dela. O hash vem da pergunta (a
950
+ * proposta com este código), nunca da resposta. Sob o lock do arco: a pergunta tem de ser a de agora (a proposta mais
951
+ * nova, no prazo e na janela), a prova não pode ter servido a outro ato, e o ato só volta com sucesso relido do disco.
952
+ */
953
+ function autorizarPelaResposta(raiz, arco, codigo, prova, quando = new Date().toISOString(), info) {
954
+ return comLockDoArco(raiz, arco, false, () => {
955
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
956
+ const proposta = [...d.propostas.values()].find(p => p.codigo === codigo);
957
+ if (!proposta)
958
+ throw new noctum_envelope_1.RecusaDoArco('noctum.pergunta.desconhecida', `o arco ${arco} não tem a pergunta ${codigo}`);
959
+ const provado = provarRespostaCurta(prova, quando);
960
+ if (provado.pergunta?.codigo !== codigo)
961
+ throw semProva('a prova responde a outra pergunta');
962
+ exigirDepoisDaPergunta(provado, proposta.em);
963
+ if (provado.origem === 'telegram')
964
+ reivindicarMensagem(raiz, chaveDaProva(provado), { arco, codigo, ato: 'autorizar' }, quando);
965
+ if (d.autorizacao?.envelopeSha256 === proposta.envelopeSha256 && d.autorizacao.versao === proposta.versao && !d.revogacao) {
966
+ const atual = estadoDe(arco, d, quando);
967
+ if (atual.situacao === 'encerrado') {
968
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.encerrado', `arco ${arco} encerrado (${d.encerramento?.motivo ?? 'fechado'}) é terminal; abra outro arco`);
969
+ }
970
+ if (atual.situacao === 'expirado') {
971
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.expirado', `o arco ${arco} terminou em ${atual.conteudo?.janela.fim ?? 'o fim da janela'}; abra outro arco`);
972
+ }
973
+ if (info) {
974
+ info.repetida = true;
975
+ info.mesmaProva = chaveDaProva(d.autorizacao.ingresso) === chaveDaProva(provado);
976
+ }
977
+ return atual;
978
+ }
979
+ if (d.usadas.has(chaveDaProva(provado)))
980
+ throw semProva('esta mensagem já provou outro ato neste arco');
981
+ // A pergunta respondida é a de agora: a proposta mais nova, no prazo e na janela (a recusa diz por que não).
982
+ const autorizavel = propostaAutorizavel(d, proposta.envelopeSha256, quando);
983
+ const ingresso = comRecibo(raiz, arco, 'autorizar', autorizavel.envelopeSha256, autorizavel.versao, provado);
984
+ anexar(raiz, arco, log, { tipo: 'autorizado', versao: autorizavel.versao, envelopeSha256: autorizavel.envelopeSha256, ingresso }, quando);
985
+ return confirmado(raiz, arco, quando, e => e.envelopeSha256 === autorizavel.envelopeSha256 && e.situacao !== 'revogado', 'autorização');
986
+ });
987
+ }
988
+ /**
989
+ * RM-057 (resposta curta, D10): revoga o arco pela resposta curta do dono à pergunta de revogação. Terminal, como a
990
+ * revogação assinada. A pergunta tem de ser a última de revogação, no prazo; o recibo é auditoria e nunca segura o ato.
991
+ */
992
+ function revogarPelaResposta(raiz, arco, codigo, prova, quando = new Date().toISOString(), info) {
993
+ return comLockDoArco(raiz, arco, false, () => {
994
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
995
+ const pedida = d.revogacoesPedidas.find(r => r.codigo === codigo);
996
+ if (!pedida)
997
+ throw new noctum_envelope_1.RecusaDoArco('noctum.pergunta.desconhecida', `o arco ${arco} não tem a pergunta ${codigo}`);
998
+ const provado = provarRespostaCurta(prova, quando);
999
+ if (provado.pergunta?.codigo !== codigo)
1000
+ throw semProva('a prova responde a outra pergunta');
1001
+ exigirDepoisDaPergunta(provado, pedida.em);
1002
+ if (provado.origem === 'telegram')
1003
+ reivindicarMensagem(raiz, chaveDaProva(provado), { arco, codigo, ato: 'revogar' }, quando);
1004
+ if (d.revogacao) {
1005
+ if (info) {
1006
+ info.repetida = true;
1007
+ info.mesmaProva = chaveDaProva(d.revogacao) === chaveDaProva(provado);
1008
+ }
1009
+ return estadoDe(arco, d, quando);
1010
+ }
1011
+ if (d.revogacoesPedidas.at(-1)?.codigo !== codigo) {
1012
+ throw new noctum_envelope_1.RecusaDoArco('noctum.pergunta.substituida', `a pergunta ${codigo} foi substituída por outra de revogação`);
1013
+ }
1014
+ if (!(Date.parse(quando) < Date.parse(pedida.prazo))) {
1015
+ throw new noctum_envelope_1.RecusaDoArco('noctum.pergunta.vencida', `a pergunta ${codigo} venceu em ${(0, horario_1.formatarDataHora)(pedida.prazo)}; peça a revogação de novo`);
1016
+ }
1017
+ if (d.usadas.has(chaveDaProva(provado)))
1018
+ throw semProva('esta mensagem já provou outro ato neste arco');
1019
+ let recibo;
1020
+ try {
1021
+ const comProva = comRecibo(raiz, arco, 'revogar', null, null, provado);
1022
+ recibo = { evidencia: comProva.evidencia, evidenciaSha256: comProva.evidenciaSha256 };
1023
+ }
1024
+ catch (e) {
1025
+ recibo = { evidencia: '', evidenciaSha256: '', semRecibo: e instanceof noctum_envelope_1.RecusaDoArco ? e.motivo : 'noctum.recibo.nao-gravado' };
1026
+ }
1027
+ anexar(raiz, arco, log, { tipo: 'revogado', ingresso: { ...provado, ...recibo } }, quando);
1028
+ return confirmado(raiz, arco, quando, e => e.situacao === 'revogado', 'revogação');
1029
+ }, true);
1030
+ }
1031
+ /** As opções do diálogo, uma por alternativa da pergunta, com o selo na recomendada. */
1032
+ function opcoesDoDialogo(ato) {
1033
+ return (0, noctum_pergunta_1.alternativasDaPergunta)({ ato }).map(a => `${a.letra}) ${a.texto}${a.porque ? ' (Recomendação)' : ''}`);
1034
+ }
1035
+ /**
1036
+ * O diálogo nativo do host para autorizar ou revogar, com o callback de elicitação fornecido pelo
1037
+ * servidor, nunca pelos argumentos de uma ferramenta. É a mesma fronteira de `criarIngressoLocal`:
1038
+ * a instalação do cliente MCP é confiável; o modelo não responde por ela. A N1 entrega o contrato;
1039
+ * a ferramenta do MCP que o expõe fica para a fatia que liga o transporte (D1).
1040
+ */
1041
+ function criarDialogoDoArco(raiz, origem, elicitar) {
1042
+ if (!['claude-code', 'codex'].includes(origem.host) || !ID_DE_THREAD.test(origem.connectionId)) {
1043
+ throw semProva('diálogo de origem desconhecida');
1044
+ }
1045
+ const conexao = { ...origem };
1046
+ const perguntar = async (a, prazo) => {
1047
+ const controller = new AbortController(), requestId = (0, node_crypto_1.randomUUID)();
1048
+ const espera = Math.min(ESPERA_DO_DIALOGO_MS, prazo === undefined ? ESPERA_DO_DIALOGO_MS : Date.parse(prazo) - Date.now());
1049
+ if (!(espera > 0))
1050
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', 'proposta vencida; proponha de novo');
1051
+ const timer = setTimeout(() => controller.abort(), espera);
1052
+ try {
1053
+ const cancelar = new Promise(r => controller.signal.addEventListener('abort', () => r({ action: 'cancel' }), { once: true }));
1054
+ const r = await Promise.race([elicitar(JSON.parse(JSON.stringify(a)), controller.signal), cancelar]);
1055
+ // Só a primeira alternativa (a recomendada) age; adiar e mudar algo não fazem nada (D13 da ork-rm057respost).
1056
+ if (!objeto(r) || r.action !== 'accept' || !objeto(r.content) || Object.keys(r.content).length !== 1 ||
1057
+ r.content.opcao !== a.opcoes[0])
1058
+ throw semProva('o dono não confirmou pelo diálogo nativo do host');
1059
+ return { requestId, recebidoEm: new Date().toISOString() };
1060
+ }
1061
+ finally {
1062
+ clearTimeout(timer);
1063
+ controller.abort();
1064
+ }
1065
+ };
1066
+ const ingressoDoDialogo = (requestId, recebidoEm, opcao) => ({
1067
+ origem: 'mcp-local', host: conexao.host, conexao: conexao.connectionId, solicitacao: requestId,
1068
+ por: `mcp-local:${conexao.host}`, recebidoEm, respostaSha256: sha(opcao)
1069
+ });
1070
+ return {
1071
+ async autorizar(arco, envelopeSha256) {
1072
+ const antes = comLockDoArco(raiz, arco, false, () => {
1073
+ const d = derivar(lerLog(raiz, arco).linhas);
1074
+ return propostaAutorizavel(d, envelopeSha256, new Date().toISOString());
1075
+ });
1076
+ const resumo = (0, noctum_envelope_1.resumoDoEnvelope)(antes.conteudo, envelopeSha256), quando = new Date().toISOString();
1077
+ const pergunta = { contrato: noctum_pergunta_1.CONTRATO_PERGUNTA_DO_ARCO, arco, ato: 'autorizar', codigo: antes.codigo ?? '',
1078
+ versao: antes.versao, envelopeSha256, criadaEm: antes.em, prazo: antes.prazo, conteudo: antes.conteudo };
1079
+ const a = { contrato: 'ork.noctum-dialogo/v1', arco, ato: 'autorizar', envelopeSha256, resumo,
1080
+ mensagem: [(0, noctum_pergunta_1.textoDoDialogoDoArco)(pergunta, quando), '', ...resumo].join('\n'), opcoes: opcoesDoDialogo('autorizar') };
1081
+ const { requestId, recebidoEm } = await perguntar(a, antes.prazo);
1082
+ // O mundo pode ter mudado enquanto o dono lia: tudo é conferido de novo sob o lock.
1083
+ return comLockDoArcoAssincrono(raiz, arco, () => {
1084
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
1085
+ if (d.autorizacao?.envelopeSha256 === envelopeSha256 && !d.revogacao)
1086
+ return estadoDe(arco, d, recebidoEm);
1087
+ const proposta = propostaAutorizavel(d, envelopeSha256, recebidoEm);
1088
+ anexar(raiz, arco, log, { tipo: 'autorizado', versao: proposta.versao, envelopeSha256,
1089
+ ingresso: ingressoDoDialogo(requestId, recebidoEm, a.opcoes[0]) }, recebidoEm);
1090
+ return confirmado(raiz, arco, recebidoEm, e => e.envelopeSha256 === envelopeSha256 && e.situacao !== 'revogado', 'autorização');
1091
+ });
1092
+ },
1093
+ async revogar(arco) {
1094
+ // Leitura sem lock (o log é lido inteiro e conferido): revogar não pode falhar antes de perguntar ao dono.
1095
+ const atual = lerArco(raiz, arco);
1096
+ if (!atual)
1097
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.sem-autorizacao', `arco ${arco} sem proposta`);
1098
+ const resumo = [`Revogar o arco ${arco}: nenhum efeito novo passa depois disto, e o arco não reabre`];
1099
+ const quando = new Date().toISOString();
1100
+ const pergunta = { contrato: noctum_pergunta_1.CONTRATO_PERGUNTA_DO_ARCO, arco, ato: 'revogar', codigo: atual.revogacaoPedida?.codigo ?? '',
1101
+ versao: atual.versao, envelopeSha256: atual.envelopeSha256, criadaEm: quando,
1102
+ prazo: atual.revogacaoPedida?.prazo ?? new Date(Date.parse(quando) + ESPERA_DO_DIALOGO_MS).toISOString(), conteudo: atual.conteudo };
1103
+ const a = { contrato: 'ork.noctum-dialogo/v1', arco, ato: 'revogar', envelopeSha256: atual.envelopeSha256, resumo,
1104
+ mensagem: [(0, noctum_pergunta_1.textoDoDialogoDoArco)(pergunta, quando), '', ...resumo].join('\n'), opcoes: opcoesDoDialogo('revogar') };
1105
+ const { requestId, recebidoEm } = await perguntar(a, undefined);
1106
+ return comLockDoArcoAssincrono(raiz, arco, () => {
1107
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
1108
+ if (!d.revogacao)
1109
+ anexar(raiz, arco, log, { tipo: 'revogado', ingresso: ingressoDoDialogo(requestId, recebidoEm, a.opcoes[0]) }, recebidoEm);
1110
+ return confirmado(raiz, arco, recebidoEm, e => e.situacao === 'revogado', 'revogação');
1111
+ });
1112
+ },
1113
+ };
1114
+ }
1115
+ const linhaCurta = (v, teto) => typeof v === 'string' && v.trim().length > 0 && v.length <= teto && !/[\r\n]/.test(v);
1116
+ /** RM-057: 3 a 5 alternativas, letras em sequência, exatamente uma recomendada com o porquê. */
1117
+ function validarDecisao(v) {
1118
+ const invalido = (detalhe) => new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', `decisão em lote: ${detalhe}`);
1119
+ if (!objeto(v))
1120
+ throw invalido('precisa ser objeto');
1121
+ const chaves = ['thread', 'acaoImpedida', 'dependentes', 'impacto', 'pergunta', 'alternativas'];
1122
+ const sobra = Object.keys(v).filter(k => !chaves.includes(k));
1123
+ if (sobra.length)
1124
+ throw invalido(`campo fora do contrato: ${sobra.join(', ')} (a resposta é do dono, depois)`);
1125
+ if (typeof v.thread !== 'string' || !ID_DE_THREAD.test(v.thread))
1126
+ throw invalido('thread inválida');
1127
+ for (const campo of ['acaoImpedida', 'impacto', 'pergunta']) {
1128
+ if (!linhaCurta(v[campo], hitl_contract_1.TETOS_HITL_V2.pergunta))
1129
+ throw invalido(`${campo} em uma linha, até ${hitl_contract_1.TETOS_HITL_V2.pergunta}`);
1130
+ }
1131
+ if (!Array.isArray(v.dependentes) || v.dependentes.length > 50 || v.dependentes.some(x => typeof x !== 'string' || !ID_DE_THREAD.test(x))) {
1132
+ throw invalido('dependentes é lista de identificadores');
1133
+ }
1134
+ const alternativas = v.alternativas;
1135
+ if (!Array.isArray(alternativas) || alternativas.length < hitl_contract_1.MINIMO_DE_ALTERNATIVAS_DE_CONDUCAO ||
1136
+ alternativas.length > hitl_contract_1.MAXIMO_DE_ALTERNATIVAS_DE_CONDUCAO) {
1137
+ throw invalido(`de ${hitl_contract_1.MINIMO_DE_ALTERNATIVAS_DE_CONDUCAO} a ${hitl_contract_1.MAXIMO_DE_ALTERNATIVAS_DE_CONDUCAO} alternativas`);
1138
+ }
1139
+ const lidas = alternativas.map((a, i) => {
1140
+ if (!objeto(a) || a.letra !== hitl_contract_1.LETRAS_DE_ALTERNATIVA[i] || !linhaCurta(a.texto, hitl_contract_1.TETOS_HITL_V2.alternativaTexto) ||
1141
+ !linhaCurta(a.consequencia, hitl_contract_1.TETOS_HITL_V2.consequencia) || Object.keys(a).some(k => !['letra', 'texto', 'consequencia', 'recomendada', 'porque'].includes(k))) {
1142
+ throw invalido(`alternativa ${i + 1} fora do contrato`);
1143
+ }
1144
+ if (a.recomendada !== undefined && a.recomendada !== true)
1145
+ throw invalido(`alternativa ${i + 1}: recomendada é true ou ausente`);
1146
+ if (a.recomendada === true && !linhaCurta(a.porque, hitl_contract_1.TETOS_HITL_V2.porque))
1147
+ throw invalido('a recomendada diz o porquê em uma linha');
1148
+ if (a.recomendada !== true && a.porque !== undefined)
1149
+ throw invalido('só a recomendada traz porquê');
1150
+ return { letra: a.letra, texto: a.texto, consequencia: a.consequencia,
1151
+ ...(a.recomendada === true ? { recomendada: true, porque: a.porque } : {}) };
1152
+ });
1153
+ if (lidas.filter(a => a.recomendada).length !== 1)
1154
+ throw invalido('exatamente uma alternativa recomendada');
1155
+ return { thread: v.thread, acaoImpedida: v.acaoImpedida, dependentes: [...v.dependentes],
1156
+ impacto: v.impacto, pergunta: v.pergunta, alternativas: lidas };
1157
+ }
1158
+ /** Id estável: o mesmo arco, thread e ação impedida dão o mesmo id, em qualquer processo. */
1159
+ function idDaDecisao(arco, thread, acaoImpedida) {
1160
+ return 'L-' + sha((0, noctum_envelope_1.canonico)([arco, thread, acaoImpedida])).slice(0, 12);
1161
+ }
1162
+ /**
1163
+ * Guarda na fila do lote o que só o dono resolve. A recomendada não é aplicada e nenhum campo de
1164
+ * resposta existe: a decisão fica pendente até o dono responder pelo ingresso, numa fatia futura.
1165
+ * Vale também depois de revogação ou expiração, porque o lote vai no relatório de qualquer jeito.
1166
+ */
1167
+ function enfileirarDecisao(raiz, arco, pedido, quando = new Date().toISOString()) {
1168
+ const lido = validarDecisao(pedido);
1169
+ return comLockDoArco(raiz, arco, false, () => {
1170
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
1171
+ if (!d.autorizacao)
1172
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.sem-autorizacao', 'arco nunca autorizado não tem lote');
1173
+ const id = idDaDecisao(arco, lido.thread, lido.acaoImpedida);
1174
+ const existente = d.decisoes.find(x => x.id === id);
1175
+ if (existente)
1176
+ return existente;
1177
+ const decisao = { id, arco, enfileiradaEm: quando, estado: 'pendente', ...lido };
1178
+ anexar(raiz, arco, log, { tipo: 'decisao_enfileirada', decisao }, quando);
1179
+ return decisao;
1180
+ });
1181
+ }
1182
+ /** O estado do arco, relido do disco e julgado no instante dado. `null` quando o arco não existe. */
1183
+ function lerArco(raiz, arco, quando = new Date().toISOString()) {
1184
+ const log = lerLog(raiz, arco);
1185
+ return log.linhas.length ? estadoDe(arco, derivar(log.linhas), quando) : null;
1186
+ }
1187
+ /**
1188
+ * RM-058, integração (D3 da ork-rm058integra): a guarda das threads do arco (`noctum-guarda.ts`) lê a lista de trabalhos
1189
+ * do envelope vigente e a da proposta no prazo; o estado só traz o hash e o prazo dela. `null` quando o arco não existe.
1190
+ */
1191
+ function lerArcoComProposta(raiz, arco, quando = new Date().toISOString()) {
1192
+ const log = lerLog(raiz, arco);
1193
+ if (!log.linhas.length)
1194
+ return null;
1195
+ const d = derivar(log.linhas), estado = estadoDe(arco, d, quando);
1196
+ return { estado, proposta: estado.propostaPendente ? d.propostas.get(estado.propostaPendente.versao).conteudo : null };
1197
+ }
1198
+ /**
1199
+ * A conferência imediatamente antes de um efeito. Relê o disco, sem cache, e recusa na ordem:
1200
+ * ação excluída, estado adulterado (inclusive cauda incompleta), revogação, falta de autorização,
1201
+ * recibo do dono que não confere, hash diferente do vigente, janela e destino. Passar aqui é necessário
1202
+ * e não suficiente: as provas do merge são da N5 e o consumo do orçamento é da N2 e da N6 (D8). O
1203
+ * instante é parâmetro para os testes; o executor chama sem ele, com o relógio do processo.
1204
+ */
1205
+ function conferirAntesDoEfeito(raiz, efeito, quando = new Date().toISOString()) {
1206
+ const excluida = (0, noctum_envelope_1.recusaDeExclusao)(efeito?.acao);
1207
+ if (excluida)
1208
+ throw excluida;
1209
+ const { vigente } = vigenteConferido(raiz, efeito.arco, efeito.envelopeSha256, lerLog(raiz, efeito.arco));
1210
+ (0, noctum_envelope_1.avaliarEfeito)(vigente.conteudo, efeito, quando);
1211
+ return { ok: true, arco: efeito.arco, versao: vigente.versao, envelopeSha256: vigente.envelopeSha256,
1212
+ acao: efeito.acao, repositorio: efeito.repositorio, branch: efeito.branch, conferidoEm: quando, fim: vigente.conteudo.janela.fim };
1213
+ }
1214
+ /**
1215
+ * A cadeia comum da conferência antes de um efeito (N1) e antes de um despacho (N2), na ordem da N1: cauda incompleta,
1216
+ * linha adulterada, revogação, falta de autorização, recibo do dono que não confere ou não pode ser reconferido,
1217
+ * conteúdo que diverge do hash e hash diferente do vigente. Relê só o que já foi lido do disco.
1218
+ */
1219
+ function vigenteConferido(raiz, arco, envelopeSha256, log) {
1220
+ // Cauda sem quebra pode ser a revogação que está sendo gravada: na dúvida, nenhum efeito passa.
1221
+ if (!log.terminaEmQuebra)
1222
+ throw adulterado('linha incompleta no fim do log (gravação abortada ou em curso); repita a conferência');
1223
+ const d = derivar(log.linhas);
1224
+ if (d.revogacao)
1225
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.revogado', `arco ${arco} revogado pelo dono`);
1226
+ if (!d.autorizacao)
1227
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.sem-autorizacao', `arco ${arco} sem envelope autorizado`);
1228
+ // RM-058 N6 (D2): encerrado é terminal como o revogado; reserva devolvida depois do teto não o reabre.
1229
+ if (d.encerramento) {
1230
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.encerrado', `arco ${arco} encerrado (${d.encerramento.motivo}) em ${(0, horario_1.formatarDataHora)(d.encerramento.em)}; ` +
1231
+ 'nenhum despacho ou efeito novo passa, e o arco não reabre');
1232
+ }
1233
+ // A prova do dono é reconferida aqui, pelo recibo do canal, e não lida da linha do log (FX2 no gate).
1234
+ const prova = reciboConfere(raiz, arco, 'autorizar', d.autorizacao.envelopeSha256, d.autorizacao.versao, d.autorizacao.ingresso);
1235
+ if (prova === 'sem-verificador') {
1236
+ throw new noctum_envelope_1.RecusaDoArco('noctum.origem.sem-prova', 'a prova do dono não pode ser reconferida neste processo: falta a chave ou o verificador público do canal');
1237
+ }
1238
+ if (prova === 'falhou')
1239
+ throw adulterado('o recibo do ingresso que autorizou o envelope não confere');
1240
+ const vigente = d.propostas.get(d.autorizacao.versao);
1241
+ if ((0, noctum_envelope_1.hashDoEnvelope)(vigente.conteudo) !== d.autorizacao.envelopeSha256)
1242
+ throw adulterado('conteúdo vigente diverge do hash autorizado');
1243
+ if (envelopeSha256 !== d.autorizacao.envelopeSha256) {
1244
+ // Hash de uma emenda que o dono ainda não autorizou não é antigo: é envelope sem autorização.
1245
+ const naoAutorizado = d.ultima !== null && d.ultima.versao > d.autorizacao.versao && d.ultima.envelopeSha256 === envelopeSha256;
1246
+ throw new noctum_envelope_1.RecusaDoArco(naoAutorizado ? 'noctum.efeito.sem-autorizacao' : 'noctum.efeito.hash-antigo', `o envelope vigente é ${d.autorizacao.envelopeSha256}; o efeito trouxe ${String(envelopeSha256)}`);
1247
+ }
1248
+ return { d, vigente };
1249
+ }
1250
+ /** As fases que o arco da N2 nunca despacha: a entrega fica com a condutora (A2 do CHECK da ork-rm058n2). */
1251
+ const FASES_DA_ENTREGA = ['SHIP', 'MASTER'];
1252
+ /** A thread é a do trabalho de pedido? O item dela mais o sha256 do pedido guardado na criação. Ilegível não é. */
1253
+ function threadDoPedido(raiz, id, item, texto) {
1254
+ try {
1255
+ const t = (0, thread_1.lerThread)(raiz, id);
1256
+ return t.roadmap === item && (0, thread_1.pedidoDaThread)(raiz, t)?.sha256 === (0, noctum_envelope_1.shaDoPedido)(texto);
1257
+ }
1258
+ catch {
1259
+ return false;
1260
+ }
1261
+ }
1262
+ /** As threads que já casam com o trabalho de pedido (o item mais o sha256 do pedido); `null` com thread ilegível. */
1263
+ function threadsDoTrabalho(raiz, item, texto) {
1264
+ const sha = (0, noctum_envelope_1.shaDoPedido)(texto);
1265
+ const achadas = [];
1266
+ for (const id of (0, thread_1.listarIds)(raiz)) {
1267
+ let t;
1268
+ try {
1269
+ t = (0, thread_1.lerThread)(raiz, id);
1270
+ }
1271
+ catch {
1272
+ return null;
1273
+ }
1274
+ if (t.roadmap === item && t.pedido?.sha256 === sha)
1275
+ achadas.push(id);
1276
+ }
1277
+ return achadas;
1278
+ }
1279
+ /**
1280
+ * D11 e D12 da ork-rm058n2: confere o envelope e reserva o despacho, as duas coisas sob o lock do arco, imediatamente
1281
+ * antes do efeito. A cadeia da N1 (`vigenteConferido`) mais a janela, o trabalho na lista `v2`, a conta e o runtime do
1282
+ * envelope e o orçamento: cada despacho reserva uma sessão no teto do arco e nos subtetos da conta e do runtime; teto em
1283
+ * outra unidade confere o consumo medido que o passo traz (`pedido.medido`, D5 da ork-rm058n6) e, sem ele ou com lacuna,
1284
+ * recusa (`sem-medicao`). O trabalho de pedido só despacha a thread que o arco abriu
1285
+ * para ele, e só abre uma pelo GOAL quando nenhuma casa ainda; SHIP e MASTER nunca saem do arco da N2. A revogação usa o
1286
+ * mesmo lock: ou ela vem antes e nada é reservado, ou vem depois e vale para o despacho seguinte.
1287
+ */
1288
+ function reservarDespacho(raiz, pedido, quando = new Date().toISOString()) {
1289
+ return comLockDoArco(raiz, pedido.arco, false, () => {
1290
+ const log = lerLog(raiz, pedido.arco);
1291
+ const { d, vigente } = vigenteConferido(raiz, pedido.arco, pedido.envelopeSha256, log);
1292
+ const c = vigente.conteudo;
1293
+ (0, noctum_envelope_1.avaliarJanela)(c, quando);
1294
+ const fora = (detalhe) => new noctum_envelope_1.RecusaDoArco('noctum.despacho.fora-do-envelope', detalhe);
1295
+ if (!(0, noctum_envelope_1.envelopeComTrabalhos)(c))
1296
+ throw fora('o envelope vigente é v1, sem lista de trabalhos: só uma emenda v2 autorizada pelo dono despacha');
1297
+ const t = c.trabalhos.find(x => x.id === pedido.trabalho);
1298
+ if (!t)
1299
+ throw fora(`o trabalho ${String(pedido.trabalho)} não está na lista do envelope`);
1300
+ if (t.thread !== undefined && pedido.thread !== t.thread)
1301
+ throw fora(`o trabalho ${t.id} é a thread ${t.thread}, não ${String(pedido.thread)}`);
1302
+ // A2 do CHECK: a thread de um trabalho de pedido é a que o arco abriu para ele, nunca outra qualquer.
1303
+ if (t.pedido !== undefined && pedido.thread !== null && !threadDoPedido(raiz, pedido.thread, t.item, t.pedido)) {
1304
+ throw fora(`a thread ${String(pedido.thread)} não é a do trabalho ${t.id}: o vínculo é o item ${t.item} mais o sha256 do pedido do envelope`);
1305
+ }
1306
+ if (FASES_DA_ENTREGA.includes(pedido.fase)) {
1307
+ throw fora(`a fase ${String(pedido.fase)} é entrega: a N2 não despacha SHIP nem MASTER, que ficam com a condutora`);
1308
+ }
1309
+ // S3 da rodada 2: abrir a thread de um trabalho de pedido é pelo GOAL, e só quando nenhuma casa com ele ainda.
1310
+ if (t.pedido !== undefined && pedido.thread === null) {
1311
+ if (pedido.fase !== 'GOAL')
1312
+ throw fora(`o trabalho ${t.id} abre a thread pelo GOAL, não pelo ${String(pedido.fase)}`);
1313
+ const ja = threadsDoTrabalho(raiz, t.item, t.pedido);
1314
+ if (ja === null)
1315
+ throw fora(`há thread ilegível no projeto, e ela pode ser a do trabalho ${t.id}: abrir outra a duplicaria`);
1316
+ if (ja.length)
1317
+ throw fora(`o trabalho ${t.id} já tem a thread ${ja.join(', ')}: o despacho vai por ela`);
1318
+ }
1319
+ const tetoDoRuntime = c.orcamento.porRuntime.find(x => x.runtime === pedido.runtime)?.teto;
1320
+ if (!tetoDoRuntime || !c.capacidade.porRuntime.some(x => x.runtime === pedido.runtime)) {
1321
+ throw fora(`o runtime ${String(pedido.runtime)} não está no envelope`);
1322
+ }
1323
+ const tetoDaConta = c.orcamento.porConta.find(x => x.conta === pedido.conta)?.teto;
1324
+ if (!tetoDaConta || !c.capacidade.porConta.some(x => x.conta === pedido.conta)) {
1325
+ throw fora(`a conta ${String(pedido.conta)} não está no envelope`);
1326
+ }
1327
+ const consumo = consumoDe(d);
1328
+ const tetos = [['do arco', c.orcamento.arco, consumo.arco, 'arco'],
1329
+ [`da conta ${pedido.conta}`, tetoDaConta, consumo.porConta[pedido.conta] ?? 0, 'conta'],
1330
+ [`do runtime ${pedido.runtime}`, tetoDoRuntime, consumo.porRuntime[pedido.runtime] ?? 0, 'runtime']];
1331
+ for (const [nome, teto, usado, chave] of tetos) {
1332
+ if (teto.unidade !== 'sessoes') {
1333
+ // RM-058 N6 (D5): fora de `sessoes`, o consumo medido fora do lock decide; sem medida, ou com lacuna, recusa.
1334
+ const medido = pedido.medido?.[chave];
1335
+ if (!medido || medido.unidade !== teto.unidade || 'lacuna' in medido || !Number.isFinite(medido.valor)) {
1336
+ throw new noctum_envelope_1.RecusaDoArco('noctum.orcamento.sem-medicao', `o teto ${nome} está em ${teto.unidade}, e o consumo nessa unidade não foi ` +
1337
+ `medido para esta reserva${medido && 'lacuna' in medido ? ` (${medido.lacuna})` : ''}: sem medir, o teto não é garantido, e ` +
1338
+ 'consumo desconhecido nunca vale zero');
1339
+ }
1340
+ if (medido.valor >= teto.valor) {
1341
+ const valor = teto.unidade === 'percentual-da-cota' ? `${Math.round(medido.valor * 10) / 10}%` : String(medido.valor);
1342
+ throw new noctum_envelope_1.RecusaDoArco('noctum.orcamento.esgotado', `o teto ${nome} é de ${teto.valor} ${teto.unidade} e o consumo medido é ` +
1343
+ (medido.tetoSuperior ? `até ${valor} (teto superior: inclui uso de fora do arco)` : valor));
1344
+ }
1345
+ continue;
1346
+ }
1347
+ if (usado + 1 > teto.valor) {
1348
+ throw new noctum_envelope_1.RecusaDoArco('noctum.orcamento.esgotado', `o teto ${nome} é de ${teto.valor} sessoes e ${usado} já foram reservadas`);
1349
+ }
1350
+ }
1351
+ const reserva = { trabalho: t.id, thread: pedido.thread, fase: pedido.fase, runtime: pedido.runtime,
1352
+ conta: pedido.conta, revisao: pedido.revisao === true, versao: vigente.versao, envelopeSha256: vigente.envelopeSha256 };
1353
+ anexar(raiz, pedido.arco, log, { tipo: 'despacho_reservado', reserva }, quando);
1354
+ const seq = log.linhas.length + 1;
1355
+ const estado = confirmado(raiz, pedido.arco, quando, e => e.despachos.some(x => x.seq === seq && x.desfecho === 'pendente'), 'reserva do despacho');
1356
+ return { arco: pedido.arco, seq, versao: vigente.versao, envelopeSha256: vigente.envelopeSha256, fim: c.janela.fim, consumo: estado.consumo };
1357
+ }, true);
1358
+ }
1359
+ /**
1360
+ * RM-058 N6 (D2 e D8 da ork-rm058n6): fecha o arco, uma vez, sob o lock do arco (espera como a revogação, porque é ato de
1361
+ * segurança). Depois disto `vigenteConferido` recusa todo despacho e efeito com `noctum.efeito.encerrado`, a emenda é
1362
+ * recusada e a fila encerra. O segundo fechamento devolve o primeiro. Só fecha arco que o dono autorizou; o revogado
1363
+ * também fecha, para o registro do fechamento (a situação continua `revogado`).
1364
+ */
1365
+ function encerrarArco(raiz, arco, fechamento, quando = new Date().toISOString()) {
1366
+ if (!exports.MOTIVOS_DE_ENCERRAMENTO.includes(fechamento?.motivo)) {
1367
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', `motivo de encerramento fora de ${exports.MOTIVOS_DE_ENCERRAMENTO.join(', ')}`);
1368
+ }
1369
+ return comLockDoArco(raiz, arco, false, () => {
1370
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
1371
+ if (!d.autorizacao)
1372
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.sem-autorizacao', `arco ${arco} nunca autorizado não encerra`);
1373
+ if (d.encerramento)
1374
+ return estadoDe(arco, d, quando);
1375
+ anexar(raiz, arco, log, { tipo: 'encerrado', motivo: fechamento.motivo, detalhe: String(fechamento.detalhe ?? '').slice(0, 500) }, quando);
1376
+ return confirmado(raiz, arco, quando, e => e.encerramento !== null, 'encerramento');
1377
+ }, true);
1378
+ }
1379
+ /**
1380
+ * O desfecho da reserva, uma vez só: o despacho que abriu sessão (ou que a sessão em andamento já atendia), ou o que o
1381
+ * núcleo recusou, que devolve a sessão ao orçamento. Vale também depois de revogação ou expiração: é a contabilidade do
1382
+ * que já estava em voo, não um efeito novo.
1383
+ */
1384
+ function concluirDespacho(raiz, arco, seq, desfecho, quando = new Date().toISOString()) {
1385
+ return comLockDoArco(raiz, arco, false, () => {
1386
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
1387
+ const r = d.despachos.get(seq);
1388
+ if (!r)
1389
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', `reserva ${seq} inexistente neste arco`);
1390
+ if (r.desfecho !== 'pendente')
1391
+ return estadoDe(arco, d, quando);
1392
+ const evento = 'motivo' in desfecho
1393
+ ? { tipo: 'despacho_recusado', reserva: seq, motivo: String(desfecho.motivo).slice(0, 200) }
1394
+ : { tipo: 'despacho_concluido', reserva: seq, thread: desfecho.thread, sessionId: desfecho.sessionId, idempotente: desfecho.idempotente };
1395
+ anexar(raiz, arco, log, evento, quando);
1396
+ return confirmado(raiz, arco, quando, e => e.despachos.some(x => x.seq === seq && x.desfecho !== 'pendente'), 'conclusão do despacho');
1397
+ }, true);
1398
+ }
1399
+ /**
1400
+ * RM-058 N3 (D6 da ork-rm058n3): as versões do envelope que o dono autorizou neste arco, na ordem do log. O ramo de
1401
+ * coordenação guarda registros assinados sob versões anteriores (a vaga tomada antes de uma emenda continua contando), e a
1402
+ * leitura confere cada um com as máquinas da versão que ele diz. A vigente é a última; o recibo do dono continua conferido
1403
+ * por `vigenteConferido`, antes de cada efeito.
1404
+ */
1405
+ function versoesAutorizadas(raiz, arco) {
1406
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
1407
+ const vistas = new Set();
1408
+ const versoes = [];
1409
+ for (const { evento: e } of log.linhas) {
1410
+ if (e.tipo !== 'autorizado' || vistas.has(e.versao))
1411
+ continue;
1412
+ vistas.add(e.versao);
1413
+ const p = d.propostas.get(e.versao);
1414
+ // RM-058, integração (T8, D12): o instante do ingresso de cada autorização, que o aceite do piloto lê.
1415
+ versoes.push({ versao: p.versao, envelopeSha256: p.envelopeSha256, conteudo: p.conteudo, autorizadoEm: e.ingresso.recebidoEm });
1416
+ }
1417
+ return versoes;
1418
+ }