@orkastery/cli 0.6.4 → 0.6.6
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/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/dist/adapters/codex-controller-sensor.js +21 -2
- package/dist/adapters/codex-events.js +15 -6
- package/dist/adapters/grok-events.js +43 -2
- package/dist/adapters/grok.js +28 -5
- package/dist/agendador-cpu.js +82 -28
- package/dist/check-agil.js +85 -9
- package/dist/check-paralelo.js +77 -15
- package/dist/check-selecao.js +149 -0
- package/dist/codex-task-complete.js +95 -0
- package/dist/junit.js +546 -0
- package/dist/locale-verify.js +37 -0
- package/dist/manifest.js +237 -13
- package/dist/retry.js +35 -0
- package/dist/session-watcher-claude.js +38 -22
- package/dist/session-watcher-grok.js +10 -0
- package/dist/session-watcher.js +141 -41
- package/dist/ship.js +9 -3
- package/dist/verify.js +215 -52
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "orkastery",
|
|
3
3
|
"name": "Orkastery",
|
|
4
4
|
"description": "Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI. Toda tool de projeto aceita projeto (nome do projeto pedido); o cwd do gateway nunca escolhe o projeto. O status do roadmap vem de ork_network_roadmap; as maquinas da pessoa, de ork_network_status (sem projeto).",
|
|
5
|
-
"version": "0.6.
|
|
5
|
+
"version": "0.6.6",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkastery/openclaw-plugin",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"description": "Plugin Orkastery para OpenClaw: 32 tools ork_* que traduzem intencao em chamada de CLI do nucleo `ork`. Zero regra de negocio no host.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": true,
|
|
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.CONTRATO_FIXACAO = exports.LIMITE_METADADO_BYTES = void 0;
|
|
37
|
+
exports.lerFonteControllerFixada = lerFonteControllerFixada;
|
|
38
|
+
exports.consumirRolloutControllerFixado = consumirRolloutControllerFixado;
|
|
37
39
|
exports.registrarFonteController = registrarFonteController;
|
|
38
40
|
exports.lerSnapshotController = lerSnapshotController;
|
|
39
41
|
/** D1/D2: leitor validado da fonte do controller. Sem RPC, resposta, sinal ou resume. */
|
|
@@ -222,7 +224,7 @@ function conferirSessionMeta(fd, tamanho, sessionId, cwd) {
|
|
|
222
224
|
* Não há atomicidade contra troca entre lstat e open: a garantia é a reconferência do
|
|
223
225
|
* descritor aberto e do vínculo com a fonte fixada, com recusa quando a prova não se sustenta.
|
|
224
226
|
*/
|
|
225
|
-
function
|
|
227
|
+
function comRolloutConferido(rollout, uid, sessionId, cwd, fixado, consumir) {
|
|
226
228
|
if (typeof rollout !== 'string' || !rollout || !path.isAbsolute(rollout))
|
|
227
229
|
recusar('rollout do controller não confirmado');
|
|
228
230
|
const file = rollout;
|
|
@@ -252,12 +254,15 @@ function conferirFonteDeRollout(rollout, uid, sessionId, cwd, fixado) {
|
|
|
252
254
|
if (fixado && (st.ino !== fixado.ino || st.dev !== fixado.dev))
|
|
253
255
|
recusar('rollout do controller rotacionado sem vínculo provado com a fonte fixada');
|
|
254
256
|
conferirSessionMeta(fd, st.size, sessionId, cwd);
|
|
255
|
-
return { file, ino: st.ino, dev: st.dev };
|
|
257
|
+
return { fonte: { file, ino: st.ino, dev: st.dev }, valor: consumir(fd, st.size) };
|
|
256
258
|
}
|
|
257
259
|
finally {
|
|
258
260
|
fs.closeSync(fd);
|
|
259
261
|
}
|
|
260
262
|
}
|
|
263
|
+
function conferirFonteDeRollout(rollout, uid, sessionId, cwd, fixado) {
|
|
264
|
+
return comRolloutConferido(rollout, uid, sessionId, cwd, fixado, () => undefined).fonte;
|
|
265
|
+
}
|
|
261
266
|
/**
|
|
262
267
|
* Fixacao privada da fonte: escrita uma unica vez, antes de qualquer efeito de observacao,
|
|
263
268
|
* e reconferida em cada poll e em cada restart do observador. Sem redescoberta de processo.
|
|
@@ -303,6 +308,20 @@ function lerFonteFixada(file, esperado, uid) {
|
|
|
303
308
|
cwd: esperado.cwd, sessionId: esperado.sessionId, rollout: f.rollout, rolloutIno: f.rolloutIno,
|
|
304
309
|
rolloutDev: f.rolloutDev, criadoEm: f.criadoEm, processoController: controlador, processoRuntime: runtime, uid };
|
|
305
310
|
}
|
|
311
|
+
/** Leitura pura da fixação do watcher: não descobre fonte, não grava pin e reconfere o rollout. */
|
|
312
|
+
function lerFonteControllerFixada(file, esperado) {
|
|
313
|
+
const uid = esperado.uid ?? uidAtual();
|
|
314
|
+
const fonte = lerFonteFixada(file, esperado, uid);
|
|
315
|
+
if (!fonte)
|
|
316
|
+
return null;
|
|
317
|
+
conferirDiretorioDaFonte(fonte.dir, esperado.dirSessoes, uid);
|
|
318
|
+
conferirFonteDeRollout(fonte.rollout, uid, fonte.sessionId, fonte.cwd, { file: fonte.rollout, ino: fonte.rolloutIno, dev: fonte.rolloutDev });
|
|
319
|
+
return fonte;
|
|
320
|
+
}
|
|
321
|
+
/** Consome bytes do descritor autenticado do rollout já fixado, sem reabrir pathname não conferido. */
|
|
322
|
+
function consumirRolloutControllerFixado(fonte, consumir) {
|
|
323
|
+
return comRolloutConferido(fonte.rollout, fonte.uid, fonte.sessionId, fonte.cwd, { file: fonte.rollout, ino: fonte.rolloutIno, dev: fonte.rolloutDev }, consumir).valor;
|
|
324
|
+
}
|
|
306
325
|
/**
|
|
307
326
|
* Espera humana so suspende o prazo com o tuple nativo completo: thread, turno, `requestId`
|
|
308
327
|
* inclusive o numero `0`, item e hash iguais aos da pergunta projetada.
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ParserCodex = exports.LIMITE_RESPOSTA_CODEX = exports.LIMITE_LINHA_CODEX = void 0;
|
|
4
4
|
exports.falhaDeContaDoErroCodex = falhaDeContaDoErroCodex;
|
|
5
5
|
exports.classificarMensagem = classificarMensagem;
|
|
6
|
+
exports.mensagemFinalPedeHumano = mensagemFinalPedeHumano;
|
|
6
7
|
/** D4/D5: stream e rollout são observações de turno, nunca aprovação de CHECK. */
|
|
7
8
|
const gates_1 = require("../gates");
|
|
8
9
|
const claude_bg_1 = require("./claude-bg");
|
|
@@ -59,13 +60,21 @@ function classificarMensagem(texto) {
|
|
|
59
60
|
const estado = /(?:^|\n)\s*(?:motivo|gate_blocked|bloquead[oa](?:\s+por)?|blocked(?:\s+by)?)\s*[:=]?\s*`?([a-z]+[._][a-z_.-]+)`?/i.exec(final);
|
|
60
61
|
if (estado)
|
|
61
62
|
return motivo(estado[1]) ?? falha();
|
|
62
|
-
|
|
63
|
+
return mensagemFinalPedeHumano(final) ? humano() : sucesso();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* RM-075: a mensagem final pede uma resposta do humano? A regra é compartilhada pelos
|
|
67
|
+
* rollouts Codex e pela tela final do claude-bg, para `human.pending` não nascer apenas
|
|
68
|
+
* do estado nativo `blocked`.
|
|
69
|
+
*/
|
|
70
|
+
function mensagemFinalPedeHumano(texto) {
|
|
71
|
+
if (typeof texto !== 'string')
|
|
72
|
+
return false;
|
|
73
|
+
const pergunta = texto.replace(/`[^`]*`/g, '').trim();
|
|
63
74
|
if (/(?:preciso|aguardo|aguardando|waiting for|need)\s+(?:da?\s+)?(?:sua|your|confirmação|resposta|autorização|approval|confirmation)/i.test(pergunta))
|
|
64
|
-
return
|
|
65
|
-
|
|
66
|
-
/(?:\b(?:você|voce|you|confirma|confirme|deseja|prefere|autoriza|posso|can you|could you|should I)\b|(?:qual|which|what)\s+(?:é\s+o\s+|is\s+the\s+)?(?:diretório|diretorio|caminho|arquivo|opção|opcao|directory|path|file|option))/i.test(pergunta)
|
|
67
|
-
return humano();
|
|
68
|
-
return sucesso();
|
|
75
|
+
return true;
|
|
76
|
+
return /\?\s*$/.test(pergunta) &&
|
|
77
|
+
/(?:\b(?:você|voce|you|confirma|confirme|deseja|prefere|autoriza|posso|can you|could you|should I)\b|(?:qual|which|what)\s+(?:é\s+o\s+|is\s+the\s+)?(?:diretório|diretorio|caminho|arquivo|opção|opcao|directory|path|file|option))/i.test(pergunta);
|
|
69
78
|
}
|
|
70
79
|
function tokens(v, fonte, escopo) {
|
|
71
80
|
if (!v || typeof v !== 'object')
|
|
@@ -64,6 +64,19 @@ exports.LIMITE_LINHA_GROK = 1024 * 1024;
|
|
|
64
64
|
exports.LIMITE_ERRO_GROK = 300;
|
|
65
65
|
const STOP = /^[a-z][a-z0-9_]{0,39}$/;
|
|
66
66
|
const ID = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/;
|
|
67
|
+
/** A saída estrutural do Bash que o runtime usa quando `dontAsk` nega antes de executar. */
|
|
68
|
+
function negacaoDaPolitica(e) {
|
|
69
|
+
const saida = e.rawOutput;
|
|
70
|
+
if (saida && typeof saida === 'object' && !Array.isArray(saida)) {
|
|
71
|
+
const o = saida;
|
|
72
|
+
if (o.type === 'PermissionDenied' || o.code === 'permission_denied' || o.outcome === 'blocked-by-permissions')
|
|
73
|
+
return true;
|
|
74
|
+
if (typeof o.exit_code === 'number')
|
|
75
|
+
return false;
|
|
76
|
+
return typeof o.output_for_prompt === 'string' && /Denied by permission policy|tool permission denied for/i.test(o.output_for_prompt);
|
|
77
|
+
}
|
|
78
|
+
return typeof saida === 'string' && /Denied by permission policy|tool permission denied for/i.test(saida);
|
|
79
|
+
}
|
|
67
80
|
/**
|
|
68
81
|
* D7: um texto do runtime pronto para o ledger, sem corte: os segredos que o núcleo já conhece, a chave da xAI
|
|
69
82
|
* (`xai-...`) e sem controle. GO-FIX 1 (A4): quem corta, corta depois de redigir, para nenhum segredo perder o prefixo.
|
|
@@ -106,7 +119,13 @@ class ParserGrok {
|
|
|
106
119
|
if (estado && estado.sessaoEsperada !== sessaoEsperada)
|
|
107
120
|
throw new Error('estado do parser do grok de outra sessão');
|
|
108
121
|
this.estado = estado ? structuredClone(estado)
|
|
109
|
-
: { sessaoEsperada, fim: null, erro: null, invalido: false, sessaoDivergente: false, eventos: 0
|
|
122
|
+
: { sessaoEsperada, fim: null, erro: null, invalido: false, sessaoDivergente: false, eventos: 0,
|
|
123
|
+
comandosTerminal: {}, negacaoTerminal: null };
|
|
124
|
+
// Cursor gravado antes da F5a continua legível.
|
|
125
|
+
if (!this.estado.comandosTerminal || typeof this.estado.comandosTerminal !== 'object')
|
|
126
|
+
this.estado.comandosTerminal = {};
|
|
127
|
+
if (this.estado.negacaoTerminal === undefined)
|
|
128
|
+
this.estado.negacaoTerminal = null;
|
|
110
129
|
}
|
|
111
130
|
/** Lê as linhas completas do bloco; o resto fica pendente para o próximo. */
|
|
112
131
|
push(chunk) {
|
|
@@ -158,12 +177,34 @@ class ParserGrok {
|
|
|
158
177
|
}
|
|
159
178
|
this.eventosValidos++;
|
|
160
179
|
this.estado.eventos++;
|
|
180
|
+
if (e.type === 'tool_call') {
|
|
181
|
+
this.estado.negacaoTerminal = null;
|
|
182
|
+
const entrada = e.rawInput;
|
|
183
|
+
if (typeof e.toolCallId === 'string' && ID.test(e.toolCallId) && (e.toolName === 'bash' || e.toolName === 'run_terminal_cmd') &&
|
|
184
|
+
entrada && typeof entrada === 'object' && !Array.isArray(entrada) && typeof entrada.command === 'string') {
|
|
185
|
+
const comando = redigirErroGrok(entrada.command);
|
|
186
|
+
if (comando)
|
|
187
|
+
this.estado.comandosTerminal[e.toolCallId] = comando;
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (e.type === 'tool_call_update') {
|
|
192
|
+
const id = typeof e.toolCallId === 'string' && ID.test(e.toolCallId) ? e.toolCallId : null;
|
|
193
|
+
const comando = id ? this.estado.comandosTerminal[id] : undefined;
|
|
194
|
+
this.estado.negacaoTerminal = comando && negacaoDaPolitica(e) ? { comando } : null;
|
|
195
|
+
if (id && e.status === 'completed')
|
|
196
|
+
delete this.estado.comandosTerminal[id];
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
161
199
|
if (e.type === 'error') {
|
|
200
|
+
this.estado.negacaoTerminal = null;
|
|
162
201
|
this.estado.erro = { mensagem: redigirErroGrok(e.message) || 'erro do grok sem mensagem' };
|
|
163
202
|
return;
|
|
164
203
|
}
|
|
165
|
-
if (e.type !== 'end')
|
|
204
|
+
if (e.type !== 'end') {
|
|
205
|
+
this.estado.negacaoTerminal = null;
|
|
166
206
|
return;
|
|
207
|
+
}
|
|
167
208
|
const sessionId = typeof e.sessionId === 'string' && ID.test(e.sessionId) ? e.sessionId : null;
|
|
168
209
|
if (sessionId !== this.estado.sessaoEsperada)
|
|
169
210
|
this.estado.sessaoDivergente = true;
|
package/dist/adapters/grok.js
CHANGED
|
@@ -33,7 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.IGNORADOS_DA_CONFIG_DO_FILHO = exports.MARCA_DA_CONFIG_DO_FILHO = exports.ESPERA_PADRAO_MS = exports.ESFORCO_DO_GROK = exports.PERFIL_DE_SANDBOX_PADRAO = exports.PERFIL_DE_SANDBOX_DO_GROK = exports.AMBIENTE_SEM_MCP_DE_OUTROS_CLIENTES = exports.ENVS_REMOVIDAS_DO_DESPACHO = exports.FONTE_VERIFICACAO = exports.NOME_ADAPTER = void 0;
|
|
36
|
+
exports.IGNORADOS_DA_CONFIG_DO_FILHO = exports.MARCA_DA_CONFIG_DO_FILHO = exports.ESPERA_PADRAO_MS = exports.ESFORCO_DO_GROK = exports.COMANDOS_TERMINAL_DO_GROK = exports.PERFIL_DE_SANDBOX_PADRAO = exports.PERFIL_DE_SANDBOX_DO_GROK = exports.AMBIENTE_SEM_MCP_DE_OUTROS_CLIENTES = exports.ENVS_REMOVIDAS_DO_DESPACHO = exports.FONTE_VERIFICACAO = exports.NOME_ADAPTER = void 0;
|
|
37
|
+
exports.regrasDosComandosDoGrok = regrasDosComandosDoGrok;
|
|
37
38
|
exports.esforcoDoGrok = esforcoDoGrok;
|
|
38
39
|
exports.ferramentasDoGrok = ferramentasDoGrok;
|
|
39
40
|
exports.ferramentasServidasAoGrok = ferramentasServidasAoGrok;
|
|
@@ -112,6 +113,24 @@ exports.PERFIL_DE_SANDBOX_DO_GROK = Object.freeze({
|
|
|
112
113
|
'danger-full-access': 'off',
|
|
113
114
|
});
|
|
114
115
|
exports.PERFIL_DE_SANDBOX_PADRAO = 'workspace';
|
|
116
|
+
/**
|
|
117
|
+
* RM-067 (F5a, D3): a lista fechada dos comandos de terminal do ciclo da fase. O motivo mora com o comando para uma
|
|
118
|
+
* inclusão futura nunca virar só mais um curinga sem justificativa. As regras do MCP continuam em `ferramentasDoGrok`.
|
|
119
|
+
*/
|
|
120
|
+
exports.COMANDOS_TERMINAL_DO_GROK = Object.freeze([
|
|
121
|
+
{ comando: 'git', motivo: 'inspecionar e registrar o estado da worktree pelos fluxos autorizados da fase' },
|
|
122
|
+
{ comando: 'node', motivo: 'executar o CLI compilado e os scripts de prova do projeto' },
|
|
123
|
+
{ comando: 'npm', motivo: 'instalar, compilar e testar o pacote pelo manifesto' },
|
|
124
|
+
{ comando: 'ork', motivo: 'consultar e operar o ciclo da fase quando o transporte nativo estiver disponível' },
|
|
125
|
+
{ comando: 'grep', motivo: 'localizar texto e produzir evidência reproduzível' },
|
|
126
|
+
{ comando: 'find', motivo: 'descobrir arquivos dentro do sandbox da worktree' },
|
|
127
|
+
{ comando: 'ls', motivo: 'inspecionar diretórios dentro do sandbox da worktree' },
|
|
128
|
+
{ comando: 'cat', motivo: 'ler arquivos de texto dentro do sandbox da worktree' },
|
|
129
|
+
]);
|
|
130
|
+
/** A forma oficial do guia 22: a regra exata cobre o comando nu, e a regra com espaço cobre seus argumentos. */
|
|
131
|
+
function regrasDosComandosDoGrok() {
|
|
132
|
+
return exports.COMANDOS_TERMINAL_DO_GROK.flatMap(({ comando }) => [`Bash(${comando})`, `Bash(${comando} *)`]);
|
|
133
|
+
}
|
|
115
134
|
/**
|
|
116
135
|
* RM-067 (F4, E6): o esforço do Orkastery nos níveis canônicos do `grok` (`none`, `minimal`, `low`, `medium`, `high`,
|
|
117
136
|
* `xhigh`, `max`, no guia headless). O `eco` vira `minimal`, para a escada de esforço (`eco` antes de `low`) seguir com
|
|
@@ -245,7 +264,10 @@ function comandoDoDespacho(pedido, ferramentas) {
|
|
|
245
264
|
args.push('-m', pedido.model);
|
|
246
265
|
if (pedido.effort?.trim())
|
|
247
266
|
args.push('--effort', esforcoDoGrok(pedido.effort));
|
|
248
|
-
|
|
267
|
+
// RM-067 (F5a, D3): no headless, o que não casar com uma regra é negado e devolvido ao modelo, sem abrir diálogo.
|
|
268
|
+
args.push('--permission-mode', 'dontAsk', '--sandbox', perfilDeSandbox(pedido.sandbox, pedido.colaboracao), '--no-auto-update');
|
|
269
|
+
for (const regra of regrasDosComandosDoGrok())
|
|
270
|
+
args.push('--allow', regra);
|
|
249
271
|
// RM-067 (F4b, G2): com o MCP do ork, uma regra por ferramenta liberada, no fim, para o despacho sem contexto ficar igual
|
|
250
272
|
// e a retomada (que guarda o que vem depois do `-s`) levar as mesmas regras.
|
|
251
273
|
for (const ferramenta of ferramentas)
|
|
@@ -253,8 +275,9 @@ function comandoDoDespacho(pedido, ferramentas) {
|
|
|
253
275
|
return ['grok', ...args];
|
|
254
276
|
}
|
|
255
277
|
/**
|
|
256
|
-
* D2: a linha de comando exata do despacho (usada também pelo `--dry-run`). O prompt fica no argumento 2, que o
|
|
257
|
-
* mascara.
|
|
278
|
+
* D2 e F5a: a linha de comando exata do despacho (usada também pelo `--dry-run`). O prompt fica no argumento 2, que o
|
|
279
|
+
* ledger mascara. Todo despacho headless usa `--permission-mode dontAsk` com as regras fechadas de D3; nunca `--yolo`,
|
|
280
|
+
* `--always-approve` nem `--permission-mode bypassPermissions`.
|
|
258
281
|
*
|
|
259
282
|
* RM-067 (F4, E8): `--trust` confia a pasta do `--cwd`, a worktree da thread, que é o checkout do próprio projeto. O guia
|
|
260
283
|
* de regras de projeto do `grok` diz que o `AGENTS.md` só carrega na partida com a pasta confiada (`--trust` ou a
|
|
@@ -263,7 +286,7 @@ function comandoDoDespacho(pedido, ferramentas) {
|
|
|
263
286
|
* `grok` lê; não aprova ferramenta nenhuma.
|
|
264
287
|
*
|
|
265
288
|
* RM-067 (F4b, G2): num projeto preparado, o comando leva no fim um `--allow MCPTool(orkastery__<ferramenta>)` por
|
|
266
|
-
* ferramenta do MCP do `ork` que o caso libera (`ferramentasDoGrok`),
|
|
289
|
+
* ferramenta do MCP do `ork` que o caso libera (`ferramentasDoGrok`), além das regras fechadas de terminal da F5a.
|
|
267
290
|
*/
|
|
268
291
|
function montarComando(pedido) {
|
|
269
292
|
return comandoDoDespacho(pedido, ferramentasDoDespacho(pedido, contextoDoDespacho(pedido)));
|
package/dist/agendador-cpu.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.JANELA_DA_SONDA_MS = exports.LIMIAR_STEAL_DO_AGENDADOR_PCT = exports.RESERVA_DO_GRUPO = exports.ENV_CONTAINERS_DA_LANE = exports.ENV_CPUS_DOCKER = exports.ENV_SINAL_DA_RESERVA = exports.ENV_CPUS_DA_LANE_APOS_RESERVA = exports.ENV_CPUS_DA_LANE_APOS_VARREDURA = exports.ENV_CPUS_DA_LANE = void 0;
|
|
3
|
+
exports.AMOSTRAS_DA_SONDA = exports.JANELA_DA_SONDA_MS = exports.LIMIAR_STEAL_DO_AGENDADOR_PCT = exports.MINIMO_ESTRUTURAL_DO_GRUPO = exports.RESERVA_DO_GRUPO = exports.ENV_CONTAINERS_DA_LANE = exports.ENV_CPUS_DOCKER = exports.ENV_SINAL_DA_RESERVA = exports.ENV_CPUS_DA_LANE_APOS_RESERVA = exports.ENV_CPUS_DA_LANE_APOS_VARREDURA = exports.ENV_WORKERS = exports.ENV_CPUS_DA_LANE = void 0;
|
|
4
4
|
exports.registrarContainerDaLane = registrarContainerDaLane;
|
|
5
|
+
exports.workersDaReserva = workersDaReserva;
|
|
5
6
|
exports.cargaDoProcLoadavg = cargaDoProcLoadavg;
|
|
7
|
+
exports.mediana = mediana;
|
|
8
|
+
exports.resumirMedicaoCpuDoGrupo = resumirMedicaoCpuDoGrupo;
|
|
6
9
|
exports.medirCpuDoGrupo = medirCpuDoGrupo;
|
|
7
10
|
exports.planejarCpuDoGrupo = planejarCpuDoGrupo;
|
|
8
11
|
exports.paredeClassicaSimulada = paredeClassicaSimulada;
|
|
@@ -11,12 +14,16 @@ exports.paredeAgileSimulada = paredeAgileSimulada;
|
|
|
11
14
|
* RM-074: o plano de CPU do grupo do Agile Check.
|
|
12
15
|
*
|
|
13
16
|
* A lane hermetica deixa duas CPUs para a varredura e para a integracao. Cada conteiner recebe uma cota dessa fatia,
|
|
14
|
-
* em vez de enxergar a maquina inteira. Antes de lancar os comandos, o auxiliar do verify mede `/proc/loadavg` e
|
|
15
|
-
*
|
|
17
|
+
* em vez de enxergar a maquina inteira. Antes de lancar os comandos, o auxiliar do verify mede `/proc/loadavg` e
|
|
18
|
+
* varias janelas curtas de `/proc/stat`. A mediana ignora um pico isolado. Steal acima do limiar reduz a capacidade do
|
|
19
|
+
* plano em degraus; a ordem do Classic, com a paralelizacao do Classic, so volta quando nao cabem as tres CPUs
|
|
20
|
+
* estruturais do grupo paralelo ou quando a carga ocupa quase a maquina inteira.
|
|
16
21
|
*/
|
|
17
22
|
const node_fs_1 = require("node:fs");
|
|
18
23
|
const steal_1 = require("./steal");
|
|
19
24
|
exports.ENV_CPUS_DA_LANE = 'ORK_CHECK_CPUS_DA_LANE';
|
|
25
|
+
/** RM-081 F4: contrato publico com a quantidade inteira de workers disponivel ao runner. */
|
|
26
|
+
exports.ENV_WORKERS = 'ORK_WORKERS';
|
|
20
27
|
exports.ENV_CPUS_DA_LANE_APOS_VARREDURA = 'ORK_CHECK_CPUS_DA_LANE_APOS_VARREDURA';
|
|
21
28
|
exports.ENV_CPUS_DA_LANE_APOS_RESERVA = 'ORK_CHECK_CPUS_DA_LANE_APOS_RESERVA';
|
|
22
29
|
exports.ENV_SINAL_DA_RESERVA = 'ORK_CHECK_RESERVA_DEVOLVIDA';
|
|
@@ -24,8 +31,11 @@ exports.ENV_CPUS_DOCKER = 'ORK_CHECK_CPUS_DOCKER';
|
|
|
24
31
|
/** Registro privado da rodada: quem sobe um container na lane anota o nome para a sonda de CPU. */
|
|
25
32
|
exports.ENV_CONTAINERS_DA_LANE = 'ORK_CHECK_CONTAINERS_DA_LANE';
|
|
26
33
|
exports.RESERVA_DO_GRUPO = 2;
|
|
34
|
+
exports.MINIMO_ESTRUTURAL_DO_GRUPO = exports.RESERVA_DO_GRUPO + 1;
|
|
35
|
+
/** Abaixo dele o steal nao desconta capacidade (como na M7); acima, a M8 desconta a fracao medida, sem corte tudo ou nada. */
|
|
27
36
|
exports.LIMIAR_STEAL_DO_AGENDADOR_PCT = 10;
|
|
28
37
|
exports.JANELA_DA_SONDA_MS = 250;
|
|
38
|
+
exports.AMOSTRAS_DA_SONDA = 5;
|
|
29
39
|
/** Registra o container antes do `docker run`; falha de evidencia nunca impede a ferramenta de rodar. */
|
|
30
40
|
function registrarContainerDaLane(nome, ambiente = process.env) {
|
|
31
41
|
const arquivo = ambiente[exports.ENV_CONTAINERS_DA_LANE];
|
|
@@ -36,7 +46,11 @@ function registrarContainerDaLane(nome, ambiente = process.env) {
|
|
|
36
46
|
}
|
|
37
47
|
catch { /* a medicao fica incompleta e o agendador conserva a reserva */ }
|
|
38
48
|
}
|
|
39
|
-
|
|
49
|
+
/** Reserva publicada para runners: entrada invalida, fracionaria ou zero nunca produz menos de um worker. */
|
|
50
|
+
function workersDaReserva(cpus) {
|
|
51
|
+
return Math.max(1, Math.floor(Number.isFinite(cpus) ? cpus : 1));
|
|
52
|
+
}
|
|
53
|
+
const cpusValidas = workersDaReserva;
|
|
40
54
|
/** A carga de um minuto de `/proc/loadavg`; entrada invalida ou ausencia nao inventam saturacao. */
|
|
41
55
|
function cargaDoProcLoadavg(conteudo) {
|
|
42
56
|
if (!conteudo)
|
|
@@ -52,49 +66,89 @@ function lerProc(arquivo) {
|
|
|
52
66
|
return null;
|
|
53
67
|
}
|
|
54
68
|
}
|
|
69
|
+
/** Mediana das amostras validas; quantidade par usa o ponto medio. */
|
|
70
|
+
function mediana(amostras) {
|
|
71
|
+
const validas = amostras.filter((x) => Number.isFinite(x) && x >= 0).sort((a, b) => a - b);
|
|
72
|
+
if (validas.length === 0)
|
|
73
|
+
return null;
|
|
74
|
+
const meio = Math.floor(validas.length / 2);
|
|
75
|
+
return validas.length % 2 ? validas[meio] : (validas[meio - 1] + validas[meio]) / 2;
|
|
76
|
+
}
|
|
77
|
+
/** Fecha as amostras da sonda nas duas medianas que alimentam o plano. */
|
|
78
|
+
function resumirMedicaoCpuDoGrupo(amostrasCarga1, amostrasStealPct) {
|
|
79
|
+
const cargas = amostrasCarga1.filter((x) => Number.isFinite(x) && x >= 0);
|
|
80
|
+
const steals = amostrasStealPct.filter((x) => Number.isFinite(x) && x >= 0);
|
|
81
|
+
return { carga1: mediana(cargas), stealPct: mediana(steals), amostrasCarga1: cargas, amostrasStealPct: steals };
|
|
82
|
+
}
|
|
55
83
|
/**
|
|
56
|
-
* Mede a maquina dentro do proprio grupo, antes de qualquer comando
|
|
57
|
-
*
|
|
84
|
+
* Mede a maquina dentro do proprio grupo, antes de qualquer comando. A janela total continua curta, mas e dividida em
|
|
85
|
+
* amostras de carga e steal; a mediana deixa um unico pico registrado sem entregar a decisao a ele.
|
|
58
86
|
*/
|
|
59
87
|
async function medirCpuDoGrupo(janelaMs = exports.JANELA_DA_SONDA_MS) {
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
const cargas = [];
|
|
89
|
+
const steals = [];
|
|
90
|
+
let statAnterior = (0, steal_1.amostraDoProcStat)(lerProc('/proc/stat'));
|
|
91
|
+
const intervaloMs = janelaMs / exports.AMOSTRAS_DA_SONDA;
|
|
92
|
+
for (let i = 0; i < exports.AMOSTRAS_DA_SONDA; i++) {
|
|
93
|
+
await new Promise((resolve) => setTimeout(resolve, intervaloMs));
|
|
94
|
+
const carga = cargaDoProcLoadavg(lerProc('/proc/loadavg'));
|
|
95
|
+
if (carga !== null)
|
|
96
|
+
cargas.push(carga);
|
|
97
|
+
const statAtual = (0, steal_1.amostraDoProcStat)(lerProc('/proc/stat'));
|
|
98
|
+
const steal = (0, steal_1.stealDaJanela)(statAnterior, statAtual);
|
|
99
|
+
if (steal !== null)
|
|
100
|
+
steals.push(steal);
|
|
101
|
+
statAnterior = statAtual;
|
|
102
|
+
}
|
|
103
|
+
return resumirMedicaoCpuDoGrupo(cargas, steals);
|
|
69
104
|
}
|
|
70
105
|
/** O plano unico que alimenta concorrencia das lanes, varredura e `docker run --cpus`. */
|
|
71
106
|
function planejarCpuDoGrupo(cpusInformadas, medicao) {
|
|
72
107
|
const cpus = cpusValidas(cpusInformadas);
|
|
108
|
+
const steal = Math.min(100, Math.max(0, medicao.stealPct ?? 0));
|
|
109
|
+
// So o steal desconta capacidade. A carga de um minuto do /proc/loadavg inclui o build do proprio verify e a rodada
|
|
110
|
+
// anterior; descontada, tiraria CPUs de maquina quieta. A carga segue a regra anterior: ordem do Classic perto da
|
|
111
|
+
// maquina inteira ocupada.
|
|
112
|
+
const descontoDeSteal = steal >= exports.LIMIAR_STEAL_DO_AGENDADOR_PCT ? steal / 100 : 0;
|
|
113
|
+
const capacidadeEfetiva = Math.max(1, Math.min(cpus, Math.round(cpus * (1 - descontoDeSteal))));
|
|
73
114
|
const classic = Math.max(1, cpus - 1);
|
|
74
115
|
const motivos = [];
|
|
75
|
-
if (cpus <
|
|
116
|
+
if (cpus < exports.MINIMO_ESTRUTURAL_DO_GRUPO)
|
|
76
117
|
motivos.push(`${cpus} CPU(s), sem duas CPUs livres para o grupo paralelo`);
|
|
118
|
+
else if (capacidadeEfetiva < exports.MINIMO_ESTRUTURAL_DO_GRUPO) {
|
|
119
|
+
motivos.push(`capacidade efetiva ${capacidadeEfetiva}/${cpus} CPU(s), abaixo do minimo estrutural ${exports.MINIMO_ESTRUTURAL_DO_GRUPO}`);
|
|
120
|
+
}
|
|
77
121
|
if (medicao.carga1 !== null && medicao.carga1 >= classic)
|
|
78
122
|
motivos.push(`carga ${medicao.carga1} em /proc/loadavg (limiar ${classic})`);
|
|
79
|
-
if (medicao.stealPct !== null && medicao.stealPct >= exports.LIMIAR_STEAL_DO_AGENDADOR_PCT) {
|
|
80
|
-
motivos.push(`steal ${medicao.stealPct}% em /proc/stat (limiar ${exports.LIMIAR_STEAL_DO_AGENDADOR_PCT}%)`);
|
|
81
|
-
}
|
|
82
123
|
const saturado = motivos.length > 0;
|
|
83
|
-
const
|
|
124
|
+
const estado = saturado ? 'fallback' : capacidadeEfetiva < cpus ? 'degradado' : 'agile';
|
|
125
|
+
const fatia = saturado ? classic : Math.min(exports.RESERVA_DO_GRUPO, capacidadeEfetiva - 1);
|
|
84
126
|
const ferramentas = Math.max(1, Math.floor(fatia));
|
|
85
127
|
const medida = medicao.cpusIntegracao;
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
128
|
+
const medidaCompativel = medicao.fonteCpusIntegracao === 'medida' && Number.isFinite(medida) && medida >= 0;
|
|
129
|
+
const cpusIntegracao = saturado ? ferramentas : medidaCompativel
|
|
130
|
+
? Math.max(1, Math.min(capacidadeEfetiva - 1, Math.ceil(medida)))
|
|
131
|
+
: ferramentas;
|
|
132
|
+
const integracaoEmParalelo = !saturado && medidaCompativel && capacidadeEfetiva - fatia - cpusIntegracao >= 1;
|
|
133
|
+
const amostras = (xs, resumo) => `[${(xs?.length ? xs : resumo === null ? [] : [resumo]).join(', ')}]`;
|
|
134
|
+
const evidencia = `carga em /proc/loadavg: amostras ${amostras(medicao.amostrasCarga1, medicao.carga1)}, mediana ` +
|
|
135
|
+
`${medicao.carga1 ?? 'indisponivel'}; steal em /proc/stat: amostras ${amostras(medicao.amostrasStealPct, medicao.stealPct)}, ` +
|
|
136
|
+
`mediana ${medicao.stealPct === null ? 'indisponivel' : `${medicao.stealPct}%`}`;
|
|
137
|
+
const motivo = saturado
|
|
138
|
+
? `${motivos.join('; ')}; ${evidencia}`
|
|
139
|
+
: `capacidade ${capacidadeEfetiva}/${cpus} CPU(s)${estado === 'degradado' ? ' depois do desconto do steal' : ' inteira'}; ${evidencia}; ` +
|
|
140
|
+
`${fatia} CPU(s) reservadas a varredura; ` +
|
|
141
|
+
(integracaoEmParalelo ? `integracao medida cabe em paralelo com ${cpusIntegracao} CPU(s)` : 'integracao permanece encadeada');
|
|
89
142
|
return {
|
|
90
143
|
cpus,
|
|
91
144
|
ordem: saturado ? 'classic' : 'paralela',
|
|
145
|
+
estado,
|
|
146
|
+
capacidadeEfetiva,
|
|
92
147
|
saturado,
|
|
93
|
-
motivo
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
cpusHermeticaAposReserva: saturado ? classic : cpus,
|
|
148
|
+
motivo,
|
|
149
|
+
cpusHermetica: saturado ? classic : Math.max(1, capacidadeEfetiva - fatia - (integracaoEmParalelo ? cpusIntegracao : 0)),
|
|
150
|
+
cpusHermeticaAposVarredura: saturado ? classic : Math.max(1, capacidadeEfetiva - cpusIntegracao),
|
|
151
|
+
cpusHermeticaAposReserva: saturado ? classic : capacidadeEfetiva,
|
|
98
152
|
cpusIntegracao,
|
|
99
153
|
fonteCpusIntegracao: saturado ? 'classic' : medidaCompativel
|
|
100
154
|
? 'medida' : 'conservadora',
|
package/dist/check-agil.js
CHANGED
|
@@ -388,6 +388,15 @@ function caiuDeVerdade(r) {
|
|
|
388
388
|
return !r.ok && r.executado !== false && (r.causa === undefined || r.causa === 'exit') && Number.isInteger(r.code)
|
|
389
389
|
&& r.code >= 1 && r.code <= MAIOR_CODIGO_DA_LANE && !(r.relogioSobSteal?.length);
|
|
390
390
|
}
|
|
391
|
+
/** O agregado so afirma contagens quando todas as lanes trouxeram JUnit valido. */
|
|
392
|
+
function junitDasLanes(lanes) {
|
|
393
|
+
if (!lanes.length || lanes.some((l) => l.resultado.junit?.estado !== 'lido'))
|
|
394
|
+
return undefined;
|
|
395
|
+
const relatorios = lanes.map((l) => l.resultado.junit);
|
|
396
|
+
const somar = (campo) => relatorios.reduce((total, r) => total + (r[campo] ?? 0), 0);
|
|
397
|
+
return { caminho: relatorios.map((r) => r.caminho).join(';'), estado: 'lido', testes: somar('testes'),
|
|
398
|
+
falhas: somar('falhas'), erros: somar('erros'), pulados: somar('pulados'), duracaoMs: somar('duracaoMs') };
|
|
399
|
+
}
|
|
391
400
|
/**
|
|
392
401
|
* O `test` das lanes (D4): ok so com todas ok; a evidencia de quem caiu; a parede e a da lane mais longa. A falha de
|
|
393
402
|
* verdade de uma lane manda, como num comando so do Classic: o estouro ou o relogio sob steal de outra lane nao a esconde.
|
|
@@ -414,9 +423,22 @@ function testDasLanes(lanes) {
|
|
|
414
423
|
const causas = lanes.map((l) => l.resultado.causa).filter((c) => !!c);
|
|
415
424
|
const causa = deVerdade.length ? deVerdade[0].resultado.causa ?? 'exit' : PIOR_CAUSA.find((c) => causas.includes(c));
|
|
416
425
|
const soRelogio = caidas.length > 0 && caidas.every((l) => (l.resultado.relogioSobSteal?.length ?? 0) > 0);
|
|
417
|
-
const
|
|
426
|
+
const nomesExtraidos = juntar((r) => r.testeQueCaiu);
|
|
427
|
+
const testes = lanes.some((l) => l.resultado.junit?.estado === 'lido') ? nomesExtraidos : nomesExtraidos.slice(0, 10);
|
|
418
428
|
const falhas = juntar((r) => r.falhasDeTeste);
|
|
429
|
+
const parciais = lanes.filter((l) => !!l.resultado.parcial);
|
|
430
|
+
const filtrados = juntar((r) => r.parcial?.testes);
|
|
431
|
+
const junit = junitDasLanes(lanes);
|
|
419
432
|
const relogio = soRelogio ? juntar((r) => r.relogioSobSteal) : [];
|
|
433
|
+
const retestes = lanes.map((l) => l.resultado.retesteDeRelogio).filter((r) => !!r);
|
|
434
|
+
const estadoDoReteste = ['reprovou', 'sem-veredito', 'sem-filtro', 'passou']
|
|
435
|
+
.find((estado) => retestes.some((r) => r.estado === estado));
|
|
436
|
+
const reteste = estadoDoReteste ? {
|
|
437
|
+
testes: [...new Set(retestes.flatMap((r) => r.testes))],
|
|
438
|
+
estado: estadoDoReteste,
|
|
439
|
+
...(retestes.find((r) => r.estado === estadoDoReteste)?.code !== undefined
|
|
440
|
+
? { code: retestes.find((r) => r.estado === estadoDoReteste).code } : {}),
|
|
441
|
+
} : undefined;
|
|
420
442
|
const trechos = caidas.map((l) => l.resultado.trecho).filter((t) => !!t);
|
|
421
443
|
return {
|
|
422
444
|
nome: 'test',
|
|
@@ -430,12 +452,22 @@ function testDasLanes(lanes) {
|
|
|
430
452
|
executado: !tempoComFalha && lanes.every((l) => l.resultado.executado !== false),
|
|
431
453
|
...(testes.length ? { testeQueCaiu: testes } : {}),
|
|
432
454
|
...(falhas.length ? { falhasDeTeste: falhas } : {}),
|
|
455
|
+
...(junit ? { junit } : {}),
|
|
456
|
+
...(parciais.length ? { parcial: { testes: [...new Set(filtrados)] } } : {}),
|
|
433
457
|
...(trechos.length ? { trecho: trechos.join('\n').slice(0, 1024) } : {}),
|
|
434
458
|
...(lanes.some((l) => l.resultado.stealPct !== undefined) ? { stealPct: lanes.find((l) => l.resultado.stealPct !== undefined).resultado.stealPct } : {}),
|
|
435
459
|
...(relogio.length ? { relogioSobSteal: relogio } : {}),
|
|
460
|
+
...(reteste ? { retesteDeRelogio: reteste } : {}),
|
|
436
461
|
lanes: lanes.map((l) => ({ lane: l.lane, comando: l.resultado.comando, ok: l.resultado.ok, code: l.resultado.code,
|
|
437
462
|
...(l.resultado.causa ? { causa: l.resultado.causa } : {}), duracaoMs: l.resultado.duracaoMs ?? 0,
|
|
438
|
-
...(l.resultado.testeQueCaiu?.length ? { testeQueCaiu: l.resultado.testeQueCaiu } : {})
|
|
463
|
+
...(l.resultado.testeQueCaiu?.length ? { testeQueCaiu: l.resultado.testeQueCaiu } : {}),
|
|
464
|
+
...(l.resultado.junit ? { junit: l.resultado.junit } : {}),
|
|
465
|
+
...(l.resultado.parcial ? { parcial: l.resultado.parcial } : {}),
|
|
466
|
+
...(l.resultado.comandoExecutado ? { comandoExecutado: l.resultado.comandoExecutado } : {}),
|
|
467
|
+
...(l.resultado.selecao ? { selecao: l.resultado.selecao } : {}),
|
|
468
|
+
...(l.resultado.relogioSobSteal?.length ? { relogioSobSteal: l.resultado.relogioSobSteal } : {}),
|
|
469
|
+
...(l.resultado.retesteDeRelogio ? { retesteDeRelogio: l.resultado.retesteDeRelogio } : {}),
|
|
470
|
+
...(l.resultado.stealPct !== undefined ? { stealPct: l.resultado.stealPct } : {}) })),
|
|
439
471
|
};
|
|
440
472
|
}
|
|
441
473
|
/**
|
|
@@ -466,7 +498,7 @@ function porQueDasLanes(c) {
|
|
|
466
498
|
* Os comandos do manifesto no Agile (D4): `build` reaproveitado do preparo com a guarda valendo; o resto num grupo
|
|
467
499
|
* paralelo, com as lanes no lugar do `verify.test`, so com a guarda valendo; sem ela, tudo em serie (o Classic).
|
|
468
500
|
*/
|
|
469
|
-
function comandosNoAgile(manifesto, cwd, lista, executor, grupo, guarda, preparo) {
|
|
501
|
+
function comandosNoAgile(manifesto, cwd, lista, executor, grupo, guarda, preparo, selecao = { comandos: {}, lanes: {} }, passagemParcial, selecaoPorMudanca = {}) {
|
|
470
502
|
const pecas = [];
|
|
471
503
|
const passosDoPreparo = manifesto.verify.preparo ? passosDoComando(manifesto.verify.preparo) : [];
|
|
472
504
|
const lanesDeclaradas = Object.entries(manifesto.check?.agile?.test ?? {});
|
|
@@ -480,7 +512,12 @@ function comandosNoAgile(manifesto, cwd, lista, executor, grupo, guarda, preparo
|
|
|
480
512
|
pecas.push({ peca: 'grupo', estado: 'fallback', motivo, fallback: 'comandos em serie' });
|
|
481
513
|
if (lanesDeclaradas.length > 0)
|
|
482
514
|
pecas.push({ peca: 'lanes', estado: 'fallback', motivo, fallback: 'verify.test' });
|
|
483
|
-
|
|
515
|
+
const executar = (c) => {
|
|
516
|
+
const parcial = c.filtro && selecao.comandos[c.nome]
|
|
517
|
+
? passagemParcial?.(c.nome, c.comando, cwd, c.filtro, selecao.comandos[c.nome]) : null;
|
|
518
|
+
return parcial && !parcial.ok ? parcial : executor(c.nome, c.comando, cwd);
|
|
519
|
+
};
|
|
520
|
+
return { comandos: lista.map(executar), pecas, paralelo: false, lanes: [] };
|
|
484
521
|
}
|
|
485
522
|
// Com a guarda: o build primeiro (do preparo, quando e so passos dele), e o resto junto.
|
|
486
523
|
const build = lista.find((c) => c.nome === 'build');
|
|
@@ -499,17 +536,55 @@ function comandosNoAgile(manifesto, cwd, lista, executor, grupo, guarda, preparo
|
|
|
499
536
|
for (const c of resto) {
|
|
500
537
|
if (c.nome === 'test' && lanesDeclaradas.length > 0) {
|
|
501
538
|
for (const [lane, comando] of lanesDeclaradas) {
|
|
539
|
+
const junit = manifesto.check?.agile?.junit?.[lane];
|
|
540
|
+
const filtro = manifesto.check?.agile?.filtro?.[lane];
|
|
541
|
+
const recursos = manifesto.check?.agile?.recursos?.[lane];
|
|
542
|
+
const testesFiltrados = selecao.lanes[lane];
|
|
543
|
+
const porMudanca = selecaoPorMudanca[lane];
|
|
544
|
+
const efetivo = porMudanca?.comando ?? comando;
|
|
545
|
+
const dadosDaSelecao = porMudanca ? { comandoIntegral: comando, selecaoPorMudanca: porMudanca } : {};
|
|
502
546
|
if (depoisDaVarredura.has(lane))
|
|
503
|
-
encadeados.push({ nome: `test:${lane}`, comando
|
|
547
|
+
encadeados.push({ nome: `test:${lane}`, comando: efetivo, ...dadosDaSelecao,
|
|
548
|
+
...(junit ? { junit } : {}), ...(filtro ? { filtro } : {}), ...(testesFiltrados ? { testesFiltrados } : {}),
|
|
549
|
+
...(recursos?.length ? { recursos } : {}),
|
|
550
|
+
apos: ['security_scan'], papelCpu: 'integracao' });
|
|
504
551
|
else
|
|
505
|
-
itens.push({ nome: `test:${lane}`, comando
|
|
552
|
+
itens.push({ nome: `test:${lane}`, comando: efetivo, ...dadosDaSelecao, ...(junit ? { junit } : {}),
|
|
553
|
+
...(filtro ? { filtro } : {}), ...(testesFiltrados ? { testesFiltrados } : {}),
|
|
554
|
+
...(recursos?.length ? { recursos } : {}),
|
|
555
|
+
papelCpu: lane === 'hermetica' ? 'hermetica' : 'outro' });
|
|
506
556
|
}
|
|
507
557
|
}
|
|
508
558
|
else
|
|
509
|
-
itens.push({ nome: c.nome, comando: c.comando,
|
|
559
|
+
itens.push({ nome: c.nome, comando: c.comando, ...(c.junit ? { junit: c.junit } : {}),
|
|
560
|
+
...(c.filtro ? { filtro: c.filtro } : {}), ...(selecao.comandos[c.nome] ? { testesFiltrados: selecao.comandos[c.nome] } : {}),
|
|
561
|
+
papelCpu: c.nome === 'security_scan' ? 'varredura' : 'outro' });
|
|
510
562
|
}
|
|
511
563
|
itens.push(...encadeados);
|
|
512
|
-
const
|
|
564
|
+
const parciaisVermelhas = new Map();
|
|
565
|
+
for (const item of itens) {
|
|
566
|
+
if (!item.filtro || !item.testesFiltrados?.length || !passagemParcial)
|
|
567
|
+
continue;
|
|
568
|
+
const parcial = passagemParcial(item.nome, item.comando, cwd, item.filtro, item.testesFiltrados);
|
|
569
|
+
if (parcial && !parcial.ok)
|
|
570
|
+
parciaisVermelhas.set(item.nome, parcial);
|
|
571
|
+
}
|
|
572
|
+
const inteiros = itens.filter((i) => !parciaisVermelhas.has(i.nome));
|
|
573
|
+
const resultadosInteiros = inteiros.length > 1 ? grupo(inteiros, cwd) : inteiros.map((i) => executor(i.nome, i.comando, cwd));
|
|
574
|
+
const porItemInteiro = new Map(inteiros.map((item, i) => [item.nome, resultadosInteiros[i]]));
|
|
575
|
+
const resultados = itens.map((item) => {
|
|
576
|
+
const parcial = parciaisVermelhas.get(item.nome);
|
|
577
|
+
if (parcial)
|
|
578
|
+
return parcial;
|
|
579
|
+
const resultado = porItemInteiro.get(item.nome);
|
|
580
|
+
if (!item.selecaoPorMudanca || !item.comandoIntegral)
|
|
581
|
+
return resultado;
|
|
582
|
+
const { comando: _comando, ...registro } = item.selecaoPorMudanca;
|
|
583
|
+
return item.selecaoPorMudanca.modo === 'selecionada'
|
|
584
|
+
? { ...resultado, comando: item.comandoIntegral, comandoExecutado: item.comando,
|
|
585
|
+
parcial: { testes: [] }, selecao: registro }
|
|
586
|
+
: { ...resultado, comando: item.comandoIntegral, selecao: registro };
|
|
587
|
+
});
|
|
513
588
|
pecas.push(...(grupo.pecas ?? []));
|
|
514
589
|
const lanes = [];
|
|
515
590
|
itens.forEach((item, i) => {
|
|
@@ -590,7 +665,8 @@ exports.RelatorioDoCheck = RelatorioDoCheck;
|
|
|
590
665
|
*/
|
|
591
666
|
function pecasDoRelatorio(linhas) {
|
|
592
667
|
const vistas = new Set();
|
|
593
|
-
return linhas.filter((l) => l.tipo === 'peca' && typeof l.peca === 'string'
|
|
668
|
+
return linhas.filter((l) => l.tipo === 'peca' && typeof l.peca === 'string'
|
|
669
|
+
&& (l.estado === 'agile' || l.estado === 'degradado' || l.estado === 'fallback'))
|
|
594
670
|
.map((l) => ({ peca: l.peca, estado: l.estado,
|
|
595
671
|
...(typeof l.motivo === 'string' ? { motivo: l.motivo } : {}), ...(typeof l.fallback === 'string' ? { fallback: l.fallback } : {}) }))
|
|
596
672
|
.filter((p) => { const chave = JSON.stringify(p); if (vistas.has(chave))
|