@orkastery/cli 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/adapters/README.md +11 -0
  2. package/adapters/claude-code/.claude-plugin/plugin.json +3 -1
  3. package/adapters/claude-code/agents/ork-check.md +1 -1
  4. package/adapters/claude-code/agents/ork-go.md +2 -2
  5. package/adapters/claude-code/agents/ork-goal.md +1 -1
  6. package/adapters/claude-code/agents/ork-master.md +1 -1
  7. package/adapters/claude-code/agents/ork-plan.md +1 -1
  8. package/adapters/claude-code/agents/ork-ship.md +1 -1
  9. package/adapters/claude-code/commands/master.md +11 -8
  10. package/adapters/claude-code/commands/ork.md +8 -0
  11. package/adapters/claude-code/commands/ship.md +2 -2
  12. package/adapters/codex/skills/ork/SKILL.md +12 -1
  13. package/adapters/hermes/README.md +3 -0
  14. package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
  15. package/adapters/hermes/bin/ork-brain.sh +3 -0
  16. package/adapters/hermes/bin/ork-maestro.sh +4 -1
  17. package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
  18. package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
  19. package/adapters/hermes/hermes.plugin.json +2 -0
  20. package/adapters/hermes/hitl-ingress/__init__.py +36 -3
  21. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
  22. package/adapters/openclaw/README.md +34 -3
  23. package/adapters/openclaw/dist/hitl-ingress.js +37 -3
  24. package/adapters/openclaw/dist/index.js +92 -39
  25. package/adapters/openclaw/openclaw.plugin.json +10 -3
  26. package/adapters/openclaw/package.json +2 -2
  27. package/adapters/openclaw/src/hitl-ingress.ts +32 -3
  28. package/adapters/openclaw/src/index.ts +108 -41
  29. package/assets/docs/padroes/documentacao-de-produto.md +2 -2
  30. package/assets/docs/padroes/roadmap-de-produto.md +1 -1
  31. package/assets/docs/produto/_modelo-feature.md +2 -0
  32. package/assets/orkmind_bridge.py +214 -1
  33. package/dist/adapters/claude-bg.js +88 -9
  34. package/dist/adapters/codex-events.js +3 -1
  35. package/dist/board.js +74 -0
  36. package/dist/branch-de-estado.js +5 -4
  37. package/dist/busca-semantica.js +161 -0
  38. package/dist/ci.js +141 -10
  39. package/dist/company-brain-cli.js +27 -2
  40. package/dist/company-brain-context.js +132 -0
  41. package/dist/company-brain-dossie.js +376 -0
  42. package/dist/company-brain-mcp.js +4 -1
  43. package/dist/conducao.js +117 -31
  44. package/dist/decisao-autonoma.js +42 -0
  45. package/dist/docs.js +64 -22
  46. package/dist/doctor.js +33 -0
  47. package/dist/entrega-pr.js +109 -5
  48. package/dist/experiencia-instalacao.js +256 -0
  49. package/dist/experiencia.js +72 -0
  50. package/dist/fabrica-estado.js +43 -10
  51. package/dist/fechamento.js +105 -0
  52. package/dist/fix.js +1 -1
  53. package/dist/forja.js +435 -0
  54. package/dist/gates.js +10 -1
  55. package/dist/hitl-classificacao.js +11 -1
  56. package/dist/hitl-contract.js +50 -7
  57. package/dist/hitl-curto.js +158 -0
  58. package/dist/hitl-gates.js +67 -1
  59. package/dist/hitl-lote.js +30 -15
  60. package/dist/hitl-presentation.js +10 -7
  61. package/dist/hitl-resumo.js +33 -12
  62. package/dist/hitl-texto-livre.js +150 -0
  63. package/dist/hosts.js +86 -1
  64. package/dist/index.js +605 -41
  65. package/dist/indice-vetorial.js +317 -0
  66. package/dist/init.js +13 -0
  67. package/dist/integracoes-locais.js +1 -1
  68. package/dist/intelligence-benchmark-contract.js +570 -0
  69. package/dist/intelligence-graph-cli.js +430 -0
  70. package/dist/intelligence-graph-contract.js +577 -0
  71. package/dist/intelligence-graph-extract-md.js +469 -0
  72. package/dist/intelligence-graph-extract-ts.js +810 -0
  73. package/dist/intelligence-graph-extract.js +369 -0
  74. package/dist/intelligence-graph-index.js +512 -0
  75. package/dist/intelligence-graph-parsers.js +327 -0
  76. package/dist/intelligence-graph-query.js +344 -0
  77. package/dist/intelligence-graph-repo.js +187 -0
  78. package/dist/leases.js +84 -0
  79. package/dist/ledger.js +14 -0
  80. package/dist/maestro-cli.js +10 -1
  81. package/dist/maestro-contract.js +5 -1
  82. package/dist/maestro-discovery.js +16 -2
  83. package/dist/maestro-snapshot.js +6 -2
  84. package/dist/manifest.js +142 -5
  85. package/dist/master-batch.js +3 -2
  86. package/dist/master-digest.js +2 -2
  87. package/dist/master-nota.js +203 -0
  88. package/dist/master.js +4 -0
  89. package/dist/mcp-experiencia.js +164 -0
  90. package/dist/mcp-git.js +36 -10
  91. package/dist/mcp-install.js +3 -1
  92. package/dist/mcp-maestro.js +3 -2
  93. package/dist/mcp-server.js +84 -5
  94. package/dist/memoria.js +147 -1
  95. package/dist/network-roadmap.js +907 -0
  96. package/dist/ocupacao.js +38 -2
  97. package/dist/onboarding.js +113 -7
  98. package/dist/orkmind.js +165 -9
  99. package/dist/phase.js +166 -13
  100. package/dist/policies.js +2 -0
  101. package/dist/projeto-alvo.js +551 -0
  102. package/dist/prompts.js +1 -1
  103. package/dist/pulse-consentimento.js +4 -2
  104. package/dist/pulse-delivery.js +5 -0
  105. package/dist/pulse-resposta.js +674 -49
  106. package/dist/pulse.js +21 -0
  107. package/dist/ratelimit.js +3 -2
  108. package/dist/retry.js +230 -9
  109. package/dist/roadmap-reservas.js +262 -20
  110. package/dist/roadmap-status.js +213 -0
  111. package/dist/runtime-ambiente.js +12 -1
  112. package/dist/session-watcher-claude.js +26 -0
  113. package/dist/sessoes.js +49 -15
  114. package/dist/thread-close.js +3 -0
  115. package/dist/verify.js +5 -4
  116. package/dist/worktree.js +77 -0
  117. package/eval/casos/orchestration-experience-pt-br.json +48 -0
  118. package/eval/casos/orchestration-experience.json +48 -0
  119. package/package.json +1 -1
  120. package/schemas/code-artifact-graph.v1.schema.json +581 -0
  121. package/schemas/graph-benchmark.v1.schema.json +2240 -0
  122. package/schemas/maestro-snapshot.schema.json +23 -0
  123. package/skills/README.md +7 -5
  124. package/skills/core/onboarding/SKILL.md +7 -0
  125. package/skills/core/orchestration-experience/SKILL.md +113 -0
  126. package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
  127. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  128. package/skills/phases/go-implementation/SKILL.md +2 -0
