@orkastery/cli 0.2.0 → 0.4.1

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 (257) 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/check.md +4 -3
  7. package/adapters/claude-code/commands/goal.md +3 -2
  8. package/adapters/claude-code/commands/master.md +3 -2
  9. package/adapters/claude-code/commands/onboarding.md +21 -0
  10. package/adapters/claude-code/commands/ork.md +99 -27
  11. package/adapters/claude-code/hooks/hooks.json +6 -1
  12. package/adapters/claude-code/hooks/ork-guard.js +1 -1
  13. package/adapters/claude-code/hooks/ork-sensor.js +66 -0
  14. package/adapters/codex/skills/ork/SKILL.md +57 -0
  15. package/adapters/hermes/README.md +161 -0
  16. package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
  17. package/adapters/hermes/bin/ork-brain.sh +4 -0
  18. package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
  19. package/adapters/hermes/bin/ork-maestro.sh +6 -0
  20. package/adapters/hermes/bin/ork-master-enviar.py +84 -0
  21. package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
  22. package/adapters/hermes/hermes.plugin.json +24 -4
  23. package/adapters/hermes/hitl-ingress/__init__.py +356 -0
  24. package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
  25. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
  26. package/adapters/openclaw/README.md +136 -27
  27. package/adapters/openclaw/bin/ork-brain.sh +4 -0
  28. package/adapters/openclaw/construir.sh +19 -0
  29. package/adapters/openclaw/dist/hitl-ingress.js +199 -0
  30. package/adapters/openclaw/dist/index.js +437 -0
  31. package/adapters/openclaw/openclaw.plugin.json +39 -134
  32. package/adapters/openclaw/package.json +26 -0
  33. package/adapters/openclaw/src/hitl-ingress.ts +180 -0
  34. package/adapters/openclaw/src/index.ts +460 -0
  35. package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
  36. package/adapters/openclaw/tsconfig.json +15 -0
  37. package/assets/docs/markdownlint-cli2.jsonc +22 -0
  38. package/assets/docs/padroes/documentacao-de-produto.md +133 -0
  39. package/assets/docs/padroes/roadmap-de-produto.md +98 -0
  40. package/assets/docs/produto/README.md +14 -0
  41. package/assets/docs/produto/_modelo-feature.md +59 -0
  42. package/assets/docs/roadmap/README.md +14 -0
  43. package/assets/docs/roadmap/_modelo-item.md +73 -0
  44. package/assets/orkmind-native-schema.json +32 -0
  45. package/assets/orkmind_bridge.py +357 -0
  46. package/assets/orkmind_fixture.py +67 -0
  47. package/assets/orkmind_prospective.py +138 -0
  48. package/assets/reference-tariffs-i07.json +23 -0
  49. package/dist/adapters/claude-bg.js +488 -39
  50. package/dist/adapters/codex-controller-sensor.js +449 -0
  51. package/dist/adapters/codex-controller-worker.js +361 -0
  52. package/dist/adapters/codex-controller.js +252 -0
  53. package/dist/adapters/codex-events.js +205 -0
  54. package/dist/adapters/codex-question.js +26 -0
  55. package/dist/adapters/codex-runner.js +133 -0
  56. package/dist/adapters/codex.js +394 -0
  57. package/dist/agents-md.js +84 -0
  58. package/dist/auditoria.js +12 -9
  59. package/dist/auditrun.js +5 -4
  60. package/dist/board.js +174 -23
  61. package/dist/branch-de-estado.js +136 -0
  62. package/dist/canarios-hitl.js +177 -0
  63. package/dist/canarios-i43.js +543 -0
  64. package/dist/canarios-pulse.js +147 -0
  65. package/dist/canarios-sensores.js +129 -0
  66. package/dist/canarios.js +111 -2
  67. package/dist/catalogo.js +9 -0
  68. package/dist/ci.js +223 -0
  69. package/dist/ciclos.js +2 -1
  70. package/dist/claim-lint.js +64 -0
  71. package/dist/claims.js +60 -0
  72. package/dist/company-brain-capture.js +195 -0
  73. package/dist/company-brain-cli.js +123 -0
  74. package/dist/company-brain-client.js +61 -0
  75. package/dist/company-brain-contract.js +166 -0
  76. package/dist/company-brain-journal.js +192 -0
  77. package/dist/company-brain-mcp.js +33 -0
  78. package/dist/company-brain-migration.js +60 -0
  79. package/dist/company-brain-source.js +177 -0
  80. package/dist/company-brain-worker.js +18 -0
  81. package/dist/conducao-texto.js +61 -0
  82. package/dist/conducao.js +876 -0
  83. package/dist/contrato-publico.js +39 -0
  84. package/dist/creation-operation-store.js +251 -0
  85. package/dist/creation-operation.js +148 -0
  86. package/dist/decisao-autonoma.js +183 -0
  87. package/dist/delegation.js +79 -0
  88. package/dist/demo.js +120 -0
  89. package/dist/docs.js +828 -0
  90. package/dist/doctor.js +184 -25
  91. package/dist/entrega-pr.js +117 -0
  92. package/dist/escopo-escrita.js +71 -0
  93. package/dist/estado-thread.js +222 -0
  94. package/dist/evalrunner.js +12 -0
  95. package/dist/fabrica-estado.js +329 -0
  96. package/dist/fabrica-publicar.js +76 -0
  97. package/dist/fix.js +22 -1
  98. package/dist/gates.js +59 -11
  99. package/dist/handoff.js +39 -23
  100. package/dist/hitl-canais.js +333 -0
  101. package/dist/hitl-classificacao.js +131 -0
  102. package/dist/hitl-contract.js +465 -0
  103. package/dist/hitl-estado.js +136 -0
  104. package/dist/hitl-gates.js +554 -0
  105. package/dist/hitl-ingress-receipt.js +382 -0
  106. package/dist/hitl-local-atestado.js +57 -0
  107. package/dist/hitl-local-receipt.js +328 -0
  108. package/dist/hitl-local.js +143 -0
  109. package/dist/hitl-lock.js +139 -0
  110. package/dist/hitl-lote.js +223 -0
  111. package/dist/hitl-native-offer.js +97 -0
  112. package/dist/hitl-native.js +65 -0
  113. package/dist/hitl-presentation.js +209 -0
  114. package/dist/hitl-public-receipt.js +176 -0
  115. package/dist/hitl-resumo.js +209 -0
  116. package/dist/hitl-sessions.js +475 -0
  117. package/dist/hitl.js +711 -0
  118. package/dist/horario.js +269 -0
  119. package/dist/hosts.js +198 -22
  120. package/dist/index.js +1741 -94
  121. package/dist/indice.js +99 -0
  122. package/dist/init.js +28 -3
  123. package/dist/integracoes-locais.js +17 -0
  124. package/dist/leases.js +65 -21
  125. package/dist/ledger-stats.js +272 -0
  126. package/dist/ledger.js +109 -2
  127. package/dist/licoes.js +221 -0
  128. package/dist/liveness.js +218 -0
  129. package/dist/maestro-actions.js +51 -0
  130. package/dist/maestro-authority.js +152 -0
  131. package/dist/maestro-cli.js +97 -0
  132. package/dist/maestro-contract.js +55 -0
  133. package/dist/maestro-discovery.js +132 -0
  134. package/dist/maestro-runtime.js +268 -0
  135. package/dist/maestro-snapshot.js +86 -0
  136. package/dist/maestro-sources.js +224 -0
  137. package/dist/manifest.js +165 -6
  138. package/dist/maquina.js +102 -0
  139. package/dist/master-audit.js +101 -0
  140. package/dist/master-batch.js +43 -0
  141. package/dist/master-digest.js +149 -0
  142. package/dist/master-migracao.js +155 -0
  143. package/dist/master.js +361 -77
  144. package/dist/mcp-artifacts.js +234 -0
  145. package/dist/mcp-git.js +433 -0
  146. package/dist/mcp-install.js +310 -0
  147. package/dist/mcp-maestro.js +25 -0
  148. package/dist/mcp-server.js +469 -0
  149. package/dist/mcp-ship.js +436 -0
  150. package/dist/mcp-verify.js +173 -0
  151. package/dist/memoria-humana.js +207 -0
  152. package/dist/memoria.js +314 -78
  153. package/dist/memory-migration.js +279 -0
  154. package/dist/memory-prospective.js +148 -0
  155. package/dist/modos-migracao.js +191 -0
  156. package/dist/modos.js +151 -15
  157. package/dist/monitor-lock.js +110 -0
  158. package/dist/objective.js +449 -0
  159. package/dist/ocupacao.js +267 -0
  160. package/dist/onboarding.js +303 -0
  161. package/dist/orkmind.js +389 -90
  162. package/dist/orquestracao.js +96 -69
  163. package/dist/phase.js +612 -151
  164. package/dist/playbook-capabilities.js +150 -0
  165. package/dist/playbook-contracts.js +188 -0
  166. package/dist/policies.js +72 -0
  167. package/dist/portfolio-context.js +61 -0
  168. package/dist/portfolio.js +180 -0
  169. package/dist/preflight.js +207 -0
  170. package/dist/process-audit.js +112 -0
  171. package/dist/project-state.js +93 -0
  172. package/dist/prompts.js +25 -5
  173. package/dist/prova-minima.js +112 -0
  174. package/dist/pulse-cadencia.js +163 -0
  175. package/dist/pulse-consentimento.js +260 -0
  176. package/dist/pulse-delivery.js +323 -0
  177. package/dist/pulse-resposta.js +542 -0
  178. package/dist/pulse.js +262 -0
  179. package/dist/ratelimit.js +35 -2
  180. package/dist/recall.js +80 -3
  181. package/dist/redacao-saida.js +45 -0
  182. package/dist/redacao-url.js +66 -0
  183. package/dist/retry.js +640 -118
  184. package/dist/roadmap-reservas.js +243 -0
  185. package/dist/runtime-ambiente.js +38 -0
  186. package/dist/runtime-context.js +215 -0
  187. package/dist/runtime-profiles.js +869 -0
  188. package/dist/runtimes.js +103 -0
  189. package/dist/sandbox.js +9 -0
  190. package/dist/session-events.js +203 -0
  191. package/dist/session-watcher-claude.js +637 -0
  192. package/dist/session-watcher.js +700 -0
  193. package/dist/sessoes-adopt.js +154 -0
  194. package/dist/sessoes-inventario.js +159 -0
  195. package/dist/sessoes.js +16 -35
  196. package/dist/setup.js +691 -0
  197. package/dist/ship.js +168 -19
  198. package/dist/slug.js +1 -1
  199. package/dist/thread-close.js +115 -0
  200. package/dist/thread.js +142 -28
  201. package/dist/tokens.js +1 -1
  202. package/dist/util.js +4 -2
  203. package/dist/verify-sandbox.js +265 -0
  204. package/dist/verify.js +270 -25
  205. package/dist/versao.js +60 -0
  206. package/dist/worktree.js +31 -2
  207. package/dist/write-activation.js +291 -0
  208. package/dist/yaml.js +5 -1
  209. package/eval/casos/master-metrics.json +1 -1
  210. package/eval/casos/onboarding.json +102 -0
  211. package/eval/casos/orkastery-bootstrap.json +37 -3
  212. package/eval/casos/scope-check-capability-map.json +45 -7
  213. package/eval/casos/ship-release.json +3 -3
  214. package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
  215. package/eval/fixtures/b2-master-log/caso.json +1 -1
  216. package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
  217. package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
  218. package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
  219. package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
  220. package/eval/fixtures/fx-codex-dry/caso.json +18 -0
  221. package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
  222. package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
  223. package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
  224. package/eval/fixtures/fx-happy/caso.json +6 -4
  225. package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
  226. package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
  227. package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
  228. package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
  229. package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
  230. package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
  231. package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
  232. package/eval/fixtures/fx-omnicanal/caso.json +17 -0
  233. package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
  234. package/monitor/company-brain.cjs +17 -0
  235. package/monitor/pulse-scope.cjs +45 -0
  236. package/monitor/pulse.cron +18 -0
  237. package/monitor/varredura-pulse.sh +27 -0
  238. package/package.json +28 -7
  239. package/references/definition-of-done.md +2 -2
  240. package/schemas/claims.schema.json +25 -0
  241. package/schemas/company-brain.schema.json +1158 -0
  242. package/schemas/creation-operation.schema.json +365 -0
  243. package/schemas/maestro-snapshot.schema.json +2716 -0
  244. package/skills/README.md +3 -3
  245. package/skills/core/onboarding/SKILL.md +50 -0
  246. package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
  247. package/skills/core/thread-state/SKILL.md +3 -1
  248. package/skills/governance/decision-triage/SKILL.md +4 -4
  249. package/skills/governance/narrative-guardian/SKILL.md +1 -1
  250. package/skills/governance/roadmap-keeper/SKILL.md +1 -1
  251. package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
  252. package/skills/observability/thread-tracing/SKILL.md +1 -1
  253. package/skills/phases/check-quality/SKILL.md +2 -2
  254. package/skills/phases/goal-definition/SKILL.md +1 -1
  255. package/skills/phases/master-metrics/SKILL.md +9 -7
  256. package/skills/phases/plan-specification/SKILL.md +1 -1
  257. package/skills/phases/ship-release/SKILL.md +1 -1
