@orkastery/cli 0.5.3 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/adapters/claude-code/README.md +158 -0
  2. package/adapters/claude-code/mods/autoridade.json +56 -0
  3. package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
  4. package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
  5. package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
  6. package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
  7. package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
  8. package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
  9. package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
  10. package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
  11. package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
  12. package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
  13. package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
  14. package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
  15. package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
  16. package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
  17. package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
  18. package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
  19. package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
  20. package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
  21. package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
  22. package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
  23. package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
  24. package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
  25. package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
  26. package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
  27. package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
  28. package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
  29. package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
  30. package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
  31. package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
  32. package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
  33. package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
  34. package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
  35. package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
  36. package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
  37. package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
  38. package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
  39. package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
  40. package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
  41. package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
  42. package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
  43. package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
  44. package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
  45. package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
  46. package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
  47. package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
  48. package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
  49. package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
  50. package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
  51. package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
  52. package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
  53. package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
  54. package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
  55. package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
  56. package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
  57. package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
  58. package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
  59. package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
  60. package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
  61. package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
  62. package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
  63. package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
  64. package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
  65. package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
  66. package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
  67. package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
  68. package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
  69. package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
  70. package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
  71. package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
  72. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
  73. package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
  74. package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
  75. package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
  76. package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
  77. package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
  78. package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
  79. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +1 -1
  80. package/adapters/openclaw/README.md +2 -1
  81. package/adapters/openclaw/openclaw.plugin.json +1 -1
  82. package/adapters/openclaw/package.json +1 -1
  83. package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
  84. package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
  85. package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
  86. package/assets/docs-en/produto/README.md +14 -0
  87. package/assets/docs-en/produto/_modelo-feature.md +62 -0
  88. package/assets/docs-en/roadmap/README.md +14 -0
  89. package/assets/docs-en/roadmap/_modelo-item.md +75 -0
  90. package/dist/adapters/claude-bg.js +77 -13
  91. package/dist/adapters/codex.js +10 -1
  92. package/dist/agents-md.js +24 -15
  93. package/dist/auditrun.js +17 -2
  94. package/dist/board.js +4 -3
  95. package/dist/branch-de-estado.js +23 -1
  96. package/dist/catalogo.js +33 -0
  97. package/dist/ci.js +13 -7
  98. package/dist/claims-conferir.js +342 -0
  99. package/dist/claims.js +3 -1
  100. package/dist/company-brain-client.js +36 -1
  101. package/dist/company-brain-journal.js +2 -1
  102. package/dist/company-brain-migration.js +36 -1
  103. package/dist/conducao-sentinela.js +388 -0
  104. package/dist/conducao-texto.js +12 -18
  105. package/dist/conducao.js +122 -30
  106. package/dist/conhecimento.js +490 -0
  107. package/dist/docs.js +22 -8
  108. package/dist/doctor-pulse-cron.js +182 -27
  109. package/dist/doctor-rede.js +18 -17
  110. package/dist/doctor.js +115 -111
  111. package/dist/entrega-pr.js +22 -21
  112. package/dist/escopo-escrita.js +10 -1
  113. package/dist/estado-thread.js +23 -1
  114. package/dist/experiencia-instalacao.js +45 -25
  115. package/dist/fabrica-batida.js +310 -0
  116. package/dist/fabrica-estado.js +38 -8
  117. package/dist/fabrica-publicar.js +3 -1
  118. package/dist/fila-pesada.js +444 -0
  119. package/dist/fix.js +3 -2
  120. package/dist/forja.js +1 -1
  121. package/dist/gates.js +3 -28
  122. package/dist/handoff.js +10 -6
  123. package/dist/hitl-local-receipt.js +2 -0
  124. package/dist/hitl-resumo.js +29 -3
  125. package/dist/hitl-tempo-parado.js +4 -4
  126. package/dist/hitl.js +29 -33
  127. package/dist/horario.js +14 -7
  128. package/dist/hosts.js +52 -1
  129. package/dist/impedimento.js +108 -14
  130. package/dist/index.js +623 -521
  131. package/dist/indice.js +7 -1
  132. package/dist/init.js +43 -47
  133. package/dist/instabilidade.js +144 -0
  134. package/dist/integracoes-locais.js +2 -0
  135. package/dist/intelligence-graph-cli.js +92 -21
  136. package/dist/intelligence-graph-contexto.js +56 -17
  137. package/dist/intelligence-graph-contract.js +2 -1
  138. package/dist/intelligence-graph-extract-md.js +56 -4
  139. package/dist/intelligence-graph-extract-ts.js +71 -3
  140. package/dist/intelligence-graph-query.js +66 -19
  141. package/dist/ledger.js +15 -1
  142. package/dist/liveness.js +6 -3
  143. package/dist/locale-ajuda.js +761 -0
  144. package/dist/locale-gerados.js +160 -0
  145. package/dist/locale-master.js +99 -0
  146. package/dist/locale-onboarding.js +92 -0
  147. package/dist/locale-phase.js +151 -0
  148. package/dist/locale-pulse.js +211 -0
  149. package/dist/locale-retry.js +385 -0
  150. package/dist/locale-roadmap.js +102 -0
  151. package/dist/locale-ship.js +494 -0
  152. package/dist/locale-thread.js +277 -0
  153. package/dist/locale-verify.js +125 -0
  154. package/dist/locale.js +714 -0
  155. package/dist/manifest.js +129 -3
  156. package/dist/maquina.js +23 -4
  157. package/dist/master-digest.js +6 -1
  158. package/dist/master-nota.js +4 -6
  159. package/dist/master.js +21 -24
  160. package/dist/mcp-artifacts.js +11 -2
  161. package/dist/mcp-git.js +23 -6
  162. package/dist/mcp-grafo.js +7 -4
  163. package/dist/mcp-server.js +8 -2
  164. package/dist/mcp-ship.js +3 -1
  165. package/dist/mcp-verify.js +6 -1
  166. package/dist/modos.js +3 -6
  167. package/dist/network-roadmap.js +5 -2
  168. package/dist/noctum-arco.js +911 -0
  169. package/dist/noctum-capacidade.js +141 -0
  170. package/dist/noctum-envelope.js +810 -0
  171. package/dist/noctum-execucao.js +304 -0
  172. package/dist/noctum-fila.js +280 -0
  173. package/dist/ocupacao.js +29 -2
  174. package/dist/onboarding.js +40 -21
  175. package/dist/orkmind.js +9 -2
  176. package/dist/orquestracao.js +13 -12
  177. package/dist/parado-no-condutor.js +34 -9
  178. package/dist/phase.js +94 -39
  179. package/dist/policies.js +4 -4
  180. package/dist/portfolio.js +4 -2
  181. package/dist/postura-local.js +234 -0
  182. package/dist/preflight.js +11 -10
  183. package/dist/procedencia.js +225 -0
  184. package/dist/projeto-alvo.js +127 -4
  185. package/dist/prompts.js +6 -2
  186. package/dist/pulse-delivery.js +13 -2
  187. package/dist/pulse.js +52 -19
  188. package/dist/ratelimit.js +4 -2
  189. package/dist/redacao-saida.js +63 -0
  190. package/dist/rede-status.js +3 -2
  191. package/dist/retry.js +232 -148
  192. package/dist/roadmap-reservas.js +20 -3
  193. package/dist/roadmap-status.js +129 -33
  194. package/dist/runtime-context.js +181 -3
  195. package/dist/runtimes.js +2 -1
  196. package/dist/session-events.js +7 -3
  197. package/dist/session-watcher-claude.js +7 -1
  198. package/dist/session-watcher.js +185 -33
  199. package/dist/sessoes-contas.js +1 -1
  200. package/dist/ship-publicar.js +800 -0
  201. package/dist/ship.js +84 -81
  202. package/dist/steal.js +77 -0
  203. package/dist/thread-close.js +3 -2
  204. package/dist/thread.js +153 -82
  205. package/dist/util.js +8 -1
  206. package/dist/verify.js +141 -52
  207. package/dist/vida-da-thread.js +254 -0
  208. package/dist/worktree-local.js +165 -0
  209. package/dist/worktree.js +20 -0
  210. package/dist/yaml.js +27 -8
  211. package/package.json +1 -1
  212. package/schemas/code-artifact-graph.v1.schema.json +2 -1