@@ -0,0 +1,376 @@
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.ID_DE_DECISAO = exports.DOSSIE_SCHEMA = void 0;
37
+ exports.buildDossie = buildDossie;
38
+ /**
39
+ * K3.1: dossiê de decisão. Só leitura: compõe o que o núcleo já grava (o ledger da thread, o objetivo
40
+ * do K1 e o escopo vinculado) com o pacote de contexto do Brain, sem registrar nada. Decidir continua
41
+ * sendo `ork decisao registrar` (decisão delegada) ou a resposta do dono pelo ingresso autenticado.
42
+ */
43
+ const fs = __importStar(require("node:fs"));
44
+ const path = __importStar(require("node:path"));
45
+ const company_brain_client_1 = require("./company-brain-client");
46
+ const company_brain_contract_1 = require("./company-brain-contract");
47
+ const company_brain_context_1 = require("./company-brain-context");
48
+ const company_brain_source_1 = require("./company-brain-source");
49
+ const decisao_autonoma_1 = require("./decisao-autonoma");
50
+ const gates_1 = require("./gates");
51
+ const hitl_contract_1 = require("./hitl-contract");
52
+ const objective_1 = require("./objective");
53
+ const portfolio_1 = require("./portfolio");
54
+ const thread_1 = require("./thread");
55
+ exports.DOSSIE_SCHEMA = 'ork.dossie-de-decisao/v1';
56
+ /** O id que o dossiê aceita para uma decisão: o do pedido ou o que o Brain dá ao fato. */
57
+ exports.ID_DE_DECISAO = /^(?:[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}|fact-[a-f0-9]{64})$/;
58
+ /** Os mesmos números do pacote de contexto: lote da seleção e teto de `get` no caso misto. */
59
+ const LIMITE = 1000;
60
+ const LIMITE_DE_GET = 100;
61
+ const comparar = (a, b) => a < b ? -1 : a > b ? 1 : 0;
62
+ const falha = (state) => ['forbidden', 'unavailable', 'conflict'].includes(state);
63
+ const texto = (v) => typeof v === 'string' ? v : null;
64
+ /**
65
+ * D10: a mesma conferência do ramo `context` de `ork brain`, repetida aqui para não tocar aquele
66
+ * comando enquanto outra thread o muda. O escopo vale só se for desta thread e do formato do bind.
67
+ */
68
+ function escopoVinculado(raiz, thread) {
69
+ const file = path.join((0, thread_1.dirThread)(raiz, thread), 'brain-scope.json');
70
+ if (!fs.existsSync(file))
71
+ return null;
72
+ let binding;
73
+ try {
74
+ binding = JSON.parse(fs.readFileSync(file, 'utf8'));
75
+ }
76
+ catch {
77
+ throw Error('brain.scope.invalid');
78
+ }
79
+ if (binding?.schema !== 'ork.brain-cycle-scope/v1' || binding.version !== 1 || binding.thread !== thread || typeof binding.projectId !== 'string' ||
80
+ !Array.isArray(binding.initiativeIds) || binding.initiativeIds.some((i) => typeof i !== 'string'))
81
+ throw Error('brain.scope.invalid');
82
+ return { projectId: binding.projectId, initiativeIds: binding.initiativeIds };
83
+ }
84
+ /**
85
+ * D3: o objetivo é o ticket do K1 (`creationOrigin.ticketId`) e, sem ele, o objetivo cuja lista de
86
+ * threads contém esta; o projeto é o escopo vinculado (o que a captura grava no `cycle`) e, sem ele, o
87
+ * `portfolio` do objetivo. Fonte faltando ou discordando vira lacuna, nunca escolha silenciosa.
88
+ */
89
+ function vinculoDaThread(raiz, t, lacuna) {
90
+ let achado = null, semObjetivo = true;
91
+ const ticket = t.creationOrigin?.ticketId;
92
+ try {
93
+ if (ticket) {
94
+ const o = (0, objective_1.readObjective)(raiz, ticket);
95
+ if (!o.threads.some(x => x.id === t.id)) {
96
+ lacuna(t.id, 'vinculo.divergente');
97
+ semObjetivo = false;
98
+ }
99
+ else
100
+ achado = { o, origem: 'ticket' };
101
+ }
102
+ else {
103
+ const comEla = (0, objective_1.listObjectives)(raiz).filter(o => o.threads.some(x => x.id === t.id));
104
+ if (comEla.length > 1) {
105
+ lacuna(t.id, 'vinculo.divergente');
106
+ semObjetivo = false;
107
+ }
108
+ else if (comEla.length === 1)
109
+ achado = { o: comEla[0], origem: 'lista-de-threads' };
110
+ }
111
+ }
112
+ catch {
113
+ lacuna(ticket ?? t.id, 'objetivo.indisponivel');
114
+ semObjetivo = false;
115
+ }
116
+ if (!achado && semObjetivo)
117
+ lacuna(t.id, 'objetivo.ausente');
118
+ const escopo = escopoVinculado(raiz, t.id), doObjetivo = achado?.o.envelope.portfolio ?? null;
119
+ const ordenados = (ids) => [...new Set(ids)].sort(comparar);
120
+ let projeto = null;
121
+ if (escopo) {
122
+ const entidade = (0, portfolio_1.findEntity)(raiz, escopo.projectId);
123
+ projeto = { productId: entidade?.kind === 'project' ? entidade.productId : null, projectId: escopo.projectId,
124
+ initiativeIds: ordenados(escopo.initiativeIds), origem: 'escopo-vinculado' };
125
+ if (doObjetivo && (doObjetivo.projectId !== projeto.projectId ||
126
+ JSON.stringify(ordenados(doObjetivo.initiativeIds)) !== JSON.stringify(projeto.initiativeIds)))
127
+ lacuna(t.id, 'vinculo.divergente');
128
+ }
129
+ else if (doObjetivo) {
130
+ projeto = { productId: doObjetivo.productId, projectId: doObjetivo.projectId, initiativeIds: ordenados(doObjetivo.initiativeIds), origem: 'objetivo' };
131
+ }
132
+ else
133
+ lacuna(t.id, 'projeto.ausente');
134
+ // Título e estado ficam fora do envelope com hash: o que faltar sai vazio, nunca `undefined` no digest.
135
+ const objetivo = achado ? { id: achado.o.id, titulo: texto(achado.o.title) ?? '', estado: texto(achado.o.status) ?? '',
136
+ envelopeHash: achado.o.envelope.hash, origem: achado.origem } : null;
137
+ return { objetivo, projeto, brain: { thread_id: t.id, objective_id: objetivo?.id ?? null, project_id: projeto?.projectId ?? null,
138
+ initiative_ids: projeto?.initiativeIds ?? [] } };
139
+ }
140
+ /**
141
+ * Uma seleção por lote de ids `fact-…`; retido chega sem id, e só o caso misto pede `get`, como no contexto.
142
+ * Seleção que não responde `ok` nem `empty` fecha o dossiê: sem estado conhecido, nada local vira conteúdo.
143
+ */
144
+ function consultarFatos(transport, tenant, ids) {
145
+ const ok = new Map(), retidos = new Set();
146
+ let gets = 0;
147
+ for (let inicio = 0; inicio < ids.length; inicio += LIMITE) {
148
+ const lote = ids.slice(inicio, inicio + LIMITE), doLote = new Set(lote);
149
+ const r = transport({ schema: company_brain_client_1.BRAIN_API, operation: 'query', payload: (0, company_brain_contract_1.validateContract)({ schema: 'orkmind.company-brain-selection/v1',
150
+ tenant_id: tenant, facets: { ids: lote, kinds: [], workspace_ids: [], source_instances: [] }, mode: 'selection', limit: lote.length, offset: 0 }) });
151
+ if (r.state !== 'ok' && r.state !== 'empty')
152
+ return r;
153
+ const itens = Array.isArray(r.items) ? r.items : [];
154
+ for (const item of itens)
155
+ if (item?.state === 'ok' && doLote.has(item.entity?.id))
156
+ ok.set(item.entity.id, item.entity);
157
+ const faltantes = lote.filter(id => !ok.has(id)), retidosNoLote = itens.filter((i) => i?.state === 'withheld').length;
158
+ if (retidosNoLote && retidosNoLote === faltantes.length)
159
+ faltantes.forEach(id => retidos.add(id));
160
+ else if (retidosNoLote)
161
+ for (const id of faltantes) {
162
+ if (++gets > LIMITE_DE_GET)
163
+ return { schema: company_brain_client_1.BRAIN_API, state: 'unavailable', error: 'brain.dossie.get-limit' };
164
+ const g = transport({ schema: company_brain_client_1.BRAIN_API, operation: 'get', payload: { tenant_id: tenant, id } });
165
+ if (falha(g.state))
166
+ return g;
167
+ if (g.state === 'withheld')
168
+ retidos.add(id);
169
+ else if (g.state === 'ok' && g.entity?.id === id)
170
+ ok.set(id, g.entity);
171
+ }
172
+ }
173
+ return { ok, retidos };
174
+ }
175
+ const TIPOS_DO_DOSSIE = new Set(['autonomous_decision', 'hitl_requested', 'human_gate', 'human_decision']);
176
+ /**
177
+ * P2: o ledger é lido pela `readCycle`, a mesma leitura da captura. Uma linha que o contrato do Brain não
178
+ * representa (horário fora do formato, em ledgers antigos) derruba a leitura inteira; então a faixa que
179
+ * recusa é partida ao meio até isolar a linha, que fica sem id no Brain. Qualquer outro erro (linha
180
+ * corrompida, de outra thread) continua recusando o dossiê, como recusa a captura.
181
+ */
182
+ function linhasDoLedger(bytes, scope) {
183
+ const brutas = bytes.subarray(0, bytes.lastIndexOf(10) + 1).toString('utf8').split('\n').slice(0, -1);
184
+ const inicios = [0];
185
+ for (const raw of brutas)
186
+ inicios.push(inicios[inicios.length - 1] + Buffer.byteLength(raw) + 1);
187
+ const linhas = [];
188
+ const faixa = (de, ate) => {
189
+ try {
190
+ (0, company_brain_source_1.readCycle)(bytes.subarray(0, inicios[ate]), scope, inicios[de]).records
191
+ .forEach((r, i) => linhas.push({ n: de + i + 1, e: JSON.parse(brutas[de + i]), evento: r.event }));
192
+ }
193
+ catch (erro) {
194
+ if (erro.message !== 'brain.contract.invalid')
195
+ throw erro;
196
+ if (ate - de === 1) {
197
+ linhas.push({ n: de + 1, e: JSON.parse(brutas[de]), evento: null });
198
+ return;
199
+ }
200
+ const meio = de + Math.floor((ate - de) / 2);
201
+ faixa(de, meio);
202
+ faixa(meio, ate);
203
+ }
204
+ };
205
+ if (brutas.length)
206
+ faixa(0, brutas.length);
207
+ return linhas.filter(l => TIPOS_DO_DOSSIE.has(l.e.tipo));
208
+ }
209
+ /**
210
+ * Monta o dossiê de uma thread ou de uma decisão dela. A fonte canônica da decisão é o ledger; o Brain
211
+ * é projeção, e a diferença entre os dois sai como frescor e lacuna. Item sem citação, resposta sem
212
+ * recibo conferido e decisão fora do contrato viram lacuna, nunca conteúdo.
213
+ */
214
+ function buildDossie(c, thread, decisao, transport) {
215
+ // Filtro vazio é erro, não a thread inteira: quem pede uma decisão recebe essa ou a recusa.
216
+ if (decisao !== undefined && !exports.ID_DE_DECISAO.test(decisao))
217
+ throw Error('brain.dossie.decisao-invalida');
218
+ const filtro = decisao ?? null;
219
+ const t = (0, thread_1.lerThread)(c.raiz, thread), tenant = c.manifesto.memory.tenant;
220
+ const lacunas = [];
221
+ const lacuna = (id, codigo) => { lacunas.push({ id, codigo }); };
222
+ const vinculo = vinculoDaThread(c.raiz, t, lacuna);
223
+ const base = { schema: exports.DOSSIE_SCHEMA, tenant, contrato: company_brain_contract_1.CONTRACT_HASH, thread: t.id, decisao: filtro };
224
+ const encerrar = (r) => ({ ...base, state: r.state, ...(r.error ? { error: r.error } : {}),
225
+ vinculo, contexto: null, decisoes: [], lacunas: [], digest: null, consultadoEm: new Date().toISOString() });
226
+ const arquivo = path.join((0, thread_1.dirThread)(c.raiz, t.id), 'ledger.jsonl');
227
+ const bytes = fs.existsSync(arquivo) ? (0, company_brain_source_1.readSourceFile)(arquivo) : Buffer.alloc(0);
228
+ const linhas = linhasDoLedger(bytes, { tenant, instance: c.manifesto.project.name, thread: t.id, aclRef: 'ork-factory' });
229
+ // Cada linha é classificada uma vez, pelo validador do contrato; o que não valida é formato anterior.
230
+ const classe = new Map(), respostas = new Map(), revertidaPor = new Map();
231
+ const semIngresso = [];
232
+ const pedidoDe = (l) => l.e.pedido;
233
+ for (const l of linhas) {
234
+ const p = l.e.pedido;
235
+ if (l.e.tipo === 'autonomous_decision') {
236
+ let decidido = false;
237
+ try {
238
+ (0, hitl_contract_1.validarPedidoHitlV2)(p);
239
+ decidido = p.classe === 'decidido';
240
+ }
241
+ catch {
242
+ decidido = false;
243
+ }
244
+ classe.set(l, decidido ? 'decidido' : 'legado');
245
+ if (decidido && typeof l.e.reverte === 'string')
246
+ revertidaPor.set(l.e.reverte, l);
247
+ }
248
+ else if (l.e.tipo === 'hitl_requested') {
249
+ try {
250
+ (0, hitl_contract_1.validarPedidoHitl)(p);
251
+ if ((0, hitl_contract_1.alvoDoPedido)(p)?.tipo === 'gate')
252
+ classe.set(l, 'pergunta');
253
+ }
254
+ catch { /* fora do contrato: não é pergunta */ }
255
+ }
256
+ else if (l.e.tipo === 'human_gate' && typeof l.e.pedidoId === 'string')
257
+ respostas.set(l.e.pedidoId, [...(respostas.get(l.e.pedidoId) ?? []), l]);
258
+ else if (l.e.tipo === 'human_decision')
259
+ semIngresso.push(l);
260
+ }
261
+ const candidatas = [...classe.keys()];
262
+ const referencia = (l) => `threads/${t.id}/ledger.jsonl#L${l.n}`;
263
+ const idDe = (l) => classe.get(l) !== 'legado' ? String(pedidoDe(l).id) : l.evento?.aggregate_id ?? referencia(l);
264
+ const escolhidas = filtro === null ? candidatas : candidatas.filter(l => idDe(l) === filtro || l.evento?.aggregate_id === filtro);
265
+ if (filtro !== null && !escolhidas.length)
266
+ lacuna(filtro, 'decisao.desconhecida');
267
+ // D8: `human_decision` é o relato de alguém sobre o dono, sem recibo: nunca vira decisão do dono.
268
+ if (filtro === null)
269
+ for (const l of semIngresso)
270
+ lacuna(referencia(l), 'decisao.humana-sem-ingresso');
271
+ // D4: a resposta só entra com o recibo reconferido pelas validadoras do núcleo, qualquer que seja o veredito.
272
+ const provadas = new Map();
273
+ for (const l of escolhidas)
274
+ if (classe.get(l) === 'pergunta') {
275
+ const gates = respostas.get(idDe(l)) ?? [];
276
+ if (gates.length === 1 && gates[0].evento && (0, gates_1.reciboHumanoConfere)(c.raiz, t.id, gates[0].e))
277
+ provadas.set(idDe(l), gates[0]);
278
+ }
279
+ let contexto = null;
280
+ if (vinculo.projeto) {
281
+ contexto = (0, company_brain_context_1.buildContext)(c, [vinculo.projeto.projectId, ...vinculo.projeto.initiativeIds], transport, t.id);
282
+ if (falha(contexto.state))
283
+ return encerrar(contexto);
284
+ }
285
+ // A decisão que desfaz outra também é conferida: a relação é conteúdo dela, e some se ela estiver retida.
286
+ const reversoras = escolhidas.flatMap(l => classe.get(l) === 'decidido' ? [revertidaPor.get(idDe(l))].filter((x) => !!x) : []);
287
+ const fatos = [...new Set([...escolhidas, ...provadas.values(), ...reversoras].flatMap(l => l.evento ? [l.evento.aggregate_id] : []))].sort(comparar);
288
+ let noBrain = { ok: new Map(), retidos: new Set() };
289
+ if (fatos.length) {
290
+ const r = consultarFatos(transport, tenant, fatos);
291
+ if ('state' in r)
292
+ return encerrar(r);
293
+ noBrain = r;
294
+ }
295
+ const ids = (evento) => ({ assertion_id: evento.aggregate_id, event_id: evento.id, source_event_id: evento.source_event_id });
296
+ const fato = (evento) => {
297
+ const id = evento.aggregate_id;
298
+ if (noBrain.retidos.has(id)) {
299
+ lacuna(id, 'brain.retido');
300
+ return { id, frescor: 'retido' };
301
+ }
302
+ const { instance, source_ref, source_hash, source_version, location } = evento.source, b = noBrain.ok.get(id);
303
+ const frescor = !b ? 'ausente-no-brain' : b.source?.source_hash === source_hash && b.source?.source_ref === source_ref ? 'confere' : 'divergente';
304
+ if (frescor === 'ausente-no-brain')
305
+ lacuna(id, 'brain.ausente');
306
+ if (frescor === 'divergente')
307
+ lacuna(id, 'fonte.divergente');
308
+ return { brain: ids(evento), citacao: { instance, source_ref, source_hash, source_version, location }, frescor };
309
+ };
310
+ const visivel = (l) => !!l?.evento && !noBrain.retidos.has(l.evento.aggregate_id);
311
+ const decisoes = [];
312
+ for (const l of escolhidas) {
313
+ // Sem id no Brain não há citação: a decisão vira lacuna, nunca conteúdo.
314
+ if (!l.evento) {
315
+ lacuna(idDe(l), 'citacao.incompleta');
316
+ continue;
317
+ }
318
+ const id = idDe(l), f = fato(l.evento), e = l.e, tipo = classe.get(l);
319
+ // D6: retido pelo Brain não mostra nem o conteúdo local (BR-024-03).
320
+ if (f.frescor === 'retido') {
321
+ decisoes.push(f);
322
+ continue;
323
+ }
324
+ const comum = { id, fase: texto(e.fase), em: e.ts, ...f };
325
+ if (tipo === 'decidido') {
326
+ const p = pedidoDe(l);
327
+ decisoes.push({ ...comum, classe: tipo, contrato: p.contrato, autoria: 'autonoma', decidido: p.decidido, porque: p.porque, comoMudar: p.comoMudar,
328
+ custoDeReverter: { agora: p.custoDeReverter.agora, depois: p.custoDeReverter.depois }, criterio: { tipo: p.criterio.tipo, referencia: p.criterio.referencia },
329
+ quemDecidiu: String(e.quemDecidiu ?? ''), evidencia: String(e.evidencia ?? ''), razao: String(e.razao ?? ''),
330
+ reverte: texto(e.reverte), revertidaPor: visivel(revertidaPor.get(p.id)) ? String(pedidoDe(revertidaPor.get(p.id)).id) : null });
331
+ // D-G3: o contrato do `decidido` não guarda alternativas; o `comoMudar` é o caminho de volta.
332
+ lacuna(id, 'alternativas.nao-registradas');
333
+ }
334
+ else if (tipo === 'pergunta') {
335
+ const p = pedidoDe(l);
336
+ const alternativas = p.contrato === 'ork.hitl/v2'
337
+ ? p.alternativas.map((a, i) => ({ numero: i + 1, letra: a.letra, texto: a.texto, acao: a.acao,
338
+ consequencia: a.consequencia, recomendada: a.recomendada === true, porque: a.porque ?? null }))
339
+ : p.opcoes.map(o => ({ numero: o.numero, letra: null, texto: o.texto, acao: o.acao, consequencia: null, recomendada: false, porque: null }));
340
+ const provada = provadas.get(id), gates = respostas.get(id) ?? [];
341
+ let resposta = null, estado = 'aguardando';
342
+ if (provada) {
343
+ const r = fato(provada.evento), g = provada.e, escolha = alternativas.find(a => a.numero === g.opcao);
344
+ // Resposta retida: nem a escolha nem a autoria saem da linha local.
345
+ estado = r.frescor === 'retido' ? 'retida' : 'decidida';
346
+ resposta = r.frescor === 'retido' ? r : { ...r, opcao: Number.isInteger(g.opcao) ? g.opcao : null, letra: escolha?.letra ?? null,
347
+ texto: escolha?.texto ?? null, veredito: texto(g.estado), quem: String(g.autorizadoPor ?? ''), origem: String(g.origem ?? ''),
348
+ canal: texto(g.canal), recebidoEm: texto(g.recebidoEm), recibo: texto(g.recibo), evidencia: texto(g.evidencia), evidenciaSha256: texto(g.evidenciaSha256) };
349
+ }
350
+ else if (gates.length) {
351
+ estado = 'sem-prova';
352
+ lacuna(id, 'resposta.sem-prova');
353
+ }
354
+ else
355
+ lacuna(id, 'resposta.pendente');
356
+ // O prazo vai como está no pedido; vencido ou não é leitura de quem consulta, e o digest não depende do relógio.
357
+ decisoes.push({ ...comum, classe: tipo, contrato: p.contrato, autoria: estado === 'decidida' ? 'dono' : null, pergunta: String(p.pergunta),
358
+ sobre: (0, hitl_contract_1.alvoDoPedido)(p)?.tipo === 'gate' ? String((0, hitl_contract_1.alvoDoPedido)(p).sobre) : '', alternativas,
359
+ recomendacao: p.contrato === 'ork.hitl/v2' ? null : texto(p.recomendacao), prazo: texto(p.prazo),
360
+ estado, resposta });
361
+ }
362
+ else {
363
+ const rastro = (0, decisao_autonoma_1.rastroDaDecisao)(e);
364
+ decisoes.push({ ...comum, classe: tipo, contrato: 'legado', autoria: 'autonoma', decidido: texto(e.decisao) ?? '',
365
+ quemDecidiu: rastro.quemDecidiu ?? null, evidencia: rastro.evidencia ?? null, razao: rastro.razao ?? null, rastroCompleto: rastro.completa });
366
+ lacuna(id, 'decisao.fora-do-contrato');
367
+ lacuna(id, 'alternativas.nao-registradas');
368
+ }
369
+ }
370
+ const unicas = [...new Map(lacunas.map(x => [`${x.id} ${x.codigo}`, x])).values()]
371
+ .sort((a, b) => comparar(a.id, b.id) || comparar(a.codigo, b.codigo));
372
+ // D7: o digest cobre o digest do pacote de contexto, não o pacote inteiro; o horário fica fora.
373
+ const corpo = { ...base, state: (decisoes.length ? 'ok' : 'empty'), vinculo, contexto: contexto?.digest ?? null,
374
+ decisoes, lacunas: unicas };
375
+ return { ...corpo, contexto, digest: (0, company_brain_contract_1.digest)(corpo), consultadoEm: new Date().toISOString() };
376
+ }
@@ -4,7 +4,8 @@ exports.BRAIN_WRITE_TOOLS = exports.BRAIN_READ_TOOLS = void 0;
4
4
  exports.registerBrainTools = registerBrainTools;
