@orkastery/cli 0.2.0 → 0.3.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 (242) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +86 -65
  3. package/adapters/README.md +30 -15
  4. package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
  5. package/adapters/claude-code/README.md +110 -8
  6. package/adapters/claude-code/commands/onboarding.md +21 -0
  7. package/adapters/claude-code/commands/ork.md +99 -27
  8. package/adapters/claude-code/hooks/hooks.json +6 -1
  9. package/adapters/claude-code/hooks/ork-sensor.js +66 -0
  10. package/adapters/codex/skills/ork/SKILL.md +57 -0
  11. package/adapters/hermes/README.md +163 -0
  12. package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
  13. package/adapters/hermes/bin/ork-brain.sh +4 -0
  14. package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
  15. package/adapters/hermes/bin/ork-maestro.sh +6 -0
  16. package/adapters/hermes/bin/ork-master-enviar.py +84 -0
  17. package/adapters/hermes/bin/ork-objective-message.sh +17 -0
  18. package/adapters/hermes/bin/ork-objective-status.sh +12 -0
  19. package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
  20. package/adapters/hermes/hermes.plugin.json +26 -4
  21. package/adapters/hermes/hitl-ingress/__init__.py +356 -0
  22. package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
  23. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
  24. package/adapters/openclaw/README.md +135 -26
  25. package/adapters/openclaw/bin/ork-brain.sh +4 -0
  26. package/adapters/openclaw/construir.sh +19 -0
  27. package/adapters/openclaw/dist/hitl-ingress.js +199 -0
  28. package/adapters/openclaw/dist/index.js +437 -0
  29. package/adapters/openclaw/openclaw.plugin.json +39 -134
  30. package/adapters/openclaw/package.json +26 -0
  31. package/adapters/openclaw/src/hitl-ingress.ts +180 -0
  32. package/adapters/openclaw/src/index.ts +458 -0
  33. package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
  34. package/adapters/openclaw/tsconfig.json +15 -0
  35. package/assets/docs/markdownlint-cli2.jsonc +22 -0
  36. package/assets/docs/padroes/documentacao-de-produto.md +133 -0
  37. package/assets/docs/padroes/roadmap-de-produto.md +98 -0
  38. package/assets/docs/produto/README.md +14 -0
  39. package/assets/docs/produto/_modelo-feature.md +59 -0
  40. package/assets/docs/roadmap/README.md +14 -0
  41. package/assets/docs/roadmap/_modelo-item.md +73 -0
  42. package/assets/orkmind-native-schema.json +32 -0
  43. package/assets/orkmind_bridge.py +357 -0
  44. package/assets/orkmind_fixture.py +67 -0
  45. package/assets/orkmind_prospective.py +138 -0
  46. package/assets/reference-tariffs-i07.json +23 -0
  47. package/dist/adapters/claude-bg.js +488 -39
  48. package/dist/adapters/codex-controller-sensor.js +449 -0
  49. package/dist/adapters/codex-controller-worker.js +361 -0
  50. package/dist/adapters/codex-controller.js +252 -0
  51. package/dist/adapters/codex-events.js +205 -0
  52. package/dist/adapters/codex-question.js +26 -0
  53. package/dist/adapters/codex-runner.js +133 -0
  54. package/dist/adapters/codex.js +394 -0
  55. package/dist/agents-md.js +84 -0
  56. package/dist/auditoria.js +11 -8
  57. package/dist/auditrun.js +5 -4
  58. package/dist/board.js +173 -22
  59. package/dist/branch-de-estado.js +136 -0
  60. package/dist/canarios-hitl.js +177 -0
  61. package/dist/canarios-i43.js +543 -0
  62. package/dist/canarios-pulse.js +147 -0
  63. package/dist/canarios-sensores.js +129 -0
  64. package/dist/canarios.js +111 -2
  65. package/dist/catalogo.js +9 -0
  66. package/dist/ci.js +216 -0
  67. package/dist/ciclos.js +2 -1
  68. package/dist/claim-lint.js +64 -0
  69. package/dist/claims.js +60 -0
  70. package/dist/company-brain-capture.js +195 -0
  71. package/dist/company-brain-cli.js +123 -0
  72. package/dist/company-brain-client.js +61 -0
  73. package/dist/company-brain-contract.js +166 -0
  74. package/dist/company-brain-journal.js +192 -0
  75. package/dist/company-brain-mcp.js +33 -0
  76. package/dist/company-brain-migration.js +60 -0
  77. package/dist/company-brain-source.js +177 -0
  78. package/dist/company-brain-worker.js +18 -0
  79. package/dist/conducao-texto.js +61 -0
  80. package/dist/conducao.js +876 -0
  81. package/dist/contrato-publico.js +39 -0
  82. package/dist/creation-operation-store.js +251 -0
  83. package/dist/creation-operation.js +148 -0
  84. package/dist/decisao-autonoma.js +183 -0
  85. package/dist/delegation.js +79 -0
  86. package/dist/demo.js +120 -0
  87. package/dist/docs.js +828 -0
  88. package/dist/doctor.js +183 -24
  89. package/dist/entrega-pr.js +117 -0
  90. package/dist/escopo-escrita.js +71 -0
  91. package/dist/estado-thread.js +222 -0
  92. package/dist/evalrunner.js +12 -0
  93. package/dist/fabrica-estado.js +329 -0
  94. package/dist/fabrica-publicar.js +76 -0
  95. package/dist/fix.js +21 -0
  96. package/dist/gates.js +55 -9
  97. package/dist/handoff.js +39 -23
  98. package/dist/hitl-canais.js +333 -0
  99. package/dist/hitl-classificacao.js +131 -0
  100. package/dist/hitl-contract.js +465 -0
  101. package/dist/hitl-estado.js +136 -0
  102. package/dist/hitl-gates.js +554 -0
  103. package/dist/hitl-ingress-receipt.js +382 -0
  104. package/dist/hitl-local-atestado.js +57 -0
  105. package/dist/hitl-local-receipt.js +328 -0
  106. package/dist/hitl-local.js +143 -0
  107. package/dist/hitl-lock.js +139 -0
  108. package/dist/hitl-lote.js +223 -0
  109. package/dist/hitl-native-offer.js +97 -0
  110. package/dist/hitl-native.js +65 -0
  111. package/dist/hitl-presentation.js +209 -0
  112. package/dist/hitl-public-receipt.js +176 -0
  113. package/dist/hitl-resumo.js +209 -0
  114. package/dist/hitl-sessions.js +475 -0
  115. package/dist/hitl.js +711 -0
  116. package/dist/horario.js +269 -0
  117. package/dist/hosts.js +198 -22
  118. package/dist/index.js +1736 -93
  119. package/dist/indice.js +99 -0
  120. package/dist/init.js +28 -3
  121. package/dist/integracoes-locais.js +17 -0
  122. package/dist/leases.js +65 -21
  123. package/dist/ledger-stats.js +272 -0
  124. package/dist/ledger.js +107 -2
  125. package/dist/licoes.js +215 -0
  126. package/dist/liveness.js +218 -0
  127. package/dist/maestro-actions.js +51 -0
  128. package/dist/maestro-authority.js +152 -0
  129. package/dist/maestro-cli.js +97 -0
  130. package/dist/maestro-contract.js +55 -0
  131. package/dist/maestro-discovery.js +132 -0
  132. package/dist/maestro-runtime.js +268 -0
  133. package/dist/maestro-snapshot.js +86 -0
  134. package/dist/maestro-sources.js +224 -0
  135. package/dist/manifest.js +165 -6
  136. package/dist/maquina.js +102 -0
  137. package/dist/master-audit.js +101 -0
  138. package/dist/master-batch.js +43 -0
  139. package/dist/master-digest.js +149 -0
  140. package/dist/master-migracao.js +155 -0
  141. package/dist/master.js +361 -77
  142. package/dist/mcp-artifacts.js +234 -0
  143. package/dist/mcp-git.js +433 -0
  144. package/dist/mcp-install.js +310 -0
  145. package/dist/mcp-maestro.js +25 -0
  146. package/dist/mcp-server.js +469 -0
  147. package/dist/mcp-ship.js +436 -0
  148. package/dist/mcp-verify.js +173 -0
  149. package/dist/memoria-humana.js +207 -0
  150. package/dist/memoria.js +314 -78
  151. package/dist/memory-migration.js +279 -0
  152. package/dist/memory-prospective.js +148 -0
  153. package/dist/modos-migracao.js +191 -0
  154. package/dist/modos.js +151 -15
  155. package/dist/monitor-lock.js +110 -0
  156. package/dist/objective.js +449 -0
  157. package/dist/ocupacao.js +267 -0
  158. package/dist/onboarding.js +303 -0
  159. package/dist/orkmind.js +389 -90
  160. package/dist/orquestracao.js +96 -69
  161. package/dist/phase.js +596 -150
  162. package/dist/playbook-capabilities.js +150 -0
  163. package/dist/playbook-contracts.js +188 -0
  164. package/dist/portfolio-context.js +61 -0
  165. package/dist/portfolio.js +180 -0
  166. package/dist/preflight.js +207 -0
  167. package/dist/process-audit.js +112 -0
  168. package/dist/project-state.js +93 -0
  169. package/dist/prompts.js +25 -5
  170. package/dist/prova-minima.js +112 -0
  171. package/dist/pulse-cadencia.js +163 -0
  172. package/dist/pulse-consentimento.js +260 -0
  173. package/dist/pulse-delivery.js +323 -0
  174. package/dist/pulse-resposta.js +542 -0
  175. package/dist/pulse.js +262 -0
  176. package/dist/ratelimit.js +35 -2
  177. package/dist/recall.js +80 -3
  178. package/dist/redacao-saida.js +45 -0
  179. package/dist/redacao-url.js +66 -0
  180. package/dist/retry.js +637 -115
  181. package/dist/roadmap-reservas.js +243 -0
  182. package/dist/runtime-ambiente.js +38 -0
  183. package/dist/runtime-context.js +215 -0
  184. package/dist/runtime-profiles.js +869 -0
  185. package/dist/runtimes.js +103 -0
  186. package/dist/sandbox.js +9 -0
  187. package/dist/session-events.js +203 -0
  188. package/dist/session-watcher-claude.js +637 -0
  189. package/dist/session-watcher.js +683 -0
  190. package/dist/sessoes-adopt.js +154 -0
  191. package/dist/sessoes-inventario.js +159 -0
  192. package/dist/sessoes.js +16 -35
  193. package/dist/setup.js +691 -0
  194. package/dist/ship.js +150 -15
  195. package/dist/slug.js +1 -1
  196. package/dist/thread-close.js +115 -0
  197. package/dist/thread.js +142 -28
  198. package/dist/tokens.js +1 -1
  199. package/dist/util.js +4 -2
  200. package/dist/verify-sandbox.js +265 -0
  201. package/dist/verify.js +270 -25
  202. package/dist/versao.js +60 -0
  203. package/dist/worktree.js +31 -2
  204. package/dist/write-activation.js +291 -0
  205. package/dist/yaml.js +2 -0
  206. package/eval/casos/onboarding.json +102 -0
  207. package/eval/casos/orkastery-bootstrap.json +35 -1
  208. package/eval/casos/scope-check-capability-map.json +45 -7
  209. package/eval/casos/ship-release.json +2 -2
  210. package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
  211. package/eval/fixtures/b2-master-log/caso.json +1 -1
  212. package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
  213. package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
  214. package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
  215. package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
  216. package/eval/fixtures/fx-codex-dry/caso.json +18 -0
  217. package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
  218. package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
  219. package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
  220. package/eval/fixtures/fx-happy/caso.json +6 -4
  221. package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
  222. package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
  223. package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
  224. package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
  225. package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
  226. package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
  227. package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
  228. package/eval/fixtures/fx-omnicanal/caso.json +17 -0
  229. package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
  230. package/monitor/company-brain.cjs +17 -0
  231. package/monitor/pulse-scope.cjs +45 -0
  232. package/monitor/pulse.cron +18 -0
  233. package/monitor/varredura-pulse.sh +27 -0
  234. package/package.json +25 -4
  235. package/schemas/claims.schema.json +25 -0
  236. package/schemas/company-brain.schema.json +1158 -0
  237. package/schemas/creation-operation.schema.json +365 -0
  238. package/schemas/maestro-snapshot.schema.json +2716 -0
  239. package/skills/core/onboarding/SKILL.md +50 -0
  240. package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
  241. package/skills/core/thread-state/SKILL.md +3 -1
  242. package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
