@orkastery/cli 0.6.2 → 0.6.4
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 +1 -1
- package/adapters/hermes/README.md +1 -1
- package/adapters/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/assets/adapters-en/claude-code/README.md +1 -1
- package/assets/adapters-en/hermes/README.md +1 -1
- package/assets/docs/padroes/roadmap-de-produto.md +1 -1
- package/assets/docs-en/padroes/roadmap-de-produto.md +1 -1
- package/assets/references-en/testing-patterns.md +1 -1
- package/dist/agendador-cpu.js +49 -5
- package/dist/catalogo.js +3 -3
- package/dist/{canarios-hitl.js → cenarios-hitl.js} +7 -7
- package/dist/{canarios-i43.js → cenarios-i43.js} +16 -16
- package/dist/{canarios-pulse.js → cenarios-pulse.js} +3 -3
- package/dist/{canarios-rm057.js → cenarios-rm057.js} +3 -3
- package/dist/{canarios-sensores.js → cenarios-sensores.js} +9 -8
- package/dist/{canarios.js → cenarios.js} +22 -22
- package/dist/check-agil.js +2 -1
- package/dist/check-paralelo.js +337 -14
- package/dist/claims.js +1 -1
- package/dist/docs.js +1 -1
- package/dist/evalrunner.js +37 -37
- package/dist/hitl-canais.js +1 -1
- package/dist/hitl-presentation.js +1 -1
- package/dist/hitl-tempo-parado.js +1 -1
- package/dist/index.js +4 -4
- package/dist/init.js +1 -1
- package/dist/locale-ajuda.js +6 -6
- package/dist/locale-operacao-restante.js +18 -18
- package/dist/sandbox.js +11 -11
- package/dist/security-scan/agil.js +28 -12
- package/dist/security-scan/executor.js +1 -0
- package/dist/security-scan/incremental.js +289 -0
- package/dist/security-scan/trabalhador.js +53 -5
- package/dist/verify.js +12 -1
- package/eval/README.md +8 -8
- package/eval/fixtures/fx-codex-dry/caso.json +1 -1
- package/eval/fixtures/fx-happy/caso.json +1 -1
- package/eval/fixtures/fx-indice-reversao/caso.json +1 -1
- package/eval/fixtures/fx-listagem-abertas/caso.json +1 -1
- package/eval/fixtures/fx-omnicanal/caso.json +1 -1
- package/eval/fixtures/fx-pedido-colado/caso.json +1 -1
- package/eval/fixtures/fx-schema-drift/caso.json +1 -1
- package/package.json +1 -1
- package/references/testing-patterns.md +1 -1
package/dist/check-paralelo.js
CHANGED
|
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.argvDoItem = argvDoItem;
|
|
37
|
+
exports.chaveDoPerfilIntegracao = chaveDoPerfilIntegracao;
|
|
38
|
+
exports.picoCpuPorFonte = picoCpuPorFonte;
|
|
37
39
|
exports.esperaDoGrupo = esperaDoGrupo;
|
|
38
40
|
exports.rodarEmParalelo = rodarEmParalelo;
|
|
39
41
|
/**
|
|
@@ -48,12 +50,17 @@ exports.rodarEmParalelo = rodarEmParalelo;
|
|
|
48
50
|
* de integracao depois da varredura), e `maxParalelo` limita quantos rodam ao mesmo tempo.
|
|
49
51
|
*/
|
|
50
52
|
const node_child_process_1 = require("node:child_process");
|
|
53
|
+
const node_crypto_1 = require("node:crypto");
|
|
51
54
|
const fs = __importStar(require("node:fs"));
|
|
52
55
|
const os = __importStar(require("node:os"));
|
|
53
56
|
const path = __importStar(require("node:path"));
|
|
54
57
|
const agendador_cpu_1 = require("./agendador-cpu");
|
|
58
|
+
const integracoes_locais_1 = require("./integracoes-locais");
|
|
55
59
|
const TIMEOUT_BIN = '/usr/bin/timeout';
|
|
56
60
|
const NICE_BIN = '/usr/bin/nice';
|
|
61
|
+
const TIME_BIN = '/usr/bin/time';
|
|
62
|
+
const CONTRATO_PERFIL_INTEGRACAO = 'ork.check.cpu-integracao/v1';
|
|
63
|
+
const INTERVALO_CPU_MS = 100;
|
|
57
64
|
/** O argv de um item: `nice` (se pedido e existir), `timeout(1)` (se existir) e o `argv` do item ou `bash -c`. */
|
|
58
65
|
function argvDoItem(item, comGrupo = fs.existsSync(TIMEOUT_BIN), comNice = fs.existsSync(NICE_BIN)) {
|
|
59
66
|
const programa = item.argv?.length ? item.argv : ['bash', '-c', item.comando];
|
|
@@ -77,6 +84,243 @@ function vagas(max) {
|
|
|
77
84
|
livres++; },
|
|
78
85
|
};
|
|
79
86
|
}
|
|
87
|
+
function arquivoDoPerfilIntegracao() {
|
|
88
|
+
const base = process.env.XDG_CACHE_HOME && path.isAbsolute(process.env.XDG_CACHE_HOME)
|
|
89
|
+
? process.env.XDG_CACHE_HOME : path.join(os.homedir(), '.cache');
|
|
90
|
+
return path.join(base, 'orkastery', 'check', 'cpu-integracao.json');
|
|
91
|
+
}
|
|
92
|
+
/** Os arquivos da lane de integracao, pela mesma regra do `test-lanes.cjs`: a lista do ambiente ou a do nucleo. */
|
|
93
|
+
function arquivosDaIntegracao(env = process.env) {
|
|
94
|
+
const lista = env.ORK_LANES_INTEGRACAO !== undefined
|
|
95
|
+
? env.ORK_LANES_INTEGRACAO.split(',').map((n) => n.trim()).filter(Boolean) : [...integracoes_locais_1.TESTES_DE_INTEGRACAO_LOCAL];
|
|
96
|
+
return [...new Set(lista)].sort();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A medida so atravessa rodadas quando comando, capacidade e entradas que definem a lane continuam iguais: o script das
|
|
100
|
+
* lanes, a lista de integracao e o conteudo compilado de cada teste dela. Teste novo, mudado ou ausente troca a chave.
|
|
101
|
+
*/
|
|
102
|
+
function chaveDoPerfilIntegracao(itens, cpus, arquivos = arquivosDaIntegracao()) {
|
|
103
|
+
const integracao = itens.filter((i) => i.papelCpu === 'integracao');
|
|
104
|
+
if (!integracao.length)
|
|
105
|
+
return null;
|
|
106
|
+
const h = (0, node_crypto_1.createHash)('sha256').update(JSON.stringify({ cpus, node: process.version,
|
|
107
|
+
comandos: integracao.map((i) => i.comando), arquivos }));
|
|
108
|
+
const cwd = integracao[0].cwd;
|
|
109
|
+
const relativos = ['core/dist/integracoes-locais.js', 'core/scripts/test-lanes.cjs',
|
|
110
|
+
...arquivos.map((nome) => path.join('core', 'dist-test', 'test', nome))];
|
|
111
|
+
for (const relativo of relativos) {
|
|
112
|
+
try {
|
|
113
|
+
h.update(relativo).update(fs.readFileSync(path.join(cwd, relativo)));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
h.update(`${relativo}:ausente`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return h.digest('hex');
|
|
120
|
+
}
|
|
121
|
+
function lerPerfilIntegracao(chave, cpus) {
|
|
122
|
+
if (!chave)
|
|
123
|
+
return null;
|
|
124
|
+
try {
|
|
125
|
+
const p = JSON.parse(fs.readFileSync(arquivoDoPerfilIntegracao(), 'utf8'));
|
|
126
|
+
return p?.contrato === CONTRATO_PERFIL_INTEGRACAO && p.chave === chave && p.cpus === cpus && p.completa
|
|
127
|
+
&& Number.isFinite(p.picoCpus) && p.picoCpus > 0 && p.picoCpus <= cpus ? p : null;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function gravarPerfilIntegracao(chave, cpus, medicao) {
|
|
134
|
+
if (!chave || !medicao.completa || medicao.anomalia || medicao.picoCpus === null
|
|
135
|
+
|| !(medicao.picoCpus > 0) || medicao.picoCpus > cpus)
|
|
136
|
+
return;
|
|
137
|
+
const arquivo = arquivoDoPerfilIntegracao();
|
|
138
|
+
try {
|
|
139
|
+
fs.mkdirSync(path.dirname(arquivo), { recursive: true });
|
|
140
|
+
const temporario = `${arquivo}.${process.pid}.tmp`;
|
|
141
|
+
const perfil = { contrato: CONTRATO_PERFIL_INTEGRACAO, chave, cpus,
|
|
142
|
+
picoCpus: medicao.picoCpus, completa: true, medidoEm: new Date().toISOString() };
|
|
143
|
+
fs.writeFileSync(temporario, JSON.stringify(perfil) + '\n', { mode: 0o600 });
|
|
144
|
+
fs.renameSync(temporario, arquivo);
|
|
145
|
+
}
|
|
146
|
+
catch { /* cache de desempenho ausente conserva a reserva; nunca derruba o CHECK */ }
|
|
147
|
+
}
|
|
148
|
+
const CLK_TCK = (() => {
|
|
149
|
+
const r = (0, node_child_process_1.spawnSync)('getconf', ['CLK_TCK'], { encoding: 'utf8', timeout: 1000 });
|
|
150
|
+
const n = Number(r.stdout?.trim());
|
|
151
|
+
return Number.isFinite(n) && n > 0 ? n : 100;
|
|
152
|
+
})();
|
|
153
|
+
function processosCpu() {
|
|
154
|
+
const tabela = new Map();
|
|
155
|
+
let nomes = [];
|
|
156
|
+
try {
|
|
157
|
+
nomes = fs.readdirSync('/proc');
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return tabela;
|
|
161
|
+
}
|
|
162
|
+
for (const nome of nomes) {
|
|
163
|
+
if (!/^\d+$/.test(nome))
|
|
164
|
+
continue;
|
|
165
|
+
try {
|
|
166
|
+
const stat = fs.readFileSync(`/proc/${nome}/stat`, 'utf8');
|
|
167
|
+
const depois = stat.slice(stat.lastIndexOf(')') + 2).trim().split(/\s+/);
|
|
168
|
+
tabela.set(Number(nome), { ppid: Number(depois[1]), cpuMs: (Number(depois[11]) + Number(depois[12])) * 1000 / CLK_TCK,
|
|
169
|
+
inicio: depois[19] });
|
|
170
|
+
}
|
|
171
|
+
catch { /* acabou entre a lista e a leitura */ }
|
|
172
|
+
}
|
|
173
|
+
return tabela;
|
|
174
|
+
}
|
|
175
|
+
function arvoreDeProcessos(raiz, tabela) {
|
|
176
|
+
const filhos = new Map();
|
|
177
|
+
for (const [pid, p] of tabela) {
|
|
178
|
+
const lista = filhos.get(p.ppid) ?? [];
|
|
179
|
+
lista.push(pid);
|
|
180
|
+
filhos.set(p.ppid, lista);
|
|
181
|
+
}
|
|
182
|
+
const vistos = new Set(), pilha = [raiz];
|
|
183
|
+
while (pilha.length) {
|
|
184
|
+
const pid = pilha.pop();
|
|
185
|
+
if (vistos.has(pid))
|
|
186
|
+
continue;
|
|
187
|
+
vistos.add(pid);
|
|
188
|
+
pilha.push(...(filhos.get(pid) ?? []));
|
|
189
|
+
}
|
|
190
|
+
return [...vistos];
|
|
191
|
+
}
|
|
192
|
+
function nomesDosContainers(arquivo) {
|
|
193
|
+
try {
|
|
194
|
+
return [...new Set(fs.readFileSync(arquivo, 'utf8').split('\n').filter(Boolean).flatMap((linha) => {
|
|
195
|
+
try {
|
|
196
|
+
const r = JSON.parse(linha);
|
|
197
|
+
return typeof r.nome === 'string' ? [r.nome] : [];
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
}))];
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function arquivoCpuDoContainer(nome) {
|
|
209
|
+
const r = (0, node_child_process_1.spawnSync)('docker', ['inspect', '--format', '{{.Id}} {{.State.Pid}}', nome], { encoding: 'utf8', timeout: 1000 });
|
|
210
|
+
const [id = '', pidTexto = ''] = r.stdout?.trim().split(/\s+/) ?? [];
|
|
211
|
+
const pid = Number(pidTexto);
|
|
212
|
+
if (r.status !== 0)
|
|
213
|
+
return { fonte: null, motivo: 'docker inspect nao encontrou o conteiner' };
|
|
214
|
+
if (!/^[a-f0-9]{12,64}$/.test(id) || !Number.isInteger(pid) || pid <= 0) {
|
|
215
|
+
return { fonte: null, motivo: 'PID ou identificador do conteiner indisponivel' };
|
|
216
|
+
}
|
|
217
|
+
let linhas = '';
|
|
218
|
+
try {
|
|
219
|
+
linhas = fs.readFileSync(`/proc/${pid}/cgroup`, 'utf8');
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return { fonte: null, motivo: 'cgroup do PID do conteiner indisponivel' };
|
|
223
|
+
}
|
|
224
|
+
const unificado = /^0::(.+)$/m.exec(linhas)?.[1];
|
|
225
|
+
const candidatos = [
|
|
226
|
+
...(unificado ? [{ arquivo: path.join('/sys/fs/cgroup', unificado, 'cpu.stat'), divisor: 1000 }] : []),
|
|
227
|
+
{ arquivo: path.join('/sys/fs/cgroup/system.slice', `docker-${id}.scope`, 'cpu.stat'), divisor: 1000 },
|
|
228
|
+
{ arquivo: path.join('/sys/fs/cgroup/docker', id, 'cpu.stat'), divisor: 1000 },
|
|
229
|
+
];
|
|
230
|
+
const legado = /^(?:\d+):[^\n]*(?:cpuacct|cpu)[^\n]*:(.+)$/m.exec(linhas)?.[1];
|
|
231
|
+
if (legado)
|
|
232
|
+
candidatos.push({ arquivo: path.join('/sys/fs/cgroup/cpuacct', legado, 'cpuacct.usage'), divisor: 1_000_000 });
|
|
233
|
+
const fonte = candidatos.find((x) => fs.existsSync(x.arquivo)) ?? null;
|
|
234
|
+
return fonte ? { fonte, motivo: null }
|
|
235
|
+
: { fonte: null, motivo: 'cpu.stat ausente no cgroup v2, no escopo do systemd e no caminho legado' };
|
|
236
|
+
}
|
|
237
|
+
function lerCpuDoCgroup(fonte) {
|
|
238
|
+
try {
|
|
239
|
+
const texto = fs.readFileSync(fonte.arquivo, 'utf8');
|
|
240
|
+
const bruto = fonte.arquivo.endsWith('cpu.stat') ? Number(/^usage_usec\s+(\d+)$/m.exec(texto)?.[1]) : Number(texto.trim());
|
|
241
|
+
return Number.isFinite(bruto) && bruto >= 0 ? bruto / fonte.divisor : null;
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Calcula o pico por fonte. A primeira leitura vira baseline; uma fonte que nasce depois dela contribui com o acumulado
|
|
249
|
+
* desde seu nascimento, sem despejar na janela o tempo anterior de processos ou conteineres que ja existiam.
|
|
250
|
+
*/
|
|
251
|
+
function picoCpuPorFonte(amostras, cpus) {
|
|
252
|
+
let pico = 0;
|
|
253
|
+
for (let i = 1; i < amostras.length; i++) {
|
|
254
|
+
const anterior = amostras[i - 1], atual = amostras[i];
|
|
255
|
+
if (!(atual.em > anterior.em))
|
|
256
|
+
continue;
|
|
257
|
+
let delta = 0;
|
|
258
|
+
for (const [fonte, acumulado] of Object.entries(atual.fontes)) {
|
|
259
|
+
const base = anterior.fontes[fonte];
|
|
260
|
+
delta += Math.max(0, acumulado - (base ?? 0));
|
|
261
|
+
}
|
|
262
|
+
pico = Math.max(pico, delta / (atual.em - anterior.em));
|
|
263
|
+
}
|
|
264
|
+
if (pico > cpus + 1e-9) {
|
|
265
|
+
return { picoCpus: null, anomalia: `pico medido de ${pico.toFixed(2)} CPU excede a capacidade de ${cpus} CPU` };
|
|
266
|
+
}
|
|
267
|
+
return { picoCpus: pico > 0 ? Math.ceil(pico * 100) / 100 : null, anomalia: null };
|
|
268
|
+
}
|
|
269
|
+
/** CPU acumulada e pico por janela da integracao; nomes registrados mas nunca observados tornam a medida incompleta. */
|
|
270
|
+
function sondarCpuDaIntegracao(pid, registro, cpus) {
|
|
271
|
+
const fontes = new Map();
|
|
272
|
+
const motivos = new Map();
|
|
273
|
+
const amostras = [];
|
|
274
|
+
const primeiraCpuDosContainers = new Map();
|
|
275
|
+
const ultimaCpuDosContainers = new Map();
|
|
276
|
+
const amostrar = () => {
|
|
277
|
+
const tabela = processosCpu();
|
|
278
|
+
const atuais = {};
|
|
279
|
+
for (const p of arvoreDeProcessos(pid, tabela)) {
|
|
280
|
+
const x = tabela.get(p);
|
|
281
|
+
if (x)
|
|
282
|
+
atuais[`processo:${p}:${x.inicio}`] = x.cpuMs;
|
|
283
|
+
}
|
|
284
|
+
for (const nome of nomesDosContainers(registro)) {
|
|
285
|
+
let fonte = fontes.get(nome);
|
|
286
|
+
if (!fonte) {
|
|
287
|
+
const busca = arquivoCpuDoContainer(nome);
|
|
288
|
+
if (busca.fonte) {
|
|
289
|
+
fonte = busca.fonte;
|
|
290
|
+
fontes.set(nome, fonte);
|
|
291
|
+
motivos.delete(nome);
|
|
292
|
+
}
|
|
293
|
+
else if (busca.motivo)
|
|
294
|
+
motivos.set(nome, busca.motivo);
|
|
295
|
+
}
|
|
296
|
+
const cpu = fonte ? lerCpuDoCgroup(fonte) : null;
|
|
297
|
+
if (cpu !== null) {
|
|
298
|
+
atuais[`conteiner:${nome}`] = cpu;
|
|
299
|
+
if (!primeiraCpuDosContainers.has(nome))
|
|
300
|
+
primeiraCpuDosContainers.set(nome, amostras.length === 0 ? cpu : 0);
|
|
301
|
+
ultimaCpuDosContainers.set(nome, cpu);
|
|
302
|
+
motivos.delete(nome);
|
|
303
|
+
}
|
|
304
|
+
else if (fonte)
|
|
305
|
+
motivos.set(nome, 'contador de CPU do cgroup ilegivel');
|
|
306
|
+
}
|
|
307
|
+
amostras.push({ em: Date.now(), fontes: atuais });
|
|
308
|
+
};
|
|
309
|
+
return {
|
|
310
|
+
amostrar,
|
|
311
|
+
fechar: (arvoreMs, paredeMs) => {
|
|
312
|
+
amostrar();
|
|
313
|
+
const nomes = nomesDosContainers(registro);
|
|
314
|
+
const containersMs = nomes.reduce((total, nome) => total + Math.max(0, (ultimaCpuDosContainers.get(nome) ?? 0) - (primeiraCpuDosContainers.get(nome) ?? 0)), 0);
|
|
315
|
+
const ausentes = nomes.filter((nome) => !ultimaCpuDosContainers.has(nome));
|
|
316
|
+
const pico = picoCpuPorFonte(amostras, cpus);
|
|
317
|
+
return { arvoreMs, containersMs, totalMs: arvoreMs + containersMs, paredeMs,
|
|
318
|
+
picoCpus: pico.picoCpus, completa: ausentes.length === 0 && !pico.anomalia,
|
|
319
|
+
containers: nomes, ausentes, motivosDosAusentes: ausentes.map((container) => ({ container,
|
|
320
|
+
motivo: motivos.get(container) ?? 'conteiner terminou antes de uma amostra completa' })), anomalia: pico.anomalia };
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
}
|
|
80
324
|
/**
|
|
81
325
|
* O lado do auxiliar: lanca cada item quando os de `apos` terminam e ha vaga, espera todos e grava `resultado.json` ao
|
|
82
326
|
* lado das saidas.
|
|
@@ -84,8 +328,17 @@ function vagas(max) {
|
|
|
84
328
|
async function rodarEspecificacao(esp) {
|
|
85
329
|
const comGrupo = fs.existsSync(TIMEOUT_BIN);
|
|
86
330
|
const usaAgendador = esp.itens.some((i) => i.papelCpu === 'hermetica');
|
|
87
|
-
const
|
|
88
|
-
const
|
|
331
|
+
const cpus = esp.cpus ?? os.availableParallelism();
|
|
332
|
+
const chaveDoPerfil = usaAgendador ? chaveDoPerfilIntegracao(esp.itens, cpus) : null;
|
|
333
|
+
const perfil = esp.medicaoCpu?.cpusIntegracao === undefined ? lerPerfilIntegracao(chaveDoPerfil, cpus) : null;
|
|
334
|
+
const medicao = esp.medicaoCpu ?? await (0, agendador_cpu_1.medirCpuDoGrupo)();
|
|
335
|
+
if (perfil)
|
|
336
|
+
Object.assign(medicao, { cpusIntegracao: perfil.picoCpus, fonteCpusIntegracao: 'medida' });
|
|
337
|
+
else if (medicao.cpusIntegracao === undefined)
|
|
338
|
+
Object.assign(medicao, { cpusIntegracao: null, fonteCpusIntegracao: 'conservadora' });
|
|
339
|
+
const plano = usaAgendador ? (0, agendador_cpu_1.planejarCpuDoGrupo)(cpus, medicao) : undefined;
|
|
340
|
+
const sinalDaReserva = plano && !plano.saturado ? path.join(esp.dir, 'reserva-devolvida.json') : undefined;
|
|
341
|
+
const itens = plano ? aplicarPlano(esp.itens, plano, sinalDaReserva) : esp.itens;
|
|
89
342
|
const limite = vagas(esp.maxParalelo && esp.maxParalelo > 0 ? esp.maxParalelo : itens.length);
|
|
90
343
|
const porNome = new Map();
|
|
91
344
|
const indice = new Map(itens.map((item, i) => [item.nome, i]));
|
|
@@ -112,26 +365,92 @@ async function rodarEspecificacao(esp) {
|
|
|
112
365
|
porNome.set(item.nome, vez);
|
|
113
366
|
return vez;
|
|
114
367
|
};
|
|
115
|
-
const
|
|
368
|
+
const todos = Promise.all(itens.map(vezDoItem));
|
|
369
|
+
const reservados = itens.filter((item) => item.papelCpu === 'varredura' || item.papelCpu === 'integracao');
|
|
370
|
+
const devolverReserva = async () => {
|
|
371
|
+
if (!sinalDaReserva || !reservados.some((item) => item.papelCpu === 'varredura')
|
|
372
|
+
|| !reservados.some((item) => item.papelCpu === 'integracao'))
|
|
373
|
+
return;
|
|
374
|
+
const varreduras = reservados.filter((item) => item.papelCpu === 'varredura');
|
|
375
|
+
const integracoes = reservados.filter((item) => item.papelCpu === 'integracao');
|
|
376
|
+
const degrau = async (origem, pecas, vagas, antes) => {
|
|
377
|
+
await Promise.all(pecas.map((item) => vezDoItem(item, indice.get(item.nome))));
|
|
378
|
+
fs.appendFileSync(sinalDaReserva, JSON.stringify({ contrato: 'ork.check.reserva-devolvida/v2', origem,
|
|
379
|
+
em: new Date().toISOString(), vagasGanhas: vagas - antes, vagas }) + '\n', { mode: 0o600 });
|
|
380
|
+
};
|
|
381
|
+
if (!plano.integracaoEmParalelo) {
|
|
382
|
+
await degrau('varredura', varreduras, plano.cpusHermeticaAposVarredura, plano.cpusHermetica);
|
|
383
|
+
await degrau('integracao', integracoes, plano.cpusHermeticaAposReserva, plano.cpusHermeticaAposVarredura);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
// As promessas escrevem em série no event loop; o primeiro término recebe somente a reserva da própria peça.
|
|
387
|
+
let vagas = plano.cpusHermetica;
|
|
388
|
+
const aoTerminar = async (origem, pecas, libera) => {
|
|
389
|
+
await Promise.all(pecas.map((item) => vezDoItem(item, indice.get(item.nome))));
|
|
390
|
+
const antes = vagas;
|
|
391
|
+
vagas = Math.min(plano.cpus, vagas + libera);
|
|
392
|
+
fs.appendFileSync(sinalDaReserva, JSON.stringify({ contrato: 'ork.check.reserva-devolvida/v2', origem,
|
|
393
|
+
em: new Date().toISOString(), vagasGanhas: vagas - antes, vagas }) + '\n', { mode: 0o600 });
|
|
394
|
+
};
|
|
395
|
+
await Promise.all([aoTerminar('varredura', varreduras, plano.cpusDaFatia),
|
|
396
|
+
aoTerminar('integracao', integracoes, plano.cpusIntegracao)]);
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
const [, terminados] = await Promise.all([devolverReserva(), todos]);
|
|
400
|
+
const medidaDaIntegracao = terminados.find((t) => t.medicaoCpu)?.medicaoCpu;
|
|
401
|
+
if (plano && medidaDaIntegracao) {
|
|
402
|
+
plano.medicaoCpuIntegracao = medidaDaIntegracao;
|
|
403
|
+
gravarPerfilIntegracao(chaveDoPerfil, cpus, medidaDaIntegracao);
|
|
404
|
+
}
|
|
116
405
|
fs.writeFileSync(path.join(esp.dir, 'resultado.json'), JSON.stringify({ terminados, ...(plano ? { agendamentoCpu: plano } : {}) }), { mode: 0o600 });
|
|
117
406
|
function lancar(item, i) {
|
|
118
407
|
return new Promise((resolve) => {
|
|
119
408
|
const saida = fs.openSync(path.join(esp.dir, `${i}.out`), 'w', 0o600);
|
|
120
409
|
const erro = fs.openSync(path.join(esp.dir, `${i}.err`), 'w', 0o600);
|
|
121
410
|
const inicio = Date.now();
|
|
122
|
-
const
|
|
411
|
+
const medirIntegracao = item.papelCpu === 'integracao';
|
|
412
|
+
const registro = path.join(esp.dir, `${i}.containers.jsonl`);
|
|
413
|
+
const tempo = path.join(esp.dir, `${i}.tempo`);
|
|
414
|
+
if (medirIntegracao)
|
|
415
|
+
fs.writeFileSync(registro, '', { mode: 0o600 });
|
|
416
|
+
const base = argvDoItem(item, comGrupo);
|
|
417
|
+
const medido = medirIntegracao && fs.existsSync(TIME_BIN)
|
|
418
|
+
? [TIME_BIN, '-f', '%U %S', '-o', tempo, ...base] : base;
|
|
419
|
+
const [bin, ...args] = medido;
|
|
123
420
|
let fim = null;
|
|
421
|
+
let sonda = null;
|
|
422
|
+
let relogio = null;
|
|
124
423
|
const encerrar = (t) => {
|
|
125
424
|
if (fim)
|
|
126
425
|
return;
|
|
127
|
-
|
|
426
|
+
if (relogio)
|
|
427
|
+
clearInterval(relogio);
|
|
428
|
+
let arvoreMs = 0;
|
|
429
|
+
if (medirIntegracao) {
|
|
430
|
+
try {
|
|
431
|
+
const [user, sys] = fs.readFileSync(tempo, 'utf8').trim().split(/\s+/).map(Number);
|
|
432
|
+
arvoreMs = (user + sys) * 1000;
|
|
433
|
+
}
|
|
434
|
+
catch { /* medida incompleta abaixo */ }
|
|
435
|
+
}
|
|
436
|
+
const medicaoCpu = medirIntegracao && sonda ? sonda.fechar(arvoreMs, t.duracaoMs) : undefined;
|
|
437
|
+
if (medicaoCpu && !(arvoreMs > 0))
|
|
438
|
+
medicaoCpu.completa = false;
|
|
439
|
+
fim = { ...t, ...(medicaoCpu ? { medicaoCpu } : {}) };
|
|
128
440
|
fs.closeSync(saida);
|
|
129
441
|
fs.closeSync(erro);
|
|
130
|
-
resolve(
|
|
442
|
+
resolve(fim);
|
|
131
443
|
};
|
|
132
444
|
// Sem o `timeout(1)`, o proprio auxiliar mata pelo prazo, como o `spawnSync` do `exec` faria.
|
|
133
|
-
const
|
|
445
|
+
const ambiente = { ...process.env, ...item.ambiente,
|
|
446
|
+
...(medirIntegracao ? { [agendador_cpu_1.ENV_CONTAINERS_DA_LANE]: registro } : {}) };
|
|
447
|
+
const filho = (0, node_child_process_1.spawn)(bin, args, { cwd: item.cwd, env: ambiente, stdio: ['ignore', saida, erro],
|
|
134
448
|
...(comGrupo ? {} : { timeout: item.prazoMs, killSignal: 'SIGTERM' }) });
|
|
449
|
+
if (medirIntegracao) {
|
|
450
|
+
sonda = sondarCpuDaIntegracao(filho.pid, registro, cpus);
|
|
451
|
+
sonda.amostrar();
|
|
452
|
+
relogio = setInterval(sonda.amostrar, INTERVALO_CPU_MS);
|
|
453
|
+
}
|
|
135
454
|
filho.on('error', (e) => encerrar({ nome: item.nome, code: null, signal: null,
|
|
136
455
|
error: e.code ?? e.message, duracaoMs: Date.now() - inicio, comGrupo }));
|
|
137
456
|
filho.on('close', (code, signal) => encerrar({ nome: item.nome, code, signal,
|
|
@@ -141,7 +460,7 @@ async function rodarEspecificacao(esp) {
|
|
|
141
460
|
}
|
|
142
461
|
}
|
|
143
462
|
/** Aplica a mesma conta aos processos, as lanes e aos conteineres que eles criarem. */
|
|
144
|
-
function aplicarPlano(originais, plano) {
|
|
463
|
+
function aplicarPlano(originais, plano, sinalDaReserva) {
|
|
145
464
|
const itens = originais.map((item) => ({ ...item, ambiente: { ...item.ambiente } }));
|
|
146
465
|
const porPapel = (papel) => itens.filter((i) => i.papelCpu === papel);
|
|
147
466
|
const hermetica = porPapel('hermetica')[0];
|
|
@@ -158,23 +477,27 @@ function aplicarPlano(originais, plano) {
|
|
|
158
477
|
item.ambiente = { ...item.ambiente, ...valores };
|
|
159
478
|
};
|
|
160
479
|
const cpusContainer = String(plano.cpusPorContainer);
|
|
161
|
-
ambiente(hermetica, { [agendador_cpu_1.ENV_CPUS_DA_LANE]: String(plano.cpusHermetica), [agendador_cpu_1.ENV_CPUS_DOCKER]: cpusContainer
|
|
480
|
+
ambiente(hermetica, { [agendador_cpu_1.ENV_CPUS_DA_LANE]: String(plano.cpusHermetica), [agendador_cpu_1.ENV_CPUS_DOCKER]: cpusContainer,
|
|
481
|
+
...(sinalDaReserva && varredura && integracoes.length > 0 ? {
|
|
482
|
+
[agendador_cpu_1.ENV_CPUS_DA_LANE_APOS_VARREDURA]: String(plano.cpusHermeticaAposVarredura),
|
|
483
|
+
[agendador_cpu_1.ENV_CPUS_DA_LANE_APOS_RESERVA]: String(plano.cpusHermeticaAposReserva), [agendador_cpu_1.ENV_SINAL_DA_RESERVA]: sinalDaReserva,
|
|
484
|
+
} : {}) });
|
|
162
485
|
ambiente(varredura, {
|
|
163
486
|
ORK_CHECK_VARREDURA_PARALELA: String(plano.ferramentasDaVarredura),
|
|
164
487
|
[agendador_cpu_1.ENV_CPUS_DOCKER]: cpusContainer,
|
|
165
488
|
});
|
|
166
|
-
let
|
|
489
|
+
let anteriorIntegracao;
|
|
167
490
|
for (const item of integracoes) {
|
|
168
491
|
ambiente(item, { [agendador_cpu_1.ENV_CPUS_DA_LANE]: String(plano.cpusIntegracao), [agendador_cpu_1.ENV_CPUS_DOCKER]: cpusContainer });
|
|
169
|
-
depender(item, [
|
|
170
|
-
|
|
492
|
+
depender(item, [anteriorIntegracao ?? (plano.integracaoEmParalelo ? undefined : varredura?.nome)]);
|
|
493
|
+
anteriorIntegracao = item.nome;
|
|
171
494
|
}
|
|
172
495
|
if (plano.saturado)
|
|
173
496
|
depender(varredura, [hermetica?.nome]);
|
|
174
497
|
// Comandos sem papel conhecido nao disputam CPU com as tres pecas: fecham a cadeia, um por vez.
|
|
175
498
|
for (const item of outros) {
|
|
176
|
-
depender(item, [hermetica?.nome,
|
|
177
|
-
|
|
499
|
+
depender(item, [hermetica?.nome, varredura?.nome, anteriorIntegracao]);
|
|
500
|
+
anteriorIntegracao = item.nome;
|
|
178
501
|
}
|
|
179
502
|
return itens;
|
|
180
503
|
}
|
package/dist/claims.js
CHANGED
|
@@ -127,7 +127,7 @@ function montarClaim(entrada) {
|
|
|
127
127
|
criadoEm: (0, util_1.agora)(),
|
|
128
128
|
estado: verificar.length > 0 ? 'pendente' : 'nao-verificavel',
|
|
129
129
|
...(entrada.origem ? { origem: entrada.origem } : {}),
|
|
130
|
-
// I-53 (RM-037, P6): a mesma regra para CLI, MCP,
|
|
130
|
+
// I-53 (RM-037, P6): a mesma regra para CLI, MCP, cenarios e auditor, porque todos passam aqui.
|
|
131
131
|
lint: (0, claim_lint_1.analisarComandos)(verificar),
|
|
132
132
|
};
|
|
133
133
|
}
|
package/dist/docs.js
CHANGED
|
@@ -110,7 +110,7 @@ exports.DIMENSOES = {
|
|
|
110
110
|
codigo: ['Não iniciado', 'Branch criada', 'PR aberto', 'Mesclado'],
|
|
111
111
|
testes: ['Não iniciados', 'Em execução', 'Aprovados', 'Falhando'],
|
|
112
112
|
deploy: ['Não implantado', 'Dev', 'Staging', 'Produção'],
|
|
113
|
-
exposicao: ['Flag desligada', 'Piloto/
|
|
113
|
+
exposicao: ['Flag desligada', 'Piloto/Gradual', 'Parcial', 'Geral'],
|
|
114
114
|
habilitacao: ['Pendente', 'Em andamento', 'Concluída'],
|
|
115
115
|
};
|
|
116
116
|
exports.CATEGORIAS = ['iniciativa', 'épico', 'feature', 'melhoria'];
|
package/dist/evalrunner.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Ele julga duas coisas, e diz em voz alta que so uma delas ele consegue julgar.
|
|
6
6
|
*
|
|
7
|
-
* 1. **Os
|
|
7
|
+
* 1. **Os cenarios** (`eval/fixtures/fx-*`): comportamento real do nucleo, exercitado ponta a
|
|
8
8
|
* ponta contra git de verdade, comparado com o que a fixture declara esperar.
|
|
9
9
|
* 2. **O corpus das skills** (`eval/casos/*.json`): as regras que precisam EXISTIR no arquivo
|
|
10
10
|
* da skill para o comportamento ser possivel. Isso e deterministico, e e o que o runner
|
|
@@ -60,12 +60,12 @@ exports.posicoesDaAssercao = posicoesDaAssercao;
|
|
|
60
60
|
exports.dirDeCasos = dirDeCasos;
|
|
61
61
|
exports.dirDeFixtures = dirDeFixtures;
|
|
62
62
|
exports.lerCasos = lerCasos;
|
|
63
|
-
exports.
|
|
63
|
+
exports.rodarCenarios = rodarCenarios;
|
|
64
64
|
exports.rodarEval = rodarEval;
|
|
65
65
|
exports.textoDoEval = textoDoEval;
|
|
66
66
|
const fs = __importStar(require("node:fs"));
|
|
67
67
|
const path = __importStar(require("node:path"));
|
|
68
|
-
const
|
|
68
|
+
const cenarios_1 = require("./cenarios");
|
|
69
69
|
const catalogo_1 = require("./catalogo");
|
|
70
70
|
const util_1 = require("./util");
|
|
71
71
|
const locale_1 = require("./locale");
|
|
@@ -271,20 +271,20 @@ function comparar(esperado, observado) {
|
|
|
271
271
|
}
|
|
272
272
|
return divergencias;
|
|
273
273
|
}
|
|
274
|
-
/** Roda os
|
|
275
|
-
function
|
|
274
|
+
/** Roda os cenarios cujas fixtures existem em `eval/fixtures/fx-*`. */
|
|
275
|
+
function rodarCenarios(catalogo, filtro) {
|
|
276
276
|
const dir = dirDeFixtures(catalogo);
|
|
277
277
|
const resultados = [];
|
|
278
278
|
const falhas = [];
|
|
279
279
|
const m = (0, locale_1.msg)().operacao.restante.eval;
|
|
280
|
-
const temGit = (0,
|
|
280
|
+
const temGit = (0, cenarios_1.gitDisponivel)();
|
|
281
281
|
const nomes = fs.existsSync(dir) ? fs.readdirSync(dir).sort() : [];
|
|
282
282
|
if (filtro && filtro.length === 0) {
|
|
283
283
|
falhas.push({ codigo: 'fixture-invalida', detalhe: m.filtroVazio });
|
|
284
284
|
}
|
|
285
285
|
for (const id of new Set(filtro ?? [])) {
|
|
286
|
-
if (!nomes.includes(id) || !(0,
|
|
287
|
-
falhas.push({ codigo: '
|
|
286
|
+
if (!nomes.includes(id) || !(0, cenarios_1.cenarioPorId)(id)) {
|
|
287
|
+
falhas.push({ codigo: 'cenario-sem-executor', caso: id, detalhe: m.cenarioAusente(id) });
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
for (const nome of nomes) {
|
|
@@ -305,22 +305,22 @@ function rodarCanarios(catalogo, filtro) {
|
|
|
305
305
|
falhas.push({ codigo: 'fixture-invalida', detalhe: `${nome}: ${e.message}` });
|
|
306
306
|
continue;
|
|
307
307
|
}
|
|
308
|
-
const
|
|
308
|
+
const cenario = (0, cenarios_1.cenarioPorId)(fixture.id ?? nome);
|
|
309
309
|
if (fixture.id !== nome) {
|
|
310
310
|
falhas.push({ codigo: 'fixture-invalida', caso: nome, detalhe: m.fixtureDiverge });
|
|
311
311
|
continue;
|
|
312
312
|
}
|
|
313
|
-
if (!
|
|
313
|
+
if (!cenario) {
|
|
314
314
|
falhas.push({
|
|
315
|
-
codigo: '
|
|
315
|
+
codigo: 'cenario-sem-executor',
|
|
316
316
|
detalhe: m.semExecutor(nome, fixture.id ?? nome),
|
|
317
317
|
});
|
|
318
318
|
continue;
|
|
319
319
|
}
|
|
320
|
-
if (
|
|
320
|
+
if (cenario.precisaDeGit && !temGit) {
|
|
321
321
|
resultados.push({
|
|
322
|
-
id:
|
|
323
|
-
sobre:
|
|
322
|
+
id: cenario.id,
|
|
323
|
+
sobre: cenario.sobre,
|
|
324
324
|
estado: 'unavailable',
|
|
325
325
|
divergencias: [],
|
|
326
326
|
motivo: m.semGit,
|
|
@@ -329,11 +329,11 @@ function rodarCanarios(catalogo, filtro) {
|
|
|
329
329
|
continue;
|
|
330
330
|
}
|
|
331
331
|
try {
|
|
332
|
-
const observado =
|
|
332
|
+
const observado = cenario.rodar({ catalogo });
|
|
333
333
|
const divergencias = comparar(fixture.esperado ?? {}, observado);
|
|
334
334
|
resultados.push({
|
|
335
|
-
id:
|
|
336
|
-
sobre:
|
|
335
|
+
id: cenario.id,
|
|
336
|
+
sobre: cenario.sobre,
|
|
337
337
|
estado: divergencias.length === 0 ? 'passou' : 'falhou',
|
|
338
338
|
divergencias,
|
|
339
339
|
motivo: divergencias.length === 0 ? '' : `${divergencias.length} chave(s) divergente(s)`,
|
|
@@ -341,33 +341,33 @@ function rodarCanarios(catalogo, filtro) {
|
|
|
341
341
|
});
|
|
342
342
|
for (const d of divergencias) {
|
|
343
343
|
falhas.push({
|
|
344
|
-
codigo: '
|
|
345
|
-
caso: `${
|
|
344
|
+
codigo: 'cenario',
|
|
345
|
+
caso: `${cenario.id}/${d.chave}`,
|
|
346
346
|
detalhe: m.divergencia(JSON.stringify(d.esperado), JSON.stringify(d.observado)),
|
|
347
347
|
});
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
350
|
catch (e) {
|
|
351
351
|
resultados.push({
|
|
352
|
-
id:
|
|
353
|
-
sobre:
|
|
352
|
+
id: cenario.id,
|
|
353
|
+
sobre: cenario.sobre,
|
|
354
354
|
estado: 'falhou',
|
|
355
355
|
divergencias: [],
|
|
356
356
|
motivo: e.message,
|
|
357
357
|
observado: {},
|
|
358
358
|
});
|
|
359
|
-
falhas.push({ codigo: '
|
|
359
|
+
falhas.push({ codigo: 'cenario', caso: cenario.id, detalhe: e.message });
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
|
-
//
|
|
362
|
+
// Cenario registrado no codigo e sem fixture em disco tambem e defeito: a expectativa
|
|
363
363
|
// precisa ser dado versionado, revisavel por quem nao le TypeScript.
|
|
364
364
|
if (!filtro) {
|
|
365
|
-
for (const c of
|
|
365
|
+
for (const c of cenarios_1.CENARIOS) {
|
|
366
366
|
if (!fs.existsSync(path.join(dir, c.id, 'caso.json'))) {
|
|
367
367
|
falhas.push({
|
|
368
368
|
codigo: 'fixture-invalida',
|
|
369
369
|
caso: c.id,
|
|
370
|
-
detalhe: m.
|
|
370
|
+
detalhe: m.cenarioSemFixture(c.id),
|
|
371
371
|
});
|
|
372
372
|
}
|
|
373
373
|
}
|
|
@@ -377,20 +377,20 @@ function rodarCanarios(catalogo, filtro) {
|
|
|
377
377
|
// ---------------------------------------------------------------------------
|
|
378
378
|
// Orquestracao
|
|
379
379
|
// ---------------------------------------------------------------------------
|
|
380
|
-
/** Roda o eval inteiro:
|
|
380
|
+
/** Roda o eval inteiro: cenarios mais corpus das skills. */
|
|
381
381
|
function rodarEval(opcoes) {
|
|
382
382
|
const { catalogo } = opcoes;
|
|
383
383
|
const falhas = [];
|
|
384
384
|
const linhas = [];
|
|
385
|
-
let
|
|
385
|
+
let cenarios = [];
|
|
386
386
|
if (!opcoes.soSkills) {
|
|
387
|
-
const r =
|
|
388
|
-
|
|
387
|
+
const r = rodarCenarios(catalogo, opcoes.cenarios);
|
|
388
|
+
cenarios = r.resultados;
|
|
389
389
|
falhas.push(...r.falhas);
|
|
390
390
|
}
|
|
391
391
|
let totalDeCasos = 0;
|
|
392
392
|
let totalDeAssercoes = 0;
|
|
393
|
-
if (!opcoes.
|
|
393
|
+
if (!opcoes.soCenarios) {
|
|
394
394
|
const { arquivos, falhas: falhasDeLeitura } = lerCasos(catalogo);
|
|
395
395
|
falhas.push(...falhasDeLeitura);
|
|
396
396
|
const doCatalogo = (0, catalogo_1.skillsDoCatalogo)(catalogo);
|
|
@@ -428,7 +428,7 @@ function rodarEval(opcoes) {
|
|
|
428
428
|
return {
|
|
429
429
|
catalogo,
|
|
430
430
|
skills: linhas,
|
|
431
|
-
|
|
431
|
+
cenarios,
|
|
432
432
|
falhas,
|
|
433
433
|
totalDeCasos,
|
|
434
434
|
totalDeAssercoes,
|
|
@@ -442,10 +442,10 @@ function textoDoEval(r) {
|
|
|
442
442
|
const linhas = [];
|
|
443
443
|
linhas.push(m.titulo(r.catalogo));
|
|
444
444
|
linhas.push('');
|
|
445
|
-
if (r.
|
|
446
|
-
linhas.push(m.
|
|
445
|
+
if (r.cenarios.length > 0) {
|
|
446
|
+
linhas.push(m.cenarios);
|
|
447
447
|
linhas.push('');
|
|
448
|
-
linhas.push((0, util_1.tabela)([...m.
|
|
448
|
+
linhas.push((0, util_1.tabela)([...m.colunasCenarios], r.cenarios.map((c) => [c.id, m.estados[c.estado] ?? c.estado, c.estado === 'passou' ? c.sobre : c.motivo || c.sobre])));
|
|
449
449
|
linhas.push('');
|
|
450
450
|
}
|
|
451
451
|
if (r.skills.length > 0) {
|
|
@@ -463,9 +463,9 @@ function textoDoEval(r) {
|
|
|
463
463
|
}
|
|
464
464
|
linhas.push('');
|
|
465
465
|
}
|
|
466
|
-
const passaram = r.
|
|
467
|
-
const indisponiveis = r.
|
|
468
|
-
linhas.push(m.
|
|
466
|
+
const passaram = r.cenarios.filter((c) => c.estado === 'passou').length;
|
|
467
|
+
const indisponiveis = r.cenarios.filter((c) => c.estado === 'unavailable').length;
|
|
468
|
+
linhas.push(m.resumoCenarios(passaram, r.cenarios.length, indisponiveis));
|
|
469
469
|
linhas.push(m.resumoSkills(r.skills.length, r.totalDeCasos, r.totalDeAssercoes, r.falhas.length));
|
|
470
470
|
linhas.push(m.metade);
|
|
471
471
|
linhas.push(m.veredito(r.ok));
|
package/dist/hitl-canais.js
CHANGED
|
@@ -204,7 +204,7 @@ function recusarIngressoNaoHomologado(ingresso) {
|
|
|
204
204
|
}
|
|
205
205
|
return ingresso;
|
|
206
206
|
}
|
|
207
|
-
/** Invariantes do registro. O teste chama isto; o produto tambem, no
|
|
207
|
+
/** Invariantes do registro. O teste chama isto; o produto tambem, no cenario. */
|
|
208
208
|
function conferirRegistro() {
|
|
209
209
|
const canais = canaisHomologados();
|
|
210
210
|
if (canais.length !== hosts_1.ORDEM_DOS_HOSTS.length || canais.length !== 4) {
|
|
@@ -178,7 +178,7 @@ function pedidoHitlAberto(raiz, id, fase, sessionId) {
|
|
|
178
178
|
/**
|
|
179
179
|
* FX6: a oferta de canais que acompanha um pedido HITL REAL.
|
|
180
180
|
*
|
|
181
|
-
* `ofertaDeCanais` existia desde D12 e era chamada de um lugar so: o
|
|
181
|
+
* `ofertaDeCanais` existia desde D12 e era chamada de um lugar so: o cenario. No caminho de
|
|
182
182
|
* producao o humano via a pergunta e um `comandoResposta`, e nada dizia por quais canais dava
|
|
183
183
|
* para responder AGORA nem por que os outros nao serviam. Uma oferta que so aparece em
|
|
184
184
|
* simulacao nao e oferta; e uma tabela bonita, como a propria narrativa de D12 admitia.
|