package/dist/prompts.js CHANGED
@@ -160,7 +160,7 @@ exports.SECOES_OBRIGATORIAS = [
160
160
  '## Pedido do builder',
161
161
  '## Regras de evidencia',
162
162
  ];
163
- /** A frase que nenhum template pode perder, porque ela e o contrato dos 5 modos. */
163
+ /** A frase que nenhum template pode perder, porque ela e o contrato de todo modo. */
164
164
  exports.REGRA_CENTRAL = 'REGRA CENTRAL: o modo afrouxa a pausa, NUNCA a verificacao.';
165
165
  /** O contrato do prompt de fase (o do B4). */
166
166
  exports.CONTRATO_DE_FASE = {
@@ -241,18 +241,29 @@ function lintTemplate(t, contrato = exports.CONTRATO_DE_FASE) {
241
241
  *
242
242
  * Ele e a fonte de verdade do prompt do `ork` e a copia versionada em `prompts/fase-padrao.md`
243
243
  * precisa ser identica byte a byte (ha teste que prova isso). Renderizado, ele produz
244
- * exatamente o prompt que o B0 montava por concatenacao: mudar o texto aqui e mudar contrato.
244
+ * o contrato distribuido a projetos sem override; mudar o texto exige sincronizar a copia.
245
245
  */
246
246
  exports.TEMPLATE_FASE_PADRAO = `---
247
247
  id: fase-padrao
248
- versao: 2
248
+ versao: 3
249
249
  descricao: Prompt canonico de uma fase do ciclo GOAL..MASTER, com nomenclatura, modo de conducao, contexto da thread, memoria injetada e regras de evidencia.
250
250
  fases: [GOAL, PLAN, GO, CHECK, SHIP, MASTER]
251
251
  variaveis: [fase, thread, bloco, nome, ciclo_canonico, tag, blocos, pausas, linha_variante, regra_de_pausa, invariantes, slug, projeto_nome, projeto_abbrev, base_branch, base_commit, diretorio, pedido, memoria_injetada, regras_de_evidencia]
252
252
  ---
253
253
  # Orkastery, fase {{fase}} da thread {{thread}}
254
254
 
255
- Voce conduz o bloco {{bloco}} da thread "{{nome}}".
255
+ Voce conduz o bloco {{bloco}} da thread "{{nome}}". {{fase}} e a fase de entrada, nao uma nova orquestracao.
256
+ Execute as fases deste bloco na ordem canonica, respeitando o escopo de cada fase e os gates do nucleo; GOAL nao implementa antes de GO.
257
+ Nao abra outra thread nem redespache a si mesmo. Se houver MCP do projeto, prefira suas ferramentas para estado e operacoes suportadas.
258
+ No Claude com servidor \`orkastery\`, use nomes completos expostos: \`ToolSearch\` com \`select:mcp__orkastery__ork_thread_status,mcp__orkastery__ork_artifact_read\` descobre essas leituras; para outra ferramenta, selecione seu nome completo. Nao busque \`select:ork_thread_status\`. Outros hosts usam seu namespace descoberto.
259
+ Quando disponiveis, use ork_artifact_read/write para objetivo, plano e parecer, e ork_claims_list/ork_claim_add para claims; nao escreva ledger nem estado canonico diretamente.
260
+ Decisao que voce tomar sem perguntar ao dono vai ao ledger por \`ork decisao registrar <thread>\`, com o que foi decidido, o porque, como mudar, o custo de reverter agora e depois, o criterio escrito, quem decidiu e a evidencia; e assim que ela chega ao dono no resumo, em vez de sumir na conversa.
261
+ Artefato de autoria do agente nao e recibo oficial; cadastrar claim nao significa verifica-la.
262
+ Para registrar produto, prefira ork_git_commit com HEAD atual e paths com claims; use ork_verify para verificacao real e ork_ship com HEADs fonte/destino conferidos para entrega. Commit local nao e SHIP; retorno incomplete exige reconciliar estado e remoto antes de repetir. Permissao para consultar nao concede essas operacoes: respeite a autorizacao nativa e os gates.
263
+ Decisao do dono deve ser apresentada pela sessao condutora ao host; a sessao filha nao responde nem assume a identidade do dono.
264
+ Preserve o perfil filho configurado: \`interactive\` concede consultas; \`worktree\` exige opt-in da instalacao e autoriza ferramentas exatas na WT validada, sem aprovar gates ou assumir o dono. Nao mude configuracao para contornar bloqueio.
265
+ Consulte \`ork_git_status\` para HEADs reais e estado Git; use \`ork_git_commit\`, \`ork_verify\` e \`ork_ship\` pelos contratos do nucleo, sem Bash Git nem inventar SHAs. Use \`source.head\` como \`expectedHead\` do commit; consulte novamente apos commit e use \`source.head\`/\`destination.head\` como \`expectedSource\`/\`expectedDestination\` do SHIP. Base historica nao substitui a consulta atual. As regras Edit/deny sao permissoes nativas de ferramentas, nao sandbox de processos.
266
+ Permissao nativa ou operacao sem transporte suportado continua um impedimento real, nunca motivo para alterar o sandbox.
256
267
 
257
268
  ## Ciclo canonico
258
269
  {{ciclo_canonico}}
@@ -278,6 +289,14 @@ REGRA CENTRAL: o modo afrouxa a pausa, NUNCA a verificacao.
278
289
  {{pedido}}
279
290
  {{memoria_injetada}}
280
291
 
292
+ ## Isolamento por sessão
293
+
294
+ O runtime pode ser o mesmo da condutora e do revisor. A independência exige IDs nativos
295
+ distintos e vínculos comprovados pelo núcleo; nome de papel no prompt não é autoridade.
296
+ Esta sessão executa o bloco recebido, sem abrir recursão, aprovar gate ou validar o próprio
297
+ trabalho. Preserve runtime/modelo/esforço e perfil \`interactive\`/\`worktree\` já configurados.
298
+ Falha de autenticação permanece impedimento; não troque provider, sandbox ou identidade.
299
+
281
300
  ## Regras de evidencia
282
301
  {{regras_de_evidencia}}`;
283
302
  /** Os templates que vem dentro do `ork`, por id. */
@@ -387,7 +406,8 @@ function threadDeExemplo(modo, fase, projeto) {
387
406
  nome: 'exemplo de prompt',
388
407
  assunto: 'exemplo',
389
408
  modo,
390
- fases: [...types_1.FASES],
409
+ // I-42: as fases saem dos blocos do modo; no `#Fast` e so a GO.
410
+ fases: def.blocos.flatMap((b) => b.fases),
391
411
  blocos: def.blocos,
392
412
  faseAtual: fase,
393
413
  status: 'aberta',
@@ -0,0 +1,112 @@
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.LIMITE_DE_COMANDOS_SEM_CHECK = void 0;
37
+ exports.cicloSemCheck = cicloSemCheck;
38
+ exports.testeFocado = testeFocado;
39
+ exports.comandoDoTesteFocado = comandoDoTesteFocado;
40
+ exports.validarProvaSemCheck = validarProvaSemCheck;
41
+ exports.registrarProvaAusente = registrarProvaAusente;
42
+ /**
43
+ * I-42 (D4, D5): a prova minima de um ciclo sem CHECK, em tres degraus.
44
+ *
45
+ * 1. Teste focado: havendo teste homonimo para algum caminho do commit (ou sendo o caminho um
46
+ * teste), roda so ele e grava comando e codigo de saida.
47
+ * 2. Claim retrospectiva: sem teste homonimo, uma claim de uma linha com ate dois comandos
48
+ * baratos. O nucleo recusa claim que rode a suite inteira do manifesto (custo medido na I-42:
49
+ * 227,6 s contra ~12 s do teste focado).
50
+ * 3. Ausencia declarada: sem teste nem comando honesto, o evento `prova_ausente` registra os
51
+ * caminhos sem prova e o motivo. Nao ha prova fingida e nao ha ausencia em silencio.
52
+ *
53
+ * O que nao acontece em degrau nenhum: baseline do manifesto ou a suite completa.
54
+ */
55
+ const fs = __importStar(require("node:fs"));
56
+ const path = __importStar(require("node:path"));
57
+ const ledger_1 = require("./ledger");
58
+ const thread_1 = require("./thread");
59
+ /** Teto de comandos por claim num ciclo sem CHECK (degrau 2). */
60
+ exports.LIMITE_DE_COMANDOS_SEM_CHECK = 2;
61
+ /** O ciclo da thread nao tem CHECK: a prova e a minima, feita pela propria GO. */
62
+ function cicloSemCheck(thread) {
63
+ return !thread.blocos.some((b) => b.fases.includes('CHECK'));
64
+ }
65
+ /**
66
+ * O teste focado de um caminho, por convencao: `core/src/X.ts` -> `core/test/X.test.ts`, e um
67
+ * `core/test/X.test.ts` e o proprio teste. Devolve o caminho do teste, ou null quando nao ha.
68
+ */
69
+ function testeFocado(raiz, caminho) {
70
+ const c = caminho.replace(/\\/g, '/');
71
+ if (/^core\/test\/[^/]+\.test\.ts$/.test(c))
72
+ return fs.existsSync(path.join(raiz, c)) ? c : null;
73
+ const m = /^core\/src\/([^/]+)\.ts$/.exec(c);
74
+ if (!m)
75
+ return null;
76
+ const teste = `core/test/${m[1]}.test.ts`;
77
+ return fs.existsSync(path.join(raiz, teste)) ? teste : null;
78
+ }
79
+ /** O comando que roda so o teste focado (degrau 1). */
80
+ function comandoDoTesteFocado(teste) {
81
+ const nome = path.basename(teste).replace(/\.ts$/, '.js');
82
+ return `npm --prefix core run build:test && node --test core/dist-test/test/${nome}`;
83
+ }
84
+ /**
85
+ * Recusa a claim que fura o degrau 2: mais comandos do que o teto, ou a suite inteira do
86
+ * manifesto. Mensagem tipada, com a saida certa.
87
+ */
88
+ function validarProvaSemCheck(verificar, comandoDeTesteDoManifesto) {
89
+ if (verificar.length > exports.LIMITE_DE_COMANDOS_SEM_CHECK) {
90
+ throw new Error(`claims.custo: ciclo sem CHECK aceita ate ${exports.LIMITE_DE_COMANDOS_SEM_CHECK} comandos por claim ` +
91
+ `(recebidos ${verificar.length}); use o teste focado ou declare a ausencia com ork claims ausente`);
92
+ }
93
+ const suite = comandoDeTesteDoManifesto?.trim();
94
+ if (suite && verificar.some((c) => c.includes(suite))) {
95
+ throw new Error(`claims.custo: ciclo sem CHECK nao roda a suite inteira ("${suite}"); ` +
96
+ 'use o teste focado do arquivo que mudou');
97
+ }
98
+ }
99
+ /** Degrau 3: grava no ledger da thread que estes caminhos ficaram sem prova, e por que. */
100
+ function registrarProvaAusente(raiz, threadId, ausencia) {
101
+ (0, thread_1.lerThread)(raiz, threadId); // a thread tem de existir
102
+ const motivo = ausencia.motivo.trim();
103
+ if (!motivo)
104
+ throw new Error('prova.ausente: o motivo e obrigatorio (uma linha)');
105
+ if (ausencia.paths.length === 0)
106
+ throw new Error('prova.ausente: informe os caminhos que ficaram sem prova');
107
+ (0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, ledger_1.TIPOS_DE_EVENTO.provaAusente, {
108
+ paths: ausencia.paths,
109
+ motivo,
110
+ ...(ausencia.commit ? { commit: ausencia.commit } : {}),
111
+ });
112
+ }
@@ -0,0 +1,163 @@
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.CADENCIA_PADRAO = exports.CADENCIAS = exports.CONTRATO_CADENCIA = void 0;
37
+ exports.extrairTagDoPulse = extrairTagDoPulse;
38
+ exports.lerCadencia = lerCadencia;
39
+ exports.gravarCadencia = gravarCadencia;
40
+ exports.janelaDoInstante = janelaDoInstante;
41
+ exports.inicioDaProximaJanela = inicioDaProximaJanela;
42
+ exports.lerUltimoResumo = lerUltimoResumo;
43
+ exports.gravarUltimoResumo = gravarUltimoResumo;
44
+ exports.janelaAberta = janelaAberta;
45
+ exports.textoDaCadencia = textoDaCadencia;
46
+ /**
47
+ * I-50 (RM-039): a cadencia do resumo do pulse, trocada pelo dono em conversa com uma tag.
48
+ *
49
+ * As tags sao as que o dono definiu:
50
+ * `#OrkPulseOff` de 8 em 8 horas, ancorado as 08h do fuso dono (08h, 16h e 00h);
51
+ * `#OrkPulseOn` de 2 em 2 horas;
52
+ * `#OrkPulseOn-15m`, `-30m` e `-60m`: de 15, 30 ou 60 minutos.
53
+ *
54
+ * O cron bate em intervalo curto e quem decide se ESTA batida entrega e o nucleo, lendo a
55
+ * cadencia duravel em `.orkastery/monitor/pulse-cadencia.json`. Assim a troca de tag vale na
56
+ * proxima batida, sem editar o crontab. A cadencia governa o status periodico; pergunta de
57
+ * verdade ao dono nao espera janela nenhuma e sai na hora.
58
+ *
59
+ * Cada cadencia divide o dia do dono em janelas ancoradas (15m em :00, :15, :30 e :45; 2h nas
60
+ * horas pares; 8h as 08h, 16h e 00h). Entrega-se no maximo uma vez por janela.
61
+ */
62
+ const fs = __importStar(require("node:fs"));
63
+ const path = __importStar(require("node:path"));
64
+ const horario_1 = require("./horario");
65
+ const estado_thread_1 = require("./estado-thread");
66
+ exports.CONTRATO_CADENCIA = 'ork.pulse-cadencia/v1';
67
+ const ARQUIVO_CADENCIA = 'pulse-cadencia.json';
68
+ const ARQUIVO_ULTIMO_RESUMO = 'pulse-ultimo-resumo.json';
69
+ exports.CADENCIAS = {
70
+ '#OrkPulseOff': { tag: '#OrkPulseOff', janelaMin: 480, ancoraMin: 8 * 60, descricao: 'de 8 em 8 horas, as 08h, 16h e 00h' },
71
+ '#OrkPulseOn': { tag: '#OrkPulseOn', janelaMin: 120, ancoraMin: 0, descricao: 'de 2 em 2 horas' },
72
+ '#OrkPulseOn-15m': { tag: '#OrkPulseOn-15m', janelaMin: 15, ancoraMin: 0, descricao: 'de 15 em 15 minutos' },
73
+ '#OrkPulseOn-30m': { tag: '#OrkPulseOn-30m', janelaMin: 30, ancoraMin: 0, descricao: 'de 30 em 30 minutos' },
74
+ '#OrkPulseOn-60m': { tag: '#OrkPulseOn-60m', janelaMin: 60, ancoraMin: 0, descricao: 'de hora em hora' },
75
+ };
76
+ /** Sem tag gravada, vale a hora cheia: o pedido do dono em 20/09/2026. */
77
+ exports.CADENCIA_PADRAO = '#OrkPulseOn-60m';
78
+ /** A primeira tag de pulse do texto, em qualquer canal; `null` quando nao ha. */
79
+ function extrairTagDoPulse(texto) {
80
+ if (typeof texto !== 'string')
81
+ return null;
82
+ const m = /#OrkPulse(On|Off)(?:-(15|30|60)m)?(?![\w-])/i.exec(texto);
83
+ if (!m)
84
+ return null;
85
+ if (m[1].toLowerCase() === 'off')
86
+ return m[2] ? null : '#OrkPulseOff';
87
+ return m[2] ? `#OrkPulseOn-${m[2]}m` : '#OrkPulseOn';
88
+ }
89
+ const dirDoMonitor = (raiz, estadoDir) => estadoDir ?? path.join((0, estado_thread_1.raizDoEstado)(raiz), '.orkastery', 'monitor');
90
+ /** A cadencia em vigor: a gravada pelo dono, ou a padrao. Arquivo ilegivel vale a padrao. */
91
+ function lerCadencia(raiz, estadoDir) {
92
+ try {
93
+ const bruto = JSON.parse(fs.readFileSync(path.join(dirDoMonitor(raiz, estadoDir), ARQUIVO_CADENCIA), 'utf8'));
94
+ if (bruto.contrato === exports.CONTRATO_CADENCIA && Object.hasOwn(exports.CADENCIAS, bruto.tag))
95
+ return { ...exports.CADENCIAS[bruto.tag], gravada: bruto };
96
+ }
97
+ catch { /* sem cadencia gravada: vale a padrao */ }
98
+ return { ...exports.CADENCIAS[exports.CADENCIA_PADRAO], gravada: null };
99
+ }
100
+ /** Grava a tag do dono. `por` e quem mandou (o remetente autenticado do canal, ou o operador). */
101
+ function gravarCadencia(raiz, tag, origem, estadoDir) {
102
+ if (!Object.hasOwn(exports.CADENCIAS, tag))
103
+ throw new Error(`pulse.cadencia: tag desconhecida ${tag}`);
104
+ const por = origem.por.trim();
105
+ if (!por)
106
+ throw new Error('pulse.cadencia: informe quem trocou a cadencia');
107
+ const gravada = { contrato: exports.CONTRATO_CADENCIA, tag, por, canal: origem.canal, em: origem.em ?? new Date().toISOString() };
108
+ const dir = dirDoMonitor(raiz, estadoDir);
109
+ fs.mkdirSync(dir, { recursive: true });
110
+ const temp = path.join(dir, `.${ARQUIVO_CADENCIA}.${process.pid}.tmp`);
111
+ fs.writeFileSync(temp, JSON.stringify(gravada, null, 2) + '\n', { mode: 0o600 });
112
+ fs.renameSync(temp, path.join(dir, ARQUIVO_CADENCIA));
113
+ return gravada;
114
+ }
115
+ /** O numero da janela de um instante: minutos locais desde a ancora, divididos pela janela. */
116
+ function janelaDoInstante(def, quando, fuso = (0, horario_1.fusoDoDono)().fuso) {
117
+ const p = (0, horario_1.partesLocais)(quando, fuso);
118
+ const localMin = Date.UTC(Number(p.ano), Number(p.mes) - 1, Number(p.dia), Number(p.hora), Number(p.minuto)) / 60000;
119
+ return Math.floor((localMin - def.ancoraMin) / def.janelaMin);
120
+ }
121
+ /** Quando a proxima janela comeca, em ISO: para dizer ao dono a hora do proximo resumo. */
122
+ function inicioDaProximaJanela(def, quando, fuso = (0, horario_1.fusoDoDono)().fuso) {
123
+ const atual = janelaDoInstante(def, quando, fuso);
124
+ let t = Date.parse(quando);
125
+ // Avanca minuto a minuto no maximo uma janela: simples e sem aritmetica de fuso.
126
+ for (let i = 0; i <= def.janelaMin; i++) {
127
+ t += 60_000;
128
+ if (janelaDoInstante(def, t, fuso) > atual) {
129
+ const d = new Date(t);
130
+ d.setUTCSeconds(0, 0);
131
+ return d.toISOString();
132
+ }
133
+ }
134
+ return new Date(t).toISOString();
135
+ }
136
+ function lerUltimoResumo(raiz, estadoDir) {
137
+ try {
138
+ const bruto = JSON.parse(fs.readFileSync(path.join(dirDoMonitor(raiz, estadoDir), ARQUIVO_ULTIMO_RESUMO), 'utf8'));
139
+ return typeof bruto.em === 'string' && Number.isFinite(Date.parse(bruto.em)) ? bruto.em : null;
140
+ }
141
+ catch {
142
+ return null;
143
+ }
144
+ }
145
+ function gravarUltimoResumo(raiz, em, estadoDir) {
146
+ const dir = dirDoMonitor(raiz, estadoDir);
147
+ fs.mkdirSync(dir, { recursive: true });
148
+ fs.writeFileSync(path.join(dir, ARQUIVO_ULTIMO_RESUMO), JSON.stringify({ em }) + '\n', { mode: 0o600 });
149
+ }
150
+ /**
151
+ * Esta batida do cron pode entregar o status periodico? So quando a janela mudou desde o ultimo
152
+ * resumo entregue. Pergunta nova ao dono nao passa por aqui: ela sai na hora.
153
+ */
154
+ function janelaAberta(def, ultimoResumoEm, quando, fuso = (0, horario_1.fusoDoDono)().fuso) {
155
+ if (!ultimoResumoEm)
156
+ return true;
157
+ return janelaDoInstante(def, quando, fuso) > janelaDoInstante(def, ultimoResumoEm, fuso);
158
+ }
159
+ /** A confirmacao que volta ao dono pelo mesmo canal, no fuso dele. */
160
+ function textoDaCadencia(def, quando) {
161
+ return `Pulse ${def.descricao} (${def.tag}). Pergunta para você continua saindo na hora. ` +
162
+ `Próximo resumo com novidade a partir de ${(0, horario_1.formatarDataHoraRotulada)(inicioDaProximaJanela(def, quando))}.`;
163
+ }
@@ -0,0 +1,260 @@
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_PADRAO_MIN = exports.ENDERECO_DA_RESPOSTA = exports.ALVO_DO_PULSE = exports.TAMANHO_DO_CODIGO = exports.ALFABETO_DO_CODIGO = exports.CONTRATO_CONSENTIMENTO = void 0;
37
+ exports.arquivoDoConsentimento = arquivoDoConsentimento;
38
+ exports.gerarCodigo = gerarCodigo;
39
+ exports.interpretarResposta = interpretarResposta;
40
+ exports.assinaturaDosCandidatos = assinaturaDosCandidatos;
41
+ exports.lerConsentimento = lerConsentimento;
42
+ exports.consentimentoVigente = consentimentoVigente;
43
+ exports.abrirConsentimento = abrirConsentimento;
44
+ exports.responderConsentimento = responderConsentimento;
45
+ exports.marcarLoteEntregue = marcarLoteEntregue;
46
+ /**
47
+ * I-41 (D16): o consentimento que separa a camada 1 da camada 2.
48
+ *
49
+ * O resumo termina perguntando "Posso te mandar as perguntas agora?". Essa pergunta e um pedido
50
+ * HITL como qualquer outro, e obedece as mesmas regras: resposta por letra ou palavra curta, sem
51
+ * identificador longo na mao do humano, e proveniencia provada pelo HMAC do ingresso autenticado.
52
+ *
53
+ * O QUE NAO MUDA, e o motivo: `assinaturaDaResposta` e `autenticarResposta` sao chamadas daqui
54
+ * sem uma linha alterada, e o corpo assinado continua o de sempre. E essa assinatura que impede o
55
+ * agente consentir no lugar do dono, e ela nao se negocia.
56
+ *
57
+ * I-41 (GO-FIX 1, B1): O ENDERECO. O dono digita "P4EJ a" no Telegram, e o gateway que recebe essa
58
+ * mensagem nao conhece identificador de pedido nenhum. Por isso o ingresso assina o endereco do
59
+ * pulse (`ALVO_DO_PULSE` na posicao de thread, `ENDERECO_DA_RESPOSTA` na posicao de pedido) junto
60
+ * com o texto exato que o dono digitou. O codigo esta DENTRO do texto assinado, entao um "sim" a um
61
+ * resumo nao vale para outro: o codigo precisa bater com o pedido aberto, e a janela de 60
62
+ * segundos do ingresso fecha a repeticao. Quem traduz o codigo no pedido e o nucleo.
63
+ *
64
+ * O consentimento nao e de uma thread: ele e do projeto, porque o resumo atravessa todas. O alvo e
65
+ * uma constante e nao um nome de thread: ids de thread deste projeto comecam com o abbrev.
66
+ *
67
+ * Silencio nao perde nada. Consentimento vencido nao nega e nao apaga: o proximo resumo abre
68
+ * outro e cita as perguntas guardadas. Os itens vivem no pulse, nao aqui.
69
+ */
70
+ const fs = __importStar(require("node:fs"));
71
+ const path = __importStar(require("node:path"));
72
+ const node_crypto_1 = require("node:crypto");
73
+ const estado_thread_1 = require("./estado-thread");
74
+ const hitl_gates_1 = require("./hitl-gates");
75
+ exports.CONTRATO_CONSENTIMENTO = 'ork.pulse-consent/v1';
76
+ /**
77
+ * Crockford base32 sem os quatro ambiguos (I, L, O, U). Com letra na frente e ao menos um digito,
78
+ * quatro caracteres ainda dao mais de 350.000 codigos, e o escopo de unicidade que importa e um: o
79
+ * consentimento aberto agora. Ambiguo fora do alfabeto importa porque o dono digita isto no
80
+ * celular, onde 0 e O sao a mesma coisa.
81
+ */
82
+ exports.ALFABETO_DO_CODIGO = '23456789ABCDEFGHJKMNPQRSTVWXYZ';
83
+ exports.TAMANHO_DO_CODIGO = 4;
84
+ const DIGITOS_DO_CODIGO = '23456789';
85
+ const LETRAS_DO_CODIGO = 'ABCDEFGHJKMNPQRSTVWXYZ';
86
+ /**
87
+ * I-41 (GO-FIX 1, B1): o endereco do pulse no corpo assinado.
88
+ *
89
+ * O ingresso poe `ALVO_DO_PULSE` na posicao de thread e `ENDERECO_DA_RESPOSTA` na posicao de
90
+ * pedido, e o texto que o dono digitou na posicao de resposta. Nenhum dos dois e nome de thread
91
+ * ou de pedido: um envelope assinado para uma thread de verdade nao serve aqui, e vice-versa.
92
+ */
93
+ exports.ALVO_DO_PULSE = 'pulse';
94
+ exports.ENDERECO_DA_RESPOSTA = 'resposta';
95
+ /** Uma hora, a mesma janela do resumo pedido em 20/09. Vencido nao nega: so deixa de valer. */
96
+ exports.PRAZO_PADRAO_MIN = 60;
97
+ const sha = (s) => (0, node_crypto_1.createHash)('sha256').update(s).digest('hex');
98
+ function arquivoDoConsentimento(raiz, estadoDir) {
99
+ return path.join(estadoDir ?? path.join((0, estado_thread_1.raizDoEstado)(raiz), '.orkastery', 'monitor'), 'pulse-consentimento.json');
100
+ }
101
+ /**
102
+ * Codigo novo, uniforme, diferente dos que estiverem em uso agora.
103
+ *
104
+ * I-41 (GO-FIX 1): todo codigo comeca por LETRA e tem pelo menos um DIGITO. O ingresso do Telegram
105
+ * reconhece a resposta ao resumo pela forma "codigo + resposta". Com digito, palavra comum de
106
+ * quatro letras ("HMMM ok") nunca tem essa forma, e a conversa com o assistente continua indo para
107
+ * o assistente. Com letra na frente, o codigo nunca e lido como resposta ao lote, que comeca pelo
108
+ * numero da pergunta: "22A2 a" seria "22a 2a".
109
+ */
110
+ function gerarCodigo(emUso = []) {
111
+ const usados = new Set(emUso.map(c => c.toUpperCase()));
112
+ for (let tentativa = 0; tentativa < 256; tentativa++) {
113
+ let codigo = LETRAS_DO_CODIGO[(0, node_crypto_1.randomInt)(LETRAS_DO_CODIGO.length)];
114
+ for (let i = 1; i < exports.TAMANHO_DO_CODIGO; i++)
115
+ codigo += exports.ALFABETO_DO_CODIGO[(0, node_crypto_1.randomInt)(exports.ALFABETO_DO_CODIGO.length)];
116
+ if (![...codigo].some(c => DIGITOS_DO_CODIGO.includes(c)))
117
+ continue;
118
+ if (!usados.has(codigo))
119
+ return codigo;
120
+ }
121
+ // 810.000 combinacoes contra um punhado em uso: chegar aqui e defeito, nao azar.
122
+ throw new Error('consentimento do pulse: não foi possível gerar código curto livre');
123
+ }
124
+ /**
125
+ * O que o dono pode digitar. Letra `a`/`b`, ou palavra curta, com ou sem o codigo na frente,
126
+ * em qualquer caixa. O que ele NAO precisa digitar e identificador longo.
127
+ *
128
+ * Quando o codigo vem escrito, ele precisa bater: um "sim" digitado para o resumo anterior nao
129
+ * consente com o resumo de agora. Quando nao vem, o pedido aberto e um so e a letra basta.
130
+ */
131
+ function interpretarResposta(bruto, codigo) {
132
+ if (typeof bruto !== 'string' || bruto.length > 64)
133
+ return undefined;
134
+ const limpo = bruto.trim().toLowerCase().replace(/[.,!]+$/, '');
135
+ const prefixo = codigo.toLowerCase();
136
+ const corpo = limpo.startsWith(prefixo) ? limpo.slice(prefixo.length).trim() : limpo;
137
+ if (limpo !== corpo && corpo === '')
138
+ return undefined; // só o código, sem escolha, não é resposta
139
+ if (['a', 's', 'sim', 'pode', 'manda'].includes(corpo))
140
+ return 'sim';
141
+ if (['b', 'n', 'nao', 'não', 'agora nao', 'agora não'].includes(corpo))
142
+ return 'nao';
143
+ return undefined;
144
+ }
145
+ /**
146
+ * A assinatura de um conjunto de candidatos: os mesmos gates, a mesma oferta. O pedido aberto nao
147
+ * entra: ele vence e renasce sem que a pergunta mude, e vencer nao e noticia para o dono.
148
+ */
149
+ function assinaturaDosCandidatos(candidatos) {
150
+ return sha(JSON.stringify(candidatos.map(c => [c.thread, c.fase, c.motivo, c.fonte ?? 'gate'])));
151
+ }
152
+ function lerConsentimento(raiz, estadoDir) {
153
+ const arquivo = arquivoDoConsentimento(raiz, estadoDir);
154
+ if (!fs.existsSync(arquivo))
155
+ return undefined;
156
+ const salvo = JSON.parse(fs.readFileSync(arquivo, 'utf8'));
157
+ if (salvo.versao !== 1 || salvo.pedido?.contrato !== exports.CONTRATO_CONSENTIMENTO) {
158
+ throw new Error('consentimento do pulse: estado inválido; exige inspeção');
159
+ }
160
+ // Estado gravado antes do GO-FIX 1 nao guardava candidatos: sem eles, o sim nao tem o que servir.
161
+ if (!Array.isArray(salvo.pedido.candidatos)) {
162
+ salvo.pedido = { ...salvo.pedido, candidatos: [], candidatosSha256: assinaturaDosCandidatos([]) };
163
+ }
164
+ return salvo;
165
+ }
166
+ function gravar(raiz, estado, estadoDir) {
167
+ const arquivo = arquivoDoConsentimento(raiz, estadoDir);
168
+ fs.mkdirSync(path.dirname(arquivo), { recursive: true });
169
+ const tmp = arquivo + '.tmp';
170
+ fs.writeFileSync(tmp, JSON.stringify(estado) + '\n', { mode: 0o600 });
171
+ fs.renameSync(tmp, arquivo);
172
+ }
173
+ /** O pedido que ainda vale agora: aberto, dentro do prazo e sem resposta gravada. */
174
+ function consentimentoVigente(raiz, quando, estadoDir) {
175
+ const estado = lerConsentimento(raiz, estadoDir);
176
+ if (!estado || estado.respondido)
177
+ return undefined;
178
+ return Date.parse(quando) < Date.parse(estado.pedido.prazo) ? estado.pedido : undefined;
179
+ }
180
+ /**
181
+ * Abre o pedido que acompanha um resumo. Um pedido vigente NAO e substituido: trocar o codigo
182
+ * debaixo de quem esta digitando e o defeito que fez a mesma pergunta sair com tres
183
+ * identificadores diferentes entre 16/09 e 20/09.
184
+ */
185
+ function abrirConsentimento(raiz, entrada) {
186
+ const candidatos = [...(entrada.candidatos ?? [])];
187
+ const candidatosSha256 = assinaturaDosCandidatos(candidatos);
188
+ const vigente = consentimentoVigente(raiz, entrada.quando, entrada.estadoDir);
189
+ if (vigente && vigente.resumoSha256 === entrada.resumoSha256 && vigente.candidatosSha256 === candidatosSha256)
190
+ return vigente;
191
+ const anterior = lerConsentimento(raiz, entrada.estadoDir)?.pedido.codigo;
192
+ const prazoMin = entrada.prazoMin ?? exports.PRAZO_PADRAO_MIN;
193
+ const pedido = {
194
+ contrato: exports.CONTRATO_CONSENTIMENTO, id: (0, node_crypto_1.randomUUID)(),
195
+ // O codigo novo nunca repete o anterior: um "sim" atrasado ao resumo velho nao vale para o novo.
196
+ codigo: gerarCodigo(anterior ? [anterior] : []),
197
+ criadoEm: entrada.quando,
198
+ prazo: new Date(Date.parse(entrada.quando) + prazoMin * 60000).toISOString(),
199
+ acumuladas: candidatos.length, resumoSha256: entrada.resumoSha256, candidatos, candidatosSha256,
200
+ };
201
+ gravar(raiz, { versao: 1, pedido }, entrada.estadoDir);
202
+ return pedido;
203
+ }
204
+ /**
205
+ * Recebe a resposta do dono pelo codigo curto.
206
+ *
207
+ * A ordem importa e e a mesma do gate: primeiro proveniencia, depois conteudo. Um envelope sem
208
+ * assinatura valida nunca chega a ser interpretado como "sim", e por isso texto malformado de
209
+ * origem nao autenticada nao consegue liberar lote nenhum. O envelope e o que o ingresso assinou
210
+ * para o endereco do pulse, com o texto que o dono digitou ("P4EJ a").
211
+ *
212
+ * Repetir a MESMA resposta devolve o que ja foi decidido, inclusive por outra mensagem: o dono que
213
+ * nao viu o lote chegar manda "P4EJ a" de novo e recebe o mesmo lote, nunca um lote novo. Mudar
214
+ * de ideia sobre o mesmo resumo e recusado com o que ja vale.
215
+ */
216
+ function responderConsentimento(raiz, entrada) {
217
+ const quando = entrada.quando ?? new Date().toISOString();
218
+ const estado = lerConsentimento(raiz, entrada.estadoDir);
219
+ if (!estado)
220
+ throw new Error('consentimento do pulse: não há pedido aberto');
221
+ const { pedido } = estado;
222
+ if (pedido.codigo.toUpperCase() !== String(entrada.codigo).trim().toUpperCase()) {
223
+ throw new Error('consentimento do pulse: código não confere com o pedido aberto');
224
+ }
225
+ // Proveniencia primeiro, pelo caminho de sempre, no endereco do pulse.
226
+ (0, hitl_gates_1.autenticarResposta)(exports.ALVO_DO_PULSE, exports.ENDERECO_DA_RESPOSTA, entrada.envelope, entrada.envelope.recebidoEm);
227
+ (0, hitl_gates_1.autenticarResposta)(exports.ALVO_DO_PULSE, exports.ENDERECO_DA_RESPOSTA, entrada.envelope, quando);
228
+ const resposta = interpretarResposta(entrada.envelope.resposta, pedido.codigo);
229
+ const recibo = sha(entrada.envelope.prova);
230
+ if (estado.respondido) {
231
+ // Uso unico por significado: a mesma resposta do mesmo dono devolve o que ja foi decidido.
232
+ if (resposta !== estado.respondido.resposta || estado.respondido.por !== entrada.envelope.por) {
233
+ throw new Error('consentimento do pulse: já respondido; resposta divergente recusada');
234
+ }
235
+ return { resposta: estado.respondido.resposta, repetida: true, pedido, respondido: estado.respondido };
236
+ }
237
+ if (Date.parse(quando) >= Date.parse(pedido.prazo)) {
238
+ throw new Error('consentimento do pulse: pedido vencido; nenhuma autorização concedida');
239
+ }
240
+ if (!resposta)
241
+ throw new Error('consentimento do pulse: responda com a (sim) ou b (agora não)');
242
+ const respondido = {
243
+ resposta, respondidoEm: quando, mensagem: entrada.envelope.mensagem, por: entrada.envelope.por, recibo,
244
+ };
245
+ gravar(raiz, { ...estado, respondido }, entrada.estadoDir);
246
+ return { resposta, repetida: false, pedido, respondido };
247
+ }
248
+ /**
249
+ * Marca o lote consentido como entregue, com os numeros que ele levou. Sem isto, um sim viraria
250
+ * autorizacao permanente e a cada resposta sairia um lote novo, que e a inundacao voltando por
251
+ * outra porta. Com os numeros, repetir o sim devolve o MESMO lote.
252
+ */
253
+ function marcarLoteEntregue(raiz, quando, estadoDir, numeros) {
254
+ const estado = lerConsentimento(raiz, estadoDir);
255
+ if (!estado?.respondido || estado.respondido.resposta !== 'sim')
256
+ throw new Error('consentimento do pulse: não há sim para servir');
257
+ if (estado.respondido.loteEntregueEm)
258
+ throw new Error('consentimento do pulse: este sim já serviu um lote');
259
+ gravar(raiz, { ...estado, respondido: { ...estado.respondido, loteEntregueEm: quando, numeros: [...numeros] } }, estadoDir);
260
+ }