@orkastery/cli 0.5.0 → 0.5.3
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/adapters/claude-code/README.md +11 -2
- package/adapters/claude-code/agents/ork-master.md +2 -0
- package/adapters/claude-code/commands/master.md +8 -0
- package/adapters/claude-code/commands/onboarding.md +3 -2
- package/adapters/claude-code/commands/ork.md +9 -0
- package/adapters/codex/skills/ork/SKILL.md +18 -3
- package/adapters/hermes/README.md +1 -0
- package/adapters/hermes/bin/ork-network-status.sh +10 -0
- package/adapters/hermes/hermes.plugin.json +1 -0
- package/adapters/hermes/hitl-ingress/__init__.py +2 -2
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +6 -5
- package/adapters/openclaw/README.md +35 -4
- package/adapters/openclaw/dist/hitl-ingress.js +2 -2
- package/adapters/openclaw/dist/index.js +47 -12
- package/adapters/openclaw/openclaw.plugin.json +5 -1
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +2 -2
- package/adapters/openclaw/src/index.ts +50 -12
- package/assets/orkmind_bridge.py +117 -9
- package/dist/adapters/claude-bg.js +59 -11
- package/dist/adapters/codex.js +4 -3
- package/dist/agents-md.js +1 -0
- package/dist/auditrun.js +6 -2
- package/dist/board.js +16 -14
- package/dist/branch-de-estado.js +66 -10
- package/dist/busca-semantica.js +52 -12
- package/dist/canarios-rm057.js +149 -0
- package/dist/canarios.js +2 -0
- package/dist/ci.js +8 -1
- package/dist/claim-lint.js +3 -1
- package/dist/claims.js +39 -1
- package/dist/company-brain-context.js +158 -22
- package/dist/docs.js +110 -3
- package/dist/doctor-pulse-cron.js +194 -0
- package/dist/doctor-rede.js +149 -0
- package/dist/doctor.js +294 -37
- package/dist/entrega-pr.js +27 -8
- package/dist/esgotamentos.js +181 -0
- package/dist/experiencia.js +12 -2
- package/dist/fabrica-estado.js +35 -9
- package/dist/fabrica-publicar.js +6 -0
- package/dist/fechamento.js +20 -66
- package/dist/fix.js +3 -3
- package/dist/forja.js +31 -7
- package/dist/gates.js +5 -1
- package/dist/hitl-contract.js +59 -4
- package/dist/hitl-curto.js +11 -6
- package/dist/hitl-gates.js +5 -0
- package/dist/hitl-lote.js +5 -2
- package/dist/hitl-resumo.js +33 -1
- package/dist/hitl-tempo-parado.js +201 -0
- package/dist/hitl-texto-livre.js +5 -5
- package/dist/horario.js +27 -0
- package/dist/hosts.js +59 -13
- package/dist/impedimento.js +205 -0
- package/dist/index.js +433 -88
- package/dist/indice-vetorial.js +87 -10
- package/dist/indice.js +3 -1
- package/dist/init.js +28 -5
- package/dist/integracoes-locais.js +4 -0
- package/dist/intelligence-graph-cli.js +267 -30
- package/dist/intelligence-graph-contexto.js +215 -0
- package/dist/intelligence-graph-contract.js +71 -12
- package/dist/intelligence-graph-extract-md.js +41 -23
- package/dist/intelligence-graph-extract-ts.js +181 -11
- package/dist/intelligence-graph-extract.js +186 -9
- package/dist/intelligence-graph-index.js +205 -24
- package/dist/intelligence-graph-parsers.js +6 -4
- package/dist/intelligence-graph-repo.js +10 -0
- package/dist/leases.js +680 -87
- package/dist/ledger-stats.js +15 -1
- package/dist/ledger.js +12 -0
- package/dist/licoes.js +6 -4
- package/dist/liveness.js +2 -1
- package/dist/maestro-runtime.js +7 -1
- package/dist/manifest.js +44 -1
- package/dist/master-nota.js +14 -5
- package/dist/master.js +129 -12
- package/dist/mcp-artifacts.js +36 -3
- package/dist/mcp-git.js +80 -15
- package/dist/mcp-grafo-worker.js +93 -0
- package/dist/mcp-grafo.js +279 -0
- package/dist/mcp-install.js +2 -1
- package/dist/mcp-server.js +23 -1
- package/dist/mcp-ship.js +2 -0
- package/dist/mcp-verify.js +3 -1
- package/dist/memoria.js +48 -6
- package/dist/network-roadmap.js +195 -20
- package/dist/ocupacao.js +17 -5
- package/dist/onboarding.js +9 -4
- package/dist/orkmind.js +90 -5
- package/dist/orquestracao.js +14 -11
- package/dist/parado-no-condutor.js +1089 -0
- package/dist/phase.js +68 -15
- package/dist/policies.js +79 -3
- package/dist/portfolio.js +4 -1
- package/dist/projeto-alvo.js +5 -1
- package/dist/prompts.js +36 -1
- package/dist/prova-ativacao.js +414 -0
- package/dist/pulse-delivery.js +60 -7
- package/dist/pulse.js +99 -3
- package/dist/ratelimit.js +20 -4
- package/dist/recall.js +3 -1
- package/dist/rede-adesao.js +399 -0
- package/dist/rede-forja.js +289 -0
- package/dist/rede-projetos.js +239 -0
- package/dist/rede-status.js +336 -0
- package/dist/rede.js +980 -0
- package/dist/retry.js +69 -2
- package/dist/roadmap-status.js +162 -7
- package/dist/runtime-context.js +5 -1
- package/dist/runtime-profiles.js +22 -1
- package/dist/saida-segura.js +58 -0
- package/dist/session-watcher-claude.js +113 -23
- package/dist/sessoes-contas.js +187 -0
- package/dist/sessoes-inventario.js +6 -12
- package/dist/sessoes.js +87 -3
- package/dist/ship.js +110 -20
- package/dist/thread.js +212 -14
- package/dist/util.js +71 -4
- package/dist/verify.js +2 -2
- package/dist/worktree.js +34 -8
- package/eval/casos/onboarding.json +1 -1
- package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
- package/package.json +7 -3
- package/references/definition-of-done.md +1 -1
- package/skills/README.md +1 -1
- package/skills/core/onboarding/SKILL.md +3 -2
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/master-metrics/SKILL.md +8 -3
- package/skills/phases/ship-release/SKILL.md +6 -3
package/dist/pulse.js
CHANGED
|
@@ -13,6 +13,7 @@ const liveness_1 = require("./liveness");
|
|
|
13
13
|
const master_1 = require("./master");
|
|
14
14
|
const master_batch_1 = require("./master-batch");
|
|
15
15
|
const orquestracao_1 = require("./orquestracao");
|
|
16
|
+
const impedimento_1 = require("./impedimento");
|
|
16
17
|
const thread_1 = require("./thread");
|
|
17
18
|
const sessoes_adopt_1 = require("./sessoes-adopt");
|
|
18
19
|
const ledger_1 = require("./ledger");
|
|
@@ -26,6 +27,9 @@ const decisao_autonoma_1 = require("./decisao-autonoma");
|
|
|
26
27
|
const docs_1 = require("./docs");
|
|
27
28
|
const conducao_1 = require("./conducao");
|
|
28
29
|
const conducao_texto_1 = require("./conducao-texto");
|
|
30
|
+
const hitl_resumo_1 = require("./hitl-resumo");
|
|
31
|
+
const hitl_tempo_parado_1 = require("./hitl-tempo-parado");
|
|
32
|
+
const parado_no_condutor_1 = require("./parado-no-condutor");
|
|
29
33
|
exports.CONTRATO_PULSE = 'ork.pulse/v1';
|
|
30
34
|
/** Uma semana: o resumo cita a decisao uma vez, e ela continua lida ate sair da janela. */
|
|
31
35
|
exports.JANELA_DAS_DECISOES_MS = 7 * 24 * 60 * 60 * 1000;
|
|
@@ -47,6 +51,17 @@ function apresentacaoCurtaDoItem(pedido, desde, quando) {
|
|
|
47
51
|
function comporPulse(carregado, entrada) {
|
|
48
52
|
const { radar, monitor, batch, orfas } = entrada, quando = radar.consultadoEm;
|
|
49
53
|
const humanos = [], automaticas = [];
|
|
54
|
+
// RM-037 (fatia 4): o fim de turno sem pergunta e do condutor. Ele sai da fila do dono aqui e volta
|
|
55
|
+
// como linha propria (`paradoNoCondutor`); leitura que falha nao esconde nada do dono.
|
|
56
|
+
let entregas = entrada.entregas;
|
|
57
|
+
if (!entregas) {
|
|
58
|
+
try {
|
|
59
|
+
entregas = (0, parado_no_condutor_1.entregasDoProjeto)(carregado, { quando, sessoes: radar.sessoes });
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
entregas = undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
50
65
|
const registros = new Map();
|
|
51
66
|
const ehRegistro = (id) => {
|
|
52
67
|
if (!id)
|
|
@@ -79,6 +94,11 @@ function comporPulse(carregado, entrada) {
|
|
|
79
94
|
};
|
|
80
95
|
const TERMINAIS = ['failed', 'done', 'stopped', 'completed'];
|
|
81
96
|
for (const s of radar.sessoes.filter(s => s.precisaDeHumano)) {
|
|
97
|
+
// RM-037 (fatia 4): sessao `blocked` de quem o turno acabou sem pergunta e do condutor: a que o ledger provou
|
|
98
|
+
// (Stop sem atividade depois; a lista numerada da mensagem final nao e menu) e a sobra sem menu na tela.
|
|
99
|
+
if (entregas?.doCondutor.sessoes.has(s.sessionId) &&
|
|
100
|
+
((0, parado_no_condutor_1.sessaoSemPergunta)(s) || entregas.doCondutor.turnosEncerrados.has(s.sessionId)))
|
|
101
|
+
continue;
|
|
82
102
|
// Job morto ou estado terminal é história. Bloqueio vivo ou desconhecido continua visível.
|
|
83
103
|
if (ehRegistro(s.thread?.id ?? null) && s.jobVivo !== true &&
|
|
84
104
|
(s.jobVivo === false || TERMINAIS.includes(s.estadoBruto)))
|
|
@@ -123,6 +143,9 @@ function comporPulse(carregado, entrada) {
|
|
|
123
143
|
for (const p of l.paradas) {
|
|
124
144
|
if (VERIFICACAO.includes(p.motivo) && jaMesclada(l.thread))
|
|
125
145
|
continue;
|
|
146
|
+
// RM-037 (fatia 4): o `human.pending` que o observador grava no fim de turno sem pergunta nao e do dono.
|
|
147
|
+
if (p.motivo === 'human.pending' && p.fonte === 'ledger' && entregas?.doCondutor.gates.has(`${l.thread}|${p.fase}`))
|
|
148
|
+
continue;
|
|
126
149
|
if (ehRegistro(l.thread)) {
|
|
127
150
|
if (p.fonte === 'escalonador' || ['lease.busy', 'conducao.em-andamento', 'concurrency.limite', 'vaga.stale', 'runtime.silencio'].includes(p.motivo))
|
|
128
151
|
continue;
|
|
@@ -148,6 +171,19 @@ function comporPulse(carregado, entrada) {
|
|
|
148
171
|
thread: l.thread, fase: p.fase, sessionId: null, desdeEm: p.desdeEm, paradaHaMin: p.paradaHaMin, impacto: impacto(p.fase),
|
|
149
172
|
pergunta: p.detalhe, opcoes: [], recomendacao: p.correcao, comandoResposta: p.correcao, evidencia: [p.evidencia],
|
|
150
173
|
fontes: ['monitor'], contextoLogs: [] };
|
|
174
|
+
// RM-055: o impedimento do despacho sai no contrato curto, com o comando exato e o re-despacho depois.
|
|
175
|
+
if (p.impedimento) {
|
|
176
|
+
try {
|
|
177
|
+
const curto = (0, impedimento_1.pedidoCurtoDoImpedimento)({ thread: l.thread, fase: p.fase, motivo: p.motivo, detalhe: p.detalhe,
|
|
178
|
+
desdeEm: p.desdeEm, impedimento: p.impedimento }, quando);
|
|
179
|
+
item.apresentacaoCurta = { telegram: (0, hitl_curto_1.textoDoPedidoCurto)(curto, 'telegram'), terminal: (0, hitl_curto_1.textoDoPedidoCurto)(curto, 'terminal') };
|
|
180
|
+
item.opcoes = curto.alternativas.map(a => `${a.chave}. ${a.texto}`);
|
|
181
|
+
item.comandoResposta = `ork retry run ${l.thread}`;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
item.evidencia.push('Pedido curto do impedimento indisponível; a correção acima vale.');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
151
187
|
// I-45: vaga parada e sinal do escalonador (`ork board reap` devolve a vaga), nao pergunta ao dono.
|
|
152
188
|
const esperaAutomatica = ['lease.busy', 'conducao.em-andamento', 'concurrency.limite', 'runtime.rate-limited', 'vaga.stale'].includes(p.motivo);
|
|
153
189
|
(esperaAutomatica ? automaticas : humanos).push(item);
|
|
@@ -216,7 +252,8 @@ function comporPulse(carregado, entrada) {
|
|
|
216
252
|
}
|
|
217
253
|
}
|
|
218
254
|
return { contrato: exports.CONTRATO_PULSE, consultadoEm: quando, runtime: { ok: radar.runtimeConsultado, detalhe: radar.runtimeDetalhe },
|
|
219
|
-
precisaDeHumanoAgora: h, acoesAutomaticas: a, resumo: { humanos: h.length, automaticas: a.length, scores: batch.length, fasesOrfas: orfas.length }
|
|
255
|
+
precisaDeHumanoAgora: h, acoesAutomaticas: a, resumo: { humanos: h.length, automaticas: a.length, scores: batch.length, fasesOrfas: orfas.length },
|
|
256
|
+
...(entregas?.parados.length ? { paradoNoCondutor: entregas.parados } : {}) };
|
|
220
257
|
}
|
|
221
258
|
function montarPulse(carregado, opcoes = {}) {
|
|
222
259
|
if (opcoes.registrar)
|
|
@@ -243,7 +280,42 @@ function montarPulse(carregado, opcoes = {}) {
|
|
|
243
280
|
}
|
|
244
281
|
}
|
|
245
282
|
const monitor = (0, orquestracao_1.montarMonitor)(carregado, { agora: quando, estados });
|
|
246
|
-
|
|
283
|
+
// RM-037 (fatia 4, D2): a forja so e lida quando alguma thread tem branch publicada; a leitura boa vira o
|
|
284
|
+
// retrato que o status do roadmap le sem rede. Falha de leitura e diagnostico, nunca "sem PR".
|
|
285
|
+
let entregas;
|
|
286
|
+
try {
|
|
287
|
+
entregas = (0, parado_no_condutor_1.entregasDoProjeto)(carregado, { quando, sessoes: radar.sessoes, lerPrs: (candidatas) => {
|
|
288
|
+
const leitura = (0, parado_no_condutor_1.lerPrsDaForja)(carregado, { quando, executor: opcoes.executorDoGh, candidatas });
|
|
289
|
+
if (leitura.ok) {
|
|
290
|
+
try {
|
|
291
|
+
(0, parado_no_condutor_1.gravarRetratoDePrs)(carregado.raiz, leitura.retrato);
|
|
292
|
+
}
|
|
293
|
+
catch { /* o retrato e economia do status */ }
|
|
294
|
+
try {
|
|
295
|
+
(0, parado_no_condutor_1.esquecerForjaSemLeitura)(carregado.raiz);
|
|
296
|
+
}
|
|
297
|
+
catch { /* sem a marca, a proxima perda de leitura e dita de novo */ }
|
|
298
|
+
}
|
|
299
|
+
return leitura;
|
|
300
|
+
} });
|
|
301
|
+
const leitura = entregas.prs;
|
|
302
|
+
if (leitura && !leitura.ok && leitura.semLeitura) {
|
|
303
|
+
// RM-037 (fatia 5, A5): a forja sem leitura de PR e dita uma vez por remoto e host; as batidas seguintes nao repetem.
|
|
304
|
+
let novidade = true;
|
|
305
|
+
try {
|
|
306
|
+
novidade = (0, parado_no_condutor_1.avisarForjaSemLeitura)(carregado.raiz, leitura.semLeitura, leitura.erro, quando);
|
|
307
|
+
}
|
|
308
|
+
catch { /* sem marca, diz de novo */ }
|
|
309
|
+
if (novidade)
|
|
310
|
+
diagnosticos.push(`prs.sem-leitura: ${leitura.erro.slice(0, 160)}`);
|
|
311
|
+
}
|
|
312
|
+
else if (leitura && !leitura.ok)
|
|
313
|
+
diagnosticos.push(`prs.nao-lidos: ${leitura.erro.slice(0, 160)}`);
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
diagnosticos.push(`entregas.indisponiveis: ${e.message.slice(0, 120)}`);
|
|
317
|
+
}
|
|
318
|
+
const pulse = comporPulse(carregado, { radar, monitor, orfas, batch: (0, master_1.pendentesDeScore)(carregado.raiz), ...(entregas ? { entregas } : {}) });
|
|
247
319
|
const conducoes = monitor.linhas.filter((l) => l.conducao).map((l) => ({ thread: l.thread, linha: (0, conducao_texto_1.linhaDeConducao)(l.conducao, { agora: quando }) }));
|
|
248
320
|
if (conducoes.length > 0)
|
|
249
321
|
pulse.conducoes = conducoes;
|
|
@@ -256,6 +328,15 @@ function montarPulse(carregado, opcoes = {}) {
|
|
|
256
328
|
catch (e) {
|
|
257
329
|
diagnosticos.push(`decisoes.indisponiveis: ${e.message.slice(0, 120)}`);
|
|
258
330
|
}
|
|
331
|
+
// RM-057 (fatia 3): leitura pura do ledger; falhar aqui nunca derruba a fila de atencao.
|
|
332
|
+
try {
|
|
333
|
+
const hitl = (0, hitl_tempo_parado_1.lerHitlDeConducaoAgora)(carregado.raiz, quando);
|
|
334
|
+
if (hitl.abertas.length || hitl.seteDias.pedidos || hitl.seteDias.respondidos)
|
|
335
|
+
pulse.hitlDeConducao = hitl;
|
|
336
|
+
}
|
|
337
|
+
catch (e) {
|
|
338
|
+
diagnosticos.push(`hitl-conducao.indisponivel: ${e.message.slice(0, 120)}`);
|
|
339
|
+
}
|
|
259
340
|
if (diagnosticos.length)
|
|
260
341
|
pulse.runtime.detalhe = [pulse.runtime.detalhe, ...diagnosticos].filter(Boolean).join('; ');
|
|
261
342
|
return pulse;
|
|
@@ -264,8 +345,10 @@ function textoDoPulse(p) {
|
|
|
264
345
|
// I-35: o pulse guarda ISO; o texto ao dono sai no fuso dele, rotulado no cabeçalho.
|
|
265
346
|
const local = (texto) => (0, horario_1.localizarTexto)(texto, { agora: p.consultadoEm });
|
|
266
347
|
const linhas = [`Pulse (${p.contrato}) ${(0, horario_1.formatarDataHoraRotulada)(p.consultadoEm, { agora: p.consultadoEm })}`, `Precisa de humano agora: ${p.resumo.humanos}`, `${p.resumo.scores} entrega(s) sem nota, aceitas por padrão com registro`];
|
|
267
|
-
|
|
348
|
+
// RM-037 (fatia 4): PR nao lido tambem e dito, para a falta de linha do condutor nao parecer "nada parado".
|
|
349
|
+
if (p.runtime.ok && ['liveness.snapshot.invalid', 'prs.nao-lidos', 'prs.sem-leitura', 'entregas.indisponiveis'].some(d => p.runtime.detalhe.includes(d))) {
|
|
268
350
|
linhas.push(`[diagnostico] ${local(p.runtime.detalhe)}`);
|
|
351
|
+
}
|
|
269
352
|
if (!p.runtime.ok)
|
|
270
353
|
linhas.push(`[consulta incompleta] ${local(p.runtime.detalhe)}`);
|
|
271
354
|
for (const i of p.precisaDeHumanoAgora) {
|
|
@@ -276,6 +359,19 @@ function textoDoPulse(p) {
|
|
|
276
359
|
for (const c of p.conducoes)
|
|
277
360
|
linhas.push(`${c.thread}: ${c.linha}`);
|
|
278
361
|
}
|
|
362
|
+
// RM-037 (fatia 4): o que espera o condutor, uma linha por thread; nao e pergunta ao dono.
|
|
363
|
+
if (p.paradoNoCondutor?.length) {
|
|
364
|
+
linhas.push('', `Parado no condutor: ${p.paradoNoCondutor.length}`);
|
|
365
|
+
for (const x of p.paradoNoCondutor)
|
|
366
|
+
linhas.push((0, hitl_resumo_1.linhaDoParadoNoCondutor)(x, { agora: p.consultadoEm }));
|
|
367
|
+
}
|
|
368
|
+
// RM-057 (fatia 3): quanto tempo cada pergunta de conducao aberta ja parou a thread, e a mediana da semana.
|
|
369
|
+
if (p.hitlDeConducao) {
|
|
370
|
+
const h = p.hitlDeConducao;
|
|
371
|
+
linhas.push('', `HITL de condução: ${h.abertas.length} aberta(s); ${(0, hitl_tempo_parado_1.textoDaMediana)(h)}`);
|
|
372
|
+
for (const a of h.abertas)
|
|
373
|
+
linhas.push((0, hitl_tempo_parado_1.linhaDaPerguntaParada)(a, { agora: p.consultadoEm }));
|
|
374
|
+
}
|
|
279
375
|
linhas.push('', `Ações automáticas pendentes: ${p.resumo.automaticas}`);
|
|
280
376
|
for (const i of p.acoesAutomaticas)
|
|
281
377
|
linhas.push(`${i.thread}: ${i.motivo}; ${i.comandoResposta}`);
|
package/dist/ratelimit.js
CHANGED
|
@@ -61,6 +61,7 @@ exports.lerFilaDeRetomada = lerFilaDeRetomada;
|
|
|
61
61
|
exports.janelaPadraoMs = janelaPadraoMs;
|
|
62
62
|
exports.enfileirar = enfileirar;
|
|
63
63
|
exports.prazoDaConta = prazoDaConta;
|
|
64
|
+
exports.prazoDaCotaVista = prazoDaCotaVista;
|
|
64
65
|
exports.marcarContaDaFalha = marcarContaDaFalha;
|
|
65
66
|
exports.gravarPedido = gravarPedido;
|
|
66
67
|
exports.exigirPedido = exigirPedido;
|
|
@@ -151,15 +152,30 @@ function prazoDaConta(manifesto, falha, agoraMs = Date.now()) {
|
|
|
151
152
|
return falha.resetEm;
|
|
152
153
|
return new Date(agoraMs + janelaPadraoMs(manifesto)).toISOString();
|
|
153
154
|
}
|
|
154
|
-
/**
|
|
155
|
-
|
|
155
|
+
/**
|
|
156
|
+
* RM-037 (fatia 5): ate quando a conta sai do rodizio pela cota que o observador viu na transcricao, com a
|
|
157
|
+
* sessao viva. Vale a hora dita (ja ancorada no instante da mensagem) ou a janela padrao contada da mensagem,
|
|
158
|
+
* nunca do relogio de quem leu. Prazo que ja venceu volta `null`: a conta ja voltou, e marcar agora tiraria do
|
|
159
|
+
* rodizio uma conta boa (a regra A10 do despacho recusado nao vale aqui, porque ninguem acabou de recusar nada).
|
|
160
|
+
*/
|
|
161
|
+
function prazoDaCotaVista(manifesto, falha, mensagemMs, agoraMs = Date.now()) {
|
|
162
|
+
if (falha.motivo !== 'runtime.quota-exhausted' || !Number.isFinite(mensagemMs))
|
|
163
|
+
return null;
|
|
164
|
+
const prazo = falha.resetEm ?? new Date(mensagemMs + janelaPadraoMs(manifesto)).toISOString();
|
|
165
|
+
return Date.parse(prazo) > agoraMs ? prazo : null;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Marca no store o perfil que falhou pela conta. Sem perfil (ambiente do processo), nada a marcar. `prazo`,
|
|
169
|
+
* quando vem, e o ja calculado por quem viu a falha (`prazoDaCotaVista`); sem ele, vale `prazoDaConta`.
|
|
170
|
+
*/
|
|
171
|
+
function marcarContaDaFalha(carregado, perfil, falha, agoraMs = Date.now(), prazo) {
|
|
156
172
|
// RM-037 (defeitosdeco D-6): modelo inacessivel nao e falha da conta inteira; o perfil fica no rodizio.
|
|
157
173
|
if (!perfil || falha.motivo === 'runtime.model-unavailable')
|
|
158
174
|
return null;
|
|
159
175
|
const esgotado = falha.motivo === 'runtime.quota-exhausted';
|
|
160
176
|
return (0, runtime_profiles_1.marcarFalhaDePerfil)(carregado.raiz, perfil.id, { estado: esgotado ? 'esgotado' : 'sem-auth',
|
|
161
|
-
esgotadoAte: prazoDaConta(carregado.manifesto, falha, agoraMs), motivo: falha.motivo,
|
|
162
|
-
em: new Date(agoraMs).toISOString() });
|
|
177
|
+
esgotadoAte: esgotado && prazo ? prazo : prazoDaConta(carregado.manifesto, falha, agoraMs), motivo: falha.motivo,
|
|
178
|
+
detalhe: falha.trecho, em: new Date(agoraMs).toISOString() });
|
|
163
179
|
}
|
|
164
180
|
/** Regrava um pedido (append-only: a versao nova vence, o historico fica). */
|
|
165
181
|
function gravarPedido(raiz, pedido) {
|
package/dist/recall.js
CHANGED
|
@@ -257,7 +257,9 @@ function recallDaThread(carregado, memoria, threadId, opcoes = {}) {
|
|
|
257
257
|
falhas.push({ id: 'memoria-busca', titulo: 'Descoberta de handoffs da fase', location: '',
|
|
258
258
|
orkmind: null, retrieve_when: fase, resolvido: false, motivo: 'orkmind-indisponivel', via: null,
|
|
259
259
|
conteudo: '', metodo: '', sha256: '', intervalo: null,
|
|
260
|
-
|
|
260
|
+
// RM-038: a janela cheia do export tem codigo proprio; o resto segue como falha de transporte.
|
|
261
|
+
detalhe: `${memoria.leituraRestrita ? falhaDeConsulta(erro) : erro instanceof Error && erro.message === 'memory.query.window-saturated'
|
|
262
|
+
? erro.message : 'memory.transport.export'}: busca semantica falhou; resultado nao comprova colecao vazia` });
|
|
261
263
|
}
|
|
262
264
|
}
|
|
263
265
|
if (opcoes.id !== undefined && resolvidos.length === 0 && falhas.length === 0 && adiados.length === 0) {
|
|
@@ -0,0 +1,399 @@
|
|
|
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.ehIdDeMaquina = exports.pastaDaRede = exports.TETO_DE_TENTATIVA_MS = exports.REPOSITORIO_PADRAO = exports.CONTRATO_DA_ADESAO = void 0;
|
|
37
|
+
exports.lerConfigDaRede = lerConfigDaRede;
|
|
38
|
+
exports.gravarConfigDaRede = gravarConfigDaRede;
|
|
39
|
+
exports.adesaoDaRede = adesaoDaRede;
|
|
40
|
+
exports.lerIdDaMaquina = lerIdDaMaquina;
|
|
41
|
+
exports.idDaMaquina = idDaMaquina;
|
|
42
|
+
exports.idDerivado = idDerivado;
|
|
43
|
+
exports.publicacaoDesligada = publicacaoDesligada;
|
|
44
|
+
exports.tomarVezDePublicar = tomarVezDePublicar;
|
|
45
|
+
exports.publicarRedeEmSegundoPlano = publicarRedeEmSegundoPlano;
|
|
46
|
+
exports.registrarNaRede = registrarNaRede;
|
|
47
|
+
/**
|
|
48
|
+
* RM-053 (D2, D9): a adesao desta maquina a rede da pessoa, e o disparo em segundo plano.
|
|
49
|
+
*
|
|
50
|
+
* Modulo leve de proposito, como a `fabrica-publicar`: quem publica em segundo plano (thread nova,
|
|
51
|
+
* despacho de fase, entrega) chega aqui por ela e nao pode arrastar o retrato nem a forja.
|
|
52
|
+
*
|
|
53
|
+
* A adesao e da MAQUINA, em `~/.orkastery/rede.json` (contrato `ork.rede/v1`), gravado por
|
|
54
|
+
* `ork network entrar` e `ork network sair`. Sem esse arquivo, a maquina que ja fez
|
|
55
|
+
* `ork fabrica entrar` (`fabricaCompartilhada: true` em `maquina.json`) e membro HERDADO: entra na
|
|
56
|
+
* rede sem refazer nada. `ork network sair` grava `membro: false`, que vence a heranca. O manifesto
|
|
57
|
+
* do projeto nao conta: um time nao inscreve a maquina de ninguem na rede pessoal.
|
|
58
|
+
*/
|
|
59
|
+
const node_crypto_1 = require("node:crypto");
|
|
60
|
+
const node_child_process_1 = require("node:child_process");
|
|
61
|
+
const fs = __importStar(require("node:fs"));
|
|
62
|
+
const os = __importStar(require("node:os"));
|
|
63
|
+
const path = __importStar(require("node:path"));
|
|
64
|
+
const maquina_1 = require("./maquina");
|
|
65
|
+
const monitor_lock_1 = require("./monitor-lock");
|
|
66
|
+
const util_1 = require("./util");
|
|
67
|
+
exports.CONTRATO_DA_ADESAO = 'ork.rede/v1';
|
|
68
|
+
exports.REPOSITORIO_PADRAO = 'orkastery-network';
|
|
69
|
+
/**
|
|
70
|
+
* D9: no maximo uma tentativa de publicacao em segundo plano (evento ou batida) a cada 14 minutos.
|
|
71
|
+
* Um minuto de folga sob o cron de 15: com o teto igual ao periodo, a oscilacao do horario de cada
|
|
72
|
+
* batida pulava metade delas (B4 do CHECK 1).
|
|
73
|
+
*/
|
|
74
|
+
exports.TETO_DE_TENTATIVA_MS = 14 * 60 * 1000;
|
|
75
|
+
/** `~/.orkastery/rede`: o cache bare da casa, as marcas e o log da rede. */
|
|
76
|
+
const pastaDaRede = () => path.join((0, maquina_1.pastaDoUsuario)(), 'rede');
|
|
77
|
+
exports.pastaDaRede = pastaDaRede;
|
|
78
|
+
const arquivoDaConfig = () => path.join((0, maquina_1.pastaDoUsuario)(), 'rede.json');
|
|
79
|
+
const arquivoDaTentativa = () => path.join((0, exports.pastaDaRede)(), 'tentativa.json');
|
|
80
|
+
const IDENTIFICADOR = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
81
|
+
const HOST = /^[A-Za-z0-9.-]+(?::\d+)?$/;
|
|
82
|
+
const texto = (v, padrao) => typeof v === 'string' && padrao.test(v) ? v : null;
|
|
83
|
+
function lerConfigDaRede() {
|
|
84
|
+
try {
|
|
85
|
+
const bruto = JSON.parse(fs.readFileSync(arquivoDaConfig(), 'utf8'));
|
|
86
|
+
if (!bruto || bruto.contrato !== exports.CONTRATO_DA_ADESAO || typeof bruto.membro !== 'boolean')
|
|
87
|
+
return null;
|
|
88
|
+
return {
|
|
89
|
+
contrato: exports.CONTRATO_DA_ADESAO, membro: bruto.membro,
|
|
90
|
+
forja: bruto.forja === 'github' || bruto.forja === 'gitlab' ? bruto.forja : null,
|
|
91
|
+
host: texto(bruto.host, HOST), dono: texto(bruto.dono, IDENTIFICADOR), repositorio: texto(bruto.repositorio, IDENTIFICADOR),
|
|
92
|
+
atualizadoEm: typeof bruto.atualizadoEm === 'string' ? bruto.atualizadoEm : '',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Grava a adesao (troca atomica). Campo ausente na mudanca fica como estava. */
|
|
100
|
+
function gravarConfigDaRede(mudanca) {
|
|
101
|
+
const atual = lerConfigDaRede();
|
|
102
|
+
const config = {
|
|
103
|
+
contrato: exports.CONTRATO_DA_ADESAO,
|
|
104
|
+
membro: mudanca.membro ?? atual?.membro ?? false,
|
|
105
|
+
forja: mudanca.forja !== undefined ? mudanca.forja : atual?.forja ?? null,
|
|
106
|
+
host: mudanca.host !== undefined ? mudanca.host : atual?.host ?? null,
|
|
107
|
+
dono: mudanca.dono !== undefined ? mudanca.dono : atual?.dono ?? null,
|
|
108
|
+
repositorio: mudanca.repositorio !== undefined ? mudanca.repositorio : atual?.repositorio ?? null,
|
|
109
|
+
atualizadoEm: new Date().toISOString(),
|
|
110
|
+
};
|
|
111
|
+
if (config.host !== null && !HOST.test(config.host))
|
|
112
|
+
throw new Error(`rede.config: host invalido ("${config.host}")`);
|
|
113
|
+
for (const [campo, valor] of [['dono', config.dono], ['repositorio', config.repositorio]]) {
|
|
114
|
+
if (valor !== null && !IDENTIFICADOR.test(valor))
|
|
115
|
+
throw new Error(`rede.config: ${campo} invalido ("${valor}")`);
|
|
116
|
+
}
|
|
117
|
+
const dir = (0, maquina_1.pastaDoUsuario)();
|
|
118
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
119
|
+
const temp = path.join(dir, `.rede.json.${process.pid}.tmp`);
|
|
120
|
+
fs.writeFileSync(temp, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
|
|
121
|
+
fs.renameSync(temp, arquivoDaConfig());
|
|
122
|
+
return config;
|
|
123
|
+
}
|
|
124
|
+
/** Esta maquina esta na rede? Explicita pelo `rede.json`, senao herdada da fabrica. */
|
|
125
|
+
function adesaoDaRede() {
|
|
126
|
+
const config = lerConfigDaRede();
|
|
127
|
+
if (config)
|
|
128
|
+
return { membro: config.membro, adesao: config.membro ? 'rede' : null, config };
|
|
129
|
+
const herdada = (0, maquina_1.lerConfigDaMaquina)()?.fabricaCompartilhada === true;
|
|
130
|
+
return { membro: herdada, adesao: herdada ? 'fabrica' : null, config: null };
|
|
131
|
+
}
|
|
132
|
+
const ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
133
|
+
const arquivoDoId = () => path.join((0, maquina_1.pastaDoUsuario)(), 'maquina-id');
|
|
134
|
+
function lerId(arquivo) {
|
|
135
|
+
try {
|
|
136
|
+
const id = fs.readFileSync(arquivo, 'utf8').trim();
|
|
137
|
+
return ID.test(id) ? id : null;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** O id desta instalacao, sem criar: `null` quando ela nunca publicou (a leitura nao cria estado). */
|
|
144
|
+
function lerIdDaMaquina() {
|
|
145
|
+
return lerId(arquivoDoId());
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* B7 do CHECK 1: um identificador aleatorio desta instalacao, criado na primeira publicacao. Vai no
|
|
149
|
+
* retrato para duas maquinas com o mesmo nome (hostname `ubuntu`, o corte em 64 caracteres) nao
|
|
150
|
+
* regravarem o arquivo uma da outra. Nao identifica pessoa.
|
|
151
|
+
*
|
|
152
|
+
* S3 da revisao 2: mora em `~/.orkastery/maquina-id`, ao lado do `maquina.json`, e NAO na pasta de
|
|
153
|
+
* cache `~/.orkastery/rede/`: apagar o cache nao pode fazer a maquina virar outra de si mesma.
|
|
154
|
+
* U6 da revisao 3: sem arquivo, `link` de um temporario cria so se nao existe (atomico).
|
|
155
|
+
* V5 da revisao 4: arquivo ruim e trocado por um id DERIVADO dele, o mesmo para todo processo que o
|
|
156
|
+
* viu. W7 e W8 da revisao 5: nenhum caminho expoe arquivo vazio, e link pendurado tambem e trocado.
|
|
157
|
+
* X6 do CHECK 6: sem hard link, e na troca do arquivo ruim, o id vem de uma reserva (`idReservado`),
|
|
158
|
+
* igual para todo host que divide a pasta; o derivado so fica para quando a reserva falha.
|
|
159
|
+
* Todo caminho devolve o que ficou gravado, nunca o que tentou gravar.
|
|
160
|
+
*/
|
|
161
|
+
function idDaMaquina() {
|
|
162
|
+
const arquivo = arquivoDoId();
|
|
163
|
+
for (let tentativa = 0; tentativa < 3; tentativa++) {
|
|
164
|
+
const lido = lerId(arquivo);
|
|
165
|
+
if (lido)
|
|
166
|
+
return lido;
|
|
167
|
+
fs.mkdirSync((0, maquina_1.pastaDoUsuario)(), { recursive: true });
|
|
168
|
+
let st = null;
|
|
169
|
+
try {
|
|
170
|
+
st = fs.lstatSync(arquivo, { bigint: true });
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
if (e.code !== 'ENOENT')
|
|
174
|
+
throw e;
|
|
175
|
+
}
|
|
176
|
+
if (st)
|
|
177
|
+
trocarIdInvalido(arquivo, st);
|
|
178
|
+
else
|
|
179
|
+
criarId(arquivo);
|
|
180
|
+
}
|
|
181
|
+
const persistido = lerId(arquivo);
|
|
182
|
+
if (!persistido)
|
|
183
|
+
throw new Error(`rede.id: nao consegui gravar nem ler ${arquivo}`);
|
|
184
|
+
return persistido;
|
|
185
|
+
}
|
|
186
|
+
function gravarTemporario(conteudo) {
|
|
187
|
+
const temporario = path.join((0, maquina_1.pastaDoUsuario)(), `.maquina-id.${process.pid}.${(0, node_crypto_1.randomUUID)()}.tmp`);
|
|
188
|
+
fs.writeFileSync(temporario, conteudo, { mode: 0o600 });
|
|
189
|
+
return temporario;
|
|
190
|
+
}
|
|
191
|
+
/** Troca o que estiver no caminho (arquivo ou link) por um arquivo inteiro com o id: `rename` e atomico. */
|
|
192
|
+
function trocarPor(arquivo, id) {
|
|
193
|
+
const temporario = gravarTemporario(id);
|
|
194
|
+
try {
|
|
195
|
+
fs.renameSync(temporario, arquivo);
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
fs.rmSync(temporario, { force: true });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const SEM_HARD_LINK = new Set(['EPERM', 'ENOTSUP', 'EOPNOTSUPP', 'ENOSYS']);
|
|
202
|
+
/** Cria o id so se o arquivo nao existe; perdeu a corrida, fica o do outro. */
|
|
203
|
+
function criarId(arquivo) {
|
|
204
|
+
const temporario = gravarTemporario((0, node_crypto_1.randomUUID)());
|
|
205
|
+
try {
|
|
206
|
+
fs.linkSync(temporario, arquivo);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
const codigo = e.code ?? '';
|
|
211
|
+
if (codigo === 'EEXIST')
|
|
212
|
+
return;
|
|
213
|
+
if (!SEM_HARD_LINK.has(codigo))
|
|
214
|
+
throw e;
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
fs.rmSync(temporario, { force: true });
|
|
218
|
+
}
|
|
219
|
+
// W7 da revisao 5: sistema de arquivos sem hard link (vboxsf, alguns FUSE e SMB). Nada de arquivo
|
|
220
|
+
// vazio nem de espera por relogio. X6 do CHECK 6: o id sai da reserva, o mesmo para todo processo
|
|
221
|
+
// de todo host que divide a pasta; so se a reserva falhar, o id DERIVADO da pasta e do boot.
|
|
222
|
+
const reservado = idReservado('maquina-id.reserva');
|
|
223
|
+
if (reservado)
|
|
224
|
+
return trocarPor(arquivo, reservado);
|
|
225
|
+
const pasta = fs.statSync((0, maquina_1.pastaDoUsuario)(), { bigint: true });
|
|
226
|
+
trocarPor(arquivo, idDerivado(['novo', pasta.dev, pasta.ino]));
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* X6 do CHECK 6: cria-se-ausente sem hard link, sem relogio e sem arquivo vazio. A pasta temporaria
|
|
230
|
+
* ganha o id inteiro antes do `rename`; `rename` de pasta sobre pasta NAO vazia falha (ENOTEMPTY ou
|
|
231
|
+
* EEXIST no POSIX, EPERM no Windows), entao so a primeira entra, e todo processo, de qualquer host ou
|
|
232
|
+
* conteiner que divida `~/.orkastery`, le dela o mesmo id. A reserva fica: e ela que responde ao lento.
|
|
233
|
+
* Clones de uma imagem (W6) tem disco proprio e criam cada um a sua. Qualquer falha devolve `null`, e
|
|
234
|
+
* quem chamou usa o id derivado de antes.
|
|
235
|
+
*/
|
|
236
|
+
function idReservado(nome) {
|
|
237
|
+
const reserva = path.join((0, maquina_1.pastaDoUsuario)(), nome);
|
|
238
|
+
const jaReservado = lerId(path.join(reserva, 'id'));
|
|
239
|
+
if (jaReservado)
|
|
240
|
+
return jaReservado;
|
|
241
|
+
const temporaria = path.join((0, maquina_1.pastaDoUsuario)(), `.${nome}.${process.pid}.${(0, node_crypto_1.randomUUID)()}.tmp`);
|
|
242
|
+
try {
|
|
243
|
+
fs.mkdirSync(temporaria, { mode: 0o700 });
|
|
244
|
+
fs.writeFileSync(path.join(temporaria, 'id'), (0, node_crypto_1.randomUUID)(), { mode: 0o600 });
|
|
245
|
+
fs.renameSync(temporaria, reserva);
|
|
246
|
+
}
|
|
247
|
+
catch { /* outro entrou antes (ou a pasta recusa): vale o que estiver na reserva */ }
|
|
248
|
+
finally {
|
|
249
|
+
fs.rmSync(temporaria, { recursive: true, force: true });
|
|
250
|
+
}
|
|
251
|
+
return lerId(path.join(reserva, 'id'));
|
|
252
|
+
}
|
|
253
|
+
/** O arquivo ruim (vazio, corrompido, link pendurado) vira o id derivado dele; o que mudou no meio, quem chamou rele. */
|
|
254
|
+
function trocarIdInvalido(arquivo, st) {
|
|
255
|
+
let conteudo;
|
|
256
|
+
if (st.isSymbolicLink()) {
|
|
257
|
+
// W8 da revisao 5: o link pendurado (ou para arquivo ruim) sai; o alvo fica como esta. X2 da revisao
|
|
258
|
+
// 6: se outro processo ja o trocou (ou tirou) depois do `lstat`, quem chamou rele.
|
|
259
|
+
let alvo;
|
|
260
|
+
try {
|
|
261
|
+
alvo = fs.readlinkSync(arquivo);
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
if (['ENOENT', 'EINVAL'].includes(e.code ?? ''))
|
|
265
|
+
return;
|
|
266
|
+
throw e;
|
|
267
|
+
}
|
|
268
|
+
conteudo = Buffer.from(`link:${alvo}`);
|
|
269
|
+
}
|
|
270
|
+
else if (st.isFile()) {
|
|
271
|
+
let fd;
|
|
272
|
+
try {
|
|
273
|
+
fd = fs.openSync(arquivo, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
if (['ENOENT', 'ELOOP'].includes(e.code ?? ''))
|
|
277
|
+
return;
|
|
278
|
+
throw e;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
// Conteudo e metadados do MESMO inode: o `fstat` do descritor aberto, nao do caminho.
|
|
282
|
+
st = fs.fstatSync(fd, { bigint: true });
|
|
283
|
+
const buffer = Buffer.alloc(4096);
|
|
284
|
+
conteudo = buffer.subarray(0, fs.readSync(fd, buffer, 0, buffer.length, 0));
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
fs.closeSync(fd);
|
|
288
|
+
}
|
|
289
|
+
if (ID.test(conteudo.toString('utf8').trim()))
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
throw new Error(`rede.id: ${arquivo} nao e um arquivo; apague-o e rode de novo`);
|
|
294
|
+
}
|
|
295
|
+
// X6 do CHECK 6: a reserva da troca e chaveada pelo que todo host ve igual do arquivo ruim (conteudo,
|
|
296
|
+
// tamanho e hora); `dev` e local a cada host e fica so no id derivado, o caminho de quando a reserva falha.
|
|
297
|
+
const sha = (0, node_crypto_1.createHash)('sha256').update(conteudo).digest('hex');
|
|
298
|
+
const chave = (0, node_crypto_1.createHash)('sha256').update([sha, st.size, st.mtimeNs].join('\0')).digest('hex').slice(0, 32);
|
|
299
|
+
trocarPor(arquivo, idReservado(`maquina-id.troca-${chave}`) ?? idDerivado([sha, st.dev, st.ino, st.mtimeNs, st.size]));
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* O id que todo processo desta maquina deriva do mesmo estado, agora. W6 da revisao 5: alem das
|
|
303
|
+
* partes (o arquivo ruim, ou a pasta), o hostname e o boot (`boot_id` e `machine-id`): VMs clonadas
|
|
304
|
+
* da mesma imagem, com o mesmo arquivo, inode e hostname, tem boot proprio e ganham ids diferentes.
|
|
305
|
+
* Formato de UUID v4.
|
|
306
|
+
*/
|
|
307
|
+
function idDerivado(partes, fontes = fontesDaMaquina()) {
|
|
308
|
+
const h = (0, node_crypto_1.createHash)('sha256').update([...partes.map(String), ...fontes].join('\0')).digest('hex');
|
|
309
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-${'89ab'[parseInt(h[16], 16) & 3]}${h.slice(17, 20)}-${h.slice(20, 32)}`;
|
|
310
|
+
}
|
|
311
|
+
function fontesDaMaquina() {
|
|
312
|
+
const ler = (arquivo) => { try {
|
|
313
|
+
return fs.readFileSync(arquivo, 'utf8').trim();
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return '';
|
|
317
|
+
} };
|
|
318
|
+
return [os.hostname(), ler('/proc/sys/kernel/random/boot_id'), ler('/etc/machine-id')];
|
|
319
|
+
}
|
|
320
|
+
const ehIdDeMaquina = (v) => typeof v === 'string' && ID.test(v);
|
|
321
|
+
exports.ehIdDeMaquina = ehIdDeMaquina;
|
|
322
|
+
/** Publicacao automatica desligada pelo ambiente (testes, `ork eval`, demo). */
|
|
323
|
+
function publicacaoDesligada(env = process.env) {
|
|
324
|
+
return env.ORK_REDE_PUBLICAR === '0' || env.ORK_FABRICA_PUBLICAR === '0';
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* D9: toma a vez de tentar publicar, no maximo uma a cada 14 minutos por maquina. Devolve `false`
|
|
328
|
+
* quando a ultima tentativa (evento ou batida) foi ha menos que isso; a marca e gravada antes da
|
|
329
|
+
* tentativa, para duas batidas simultaneas nao dispararem juntas. Revisao de 03/10: a leitura e a
|
|
330
|
+
* gravacao da marca correm sob uma trava (quem nao a pega perde a vez) e a marca troca por rename;
|
|
331
|
+
* sem isso, varios processos na mesma batida liam a marca velha e todos tomavam a vez.
|
|
332
|
+
*/
|
|
333
|
+
function tomarVezDePublicar(agoraPedido) {
|
|
334
|
+
try {
|
|
335
|
+
fs.mkdirSync((0, exports.pastaDaRede)(), { recursive: true });
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
const trava = (0, monitor_lock_1.adquirirLockMonitor)(path.join((0, exports.pastaDaRede)(), 'tentativa.lock'));
|
|
341
|
+
if (!trava.ok)
|
|
342
|
+
return false;
|
|
343
|
+
// A hora e lida DENTRO da trava: lida antes, o processo que perdia a CPU entre a leitura e a trava
|
|
344
|
+
// via a marca do vencedor "no futuro", a tomava por relogio que voltou e tomava a vez tambem.
|
|
345
|
+
const agora = agoraPedido ?? Date.now();
|
|
346
|
+
try {
|
|
347
|
+
try {
|
|
348
|
+
const ultima = Date.parse(JSON.parse(fs.readFileSync(arquivoDaTentativa(), 'utf8')).em ?? '');
|
|
349
|
+
if (Number.isFinite(ultima) && agora - ultima < exports.TETO_DE_TENTATIVA_MS && agora >= ultima)
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
catch { /* primeira vez, ou marca ilegivel: tenta */ }
|
|
353
|
+
try {
|
|
354
|
+
// Dado de maquina: o JSON vai inteiro, sem concatenar o ISO em texto (lint de horario, RM-035).
|
|
355
|
+
const temporario = `${arquivoDaTentativa()}.${process.pid}.tmp`;
|
|
356
|
+
fs.writeFileSync(temporario, JSON.stringify({ em: new Date(agora).toISOString() }), { mode: 0o600 });
|
|
357
|
+
fs.renameSync(temporario, arquivoDaTentativa());
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
try {
|
|
366
|
+
trava.liberar();
|
|
367
|
+
}
|
|
368
|
+
catch { /* a trava de outro dono nao e desfeita aqui */ }
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Evento de thread (criada, fase despachada, entregue, fechada): dispara `ork network publicar
|
|
373
|
+
* --silencioso` sem segurar quem chamou. So para membro, so fora do teto, nunca com a publicacao
|
|
374
|
+
* desligada pelo ambiente.
|
|
375
|
+
*/
|
|
376
|
+
function publicarRedeEmSegundoPlano(opcoes = {}) {
|
|
377
|
+
const env = opcoes.env ?? process.env;
|
|
378
|
+
if (publicacaoDesligada(env) || !adesaoDaRede().membro)
|
|
379
|
+
return false;
|
|
380
|
+
const cli = opcoes.cli ?? path.join(__dirname, 'index.js');
|
|
381
|
+
if (!fs.existsSync(cli) || !tomarVezDePublicar(opcoes.agora))
|
|
382
|
+
return false;
|
|
383
|
+
try {
|
|
384
|
+
const filho = (0, node_child_process_1.spawn)(process.execPath, [cli, 'network', 'publicar', '--silencioso'], { cwd: opcoes.diretorio ?? process.cwd(), detached: true, stdio: 'ignore', env });
|
|
385
|
+
filho.unref();
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/** Uma linha JSON por publicacao em segundo plano, em `~/.orkastery/rede/rede.log`: quem publica sozinho deixa rastro. */
|
|
393
|
+
function registrarNaRede(registro) {
|
|
394
|
+
try {
|
|
395
|
+
fs.mkdirSync((0, exports.pastaDaRede)(), { recursive: true });
|
|
396
|
+
fs.appendFileSync(path.join((0, exports.pastaDaRede)(), 'rede.log'), JSON.stringify({ ts: (0, util_1.agora)(), ...registro }) + '\n', { mode: 0o600 });
|
|
397
|
+
}
|
|
398
|
+
catch { /* o log e informativo */ }
|
|
399
|
+
}
|