@@ -0,0 +1,382 @@
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.chaveDoIngresso = chaveDoIngresso;
37
+ exports.contaDoEnvelope = contaDoEnvelope;
38
+ exports.canalDoEnvelope = canalDoEnvelope;
39
+ exports.instanteDoIngresso = instanteDoIngresso;
40
+ exports.evidenciaDoIngresso = evidenciaDoIngresso;
41
+ exports.validarEvidenciaDoIngresso = validarEvidenciaDoIngresso;
42
+ exports.validarEvidenciaReconstruida = validarEvidenciaReconstruida;
43
+ exports.gravarIngresso = gravarIngresso;
44
+ exports.prepararRecibosParaDespacho = prepararRecibosParaDespacho;
45
+ /** Reserva durável do ingresso antes do lock; guarda somente o hash da resposta. */
46
+ const fs = __importStar(require("node:fs"));
47
+ const path = __importStar(require("node:path"));
48
+ const node_crypto_1 = require("node:crypto");
49
+ const hitl_native_1 = require("./hitl-native");
50
+ const thread_1 = require("./thread");
51
+ const ledger_1 = require("./ledger");
52
+ const hitl_contract_1 = require("./hitl-contract");
53
+ const hitl_public_receipt_1 = require("./hitl-public-receipt");
54
+ const hitl_canais_1 = require("./hitl-canais");
55
+ const sha = (s) => (0, node_crypto_1.createHash)('sha256').update(s).digest('hex');
56
+ function pedidoDoIngresso(raiz, id, pedidoId) {
57
+ const pedidos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id)).filter(e => e.tipo === 'hitl_requested' && e.pedido?.id === pedidoId);
58
+ if (pedidos.length !== 1)
59
+ throw new Error('ingresso durável sem pedido único');
60
+ (0, hitl_contract_1.validarPedidoHitl)(pedidos[0].pedido);
61
+ return pedidos[0].pedido;
62
+ }
63
+ // D12: `canal` entra por ultimo e so quando existe. Um ingresso v1 continua produzindo
64
+ // exatamente os mesmos bytes e o mesmo MAC de antes; compatibilidade aqui e literal.
65
+ // FX5: `conta` entra depois de `canal`, pela mesma regra e pelo mesmo motivo: e ela que faz
66
+ // o recibo provar a CONTA autorizada do canal, e nao so o transporte telegram.
67
+ const campos = (raiz, id, pedido, r) => {
68
+ const request = pedidoDoIngresso(raiz, id, pedido);
69
+ // Opção usa a mesma normalização do validador. Texto nativo continua literal;
70
+ // a assinatura do envelope autentica os bytes recebidos antes desta projeção.
71
+ const resposta = r.origem === 'native' && (0, hitl_contract_1.respostaAceitaDoPedido)(request)?.tipo === 'texto' ? r.resposta : r.resposta.trim();
72
+ return { contrato: 'ork.hitl-ingress/v1', thread: id, pedidoId: pedido,
73
+ pedidoSha256: sha(JSON.stringify(request)), por: r.por, mensagem: r.mensagem, recebidoEm: r.recebidoEm,
74
+ recibo: sha(r.prova), respostaSha256: sha(resposta), ...(r.canal === undefined ? {} : { canal: r.canal }),
75
+ ...(r.conta === undefined ? {} : { conta: r.conta }),
76
+ ...(r.origem === 'native' ? { native: hitl_native_1.nativeContextSchema.parse(r.native) } : {}) };
77
+ };
78
+ const arquivo = (raiz, id, r) => path.join((0, thread_1.dirThread)(raiz, id), 'hitl-ingress', sha(r.mensagem) + '.json');
79
+ /**
80
+ * FX1: a chave do MAC vem do CANAL. Envelope v2 sem a variavel do proprio canal nao
81
+ * autentica, mesmo com a chave global presente: e essa recusa que impede um canal de
82
+ * assinar pelo outro. O v1, que nao declara canal, continua na chave global.
83
+ */
84
+ function chaveDoIngresso(canal) {
85
+ const nome = (0, hitl_canais_1.variavelDaChaveDoCanal)(canal);
86
+ const chave = process.env[nome] ?? '';
87
+ if (Buffer.byteLength(chave) < 32)
88
+ throw new Error(`hitl.credencial: ${nome} ausente ou menor que 32 bytes`);
89
+ // FX16 / SEC7: a oferta e apenas apresentacao. A fronteira que autentica cada
90
+ // operacao tambem precisa falhar fechada quando os dois canais Telegram foram
91
+ // provisionados com o mesmo material, sem revelar esse material no diagnostico.
92
+ if (canal === 'hermes' || canal === 'openclaw') {
93
+ const outro = canal === 'hermes' ? 'openclaw' : 'hermes';
94
+ const nomeDoOutro = hitl_canais_1.VARIAVEL_DA_CHAVE[outro];
95
+ const chaveDoOutro = process.env[nomeDoOutro] ?? '';
96
+ if (chaveDoOutro && chave === chaveDoOutro) {
97
+ throw new Error('hitl.credencial: chaves dos canais telegram precisam de material distinto');
98
+ }
99
+ }
100
+ return chave;
101
+ }
102
+ /**
103
+ * FX5: a conta declarada por um envelope, conferida contra a allowlist do proprio canal.
104
+ *
105
+ * O adaptador do OpenClaw ja recusava evento de outra conta, mas essa conferencia morria no
106
+ * processo do host. Aqui ela vira parte da autenticacao do nucleo e, por tabela, do corpo
107
+ * assinado: o recibo duravel passa a provar a CONTA, nao so o transporte. Canal sem conta
108
+ * homologada recusa envelope que traga conta, em vez de ignorar o campo.
109
+ */
110
+ function contaDoEnvelope(canal, conta) {
111
+ const nome = canal === null ? null : hitl_canais_1.VARIAVEL_DA_CONTA[canal];
112
+ if (nome === null) {
113
+ if (conta !== undefined)
114
+ throw new Error('hitl.canal.conta-inesperada: canal sem conta homologada');
115
+ return null;
116
+ }
117
+ const autorizada = (process.env[nome] ?? '').trim();
118
+ if (!autorizada)
119
+ throw new Error(`hitl.credencial: ${nome} ausente`);
120
+ if (typeof conta !== 'string' || conta !== autorizada) {
121
+ throw new Error('hitl.canal.conta-divergente: envelope nao prova a conta autorizada do canal');
122
+ }
123
+ return conta;
124
+ }
125
+ /** Canal declarado por um envelope, já conferido contra o registro. `null` é o v1 legado. */
126
+ function canalDoEnvelope(r) {
127
+ if (r.origem === 'native') {
128
+ if (r.canal !== 'hermes' && r.canal !== 'openclaw')
129
+ throw Error('hitl.native.host');
130
+ return r.canal;
131
+ }
132
+ if (r.canal === undefined)
133
+ return null;
134
+ const canal = (0, hitl_canais_1.parseCanal)(r.canal);
135
+ // Apelido não serve: o valor entra literal no HMAC, então ele tem de ser o nome canônico.
136
+ if (!canal || canal !== r.canal)
137
+ throw new Error('hitl.canal.desconhecido: envelope declara canal fora do registro');
138
+ if ((0, hitl_canais_1.transporteDoCanal)(canal) !== 'telegram')
139
+ throw new Error('hitl.canal.transporte-divergente: canal não atende por telegram');
140
+ return canal;
141
+ }
142
+ const mac = (v, canal, native = false) => (0, node_crypto_1.createHmac)('sha256', native ? (0, hitl_native_1.nativeKey)(String(canal)) : chaveDoIngresso(canal)).update(JSON.stringify(v)).digest('hex');
143
+ function instanteDoIngresso(raiz, id, pedido, r) {
144
+ const file = arquivo(raiz, id, r);
145
+ if (!fs.existsSync(file))
146
+ return undefined;
147
+ const st = fs.lstatSync(file);
148
+ if (!st.isFile() || st.isSymbolicLink() || st.uid !== process.getuid?.() || st.mode & 0o077)
149
+ throw new Error('ingresso durável inválido');
150
+ const v = JSON.parse(fs.readFileSync(file, 'utf8'));
151
+ const data = { ...campos(raiz, id, pedido, r), observadoEm: v.observadoEm };
152
+ const idade = Date.parse(v.observadoEm) - Date.parse(r.recebidoEm);
153
+ const esperado = /^[a-f0-9]{64}$/.test(v.assinatura ?? '') ? mac(data, r.canal ?? null, r.origem === 'native') : '';
154
+ if (!esperado || !Number.isFinite(idade) || idade < 0 || idade > 60000 || JSON.stringify(v.dados) !== JSON.stringify(data) ||
155
+ !(0, node_crypto_1.timingSafeEqual)(Buffer.from(v.assinatura, 'hex'), Buffer.from(esperado, 'hex')))
156
+ throw new Error('mensagem já reservada; ingresso divergente ou inválido');
157
+ return v.observadoEm;
158
+ }
159
+ /** Referência imutável do ingresso autenticado; o corpo da resposta nunca entra no artefato. */
160
+ function evidenciaDoIngresso(raiz, id, pedido, r) {
161
+ if (!instanteDoIngresso(raiz, id, pedido, r))
162
+ throw new Error('ingresso durável ausente');
163
+ const file = arquivo(raiz, id, r), bytes = fs.readFileSync(file);
164
+ return { arquivo: `.orkastery/threads/${id}/hitl-ingress/${path.basename(file)}`, sha256: sha(bytes.toString('utf8')) };
165
+ }
166
+ /**
167
+ * Revalida no sync o MAC durável e seu vínculo exato ao evento do ledger.
168
+ *
169
+ * FX4: vale para os DOIS alvos do contrato HITL. Antes desta correção havia aqui um
170
+ * `pedido.alvo.tipo !== 'gate'` que devolvia `false` sem dizer nada: uma resposta de SESSÃO
171
+ * vinda por Telegram jamais tinha a sua evidência durável reconferida, embora o ingresso
172
+ * durável dela já existisse em disco, com o mesmo MAC. Metade do HITL tinha prova durável e
173
+ * metade tinha só a palavra do controller, apesar de a narrativa prometer "o mesmo para os
174
+ * quatro canais nos dois alvos".
175
+ *
176
+ * O que muda por alvo é o que o MAC precisa amarrar do outro lado: no `gate`, a opção
177
+ * escolhida e o veredito derivado dela; na `session`, o envio único, a instância e o prompt
178
+ * nativo que receberam a resposta. O corpo respondido continua fora de tudo: o que entra no
179
+ * MAC é `sha256(resposta)`, como sempre foi.
180
+ */
181
+ function validarEvidenciaDoIngresso(raiz, id, e) {
182
+ return conferirIngresso(raiz, id, e, true);
183
+ }
184
+ /**
185
+ * FX10: o MESMO MAC, conferido ANTES de o evento existir no ledger.
186
+ *
187
+ * A reconciliação de um envio pendente reconstrói a referência durável só com o que o ledger
188
+ * já guardava. Reconstruir é legítimo; gravar sem reconferir não é. Antes desta função o
189
+ * núcleo escrevia `session_answered` e só muito depois, no sync, alguém descobria que o MAC
190
+ * não fechava: um byte trocado no pendente, ou no próprio arquivo de ingresso, virava
191
+ * resposta humana aceita, com a etiqueta `prova: 'ingresso-duravel'` por cima.
192
+ *
193
+ * A única diferença para a validação do evento já gravado é onde a resposta precisa estar:
194
+ * aqui ela ainda NÃO pode existir no ledger. É isso que impede esta porta de ser usada para
195
+ * carimbar um evento já persistido por outro caminho.
196
+ */
197
+ function validarEvidenciaReconstruida(raiz, id, e) {
198
+ return conferirIngresso(raiz, id, e, false);
199
+ }
200
+ /** `registrado` diz se a resposta já está no ledger; o resto da conferência é idêntico. */
201
+ function conferirIngresso(raiz, id, e, registrado) {
202
+ try {
203
+ // FX1: a chave e a do canal gravado. Um evento v2 cujo canal saiu do registro, ou cuja
204
+ // variavel de chave nao esta no ambiente, reprova aqui e nao cai na chave global.
205
+ const canal = e.canal == null ? null : (0, hitl_canais_1.parseCanal)(String(e.canal));
206
+ if (e.canal != null && (canal !== e.canal || (0, hitl_canais_1.transporteDoCanal)(canal) !== 'telegram'))
207
+ return false;
208
+ const isNative = e.origem === 'native';
209
+ // Ausência da chave no filho usa só a autoridade pública fixada no startup.
210
+ // Chave presente, porém inválida, continua falhando fechada.
211
+ const keyName = isNative ? `ORK_HITL_NATIVE_KEY_${String(canal).toUpperCase()}` : (0, hitl_canais_1.variavelDaChaveDoCanal)(canal);
212
+ const publicAuthority = process.env[keyName] === undefined ? (0, hitl_public_receipt_1.publicReceiptAuthority)(canal, isNative) : undefined;
213
+ const chave = publicAuthority ? undefined : isNative ? (0, hitl_native_1.nativeKey)(String(canal)) : chaveDoIngresso(canal);
214
+ const native = isNative ? hitl_native_1.nativeContextSchema.parse(e.native) : undefined;
215
+ if (native) {
216
+ const { messageId: _message, context: _context, pedidoSha256: _pedido, expiresAt: _expires, ...identity } = native;
217
+ const bindingValid = publicAuthority ? (0, hitl_public_receipt_1.publicBindingMatches)(publicAuthority, identity) :
218
+ Object.entries((0, hitl_native_1.nativeBinding)(String(canal))).every(([k, v]) => native[k] === v);
219
+ if (!bindingValid ||
220
+ e.mensagem !== (0, hitl_native_1.nativeMessage)(native) || e.autorizadoPor !== `native:${native.host}:${native.personId}` ||
221
+ e.conta !== native.accountId)
222
+ return false;
223
+ }
224
+ // FX5: a conta gravada tem de ser a conta autorizada do canal, agora e no MAC.
225
+ const conta = native ? native.accountId : publicAuthority ? e.conta ?? null :
226
+ contaDoEnvelope(canal, e.conta == null ? undefined : String(e.conta));
227
+ if (!native && publicAuthority && !(0, hitl_public_receipt_1.publicBindingMatches)(publicAuthority, conta))
228
+ return false;
229
+ if ((!isNative && (e.origem !== 'telegram' || !/^telegram:\d+$/.test(String(e.autorizadoPor ?? '')))) || typeof e.pedidoId !== 'string' ||
230
+ typeof e.mensagem !== 'string' || typeof e.recebidoEm !== 'string' ||
231
+ !/^[a-f0-9]{64}$/.test(String(e.recibo ?? '')) || typeof e.evidencia !== 'string' ||
232
+ !/^[a-f0-9]{64}$/.test(String(e.evidenciaSha256 ?? '')))
233
+ return false;
234
+ const nome = sha(e.mensagem) + '.json';
235
+ const referencia = `.orkastery/threads/${id}/hitl-ingress/${nome}`;
236
+ if (e.evidencia !== referencia)
237
+ return false;
238
+ const file = path.join((0, thread_1.dirThread)(raiz, id), 'hitl-ingress', nome);
239
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
240
+ try {
241
+ const st = fs.fstatSync(fd);
242
+ if (!st.isFile() || st.nlink !== 1 || st.uid !== process.getuid?.() || (st.mode & 0o777) !== 0o600)
243
+ return false;
244
+ const bytes = fs.readFileSync(fd), v = JSON.parse(bytes.toString('utf8'));
245
+ if (Object.keys(v).sort().join(',') !== 'assinatura,dados,observadoEm' ||
246
+ !/^[a-f0-9]{64}$/.test(v.assinatura ?? '') || e.evidenciaSha256 !== sha(bytes))
247
+ return false;
248
+ const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id));
249
+ const solicitados = eventos.filter(x => x.tipo === 'hitl_requested' && x.pedido?.id === e.pedidoId);
250
+ if (solicitados.length !== 1)
251
+ return false;
252
+ const pedido = solicitados[0].pedido;
253
+ (0, hitl_contract_1.validarPedidoHitl)(pedido);
254
+ if (pedido.thread !== id)
255
+ return false;
256
+ const tipoEsperado = pedido.alvo.tipo === 'gate' ? 'human_gate' : 'session_answered';
257
+ const respostas = eventos.filter(x => x.tipo === tipoEsperado && x.pedidoId === e.pedidoId);
258
+ if (Object.hasOwn(e, 'observacao'))
259
+ return false;
260
+ // Já gravado: a resposta do ledger tem de ser exatamente esta. Ainda por gravar: não
261
+ // pode haver nenhuma, senão esta porta viraria um segundo caminho de revalidação.
262
+ if (registrado ? respostas.length !== 1 || JSON.stringify(respostas[0]) !== JSON.stringify(e)
263
+ : respostas.length !== 0)
264
+ return false;
265
+ const pedidoSha256 = sha(JSON.stringify(pedido));
266
+ if (v.dados?.pedidoSha256 !== pedidoSha256)
267
+ return false;
268
+ if (native && (native.pedidoSha256 !== pedidoSha256 || native.expiresAt !== (0, hitl_contract_1.prazoDoPedido)(pedido) ||
269
+ native.context !== solicitados[0].contexto || Date.parse(e.recebidoEm) >= Date.parse(native.expiresAt)))
270
+ return false;
271
+ const respostaSha256 = String(v.dados?.respostaSha256 ?? '');
272
+ if (!vinculoDoAlvo(pedido, e, eventos, respostaSha256))
273
+ return false;
274
+ const contratoResposta = native ? 'ork.hitl-native/v1' : canal ? 'ork.hitl-answer/v2' : 'ork.hitl-answer/v1';
275
+ if ((e.contratoResposta ?? 'ork.hitl-answer/v1') !== contratoResposta)
276
+ return false;
277
+ const data = { contrato: 'ork.hitl-ingress/v1', thread: id, pedidoId: e.pedidoId,
278
+ pedidoSha256, por: e.autorizadoPor, mensagem: e.mensagem, recebidoEm: e.recebidoEm,
279
+ recibo: e.recibo, respostaSha256, ...(canal ? { canal } : {}), ...(conta === null ? {} : { conta }), ...(native ? { native } : {}),
280
+ observadoEm: v.observadoEm };
281
+ const idade = Date.parse(v.observadoEm) - Date.parse(e.recebidoEm);
282
+ const signatureValid = publicAuthority ? (0, hitl_public_receipt_1.verifyPublicReceipt)(file, bytes, publicAuthority) :
283
+ (0, node_crypto_1.timingSafeEqual)(Buffer.from(v.assinatura, 'hex'), (0, node_crypto_1.createHmac)('sha256', chave).update(JSON.stringify(data)).digest());
284
+ return Number.isFinite(idade) && idade >= 0 && idade <= 60000 &&
285
+ JSON.stringify(v.dados) === JSON.stringify(data) &&
286
+ signatureValid;
287
+ }
288
+ finally {
289
+ fs.closeSync(fd);
290
+ }
291
+ }
292
+ catch {
293
+ return false;
294
+ }
295
+ }
296
+ /**
297
+ * FX4: o que o MAC precisa amarrar do lado do ledger, por alvo do pedido.
298
+ *
299
+ * Gate: a opção existe no pedido e o veredito gravado é exatamente o que ela deriva.
300
+ * Sessão: a entrega foi única, correlacionada ao envio reservado, e caiu na mesma instância
301
+ * e no mesmo prompt nativo que abriram o pedido. Em pedido de OPÇÃO o conteúdo entregue
302
+ * ainda precisa ser uma das opções; em texto livre o `sha256` do MAC é a única amarra
303
+ * possível, e é ela que vale, porque o corpo nunca é persistido.
304
+ */
305
+ function vinculoDoAlvo(pedido, e, eventos, respostaSha256) {
306
+ const alvo = (0, hitl_contract_1.alvoDoPedido)(pedido);
307
+ // Fato consumado nao tem alvo nem resposta: nao ha vinculo a conferir, e por isso nao passa.
308
+ if (!alvo)
309
+ return false;
310
+ // I-41: a resposta digitada pode ser a letra (v2) ou o numero (as duas versoes). Conferir so
311
+ // o numero faria uma resposta legitima por letra deixar de bater com o proprio pedido.
312
+ const bate = (numero) => (0, hitl_contract_1.chavesAceitas)(pedido, numero).some(c => sha(c) === respostaSha256);
313
+ if (alvo.tipo === 'gate') {
314
+ const opcao = (0, hitl_contract_1.escolhasDoPedido)(pedido).find(o => bate(o.numero));
315
+ if (!opcao)
316
+ return false;
317
+ const veredito = (0, hitl_contract_1.vereditoDoGate)(pedido, opcao);
318
+ return e.fase === veredito.fase && e.sobre === veredito.sobre &&
319
+ e.estado === veredito.estado && e.opcao === veredito.opcao;
320
+ }
321
+ if (e.estado !== 'entregue' || e.fase !== pedido.fase || e.sessionId !== alvo.sessionId ||
322
+ e.runtime !== alvo.runtime || typeof e.envioId !== 'string' ||
323
+ typeof e.instancia !== 'string' || typeof e.bloqueio !== 'string')
324
+ return false;
325
+ if ((0, hitl_contract_1.respostaAceitaDoPedido)(pedido)?.tipo === 'opcao' && !(0, hitl_contract_1.escolhasDoPedido)(pedido).some(o => bate(o.numero)))
326
+ return false;
327
+ const solicitado = eventos.find(x => x.tipo === 'hitl_requested' && x.pedido?.id === pedido.id);
328
+ const sessao = solicitado?.sessao;
329
+ if (sessao?.instancia !== e.instancia || sessao?.bloqueio !== e.bloqueio)
330
+ return false;
331
+ const envios = eventos.filter(x => x.tipo === 'session_answer_sending' && x.pedidoId === e.pedidoId);
332
+ return envios.length === 1 && envios[0].envioId === e.envioId && envios[0].mensagem === e.mensagem &&
333
+ envios[0].recibo === e.recibo && envios[0].autorizadoPor === e.autorizadoPor;
334
+ }
335
+ /** O chamador autentica assinatura, janela e pedido antes de chamar; revalida sob lock depois. */
336
+ function gravarIngresso(raiz, id, pedido, r, observadoEm) {
337
+ const file = arquivo(raiz, id, r), dir = path.dirname(file);
338
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
339
+ const dados = { ...campos(raiz, id, pedido, r), observadoEm };
340
+ const temp = path.join(dir, `${(0, node_crypto_1.randomUUID)()}.tmp`);
341
+ const fd = fs.openSync(temp, 'wx', 0o600);
342
+ try {
343
+ fs.writeFileSync(fd, JSON.stringify({ dados, observadoEm, assinatura: mac(dados, r.canal ?? null, r.origem === 'native') }));
344
+ fs.fsyncSync(fd);
345
+ }
346
+ finally {
347
+ fs.closeSync(fd);
348
+ }
349
+ try {
350
+ fs.linkSync(temp, file);
351
+ const d = fs.openSync(dir, 'r');
352
+ try {
353
+ fs.fsyncSync(d);
354
+ }
355
+ finally {
356
+ fs.closeSync(d);
357
+ }
358
+ }
359
+ catch (e) {
360
+ if (e.code !== 'EEXIST')
361
+ throw e;
362
+ }
363
+ finally {
364
+ fs.unlinkSync(temp);
365
+ }
366
+ instanteDoIngresso(raiz, id, pedido, r); // corrida: só aceita vencedor idêntico
367
+ (0, hitl_public_receipt_1.signPublicReceipt)(file, r.canal ?? null, r.origem === 'native');
368
+ }
369
+ /** Núcleo antes do despacho: acrescenta prova pública a recibos legados autenticados.
370
+ * Nunca certifica um hash fornecido pelo modelo, nem altera recibo/ledger antigos.
371
+ */
372
+ function prepararRecibosParaDespacho(raiz, id) {
373
+ for (const e of (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id))) {
374
+ if (!['human_gate', 'session_answered'].includes(e.tipo) || !['telegram', 'native'].includes(String(e.origem)))
375
+ continue;
376
+ const canal = e.canal == null ? null : (0, hitl_canais_1.parseCanal)(String(e.canal));
377
+ const keyName = e.origem === 'native' ? `ORK_HITL_NATIVE_KEY_${String(canal).toUpperCase()}` : (0, hitl_canais_1.variavelDaChaveDoCanal)(canal);
378
+ if (process.env[keyName] === undefined || !validarEvidenciaDoIngresso(raiz, id, e))
379
+ continue;
380
+ (0, hitl_public_receipt_1.signPublicReceipt)(path.join((0, thread_1.dirThread)(raiz, id), 'hitl-ingress', sha(String(e.mensagem)) + '.json'), canal, e.origem === 'native');
381
+ }
382
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hashPedidoLocal = void 0;
4
+ exports.reciboDoMaterial = reciboDoMaterial;
5
+ exports.reciboDoAtestado = reciboDoAtestado;
6
+ exports.registrarAtestadoLocal = registrarAtestadoLocal;
7
+ exports.descartarAtestadoLocal = descartarAtestadoLocal;
8
+ exports.consumirAtestadoLocal = consumirAtestadoLocal;
9
+ /**
10
+ * Capacidade de uso único do ingresso MCP local, num módulo sem dependência de fluxo.
11
+ *
12
+ * Isto morava em `hitl-local.ts`. Quando o ingresso local passou a atender também pergunta
13
+ * de sessão (D12), `hitl-local` precisou chamar `hitl-sessions`, que já chamava
14
+ * `hitl-local` para consumir o atestado: um ciclo em tempo de execução, do tipo que só
15
+ * aparece como `undefined is not a function` na ordem errada de carga. O atestado não
16
+ * depende de gate nem de sessão, então ele fica aqui e os dois fluxos importam daqui.
17
+ */
18
+ const node_crypto_1 = require("node:crypto");
19
+ /** Chave é o próprio token congelado: quem não tem a referência não consome a capacidade. */
20
+ const atestados = new WeakMap();
21
+ /** I-41: o hash e dos BYTES do pedido, e por isso nao depende da versao do contrato. */
22
+ const hashPedidoLocal = (p) => (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(p)).digest('hex');
23
+ exports.hashPedidoLocal = hashPedidoLocal;
24
+ /**
25
+ * D12: o recibo entregue ao receptor amarra o conteúdo, não só a conexão.
26
+ *
27
+ * O envelope do Telegram já fazia isso: o `prova` é um HMAC sobre um corpo que inclui a
28
+ * resposta, então o recibo derivado dele muda se o conteúdo mudar. Um recibo local sobre
29
+ * `[conexão, solicitação, pedido]` provaria apenas QUE alguém respondeu daquela conexão,
30
+ * nunca O QUE o receptor recebeu. Com o hash da resposta dentro, o recibo que o controller
31
+ * confirma é específico daquele conteúdo.
32
+ */
33
+ function reciboDoMaterial(connectionId, requestId, pedidoSha256, respostaSha256) {
34
+ return (0, node_crypto_1.createHash)('sha256')
35
+ .update(JSON.stringify([connectionId, requestId, pedidoSha256, respostaSha256]))
36
+ .digest('hex');
37
+ }
38
+ function reciboDoAtestado(a) {
39
+ return reciboDoMaterial(a.connectionId, a.requestId, a.pedidoSha256, (0, node_crypto_1.createHash)('sha256').update(a.opcao).digest('hex'));
40
+ }
41
+ function registrarAtestadoLocal(token, dados) {
42
+ atestados.set(token, dados);
43
+ }
44
+ function descartarAtestadoLocal(token) {
45
+ atestados.delete(token);
46
+ }
47
+ /** Só consome capacidades emitidas nesta instância após resposta protocolar viva. */
48
+ function consumirAtestadoLocal(token, raiz, thread, pedidoId) {
49
+ if (!token || typeof token !== 'object')
50
+ throw new Error('hitl.local.atestado-invalido');
51
+ const dados = atestados.get(token);
52
+ atestados.delete(token);
53
+ if (!dados || dados.raiz !== raiz || dados.thread !== thread || dados.pedidoId !== pedidoId) {
54
+ throw new Error('hitl.local.atestado-invalido');
55
+ }
56
+ return dados;
57
+ }