@orkastery/cli 0.5.1 → 0.5.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/claude-code/README.md +158 -0
- 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/claude-code/mods/autoridade.json +56 -0
- package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
- package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
- package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
- package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
- package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
- package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
- package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
- package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
- package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
- package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
- package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
- package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
- package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
- package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
- package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
- package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
- package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
- package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
- package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
- package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
- package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
- package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
- package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
- package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
- package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
- package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
- package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
- package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
- package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
- package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
- package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
- package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
- package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
- package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
- package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
- package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -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 +7 -6
- package/adapters/openclaw/README.md +26 -5
- 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/docs-en/markdownlint-cli2.jsonc +22 -0
- package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
- package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
- package/assets/docs-en/produto/README.md +14 -0
- package/assets/docs-en/produto/_modelo-feature.md +62 -0
- package/assets/docs-en/roadmap/README.md +14 -0
- package/assets/docs-en/roadmap/_modelo-item.md +75 -0
- package/assets/orkmind_bridge.py +117 -9
- package/dist/adapters/claude-bg.js +116 -18
- package/dist/adapters/codex.js +14 -4
- package/dist/agents-md.js +24 -14
- package/dist/auditrun.js +22 -3
- package/dist/board.js +20 -17
- package/dist/branch-de-estado.js +88 -10
- package/dist/busca-semantica.js +52 -12
- package/dist/canarios-rm057.js +149 -0
- package/dist/canarios.js +2 -0
- package/dist/catalogo.js +33 -0
- package/dist/ci.js +21 -8
- package/dist/claims-conferir.js +342 -0
- package/dist/claims.js +41 -1
- package/dist/company-brain-client.js +36 -1
- package/dist/company-brain-context.js +158 -22
- package/dist/company-brain-journal.js +2 -1
- package/dist/company-brain-migration.js +36 -1
- package/dist/conducao-sentinela.js +388 -0
- package/dist/conducao-texto.js +12 -18
- package/dist/conducao.js +122 -30
- package/dist/conhecimento.js +490 -0
- package/dist/docs.js +70 -17
- package/dist/doctor-pulse-cron.js +349 -0
- package/dist/doctor-rede.js +150 -0
- package/dist/doctor.js +195 -111
- package/dist/entrega-pr.js +42 -29
- package/dist/escopo-escrita.js +10 -1
- package/dist/esgotamentos.js +181 -0
- package/dist/estado-thread.js +23 -1
- package/dist/experiencia-instalacao.js +45 -25
- package/dist/experiencia.js +12 -2
- package/dist/fabrica-batida.js +310 -0
- package/dist/fabrica-estado.js +52 -14
- package/dist/fabrica-publicar.js +9 -1
- package/dist/fechamento.js +20 -66
- package/dist/fila-pesada.js +444 -0
- package/dist/fix.js +3 -2
- package/dist/forja.js +31 -7
- package/dist/gates.js +3 -24
- package/dist/handoff.js +10 -6
- package/dist/hitl-contract.js +59 -4
- package/dist/hitl-curto.js +11 -6
- package/dist/hitl-gates.js +5 -0
- package/dist/hitl-local-receipt.js +2 -0
- package/dist/hitl-lote.js +5 -2
- package/dist/hitl-resumo.js +38 -3
- package/dist/hitl-tempo-parado.js +201 -0
- package/dist/hitl-texto-livre.js +5 -5
- package/dist/hitl.js +29 -33
- package/dist/horario.js +41 -7
- package/dist/hosts.js +104 -11
- package/dist/impedimento.js +299 -0
- package/dist/index.js +961 -542
- package/dist/indice-vetorial.js +87 -10
- package/dist/indice.js +9 -1
- package/dist/init.js +56 -43
- package/dist/instabilidade.js +144 -0
- package/dist/integracoes-locais.js +4 -0
- package/dist/intelligence-graph-cli.js +170 -16
- package/dist/intelligence-graph-contexto.js +254 -0
- package/dist/intelligence-graph-contract.js +2 -1
- package/dist/intelligence-graph-extract-md.js +56 -4
- package/dist/intelligence-graph-extract-ts.js +71 -3
- package/dist/intelligence-graph-parsers.js +6 -4
- package/dist/intelligence-graph-query.js +66 -19
- package/dist/leases.js +664 -87
- package/dist/ledger-stats.js +15 -1
- package/dist/ledger.js +18 -1
- package/dist/licoes.js +1 -0
- package/dist/liveness.js +6 -3
- package/dist/locale-ajuda.js +761 -0
- package/dist/locale-gerados.js +160 -0
- package/dist/locale-master.js +99 -0
- package/dist/locale-onboarding.js +92 -0
- package/dist/locale-phase.js +151 -0
- package/dist/locale-pulse.js +211 -0
- package/dist/locale-retry.js +385 -0
- package/dist/locale-roadmap.js +102 -0
- package/dist/locale-ship.js +494 -0
- package/dist/locale-thread.js +277 -0
- package/dist/locale-verify.js +125 -0
- package/dist/locale.js +714 -0
- package/dist/maestro-runtime.js +4 -1
- package/dist/manifest.js +149 -4
- package/dist/maquina.js +23 -4
- package/dist/master-digest.js +6 -1
- package/dist/master-nota.js +18 -11
- package/dist/master.js +145 -31
- package/dist/mcp-artifacts.js +43 -3
- package/dist/mcp-git.js +42 -8
- package/dist/mcp-grafo-worker.js +5 -3
- package/dist/mcp-grafo.js +55 -6
- package/dist/mcp-server.js +22 -3
- package/dist/mcp-ship.js +3 -1
- package/dist/mcp-verify.js +6 -1
- package/dist/memoria.js +48 -6
- package/dist/modos.js +3 -6
- package/dist/network-roadmap.js +196 -18
- package/dist/noctum-arco.js +911 -0
- package/dist/noctum-capacidade.js +141 -0
- package/dist/noctum-envelope.js +810 -0
- package/dist/noctum-execucao.js +304 -0
- package/dist/noctum-fila.js +280 -0
- package/dist/ocupacao.js +40 -5
- package/dist/onboarding.js +40 -21
- package/dist/orkmind.js +99 -7
- package/dist/orquestracao.js +24 -21
- package/dist/parado-no-condutor.js +219 -38
- package/dist/phase.js +160 -52
- package/dist/policies.js +56 -5
- package/dist/portfolio.js +8 -3
- package/dist/postura-local.js +234 -0
- package/dist/preflight.js +11 -10
- package/dist/procedencia.js +225 -0
- package/dist/projeto-alvo.js +132 -5
- package/dist/prompts.js +40 -1
- package/dist/prova-ativacao.js +75 -6
- package/dist/pulse-delivery.js +47 -4
- package/dist/pulse.js +98 -19
- package/dist/ratelimit.js +24 -6
- package/dist/recall.js +3 -1
- package/dist/redacao-saida.js +63 -0
- 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 +337 -0
- package/dist/rede.js +980 -0
- package/dist/retry.js +291 -140
- package/dist/roadmap-reservas.js +20 -3
- package/dist/roadmap-status.js +150 -34
- package/dist/runtime-context.js +181 -3
- package/dist/runtime-profiles.js +22 -1
- package/dist/runtimes.js +2 -1
- package/dist/saida-segura.js +58 -0
- package/dist/session-events.js +7 -3
- package/dist/session-watcher-claude.js +120 -24
- package/dist/session-watcher.js +185 -33
- package/dist/sessoes-contas.js +187 -0
- package/dist/sessoes-inventario.js +6 -18
- package/dist/sessoes.js +83 -2
- package/dist/ship-publicar.js +800 -0
- package/dist/ship.js +157 -88
- package/dist/steal.js +77 -0
- package/dist/thread-close.js +3 -2
- package/dist/thread.js +299 -49
- package/dist/util.js +8 -1
- package/dist/verify.js +141 -52
- package/dist/vida-da-thread.js +254 -0
- package/dist/worktree-local.js +165 -0
- package/dist/worktree.js +25 -2
- package/dist/yaml.js +27 -8
- package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
- package/package.json +7 -3
- package/references/definition-of-done.md +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +2 -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,304 @@
|
|
|
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.ESPERA_DA_VEZ_DE_DESPACHAR_MS = void 0;
|
|
37
|
+
exports.passoDoArco = passoDoArco;
|
|
38
|
+
/**
|
|
39
|
+
* RM-058, fatia N2 (D11 da ork-rm058n2): o passo do arco.
|
|
40
|
+
*
|
|
41
|
+
* Um despacho por chamada, sempre pelo núcleo. Sob a trava do kernel de despacho da raiz de estado (um despachante por
|
|
42
|
+
* vez, entre todos os arcos desta raiz), o passo relê o arco e os fatos, monta a fila, escolhe o primeiro candidato que
|
|
43
|
+
* a capacidade admite, reserva o despacho com a conferência do envelope sob o lock do arco (`reservarDespacho`, que é a
|
|
44
|
+
* conferência imediatamente antes do efeito), abre a thread quando o trabalho é novo (`novaThread`) e despacha por
|
|
45
|
+
* `rodarFase`, que toma o lease de condução e é idempotente para a mesma fase e o mesmo prompt. O desfecho volta ao log do
|
|
46
|
+
* arco (`concluirDespacho`): a recusa do núcleo devolve a sessão ao orçamento.
|
|
47
|
+
*
|
|
48
|
+
* A baseline que o bloco com GO no codex pede nunca roda entre a reserva e o lançamento (A1 do CHECK da ork-rm058n2): o
|
|
49
|
+
* despacho recusa com `baseline.pendente`, a reserva volta, a trava de despacho é solta e só então a baseline é gravada;
|
|
50
|
+
* o passo seguinte confere o envelope de novo e despacha. A falha de um candidato antes de lançar (abrir a thread, ler o
|
|
51
|
+
* pedido) devolve a reserva e o passo segue para o próximo (A4). Do núcleo, só a recusa de antes do lançamento devolve a
|
|
52
|
+
* reserva: a recusa dos portões, e a da conta, do limite de uso ou do impedimento do dono quando nenhuma sessão foi dita.
|
|
53
|
+
* Quando o runtime pode ter aberto a sessão (a falha seca dele, o id dito antes da falha, o núcleo que voltou sem sessão
|
|
54
|
+
* nem recusa, ou a exceção de dentro do `rodarFase`), a reserva fica pendente e contada até a N4 reconciliar (B1 da rodada
|
|
55
|
+
* 2). A recusa tipada do arco, inclusive a do log adulterado, volta como resultado.
|
|
56
|
+
*
|
|
57
|
+
* A revogação nunca espera um despacho: ela usa o lock do arco, e a trava de despacho é outra. Quem chama decide quando
|
|
58
|
+
* chamar de novo (o vigia é da N4); a decisão da fila diz até quando esperar. Nada aqui liga o modo.
|
|
59
|
+
*/
|
|
60
|
+
const fs = __importStar(require("node:fs"));
|
|
61
|
+
const path = __importStar(require("node:path"));
|
|
62
|
+
const board_1 = require("./board");
|
|
63
|
+
const conducao_1 = require("./conducao");
|
|
64
|
+
const ledger_1 = require("./ledger");
|
|
65
|
+
const estado_thread_1 = require("./estado-thread");
|
|
66
|
+
const fila_pesada_1 = require("./fila-pesada");
|
|
67
|
+
const noctum_arco_1 = require("./noctum-arco");
|
|
68
|
+
const noctum_capacidade_1 = require("./noctum-capacidade");
|
|
69
|
+
const noctum_envelope_1 = require("./noctum-envelope");
|
|
70
|
+
const noctum_fila_1 = require("./noctum-fila");
|
|
71
|
+
const phase_1 = require("./phase");
|
|
72
|
+
const runtime_profiles_1 = require("./runtime-profiles");
|
|
73
|
+
const setup_1 = require("./setup");
|
|
74
|
+
const thread_1 = require("./thread");
|
|
75
|
+
const verify_1 = require("./verify");
|
|
76
|
+
/** Quanto o passo espera a vez de despachar antes de devolver `aguardar`: o despacho de outro arco leva segundos. */
|
|
77
|
+
exports.ESPERA_DA_VEZ_DE_DESPACHAR_MS = 30_000;
|
|
78
|
+
const PAUSA_MS = 200;
|
|
79
|
+
const pausa = new Int32Array(new SharedArrayBuffer(4));
|
|
80
|
+
const dormir = (ms) => { if (ms > 0)
|
|
81
|
+
Atomics.wait(pausa, 0, 0, Math.ceil(ms)); };
|
|
82
|
+
/** A trava de despacho da raiz de estado: o descritor, ou `null` quando outro passo não a soltou no prazo. */
|
|
83
|
+
function vezDeDespachar(raiz, esperarMs) {
|
|
84
|
+
const principal = (0, estado_thread_1.raizDoEstado)(raiz);
|
|
85
|
+
(0, estado_thread_1.exigirEstadoSemLink)(principal, ['.orkastery', 'noctum']);
|
|
86
|
+
const dir = path.join(principal, '.orkastery', 'noctum');
|
|
87
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
88
|
+
const prazo = Date.now() + esperarMs;
|
|
89
|
+
for (;;) {
|
|
90
|
+
const fd = (0, fila_pesada_1.tentarTravaExclusiva)(path.join(dir, 'despacho.lock'));
|
|
91
|
+
if (fd !== null)
|
|
92
|
+
return fd;
|
|
93
|
+
if (Date.now() >= prazo)
|
|
94
|
+
return null;
|
|
95
|
+
dormir(Math.min(PAUSA_MS, prazo - Date.now()));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** O runtime que o `phase run` vai usar para a fase desta thread: o do bloco no setup, senão o do manifesto. */
|
|
99
|
+
function runtimeDaFase(carregado, modo, fase) {
|
|
100
|
+
return (0, phase_1.resolverDespacho)(carregado.manifesto, {}, (0, setup_1.configDoBloco)((0, setup_1.lerSetup)(carregado.raiz), modo, fase)).runtime;
|
|
101
|
+
}
|
|
102
|
+
/** Os perfis do store que podem receber despacho agora; store ilegível é nenhum perfil (a fila espera, não chuta). */
|
|
103
|
+
function perfisDisponiveis(raiz, agoraMs) {
|
|
104
|
+
try {
|
|
105
|
+
return (0, runtime_profiles_1.lerPerfis)(raiz).perfis.filter(p => (0, runtime_profiles_1.perfilDisponivel)(p, agoraMs)).map(p => ({ id: p.id, runtime: p.runtime }));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const aguardar = (decisao, fila) => ({ acao: 'aguardar', ate: decisao.ate, motivo: decisao.motivo, detalhe: fila.lacunas.join('; '), fila });
|
|
112
|
+
/** O motivo que sobra quando nenhum candidato coube: a máquina cheia vale para todos, e o resto é do último tentado. */
|
|
113
|
+
const MOTIVOS_DE_MAQUINA = ['capacidade.fonte-indisponivel', 'capacidade.projeto', 'capacidade.maquina'];
|
|
114
|
+
/** As recusas da reserva que são só deste candidato (A4): a janela, a revogação, o hash e o log valem para todos. */
|
|
115
|
+
const RECUSAS_DO_CANDIDATO = ['noctum.despacho.fora-do-envelope', 'noctum.orcamento.esgotado'];
|
|
116
|
+
const mensagem = (e) => (e instanceof Error ? e.message : String(e)).slice(0, 500);
|
|
117
|
+
/** Os eventos do ledger da thread, ou `null` quando ele não se lê. */
|
|
118
|
+
function eventosDaThread(raiz, threadId) {
|
|
119
|
+
try {
|
|
120
|
+
return (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, threadId));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Um passo do arco: no máximo um despacho, pelo núcleo, com o envelope conferido imediatamente antes. Lança só o erro
|
|
128
|
+
* que não é recusa (disco, git); toda recusa tipada volta como resultado.
|
|
129
|
+
*/
|
|
130
|
+
function passoDoArco(carregado, arco, opcoes = {}) {
|
|
131
|
+
const quando = opcoes.quando ?? new Date().toISOString();
|
|
132
|
+
const { raiz } = carregado;
|
|
133
|
+
const cadencia = new Date(Date.parse(quando) + 60_000).toISOString();
|
|
134
|
+
if (!(0, fila_pesada_1.kernelDisponivel)()) {
|
|
135
|
+
return { acao: 'aguardar', ate: cadencia, motivo: 'sem-trava-do-kernel', fila: null,
|
|
136
|
+
detalhe: 'o arco só despacha com /usr/bin/flock: sem a trava do kernel, dois passos poderiam passar juntos dos limites' };
|
|
137
|
+
}
|
|
138
|
+
const fd = vezDeDespachar(raiz, opcoes.esperarVezMs ?? exports.ESPERA_DA_VEZ_DE_DESPACHAR_MS);
|
|
139
|
+
if (fd === null) {
|
|
140
|
+
return { acao: 'aguardar', ate: cadencia, motivo: 'despacho-em-curso', fila: null,
|
|
141
|
+
detalhe: 'outro passo de arco desta máquina segura a vez de despachar' };
|
|
142
|
+
}
|
|
143
|
+
let pendente = null;
|
|
144
|
+
try {
|
|
145
|
+
let fatos;
|
|
146
|
+
try {
|
|
147
|
+
fatos = (0, noctum_fila_1.fatosDaFila)(carregado, arco, { quando, maquina: opcoes.maquina, lerReservas: opcoes.lerReservas });
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
// S6 do CHECK: a recusa tipada do arco (o log adulterado, por exemplo) volta como resultado, e não como exceção.
|
|
151
|
+
if (!(e instanceof noctum_envelope_1.RecusaDoArco))
|
|
152
|
+
throw e;
|
|
153
|
+
return { acao: 'aguardar', ate: cadencia, motivo: e.motivo, detalhe: e.message, fila: null };
|
|
154
|
+
}
|
|
155
|
+
const fila = (0, noctum_fila_1.montarFila)(fatos);
|
|
156
|
+
if (fila.decisao.acao === 'encerrar')
|
|
157
|
+
return { acao: 'encerrar', motivo: fila.decisao.motivo, fila };
|
|
158
|
+
if (fila.decisao.acao === 'aguardar')
|
|
159
|
+
return aguardar(fila.decisao, fila);
|
|
160
|
+
// A fila só decide despachar com o envelope v2 vigente; a guarda estreita o tipo e cobre o impossível.
|
|
161
|
+
const conteudo = fatos.estado?.conteudo;
|
|
162
|
+
if (!conteudo || !(0, noctum_envelope_1.envelopeComTrabalhos)(conteudo)) {
|
|
163
|
+
return { acao: 'aguardar', ate: cadencia, motivo: 'fonte-indisponivel', detalhe: 'o arco não tem envelope v2 vigente', fila };
|
|
164
|
+
}
|
|
165
|
+
const capacidade = (0, noctum_capacidade_1.fatosDeCapacidade)(carregado, { quando, consultar: opcoes.consultarSessoes });
|
|
166
|
+
const perfis = (opcoes.perfis ?? (() => perfisDisponiveis(raiz, Date.parse(quando))))();
|
|
167
|
+
let recusa = null;
|
|
168
|
+
let doCandidato = null;
|
|
169
|
+
for (const candidato of fila.candidatos) {
|
|
170
|
+
const fase = candidato.fase;
|
|
171
|
+
let thread, runtime;
|
|
172
|
+
try {
|
|
173
|
+
thread = candidato.thread ? (0, thread_1.lerThread)(raiz, candidato.thread) : null;
|
|
174
|
+
runtime = runtimeDaFase(carregado, thread?.modo ?? 'auto', fase);
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
// A4 do CHECK: a thread que não se lê ou o bloco sem runtime são deste candidato; os outros seguem.
|
|
178
|
+
doCandidato = { acao: 'recusado', trabalho: candidato.id, thread: candidato.thread, fase, motivo: noctum_fila_1.MOTIVO_DO_ERRO_NO_DESPACHO,
|
|
179
|
+
detalhe: mensagem(e), reserva: null, fila };
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const doRuntime = perfis.filter(p => p.runtime === runtime).map(p => p.id);
|
|
183
|
+
// A conta com mais folga; sem folga em nenhuma, a primeira do envelope no runtime, para `admitir` dizer o motivo na
|
|
184
|
+
// ordem dele (a máquina cheia antes da conta).
|
|
185
|
+
const conta = (0, noctum_capacidade_1.escolherConta)(conteudo.capacidade, capacidade, doRuntime)
|
|
186
|
+
?? conteudo.capacidade.porConta.find(c => doRuntime.includes(c.conta))?.conta ?? null;
|
|
187
|
+
const vaga = (0, board_1.vagaDoDespacho)(carregado, thread?.id ?? '', quando);
|
|
188
|
+
const admissao = conta === null
|
|
189
|
+
? { ok: false, motivo: 'capacidade.conta-nao-autorizada',
|
|
190
|
+
detalhe: `nenhuma conta do envelope é perfil disponível do runtime ${runtime}` }
|
|
191
|
+
: (0, noctum_capacidade_1.admitir)({ runtime, conta, revisao: fase === 'CHECK' }, conteudo.capacidade, capacidade, vaga);
|
|
192
|
+
if (!admissao.ok) {
|
|
193
|
+
recusa = admissao;
|
|
194
|
+
if (MOTIVOS_DE_MAQUINA.includes(admissao.motivo))
|
|
195
|
+
break;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const r = despacharCandidato(carregado, arco, fila, { trabalho: candidato.id, thread, item: candidato.item, fase, runtime,
|
|
199
|
+
conta: conta, quando, pedido: conteudo.trabalhos.find(t => t.id === candidato.id)?.pedido ?? null }, opcoes);
|
|
200
|
+
if (r.acao === 'baseline') {
|
|
201
|
+
pendente = r;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
if ('seguir' in r) {
|
|
205
|
+
const { seguir: _seguir, ...recusado } = r;
|
|
206
|
+
doCandidato = recusado;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
return r;
|
|
210
|
+
}
|
|
211
|
+
if (!pendente) {
|
|
212
|
+
// Sem vaga para ninguém, espera a vaga; sem vaga por erro de candidato, diz o erro.
|
|
213
|
+
if (doCandidato && !recusa)
|
|
214
|
+
return doCandidato;
|
|
215
|
+
return { acao: 'aguardar', ate: new Date(Math.min(Date.parse(quando) + 5 * 60_000, Date.parse(conteudo.janela.fim))).toISOString(),
|
|
216
|
+
motivo: recusa?.motivo ?? 'capacidade', detalhe: recusa?.detalhe ?? 'nenhum candidato coube', fila };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
fs.closeSync(fd);
|
|
221
|
+
}
|
|
222
|
+
return baselineForaDaTrava(carregado, arco, pendente, cadencia, opcoes);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* A1 do CHECK: a baseline que o despacho pediu, com a trava de despacho já solta (os outros arcos não esperam a suíte).
|
|
226
|
+
* Ela espera a fila de recursos pesados como todo verify; sem a vez, ou com a thread conduzida, o passo espera a cadência.
|
|
227
|
+
*/
|
|
228
|
+
function baselineForaDaTrava(carregado, arco, p, cadencia, opcoes) {
|
|
229
|
+
const conducao = { canal: (0, conducao_1.canalDoProcesso)(), correlacao: `noctum:${arco}` };
|
|
230
|
+
try {
|
|
231
|
+
(opcoes.gravarBaseline ?? ((k, t, c) => { (0, verify_1.gravarBaseline)(k, t, undefined, c, 'phase.run'); }))(carregado, p.thread, conducao);
|
|
232
|
+
}
|
|
233
|
+
catch (e) {
|
|
234
|
+
if (!(e instanceof fila_pesada_1.ErroDaFilaPesada) && !(e instanceof conducao_1.ErroDeConducao))
|
|
235
|
+
throw e;
|
|
236
|
+
return { acao: 'aguardar', ate: cadencia, motivo: 'baseline-pendente', detalhe: mensagem(e), fila: p.fila };
|
|
237
|
+
}
|
|
238
|
+
return { acao: 'aguardar', ate: new Date().toISOString(), motivo: 'baseline-gravada', fila: p.fila,
|
|
239
|
+
detalhe: `a baseline da thread ${p.thread} foi gravada fora da trava de despacho; o próximo passo confere o envelope e despacha` };
|
|
240
|
+
}
|
|
241
|
+
/** Reserva, abre a thread quando falta e despacha pelo núcleo; o desfecho volta ao log do arco. */
|
|
242
|
+
function despacharCandidato(carregado, arco, fila, c, opcoes) {
|
|
243
|
+
const { raiz } = carregado;
|
|
244
|
+
let reserva;
|
|
245
|
+
try {
|
|
246
|
+
reserva = (opcoes.reservar ?? noctum_arco_1.reservarDespacho)(raiz, { arco, envelopeSha256: fila.envelopeSha256 ?? '', trabalho: c.trabalho,
|
|
247
|
+
thread: c.thread?.id ?? null, fase: c.fase, runtime: c.runtime, conta: c.conta, revisao: c.fase === 'CHECK' }, c.quando).seq;
|
|
248
|
+
}
|
|
249
|
+
catch (e) {
|
|
250
|
+
if (!(e instanceof noctum_envelope_1.RecusaDoArco))
|
|
251
|
+
throw e;
|
|
252
|
+
const r = { acao: 'recusado', trabalho: c.trabalho, thread: c.thread?.id ?? null, fase: c.fase, motivo: e.motivo,
|
|
253
|
+
detalhe: e.message, reserva: null, fila };
|
|
254
|
+
return RECUSAS_DO_CANDIDATO.includes(e.motivo) ? { ...r, seguir: true } : r;
|
|
255
|
+
}
|
|
256
|
+
const recusar = (motivo, detalhe, thread) => {
|
|
257
|
+
(0, noctum_arco_1.concluirDespacho)(raiz, arco, reserva, { motivo });
|
|
258
|
+
return { acao: 'recusado', trabalho: c.trabalho, thread, fase: c.fase, motivo, detalhe, reserva, fila };
|
|
259
|
+
};
|
|
260
|
+
let thread = c.thread;
|
|
261
|
+
let pedido;
|
|
262
|
+
try {
|
|
263
|
+
if (!thread) {
|
|
264
|
+
if (!c.pedido)
|
|
265
|
+
return { ...recusar('noctum.despacho.pedido-ausente', `o trabalho ${c.trabalho} não traz pedido para abrir a thread`, null), seguir: true };
|
|
266
|
+
thread = (opcoes.abrirThread ?? ((k, o) => (0, thread_1.novaThread)(k, o).thread))(carregado, { nome: `${c.item.toLowerCase()} ${c.trabalho}`,
|
|
267
|
+
modo: 'auto', roadmap: c.item, pedido: c.pedido, criarWorktree: true });
|
|
268
|
+
}
|
|
269
|
+
pedido = (0, thread_1.pedidoDaThread)(raiz, thread);
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
// A4 do CHECK: a falha antes de lançar (abrir a thread, ler o pedido) é deste candidato: a reserva volta e o passo segue.
|
|
273
|
+
return { ...recusar(noctum_fila_1.MOTIVO_DO_ERRO_NO_DESPACHO, mensagem(e), thread?.id ?? null), seguir: true };
|
|
274
|
+
}
|
|
275
|
+
if (!pedido)
|
|
276
|
+
return { ...recusar('noctum.despacho.pedido-ausente', `a thread ${thread.id} não guardou o pedido`, thread.id), seguir: true };
|
|
277
|
+
// S5 do CHECK: a falha de dentro do núcleo pode ter aberto a sessão; ela sobe, e a reserva fica pendente e conta.
|
|
278
|
+
const antes = eventosDaThread(raiz, thread.id)?.length ?? 0;
|
|
279
|
+
const r = (opcoes.despachar ?? phase_1.rodarFase)(carregado, thread.id, { fase: c.fase, prompt: pedido.texto, perfil: c.conta,
|
|
280
|
+
canal: (0, conducao_1.canalDoProcesso)(), correlacao: `noctum:${arco}`, baselinePeloDespacho: false });
|
|
281
|
+
if (r.motivo === 'baseline.pendente') {
|
|
282
|
+
(0, noctum_arco_1.concluirDespacho)(raiz, arco, reserva, { motivo: 'baseline.pendente' });
|
|
283
|
+
return { acao: 'baseline', thread: thread.id, fila };
|
|
284
|
+
}
|
|
285
|
+
// A recusa dos portões do núcleo vem antes de chamar o runtime: nada abriu, e a sessão volta ao orçamento.
|
|
286
|
+
if (r.bloqueado)
|
|
287
|
+
return recusar(String(r.motivo ?? 'despacho.bloqueado'), r.erro ?? 'o núcleo recusou o despacho', thread.id);
|
|
288
|
+
if (r.sessionId || r.idempotente) {
|
|
289
|
+
(0, noctum_arco_1.concluirDespacho)(raiz, arco, reserva, { thread: thread.id, sessionId: r.sessionId, idempotente: r.idempotente === true });
|
|
290
|
+
return { acao: 'despachou', trabalho: c.trabalho, thread: thread.id, fase: c.fase, runtime: c.runtime, conta: c.conta,
|
|
291
|
+
sessionId: r.sessionId, idempotente: r.idempotente === true, reserva, fila };
|
|
292
|
+
}
|
|
293
|
+
// B1 da rodada 2: o núcleo chamou o runtime e voltou sem sessão. A conta, o limite de uso e o impedimento do dono recusam
|
|
294
|
+
// antes de rodar, e devolvem a sessão; mas a falha seca do runtime, o id dito antes da falha (o `claude --bg` que diz o id
|
|
295
|
+
// e sai com erro deixa a sessão aberta) e o núcleo que não diz nada deixam a reserva pendente e contada.
|
|
296
|
+
const novos = eventosDaThread(raiz, thread.id);
|
|
297
|
+
const idDaFalha = novos === null ? '(ledger ilegível)' : novos.slice(antes)
|
|
298
|
+
.filter(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.despachoFalhou && typeof e.sessionId === 'string').map(e => e.sessionId).at(-1) ?? null;
|
|
299
|
+
if (!idDaFalha && r.motivo && r.motivo !== 'runtime.unavailable')
|
|
300
|
+
return recusar(String(r.motivo), r.erro ?? 'o runtime recusou o despacho', thread.id);
|
|
301
|
+
return { acao: 'recusado', trabalho: c.trabalho, thread: thread.id, fase: c.fase, motivo: String(r.motivo ?? 'despacho.sem-sessao'),
|
|
302
|
+
detalhe: `${r.erro ?? 'o núcleo não abriu sessão'}; a sessão pode ter aberto${idDaFalha ? ` (${idDaFalha})` : ''}, e a reserva fica ` +
|
|
303
|
+
'pendente e contada até a N4 reconciliar', reserva, fila };
|
|
304
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MOTIVO_DO_ERRO_NO_DESPACHO = exports.ERROS_SEGUIDOS_QUE_BLOQUEIAM = exports.BACKOFF_MAXIMO_MS = exports.CADENCIA_MS = exports.CONTRATO_FILA = void 0;
|
|
4
|
+
exports.montarFila = montarFila;
|
|
5
|
+
exports.fatosDaFila = fatosDaFila;
|
|
6
|
+
/**
|
|
7
|
+
* RM-058, fatia N2 (D2 a D6 da ork-rm058n2): a fila do arco.
|
|
8
|
+
*
|
|
9
|
+
* O que entra: os trabalhos do envelope `v2` vigente, cada um uma thread (a que já existe, ou a que o arco abre com o
|
|
10
|
+
* pedido do dono), com o item reservado por esta máquina e trabalho possível. A ordem: só com as dependências entregues,
|
|
11
|
+
* depois a prioridade do dono, a prova mais adiantada (a fase a despachar mais adiante primeiro; abrir thread por último),
|
|
12
|
+
* a posição no envelope e o id. O passo de cada thread aberta é o da Sentinela (`passoDaThread`, pela prova mais adiantada
|
|
13
|
+
* que a sessão do bloco deixou): o bloco inteiro numa sessão só, cujo `phase_result` sai com a fase do despacho, não ganha
|
|
14
|
+
* um PLAN repetido (aprendizado 3 de 12/10).
|
|
15
|
+
*
|
|
16
|
+
* Quando a fila acaba: vazia (todo trabalho entregue, encerrado ou preso a uma dependência que fechou sem entrega)
|
|
17
|
+
* encerra cedo; só bloqueada, ou com fonte que não foi lida, espera com backoff até o fim da janela; com sessão em curso,
|
|
18
|
+
* espera a cadência de 5 min. Lacuna nunca vira fila vazia. O trabalho cujo despacho falhou no núcleo, antes de lançar,
|
|
19
|
+
* três vezes seguidas fica bloqueado (`erro-repetido`), e o que pode ter uma sessão aberta (reserva pendente no log do arco,
|
|
20
|
+
* ou thread nunca despachada cuja falha disse o id da sessão) vai para a retomada da N4: o passo não insiste no mesmo erro a
|
|
21
|
+
* noite toda nem abre outra sessão por cima de uma que pode estar aberta. A falha que não abriu nada (a conta sem cota, sem
|
|
22
|
+
* id) deixa o trabalho na fila, e o despacho seguinte sai por outra conta. Fechar o arco e o relatório são da N6.
|
|
23
|
+
*
|
|
24
|
+
* `fatosDaFila` relê o disco a cada chamada; `montarFila` é pura sobre os fatos e o instante. Nada aqui despacha nem grava.
|
|
25
|
+
*/
|
|
26
|
+
const roadmap_reservas_1 = require("./roadmap-reservas");
|
|
27
|
+
const branch_de_estado_1 = require("./branch-de-estado");
|
|
28
|
+
const parado_no_condutor_1 = require("./parado-no-condutor");
|
|
29
|
+
const conducao_sentinela_1 = require("./conducao-sentinela");
|
|
30
|
+
const ledger_1 = require("./ledger");
|
|
31
|
+
const maquina_1 = require("./maquina");
|
|
32
|
+
const noctum_arco_1 = require("./noctum-arco");
|
|
33
|
+
const noctum_envelope_1 = require("./noctum-envelope");
|
|
34
|
+
const thread_1 = require("./thread");
|
|
35
|
+
const types_1 = require("./types");
|
|
36
|
+
exports.CONTRATO_FILA = 'ork.noctum-fila/v1';
|
|
37
|
+
/** A cadência do vigia (D3 do dono): a espera da fila em curso e o menor passo do backoff. */
|
|
38
|
+
exports.CADENCIA_MS = 5 * 60 * 1000;
|
|
39
|
+
/** O maior passo do backoff da fila bloqueada. */
|
|
40
|
+
exports.BACKOFF_MAXIMO_MS = 60 * 60 * 1000;
|
|
41
|
+
/** Quantas falhas seguidas do núcleo no despacho de um trabalho o bloqueiam (A4 do CHECK da ork-rm058n2). */
|
|
42
|
+
exports.ERROS_SEGUIDOS_QUE_BLOQUEIAM = 3;
|
|
43
|
+
/** O motivo que o passo grava no log do arco quando o despacho de um candidato falha antes de lançar a sessão. */
|
|
44
|
+
exports.MOTIVO_DO_ERRO_NO_DESPACHO = 'noctum.despacho.erro';
|
|
45
|
+
/** Bloqueio que vem de fonte não lida, e não de um fato: vira lacuna, nunca trabalho a menos. */
|
|
46
|
+
const DE_LACUNA = ['reserva-sem-leitura', 'thread-ilegivel', 'thread-ambigua'];
|
|
47
|
+
const adiante = (t) => t.estado === 'abrir' ? -1 : t.fase ? types_1.FASES.indexOf(t.fase) : -1;
|
|
48
|
+
/** D3: prioridade do dono, prova mais adiantada, posição no envelope e id. */
|
|
49
|
+
function antes(a, b) {
|
|
50
|
+
return a.prioridade - b.prioridade || adiante(b) - adiante(a) || a.posicao - b.posicao || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
51
|
+
}
|
|
52
|
+
const FASES_DA_ENTREGA = ['SHIP', 'MASTER'];
|
|
53
|
+
/** As falhas seguidas do núcleo antes de lançar, nos despachos mais recentes do trabalho, contadas do último para trás. */
|
|
54
|
+
function errosSeguidos(trabalho, despachos) {
|
|
55
|
+
let n = 0;
|
|
56
|
+
for (const d of despachos.filter(x => x.trabalho === trabalho).sort((a, b) => b.seq - a.seq)) {
|
|
57
|
+
if (d.desfecho !== 'recusado' || d.motivo !== exports.MOTIVO_DO_ERRO_NO_DESPACHO)
|
|
58
|
+
break;
|
|
59
|
+
n += 1;
|
|
60
|
+
}
|
|
61
|
+
return n;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* O estado de um trabalho do envelope pelos fatos; `entregues` são os ids já entregues (para as dependências) e
|
|
65
|
+
* `semSaida` os que nunca mais entregam neste arco (fecharam sem entrega, ou dependem de quem fechou).
|
|
66
|
+
*/
|
|
67
|
+
function classificar(t, posicao, f, fatos, entregues, semSaida, estado) {
|
|
68
|
+
const decisoes = estado.decisoes;
|
|
69
|
+
const base = { id: t.id, item: t.item, prioridade: t.prioridade, posicao, thread: null, fase: null,
|
|
70
|
+
motivo: null, entregueEm: null };
|
|
71
|
+
const bloqueado = (motivo, detalhe) => ({ ...base, estado: 'bloqueado', motivo, detalhe });
|
|
72
|
+
if (!f)
|
|
73
|
+
return bloqueado('thread-ilegivel', 'o trabalho não foi lido');
|
|
74
|
+
if (f.ilegivel)
|
|
75
|
+
return bloqueado('thread-ilegivel', `a thread ${f.ilegivel} não pôde ser lida`);
|
|
76
|
+
if (f.threads.length > 1) {
|
|
77
|
+
return bloqueado('thread-ambigua', `${f.threads.map(x => x.id).join(', ')} casam com o item e o pedido; o arco não escolhe por ninguém`);
|
|
78
|
+
}
|
|
79
|
+
const th = f.threads[0] ?? null;
|
|
80
|
+
base.thread = th?.id ?? null;
|
|
81
|
+
if (t.thread !== undefined && !th)
|
|
82
|
+
return bloqueado('thread-ausente', `a thread ${t.thread} não existe neste projeto`);
|
|
83
|
+
if (th && th.roadmap !== t.item)
|
|
84
|
+
return bloqueado('item-divergente', `a thread ${th.id} é do item ${th.roadmap ?? 'nenhum'}, não do ${t.item}`);
|
|
85
|
+
if (th && f.entregueEm)
|
|
86
|
+
return { ...base, estado: 'entregue', entregueEm: f.entregueEm, detalhe: `ship_done da thread ${th.id}` };
|
|
87
|
+
if (th && th.status === 'fechada')
|
|
88
|
+
return { ...base, estado: 'encerrado-sem-entrega', detalhe: `a thread ${th.id} fechou sem ship_done` };
|
|
89
|
+
if (th && f.passo?.conduzida)
|
|
90
|
+
return { ...base, estado: 'em-curso', detalhe: `a thread ${th.id} está sendo conduzida agora` };
|
|
91
|
+
const mortas = t.dependeDe.filter(d => semSaida.has(d));
|
|
92
|
+
if (mortas.length) {
|
|
93
|
+
return bloqueado('dependencia-encerrada', `depende de ${mortas.join(', ')}, que não entrega mais neste arco (fechou sem entrega)`);
|
|
94
|
+
}
|
|
95
|
+
const faltam = t.dependeDe.filter(d => !entregues.has(d));
|
|
96
|
+
if (faltam.length)
|
|
97
|
+
return bloqueado('dependencia', `depende de ${faltam.join(', ')}, ainda não entregue(s)`);
|
|
98
|
+
if (fatos.reservas.leitura === 'sem-copia')
|
|
99
|
+
return bloqueado('reserva-sem-leitura', 'esta máquina não tem cópia da branch de reservas');
|
|
100
|
+
const reserva = fatos.reservas.porItem.get(t.item);
|
|
101
|
+
if (!reserva)
|
|
102
|
+
return bloqueado('reserva-ausente', `o item ${t.item} não está reservado; tomar reserva é da N3`);
|
|
103
|
+
if (reserva.maquina !== fatos.maquina)
|
|
104
|
+
return bloqueado('reserva-de-outra-maquina', `o item ${t.item} está com a máquina ${reserva.maquina}`);
|
|
105
|
+
if (th && decisoes.some(d => d.thread === th.id || d.dependentes.includes(th.id))) {
|
|
106
|
+
return bloqueado('decisao-no-lote', `a thread ${th.id} espera uma decisão do dono no lote do arco`);
|
|
107
|
+
}
|
|
108
|
+
// B1 da rodada 2 e A1 da rodada 3: a reserva pendente é um lançamento incerto; despachar de novo abriria outra sessão.
|
|
109
|
+
if (estado.despachos.some(d => d.trabalho === t.id && d.desfecho === 'pendente')) {
|
|
110
|
+
return bloqueado('retomada', 'um despacho deste trabalho ficou pendente, e o runtime pode ter aberto a sessão; a retomada é da N4');
|
|
111
|
+
}
|
|
112
|
+
const erros = errosSeguidos(t.id, estado.despachos);
|
|
113
|
+
if (erros >= exports.ERROS_SEGUIDOS_QUE_BLOQUEIAM) {
|
|
114
|
+
return bloqueado('erro-repetido', `os ${erros} últimos despachos falharam no núcleo antes de lançar; quem decide é a N4 ou o dono`);
|
|
115
|
+
}
|
|
116
|
+
if (!th) {
|
|
117
|
+
// A4 do CHECK: abrir a thread num projeto que não deixa criar #Auto falharia toda noite; o arco nem tenta.
|
|
118
|
+
if (!fatos.modosPermitidos.includes('auto'))
|
|
119
|
+
return bloqueado('modo', 'o projeto não deixa criar thread #Auto (conduction.allowed_modes)');
|
|
120
|
+
return { ...base, estado: 'abrir', fase: 'GOAL', detalhe: `abre a thread do item ${t.item} com o pedido do dono` };
|
|
121
|
+
}
|
|
122
|
+
if (th.modo !== 'auto')
|
|
123
|
+
return bloqueado('modo', `a thread ${th.id} está em ${th.modo}; o arco só conduz #Auto`);
|
|
124
|
+
if (f.doDono)
|
|
125
|
+
return bloqueado('dono', `a thread ${th.id} espera o dono (${f.doDono})`);
|
|
126
|
+
if (!f.pedidoGuardado)
|
|
127
|
+
return bloqueado('pedido-ausente', `a thread ${th.id} não guardou o pedido na criação; o arco não inventa um`);
|
|
128
|
+
if (!f.despachada) {
|
|
129
|
+
// B1 da rodada 2: a falha disse o id da sessão, que pode estar aberta; despachar de novo abriria outra.
|
|
130
|
+
if (f.despachoFalho)
|
|
131
|
+
return bloqueado('retomada', `o despacho da thread ${th.id} falhou depois de o runtime dizer o id da sessão; a retomada é da N4`);
|
|
132
|
+
return { ...base, estado: 'despachavel', fase: th.faseAtual, detalhe: `a thread ${th.id} nunca foi despachada` };
|
|
133
|
+
}
|
|
134
|
+
const passo = f.passo;
|
|
135
|
+
if (passo?.acao === 'despachar' && passo.fase) {
|
|
136
|
+
if (FASES_DA_ENTREGA.includes(passo.fase))
|
|
137
|
+
return { ...base, estado: 'aguardando-entrega', detalhe: `${passo.porque}; a entrega é da N5` };
|
|
138
|
+
return { ...base, estado: 'despachavel', fase: passo.fase, detalhe: passo.porque };
|
|
139
|
+
}
|
|
140
|
+
if (passo?.acao === 'retomar')
|
|
141
|
+
return bloqueado('retomada', `${passo.porque}; a retomada é da N4`);
|
|
142
|
+
if (f.espera?.tipo === 'stop-sem-resultado')
|
|
143
|
+
return bloqueado('fim-de-turno-sem-resultado', 'o turno acabou sem o resultado da fase; quem decide é a N4');
|
|
144
|
+
if (f.espera?.tipo === 'concluida')
|
|
145
|
+
return { ...base, estado: 'aguardando-entrega', detalhe: `${passo?.porque ?? 'fase concluída'}; a entrega é da N5` };
|
|
146
|
+
return bloqueado('sem-passo', passo?.porque ?? 'nenhum passo do condutor para a thread');
|
|
147
|
+
}
|
|
148
|
+
/** O último progresso do arco: a autorização, um despacho ou uma entrega. O backoff conta a partir dele. */
|
|
149
|
+
function ultimoProgresso(estado, trabalhos) {
|
|
150
|
+
const instantes = [estado.autorizadoPor?.recebidoEm, ...estado.despachos.flatMap(d => [d.reservadoEm, d.concluidoEm]),
|
|
151
|
+
...trabalhos.map(t => t.entregueEm)].map(x => typeof x === 'string' ? Date.parse(x) : NaN).filter(Number.isFinite);
|
|
152
|
+
return instantes.length ? Math.max(...instantes) : Date.parse(estado.conteudo?.janela.inicio ?? '');
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* A fila e a decisão no instante dos fatos. Fora da janela, ou sem envelope `v2` autorizado, nada é candidato; o
|
|
156
|
+
* resto segue a D6 da ork-rm058n2.
|
|
157
|
+
*/
|
|
158
|
+
function montarFila(fatos) {
|
|
159
|
+
const { arco, quando, estado } = fatos;
|
|
160
|
+
const agora = Date.parse(quando);
|
|
161
|
+
const vazia = (situacao, decisao, lacunas = []) => ({ contrato: exports.CONTRATO_FILA, arco, envelopeSha256: estado?.envelopeSha256 ?? null, quando, situacao, trabalhos: [], candidatos: [],
|
|
162
|
+
lacunas, decisao });
|
|
163
|
+
const daqui = (ms, limite) => new Date(Math.min(agora + ms, limite ? Date.parse(limite) : Number.POSITIVE_INFINITY)).toISOString();
|
|
164
|
+
if (!estado || !estado.conteudo || estado.situacao === 'proposto') {
|
|
165
|
+
return vazia('fonte-indisponivel', { acao: 'aguardar', motivo: 'sem-autorizacao', ate: daqui(exports.CADENCIA_MS) }, ['o arco não tem envelope autorizado']);
|
|
166
|
+
}
|
|
167
|
+
const conteudo = estado.conteudo;
|
|
168
|
+
if (estado.situacao === 'revogado' || estado.situacao === 'expirado')
|
|
169
|
+
return vazia('vazia', { acao: 'encerrar', motivo: 'arco-encerrado' });
|
|
170
|
+
if (estado.situacao === 'agendado')
|
|
171
|
+
return vazia('bloqueada', { acao: 'aguardar', motivo: 'agendado', ate: conteudo.janela.inicio });
|
|
172
|
+
if (!(0, noctum_envelope_1.envelopeComTrabalhos)(conteudo)) {
|
|
173
|
+
return vazia('fonte-indisponivel', { acao: 'aguardar', motivo: 'fonte-indisponivel', ate: daqui(exports.CADENCIA_MS, conteudo.janela.fim) }, ['o envelope vigente é v1, sem lista de trabalhos']);
|
|
174
|
+
}
|
|
175
|
+
// Primeiro quem já foi entregue, para as dependências; depois cada trabalho.
|
|
176
|
+
const entregues = new Set(conteudo.trabalhos.filter(t => {
|
|
177
|
+
const f = fatos.porTrabalho.get(t.id);
|
|
178
|
+
return !!f && f.threads.length === 1 && !!f.entregueEm && f.threads[0].roadmap === t.item;
|
|
179
|
+
}).map(t => t.id));
|
|
180
|
+
// S7 do CHECK: quem fechou sem entrega nunca mais entrega neste arco, nem quem depende dele, de qualquer distância.
|
|
181
|
+
const semSaida = new Set(conteudo.trabalhos.filter(t => {
|
|
182
|
+
const f = fatos.porTrabalho.get(t.id);
|
|
183
|
+
return !!f && f.threads.length === 1 && !f.entregueEm && f.threads[0].status === 'fechada' && f.threads[0].roadmap === t.item;
|
|
184
|
+
}).map(t => t.id));
|
|
185
|
+
for (let mudou = true; mudou;) {
|
|
186
|
+
mudou = false;
|
|
187
|
+
for (const t of conteudo.trabalhos) {
|
|
188
|
+
if (!semSaida.has(t.id) && !entregues.has(t.id) && t.dependeDe.some(d => semSaida.has(d))) {
|
|
189
|
+
semSaida.add(t.id);
|
|
190
|
+
mudou = true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const trabalhos = conteudo.trabalhos.map((t, i) => classificar(t, i, fatos.porTrabalho.get(t.id), fatos, entregues, semSaida, estado));
|
|
195
|
+
const candidatos = trabalhos.filter(t => t.estado === 'despachavel' || t.estado === 'abrir').sort(antes);
|
|
196
|
+
// Thread ilegível só é lacuna quando pode ser a de um trabalho (S7 do CHECK): a classificação já a pôs no trabalho.
|
|
197
|
+
const lacunas = trabalhos.filter(t => t.motivo && DE_LACUNA.includes(t.motivo)).map(t => `trabalho ${t.id}: ${t.detalhe}`);
|
|
198
|
+
const terminal = (t) => t.estado === 'entregue' || t.estado === 'encerrado-sem-entrega' ||
|
|
199
|
+
t.motivo === 'dependencia-encerrada';
|
|
200
|
+
const situacao = candidatos.length ? 'com-trabalho' : trabalhos.some(t => t.estado === 'em-curso') ? 'em-curso'
|
|
201
|
+
: lacunas.length ? 'fonte-indisponivel' : trabalhos.every(terminal) ? 'vazia' : 'bloqueada';
|
|
202
|
+
const fim = conteudo.janela.fim;
|
|
203
|
+
const backoff = Math.min(exports.BACKOFF_MAXIMO_MS, Math.max(exports.CADENCIA_MS, Math.floor((agora - ultimoProgresso(estado, trabalhos)) / 2)));
|
|
204
|
+
const decisao = situacao === 'com-trabalho' ? { acao: 'despachar' }
|
|
205
|
+
: situacao === 'vazia' ? { acao: 'encerrar', motivo: 'fila-vazia' }
|
|
206
|
+
: situacao === 'em-curso' ? { acao: 'aguardar', motivo: 'em-curso', ate: daqui(exports.CADENCIA_MS, fim) }
|
|
207
|
+
: { acao: 'aguardar', motivo: situacao, ate: daqui(backoff, fim) };
|
|
208
|
+
return { contrato: exports.CONTRATO_FILA, arco, envelopeSha256: estado.envelopeSha256, quando, situacao, trabalhos, candidatos, lacunas, decisao };
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Os fatos da fila, relidos do disco: o arco, as threads de cada trabalho, o passo do condutor delas, as reservas da
|
|
212
|
+
* última cópia local (sem rede) e o lote. `lerReservas` é dependência dos testes.
|
|
213
|
+
*/
|
|
214
|
+
function fatosDaFila(carregado, arco, opcoes = {}) {
|
|
215
|
+
const quando = opcoes.quando ?? new Date().toISOString();
|
|
216
|
+
const { raiz } = carregado;
|
|
217
|
+
const estado = (0, noctum_arco_1.lerArco)(raiz, arco, quando);
|
|
218
|
+
const maquina = (0, maquina_1.nomeDaMaquina)(opcoes.maquina);
|
|
219
|
+
const lidas = (opcoes.lerReservas ?? (() => (0, branch_de_estado_1.pontaLocal)(raiz, roadmap_reservas_1.REMOTO_PADRAO, roadmap_reservas_1.BRANCH_DE_RESERVAS) === null
|
|
220
|
+
? { leitura: 'sem-copia', reservas: [] }
|
|
221
|
+
: { leitura: 'copia-local', reservas: (0, roadmap_reservas_1.reservasLocais)(raiz) }))();
|
|
222
|
+
const fatos = { arco, quando, maquina, estado, porTrabalho: new Map(), threadsIlegiveis: [],
|
|
223
|
+
modosPermitidos: carregado.manifesto.conduction.allowed_modes,
|
|
224
|
+
reservas: { leitura: lidas.leitura, porItem: new Map(lidas.reservas.map(r => [r.item, { maquina: r.maquina, thread: r.thread }])) } };
|
|
225
|
+
if (!estado?.conteudo || !(0, noctum_envelope_1.envelopeComTrabalhos)(estado.conteudo))
|
|
226
|
+
return fatos;
|
|
227
|
+
const threads = [];
|
|
228
|
+
for (const id of (0, thread_1.listarIds)(raiz)) {
|
|
229
|
+
try {
|
|
230
|
+
threads.push((0, thread_1.lerThread)(raiz, id));
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
fatos.threadsIlegiveis.push(id);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const passos = new Map((0, conducao_sentinela_1.passosDoCondutor)(carregado, { quando }).map(p => [p.thread, p]));
|
|
237
|
+
for (const t of estado.conteudo.trabalhos) {
|
|
238
|
+
let casam;
|
|
239
|
+
let ilegivel = null;
|
|
240
|
+
if (t.thread !== undefined) {
|
|
241
|
+
casam = threads.filter(x => x.id === t.thread);
|
|
242
|
+
if (!casam.length && fatos.threadsIlegiveis.includes(t.thread))
|
|
243
|
+
ilegivel = t.thread;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
const alvo = (0, noctum_envelope_1.shaDoPedido)(t.pedido);
|
|
247
|
+
casam = threads.filter(x => x.roadmap === t.item && x.pedido?.sha256 === alvo);
|
|
248
|
+
// Sem casar e com thread ilegível, ela pode ser a do trabalho: abrir outra duplicaria a thread.
|
|
249
|
+
if (!casam.length && fatos.threadsIlegiveis.length)
|
|
250
|
+
ilegivel = fatos.threadsIlegiveis[0];
|
|
251
|
+
}
|
|
252
|
+
const th = casam.length === 1 ? casam[0] : null;
|
|
253
|
+
let eventos = [];
|
|
254
|
+
try {
|
|
255
|
+
eventos = th ? (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, th.id)) : [];
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
ilegivel = th.id;
|
|
259
|
+
}
|
|
260
|
+
let pedidoGuardado = false;
|
|
261
|
+
try {
|
|
262
|
+
pedidoGuardado = !!th && (0, thread_1.pedidoDaThread)(raiz, th) !== null;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
pedidoGuardado = false;
|
|
266
|
+
}
|
|
267
|
+
fatos.porTrabalho.set(t.id, {
|
|
268
|
+
threads: casam, ilegivel,
|
|
269
|
+
entregueEm: eventos.find(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.shipConcluido)?.ts ?? null,
|
|
270
|
+
despachada: eventos.some(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.faseDespachada),
|
|
271
|
+
despachoFalho: !eventos.some(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.faseDespachada) &&
|
|
272
|
+
eventos.some(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.despachoFalhou && typeof e.sessionId === 'string'),
|
|
273
|
+
pedidoGuardado,
|
|
274
|
+
passo: th ? passos.get(th.id) ?? null : null,
|
|
275
|
+
espera: th && th.status !== 'fechada' ? (0, parado_no_condutor_1.esperaDoCondutor)(th, eventos, quando) : null,
|
|
276
|
+
doDono: th && th.status !== 'fechada' ? (0, parado_no_condutor_1.pendenciaDoDono)(th, eventos, quando) : null,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return fatos;
|
|
280
|
+
}
|