@orkastery/cli 0.5.1 → 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/agents/ork-master.md +2 -0
- package/adapters/claude-code/commands/master.md +8 -0
- 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 +24 -4
- package/adapters/openclaw/dist/hitl-ingress.js +2 -2
- package/adapters/openclaw/dist/index.js +46 -11
- 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 +49 -11
- package/assets/orkmind_bridge.py +117 -9
- package/dist/adapters/claude-bg.js +39 -5
- package/dist/adapters/codex.js +4 -3
- package/dist/agents-md.js +1 -0
- package/dist/auditrun.js +5 -1
- 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/claims.js +39 -1
- package/dist/company-brain-context.js +158 -22
- package/dist/docs.js +48 -9
- package/dist/doctor-pulse-cron.js +194 -0
- package/dist/doctor-rede.js +149 -0
- package/dist/doctor.js +89 -9
- package/dist/entrega-pr.js +20 -8
- package/dist/esgotamentos.js +181 -0
- package/dist/experiencia.js +12 -2
- package/dist/fabrica-estado.js +14 -6
- package/dist/fabrica-publicar.js +6 -0
- package/dist/fechamento.js +20 -66
- 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 +9 -0
- 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 +52 -10
- package/dist/impedimento.js +205 -0
- package/dist/index.js +391 -74
- package/dist/indice-vetorial.js +87 -10
- package/dist/indice.js +3 -1
- package/dist/init.js +17 -0
- package/dist/integracoes-locais.js +2 -0
- package/dist/intelligence-graph-cli.js +91 -8
- package/dist/intelligence-graph-contexto.js +215 -0
- package/dist/intelligence-graph-parsers.js +6 -4
- package/dist/leases.js +664 -87
- package/dist/ledger-stats.js +15 -1
- package/dist/ledger.js +3 -0
- package/dist/licoes.js +1 -0
- package/dist/maestro-runtime.js +4 -1
- package/dist/manifest.js +20 -1
- package/dist/master-nota.js +14 -5
- package/dist/master.js +129 -12
- package/dist/mcp-artifacts.js +34 -3
- package/dist/mcp-git.js +20 -3
- package/dist/mcp-grafo-worker.js +5 -3
- package/dist/mcp-grafo.js +50 -4
- package/dist/mcp-server.js +14 -1
- package/dist/memoria.js +48 -6
- package/dist/network-roadmap.js +191 -16
- package/dist/ocupacao.js +11 -3
- package/dist/orkmind.js +90 -5
- package/dist/orquestracao.js +12 -10
- package/dist/parado-no-condutor.js +188 -32
- package/dist/phase.js +68 -15
- package/dist/policies.js +52 -1
- package/dist/portfolio.js +4 -1
- package/dist/projeto-alvo.js +5 -1
- package/dist/prompts.js +36 -1
- package/dist/prova-ativacao.js +75 -6
- package/dist/pulse-delivery.js +34 -2
- package/dist/pulse.js +49 -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 +23 -3
- 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 -18
- package/dist/sessoes.js +83 -2
- package/dist/ship.js +85 -19
- package/dist/thread.js +192 -13
- package/dist/worktree.js +5 -2
- 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/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/master-metrics/SKILL.md +8 -3
|
@@ -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
|
+
}
|