@@ -0,0 +1,911 @@
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.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.autorizarPeloIngresso = autorizarPeloIngresso;
41
+ exports.revogarPeloIngresso = revogarPeloIngresso;
42
+ exports.criarDialogoDoArco = criarDialogoDoArco;
43
+ exports.idDaDecisao = idDaDecisao;
44
+ exports.enfileirarDecisao = enfileirarDecisao;
45
+ exports.lerArco = lerArco;
46
+ exports.conferirAntesDoEfeito = conferirAntesDoEfeito;
47
+ exports.reservarDespacho = reservarDespacho;
48
+ exports.concluirDespacho = concluirDespacho;
49
+ /**
50
+ * RM-058, fatia N1: o estado do arco #Noctum e o ingresso do envelope.
51
+ *
52
+ * O estado mora num log append-only em `.orkastery/noctum/arcos/<arco>/arco.jsonl`, na raiz do
53
+ * estado. Cada linha leva o sha256 da anterior e um MAC de subchave derivada da chave local do
54
+ * núcleo (`.orkastery/private`, a mesma fronteira do recibo local do HITL), e linha trocada reprova
55
+ * toda leitura. Essa chave fica legível para qualquer processo com o mesmo usuário do sistema, que é
56
+ * o caso dos agentes da fábrica: o MAC do log não os impede de escrever uma linha. Uma linha que se diz
57
+ * do Telegram ou do callback nativo ainda precisa do recibo do canal, que a conferência antes do efeito
58
+ * 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:
59
+ * a fronteira dela é a conta do sistema, como no recibo local do HITL, e com a chave local ela passa.
60
+ * Truncar o log também segue possível, como no ledger (D5 da ork-rm058n1, limites ditos na página).
61
+ *
62
+ * Proposta não autoriza nada. O envelope só passa a valer por ingresso com prova de origem do
63
+ * dono, pelo mesmo caminho do HITL: o envelope assinado do Telegram ou do callback nativo, que
64
+ * `autenticarResposta` confere no endereço `noctum` / `arco:<arco>` (D1), ou o diálogo nativo
65
+ * do host, por callback de elicitação que o servidor fornece. Argumento de ferramenta nunca é
66
+ * resposta.
67
+ *
68
+ * Nada aqui guarda estado em memória: toda leitura relê o disco e todo veredito usa o instante
69
+ * dado. Por isso revogação e expiração valem na chamada seguinte, e restart não renova nada.
70
+ */
71
+ const fs = __importStar(require("node:fs"));
72
+ const path = __importStar(require("node:path"));
73
+ const node_crypto_1 = require("node:crypto");
74
+ const estado_thread_1 = require("./estado-thread");
75
+ const horario_1 = require("./horario");
76
+ const hitl_canais_1 = require("./hitl-canais");
77
+ const hitl_gates_1 = require("./hitl-gates");
78
+ const hitl_ingress_receipt_1 = require("./hitl-ingress-receipt");
79
+ const hitl_contract_1 = require("./hitl-contract");
80
+ const hitl_lock_1 = require("./hitl-lock");
81
+ const hitl_local_receipt_1 = require("./hitl-local-receipt");
82
+ const hitl_native_1 = require("./hitl-native");
83
+ const hitl_public_receipt_1 = require("./hitl-public-receipt");
84
+ const noctum_envelope_1 = require("./noctum-envelope");
85
+ const thread_1 = require("./thread");
86
+ exports.CONTRATO_ARCO = 'ork.noctum-arco/v1';
87
+ /**
88
+ * D1 da ork-rm058n1: o endereço que o ingresso assina, nas posições de thread e de pedido. O
89
+ * `:` não cabe no identificador que `/ork gate` e `/ork session` aceitam, e o pulse assina outro
90
+ * alvo: nenhum envelope de gate, de sessão ou do pulse serve de prova aqui, nem o contrário.
91
+ */
92
+ exports.ALVO_DO_NOCTUM = 'noctum';
93
+ const enderecoDoArco = (arco) => `arco:${arco}`;
94
+ exports.enderecoDoArco = enderecoDoArco;
95
+ /** O texto que o dono assina. Fechado: qualquer outro não autoriza nem revoga. */
96
+ const textoDeAutorizacao = (envelopeSha256) => `autorizar ${envelopeSha256}`;
97
+ exports.textoDeAutorizacao = textoDeAutorizacao;
98
+ exports.TEXTO_DE_REVOGACAO = 'revogar';
99
+ /** Uma hora para autorizar a proposta, como o pedido HITL. Vencida, ela não autoriza mais nada. */
100
+ exports.PRAZO_PARA_AUTORIZAR_MIN = 60;
101
+ /** O diálogo do host espera no máximo isto, e nunca além do prazo da proposta. */
102
+ const ESPERA_DO_DIALOGO_MS = 300000;
103
+ const ZERO = '0'.repeat(64);
104
+ const ARQUIVO = 'arco.jsonl';
105
+ const sha = (s) => (0, node_crypto_1.createHash)('sha256').update(s).digest('hex');
106
+ const adulterado = (detalhe) => new noctum_envelope_1.RecusaDoArco('noctum.estado.adulterado', `${detalhe}; o estado do arco exige inspeção`);
107
+ const semProva = (detalhe) => new noctum_envelope_1.RecusaDoArco('noctum.origem.sem-prova', detalhe);
108
+ const objeto = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
109
+ const ID_DE_THREAD = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/;
110
+ /** Subchave própria do arco: o mesmo segredo do recibo local, separado por domínio. */
111
+ function chaveDoArco(raiz, criar) {
112
+ return (0, node_crypto_1.createHmac)('sha256', (0, hitl_local_receipt_1.chaveLocal)(raiz, criar)).update(exports.CONTRATO_ARCO).digest();
113
+ }
114
+ function dirDoArco(raiz, arco) {
115
+ if (typeof arco !== 'string' || !noctum_envelope_1.ID_DO_ARCO.test(arco))
116
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', 'identificador de arco inválido');
117
+ const principal = (0, estado_thread_1.raizDoEstado)(raiz);
118
+ (0, estado_thread_1.exigirEstadoSemLink)(principal, ['.orkastery', 'noctum', 'arcos', arco]);
119
+ return path.join(principal, '.orkastery', 'noctum', 'arcos', arco);
120
+ }
121
+ /** O contexto que o callback nativo amarra, por arco e versão (ou zero, na revogação). */
122
+ function contextoDoArco(arco, versao) {
123
+ return sha(JSON.stringify([exports.CONTRATO_ARCO, arco, versao]));
124
+ }
125
+ /** O `pedidoSha256` que o callback nativo assina para revogar: não é hash de envelope nenhum. */
126
+ function hashDaRevogacao(arco) {
127
+ return sha(JSON.stringify(['ork.noctum-revogacao/v1', arco]));
128
+ }
129
+ /**
130
+ * Lê e confere o log inteiro. Linha sem quebra no fim está sendo gravada: fica de fora e não
131
+ * dá autoridade a ninguém. Qualquer outra divergência (MAC, cadeia, sequência, arco) reprova.
132
+ */
133
+ function lerLog(raiz, arco) {
134
+ const file = path.join(dirDoArco(raiz, arco), ARQUIVO);
135
+ let bruto;
136
+ try {
137
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
138
+ try {
139
+ const st = fs.fstatSync(fd);
140
+ if (!st.isFile() || st.nlink !== 1 || st.uid !== process.getuid?.() || (st.mode & 0o077) !== 0) {
141
+ throw adulterado('log do arco não é arquivo privado do núcleo');
142
+ }
143
+ bruto = fs.readFileSync(fd);
144
+ }
145
+ finally {
146
+ fs.closeSync(fd);
147
+ }
148
+ }
149
+ catch (e) {
150
+ if (e.code === 'ENOENT') {
151
+ return { linhas: [], ultimaLinha: null, terminaEmQuebra: true, bytesCompletos: 0, bytesLidos: 0 };
152
+ }
153
+ if (e instanceof noctum_envelope_1.RecusaDoArco)
154
+ throw e;
155
+ throw adulterado('log do arco ilegível');
156
+ }
157
+ // Em bytes, não em caracteres: cauda que corta um caractere de vários bytes não desalinha os tamanhos.
158
+ const fim = bruto.lastIndexOf(0x0a) + 1;
159
+ const textos = bruto.subarray(0, fim).toString('utf8').split('\n').slice(0, -1);
160
+ const tamanhos = { terminaEmQuebra: fim === bruto.length, bytesCompletos: fim, bytesLidos: bruto.length };
161
+ if (!textos.length)
162
+ return { linhas: [], ultimaLinha: null, ...tamanhos };
163
+ let chave;
164
+ try {
165
+ chave = chaveDoArco(raiz, false);
166
+ }
167
+ catch {
168
+ throw adulterado('chave do núcleo ausente para conferir o log');
169
+ }
170
+ let anterior = ZERO;
171
+ const linhas = textos.map((texto, i) => {
172
+ let l;
173
+ try {
174
+ l = JSON.parse(texto);
175
+ }
176
+ catch {
177
+ throw adulterado(`linha ${i + 1} não é JSON`);
178
+ }
179
+ if (!objeto(l) || Object.keys(l).sort().join(',') !== 'dados,mac' || typeof l.mac !== 'string' || !/^[a-f0-9]{64}$/.test(l.mac)) {
180
+ throw adulterado(`linha ${i + 1} fora do formato`);
181
+ }
182
+ const esperado = (0, node_crypto_1.createHmac)('sha256', chave).update(JSON.stringify(l.dados)).digest();
183
+ if (!(0, node_crypto_1.timingSafeEqual)(Buffer.from(l.mac, 'hex'), esperado))
184
+ throw adulterado(`linha ${i + 1} com MAC que não confere`);
185
+ const d = l.dados;
186
+ if (d.contrato !== exports.CONTRATO_ARCO || d.arco !== arco || d.seq !== i + 1 || d.anterior !== anterior) {
187
+ throw adulterado(`linha ${i + 1} fora da cadeia`);
188
+ }
189
+ anterior = sha(texto);
190
+ return d;
191
+ });
192
+ return { linhas, ultimaLinha: textos.at(-1), ...tamanhos };
193
+ }
194
+ /**
195
+ * Grava uma linha. Só roda sob o lock do arco, depois de `lerLog`.
196
+ *
197
+ * Sob o lock, cauda sem quebra de linha é escrita abortada (queda, disco cheio) ou lixo: nunca deu
198
+ * autoridade a ninguém, e é descartada antes da linha nova. Sem isso, uma cauda rasgada impediria a
199
+ * revogação do dono. Escrita curta volta o arquivo ao tamanho de antes e é acusada: o chamador nunca
200
+ * recebe sucesso de uma linha que não ficou inteira no disco.
201
+ */
202
+ function anexar(raiz, arco, log, evento, quando) {
203
+ const dados = { contrato: exports.CONTRATO_ARCO, arco, seq: log.linhas.length + 1,
204
+ anterior: log.ultimaLinha === null ? ZERO : sha(log.ultimaLinha), em: quando, evento };
205
+ const mac = (0, node_crypto_1.createHmac)('sha256', chaveDoArco(raiz, true)).update(JSON.stringify(dados)).digest('hex');
206
+ const linha = Buffer.from(JSON.stringify({ dados, mac }) + '\n', 'utf8');
207
+ const file = path.join(dirDoArco(raiz, arco), ARQUIVO);
208
+ const fd = fs.openSync(file, fs.constants.O_WRONLY | fs.constants.O_APPEND | fs.constants.O_CREAT | fs.constants.O_NOFOLLOW, 0o600);
209
+ try {
210
+ if (fs.fstatSync(fd).size !== log.bytesLidos)
211
+ throw adulterado('o log mudou entre a leitura e a escrita sob o lock');
212
+ if (log.bytesLidos !== log.bytesCompletos)
213
+ fs.ftruncateSync(fd, log.bytesCompletos);
214
+ const escritos = fs.writeSync(fd, linha);
215
+ if (escritos !== linha.length) {
216
+ fs.ftruncateSync(fd, log.bytesCompletos);
217
+ fs.fsyncSync(fd);
218
+ throw adulterado(`escrita curta no log (${escritos} de ${linha.length} bytes); nada foi gravado`);
219
+ }
220
+ fs.fsyncSync(fd);
221
+ }
222
+ finally {
223
+ fs.closeSync(fd);
224
+ }
225
+ }
226
+ /** Tentativas de 100 ms pelo lock: a revogação não perde para um escritor que segura o arco por um instante. */
227
+ const TENTATIVAS_DO_LOCK = 30;
228
+ function dirParaLock(raiz, arco, criar) {
229
+ const dir = dirDoArco(raiz, arco);
230
+ if (criar)
231
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
232
+ else if (!fs.existsSync(dir))
233
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.sem-autorizacao', `arco ${arco} sem proposta`);
234
+ return dir;
235
+ }
236
+ /** Só a disputa pelo kernel espera; qualquer outra falha, inclusive a recusa de dentro, sobe na hora. */
237
+ const disputaDoLock = (e) => e instanceof Error && e.message.startsWith('HITL ocupado; exclusão do kernel');
238
+ /**
239
+ * `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
240
+ * já tem a vez de despachar; o resto falha na hora, sem prender o processo.
241
+ */
242
+ function comLockDoArco(raiz, arco, criar, executar, esperar = false) {
243
+ const dir = dirParaLock(raiz, arco, criar);
244
+ for (let tentativa = 1;; tentativa++) {
245
+ try {
246
+ return (0, hitl_lock_1.comLockInspecionavel)(dir, 'noctum-arco', executar);
247
+ }
248
+ catch (e) {
249
+ if (!esperar || tentativa >= TENTATIVAS_DO_LOCK || !disputaDoLock(e))
250
+ throw e;
251
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
252
+ }
253
+ }
254
+ }
255
+ /** A mesma espera, sem prender o event loop: é a que o diálogo usa dentro do servidor MCP. */
256
+ async function comLockDoArcoAssincrono(raiz, arco, executar) {
257
+ const dir = dirParaLock(raiz, arco, false);
258
+ for (let tentativa = 1;; tentativa++) {
259
+ try {
260
+ return (0, hitl_lock_1.comLockInspecionavel)(dir, 'noctum-arco', executar);
261
+ }
262
+ catch (e) {
263
+ if (tentativa >= TENTATIVAS_DO_LOCK || !disputaDoLock(e))
264
+ throw e;
265
+ await new Promise(r => setTimeout(r, 100));
266
+ }
267
+ }
268
+ }
269
+ function derivar(linhas) {
270
+ const d = { propostas: new Map(), ultima: null, autorizacao: null, revogacao: null, decisoes: [], usadas: new Set(),
271
+ despachos: new Map() };
272
+ const usar = (i) => {
273
+ const prova = i.origem === 'mcp-local' ? `mcp-local:${i.solicitacao}` : `${i.origem}:${i.mensagem}`;
274
+ // Uma prova do dono vale para um ato só: reaproveitada noutra linha, o log foi emendado à mão.
275
+ if (d.usadas.has(prova))
276
+ throw adulterado('a mesma prova do dono aparece em dois atos do log');
277
+ d.usadas.add(prova);
278
+ };
279
+ for (const { evento: e, seq, em } of linhas) {
280
+ if (e.tipo === 'despacho_reservado') {
281
+ d.despachos.set(seq, { ...e.reserva, seq, reservadoEm: em, desfecho: 'pendente', threadDespachada: null, sessionId: null,
282
+ idempotente: false, motivo: null, concluidoEm: null });
283
+ continue;
284
+ }
285
+ if (e.tipo === 'despacho_concluido' || e.tipo === 'despacho_recusado') {
286
+ const r = d.despachos.get(e.reserva);
287
+ // Desfecho sem reserva, ou um segundo desfecho, só existe num log emendado à mão.
288
+ if (!r || r.desfecho !== 'pendente')
289
+ throw adulterado(`desfecho da reserva ${e.reserva} sem reserva pendente`);
290
+ if (e.tipo === 'despacho_concluido')
291
+ Object.assign(r, { desfecho: 'concluido', threadDespachada: e.thread, sessionId: e.sessionId,
292
+ idempotente: e.idempotente, concluidoEm: em });
293
+ else
294
+ Object.assign(r, { desfecho: 'recusado', motivo: e.motivo, concluidoEm: em });
295
+ continue;
296
+ }
297
+ if (e.tipo === 'proposto') {
298
+ const p = { versao: e.versao, envelopeSha256: e.envelopeSha256, conteudo: e.conteudo, prazo: e.prazo };
299
+ if ((0, noctum_envelope_1.hashDoEnvelope)(p.conteudo) !== p.envelopeSha256)
300
+ throw adulterado(`proposta ${p.versao} com hash que não confere`);
301
+ d.propostas.set(p.versao, p);
302
+ d.ultima = p;
303
+ }
304
+ else if (e.tipo === 'autorizado') {
305
+ if (d.propostas.get(e.versao)?.envelopeSha256 !== e.envelopeSha256)
306
+ throw adulterado(`autorização da versão ${e.versao} sem proposta`);
307
+ d.autorizacao = { versao: e.versao, envelopeSha256: e.envelopeSha256, ingresso: e.ingresso };
308
+ usar(e.ingresso);
309
+ }
310
+ else if (e.tipo === 'revogado') {
311
+ d.revogacao = e.ingresso;
312
+ usar(e.ingresso);
313
+ }
314
+ else if (e.tipo === 'decisao_enfileirada')
315
+ d.decisoes.push(e.decisao);
316
+ }
317
+ return d;
318
+ }
319
+ function situacao(d, quando) {
320
+ if (d.revogacao)
321
+ return 'revogado';
322
+ if (!d.autorizacao)
323
+ return 'proposto';
324
+ const janela = d.propostas.get(d.autorizacao.versao).conteudo.janela;
325
+ const agora = Date.parse(quando);
326
+ if (agora < Date.parse(janela.inicio))
327
+ return 'agendado';
328
+ return agora >= Date.parse(janela.fim) ? 'expirado' : 'vigente';
329
+ }
330
+ function estadoDe(arco, d, quando) {
331
+ const vigente = d.autorizacao ? d.propostas.get(d.autorizacao.versao) : null;
332
+ const pendente = d.ultima && !d.revogacao && d.ultima.versao !== d.autorizacao?.versao &&
333
+ Date.parse(quando) < Date.parse(d.ultima.prazo) ? d.ultima : null;
334
+ return { contrato: exports.CONTRATO_ARCO, arco, situacao: situacao(d, quando), versao: vigente?.versao ?? null,
335
+ envelopeSha256: vigente?.envelopeSha256 ?? null, conteudo: vigente?.conteudo ?? null,
336
+ autorizadoPor: d.autorizacao?.ingresso ?? null, revogadoPor: d.revogacao,
337
+ propostaPendente: pendente ? { versao: pendente.versao, envelopeSha256: pendente.envelopeSha256, prazo: pendente.prazo } : null,
338
+ decisoes: d.decisoes, despachos: [...d.despachos.values()], consumo: consumoDe(d) };
339
+ }
340
+ /** D12 da ork-rm058n2: a reserva consome uma sessão até ser recusada; a concluída e a pendente contam. */
341
+ function consumoDe(d) {
342
+ const c = { arco: 0, porConta: {}, porRuntime: {} };
343
+ for (const r of d.despachos.values()) {
344
+ if (r.desfecho === 'recusado')
345
+ continue;
346
+ c.arco += 1;
347
+ c.porConta[r.conta] = (c.porConta[r.conta] ?? 0) + 1;
348
+ c.porRuntime[r.runtime] = (c.porRuntime[r.runtime] ?? 0) + 1;
349
+ }
350
+ return c;
351
+ }
352
+ /**
353
+ * Grava a proposta de um envelope. Não autoriza nada: quem autoriza é o dono, pelo ingresso.
354
+ *
355
+ * A mesma proposta repetida devolve a que está pendente. Proposta nova sobre um envelope já
356
+ * autorizado é emenda: o vigente continua valendo até o dono autorizar a nova versão.
357
+ */
358
+ function proporEnvelope(raiz, pedido, quando = new Date().toISOString()) {
359
+ const conteudo = (0, noctum_envelope_1.validarPedidoDeAbertura)(pedido);
360
+ const envelopeSha256 = (0, noctum_envelope_1.hashDoEnvelope)(conteudo);
361
+ if (!(Date.parse(quando) < Date.parse(conteudo.janela.fim))) {
362
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', `a janela pedida terminou em ${conteudo.janela.fim}`);
363
+ }
364
+ return comLockDoArco(raiz, conteudo.arco, true, () => {
365
+ const log = lerLog(raiz, conteudo.arco), d = derivar(log.linhas);
366
+ if (d.revogacao)
367
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.revogado', 'arco revogado é terminal; abra outro arco');
368
+ const resumo = (0, noctum_envelope_1.resumoDoEnvelope)(conteudo, envelopeSha256);
369
+ const pendente = estadoDe(conteudo.arco, d, quando).propostaPendente;
370
+ if (pendente?.envelopeSha256 === envelopeSha256)
371
+ return { arco: conteudo.arco, ...pendente, resumo };
372
+ if (d.autorizacao?.envelopeSha256 === envelopeSha256) {
373
+ const vigente = d.propostas.get(d.autorizacao.versao);
374
+ return { arco: conteudo.arco, versao: vigente.versao, envelopeSha256, prazo: vigente.prazo, resumo };
375
+ }
376
+ exigirEmendaSemProrrogar(d, conteudo, quando);
377
+ const versao = (d.ultima?.versao ?? 0) + 1;
378
+ const prazo = new Date(Date.parse(quando) + exports.PRAZO_PARA_AUTORIZAR_MIN * 60000).toISOString();
379
+ anexar(raiz, conteudo.arco, log, { tipo: 'proposto', versao, envelopeSha256, conteudo, prazo }, quando);
380
+ return { arco: conteudo.arco, versao, envelopeSha256, prazo, resumo };
381
+ });
382
+ }
383
+ /**
384
+ * D7: emenda é versão nova com prova nova, mas não prorroga nada. Ela mantém o início, não passa do
385
+ * fim do envelope autorizado e não reabre arco que já terminou: arco novo é outro arco.
386
+ */
387
+ function exigirEmendaSemProrrogar(d, conteudo, quando) {
388
+ if (!d.autorizacao)
389
+ return;
390
+ const janela = d.propostas.get(d.autorizacao.versao).conteudo.janela;
391
+ if (!(Date.parse(quando) < Date.parse(janela.fim))) {
392
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.expirado', `o arco terminou em ${janela.fim}; emenda não reabre arco, abra outro`);
393
+ }
394
+ if (Date.parse(conteudo.janela.inicio) !== Date.parse(janela.inicio) || Date.parse(conteudo.janela.fim) > Date.parse(janela.fim)) {
395
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', 'emenda mantém o início e não prorroga o fim do envelope autorizado');
396
+ }
397
+ }
398
+ /** A proposta que o dono pode autorizar agora, ou a recusa tipada de por que não. */
399
+ function propostaAutorizavel(d, envelopeSha256, quando) {
400
+ if (d.revogacao)
401
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.revogado', 'arco revogado é terminal; abra outro arco');
402
+ if (!d.ultima)
403
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.sem-autorizacao', 'arco sem proposta');
404
+ if (d.ultima.envelopeSha256 !== envelopeSha256) {
405
+ const conhecida = [...d.propostas.values()].some(p => p.envelopeSha256 === envelopeSha256);
406
+ 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');
407
+ }
408
+ if (!(Date.parse(quando) < Date.parse(d.ultima.prazo))) {
409
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', `proposta vencida em ${(0, horario_1.formatarDataHora)(d.ultima.prazo)}; proponha de novo`);
410
+ }
411
+ if (!(Date.parse(quando) < Date.parse(d.ultima.conteudo.janela.fim))) {
412
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.expirado', `a janela terminou em ${d.ultima.conteudo.janela.fim}`);
413
+ }
414
+ if (d.autorizacao?.versao !== d.ultima.versao)
415
+ exigirEmendaSemProrrogar(d, d.ultima.conteudo, quando);
416
+ return d.ultima;
417
+ }
418
+ /**
419
+ * A prova de origem do envelope assinado, pela mesma `autenticarResposta` do HITL: chave do canal,
420
+ * allowlists, conta homologada e janela de 60 s. O texto assinado precisa ser exatamente o ato.
421
+ * O callback nativo ainda amarra o hash do pedido, o contexto e o prazo no próprio corpo.
422
+ */
423
+ function provarEnvelope(arco, texto, envelope, quando, amarra) {
424
+ if (!objeto(envelope) || (envelope.origem !== 'telegram' && envelope.origem !== 'native')) {
425
+ throw semProva('o ingresso precisa ser o envelope assinado do Telegram ou do callback nativo do host');
426
+ }
427
+ try {
428
+ (0, hitl_gates_1.autenticarResposta)(exports.ALVO_DO_NOCTUM, (0, exports.enderecoDoArco)(arco), envelope, quando);
429
+ }
430
+ catch (e) {
431
+ throw semProva(`ingresso não autenticado (${e.message})`);
432
+ }
433
+ if (typeof envelope.resposta !== 'string' || envelope.resposta.trim() !== texto) {
434
+ throw semProva(`o texto assinado não é "${texto}"`);
435
+ }
436
+ const base = { canal: envelope.canal ?? null, conta: envelope.conta ?? null, por: envelope.por,
437
+ mensagem: envelope.mensagem, recebidoEm: envelope.recebidoEm, recibo: sha(envelope.prova) };
438
+ if (envelope.origem === 'telegram')
439
+ return { origem: 'telegram', ...base };
440
+ const native = hitl_native_1.nativeContextSchema.parse(envelope.native);
441
+ if (native.pedidoSha256 !== amarra.pedidoSha256 || native.context !== amarra.contexto ||
442
+ (amarra.prazo !== undefined && native.expiresAt !== amarra.prazo)) {
443
+ throw semProva('o callback nativo assinou outro pedido, contexto ou prazo');
444
+ }
445
+ return { origem: 'native', ...base, native: { ...native } };
446
+ }
447
+ /** O corpo do recibo durável: o que o ingresso provou e para qual ato, sem o texto assinado. */
448
+ function dadosDoRecibo(arco, ato, envelopeSha256, versao, i) {
449
+ return { contrato: 'ork.noctum-ingresso/v1', arco, endereco: (0, exports.enderecoDoArco)(arco), ato, envelopeSha256, versao,
450
+ origem: i.origem, canal: i.canal, conta: i.conta, por: i.por, mensagem: i.mensagem, recebidoEm: i.recebidoEm,
451
+ recibo: i.recibo, ...(i.origem === 'native' ? { native: i.native } : {}) };
452
+ }
453
+ function lerPrivado(file) {
454
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
455
+ try {
456
+ const st = fs.fstatSync(fd);
457
+ if (!st.isFile() || st.nlink !== 1 || st.uid !== process.getuid?.() || (st.mode & 0o077) !== 0) {
458
+ throw adulterado('recibo do ingresso não é arquivo privado do núcleo');
459
+ }
460
+ return fs.readFileSync(fd);
461
+ }
462
+ finally {
463
+ fs.closeSync(fd);
464
+ }
465
+ }
466
+ const nomeDoRecibo = (i) => `${sha(i.mensagem)}.json`;
467
+ /**
468
+ * O recibo durável do envelope assinado, no formato que o HITL assina e publica: `dados` com o MAC
469
+ * da chave do canal e, ao lado, o `.public` que deixa reconferir sem a chave. É ele que a conferência
470
+ * antes do efeito relê (como o FX2 faz no gate): quem tem só a chave local do núcleo não o forja.
471
+ */
472
+ function gravarRecibo(raiz, arco, ato, envelopeSha256, versao, i) {
473
+ const dir = path.join(dirDoArco(raiz, arco), 'ingresso');
474
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
475
+ const nome = nomeDoRecibo(i), file = path.join(dir, nome);
476
+ const canal = i.canal === null ? null : (0, hitl_canais_1.parseCanal)(i.canal);
477
+ const dados = dadosDoRecibo(arco, ato, envelopeSha256, versao, i);
478
+ const chave = i.origem === 'native' ? (0, hitl_native_1.nativeKey)(String(canal)) : (0, hitl_ingress_receipt_1.chaveDoIngresso)(canal);
479
+ const bytes = JSON.stringify({ dados, assinatura: (0, node_crypto_1.createHmac)('sha256', chave).update(JSON.stringify(dados)).digest('hex') });
480
+ const temp = path.join(dir, `${(0, node_crypto_1.randomUUID)()}.tmp`);
481
+ const fd = fs.openSync(temp, 'wx', 0o600);
482
+ try {
483
+ fs.writeFileSync(fd, bytes);
484
+ fs.fsyncSync(fd);
485
+ }
486
+ finally {
487
+ fs.closeSync(fd);
488
+ }
489
+ try {
490
+ fs.linkSync(temp, file);
491
+ }
492
+ catch (e) {
493
+ if (e.code !== 'EEXIST')
494
+ throw e;
495
+ }
496
+ finally {
497
+ fs.unlinkSync(temp);
498
+ }
499
+ if (lerPrivado(file).toString('utf8') !== bytes)
500
+ throw adulterado('recibo do ingresso já existe com outro conteúdo');
501
+ (0, hitl_public_receipt_1.signPublicReceipt)(file, canal, i.origem === 'native');
502
+ return { evidencia: `.orkastery/noctum/arcos/${arco}/ingresso/${nome}`, evidenciaSha256: sha(bytes) };
503
+ }
504
+ /**
505
+ * Reconfere o recibo durável do envelope assinado, com a chave do canal quando o processo a tem, ou
506
+ * com o verificador público herdado (`ORK_RECEIPT_VERIFIERS`). Chave presente e inválida reprova; sem
507
+ * chave e sem verificador, a prova não pode ser reconferida aqui.
508
+ */
509
+ function reciboConfere(raiz, arco, ato, envelopeSha256, versao, i) {
510
+ if (i.origem === 'mcp-local')
511
+ return 'ok';
512
+ try {
513
+ const nome = nomeDoRecibo(i);
514
+ if (i.evidencia !== `.orkastery/noctum/arcos/${arco}/ingresso/${nome}`)
515
+ return 'falhou';
516
+ const file = path.join(dirDoArco(raiz, arco), 'ingresso', nome);
517
+ const bytes = lerPrivado(file);
518
+ if (sha(bytes) !== i.evidenciaSha256)
519
+ return 'falhou';
520
+ const v = JSON.parse(bytes.toString('utf8'));
521
+ const { evidencia: _evidencia, evidenciaSha256: _sha, ...assinado } = i;
522
+ if (!objeto(v) || Object.keys(v).sort().join(',') !== 'assinatura,dados' || typeof v.assinatura !== 'string' ||
523
+ !/^[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))) {
524
+ return 'falhou';
525
+ }
526
+ const native = i.origem === 'native';
527
+ const canal = i.canal === null ? null : (0, hitl_canais_1.parseCanal)(i.canal);
528
+ if (i.canal !== null && canal !== i.canal)
529
+ return 'falhou';
530
+ const variavel = native ? `ORK_HITL_NATIVE_KEY_${String(canal).toUpperCase()}` : (0, hitl_canais_1.variavelDaChaveDoCanal)(canal);
531
+ if (process.env[variavel] !== undefined) {
532
+ // A conta e o vínculo de agora, como no gate: depois de uma troca, o que a conta antiga autorizou não vale.
533
+ if (i.origem === 'native') {
534
+ if (Object.entries((0, hitl_native_1.nativeBinding)(String(canal))).some(([k, v]) => i.native[k] !== v))
535
+ return 'falhou';
536
+ }
537
+ else
538
+ (0, hitl_ingress_receipt_1.contaDoEnvelope)(canal, i.conta ?? undefined);
539
+ const chave = native ? (0, hitl_native_1.nativeKey)(String(canal)) : (0, hitl_ingress_receipt_1.chaveDoIngresso)(canal);
540
+ return (0, node_crypto_1.timingSafeEqual)(Buffer.from(v.assinatura, 'hex'), (0, node_crypto_1.createHmac)('sha256', chave).update(JSON.stringify(v.dados)).digest())
541
+ ? 'ok' : 'falhou';
542
+ }
543
+ const autoridade = (0, hitl_public_receipt_1.publicReceiptAuthority)(canal, native);
544
+ if (!autoridade)
545
+ return 'sem-verificador';
546
+ let vinculo = i.conta;
547
+ if (i.origem === 'native') {
548
+ const { messageId: _m, context: _c, pedidoSha256: _p, expiresAt: _e, ...identidade } = i.native;
549
+ vinculo = identidade;
550
+ }
551
+ return (0, hitl_public_receipt_1.publicBindingMatches)(autoridade, vinculo) && (0, hitl_public_receipt_1.verifyPublicReceipt)(file, bytes, autoridade) ? 'ok' : 'falhou';
552
+ }
553
+ catch {
554
+ return 'falhou';
555
+ }
556
+ }
557
+ /** Relê o disco depois de gravar: o ato só volta com sucesso quando está no log, inteiro. */
558
+ function confirmado(raiz, arco, quando, vale, ato) {
559
+ const log = lerLog(raiz, arco), estado = estadoDe(arco, derivar(log.linhas), quando);
560
+ if (!log.terminaEmQuebra || !vale(estado))
561
+ throw adulterado(`a ${ato} não ficou gravada no log`);
562
+ return estado;
563
+ }
564
+ /** Autoriza a proposta pendente pelo envelope assinado do dono. Sem prova, nada muda. */
565
+ function autorizarPeloIngresso(raiz, arco, envelopeSha256, envelope, quando = new Date().toISOString()) {
566
+ return comLockDoArco(raiz, arco, false, () => {
567
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
568
+ const versao = d.ultima?.envelopeSha256 === envelopeSha256 ? d.ultima.versao : 0;
569
+ const provado = provarEnvelope(arco, (0, exports.textoDeAutorizacao)(envelopeSha256), envelope, quando, { pedidoSha256: envelopeSha256, contexto: contextoDoArco(arco, versao), prazo: d.ultima?.prazo });
570
+ // A mesma autorização repetida, pela mesma mensagem, devolve o que já vale.
571
+ if (d.autorizacao?.envelopeSha256 === envelopeSha256 && !d.revogacao)
572
+ return estadoDe(arco, d, quando);
573
+ if (d.usadas.has(`${provado.origem}:${provado.mensagem}`))
574
+ throw semProva('esta mensagem já provou outro ato neste arco');
575
+ const proposta = propostaAutorizavel(d, envelopeSha256, quando);
576
+ const ingresso = { ...provado, ...gravarRecibo(raiz, arco, 'autorizar', envelopeSha256, proposta.versao, provado) };
577
+ anexar(raiz, arco, log, { tipo: 'autorizado', versao: proposta.versao, envelopeSha256, ingresso }, quando);
578
+ return confirmado(raiz, arco, quando, e => e.envelopeSha256 === envelopeSha256 && e.situacao !== 'revogado', 'autorização');
579
+ });
580
+ }
581
+ /** Revoga o arco pelo envelope assinado do dono. Terminal: nenhum efeito passa depois (D7). */
582
+ function revogarPeloIngresso(raiz, arco, envelope, quando = new Date().toISOString()) {
583
+ return comLockDoArco(raiz, arco, false, () => {
584
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
585
+ const provado = provarEnvelope(arco, exports.TEXTO_DE_REVOGACAO, envelope, quando, { pedidoSha256: hashDaRevogacao(arco), contexto: contextoDoArco(arco, 0) });
586
+ if (d.revogacao)
587
+ return estadoDe(arco, d, quando);
588
+ if (d.usadas.has(`${provado.origem}:${provado.mensagem}`))
589
+ throw semProva('esta mensagem já provou outro ato neste arco');
590
+ // 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.
591
+ let recibo;
592
+ try {
593
+ recibo = gravarRecibo(raiz, arco, 'revogar', null, null, provado);
594
+ }
595
+ // Só o motivo tipado: a mensagem crua do erro pode trazer caminho da máquina, e o relatório da manhã lê esta linha.
596
+ catch (e) {
597
+ recibo = { evidencia: '', evidenciaSha256: '', semRecibo: e instanceof noctum_envelope_1.RecusaDoArco ? e.motivo : 'noctum.recibo.nao-gravado' };
598
+ }
599
+ const ingresso = { ...provado, ...recibo };
600
+ anexar(raiz, arco, log, { tipo: 'revogado', ingresso }, quando);
601
+ return confirmado(raiz, arco, quando, e => e.situacao === 'revogado', 'revogação');
602
+ }, true);
603
+ }
604
+ /**
605
+ * O diálogo nativo do host para autorizar ou revogar, com o callback de elicitação fornecido pelo
606
+ * servidor, nunca pelos argumentos de uma ferramenta. É a mesma fronteira de `criarIngressoLocal`:
607
+ * a instalação do cliente MCP é confiável; o modelo não responde por ela. A N1 entrega o contrato;
608
+ * a ferramenta do MCP que o expõe fica para a fatia que liga o transporte (D1).
609
+ */
610
+ function criarDialogoDoArco(raiz, origem, elicitar) {
611
+ if (!['claude-code', 'codex'].includes(origem.host) || !ID_DE_THREAD.test(origem.connectionId)) {
612
+ throw semProva('diálogo de origem desconhecida');
613
+ }
614
+ const conexao = { ...origem };
615
+ const perguntar = async (a, prazo) => {
616
+ const controller = new AbortController(), requestId = (0, node_crypto_1.randomUUID)();
617
+ const espera = Math.min(ESPERA_DO_DIALOGO_MS, prazo === undefined ? ESPERA_DO_DIALOGO_MS : Date.parse(prazo) - Date.now());
618
+ if (!(espera > 0))
619
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', 'proposta vencida; proponha de novo');
620
+ const timer = setTimeout(() => controller.abort(), espera);
621
+ try {
622
+ const cancelar = new Promise(r => controller.signal.addEventListener('abort', () => r({ action: 'cancel' }), { once: true }));
623
+ const r = await Promise.race([elicitar(JSON.parse(JSON.stringify(a)), controller.signal), cancelar]);
624
+ if (!objeto(r) || r.action !== 'accept' || !objeto(r.content) || Object.keys(r.content).length !== 1 ||
625
+ r.content.opcao !== a.opcoes[0])
626
+ throw semProva('o dono não confirmou pelo diálogo nativo do host');
627
+ return { requestId, recebidoEm: new Date().toISOString() };
628
+ }
629
+ finally {
630
+ clearTimeout(timer);
631
+ controller.abort();
632
+ }
633
+ };
634
+ const ingressoDoDialogo = (requestId, recebidoEm, opcao) => ({
635
+ origem: 'mcp-local', host: conexao.host, conexao: conexao.connectionId, solicitacao: requestId,
636
+ por: `mcp-local:${conexao.host}`, recebidoEm, respostaSha256: sha(opcao)
637
+ });
638
+ return {
639
+ async autorizar(arco, envelopeSha256) {
640
+ const antes = comLockDoArco(raiz, arco, false, () => {
641
+ const d = derivar(lerLog(raiz, arco).linhas);
642
+ return propostaAutorizavel(d, envelopeSha256, new Date().toISOString());
643
+ });
644
+ const a = { contrato: 'ork.noctum-dialogo/v1', arco, ato: 'autorizar', envelopeSha256,
645
+ resumo: (0, noctum_envelope_1.resumoDoEnvelope)(antes.conteudo, envelopeSha256), opcoes: ['Autorizar este envelope', 'Não autorizar'] };
646
+ const { requestId, recebidoEm } = await perguntar(a, antes.prazo);
647
+ // O mundo pode ter mudado enquanto o dono lia: tudo é conferido de novo sob o lock.
648
+ return comLockDoArcoAssincrono(raiz, arco, () => {
649
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
650
+ if (d.autorizacao?.envelopeSha256 === envelopeSha256 && !d.revogacao)
651
+ return estadoDe(arco, d, recebidoEm);
652
+ const proposta = propostaAutorizavel(d, envelopeSha256, recebidoEm);
653
+ anexar(raiz, arco, log, { tipo: 'autorizado', versao: proposta.versao, envelopeSha256,
654
+ ingresso: ingressoDoDialogo(requestId, recebidoEm, a.opcoes[0]) }, recebidoEm);
655
+ return confirmado(raiz, arco, recebidoEm, e => e.envelopeSha256 === envelopeSha256 && e.situacao !== 'revogado', 'autorização');
656
+ });
657
+ },
658
+ async revogar(arco) {
659
+ // Leitura sem lock (o log é lido inteiro e conferido): revogar não pode falhar antes de perguntar ao dono.
660
+ const atual = lerArco(raiz, arco);
661
+ if (!atual)
662
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.sem-autorizacao', `arco ${arco} sem proposta`);
663
+ const a = { contrato: 'ork.noctum-dialogo/v1', arco, ato: 'revogar', envelopeSha256: atual.envelopeSha256,
664
+ resumo: [`Revogar o arco ${arco}: nenhum efeito novo passa depois disto, e o arco não reabre`],
665
+ opcoes: ['Revogar o arco', 'Manter o arco'] };
666
+ const { requestId, recebidoEm } = await perguntar(a, undefined);
667
+ return comLockDoArcoAssincrono(raiz, arco, () => {
668
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
669
+ if (!d.revogacao)
670
+ anexar(raiz, arco, log, { tipo: 'revogado', ingresso: ingressoDoDialogo(requestId, recebidoEm, a.opcoes[0]) }, recebidoEm);
671
+ return confirmado(raiz, arco, recebidoEm, e => e.situacao === 'revogado', 'revogação');
672
+ });
673
+ },
674
+ };
675
+ }
676
+ const linhaCurta = (v, teto) => typeof v === 'string' && v.trim().length > 0 && v.length <= teto && !/[\r\n]/.test(v);
677
+ /** RM-057: 3 a 5 alternativas, letras em sequência, exatamente uma recomendada com o porquê. */
678
+ function validarDecisao(v) {
679
+ const invalido = (detalhe) => new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', `decisão em lote: ${detalhe}`);
680
+ if (!objeto(v))
681
+ throw invalido('precisa ser objeto');
682
+ const chaves = ['thread', 'acaoImpedida', 'dependentes', 'impacto', 'pergunta', 'alternativas'];
683
+ const sobra = Object.keys(v).filter(k => !chaves.includes(k));
684
+ if (sobra.length)
685
+ throw invalido(`campo fora do contrato: ${sobra.join(', ')} (a resposta é do dono, depois)`);
686
+ if (typeof v.thread !== 'string' || !ID_DE_THREAD.test(v.thread))
687
+ throw invalido('thread inválida');
688
+ for (const campo of ['acaoImpedida', 'impacto', 'pergunta']) {
689
+ if (!linhaCurta(v[campo], hitl_contract_1.TETOS_HITL_V2.pergunta))
690
+ throw invalido(`${campo} em uma linha, até ${hitl_contract_1.TETOS_HITL_V2.pergunta}`);
691
+ }
692
+ if (!Array.isArray(v.dependentes) || v.dependentes.length > 50 || v.dependentes.some(x => typeof x !== 'string' || !ID_DE_THREAD.test(x))) {
693
+ throw invalido('dependentes é lista de identificadores');
694
+ }
695
+ const alternativas = v.alternativas;
696
+ if (!Array.isArray(alternativas) || alternativas.length < hitl_contract_1.MINIMO_DE_ALTERNATIVAS_DE_CONDUCAO ||
697
+ alternativas.length > hitl_contract_1.MAXIMO_DE_ALTERNATIVAS_DE_CONDUCAO) {
698
+ throw invalido(`de ${hitl_contract_1.MINIMO_DE_ALTERNATIVAS_DE_CONDUCAO} a ${hitl_contract_1.MAXIMO_DE_ALTERNATIVAS_DE_CONDUCAO} alternativas`);
699
+ }
700
+ const lidas = alternativas.map((a, i) => {
701
+ if (!objeto(a) || a.letra !== hitl_contract_1.LETRAS_DE_ALTERNATIVA[i] || !linhaCurta(a.texto, hitl_contract_1.TETOS_HITL_V2.alternativaTexto) ||
702
+ !linhaCurta(a.consequencia, hitl_contract_1.TETOS_HITL_V2.consequencia) || Object.keys(a).some(k => !['letra', 'texto', 'consequencia', 'recomendada', 'porque'].includes(k))) {
703
+ throw invalido(`alternativa ${i + 1} fora do contrato`);
704
+ }
705
+ if (a.recomendada !== undefined && a.recomendada !== true)
706
+ throw invalido(`alternativa ${i + 1}: recomendada é true ou ausente`);
707
+ if (a.recomendada === true && !linhaCurta(a.porque, hitl_contract_1.TETOS_HITL_V2.porque))
708
+ throw invalido('a recomendada diz o porquê em uma linha');
709
+ if (a.recomendada !== true && a.porque !== undefined)
710
+ throw invalido('só a recomendada traz porquê');
711
+ return { letra: a.letra, texto: a.texto, consequencia: a.consequencia,
712
+ ...(a.recomendada === true ? { recomendada: true, porque: a.porque } : {}) };
713
+ });
714
+ if (lidas.filter(a => a.recomendada).length !== 1)
715
+ throw invalido('exatamente uma alternativa recomendada');
716
+ return { thread: v.thread, acaoImpedida: v.acaoImpedida, dependentes: [...v.dependentes],
717
+ impacto: v.impacto, pergunta: v.pergunta, alternativas: lidas };
718
+ }
719
+ /** Id estável: o mesmo arco, thread e ação impedida dão o mesmo id, em qualquer processo. */
720
+ function idDaDecisao(arco, thread, acaoImpedida) {
721
+ return 'L-' + sha((0, noctum_envelope_1.canonico)([arco, thread, acaoImpedida])).slice(0, 12);
722
+ }
723
+ /**
724
+ * Guarda na fila do lote o que só o dono resolve. A recomendada não é aplicada e nenhum campo de
725
+ * resposta existe: a decisão fica pendente até o dono responder pelo ingresso, numa fatia futura.
726
+ * Vale também depois de revogação ou expiração, porque o lote vai no relatório de qualquer jeito.
727
+ */
728
+ function enfileirarDecisao(raiz, arco, pedido, quando = new Date().toISOString()) {
729
+ const lido = validarDecisao(pedido);
730
+ return comLockDoArco(raiz, arco, false, () => {
731
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
732
+ if (!d.autorizacao)
733
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.sem-autorizacao', 'arco nunca autorizado não tem lote');
734
+ const id = idDaDecisao(arco, lido.thread, lido.acaoImpedida);
735
+ const existente = d.decisoes.find(x => x.id === id);
736
+ if (existente)
737
+ return existente;
738
+ const decisao = { id, arco, enfileiradaEm: quando, estado: 'pendente', ...lido };
739
+ anexar(raiz, arco, log, { tipo: 'decisao_enfileirada', decisao }, quando);
740
+ return decisao;
741
+ });
742
+ }
743
+ /** O estado do arco, relido do disco e julgado no instante dado. `null` quando o arco não existe. */
744
+ function lerArco(raiz, arco, quando = new Date().toISOString()) {
745
+ const log = lerLog(raiz, arco);
746
+ return log.linhas.length ? estadoDe(arco, derivar(log.linhas), quando) : null;
747
+ }
748
+ /**
749
+ * A conferência imediatamente antes de um efeito. Relê o disco, sem cache, e recusa na ordem:
750
+ * ação excluída, estado adulterado (inclusive cauda incompleta), revogação, falta de autorização,
751
+ * recibo do dono que não confere, hash diferente do vigente, janela e destino. Passar aqui é necessário
752
+ * 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
753
+ * instante é parâmetro para os testes; o executor chama sem ele, com o relógio do processo.
754
+ */
755
+ function conferirAntesDoEfeito(raiz, efeito, quando = new Date().toISOString()) {
756
+ const excluida = (0, noctum_envelope_1.recusaDeExclusao)(efeito?.acao);
757
+ if (excluida)
758
+ throw excluida;
759
+ const { vigente } = vigenteConferido(raiz, efeito.arco, efeito.envelopeSha256, lerLog(raiz, efeito.arco));
760
+ (0, noctum_envelope_1.avaliarEfeito)(vigente.conteudo, efeito, quando);
761
+ return { ok: true, arco: efeito.arco, versao: vigente.versao, envelopeSha256: vigente.envelopeSha256,
762
+ acao: efeito.acao, repositorio: efeito.repositorio, branch: efeito.branch, conferidoEm: quando, fim: vigente.conteudo.janela.fim };
763
+ }
764
+ /**
765
+ * A cadeia comum da conferência antes de um efeito (N1) e antes de um despacho (N2), na ordem da N1: cauda incompleta,
766
+ * linha adulterada, revogação, falta de autorização, recibo do dono que não confere ou não pode ser reconferido,
767
+ * conteúdo que diverge do hash e hash diferente do vigente. Relê só o que já foi lido do disco.
768
+ */
769
+ function vigenteConferido(raiz, arco, envelopeSha256, log) {
770
+ // Cauda sem quebra pode ser a revogação que está sendo gravada: na dúvida, nenhum efeito passa.
771
+ if (!log.terminaEmQuebra)
772
+ throw adulterado('linha incompleta no fim do log (gravação abortada ou em curso); repita a conferência');
773
+ const d = derivar(log.linhas);
774
+ if (d.revogacao)
775
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.revogado', `arco ${arco} revogado pelo dono`);
776
+ if (!d.autorizacao)
777
+ throw new noctum_envelope_1.RecusaDoArco('noctum.efeito.sem-autorizacao', `arco ${arco} sem envelope autorizado`);
778
+ // A prova do dono é reconferida aqui, pelo recibo do canal, e não lida da linha do log (FX2 no gate).
779
+ const prova = reciboConfere(raiz, arco, 'autorizar', d.autorizacao.envelopeSha256, d.autorizacao.versao, d.autorizacao.ingresso);
780
+ if (prova === 'sem-verificador') {
781
+ 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');
782
+ }
783
+ if (prova === 'falhou')
784
+ throw adulterado('o recibo do ingresso que autorizou o envelope não confere');
785
+ const vigente = d.propostas.get(d.autorizacao.versao);
786
+ if ((0, noctum_envelope_1.hashDoEnvelope)(vigente.conteudo) !== d.autorizacao.envelopeSha256)
787
+ throw adulterado('conteúdo vigente diverge do hash autorizado');
788
+ if (envelopeSha256 !== d.autorizacao.envelopeSha256) {
789
+ // Hash de uma emenda que o dono ainda não autorizou não é antigo: é envelope sem autorização.
790
+ const naoAutorizado = d.ultima !== null && d.ultima.versao > d.autorizacao.versao && d.ultima.envelopeSha256 === envelopeSha256;
791
+ 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)}`);
792
+ }
793
+ return { d, vigente };
794
+ }
795
+ /** As fases que o arco da N2 nunca despacha: a entrega fica com a condutora (A2 do CHECK da ork-rm058n2). */
796
+ const FASES_DA_ENTREGA = ['SHIP', 'MASTER'];
797
+ /** A thread é a do trabalho de pedido? O item dela mais o sha256 do pedido guardado na criação. Ilegível não é. */
798
+ function threadDoPedido(raiz, id, item, texto) {
799
+ try {
800
+ const t = (0, thread_1.lerThread)(raiz, id);
801
+ return t.roadmap === item && (0, thread_1.pedidoDaThread)(raiz, t)?.sha256 === (0, noctum_envelope_1.shaDoPedido)(texto);
802
+ }
803
+ catch {
804
+ return false;
805
+ }
806
+ }
807
+ /** As threads que já casam com o trabalho de pedido (o item mais o sha256 do pedido); `null` com thread ilegível. */
808
+ function threadsDoTrabalho(raiz, item, texto) {
809
+ const sha = (0, noctum_envelope_1.shaDoPedido)(texto);
810
+ const achadas = [];
811
+ for (const id of (0, thread_1.listarIds)(raiz)) {
812
+ let t;
813
+ try {
814
+ t = (0, thread_1.lerThread)(raiz, id);
815
+ }
816
+ catch {
817
+ return null;
818
+ }
819
+ if (t.roadmap === item && t.pedido?.sha256 === sha)
820
+ achadas.push(id);
821
+ }
822
+ return achadas;
823
+ }
824
+ /**
825
+ * D11 e D12 da ork-rm058n2: confere o envelope e reserva o despacho, as duas coisas sob o lock do arco, imediatamente
826
+ * antes do efeito. A cadeia da N1 (`vigenteConferido`) mais a janela, o trabalho na lista `v2`, a conta e o runtime do
827
+ * envelope e o orçamento: cada despacho reserva uma sessão no teto do arco e nos subtetos da conta e do runtime, e teto
828
+ * em outra unidade recusa, porque a N2 não mede consumo nela. O trabalho de pedido só despacha a thread que o arco abriu
829
+ * 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
830
+ * mesmo lock: ou ela vem antes e nada é reservado, ou vem depois e vale para o despacho seguinte.
831
+ */
832
+ function reservarDespacho(raiz, pedido, quando = new Date().toISOString()) {
833
+ return comLockDoArco(raiz, pedido.arco, false, () => {
834
+ const log = lerLog(raiz, pedido.arco);
835
+ const { d, vigente } = vigenteConferido(raiz, pedido.arco, pedido.envelopeSha256, log);
836
+ const c = vigente.conteudo;
837
+ (0, noctum_envelope_1.avaliarJanela)(c, quando);
838
+ const fora = (detalhe) => new noctum_envelope_1.RecusaDoArco('noctum.despacho.fora-do-envelope', detalhe);
839
+ if (!(0, noctum_envelope_1.envelopeComTrabalhos)(c))
840
+ throw fora('o envelope vigente é v1, sem lista de trabalhos: só uma emenda v2 autorizada pelo dono despacha');
841
+ const t = c.trabalhos.find(x => x.id === pedido.trabalho);
842
+ if (!t)
843
+ throw fora(`o trabalho ${String(pedido.trabalho)} não está na lista do envelope`);
844
+ if (t.thread !== undefined && pedido.thread !== t.thread)
845
+ throw fora(`o trabalho ${t.id} é a thread ${t.thread}, não ${String(pedido.thread)}`);
846
+ // A2 do CHECK: a thread de um trabalho de pedido é a que o arco abriu para ele, nunca outra qualquer.
847
+ if (t.pedido !== undefined && pedido.thread !== null && !threadDoPedido(raiz, pedido.thread, t.item, t.pedido)) {
848
+ 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`);
849
+ }
850
+ if (FASES_DA_ENTREGA.includes(pedido.fase)) {
851
+ throw fora(`a fase ${String(pedido.fase)} é entrega: a N2 não despacha SHIP nem MASTER, que ficam com a condutora`);
852
+ }
853
+ // S3 da rodada 2: abrir a thread de um trabalho de pedido é pelo GOAL, e só quando nenhuma casa com ele ainda.
854
+ if (t.pedido !== undefined && pedido.thread === null) {
855
+ if (pedido.fase !== 'GOAL')
856
+ throw fora(`o trabalho ${t.id} abre a thread pelo GOAL, não pelo ${String(pedido.fase)}`);
857
+ const ja = threadsDoTrabalho(raiz, t.item, t.pedido);
858
+ if (ja === null)
859
+ throw fora(`há thread ilegível no projeto, e ela pode ser a do trabalho ${t.id}: abrir outra a duplicaria`);
860
+ if (ja.length)
861
+ throw fora(`o trabalho ${t.id} já tem a thread ${ja.join(', ')}: o despacho vai por ela`);
862
+ }
863
+ const tetoDoRuntime = c.orcamento.porRuntime.find(x => x.runtime === pedido.runtime)?.teto;
864
+ if (!tetoDoRuntime || !c.capacidade.porRuntime.some(x => x.runtime === pedido.runtime)) {
865
+ throw fora(`o runtime ${String(pedido.runtime)} não está no envelope`);
866
+ }
867
+ const tetoDaConta = c.orcamento.porConta.find(x => x.conta === pedido.conta)?.teto;
868
+ if (!tetoDaConta || !c.capacidade.porConta.some(x => x.conta === pedido.conta)) {
869
+ throw fora(`a conta ${String(pedido.conta)} não está no envelope`);
870
+ }
871
+ const consumo = consumoDe(d);
872
+ const tetos = [['do arco', c.orcamento.arco, consumo.arco],
873
+ [`da conta ${pedido.conta}`, tetoDaConta, consumo.porConta[pedido.conta] ?? 0],
874
+ [`do runtime ${pedido.runtime}`, tetoDoRuntime, consumo.porRuntime[pedido.runtime] ?? 0]];
875
+ for (const [nome, teto, usado] of tetos) {
876
+ if (teto.unidade !== 'sessoes') {
877
+ throw new noctum_envelope_1.RecusaDoArco('noctum.orcamento.sem-medicao', `o teto ${nome} está em ${teto.unidade}, e a N2 só mede sessoes: ` +
878
+ 'sem medir o consumo, o teto não é garantido, e consumo desconhecido nunca vale zero');
879
+ }
880
+ if (usado + 1 > teto.valor) {
881
+ throw new noctum_envelope_1.RecusaDoArco('noctum.orcamento.esgotado', `o teto ${nome} é de ${teto.valor} sessoes e ${usado} já foram reservadas`);
882
+ }
883
+ }
884
+ const reserva = { trabalho: t.id, thread: pedido.thread, fase: pedido.fase, runtime: pedido.runtime,
885
+ conta: pedido.conta, revisao: pedido.revisao === true, versao: vigente.versao, envelopeSha256: vigente.envelopeSha256 };
886
+ anexar(raiz, pedido.arco, log, { tipo: 'despacho_reservado', reserva }, quando);
887
+ const seq = log.linhas.length + 1;
888
+ const estado = confirmado(raiz, pedido.arco, quando, e => e.despachos.some(x => x.seq === seq && x.desfecho === 'pendente'), 'reserva do despacho');
889
+ return { arco: pedido.arco, seq, versao: vigente.versao, envelopeSha256: vigente.envelopeSha256, fim: c.janela.fim, consumo: estado.consumo };
890
+ }, true);
891
+ }
892
+ /**
893
+ * 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
894
+ * núcleo recusou, que devolve a sessão ao orçamento. Vale também depois de revogação ou expiração: é a contabilidade do
895
+ * que já estava em voo, não um efeito novo.
896
+ */
897
+ function concluirDespacho(raiz, arco, seq, desfecho, quando = new Date().toISOString()) {
898
+ return comLockDoArco(raiz, arco, false, () => {
899
+ const log = lerLog(raiz, arco), d = derivar(log.linhas);
900
+ const r = d.despachos.get(seq);
901
+ if (!r)
902
+ throw new noctum_envelope_1.RecusaDoArco('noctum.envelope.invalido', `reserva ${seq} inexistente neste arco`);
903
+ if (r.desfecho !== 'pendente')
904
+ return estadoDe(arco, d, quando);
905
+ const evento = 'motivo' in desfecho
906
+ ? { tipo: 'despacho_recusado', reserva: seq, motivo: String(desfecho.motivo).slice(0, 200) }
907
+ : { tipo: 'despacho_concluido', reserva: seq, thread: desfecho.thread, sessionId: desfecho.sessionId, idempotente: desfecho.idempotente };
908
+ anexar(raiz, arco, log, evento, quando);
909
+ return confirmado(raiz, arco, quando, e => e.despachos.some(x => x.seq === seq && x.desfecho !== 'pendente'), 'conclusão do despacho');
910
+ }, true);
911
+ }