5
5
  const zod_1 = require("zod");
6
6
  const company_brain_cli_1 = require("./company-brain-cli");
7
- exports.BRAIN_READ_TOOLS = ['ork_brain_status', 'ork_brain_inventory', 'ork_brain_get', 'ork_brain_query', 'ork_brain_receipts'];
7
+ const company_brain_dossie_1 = require("./company-brain-dossie");
8
+ exports.BRAIN_READ_TOOLS = ['ork_brain_status', 'ork_brain_inventory', 'ork_brain_get', 'ork_brain_query', 'ork_brain_receipts', 'ork_brain_context', 'ork_brain_dossie'];
8
9
  exports.BRAIN_WRITE_TOOLS = ['ork_brain_sync', 'ork_brain_apply', 'ork_brain_rollback', 'ork_brain_bind'];
9
10
  /** Installation supplies exact grants; presence of a tool is never activation or human identity. */
10
11
  function registerBrainTools(register, load, checkThread, writeGrants = []) {
@@ -13,6 +14,8 @@ function registerBrainTools(register, load, checkThread, writeGrants = []) {
13
14
  const id = zod_1.z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/), base = zod_1.z.object({ threadId: id }).strict();
14
15
  const definitions = [['status', base], ['inventory', base], ['get', base.extend({ id })], ['receipts', base.extend({ id })],
15
16
  ['query', base.extend({ ids: zod_1.z.array(id).max(1000).optional(), kinds: zod_1.z.array(zod_1.z.enum(['prod', 'proj', 'init', 'assertion'])).optional(), limit: zod_1.z.number().int().min(1).max(1000).optional() })],
17
+ ['context', base.extend({ ids: zod_1.z.array(id).min(1).max(1000).optional() })],
18
+ ['dossie', base.extend({ decisao: zod_1.z.string().regex(company_brain_dossie_1.ID_DE_DECISAO).optional() })],
16
19
  ['sync', base], ['apply', base.extend({ plan: zod_1.z.string().min(1).max(512), expectedSha256: zod_1.z.string().regex(/^[a-f0-9]{64}$/) })],
17
20
  ['rollback', base.extend({ batch: id })], ['bind', base.extend({ project: id, initiatives: zod_1.z.array(id).min(1).max(1000) })]];
18
21
  for (const [operation, inputSchema] of definitions) {
package/dist/conducao.js CHANGED
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.ErroDeConducao = exports.ESPERA_PADRAO_MS = exports.PRAZO_DA_RESERVA_MS = exports.PRAZO_DE_SESSAO_PADRAO_MS = exports.MARGEM_DO_PRAZO_MS = exports.ENV_THREAD_DO_DESPACHO = exports.ENV_IDENTIDADE_DE_DESPACHO = exports.ENV_CANAL = exports.ERRO_CANAL_DESCONHECIDO = exports.CANAIS_DE_CONDUCAO = exports.conducaoDoLease = void 0;
36
+ exports.ErroDeConducao = exports.ESPERA_PADRAO_MS = exports.PRAZO_DA_RESERVA_MS = exports.PRAZO_DE_SESSAO_PADRAO_MS = exports.MARGEM_DO_PRAZO_MS = exports.ENV_SESSAO_CLAUDE = exports.ENV_THREAD_DO_DESPACHO = exports.ENV_IDENTIDADE_DE_DESPACHO = exports.ENV_CANAL = exports.ERRO_CANAL_DESCONHECIDO = exports.CANAIS_DE_CONDUCAO = exports.conducaoDoLease = void 0;
37
37
  exports.validarCanal = validarCanal;
38
38
  exports.canalDoProcesso = canalDoProcesso;
39
39
  exports.ambienteDaConducao = ambienteDaConducao;
@@ -86,6 +86,9 @@ const maquina_1 = require("./maquina");
86
86
  const thread_1 = require("./thread");
87
87
  const horario_1 = require("./horario");
88
88
  const util_1 = require("./util");
89
+ const runtime_ambiente_1 = require("./runtime-ambiente");
90
+ Object.defineProperty(exports, "ENV_IDENTIDADE_DE_DESPACHO", { enumerable: true, get: function () { return runtime_ambiente_1.ENV_IDENTIDADE_DE_DESPACHO; } });
91
+ Object.defineProperty(exports, "ENV_THREAD_DO_DESPACHO", { enumerable: true, get: function () { return runtime_ambiente_1.ENV_THREAD_DO_DESPACHO; } });
89
92
  // ---------------------------------------------------------------------------
90
93
  // Canais (D5, D6): registro fechado, declarado pela borda.
91
94
  // ---------------------------------------------------------------------------
@@ -146,21 +149,51 @@ function canalDoProcesso(explicito, ambiente = process.env) {
146
149
  return 'hermes';
147
150
  return 'cli';
148
151
  }
149
- // ---------------------------------------------------------------------------
150
- // Identidade de despacho (D4): a reentrada da sessao que ja conduz.
151
- // ---------------------------------------------------------------------------
152
- /** Identidade do despacho no ambiente da sessao filha, para as chamadas do CLI de dentro dela. */
153
- exports.ENV_IDENTIDADE_DE_DESPACHO = 'ORK_DISPATCH_ID';
154
- exports.ENV_THREAD_DO_DESPACHO = 'ORK_DISPATCH_THREAD';
155
152
  /** O que o despacho poe no ambiente da sessao filha: identidade, thread e canal de origem. */
156
153
  function ambienteDaConducao(identidade, threadId, canal) {
157
- return { [exports.ENV_IDENTIDADE_DE_DESPACHO]: identidade, [exports.ENV_THREAD_DO_DESPACHO]: threadId, [exports.ENV_CANAL]: canal };
154
+ return { [runtime_ambiente_1.ENV_IDENTIDADE_DE_DESPACHO]: identidade, [runtime_ambiente_1.ENV_THREAD_DO_DESPACHO]: threadId, [exports.ENV_CANAL]: canal };
158
155
  }
159
- /** A identidade de despacho que este processo herdou, quando ela e desta thread. */
160
- function identidadeDoAmbiente(threadId, ambiente = process.env) {
161
- const id = (ambiente[exports.ENV_IDENTIDADE_DE_DESPACHO] ?? '').trim();
162
- const thread = (ambiente[exports.ENV_THREAD_DO_DESPACHO] ?? '').trim();
163
- return /^[a-f0-9-]{36}$/.test(id) && thread === threadId ? id : null;
156
+ /** Variavel que o Claude Code poe em cada sessao, com o UUID dela; nao vem do daemon. */
157
+ exports.ENV_SESSAO_CLAUDE = 'CLAUDE_CODE_SESSION_ID';
158
+ /**
159
+ * A identidade de despacho deste processo, quando ela e desta thread.
160
+ *
161
+ * RM-037 (defeitosdeco D-4): dentro de uma sessao Claude Code, o ambiente pode trazer o par de OUTRO
162
+ * despacho (o daemon do `claude --bg` guardava o do primeiro despacho da conta e o passava as sessoes
163
+ * reserva). Com `raiz`, a sessao se reconhece pelo ledger: vale o ultimo `phase_dispatch` desta thread
164
+ * com o `sessionId` dela. O par do ambiente so vale se bater com esse despacho; par que aponta para um
165
+ * despacho claude-bg de outra sessao e recusado, para nunca reentrar a conducao de quem nao e. Fora de
166
+ * sessao Claude, ou com despacho codex (processo proprio por despacho), vale o ambiente, como antes.
167
+ */
168
+ function identidadeDoAmbiente(threadId, ambiente = process.env, raiz) {
169
+ const id = (ambiente[runtime_ambiente_1.ENV_IDENTIDADE_DE_DESPACHO] ?? '').trim();
170
+ const thread = (ambiente[runtime_ambiente_1.ENV_THREAD_DO_DESPACHO] ?? '').trim();
171
+ const doAmbiente = /^[a-f0-9-]{36}$/.test(id) && thread === threadId ? id : null;
172
+ const sessao = (ambiente[exports.ENV_SESSAO_CLAUDE] ?? '').trim();
173
+ if (!raiz || !/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(sessao))
174
+ return doAmbiente;
175
+ let eventos;
176
+ try {
177
+ eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, threadId));
178
+ }
179
+ catch {
180
+ return doAmbiente;
181
+ }
182
+ const despachoId = (e) => e.identidade?.dispatchId;
183
+ const despachos = eventos.filter(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.faseDespachada);
184
+ const doPar = doAmbiente ? despachos.filter(e => despachoId(e) === doAmbiente).at(-1) : undefined;
185
+ // GO-FIX (R2 do CHECK): o codex despachado de dentro de uma sessao Claude herda o
186
+ // CLAUDE_CODE_SESSION_ID dela, mas o par do ambiente e do proprio processo codex, por despacho.
187
+ if (doPar?.runtime === 'codex')
188
+ return doAmbiente;
189
+ const daSessao = despachos.filter(e => e.sessionId === sessao && typeof despachoId(e) === 'string').at(-1);
190
+ if (daSessao)
191
+ return String(despachoId(daSessao));
192
+ if (!doAmbiente)
193
+ return null;
194
+ if (doPar && (doPar.runtime ?? 'claude-bg') === 'claude-bg' && typeof doPar.sessionId === 'string' && doPar.sessionId !== sessao)
195
+ return null;
196
+ return doAmbiente;
164
197
  }
165
198
  // ---------------------------------------------------------------------------
166
199
  // Prazos (D3): derivados do teto real da operacao; metadado, nunca prova.
@@ -305,6 +338,18 @@ function vidaDoProcesso(raiz, threadId, lease) {
305
338
  fs.closeSync(fd);
306
339
  return 'morto';
307
340
  }
341
+ /** Fecha a tomada uma vez so (RM-037, CHECK 4): liberar e devolver podem ser chamados mais de uma vez. */
342
+ function encerrar(k, estado) {
343
+ if (estado.encerrada)
344
+ return;
345
+ estado.encerrada = true;
346
+ if (emCurso.get(k) === estado)
347
+ emCurso.delete(k);
348
+ try {
349
+ fs.closeSync(estado.fd);
350
+ }
351
+ catch { /* ja fechado */ }
352
+ }
308
353
  /** Conducoes que ESTE processo segura, pela chave canonica: a reentrada no mesmo processo. */
309
354
  const emCurso = new Map();
310
355
  function chave(raiz, threadId) {
@@ -399,7 +444,7 @@ function liberarComProva(raiz, threadId, lease, tipo, dados) {
399
444
  * estava livre, entao ele morreu). Dono sessao vale ate a prova de fim. Reserva vale no prazo, e para
400
445
  * o mesmo canal vira a conducao dele.
401
446
  */
402
- function avaliarComTrava(raiz, threadId, pedido, opcoes = { usarReserva: true }) {
447
+ function avaliarComTrava(raiz, threadId, pedido, opcoes = { usarReserva: true }, saida = {}) {
403
448
  const lease = (0, leases_1.lerLease)(raiz, nomeDaConducao(threadId));
404
449
  if (!lease)
405
450
  return null;
@@ -427,6 +472,7 @@ function avaliarComTrava(raiz, threadId, pedido, opcoes = { usarReserva: true })
427
472
  return null;
428
473
  }
429
474
  if (opcoes.usarReserva && atual.canal === pedido.canal) {
475
+ saida.substituido = lease;
430
476
  liberarComProva(raiz, threadId, lease, ledger_1.TIPOS_DE_EVENTO.conducaoLiberada, { motivo: 'reserva-usada', prova: `pedido do mesmo canal ${pedido.canal} de quem assumiu` });
431
477
  return null;
432
478
  }
@@ -444,6 +490,7 @@ function avaliarComTrava(raiz, threadId, pedido, opcoes = { usarReserva: true })
444
490
  // A sucessora (fluxo de superacao das sessoes HITL): a sessao `blocked` espera alguem e nao executa;
445
491
  // o despacho seguinte da MESMA fase a sucede, e a superacao a encerra pelo controle do runtime.
446
492
  if (despacho && mesmaFase && !mesmoPedido && estadoNoRuntime === 'blocked') {
493
+ saida.substituido = lease;
447
494
  liberarComProva(raiz, threadId, lease, ledger_1.TIPOS_DE_EVENTO.conducaoLiberada, {
448
495
  motivo: 'sessao-bloqueada-sucedida',
449
496
  prova: `runtime ${atual.dono.tipo === 'sessao' ? atual.dono.runtime : '?'}: estado blocked; o despacho seguinte da fase ${atual.fase} a sucede`,
@@ -464,7 +511,8 @@ function tomarConducao(raiz, threadId, pedido) {
464
511
  const propria = emCurso.get(k);
465
512
  if (propria && !propria.convertida) {
466
513
  propria.profundidade++;
467
- return reentrada(propria.identidade, () => { propria.profundidade--; });
514
+ // R5-S2 do CHECK 5: a reentrada renova o lease de quem a segura (a baseline dentro da tomada retida).
515
+ return reentrada(propria.identidade, () => { propria.profundidade--; }, (prazo, agoraMs = Date.now()) => renovar(raiz, threadId, propria, prazo, agoraMs));
468
516
  }
469
517
  const existente = (0, leases_1.lerLease)(raiz, nomeDaConducao(threadId));
470
518
  if (pedido.identidade && existente?.conducao?.identidade === pedido.identidade) {
@@ -488,7 +536,8 @@ function tomarConducao(raiz, threadId, pedido) {
488
536
  return { ok: false, idempotente: false, atual };
489
537
  }
490
538
  try {
491
- const ocupada = avaliarComTrava(raiz, threadId, pedido);
539
+ const saida = {};
540
+ const ocupada = avaliarComTrava(raiz, threadId, pedido, undefined, saida);
492
541
  if (ocupada) {
493
542
  fs.closeSync(fd);
494
543
  return ocupada;
@@ -504,7 +553,8 @@ function tomarConducao(raiz, threadId, pedido) {
504
553
  conducao: dadosDoPedido(pedido, identidade, donoProcesso()),
505
554
  };
506
555
  (0, leases_1.regravarLease)(raiz, lease);
507
- const estado = { identidade, profundidade: 0, lease, fd, convertida: false };
556
+ const estado = { identidade, profundidade: 0, lease, fd, convertida: false, ...(saida.substituido ? { substituido: saida.substituido } : {}),
557
+ ...(pedido.consultarSessao ? { consultarSessao: pedido.consultarSessao } : {}) };
508
558
  emCurso.set(k, estado);
509
559
  return {
510
560
  ok: true,
@@ -513,6 +563,8 @@ function tomarConducao(raiz, threadId, pedido) {
513
563
  renovar: (prazoDoProximoMs, agoraMs = Date.now()) => renovar(raiz, threadId, estado, prazoDoProximoMs, agoraMs),
514
564
  converterEmSessao: (dados) => converter(raiz, k, estado, dados),
515
565
  liberar: () => liberarPropria(raiz, k, estado),
566
+ devolver: () => devolverPropria(raiz, threadId, k, estado),
567
+ sucessaoAindaVale: () => sucessaoAindaVale(raiz, threadId, estado),
516
568
  };
517
569
  }
518
570
  catch (e) {
@@ -523,20 +575,42 @@ function tomarConducao(raiz, threadId, pedido) {
523
575
  throw e;
524
576
  }
525
577
  }
526
- function reentrada(identidade, sair) {
578
+ function reentrada(identidade, sair, renovarExterno = () => undefined) {
527
579
  let saiu = false;
528
580
  return {
529
581
  ok: true, reentrada: true, identidade,
530
- renovar: () => undefined,
582
+ renovar: renovarExterno,
531
583
  converterEmSessao: () => undefined,
532
584
  liberar: () => { if (!saiu) {
533
585
  saiu = true;
534
586
  sair();
535
587
  } },
588
+ devolver: () => { if (!saiu) {
589
+ saiu = true;
590
+ sair();
591
+ } },
592
+ sucessaoAindaVale: () => true,
536
593
  };
537
594
  }
595
+ /** Ver `ConducaoTomada.sucessaoAindaVale`. Sessao sucedida que voltou a trabalhar derruba a sucessao. */
596
+ function sucessaoAindaVale(raiz, threadId, estado) {
597
+ // S2 do CHECK 6: tomada ja encerrada nao converte mais em sessao; despachar com ela deixaria a sessao sem lease.
598
+ if (estado.encerrada)
599
+ return false;
600
+ const dono = estado.substituido?.conducao?.dono;
601
+ if (!dono || dono.tipo !== 'sessao' || estado.convertida)
602
+ return true;
603
+ const r = (estado.consultarSessao ?? consultaNativa)(dono, raiz, threadId);
604
+ // Sem resposta do runtime nao ha prova de que ela segue parada: a sucessao nao vale.
605
+ if (!r.ok)
606
+ return false;
607
+ // S1 do CHECK 6: a sessao que sumiu do runtime, sem perfil, acabou; a mesma prova de `provaDeFimDaSessao`.
608
+ if (r.estado === null)
609
+ return !dono.perfil;
610
+ return r.estado === 'blocked' || TERMINAIS.includes(r.estado);
611
+ }
538
612
  function renovar(raiz, threadId, estado, prazoDoProximoMs, agoraMs) {
539
- if (estado.convertida)
613
+ if (estado.convertida || estado.encerrada)
540
614
  return;
541
615
  const falta = Date.parse(estado.lease.expiraEm) - agoraMs;
542
616
  if (falta >= prazoDoProximoMs + exports.MARGEM_DO_PRAZO_MS)
@@ -552,7 +626,7 @@ function renovar(raiz, threadId, estado, prazoDoProximoMs, agoraMs) {
552
626
  });
553
627
  }
554
628
  function converter(raiz, k, estado, dados) {
555
- if (estado.convertida)
629
+ if (estado.convertida || estado.encerrada)
556
630
  return;
557
631
  const c = estado.lease.conducao;
558
632
  estado.lease = {
@@ -563,14 +637,30 @@ function converter(raiz, k, estado, dados) {
563
637
  };
564
638
  (0, leases_1.regravarLease)(raiz, estado.lease);
565
639
  estado.convertida = true;
566
- emCurso.delete(k);
567
- try {
568
- fs.closeSync(estado.fd);
640
+ encerrar(k, estado);
641
+ }
642
+ function devolverPropria(raiz, threadId, k, estado) {
643
+ const anterior = estado.substituido;
644
+ if (estado.convertida || estado.encerrada || estado.profundidade > 0 || !anterior) {
645
+ liberarPropria(raiz, k, estado);
646
+ return;
569
647
  }
570
- catch { /* ja fechado */ }
648
+ const atual = (0, leases_1.lerLease)(raiz, estado.lease.nome);
649
+ // So devolve por cima do proprio lease: outro dono que tenha chegado depois nunca e sobrescrito.
650
+ if (atual?.conducao?.identidade === estado.identidade && atual.conducao.dono.tipo === 'processo') {
651
+ (0, leases_1.regravarLease)(raiz, anterior);
652
+ const devolvida = (0, conducao_texto_1.conducaoDoLease)(anterior);
653
+ (0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, ledger_1.TIPOS_DE_EVENTO.conducaoDevolvida, {
654
+ lease: anterior.nome,
655
+ devolvida: devolvida ? { canal: devolvida.canal, sessao: devolvida.sessao, fase: devolvida.fase, desde: devolvida.desde,
656
+ operacao: devolvida.operacao, dono: devolvida.dono } : null,
657
+ razao: 'o pedido foi recusado antes de virar sessao: o lease que a tomada consumiu volta a quem o tinha',
658
+ });
659
+ }
660
+ encerrar(k, estado);
571
661
  }
572
662
  function liberarPropria(raiz, k, estado) {
573
- if (estado.convertida)
663
+ if (estado.convertida || estado.encerrada)
574
664
  return;
575
665
  if (estado.profundidade > 0) {
576
666
  estado.profundidade--;
@@ -583,11 +673,7 @@ function liberarPropria(raiz, k, estado) {
583
673
  }
584
674
  catch { /* ja saiu */ }
585
675
  }
586
- emCurso.delete(k);
587
- try {
588
- fs.closeSync(estado.fd);
589
- }
590
- catch { /* ja fechado */ }
676
+ encerrar(k, estado);
591
677
  }
592
678
  /**
593
679
  * Registra a conducao da sessao de um despacho que nao tomou o lease antes (chamada direta de