@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.
- package/LICENSE +1 -1
- package/README.md +86 -65
- package/adapters/README.md +30 -15
- package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
- package/adapters/claude-code/README.md +110 -8
- package/adapters/claude-code/commands/check.md +4 -3
- package/adapters/claude-code/commands/goal.md +3 -2
- package/adapters/claude-code/commands/master.md +3 -2
- package/adapters/claude-code/commands/onboarding.md +21 -0
- package/adapters/claude-code/commands/ork.md +99 -27
- package/adapters/claude-code/hooks/hooks.json +6 -1
- package/adapters/claude-code/hooks/ork-guard.js +1 -1
- package/adapters/claude-code/hooks/ork-sensor.js +66 -0
- package/adapters/codex/skills/ork/SKILL.md +57 -0
- package/adapters/hermes/README.md +161 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
- package/adapters/hermes/bin/ork-brain.sh +4 -0
- package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
- package/adapters/hermes/bin/ork-maestro.sh +6 -0
- package/adapters/hermes/bin/ork-master-enviar.py +84 -0
- package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
- package/adapters/hermes/hermes.plugin.json +24 -4
- package/adapters/hermes/hitl-ingress/__init__.py +356 -0
- package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
- package/adapters/openclaw/README.md +136 -27
- package/adapters/openclaw/bin/ork-brain.sh +4 -0
- package/adapters/openclaw/construir.sh +19 -0
- package/adapters/openclaw/dist/hitl-ingress.js +199 -0
- package/adapters/openclaw/dist/index.js +437 -0
- package/adapters/openclaw/openclaw.plugin.json +39 -134
- package/adapters/openclaw/package.json +26 -0
- package/adapters/openclaw/src/hitl-ingress.ts +180 -0
- package/adapters/openclaw/src/index.ts +460 -0
- package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
- package/adapters/openclaw/tsconfig.json +15 -0
- package/assets/docs/markdownlint-cli2.jsonc +22 -0
- package/assets/docs/padroes/documentacao-de-produto.md +133 -0
- package/assets/docs/padroes/roadmap-de-produto.md +98 -0
- package/assets/docs/produto/README.md +14 -0
- package/assets/docs/produto/_modelo-feature.md +59 -0
- package/assets/docs/roadmap/README.md +14 -0
- package/assets/docs/roadmap/_modelo-item.md +73 -0
- package/assets/orkmind-native-schema.json +32 -0
- package/assets/orkmind_bridge.py +357 -0
- package/assets/orkmind_fixture.py +67 -0
- package/assets/orkmind_prospective.py +138 -0
- package/assets/reference-tariffs-i07.json +23 -0
- package/dist/adapters/claude-bg.js +488 -39
- package/dist/adapters/codex-controller-sensor.js +449 -0
- package/dist/adapters/codex-controller-worker.js +361 -0
- package/dist/adapters/codex-controller.js +252 -0
- package/dist/adapters/codex-events.js +205 -0
- package/dist/adapters/codex-question.js +26 -0
- package/dist/adapters/codex-runner.js +133 -0
- package/dist/adapters/codex.js +394 -0
- package/dist/agents-md.js +84 -0
- package/dist/auditoria.js +12 -9
- package/dist/auditrun.js +5 -4
- package/dist/board.js +174 -23
- package/dist/branch-de-estado.js +136 -0
- package/dist/canarios-hitl.js +177 -0
- package/dist/canarios-i43.js +543 -0
- package/dist/canarios-pulse.js +147 -0
- package/dist/canarios-sensores.js +129 -0
- package/dist/canarios.js +111 -2
- package/dist/catalogo.js +9 -0
- package/dist/ci.js +223 -0
- package/dist/ciclos.js +2 -1
- package/dist/claim-lint.js +64 -0
- package/dist/claims.js +60 -0
- package/dist/company-brain-capture.js +195 -0
- package/dist/company-brain-cli.js +123 -0
- package/dist/company-brain-client.js +61 -0
- package/dist/company-brain-contract.js +166 -0
- package/dist/company-brain-journal.js +192 -0
- package/dist/company-brain-mcp.js +33 -0
- package/dist/company-brain-migration.js +60 -0
- package/dist/company-brain-source.js +177 -0
- package/dist/company-brain-worker.js +18 -0
- package/dist/conducao-texto.js +61 -0
- package/dist/conducao.js +876 -0
- package/dist/contrato-publico.js +39 -0
- package/dist/creation-operation-store.js +251 -0
- package/dist/creation-operation.js +148 -0
- package/dist/decisao-autonoma.js +183 -0
- package/dist/delegation.js +79 -0
- package/dist/demo.js +120 -0
- package/dist/docs.js +828 -0
- package/dist/doctor.js +184 -25
- package/dist/entrega-pr.js +117 -0
- package/dist/escopo-escrita.js +71 -0
- package/dist/estado-thread.js +222 -0
- package/dist/evalrunner.js +12 -0
- package/dist/fabrica-estado.js +329 -0
- package/dist/fabrica-publicar.js +76 -0
- package/dist/fix.js +22 -1
- package/dist/gates.js +59 -11
- package/dist/handoff.js +39 -23
- package/dist/hitl-canais.js +333 -0
- package/dist/hitl-classificacao.js +131 -0
- package/dist/hitl-contract.js +465 -0
- package/dist/hitl-estado.js +136 -0
- package/dist/hitl-gates.js +554 -0
- package/dist/hitl-ingress-receipt.js +382 -0
- package/dist/hitl-local-atestado.js +57 -0
- package/dist/hitl-local-receipt.js +328 -0
- package/dist/hitl-local.js +143 -0
- package/dist/hitl-lock.js +139 -0
- package/dist/hitl-lote.js +223 -0
- package/dist/hitl-native-offer.js +97 -0
- package/dist/hitl-native.js +65 -0
- package/dist/hitl-presentation.js +209 -0
- package/dist/hitl-public-receipt.js +176 -0
- package/dist/hitl-resumo.js +209 -0
- package/dist/hitl-sessions.js +475 -0
- package/dist/hitl.js +711 -0
- package/dist/horario.js +269 -0
- package/dist/hosts.js +198 -22
- package/dist/index.js +1741 -94
- package/dist/indice.js +99 -0
- package/dist/init.js +28 -3
- package/dist/integracoes-locais.js +17 -0
- package/dist/leases.js +65 -21
- package/dist/ledger-stats.js +272 -0
- package/dist/ledger.js +109 -2
- package/dist/licoes.js +221 -0
- package/dist/liveness.js +218 -0
- package/dist/maestro-actions.js +51 -0
- package/dist/maestro-authority.js +152 -0
- package/dist/maestro-cli.js +97 -0
- package/dist/maestro-contract.js +55 -0
- package/dist/maestro-discovery.js +132 -0
- package/dist/maestro-runtime.js +268 -0
- package/dist/maestro-snapshot.js +86 -0
- package/dist/maestro-sources.js +224 -0
- package/dist/manifest.js +165 -6
- package/dist/maquina.js +102 -0
- package/dist/master-audit.js +101 -0
- package/dist/master-batch.js +43 -0
- package/dist/master-digest.js +149 -0
- package/dist/master-migracao.js +155 -0
- package/dist/master.js +361 -77
- package/dist/mcp-artifacts.js +234 -0
- package/dist/mcp-git.js +433 -0
- package/dist/mcp-install.js +310 -0
- package/dist/mcp-maestro.js +25 -0
- package/dist/mcp-server.js +469 -0
- package/dist/mcp-ship.js +436 -0
- package/dist/mcp-verify.js +173 -0
- package/dist/memoria-humana.js +207 -0
- package/dist/memoria.js +314 -78
- package/dist/memory-migration.js +279 -0
- package/dist/memory-prospective.js +148 -0
- package/dist/modos-migracao.js +191 -0
- package/dist/modos.js +151 -15
- package/dist/monitor-lock.js +110 -0
- package/dist/objective.js +449 -0
- package/dist/ocupacao.js +267 -0
- package/dist/onboarding.js +303 -0
- package/dist/orkmind.js +389 -90
- package/dist/orquestracao.js +96 -69
- package/dist/phase.js +612 -151
- package/dist/playbook-capabilities.js +150 -0
- package/dist/playbook-contracts.js +188 -0
- package/dist/policies.js +72 -0
- package/dist/portfolio-context.js +61 -0
- package/dist/portfolio.js +180 -0
- package/dist/preflight.js +207 -0
- package/dist/process-audit.js +112 -0
- package/dist/project-state.js +93 -0
- package/dist/prompts.js +25 -5
- package/dist/prova-minima.js +112 -0
- package/dist/pulse-cadencia.js +163 -0
- package/dist/pulse-consentimento.js +260 -0
- package/dist/pulse-delivery.js +323 -0
- package/dist/pulse-resposta.js +542 -0
- package/dist/pulse.js +262 -0
- package/dist/ratelimit.js +35 -2
- package/dist/recall.js +80 -3
- package/dist/redacao-saida.js +45 -0
- package/dist/redacao-url.js +66 -0
- package/dist/retry.js +640 -118
- package/dist/roadmap-reservas.js +243 -0
- package/dist/runtime-ambiente.js +38 -0
- package/dist/runtime-context.js +215 -0
- package/dist/runtime-profiles.js +869 -0
- package/dist/runtimes.js +103 -0
- package/dist/sandbox.js +9 -0
- package/dist/session-events.js +203 -0
- package/dist/session-watcher-claude.js +637 -0
- package/dist/session-watcher.js +700 -0
- package/dist/sessoes-adopt.js +154 -0
- package/dist/sessoes-inventario.js +159 -0
- package/dist/sessoes.js +16 -35
- package/dist/setup.js +691 -0
- package/dist/ship.js +168 -19
- package/dist/slug.js +1 -1
- package/dist/thread-close.js +115 -0
- package/dist/thread.js +142 -28
- package/dist/tokens.js +1 -1
- package/dist/util.js +4 -2
- package/dist/verify-sandbox.js +265 -0
- package/dist/verify.js +270 -25
- package/dist/versao.js +60 -0
- package/dist/worktree.js +31 -2
- package/dist/write-activation.js +291 -0
- package/dist/yaml.js +5 -1
- package/eval/casos/master-metrics.json +1 -1
- package/eval/casos/onboarding.json +102 -0
- package/eval/casos/orkastery-bootstrap.json +37 -3
- package/eval/casos/scope-check-capability-map.json +45 -7
- package/eval/casos/ship-release.json +3 -3
- package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
- package/eval/fixtures/b2-master-log/caso.json +1 -1
- package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
- package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
- package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
- package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
- package/eval/fixtures/fx-codex-dry/caso.json +18 -0
- package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
- package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
- package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
- package/eval/fixtures/fx-happy/caso.json +6 -4
- package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
- package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
- package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
- package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
- package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
- package/eval/fixtures/fx-omnicanal/caso.json +17 -0
- package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
- package/monitor/company-brain.cjs +17 -0
- package/monitor/pulse-scope.cjs +45 -0
- package/monitor/pulse.cron +18 -0
- package/monitor/varredura-pulse.sh +27 -0
- package/package.json +28 -7
- package/references/definition-of-done.md +2 -2
- package/schemas/claims.schema.json +25 -0
- package/schemas/company-brain.schema.json +1158 -0
- package/schemas/creation-operation.schema.json +365 -0
- package/schemas/maestro-snapshot.schema.json +2716 -0
- package/skills/README.md +3 -3
- package/skills/core/onboarding/SKILL.md +50 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
- package/skills/core/thread-state/SKILL.md +3 -1
- package/skills/governance/decision-triage/SKILL.md +4 -4
- package/skills/governance/narrative-guardian/SKILL.md +1 -1
- package/skills/governance/roadmap-keeper/SKILL.md +1 -1
- package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
- package/skills/observability/thread-tracing/SKILL.md +1 -1
- package/skills/phases/check-quality/SKILL.md +2 -2
- package/skills/phases/goal-definition/SKILL.md +1 -1
- package/skills/phases/master-metrics/SKILL.md +9 -7
- package/skills/phases/plan-specification/SKILL.md +1 -1
- package/skills/phases/ship-release/SKILL.md +1 -1
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MOTIVOS_DE_ESCALACAO_HUMANA = exports.contaDoEnvelope = exports.canalDoEnvelope = void 0;
|
|
37
|
+
exports.contratoDaResposta = contratoDaResposta;
|
|
38
|
+
exports.contextoHitl = contextoHitl;
|
|
39
|
+
exports.contextoHitlDosEventos = contextoHitlDosEventos;
|
|
40
|
+
exports.contextoDoPedidoNativo = contextoDoPedidoNativo;
|
|
41
|
+
exports.comLockHitl = comLockHitl;
|
|
42
|
+
exports.assinaturaDaResposta = assinaturaDaResposta;
|
|
43
|
+
exports.autenticarResposta = autenticarResposta;
|
|
44
|
+
exports.resolverCriterio = resolverCriterio;
|
|
45
|
+
exports.registrarPedidoHitl = registrarPedidoHitl;
|
|
46
|
+
exports.abrirPedidoGate = abrirPedidoGate;
|
|
47
|
+
exports.prepararPedidoGate = prepararPedidoGate;
|
|
48
|
+
exports.conferirResposta = conferirResposta;
|
|
49
|
+
exports.dadosDaResposta = dadosDaResposta;
|
|
50
|
+
exports.receberResposta = receberResposta;
|
|
51
|
+
exports.responderGate = responderGate;
|
|
52
|
+
exports.responderGateLocal = responderGateLocal;
|
|
53
|
+
exports.lerRespostaStdin = lerRespostaStdin;
|
|
54
|
+
/** D2: pedidos correlacionados; somente o ingresso autenticado pode atestar resposta. */
|
|
55
|
+
const fs = __importStar(require("node:fs"));
|
|
56
|
+
const hitl_lock_1 = require("./hitl-lock");
|
|
57
|
+
const node_crypto_1 = require("node:crypto");
|
|
58
|
+
const hitl_contract_1 = require("./hitl-contract");
|
|
59
|
+
const hitl_lote_1 = require("./hitl-lote");
|
|
60
|
+
const pulse_consentimento_1 = require("./pulse-consentimento");
|
|
61
|
+
const manifest_1 = require("./manifest");
|
|
62
|
+
const thread_1 = require("./thread");
|
|
63
|
+
const ledger_1 = require("./ledger");
|
|
64
|
+
const hitl_ingress_receipt_1 = require("./hitl-ingress-receipt");
|
|
65
|
+
// FX5: `canalDoEnvelope` e `contaDoEnvelope` moraram aqui ate a conta entrar no corpo
|
|
66
|
+
// assinado. As duas conferem ENVELOPE contra REGISTRO e ambiente, que e o assunto de
|
|
67
|
+
// `hitl-ingress-receipt`; mante-las aqui obrigaria esse modulo a importar este de volta, e
|
|
68
|
+
// o ciclo em tempo de execucao so aparece como `undefined is not a function`. A reexportacao
|
|
69
|
+
// preserva a superficie publica que adaptadores e testes ja usam.
|
|
70
|
+
var hitl_ingress_receipt_2 = require("./hitl-ingress-receipt");
|
|
71
|
+
Object.defineProperty(exports, "canalDoEnvelope", { enumerable: true, get: function () { return hitl_ingress_receipt_2.canalDoEnvelope; } });
|
|
72
|
+
Object.defineProperty(exports, "contaDoEnvelope", { enumerable: true, get: function () { return hitl_ingress_receipt_2.contaDoEnvelope; } });
|
|
73
|
+
const hitl_local_atestado_1 = require("./hitl-local-atestado");
|
|
74
|
+
const hitl_local_receipt_1 = require("./hitl-local-receipt");
|
|
75
|
+
const memoria_humana_1 = require("./memoria-humana");
|
|
76
|
+
const hitl_canais_1 = require("./hitl-canais");
|
|
77
|
+
const hitl_native_1 = require("./hitl-native");
|
|
78
|
+
const modos_1 = require("./modos");
|
|
79
|
+
/**
|
|
80
|
+
* D12: o canal entra no corpo assinado, e é isso que o torna recibo em vez de rótulo.
|
|
81
|
+
* Hermes e OpenClaw compartilham transporte, mas usam chaves próprias e identidades
|
|
82
|
+
* distintas; sem o canal dentro do HMAC o recibo ainda perderia essa proveniência. O envelope `v1`
|
|
83
|
+
* continua aceito byte a byte, e um `v1` nunca ganha canal por inferência.
|
|
84
|
+
*/
|
|
85
|
+
function contratoDaResposta(r) {
|
|
86
|
+
if (r.origem === 'native')
|
|
87
|
+
return 'ork.hitl-native/v1';
|
|
88
|
+
return r.canal === undefined ? 'ork.hitl-answer/v1' : 'ork.hitl-answer/v2';
|
|
89
|
+
}
|
|
90
|
+
const sha = (s) => (0, node_crypto_1.createHash)('sha256').update(s).digest('hex');
|
|
91
|
+
function contextoHitl(raiz, id) {
|
|
92
|
+
return contextoHitlDosEventos((0, thread_1.lerThread)(raiz, id), (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id)));
|
|
93
|
+
}
|
|
94
|
+
/** Mesmo contexto na autorização e na projeção, usando as fontes já observadas. */
|
|
95
|
+
function contextoHitlDosEventos(t, eventos) {
|
|
96
|
+
const despacho = eventos.filter(e => e.tipo === 'phase_dispatch').at(-1);
|
|
97
|
+
return sha(JSON.stringify([t.modo, t.faseAtual, t.status, t.base, despacho?.sessionId, despacho?.ts]));
|
|
98
|
+
}
|
|
99
|
+
/** Leitura para o callback; não abre pedido nem recebe resposta ou identidade humana. */
|
|
100
|
+
function contextoDoPedidoNativo(raiz, id, pedidoId) {
|
|
101
|
+
const events = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id));
|
|
102
|
+
const matches = events.filter(e => e.tipo === 'hitl_requested' && e.pedido?.id === pedidoId);
|
|
103
|
+
if (matches.length !== 1)
|
|
104
|
+
throw Error('hitl.native.request-unavailable');
|
|
105
|
+
const event = matches[0];
|
|
106
|
+
(0, hitl_contract_1.validarPedidoHitl)(event.pedido);
|
|
107
|
+
const pedido = event.pedido;
|
|
108
|
+
if (pedido.thread !== id || event.contexto !== contextoHitl(raiz, id) || (0, hitl_contract_1.estadoDoPedido)(pedido) !== 'aberto')
|
|
109
|
+
throw Error('hitl.native.request-stale');
|
|
110
|
+
return { pedido, contexto: event.contexto, pedidoSha256: (0, hitl_local_atestado_1.hashPedidoLocal)(pedido) };
|
|
111
|
+
}
|
|
112
|
+
/** Serializa respostas concorrentes da mesma thread; lock ocupado recusa sem efeito. */
|
|
113
|
+
function comLockHitl(raiz, id, executar) {
|
|
114
|
+
const dir = (0, thread_1.dirThread)(raiz, id);
|
|
115
|
+
(0, thread_1.lerThread)(raiz, id);
|
|
116
|
+
return (0, hitl_lock_1.comLockInspecionavel)(dir, 'thread-dispatch-ou-hitl', executar);
|
|
117
|
+
}
|
|
118
|
+
/** O corpo não é gravado. A chave pertence somente ao ingresso nativo do gateway. */
|
|
119
|
+
function assinaturaDaResposta(thread, pedido, resposta, chave) {
|
|
120
|
+
if (resposta.origem === 'native')
|
|
121
|
+
return (0, hitl_native_1.nativeSignature)(thread, pedido, resposta, chave);
|
|
122
|
+
// FX5: `conta` tem posicao FIXA no corpo v2, e `null` quando o canal nao tem conta. Um
|
|
123
|
+
// campo opcional mudaria a aridade do array conforme o envelope, e dois envelopes
|
|
124
|
+
// diferentes poderiam produzir o mesmo corpo assinado. O v1 segue byte a byte o de antes.
|
|
125
|
+
const corpo = resposta.canal === undefined
|
|
126
|
+
? ['ork.hitl-answer/v1', thread, pedido, resposta.origem, resposta.por, resposta.mensagem, resposta.recebidoEm, resposta.resposta]
|
|
127
|
+
: ['ork.hitl-answer/v2', thread, pedido, resposta.canal, resposta.conta ?? null,
|
|
128
|
+
resposta.origem, resposta.por, resposta.mensagem, resposta.recebidoEm, resposta.resposta];
|
|
129
|
+
return (0, node_crypto_1.createHmac)('sha256', chave).update(JSON.stringify(corpo)).digest('hex');
|
|
130
|
+
}
|
|
131
|
+
function autenticarResposta(thread, pedido, r, quando = new Date().toISOString()) {
|
|
132
|
+
if (r.origem === 'native') {
|
|
133
|
+
(0, hitl_native_1.authenticateNative)(thread, pedido, r, quando);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// FX1: o canal decide a chave, e por isso ele e conferido ANTES dela. Um envelope que
|
|
137
|
+
// declara canal fora do registro nunca chega a consultar segredo nenhum.
|
|
138
|
+
const canal = (0, hitl_ingress_receipt_1.canalDoEnvelope)(r);
|
|
139
|
+
(0, hitl_ingress_receipt_1.contaDoEnvelope)(canal, r.conta);
|
|
140
|
+
const chave = (0, hitl_ingress_receipt_1.chaveDoIngresso)(canal);
|
|
141
|
+
const usuarios = (process.env.ORK_HITL_TELEGRAM_USERS ?? '').split(',').map(s => s.trim());
|
|
142
|
+
const chats = (process.env.ORK_HITL_TELEGRAM_CHATS ?? '').split(',').map(s => s.trim());
|
|
143
|
+
const usuario = /^telegram:(\d+)$/.exec(r.por ?? '');
|
|
144
|
+
const mensagem = /^telegram:(-?\d+):([a-zA-Z0-9_-]{1,100})$/.exec(r.mensagem ?? '');
|
|
145
|
+
const idade = Date.parse(quando) - Date.parse(r.recebidoEm);
|
|
146
|
+
if (r.origem !== 'telegram' || !usuario || !mensagem || !usuarios.includes(usuario[1]) || !chats.includes(mensagem[1]) ||
|
|
147
|
+
!Number.isFinite(idade) || idade < 0 || idade > 60000 || typeof r.resposta !== 'string' || r.resposta.length > 4096 ||
|
|
148
|
+
!/^[a-f0-9]{64}$/.test(r.prova ?? ''))
|
|
149
|
+
throw new Error('resposta humana sem proveniência válida');
|
|
150
|
+
const esperado = assinaturaDaResposta(thread, pedido, r, chave);
|
|
151
|
+
if (!(0, node_crypto_1.timingSafeEqual)(Buffer.from(esperado, 'hex'), Buffer.from(r.prova, 'hex')))
|
|
152
|
+
throw new Error('resposta humana não autenticada');
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* D2: o criterio de uma decisao informada RESOLVE, ou o pedido nao existe.
|
|
156
|
+
*
|
|
157
|
+
* Duas das tres resolvem de verdade. `manifesto` le a chave pelo caminho pontuado e confere que
|
|
158
|
+
* ela existe e tem valor; `ledger` le o ledger da thread e confere que o evento existe. A
|
|
159
|
+
* terceira, `medicao`, so tem a FORMA conferida, e o buraco esta dito com todas as letras:
|
|
160
|
+
* um comando citado pode nao provar nada, falhar, ou nao ter relacao com a decisao, e o nucleo
|
|
161
|
+
* nao sabe a diferenca na hora de abrir o pedido.
|
|
162
|
+
*
|
|
163
|
+
* Nao se fecha esse buraco executando o comando aqui, e por dois motivos: abrir um pedido HITL
|
|
164
|
+
* viraria executar comando arbitrario, e isto roda sob lock, entao um comando lento travaria a
|
|
165
|
+
* thread inteira. Quem fecha e o CHECK, que percorre os itens `decidido` e EXECUTA os comandos
|
|
166
|
+
* citados. A divisao fica registrada assim: a validacao impede citar NADA; o CHECK impede citar
|
|
167
|
+
* MENTIRA.
|
|
168
|
+
*/
|
|
169
|
+
function resolverCriterio(raiz, pedido) {
|
|
170
|
+
const { tipo, referencia } = pedido.criterio;
|
|
171
|
+
if (tipo === 'medicao') {
|
|
172
|
+
return { resolve: true, detalhe: 'comando citado; quem executa e reprova é o CHECK' };
|
|
173
|
+
}
|
|
174
|
+
if (tipo === 'manifesto') {
|
|
175
|
+
let alvo;
|
|
176
|
+
try {
|
|
177
|
+
alvo = (0, manifest_1.exigirManifesto)(raiz).manifesto;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return { resolve: false, detalhe: 'manifesto do projeto não pôde ser lido' };
|
|
181
|
+
}
|
|
182
|
+
for (const parte of referencia.split('.')) {
|
|
183
|
+
if (!alvo || typeof alvo !== 'object' || !(parte in alvo)) {
|
|
184
|
+
return { resolve: false, detalhe: `o manifesto não tem a chave ${referencia}` };
|
|
185
|
+
}
|
|
186
|
+
alvo = alvo[parte];
|
|
187
|
+
}
|
|
188
|
+
const vazio = alvo === undefined || alvo === null || alvo === '' ||
|
|
189
|
+
(Array.isArray(alvo) && alvo.length === 0);
|
|
190
|
+
return vazio
|
|
191
|
+
? { resolve: false, detalhe: `a chave ${referencia} existe mas está vazia` }
|
|
192
|
+
: { resolve: true, detalhe: `chave ${referencia} lida do manifesto` };
|
|
193
|
+
}
|
|
194
|
+
// `ledger`: `<thread>#evento:<n>` (1-based) ou o `eventId` do proprio evento.
|
|
195
|
+
const comIndice = /^([a-zA-Z0-9][a-zA-Z0-9._-]{0,79})#evento:(\d+)$/.exec(referencia);
|
|
196
|
+
const thread = comIndice ? comIndice[1] : pedido.thread;
|
|
197
|
+
let eventos;
|
|
198
|
+
try {
|
|
199
|
+
eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, thread));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return { resolve: false, detalhe: `a thread ${thread} não tem ledger legível` };
|
|
203
|
+
}
|
|
204
|
+
if (comIndice) {
|
|
205
|
+
const n = Number(comIndice[2]);
|
|
206
|
+
return n >= 1 && n <= eventos.length
|
|
207
|
+
? { resolve: true, detalhe: `evento ${n} de ${thread}` }
|
|
208
|
+
: { resolve: false, detalhe: `${thread} não tem evento ${n}` };
|
|
209
|
+
}
|
|
210
|
+
return eventos.some(e => e.eventId === referencia)
|
|
211
|
+
? { resolve: true, detalhe: `evento ${referencia} de ${thread}` }
|
|
212
|
+
: { resolve: false, detalhe: `nenhum evento de ${thread} tem esse identificador` };
|
|
213
|
+
}
|
|
214
|
+
function registrarPedidoHitl(raiz, pedido) {
|
|
215
|
+
(0, hitl_contract_1.validarPedidoHitl)(pedido);
|
|
216
|
+
// R1, camada 1: um `decidido` cujo critério não resolve não chega a existir.
|
|
217
|
+
if ((0, hitl_contract_1.ehV2)(pedido) && pedido.classe === 'decidido') {
|
|
218
|
+
const { resolve, detalhe } = resolverCriterio(raiz, pedido);
|
|
219
|
+
if (!resolve)
|
|
220
|
+
throw new Error(`decisão informada: critério não resolve (${detalhe})`);
|
|
221
|
+
}
|
|
222
|
+
return comLockHitl(raiz, pedido.thread, () => {
|
|
223
|
+
const t = (0, thread_1.lerThread)(raiz, pedido.thread), dir = (0, thread_1.dirThread)(raiz, t.id);
|
|
224
|
+
if (t.status === 'fechada' || t.faseAtual !== pedido.fase || t.modo !== pedido.modo)
|
|
225
|
+
throw new Error('pedido não corresponde à thread corrente');
|
|
226
|
+
const eventos = (0, ledger_1.lerLedger)(dir), existente = eventos.find(e => e.tipo === 'hitl_requested' && e.pedido?.id === pedido.id);
|
|
227
|
+
if (existente) {
|
|
228
|
+
if (JSON.stringify(existente.pedido) !== JSON.stringify(pedido))
|
|
229
|
+
throw new Error('id de pedido reutilizado com outro conteúdo');
|
|
230
|
+
return existente.pedido;
|
|
231
|
+
}
|
|
232
|
+
(0, ledger_1.registrar)(dir, t.id, 'hitl_requested', { fase: pedido.fase, pedido, contexto: contextoHitl(raiz, t.id) });
|
|
233
|
+
return pedido;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* D18: diagnóstico de um `phase_result` `human.pending` que não é conclusão provada. O watcher
|
|
238
|
+
* claude-bg grava `diagnostico`; resultado anterior a ele cai no `provaOrk.fonte` sem prova.
|
|
239
|
+
* Texto limpo de controle e com teto, para caber no pedido HITL.
|
|
240
|
+
*/
|
|
241
|
+
function diagnosticoSemConclusao(resultado) {
|
|
242
|
+
const prova = resultado.provaOrk;
|
|
243
|
+
const bruto = typeof resultado.diagnostico === 'string' ? resultado.diagnostico
|
|
244
|
+
: prova?.ok === false && typeof prova.fonte === 'string' ? `sem prova do ork: ${prova.fonte}` : null;
|
|
245
|
+
const limpo = bruto === null ? '' : Array.from(bruto.replace(/\p{Cc}/gu, ' ').trim()).slice(0, 600).join('');
|
|
246
|
+
return limpo === '' ? null : limpo;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* As escalacoes tipadas que abrem gate para o dono. Lista fechada, domicilio unico: quem abre o
|
|
250
|
+
* gate e quem pergunta se um gate ainda espera o dono leem a mesma.
|
|
251
|
+
*/
|
|
252
|
+
exports.MOTIVOS_DE_ESCALACAO_HUMANA = ['policy.violation', 'cost.violation', 'retry.max_tentativas',
|
|
253
|
+
'hitl.credencial', 'runtime.unavailable'];
|
|
254
|
+
/** Uma pausa do modo cria pedido, nunca human_gate. Escalação exige evento tipado. */
|
|
255
|
+
function abrirPedidoGate(raiz, id, motivo = 'human.pending', quando = new Date().toISOString()) {
|
|
256
|
+
const preparado = prepararPedidoGate(raiz, id, motivo, quando);
|
|
257
|
+
return 'aberto' in preparado ? preparado.aberto : registrarPedidoHitl(raiz, preparado.novo);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* I-41 (GO-FIX 1, B3): o gate que ainda espera o dono, SEM escrever nada.
|
|
261
|
+
*
|
|
262
|
+
* Faz todas as conferencias de `abrirPedidoGate` e devolve o pedido aberto que ja existe, ou o
|
|
263
|
+
* pedido novo que seria registrado. Nao grava, nao toma lock e nao mexe no ledger. E por aqui que
|
|
264
|
+
* o resumo sabe quantas perguntas vao de fato sair: um gate que ainda espera o dono e pergunta; um
|
|
265
|
+
* pedido velho cuja thread ja seguiu em frente e historia, e contar historia como pergunta era o
|
|
266
|
+
* defeito de B3 (o dono via "esperando voce" e, ao consentir, recebia zero perguntas).
|
|
267
|
+
*
|
|
268
|
+
* Lanca, com a mesma mensagem de sempre, quando o gate nao espera ninguem: modo sem pausa nesta
|
|
269
|
+
* fase, conclusao da fase ainda nao registrada, ou escalacao sem evento tipado que a prove.
|
|
270
|
+
*/
|
|
271
|
+
function prepararPedidoGate(raiz, id, motivo = 'human.pending', quando = new Date().toISOString()) {
|
|
272
|
+
const t = (0, thread_1.lerThread)(raiz, id), bloco = (0, thread_1.blocoDaThread)(t, t.faseAtual);
|
|
273
|
+
const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id));
|
|
274
|
+
let semConclusao = null;
|
|
275
|
+
let parecerNegativo = '';
|
|
276
|
+
if (motivo === 'human.pending') {
|
|
277
|
+
if (!(0, thread_1.pausaNaThread)(t, t.faseAtual) || bloco.fases.at(-1) !== t.faseAtual)
|
|
278
|
+
throw new Error('modo não prevê pausa humana nesta fase');
|
|
279
|
+
// I-34 (D7, GO-FIX 1): vale o resultado do despacho corrente da fase, e só a falha de
|
|
280
|
+
// execução não é conclusão (o turno não terminou ou não entregou o artefato; cada motivo tem
|
|
281
|
+
// retry ou escalação própria). Parecer negativo (`claims.failed`, `verify.*`, `ci.failed`)
|
|
282
|
+
// é fase concluída e abre a pausa. Resultado legado sem classificação ou sem sessão vale.
|
|
283
|
+
const despacho = eventos.filter(e => e.tipo === 'phase_dispatch' && e.fase === t.faseAtual).at(-1);
|
|
284
|
+
const falhaDeExecucao = ['runtime.unavailable', 'runtime.silencio', 'runtime.rate-limited', 'artifact.missing'];
|
|
285
|
+
const doDespacho = (e) => !e.sessionId || !despacho?.sessionId || e.sessionId === despacho.sessionId;
|
|
286
|
+
const conclusao = (e) => e.tipo === 'phase_result' &&
|
|
287
|
+
!(e.classificacao === 'gate_blocked' && falhaDeExecucao.includes(String(e.motivo)));
|
|
288
|
+
if (!eventos.some(e => e.fase === t.faseAtual && doDespacho(e) && (conclusao(e) ||
|
|
289
|
+
e.tipo === 'gate_blocked' && e.motivo === 'human.pending')))
|
|
290
|
+
throw new Error('a conclusão da fase precisa estar registrada antes da pausa');
|
|
291
|
+
// D18: `human.pending` sem conclusão provada (falta de prova do ork, ou `failed` depois do
|
|
292
|
+
// Stop) leva o diagnóstico ao pedido, que não se apresenta como fase concluída.
|
|
293
|
+
const resultado = eventos.filter(e => e.tipo === 'phase_result' && e.fase === t.faseAtual && doDespacho(e)).at(-1);
|
|
294
|
+
if (resultado?.motivo === 'human.pending')
|
|
295
|
+
semConclusao = diagnosticoSemConclusao(resultado);
|
|
296
|
+
else if (PARECERES_QUE_PEDEM_REVISAO.includes(String(resultado?.motivo ?? '')))
|
|
297
|
+
parecerNegativo = String(resultado?.motivo);
|
|
298
|
+
}
|
|
299
|
+
else if (!exports.MOTIVOS_DE_ESCALACAO_HUMANA.includes(motivo) ||
|
|
300
|
+
!eventos.some(e => e.fase === t.faseAtual && (e.tipo === 'gate_blocked' && e.motivo === motivo ||
|
|
301
|
+
e.tipo === 'retry_escalated' && motivo === 'retry.max_tentativas')))
|
|
302
|
+
throw new Error('escalação tipada não comprovada');
|
|
303
|
+
// I-41 (T4d): pedido aberto e lido pelos leitores das duas versoes. Ler `.alvo.tipo` direto
|
|
304
|
+
// lancaria TypeError diante de um `decidido`, que nao tem alvo e pode estar no mesmo ledger.
|
|
305
|
+
// GO-FIX 1: o contexto e calculado uma vez sobre o que ja foi lido; reler thread e ledger por
|
|
306
|
+
// pedido deixava a conferencia quadratica, e o resumo agora a faz para cada thread da fila.
|
|
307
|
+
const contexto = contextoHitlDosEventos(t, eventos);
|
|
308
|
+
const atual = eventos.filter(e => e.tipo === 'hitl_requested' && e.contexto === contexto &&
|
|
309
|
+
(0, hitl_contract_1.alvoDoPedido)(e.pedido)?.tipo === 'gate' &&
|
|
310
|
+
(0, hitl_contract_1.motivoDoPedido)(e.pedido) === motivo).at(-1);
|
|
311
|
+
const pedidoAtual = atual?.pedido;
|
|
312
|
+
if (pedidoAtual && (0, hitl_contract_1.estadoDoPedido)(pedidoAtual, quando) === 'aberto' &&
|
|
313
|
+
!eventos.some(e => e.tipo === 'human_gate' && e.pedidoId === pedidoAtual.id))
|
|
314
|
+
return { aberto: pedidoAtual };
|
|
315
|
+
const sobre = motivo === 'human.pending' ? bloco.pausaSobre : `escalacao:${motivo}`;
|
|
316
|
+
const primeira = semConclusao ? 'Aprovar sem conclusão provada (conferi a entrega)'
|
|
317
|
+
: motivo === 'human.pending' ? 'Aprovar com as evidências apresentadas' : 'Reconhecer e encaminhar a correção';
|
|
318
|
+
const recomendada = recomendadaDoGate(motivo, semConclusao, parecerNegativo);
|
|
319
|
+
const alternativas = [
|
|
320
|
+
[primeira, motivo === 'human.pending' ? 'aprovar' : 'responder'],
|
|
321
|
+
['Solicitar revisão', 'recusar'],
|
|
322
|
+
['Continuar esperando', 'esperar'],
|
|
323
|
+
].map(([texto, acao], i) => {
|
|
324
|
+
const letra = LETRAS_DO_GATE[i];
|
|
325
|
+
return { letra, texto, acao, consequencia: hitl_lote_1.CONSEQUENCIA_DA_ACAO[acao],
|
|
326
|
+
...(letra === recomendada.letra ? { recomendada: true, porque: recomendada.porque } : {}) };
|
|
327
|
+
});
|
|
328
|
+
// A1: o ato vem do motivo ou do que a pausa declara. A pausa que libera push na base protegida
|
|
329
|
+
// e sem volta, e dizer isso e o que tira `criticos` do zero por construcao.
|
|
330
|
+
const ato = (0, hitl_contract_1.atoDaPausa)(motivo, bloco.pausaSobre);
|
|
331
|
+
// O codigo curto e unico contra os que ja estao no ledger desta thread. T11 acrescenta o
|
|
332
|
+
// comando que o resolve; a unicidade precisa nascer com o primeiro pedido que o carrega.
|
|
333
|
+
const codigo = (0, pulse_consentimento_1.gerarCodigo)(eventos.flatMap(e => {
|
|
334
|
+
const c = e.pedido?.codigo;
|
|
335
|
+
return typeof c === 'string' ? [c] : [];
|
|
336
|
+
}));
|
|
337
|
+
// I-43: thread em modo aposentado e lida para sempre, mas nao escreve pergunta nova.
|
|
338
|
+
const modo = (0, modos_1.exigirModoVivo)(t.modo);
|
|
339
|
+
const pedido = { contrato: hitl_contract_1.CONTRATO_HITL_V2, classe: 'pergunta', id: (0, node_crypto_1.randomUUID)(),
|
|
340
|
+
thread: id, fase: t.faseAtual, modo, criadoEm: quando,
|
|
341
|
+
// T6: GOAL e PLAN nascem em `profunda`. Um gate sobre premissa sem artefato e sem diff e
|
|
342
|
+
// decidido no escuro, e o modo nao sabe sobre o que se esta perguntando; a fase sabe.
|
|
343
|
+
profundidade: (0, hitl_contract_1.profundidadeDoPedido)(t.faseAtual, modo),
|
|
344
|
+
alvo: { tipo: 'gate', sobre }, motivo,
|
|
345
|
+
// I-41 (D1/T6): a pergunta e uma frase e a evidencia mora no corpo. O diagnostico de uma
|
|
346
|
+
// fase sem conclusao chega a 600 caracteres e estourava o teto da pergunta; separado, ele
|
|
347
|
+
// cabe e passa a ser lido como evidencia, que e o que ele sempre foi.
|
|
348
|
+
pergunta: `Qual é o veredito sobre ${sobre}?`,
|
|
349
|
+
alternativas,
|
|
350
|
+
corpo: [`Fase ${t.faseAtual}, motivo ${motivo}`,
|
|
351
|
+
...(semConclusao ? [linhaDoCorpo(`Sem conclusão provada: ${semConclusao}`)] : []),
|
|
352
|
+
...(parecerNegativo ? [`Parecer registrado da fase: ${parecerNegativo}`] : [])],
|
|
353
|
+
tipoDeResposta: 'objetiva', irreversivel: ato !== undefined, ...(ato ? { ato } : {}), codigo,
|
|
354
|
+
prazo: new Date(Date.parse(quando) + 60 * 60 * 1000).toISOString(), acaoPadraoAoExpirar: 'esperar',
|
|
355
|
+
respostaAceita: { tipo: 'opcao', maxCaracteres: 100 } };
|
|
356
|
+
return { novo: pedido };
|
|
357
|
+
}
|
|
358
|
+
const LETRAS_DO_GATE = ['a', 'b', 'c'];
|
|
359
|
+
/** Corta uma linha de evidencia no teto do corpo, sem deixar o pedido inteiro ser recusado. */
|
|
360
|
+
const linhaDoCorpo = (texto) => texto.slice(0, hitl_contract_1.TETOS_HITL_V2.corpoLinha);
|
|
361
|
+
/**
|
|
362
|
+
* Pareceres que ja sao conclusao da fase E pedem revisao. Um `phase_result` com qualquer um
|
|
363
|
+
* deles abre a pausa (I-34/D7), e recomendar "aprovar" em cima dele seria recomendar aprovar
|
|
364
|
+
* uma fase que o proprio produto reprovou.
|
|
365
|
+
*/
|
|
366
|
+
const PARECERES_QUE_PEDEM_REVISAO = ['claims.failed', 'claims.unverifiable', 'verify.failed', 'verify.regression', 'verify.timeout', 'verify.sem-veredito', 'ci.failed'];
|
|
367
|
+
/**
|
|
368
|
+
* Qual alternativa o nucleo recomenda neste gate, e por que, em uma linha.
|
|
369
|
+
*
|
|
370
|
+
* I-41: ate aqui a recomendacao era "Confira artefatos, claims e riscos antes de responder.",
|
|
371
|
+
* que nao aponta alternativa nenhuma e por isso nao e recomendacao: e um aviso. Os tres casos
|
|
372
|
+
* abaixo saem de FATO REGISTRADO no ledger, nunca de julgamento do agente, e e essa derivacao
|
|
373
|
+
* que faz a recomendada ser verificavel em vez de escolhida no chute.
|
|
374
|
+
*/
|
|
375
|
+
function recomendadaDoGate(motivo, semConclusao, parecer) {
|
|
376
|
+
if (motivo !== 'human.pending') {
|
|
377
|
+
return { letra: 'a', porque: `escalação tipada ${motivo}: reconhecer encaminha a correção dirigida` };
|
|
378
|
+
}
|
|
379
|
+
if (semConclusao)
|
|
380
|
+
return { letra: 'b', porque: 'a fase não tem conclusão provada; o diagnóstico está no corpo' };
|
|
381
|
+
if (parecer)
|
|
382
|
+
return { letra: 'b', porque: `o parecer registrado da fase foi ${parecer}` };
|
|
383
|
+
return { letra: 'a', porque: 'a fase registrou conclusão e as evidências acompanham o pedido' };
|
|
384
|
+
}
|
|
385
|
+
const CAMPOS_QUE_A_DERIVADA_PRESERVA = ['origem', 'por', 'mensagem', 'recebidoEm', 'prova', 'canal', 'conta'];
|
|
386
|
+
/** Quem prova que a resposta veio do dono: o envelope do proprio pedido, ou o do endereco assinado. */
|
|
387
|
+
function provaDaResposta(id, pedidoId, resposta, assinado) {
|
|
388
|
+
if (!assinado)
|
|
389
|
+
return instante => autenticarResposta(id, pedidoId, resposta, instante);
|
|
390
|
+
const { envelope } = assinado;
|
|
391
|
+
// So o endereco do pulse e aceito. Um envelope assinado para OUTRO pedido nunca serve de prova
|
|
392
|
+
// para este: sem esta porta, quem tivesse um envelope legitimo de um gate poderia aponta-lo
|
|
393
|
+
// para outro pedido, e a assinatura continuaria batendo com o endereco que ela de fato cobre.
|
|
394
|
+
if (assinado.alvo !== pulse_consentimento_1.ALVO_DO_PULSE || assinado.endereco !== pulse_consentimento_1.ENDERECO_DA_RESPOSTA) {
|
|
395
|
+
throw new Error('endereço assinado fora do pulse não prova resposta a outro pedido');
|
|
396
|
+
}
|
|
397
|
+
if (envelope.origem !== 'telegram' || resposta.native !== undefined || envelope.native !== undefined ||
|
|
398
|
+
CAMPOS_QUE_A_DERIVADA_PRESERVA.some(c => resposta[c] !== envelope[c])) {
|
|
399
|
+
throw new Error('resposta derivada diverge do envelope assinado pelo ingresso');
|
|
400
|
+
}
|
|
401
|
+
return instante => autenticarResposta(assinado.alvo, assinado.endereco, envelope, instante);
|
|
402
|
+
}
|
|
403
|
+
/** Usado sob lock tanto por gates como por sessões. Repetição não executa novamente. */
|
|
404
|
+
function conferirResposta(raiz, id, pedidoId, resposta, quando, assinado) {
|
|
405
|
+
const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id));
|
|
406
|
+
const anterior = eventos.find(e => ['human_gate', 'session_answered'].includes(e.tipo) && e.pedidoId === pedidoId);
|
|
407
|
+
if (!anterior)
|
|
408
|
+
(0, hitl_canais_1.conferirCanalSelecionado)(eventos, pedidoId, resposta.canal, resposta.origem, resposta.native?.connectionId);
|
|
409
|
+
const pendente = eventos.find(e => e.tipo === 'session_answer_sending' && e.pedidoId === pedidoId);
|
|
410
|
+
const conhecido = anterior ?? pendente;
|
|
411
|
+
const replay = conhecido && typeof resposta.prova === 'string' && conhecido.mensagem === resposta.mensagem &&
|
|
412
|
+
conhecido.autorizadoPor === resposta.por && conhecido.recibo === sha(resposta.prova);
|
|
413
|
+
// Recibo persistido de um envelope idêntico já prova sua janela original. Replay
|
|
414
|
+
// ainda verifica assinatura/allowlists, mas não volta a executar após os 60 segundos.
|
|
415
|
+
// Primeiro verifica chave, allowlists e assinatura; a janela real é exigida logo abaixo.
|
|
416
|
+
const provar = provaDaResposta(id, pedidoId, resposta, assinado);
|
|
417
|
+
provar(resposta.recebidoEm);
|
|
418
|
+
const ingresso = (0, hitl_ingress_receipt_1.instanteDoIngresso)(raiz, id, pedidoId, resposta);
|
|
419
|
+
provar(replay ? resposta.recebidoEm : ingresso ?? quando);
|
|
420
|
+
const evento = eventos.find(e => e.tipo === 'hitl_requested' && e.pedido?.id === pedidoId);
|
|
421
|
+
if (!evento)
|
|
422
|
+
throw new Error('pedido HITL não encontrado nesta thread');
|
|
423
|
+
const pedido = evento.pedido;
|
|
424
|
+
(0, hitl_contract_1.validarPedidoHitl)(pedido);
|
|
425
|
+
if (resposta.origem === 'native' && (resposta.native?.pedidoSha256 !== (0, hitl_local_atestado_1.hashPedidoLocal)(pedido) ||
|
|
426
|
+
resposta.native.context !== evento.contexto || resposta.native.expiresAt !== pedido.prazo))
|
|
427
|
+
throw Error('hitl.native.request-mismatch');
|
|
428
|
+
if (anterior) {
|
|
429
|
+
if (anterior.mensagem !== resposta.mensagem || anterior.autorizadoPor !== resposta.por || anterior.recibo !== sha(resposta.prova))
|
|
430
|
+
throw new Error('pedido já respondido; resposta divergente recusada');
|
|
431
|
+
return { pedido, anterior, opcao: null };
|
|
432
|
+
}
|
|
433
|
+
if (pendente && pedido.alvo.tipo === 'session') {
|
|
434
|
+
if (!replay)
|
|
435
|
+
throw new Error('envio já iniciado; resposta divergente recusada');
|
|
436
|
+
// responderSessao apenas consulta o envioId persistido neste caminho. Não
|
|
437
|
+
// reaplica o conteúdo, não responde à sessão nova e não autoriza o bloco.
|
|
438
|
+
return { pedido, opcao: null };
|
|
439
|
+
}
|
|
440
|
+
if (eventos.some(e => ['human_gate', 'session_answered', 'session_answer_sending'].includes(e.tipo) &&
|
|
441
|
+
e.pedidoId !== pedidoId && e.mensagem === resposta.mensagem))
|
|
442
|
+
throw new Error('mensagem já usada ou reservada em outro pedido');
|
|
443
|
+
if (evento.contexto !== contextoHitl(raiz, id))
|
|
444
|
+
throw new Error('pedido antigo: fase, modo ou sessão mudou');
|
|
445
|
+
return { pedido, opcao: (0, hitl_contract_1.validarRespostaHitl)(pedido, resposta.resposta, quando) };
|
|
446
|
+
}
|
|
447
|
+
function dadosDaResposta(pedido, r) {
|
|
448
|
+
const canal = (0, hitl_ingress_receipt_1.canalDoEnvelope)(r);
|
|
449
|
+
return { contrato: pedido.contrato, pedidoId: pedido.id, fase: pedido.fase, origem: r.origem,
|
|
450
|
+
autorizadoPor: r.por, mensagem: r.mensagem, recebidoEm: r.recebidoEm, recibo: sha(r.prova),
|
|
451
|
+
canal, conta: r.origem === 'native' ? r.conta : (0, hitl_ingress_receipt_1.contaDoEnvelope)(canal, r.conta), contratoResposta: contratoDaResposta(r),
|
|
452
|
+
...(r.origem === 'native' ? { native: hitl_native_1.nativeContextSchema.parse(r.native) } : {}) };
|
|
453
|
+
}
|
|
454
|
+
function receberResposta(raiz, id, pedidoId, r, quando = new Date().toISOString(), assinado) {
|
|
455
|
+
// Consulta sem efeito: prova a janela antes da contenção, mas não autoriza efeito fora do lock.
|
|
456
|
+
const conferida = conferirResposta(raiz, id, pedidoId, r, quando, assinado);
|
|
457
|
+
if (conferida.anterior || (0, hitl_ingress_receipt_1.instanteDoIngresso)(raiz, id, pedidoId, r))
|
|
458
|
+
return;
|
|
459
|
+
const pendente = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id)).some(e => e.tipo === 'session_answer_sending' && e.pedidoId === pedidoId);
|
|
460
|
+
if (!pendente)
|
|
461
|
+
(0, hitl_ingress_receipt_1.gravarIngresso)(raiz, id, pedidoId, r, quando);
|
|
462
|
+
}
|
|
463
|
+
function responderGate(raiz, id, pedidoId, r, quando, assinado) {
|
|
464
|
+
receberResposta(raiz, id, pedidoId, r, quando, assinado);
|
|
465
|
+
const resultado = comLockHitl(raiz, id, () => {
|
|
466
|
+
const { pedido, anterior, opcao } = conferirResposta(raiz, id, pedidoId, r, quando, assinado);
|
|
467
|
+
if (pedido.alvo.tipo !== 'gate')
|
|
468
|
+
throw new Error('pedido destina-se à sessão, não ao gate');
|
|
469
|
+
if (anterior)
|
|
470
|
+
return { ok: true, pedidoId, estado: String(anterior.estado), repetida: true, evento: anterior };
|
|
471
|
+
const evidencia = (0, hitl_ingress_receipt_1.evidenciaDoIngresso)(raiz, id, pedidoId, r);
|
|
472
|
+
const veredito = registrarVereditoGate(raiz, id, pedido, opcao, {
|
|
473
|
+
...dadosDaResposta(pedido, r), source: 'human', evidencia: evidencia.arquivo, evidenciaSha256: evidencia.sha256,
|
|
474
|
+
// O caminho que o numero da pergunta percorreu ate este pedido, para a auditoria refazer.
|
|
475
|
+
...(assinado ? { enderecoAssinado: { alvo: assinado.alvo, endereco: assinado.endereco, ...assinado.rastro } } : {}),
|
|
476
|
+
});
|
|
477
|
+
return { ok: true, pedidoId, estado: String(veredito.estado), repetida: false, evento: veredito };
|
|
478
|
+
});
|
|
479
|
+
if (resultado.repetida || resultado.estado !== 'aprovado') {
|
|
480
|
+
const { evento: _evento, ...publico } = resultado;
|
|
481
|
+
return publico;
|
|
482
|
+
}
|
|
483
|
+
const publicacaoMemoria = (0, memoria_humana_1.publicarAprovacaoHumana)(raiz, id, resultado.evento);
|
|
484
|
+
const { evento: _evento, ...publico } = resultado;
|
|
485
|
+
return { ...publico, publicacaoMemoria };
|
|
486
|
+
}
|
|
487
|
+
/** Transição única para ambos os ingressos: reconhecer escalação não libera policy. */
|
|
488
|
+
function registrarVereditoGate(raiz, id, pedido, opcao, proveniencia) {
|
|
489
|
+
return (0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, id), id, 'human_gate', { ...proveniencia, ...(0, hitl_contract_1.vereditoDoGate)(pedido, opcao) });
|
|
490
|
+
}
|
|
491
|
+
/** Não aceita envelope JSON: o servidor fornece capacidade local de uso único. */
|
|
492
|
+
function responderGateLocal(raiz, id, pedidoId, token) {
|
|
493
|
+
const atestado = (0, hitl_local_atestado_1.consumirAtestadoLocal)(token, raiz, id, pedidoId);
|
|
494
|
+
const resultado = comLockHitl(raiz, id, () => {
|
|
495
|
+
const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id));
|
|
496
|
+
const anterior = eventos.find(e => e.tipo === 'human_gate' && e.pedidoId === pedidoId);
|
|
497
|
+
if (anterior)
|
|
498
|
+
return { ok: true, pedidoId, estado: String(anterior.estado), repetida: true };
|
|
499
|
+
(0, hitl_canais_1.conferirCanalSelecionado)(eventos, pedidoId, atestado.host, 'mcp-local', atestado.connectionId, atestado.requestId);
|
|
500
|
+
const evento = eventos.find(e => e.tipo === 'hitl_requested' && e.pedido?.id === pedidoId);
|
|
501
|
+
(0, hitl_contract_1.validarPedidoHitl)(evento?.pedido);
|
|
502
|
+
const pedido = evento.pedido;
|
|
503
|
+
if (pedido.thread !== id || evento.contexto !== atestado.contexto || contextoHitl(raiz, id) !== atestado.contexto ||
|
|
504
|
+
(0, hitl_local_atestado_1.hashPedidoLocal)(pedido) !== atestado.pedidoSha256)
|
|
505
|
+
throw new Error('hitl.local.pedido-antigo');
|
|
506
|
+
const opcao = (0, hitl_contract_1.validarRespostaHitl)(pedido, atestado.opcao);
|
|
507
|
+
const evidencia = (0, hitl_local_receipt_1.gravarEvidenciaLocal)(atestado, (0, hitl_contract_1.vereditoDoGate)(pedido, opcao));
|
|
508
|
+
const veredito = registrarVereditoGate(raiz, id, pedido, opcao, {
|
|
509
|
+
contrato: pedido.contrato, pedidoId, fase: pedido.fase, origem: 'mcp-local', source: 'human',
|
|
510
|
+
proveniencia: 'ork.mcp-elicitation/v1',
|
|
511
|
+
// D12: os quatro canais gravam `canal`; aqui ele vem da conexão, não de argumento.
|
|
512
|
+
canal: atestado.host, contratoResposta: 'ork.mcp-elicitation/v1',
|
|
513
|
+
autorizadoPor: `mcp-local:${atestado.host}`, conexao: atestado.connectionId, solicitacao: atestado.requestId,
|
|
514
|
+
contexto: atestado.contexto, pedidoSha256: atestado.pedidoSha256,
|
|
515
|
+
recebidoEm: atestado.recebidoEm, recibo: evidencia.recibo,
|
|
516
|
+
evidencia: evidencia.arquivo, evidenciaSha256: evidencia.sha256,
|
|
517
|
+
});
|
|
518
|
+
return { ok: true, pedidoId, estado: String(veredito.estado), repetida: false, evento: veredito };
|
|
519
|
+
});
|
|
520
|
+
const evento = 'evento' in resultado ? resultado.evento : undefined;
|
|
521
|
+
const { evento: _evento, ...publico } = resultado;
|
|
522
|
+
if (resultado.repetida || resultado.estado !== 'aprovado' || !evento)
|
|
523
|
+
return publico;
|
|
524
|
+
const publicacaoMemoria = (0, memoria_humana_1.publicarAprovacaoHumana)(raiz, id, evento);
|
|
525
|
+
return { ...publico, publicacaoMemoria };
|
|
526
|
+
}
|
|
527
|
+
/** Stdin contém um envelope assinado pelo ingresso; argv só carrega identificadores. */
|
|
528
|
+
function lerRespostaStdin(meta) {
|
|
529
|
+
const partes = [];
|
|
530
|
+
let total = 0;
|
|
531
|
+
while (true) {
|
|
532
|
+
const buf = Buffer.alloc(4096), n = fs.readSync(0, buf, 0, buf.length, null);
|
|
533
|
+
if (!n)
|
|
534
|
+
break;
|
|
535
|
+
total += n;
|
|
536
|
+
if (total > 32768)
|
|
537
|
+
throw new Error('envelope HITL excede limite');
|
|
538
|
+
partes.push(buf.subarray(0, n));
|
|
539
|
+
}
|
|
540
|
+
let r;
|
|
541
|
+
try {
|
|
542
|
+
r = JSON.parse(Buffer.concat(partes).toString('utf8'));
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
throw new Error('ingresso autenticado exige envelope JSON em stdin');
|
|
546
|
+
}
|
|
547
|
+
// D12: o canal tambem e proveniencia. Envelope que declara canal sem `--canal` em argv,
|
|
548
|
+
// ou com canal diferente, e recusado antes de qualquer conferencia de assinatura.
|
|
549
|
+
if (!r || r.por !== meta.por || r.mensagem !== meta.mensagem || r.origem !== meta.origem ||
|
|
550
|
+
(r.canal ?? undefined) !== (meta.canal ?? undefined) ||
|
|
551
|
+
(r.conta ?? undefined) !== (meta.conta ?? undefined))
|
|
552
|
+
throw new Error('proveniência em argv diverge do envelope');
|
|
553
|
+
return r;
|
|
554
|
+
}
|