@orkastery/cli 0.5.6 → 0.6.0
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/README.md +18 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +1 -0
- package/adapters/claude-code/README.md +37 -5
- package/adapters/claude-code/agents/ork-check.md +1 -0
- package/adapters/claude-code/agents/ork-go.md +1 -0
- package/adapters/claude-code/agents/ork-goal.md +1 -0
- package/adapters/claude-code/agents/ork-master.md +1 -0
- package/adapters/claude-code/agents/ork-plan.md +1 -0
- package/adapters/claude-code/agents/ork-ship.md +1 -0
- package/adapters/claude-code/hooks/hooks.json +2 -1
- package/adapters/claude-code/hooks/ork-guard.js +15 -1
- package/adapters/claude-code/hooks/ork-resposta.js +91 -0
- package/adapters/claude-code/hooks/ork-sensor.js +18 -2
- package/adapters/claude-code/hooks/quota-signal.js +14 -0
- package/adapters/claude-code/mods/ork-mensageiro/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/caixa.ts +215 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/painel.tsx +165 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/recepcao.ts +256 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/register.tsx +481 -0
- package/adapters/claude-code/mods/ork-mensageiro/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-mensageiro/types/index.d.ts +85 -0
- package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +9 -5
- package/adapters/claude-code/mods/ork-ritual/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/nota.ts +88 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/register.tsx +494 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/replay.tsx +271 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/ritual.ts +218 -0
- package/adapters/claude-code/mods/ork-ritual/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-ritual/types/index.d.ts +106 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +1 -1
- package/adapters/codex/skills/ork/SKILL.md +2 -0
- package/adapters/hermes/README.md +64 -1
- package/adapters/hermes/bin/ork-conhecimento-colecoes.sh +14 -0
- package/adapters/hermes/bin/ork-conhecimento-listar.sh +15 -0
- package/adapters/hermes/bin/ork-conhecimento-mostrar.sh +16 -0
- package/adapters/hermes/bin/ork-conhecimento-verificar.sh +15 -0
- package/adapters/hermes/bin/ork-vigia-batida.py +127 -0
- package/adapters/hermes/hermes.plugin.json +4 -0
- package/adapters/hermes/hitl-ingress/__init__.py +101 -8
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +2 -2
- package/adapters/openclaw/README.md +81 -9
- package/adapters/openclaw/bin/ork-vigia-batida.js +86 -0
- package/adapters/openclaw/dist/hitl-ingress.js +106 -9
- package/adapters/openclaw/dist/index.js +86 -0
- package/adapters/openclaw/openclaw.plugin.json +34 -4
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +103 -9
- package/adapters/openclaw/src/index.ts +94 -0
- package/assets/adapters-en/claude-code/README.md +403 -0
- package/assets/adapters-en/claude-code/agents/ork-check.md +33 -0
- package/assets/adapters-en/claude-code/agents/ork-go.md +33 -0
- package/assets/adapters-en/claude-code/agents/ork-goal.md +34 -0
- package/assets/adapters-en/claude-code/agents/ork-master.md +35 -0
- package/assets/adapters-en/claude-code/agents/ork-plan.md +33 -0
- package/assets/adapters-en/claude-code/agents/ork-ship.md +33 -0
- package/assets/adapters-en/claude-code/commands/check.md +46 -0
- package/assets/adapters-en/claude-code/commands/go.md +46 -0
- package/assets/adapters-en/claude-code/commands/goal.md +55 -0
- package/assets/adapters-en/claude-code/commands/master.md +54 -0
- package/assets/adapters-en/claude-code/commands/onboarding.md +22 -0
- package/assets/adapters-en/claude-code/commands/ork.md +126 -0
- package/assets/adapters-en/claude-code/commands/plan.md +47 -0
- package/assets/adapters-en/claude-code/commands/ship.md +45 -0
- package/assets/adapters-en/codex/skills/ork/SKILL.md +85 -0
- package/assets/adapters-en/hermes/README.md +273 -0
- package/assets/adapters-en/hermes/skills/orkastery-devmaster/SKILL.md +169 -0
- package/assets/references-en/README.md +25 -0
- package/assets/references-en/code-review-axes.md +82 -0
- package/assets/references-en/definition-of-done.md +74 -0
- package/assets/references-en/performance-checklist.md +53 -0
- package/assets/references-en/security-checklist.md +101 -0
- package/assets/references-en/testing-patterns.md +51 -0
- package/assets/security-scan/defaults/.golangci.yml +2 -0
- package/assets/security-scan/defaults/eslint.config.mjs +11 -0
- package/assets/security-scan/defaults/gitleaks.toml +3 -0
- package/assets/security-scan/defaults/ruff.toml +1 -0
- package/assets/security-scan/tools-agil.json +87 -0
- package/assets/security-scan/tools.json +625 -0
- package/assets/skills-en/core/onboarding/SKILL.md +58 -0
- package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +119 -0
- package/assets/skills-en/core/thread-state/SKILL.md +88 -0
- package/assets/skills-en/governance/decision-triage/SKILL.md +65 -0
- package/assets/skills-en/governance/narrative-guardian/SKILL.md +66 -0
- package/assets/skills-en/governance/roadmap-keeper/SKILL.md +60 -0
- package/assets/skills-en/governance/scope-check-capability-map/SKILL.md +71 -0
- package/assets/skills-en/observability/thread-tracing/SKILL.md +63 -0
- package/assets/skills-en/phases/check-quality/SKILL.md +103 -0
- package/assets/skills-en/phases/go-implementation/SKILL.md +78 -0
- package/assets/skills-en/phases/goal-definition/SKILL.md +69 -0
- package/assets/skills-en/phases/master-metrics/SKILL.md +72 -0
- package/assets/skills-en/phases/plan-specification/SKILL.md +67 -0
- package/assets/skills-en/phases/security-remediation/SKILL.md +70 -0
- package/assets/skills-en/phases/ship-release/SKILL.md +71 -0
- package/assets/skills-en/reviewers/code-reviewer/SKILL.md +61 -0
- package/assets/skills-en/reviewers/security-auditor/SKILL.md +93 -0
- package/assets/skills-en/reviewers/test-engineer/SKILL.md +66 -0
- package/assets/skills-en/reviewers/web-performance-auditor/SKILL.md +63 -0
- package/dist/adapters/claude-bg.js +118 -4
- package/dist/adapters/codex-controller-worker.js +12 -5
- package/dist/adapters/codex-events.js +16 -4
- package/dist/adapters/codex.js +3 -1
- package/dist/atualizar-branch.js +245 -0
- package/dist/auditoria.js +13 -11
- package/dist/auditrun.js +51 -46
- package/dist/autorizacao-duravel.js +327 -0
- package/dist/board.js +94 -40
- package/dist/branch-de-estado.js +16 -8
- package/dist/busca-semantica.js +7 -5
- package/dist/catalogo.js +2 -1
- package/dist/changelog-reconciliacao.js +290 -0
- package/dist/check-agil.js +500 -0
- package/dist/check-modo.js +41 -0
- package/dist/check-paralelo.js +183 -0
- package/dist/ci.js +248 -45
- package/dist/ciclos.js +8 -6
- package/dist/claim-lint.js +12 -8
- package/dist/claims-conferir.js +47 -39
- package/dist/claims.js +10 -9
- package/dist/company-brain-client.js +10 -3
- package/dist/company-brain-migration.js +11 -5
- package/dist/conducao-batida.js +759 -0
- package/dist/conducao-sentinela.js +25 -8
- package/dist/conducao.js +84 -29
- package/dist/conhecimento-consulta.js +270 -0
- package/dist/conhecimento.js +111 -87
- package/dist/conta-esgotada.js +127 -0
- package/dist/dado-privado.js +816 -0
- package/dist/decisao-autonoma.js +5 -4
- package/dist/demo.js +14 -12
- package/dist/dependencias-da-worktree.js +25 -0
- package/dist/divida.js +38 -36
- package/dist/docs.js +81 -74
- package/dist/doctor-forja.js +345 -0
- package/dist/doctor-pulse-cron.js +113 -1
- package/dist/doctor.js +164 -6
- package/dist/dsn-da-memoria.js +417 -0
- package/dist/entrega-por-mod.js +503 -0
- package/dist/entrega-pr.js +143 -43
- package/dist/esgotamentos.js +13 -11
- package/dist/estado-thread.js +21 -18
- package/dist/evalrunner.js +35 -32
- package/dist/fabrica-batida.js +34 -21
- package/dist/fabrica-estado.js +13 -10
- package/dist/fix.js +21 -19
- package/dist/forja/adaptador.js +6 -0
- package/dist/forja/ambiente-de-ci.js +27 -0
- package/dist/forja/api.js +256 -0
- package/dist/forja/cache.js +89 -0
- package/dist/forja/cli.js +60 -0
- package/dist/forja/contrato.js +9 -0
- package/dist/forja/endereco.js +43 -0
- package/dist/forja/estado.js +22 -0
- package/dist/forja/executor.js +73 -0
- package/dist/forja/github/ci.js +95 -0
- package/dist/forja/github/dono-da-rede.js +18 -0
- package/dist/forja/github/metadados.js +15 -0
- package/dist/forja/github/pedidos.js +200 -0
- package/dist/forja/gitlab/ci.js +141 -0
- package/dist/forja/gitlab/consultas.js +59 -0
- package/dist/forja/gitlab/dono-da-rede.js +43 -0
- package/dist/forja/gitlab/metadados.js +15 -0
- package/dist/forja/gitlab/pedidos-abertos.js +158 -0
- package/dist/forja/gitlab/pedidos.js +141 -0
- package/dist/forja/produtor.js +32 -0
- package/dist/forja/registro.js +31 -0
- package/dist/forja/resolver.js +121 -0
- package/dist/forja/texto.js +13 -0
- package/dist/forja/versao.js +71 -0
- package/dist/forja.js +123 -80
- package/dist/handoff.js +23 -13
- package/dist/hitl-contract.js +46 -43
- package/dist/hitl-curto.js +37 -25
- package/dist/hitl-gates.js +13 -11
- package/dist/hitl-local-receipt.js +51 -0
- package/dist/hitl-local.js +11 -2
- package/dist/hitl-lock.js +5 -11
- package/dist/hitl-public-receipt.js +11 -0
- package/dist/hitl-resumo.js +44 -1
- package/dist/hitl-sessions.js +62 -9
- package/dist/hitl-texto-livre.js +7 -4
- package/dist/hitl.js +34 -31
- package/dist/horario.js +16 -0
- package/dist/hosts.js +104 -134
- package/dist/index.js +848 -548
- package/dist/indice-vetorial.js +14 -12
- package/dist/init.js +11 -2
- package/dist/integracoes-locais.js +2 -0
- package/dist/intelligence-graph-cli.js +123 -69
- package/dist/intelligence-graph-query.js +37 -22
- package/dist/leases.js +19 -18
- package/dist/ledger-stats.js +17 -16
- package/dist/ledger.js +31 -1
- package/dist/licoes.js +12 -11
- package/dist/locale-accounts.js +220 -0
- package/dist/locale-adapter.js +291 -0
- package/dist/locale-ajuda.js +76 -4
- package/dist/locale-audit.js +199 -0
- package/dist/locale-claims.js +213 -0
- package/dist/locale-conducao.js +138 -0
- package/dist/locale-conhecimento.js +181 -0
- package/dist/locale-decisao.js +205 -0
- package/dist/locale-docs.js +230 -0
- package/dist/locale-estado.js +101 -0
- package/dist/locale-forja.js +290 -0
- package/dist/locale-gerados.js +5 -0
- package/dist/locale-grafo.js +129 -0
- package/dist/locale-master.js +83 -0
- package/dist/locale-network.js +524 -0
- package/dist/locale-operacao-restante.js +931 -0
- package/dist/locale-projetos.js +100 -0
- package/dist/locale-prompts.js +194 -0
- package/dist/locale-pulse.js +140 -2
- package/dist/locale-reservas.js +87 -0
- package/dist/locale-retry.js +145 -6
- package/dist/locale-security-scan.js +69 -0
- package/dist/locale-setup.js +192 -0
- package/dist/locale-ship.js +169 -66
- package/dist/locale-thread.js +149 -5
- package/dist/locale-verify.js +80 -4
- package/dist/locale-worktree.js +246 -0
- package/dist/locale.js +553 -9
- package/dist/maestro-runtime.js +22 -3
- package/dist/manifest.js +287 -11
- package/dist/maquina-ociosa.js +211 -0
- package/dist/maquina.js +10 -5
- package/dist/master-nota-local.js +200 -0
- package/dist/master-nota.js +10 -2
- package/dist/master.js +118 -41
- package/dist/mcp-conhecimento.js +52 -0
- package/dist/mcp-experiencia.js +26 -5
- package/dist/mcp-install.js +73 -8
- package/dist/mcp-noctum.js +101 -0
- package/dist/mcp-server.js +82 -14
- package/dist/mcp-ship-transporte.js +977 -0
- package/dist/mcp-ship.js +68 -24
- package/dist/mcp-transportes.js +17 -0
- package/dist/memoria.js +63 -58
- package/dist/modos-migracao.js +8 -6
- package/dist/modos.js +17 -18
- package/dist/network-roadmap.js +85 -49
- package/dist/noctum-aceite.js +789 -0
- package/dist/noctum-arco.js +540 -33
- package/dist/noctum-capacidade.js +3 -3
- package/dist/noctum-changelog.js +399 -0
- package/dist/noctum-cli.js +482 -0
- package/dist/noctum-coordenacao.js +1225 -0
- package/dist/noctum-entrega.js +1169 -0
- package/dist/noctum-envelope.js +252 -16
- package/dist/noctum-execucao.js +95 -23
- package/dist/noctum-fechamento.js +405 -0
- package/dist/noctum-fila.js +43 -9
- package/dist/noctum-guarda.js +256 -0
- package/dist/noctum-integracao.js +507 -0
- package/dist/noctum-maquinas.js +383 -0
- package/dist/noctum-medicao.js +450 -0
- package/dist/noctum-modo.js +362 -0
- package/dist/noctum-pergunta-canal.js +96 -0
- package/dist/noctum-pergunta.js +115 -0
- package/dist/noctum-provas.js +838 -0
- package/dist/noctum-reconciliacao.js +591 -0
- package/dist/noctum-registro.js +215 -0
- package/dist/noctum-relatorio.js +827 -0
- package/dist/noctum-resposta.js +207 -0
- package/dist/noctum-sessoes-em-voo.js +93 -0
- package/dist/noctum-supervisao.js +649 -0
- package/dist/noctum-terminal.js +459 -0
- package/dist/noctum-transporte.js +655 -0
- package/dist/noctum-vigia.js +639 -0
- package/dist/ocupacao.js +3 -1
- package/dist/onboarding.js +16 -5
- package/dist/orkmind.js +39 -23
- package/dist/orquestracao.js +31 -35
- package/dist/parado-no-condutor.js +146 -124
- package/dist/phase.js +53 -27
- package/dist/policies.js +33 -0
- package/dist/postura-local.js +4 -3
- package/dist/procedencia.js +9 -14
- package/dist/projeto-alvo.js +59 -20
- package/dist/prompts.js +136 -42
- package/dist/prova-minima.js +5 -6
- package/dist/pulse-cadencia.js +5 -4
- package/dist/pulse-consentimento.js +53 -1
- package/dist/pulse-delivery.js +143 -27
- package/dist/pulse-resposta.js +368 -4
- package/dist/pulse.js +136 -3
- package/dist/ratelimit.js +12 -4
- package/dist/recall.js +14 -13
- package/dist/rede-adesao.js +13 -8
- package/dist/rede-forja.js +108 -24
- package/dist/rede-status.js +63 -45
- package/dist/rede.js +86 -46
- package/dist/retomada-checkpoint.js +101 -0
- package/dist/retry.js +228 -73
- package/dist/roadmap-reservas.js +32 -32
- package/dist/runtime-ambiente.js +5 -0
- package/dist/runtime-context.js +117 -43
- package/dist/runtime-escada.js +423 -0
- package/dist/runtime-profiles.js +103 -46
- package/dist/saida-segura.js +3 -2
- package/dist/security-scan/agil.js +355 -0
- package/dist/security-scan/cli.js +76 -0
- package/dist/security-scan/compare.js +149 -0
- package/dist/security-scan/config.js +93 -0
- package/dist/security-scan/detect.js +97 -0
- package/dist/security-scan/executor.js +188 -0
- package/dist/security-scan/guard.js +180 -0
- package/dist/security-scan/imagem-de-lint.js +96 -0
- package/dist/security-scan/normalize/actionlint.js +12 -0
- package/dist/security-scan/normalize/eslint.js +12 -0
- package/dist/security-scan/normalize/gitleaks.js +13 -0
- package/dist/security-scan/normalize/golangci-lint.js +13 -0
- package/dist/security-scan/normalize/govulncheck.js +36 -0
- package/dist/security-scan/normalize/index.js +25 -0
- package/dist/security-scan/normalize/npm-audit.js +30 -0
- package/dist/security-scan/normalize/osv-scanner.js +32 -0
- package/dist/security-scan/normalize/oxlint.js +51 -0
- package/dist/security-scan/normalize/pip-audit.js +16 -0
- package/dist/security-scan/normalize/ruff.js +12 -0
- package/dist/security-scan/normalize/semgrep.js +20 -0
- package/dist/security-scan/normalize/trivy.js +33 -0
- package/dist/security-scan/normalize/util.js +204 -0
- package/dist/security-scan/normalize/zizmor.js +19 -0
- package/dist/security-scan/report.js +156 -0
- package/dist/security-scan/run.js +448 -0
- package/dist/security-scan/tools.js +62 -0
- package/dist/security-scan/trabalhador.js +74 -0
- package/dist/security-scan/types.js +15 -0
- package/dist/session-events.js +19 -2
- package/dist/session-watcher-claude.js +74 -19
- package/dist/session-watcher.js +156 -10
- package/dist/sessoes-contas.js +9 -0
- package/dist/sessoes-inventario.js +1 -1
- package/dist/sessoes.js +25 -23
- package/dist/setup.js +59 -58
- package/dist/ship-publicar.js +263 -92
- package/dist/ship.js +160 -17
- package/dist/superficie.js +14 -11
- package/dist/thread-close.js +9 -9
- package/dist/thread.js +121 -18
- package/dist/tokens.js +15 -11
- package/dist/trabalho-thread.js +297 -0
- package/dist/transporte-do-canal.js +189 -0
- package/dist/types.js +12 -1
- package/dist/util.js +5 -2
- package/dist/varredura-acoes.js +507 -0
- package/dist/verify.js +472 -29
- package/dist/worktree-local.js +12 -10
- package/dist/worktree.js +81 -85
- package/eval/casos/check-quality.json +77 -0
- package/eval/casos/security-auditor.json +148 -0
- package/eval/casos/security-remediation.json +161 -0
- package/eval/fixtures/security-scan/README.md +8 -0
- package/eval/fixtures/security-scan/go/.github/workflows/ci.yml +12 -0
- package/eval/fixtures/security-scan/go/Dockerfile +4 -0
- package/eval/fixtures/security-scan/go/README.md +4 -0
- package/eval/fixtures/security-scan/go/config.go +4 -0
- package/eval/fixtures/security-scan/go/go.mod +5 -0
- package/eval/fixtures/security-scan/go/go.sum +2 -0
- package/eval/fixtures/security-scan/go/main.go +31 -0
- package/eval/fixtures/security-scan/js/.github/workflows/ci.yml +12 -0
- package/eval/fixtures/security-scan/js/Dockerfile +3 -0
- package/eval/fixtures/security-scan/js/README.md +4 -0
- package/eval/fixtures/security-scan/js/package-lock.json +844 -0
- package/eval/fixtures/security-scan/js/package.json +10 -0
- package/eval/fixtures/security-scan/js/src/config.js +4 -0
- package/eval/fixtures/security-scan/js/src/server.js +17 -0
- package/eval/fixtures/security-scan/js/src/util.ts +4 -0
- package/eval/fixtures/security-scan/js/tsconfig.json +4 -0
- package/eval/fixtures/security-scan/python/.github/workflows/ci.yml +12 -0
- package/eval/fixtures/security-scan/python/Dockerfile +3 -0
- package/eval/fixtures/security-scan/python/README.md +4 -0
- package/eval/fixtures/security-scan/python/app/__init__.py +0 -0
- package/eval/fixtures/security-scan/python/app/config.py +2 -0
- package/eval/fixtures/security-scan/python/app/main.py +19 -0
- package/eval/fixtures/security-scan/python/requirements.txt +1 -0
- package/package.json +9 -4
- package/references/security-checklist.md +3 -3
- package/skills/README.md +1 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/check-quality/SKILL.md +16 -0
- package/skills/phases/go-implementation/SKILL.md +6 -0
- package/skills/phases/security-remediation/SKILL.md +70 -0
- package/skills/reviewers/security-auditor/SKILL.md +25 -0
|
@@ -0,0 +1,1169 @@
|
|
|
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.EVENTO_DO_PUSH_RECUSADO = exports.EVENTO_DO_PUSH_INCERTO = exports.EVENTO_DA_ATUALIZACAO_RECUSADA = exports.ESPERA_DO_CI_MS = exports.LIMITE_DE_REVISOES = exports.CONTRATO_DA_ENTREGA = void 0;
|
|
37
|
+
exports.decidirEntrega = decidirEntrega;
|
|
38
|
+
exports.passoDaEntrega = passoDaEntrega;
|
|
39
|
+
exports.caminhoDaTravaDoDestino = caminhoDaTravaDoDestino;
|
|
40
|
+
exports.integrarNoDestino = integrarNoDestino;
|
|
41
|
+
exports.candidatoDaEntrega = candidatoDaEntrega;
|
|
42
|
+
exports.integradorDaEntrega = integradorDaEntrega;
|
|
43
|
+
/**
|
|
44
|
+
* RM-058, fatia N5 (D1, D2, D4, D7 a D11 da ork-rm058n5): a entrega dentro do arco #Noctum, numa máquina.
|
|
45
|
+
*
|
|
46
|
+
* A N2 deixa "aguardando entrega" a thread cujo passo é SHIP ou MASTER. Aqui o arco entrega sozinho: `passoDaEntrega` relê
|
|
47
|
+
* a fila e, para cada trabalho na entrega, decide pela ordem de `decidirEntrega` e executa no máximo um efeito por chamada:
|
|
48
|
+
*
|
|
49
|
+
* - `atualizar`: a base andou (ou o bundle ficou velho): merge da base na worktree da thread sob a condução do verify, conflito
|
|
50
|
+
* só aditivo resolvido pela regra do núcleo, a entrada da thread posta em "Não publicado", o bundle de CI regravado e o
|
|
51
|
+
* verify logo depois, na mesma condução; o conflito que não é só aditivo aborta o merge e bloqueia sem repetir a mesma base;
|
|
52
|
+
* - `revisar`: falta a revisão independente: o CHECK sai numa sessão nova pela reserva do arco (`revisao: true`), pelos slots
|
|
53
|
+
* de revisão e pela trava de despacho, até `LIMITE_DE_REVISOES` vezes por trabalho;
|
|
54
|
+
* - `verificar`: o verify no SHA, na fila pesada, até três rodadas por SHA;
|
|
55
|
+
* - `publicar`: push e PR pelo `publicarEntrega`, com o envelope conferido imediatamente antes de cada um;
|
|
56
|
+
* - `integrar`: sob a trava do kernel do destino, as provas relidas do zero, o merge da fonte sobre a base conferida, a
|
|
57
|
+
* página do item com o estado derivado (quando ela deixaria a `main` vermelha), a conferência da N1 com `merge` e, logo
|
|
58
|
+
* depois, o push amarrado à ponta conferida (a base que andou recusa) e a prova pelo `ls-remote`; o `ship_done` leva a
|
|
59
|
+
* evidência das provas;
|
|
60
|
+
* - `reconciliar`: a fonte já está na base (o push que ficou incerto chegou): o `ship_done` sai sem integrar de novo;
|
|
61
|
+
* - `master`: o MASTER por omissão da thread entregue, com o índice derivado do ledger.
|
|
62
|
+
*
|
|
63
|
+
* A recusa que vem antes de qualquer efeito (a leitura que falhou, a trava ocupada, a prova que mudou sob a trava) não
|
|
64
|
+
* encerra o passo: o trabalho seguinte da fila tem a vez, e um trabalho travado não prende os outros.
|
|
65
|
+
*
|
|
66
|
+
* Só o envelope `v2` integra por aqui; com outro contrato, a integração é da vez entre máquinas da N3, e `integradorDaEntrega`
|
|
67
|
+
* dá a função de merge no formato que ela recebe. Nada aqui liga o modo: ninguém chama o passo ainda (o vigia é da N4).
|
|
68
|
+
*/
|
|
69
|
+
const fs = __importStar(require("node:fs"));
|
|
70
|
+
const os = __importStar(require("node:os"));
|
|
71
|
+
const path = __importStar(require("node:path"));
|
|
72
|
+
const node_crypto_1 = require("node:crypto");
|
|
73
|
+
const board_1 = require("./board");
|
|
74
|
+
const ci_1 = require("./ci");
|
|
75
|
+
const conducao_1 = require("./conducao");
|
|
76
|
+
const docs_1 = require("./docs");
|
|
77
|
+
const estado_thread_1 = require("./estado-thread");
|
|
78
|
+
const fabrica_publicar_1 = require("./fabrica-publicar");
|
|
79
|
+
const fila_pesada_1 = require("./fila-pesada");
|
|
80
|
+
const ledger_1 = require("./ledger");
|
|
81
|
+
const master_1 = require("./master");
|
|
82
|
+
const modos_1 = require("./modos");
|
|
83
|
+
const noctum_arco_1 = require("./noctum-arco");
|
|
84
|
+
const noctum_capacidade_1 = require("./noctum-capacidade");
|
|
85
|
+
const noctum_changelog_1 = require("./noctum-changelog");
|
|
86
|
+
const noctum_envelope_1 = require("./noctum-envelope");
|
|
87
|
+
const noctum_fila_1 = require("./noctum-fila");
|
|
88
|
+
const noctum_guarda_1 = require("./noctum-guarda");
|
|
89
|
+
const noctum_provas_1 = require("./noctum-provas");
|
|
90
|
+
const noctum_supervisao_1 = require("./noctum-supervisao");
|
|
91
|
+
const phase_1 = require("./phase");
|
|
92
|
+
const policies_1 = require("./policies");
|
|
93
|
+
const runtime_profiles_1 = require("./runtime-profiles");
|
|
94
|
+
const setup_1 = require("./setup");
|
|
95
|
+
const ship_1 = require("./ship");
|
|
96
|
+
const ship_publicar_1 = require("./ship-publicar");
|
|
97
|
+
const thread_1 = require("./thread");
|
|
98
|
+
const verify_1 = require("./verify");
|
|
99
|
+
const worktree_1 = require("./worktree");
|
|
100
|
+
exports.CONTRATO_DA_ENTREGA = 'ork.noctum-entrega/v1';
|
|
101
|
+
/** D2: quantas vezes o arco despacha o CHECK independente de um trabalho antes de bloquear. */
|
|
102
|
+
exports.LIMITE_DE_REVISOES = 4;
|
|
103
|
+
/** D7: a espera pelo CI pendente, a cadência do vigia. */
|
|
104
|
+
exports.ESPERA_DO_CI_MS = 5 * 60_000;
|
|
105
|
+
/** O evento do ledger da thread que guarda a atualização recusada por conflito, com a ponta da base. */
|
|
106
|
+
exports.EVENTO_DA_ATUALIZACAO_RECUSADA = 'noctum_atualizacao_recusada';
|
|
107
|
+
/** O evento do ledger da thread que guarda o push para a base de resultado incerto. */
|
|
108
|
+
exports.EVENTO_DO_PUSH_INCERTO = 'noctum_push_incerto';
|
|
109
|
+
/** A4 da rodada 1: o evento do ledger da thread que guarda o push que o remoto recusou com a base parada (proteção, gancho). */
|
|
110
|
+
exports.EVENTO_DO_PUSH_RECUSADO = 'noctum_push_recusado';
|
|
111
|
+
/**
|
|
112
|
+
* R2-A3 da rodada 2 e R3-S1 da rodada 3: a linha em que o remoto recusa o push de vez (a proteção da branch, um gancho, a
|
|
113
|
+
* permissão), e não a rede nem a falha passageira do servidor (a trava da ref, o desempacotamento), que esperam. O código do
|
|
114
|
+
* GitHub só vale na linha `remote: error: GH0..`, nunca no endereço do remoto; no `[remote rejected]`, decide o motivo entre
|
|
115
|
+
* parênteses.
|
|
116
|
+
*/
|
|
117
|
+
function recusaDoRemoto(stderr) {
|
|
118
|
+
for (const bruta of stderr.split('\n')) {
|
|
119
|
+
const linha = bruta.trim();
|
|
120
|
+
if (/^remote: error: GH0\d\d\b/.test(linha) || /\bhook declined\b/i.test(linha) || /^!? *\[rejected\] /.test(linha))
|
|
121
|
+
return linha.slice(0, 300);
|
|
122
|
+
const remota = /^!? *\[remote rejected\] [^(]*\((.*)\)$/.exec(linha);
|
|
123
|
+
if (remota && /protected|\bden(?:y|ied)\b|permission/i.test(remota[1]))
|
|
124
|
+
return linha.slice(0, 300);
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const SHA = /^[0-9a-f]{40}$/;
|
|
129
|
+
const curto = (s) => (s ? s.slice(0, 12) : '-');
|
|
130
|
+
const mensagem = (e) => (e instanceof Error ? e.message : String(e)).split('\n')[0].slice(0, 300);
|
|
131
|
+
const primeira = (s) => s.trim().split('\n')[0].slice(0, 200);
|
|
132
|
+
/** B1 da rodada 1: o git da entrega, com o buffer grande e `ok` só com saída 0 (o mesmo das provas). */
|
|
133
|
+
const git = noctum_provas_1.gitDaEntrega;
|
|
134
|
+
/** A leitura do git que falhou num efeito: lacuna tipada, que espera e não grava recusa nenhuma. */
|
|
135
|
+
const lacuna = (detalhe) => new noctum_provas_1.RecusaDaEntrega('noctum.entrega.git-sem-leitura', detalhe);
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// A supervisão da N4 (R2-B1 da rodada 2): o que ela decide vale aqui como no passo da N2.
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
/** O log de supervisão do arco; o que não se lê é lacuna tipada, e nenhum efeito sai sem ele. */
|
|
140
|
+
function supervisaoDoArco(raiz, arco) {
|
|
141
|
+
try {
|
|
142
|
+
return (0, noctum_supervisao_1.lerSupervisao)(raiz, arco);
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.supervisao-sem-leitura', `o log de supervisão do arco ${arco} não se leu: ${mensagem(e)}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** As decisões da supervisão na forma que a fila da N2 respeita: o trabalho isolado não sai sozinho neste arco. */
|
|
149
|
+
function decisoesDaEntrega(sup) {
|
|
150
|
+
return { isolados: new Map([...sup.isolados].map(([t, i]) => [t, `${i.motivo}: ${i.detalhe}`])), retomadas: new Map() };
|
|
151
|
+
}
|
|
152
|
+
/** Imediatamente antes de um efeito: a supervisão não parou o arco nem isolou o trabalho desde a leitura da fila. */
|
|
153
|
+
function conferirSupervisao(raiz, arco, trabalhos) {
|
|
154
|
+
const sup = supervisaoDoArco(raiz, arco);
|
|
155
|
+
if (sup.parada)
|
|
156
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.arco-parado', `a supervisão parou o arco (${sup.parada.motivo}): ${sup.parada.detalhe}`);
|
|
157
|
+
for (const id of trabalhos) {
|
|
158
|
+
const isolado = sup.isolados.get(id);
|
|
159
|
+
if (isolado)
|
|
160
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.isolado', `o trabalho ${id} foi isolado pela supervisão (${isolado.motivo}): ${isolado.detalhe}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** R2-S3 da rodada 2: os trabalhos do envelope que são da thread, pela regra da fila: a thread fixada, ou o item com o pedido. */
|
|
164
|
+
function trabalhosDaThread(conteudo, t) {
|
|
165
|
+
// R3-S3 da rodada 3: a thread fixada de outro item é `item-divergente` na fila, e não integra aqui.
|
|
166
|
+
return conteudo.trabalhos.filter(w => w.item === t.roadmap && (w.thread !== undefined ? w.thread === t.id
|
|
167
|
+
: !!w.pedido && t.pedido?.sha256 === (0, noctum_envelope_1.shaDoPedido)(w.pedido)));
|
|
168
|
+
}
|
|
169
|
+
const motivoDe = (p, id) => p.provas.find(x => x.id === id) ?? null;
|
|
170
|
+
/**
|
|
171
|
+
* A ordem do plano da N5: entregue (MASTER), destino, produto, integração com a base, leituras que faltaram, provas
|
|
172
|
+
* estáticas, falhas que não se repetem, revisão, verify, CI com a publicação e, por fim, a integração.
|
|
173
|
+
*/
|
|
174
|
+
function decidirEntrega(e) {
|
|
175
|
+
if (e.entregue)
|
|
176
|
+
return e.masterPendente ? { acao: 'master', porque: 'entregue: falta o MASTER' } : { acao: 'nada', porque: 'entregue e com MASTER' };
|
|
177
|
+
if (!e.destino.ok)
|
|
178
|
+
return { acao: 'bloqueado', motivo: e.destino.motivo, porque: e.destino.detalhe };
|
|
179
|
+
if (e.semProduto) {
|
|
180
|
+
return { acao: 'bloqueado', motivo: 'noctum.entrega.sem-produto', porque: 'a branch não tem commit próprio desde o commit de partida da thread: não há o que entregar' };
|
|
181
|
+
}
|
|
182
|
+
if (e.incorporada)
|
|
183
|
+
return { acao: 'reconciliar', porque: 'a fonte já está na base remota e falta o ship_done' };
|
|
184
|
+
if (!e.provas)
|
|
185
|
+
return { acao: 'bloqueado', motivo: 'noctum.entrega.fonte-ausente', porque: 'as provas não se leram' };
|
|
186
|
+
const p = e.provas;
|
|
187
|
+
const integracao = motivoDe(p, 'integracao');
|
|
188
|
+
if (integracao.motivo === 'noctum.entrega.fonte-ausente')
|
|
189
|
+
return { acao: 'bloqueado', motivo: integracao.motivo, porque: integracao.detalhe };
|
|
190
|
+
if (integracao.motivo === 'noctum.entrega.base-sem-leitura')
|
|
191
|
+
return { acao: 'aguardar', motivo: integracao.motivo, porque: integracao.detalhe };
|
|
192
|
+
// B1 da rodada 1: a leitura do git que falhou não vira "nada mudou"; nenhuma decisão sai de fatos pela metade.
|
|
193
|
+
const semLeitura = p.provas.find(x => x.motivo === 'noctum.entrega.git-sem-leitura');
|
|
194
|
+
if (semLeitura)
|
|
195
|
+
return { acao: 'aguardar', motivo: 'noctum.entrega.git-sem-leitura', porque: `${semLeitura.id}: ${semLeitura.detalhe}` };
|
|
196
|
+
if (integracao.motivo === 'noctum.entrega.base-mudou') {
|
|
197
|
+
return e.atualizacaoRecusada
|
|
198
|
+
? { acao: 'bloqueado', motivo: 'noctum.entrega.conflito-nao-aditivo', porque: 'a atualização sobre esta base já foi recusada por conflito que não é só aditivo' }
|
|
199
|
+
: { acao: 'atualizar', porque: integracao.detalhe };
|
|
200
|
+
}
|
|
201
|
+
for (const id of ['segredo', 'mutantes', 'changelog']) {
|
|
202
|
+
const x = motivoDe(p, id);
|
|
203
|
+
if (x.motivo && x.motivo !== 'noctum.entrega.mutante-nao-executado')
|
|
204
|
+
return { acao: 'bloqueado', motivo: x.motivo, porque: x.detalhe };
|
|
205
|
+
}
|
|
206
|
+
const bundle = motivoDe(p, 'bundle');
|
|
207
|
+
if (bundle.motivo)
|
|
208
|
+
return { acao: 'atualizar', porque: bundle.detalhe };
|
|
209
|
+
const verify = motivoDe(p, 'verify');
|
|
210
|
+
if (verify.motivo && ['noctum.entrega.verify-falha-consistente', 'noctum.entrega.verify-esgotado', 'noctum.entrega.baseline-ausente']
|
|
211
|
+
.includes(verify.motivo))
|
|
212
|
+
return { acao: 'bloqueado', motivo: verify.motivo, porque: verify.detalhe };
|
|
213
|
+
const revisao = motivoDe(p, 'revisao');
|
|
214
|
+
if (revisao.motivo === 'noctum.entrega.revisao-em-curso')
|
|
215
|
+
return { acao: 'aguardar', motivo: revisao.motivo, porque: revisao.detalhe };
|
|
216
|
+
if (revisao.motivo === 'noctum.entrega.revisao-reprovada')
|
|
217
|
+
return { acao: 'bloqueado', motivo: revisao.motivo, porque: revisao.detalhe };
|
|
218
|
+
if (revisao.motivo) {
|
|
219
|
+
return e.revisoesDespachadas >= exports.LIMITE_DE_REVISOES
|
|
220
|
+
? { acao: 'bloqueado', motivo: 'noctum.entrega.revisao-esgotada', porque: `${e.revisoesDespachadas} CHECKs despachados sem revisão válida: ${revisao.detalhe}` }
|
|
221
|
+
: { acao: 'revisar', porque: revisao.detalhe };
|
|
222
|
+
}
|
|
223
|
+
const mutantes = motivoDe(p, 'mutantes');
|
|
224
|
+
if (verify.motivo || mutantes.motivo)
|
|
225
|
+
return { acao: 'verificar', porque: verify.motivo ? verify.detalhe : mutantes.detalhe };
|
|
226
|
+
const ci = motivoDe(p, 'ci');
|
|
227
|
+
if (ci.motivo === 'noctum.entrega.ci-vermelho' || ci.motivo === 'noctum.entrega.ci-ausente')
|
|
228
|
+
return { acao: 'bloqueado', motivo: ci.motivo, porque: ci.detalhe };
|
|
229
|
+
if (ci.motivo === 'noctum.entrega.ci-sem-leitura')
|
|
230
|
+
return { acao: 'aguardar', motivo: ci.motivo, porque: ci.detalhe };
|
|
231
|
+
if (ci.motivo) {
|
|
232
|
+
// CI pendente: sem a branch no SHA ou sem o PR, o check nem começa; com os dois, é esperar.
|
|
233
|
+
if (e.publicacao?.acao === 'publicaria')
|
|
234
|
+
return { acao: 'publicar', porque: `${ci.detalhe}; a branch ou o PR ainda não estão no remoto` };
|
|
235
|
+
if (e.publicacao?.acao === 'recusada')
|
|
236
|
+
return { acao: 'aguardar', motivo: 'noctum.entrega.ci-pendente', porque: `a publicação recusou: ${e.publicacao.detalhe}` };
|
|
237
|
+
return { acao: 'aguardar', motivo: ci.motivo, porque: ci.detalhe };
|
|
238
|
+
}
|
|
239
|
+
if (!e.destino.podeMerge)
|
|
240
|
+
return { acao: 'bloqueado', motivo: 'noctum.entrega.fora-do-destino', porque: 'o envelope não autoriza merge neste destino' };
|
|
241
|
+
if (e.pushRecusado) {
|
|
242
|
+
return { acao: 'bloqueado', motivo: 'noctum.entrega.push-recusado',
|
|
243
|
+
porque: 'o remoto já recusou o push desta fonte sobre esta base, com a base parada: a proteção da branch ou um gancho decide, e não o arco' };
|
|
244
|
+
}
|
|
245
|
+
return { acao: 'integrar', porque: 'todas as provas valem no SHA fonte e na base vigente' };
|
|
246
|
+
}
|
|
247
|
+
const depoisDe = (quando, ms) => new Date(Date.parse(quando) + ms).toISOString();
|
|
248
|
+
/**
|
|
249
|
+
* O destino do envelope para o repositório da thread: o mesmo repositório com a base do projeto (S9 da rodada 1: entre os
|
|
250
|
+
* destinos do repositório, o da base, e não o primeiro da lista), com `push` e `pr`.
|
|
251
|
+
*/
|
|
252
|
+
function destinoDoTrabalho(carregado, destinos, remoto, forja) {
|
|
253
|
+
const url = git(carregado.raiz, ['remote', 'get-url', '--', remoto]);
|
|
254
|
+
const repositorio = forja?.repositorio ?? (url.ok ? (0, ci_1.repositorioGitHub)(url.stdout.trim()) : null);
|
|
255
|
+
if (!repositorio)
|
|
256
|
+
return { ok: false, motivo: 'noctum.entrega.fora-do-destino', detalhe: `o remoto ${remoto} não é um repositório do GitHub` };
|
|
257
|
+
const doRepositorio = destinos.filter(d => d.repositorio.toLowerCase() === repositorio.toLowerCase());
|
|
258
|
+
if (!doRepositorio.length)
|
|
259
|
+
return { ok: false, motivo: 'noctum.entrega.fora-do-destino', detalhe: `o repositório ${repositorio} não está no envelope` };
|
|
260
|
+
const base = carregado.manifesto.worktree.base_branch;
|
|
261
|
+
const destino = doRepositorio.find(d => d.base === base);
|
|
262
|
+
if (!destino) {
|
|
263
|
+
return { ok: false, motivo: 'noctum.entrega.fora-do-destino',
|
|
264
|
+
detalhe: `nenhum destino do envelope em ${repositorio} tem a base do projeto (${base}); o envelope tem ${doRepositorio.map(d => d.base).join(', ')}` };
|
|
265
|
+
}
|
|
266
|
+
if (!destino.permissoes.includes('push') || !destino.permissoes.includes('pr')) {
|
|
267
|
+
return { ok: false, motivo: 'noctum.entrega.fora-do-destino', detalhe: `o envelope não autoriza push e PR em ${destino.repositorio}` };
|
|
268
|
+
}
|
|
269
|
+
return { ok: true, destino, podeMerge: destino.permissoes.includes('merge') };
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* B4 da rodada 1: a branch tem produto quando tem ao menos um commit que não é merge desde o commit de partida da thread.
|
|
273
|
+
* A branch que nunca divergiu da base (ou só recebeu merges dela) não entrega nada, nem por reconciliação. `null` é lacuna.
|
|
274
|
+
*/
|
|
275
|
+
function temProduto(raiz, t, fonte) {
|
|
276
|
+
const partida = t.base?.commit;
|
|
277
|
+
if (!partida || !SHA.test(partida))
|
|
278
|
+
return false;
|
|
279
|
+
const r = git(raiz, ['rev-list', '--count', '--no-merges', `${partida}..${fonte}`]);
|
|
280
|
+
if (!r.ok)
|
|
281
|
+
return null;
|
|
282
|
+
return Number(r.stdout.trim()) > 0;
|
|
283
|
+
}
|
|
284
|
+
/** O que o passo lê de um trabalho na entrega, agora. A leitura que falha lança a lacuna tipada, e o trabalho espera. */
|
|
285
|
+
function entradaDoTrabalho(carregado, estado, trabalho, opcoes, remoto, leituras) {
|
|
286
|
+
const { raiz } = carregado;
|
|
287
|
+
const t = (0, thread_1.lerThread)(raiz, trabalho.thread);
|
|
288
|
+
const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, t.id));
|
|
289
|
+
const entregue = eventos.some(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.shipConcluido);
|
|
290
|
+
const masterPendente = entregue && t.status !== 'fechada' && !(0, master_1.masterRatificado)(raiz, t.id);
|
|
291
|
+
const revisoesDespachadas = revisoesDoTrabalho(estado, trabalho.id);
|
|
292
|
+
const d = destinoDoTrabalho(carregado, estado.conteudo.destinos, remoto, opcoes.forja);
|
|
293
|
+
const vazia = { entregue, masterPendente, semProduto: false, incorporada: false,
|
|
294
|
+
destino: d.ok ? { ok: true, podeMerge: d.podeMerge } : d, provas: null, atualizacaoRecusada: false, pushRecusado: false,
|
|
295
|
+
revisoesDespachadas, publicacao: null };
|
|
296
|
+
if (entregue || !d.ok)
|
|
297
|
+
return { t, entrada: vazia, destino: d.ok ? d.destino : null, base: null };
|
|
298
|
+
const chave = `${remoto}\n${d.destino.base}`;
|
|
299
|
+
const ponta = leituras.get(chave) ?? (0, noctum_provas_1.pontaDaBaseNoRemoto)(raiz, remoto, d.destino.base);
|
|
300
|
+
leituras.set(chave, ponta);
|
|
301
|
+
if (ponta.sha)
|
|
302
|
+
(0, noctum_provas_1.garantirObjetos)(raiz, remoto, d.destino.base, ponta.sha);
|
|
303
|
+
const branch = (0, worktree_1.branchDaWorktree)(t);
|
|
304
|
+
const ref = git(raiz, ['rev-parse', '--verify', '--quiet', `refs/heads/${branch}^{commit}`]);
|
|
305
|
+
const fonte = ref.ok && SHA.test(ref.stdout.trim()) ? ref.stdout.trim() : null;
|
|
306
|
+
if (fonte) {
|
|
307
|
+
const produto = temProduto(raiz, t, fonte);
|
|
308
|
+
if (produto === null)
|
|
309
|
+
throw lacuna(`não se leu se a branch ${branch} tem commit próprio desde a partida da thread`);
|
|
310
|
+
if (!produto)
|
|
311
|
+
return { t, entrada: { ...vazia, semProduto: true }, destino: d.destino, base: ponta.sha };
|
|
312
|
+
}
|
|
313
|
+
if (ponta.sha && fonte) {
|
|
314
|
+
const contida = git(raiz, ['merge-base', '--is-ancestor', fonte, ponta.sha]);
|
|
315
|
+
if (contida.code !== 0 && contida.code !== 1)
|
|
316
|
+
throw lacuna(`não se leu se a fonte ${curto(fonte)} já está na base`);
|
|
317
|
+
if (contida.code === 0)
|
|
318
|
+
return { t, entrada: { ...vazia, incorporada: true }, destino: d.destino, base: ponta.sha };
|
|
319
|
+
}
|
|
320
|
+
const opcoesDasProvas = { remoto, base: d.destino.base, forja: opcoes.forja, executorCi: opcoes.executorCi, pontaDaBase: ponta };
|
|
321
|
+
const provas = (0, noctum_provas_1.provasDaEntrega)(carregado, t.id, opcoesDasProvas);
|
|
322
|
+
const atualizacaoRecusada = !!ponta.sha && eventos.some(e => e.tipo === exports.EVENTO_DA_ATUALIZACAO_RECUSADA && e.base === ponta.sha);
|
|
323
|
+
const pushRecusado = !!ponta.sha && !!provas.fonte &&
|
|
324
|
+
eventos.some(e => e.tipo === exports.EVENTO_DO_PUSH_RECUSADO && e.base === ponta.sha && e.fonte === provas.fonte);
|
|
325
|
+
// A publicação só importa com o CI pendente: o ensaio do `publicarEntrega` lê o remoto e a forja sem efeito nenhum.
|
|
326
|
+
const ciPendente = provas.provas.find(x => x.id === 'ci')?.motivo === 'noctum.entrega.ci-pendente';
|
|
327
|
+
const publicacao = ciPendente ? (() => {
|
|
328
|
+
const r = (0, ship_publicar_1.publicarEntrega)(carregado, t.id, { remoto, forja: opcoes.forja, executorGh: opcoes.executorGh, dryRun: true, quando: opcoes.quando,
|
|
329
|
+
doArco: estado.arco });
|
|
330
|
+
return { acao: r.acao, detalhe: r.detalhe };
|
|
331
|
+
})() : null;
|
|
332
|
+
return { t, entrada: { ...vazia, provas, atualizacaoRecusada, pushRecusado, publicacao }, destino: d.destino, base: ponta.sha };
|
|
333
|
+
}
|
|
334
|
+
/** Os CHECKs de revisão que o arco já despachou para o trabalho (a reserva não recusada). */
|
|
335
|
+
function revisoesDoTrabalho(estado, trabalho) {
|
|
336
|
+
return estado.despachos.filter(d => d.trabalho === trabalho && d.fase === 'CHECK' && d.revisao && d.desfecho !== 'recusado').length;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Um passo da entrega do arco: no máximo um efeito, nos trabalhos que a fila da N2 mostra na entrega (`aguardando-entrega`)
|
|
340
|
+
* ou entregues sem MASTER, na ordem da fila. Lança só o erro que não é recusa (disco); toda recusa volta como resultado.
|
|
341
|
+
*/
|
|
342
|
+
function passoDaEntrega(carregado, arco, opcoes = {}) {
|
|
343
|
+
const quando = opcoes.quando ?? new Date().toISOString();
|
|
344
|
+
const remoto = opcoes.remoto ?? 'origin';
|
|
345
|
+
let estado;
|
|
346
|
+
try {
|
|
347
|
+
estado = (0, noctum_arco_1.lerArco)(carregado.raiz, arco, quando);
|
|
348
|
+
}
|
|
349
|
+
catch (e) {
|
|
350
|
+
if (!(e instanceof noctum_envelope_1.RecusaDoArco))
|
|
351
|
+
throw e;
|
|
352
|
+
return { acao: 'aguardar', trabalho: null, thread: null, motivo: e.motivo, detalhe: e.message, ate: depoisDe(quando, exports.ESPERA_DO_CI_MS) };
|
|
353
|
+
}
|
|
354
|
+
if (!estado || !estado.conteudo || estado.situacao === 'proposto')
|
|
355
|
+
return { acao: 'nada', motivo: 'sem-autorizacao', detalhe: 'o arco não tem envelope autorizado' };
|
|
356
|
+
if (estado.situacao === 'agendado') {
|
|
357
|
+
return { acao: 'aguardar', trabalho: null, thread: null, motivo: 'noctum.entrega.agendado', detalhe: 'a janela do arco ainda não começou',
|
|
358
|
+
ate: estado.conteudo.janela.inicio };
|
|
359
|
+
}
|
|
360
|
+
if (estado.situacao !== 'vigente')
|
|
361
|
+
return { acao: 'nada', motivo: 'arco-encerrado', detalhe: `o arco está ${estado.situacao}: nenhum efeito novo` };
|
|
362
|
+
const conteudo = estado.conteudo;
|
|
363
|
+
if (!(0, noctum_envelope_1.envelopeComTrabalhos)(conteudo))
|
|
364
|
+
return { acao: 'nada', motivo: 'sem-trabalhos', detalhe: 'o envelope vigente não tem lista de trabalhos' };
|
|
365
|
+
// R2-B1 da rodada 2: o arco que a supervisão parou não faz efeito novo, e o trabalho que ela isolou não sai da fila.
|
|
366
|
+
let sup;
|
|
367
|
+
try {
|
|
368
|
+
sup = supervisaoDoArco(carregado.raiz, arco);
|
|
369
|
+
}
|
|
370
|
+
catch (e) {
|
|
371
|
+
if (!(e instanceof noctum_provas_1.RecusaDaEntrega))
|
|
372
|
+
throw e;
|
|
373
|
+
return { acao: 'aguardar', trabalho: null, thread: null, motivo: e.motivo, detalhe: e.message, ate: depoisDe(quando, exports.ESPERA_DO_CI_MS) };
|
|
374
|
+
}
|
|
375
|
+
if (sup.parada)
|
|
376
|
+
return { acao: 'nada', motivo: 'arco-encerrado', detalhe: `a supervisão parou o arco (${sup.parada.motivo}): nenhum efeito novo` };
|
|
377
|
+
const fila = (0, noctum_fila_1.montarFila)((0, noctum_fila_1.fatosDaFila)(carregado, arco, { quando, maquina: opcoes.maquina, lerReservas: opcoes.lerReservas,
|
|
378
|
+
supervisao: decisoesDaEntrega(sup) }));
|
|
379
|
+
const naEntrega = fila.trabalhos.filter(t => t.thread && (t.estado === 'aguardando-entrega' || t.estado === 'entregue'))
|
|
380
|
+
.sort((a, b) => a.prioridade - b.prioridade || a.posicao - b.posicao);
|
|
381
|
+
if (!naEntrega.length)
|
|
382
|
+
return { acao: 'nada', motivo: 'sem-entrega', detalhe: 'nenhum trabalho do arco está na entrega' };
|
|
383
|
+
let espera = null;
|
|
384
|
+
const bloqueios = [];
|
|
385
|
+
const leituras = new Map();
|
|
386
|
+
for (const trabalho of naEntrega) {
|
|
387
|
+
// R3-S4 da rodada 3: o trabalho entregue que a supervisão isolou depois não recebe o MASTER do arco (a fila o mostra
|
|
388
|
+
// entregue antes de olhar o isolamento).
|
|
389
|
+
const isoladoDepois = trabalho.estado === 'entregue' ? sup.isolados.get(trabalho.id) : undefined;
|
|
390
|
+
if (isoladoDepois) {
|
|
391
|
+
bloqueios.push({ trabalho: trabalho.id, thread: trabalho.thread, motivo: 'noctum.entrega.isolado',
|
|
392
|
+
detalhe: `o trabalho ${trabalho.id} foi isolado pela supervisão (${isoladoDepois.motivo}): ${isoladoDepois.detalhe}` });
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
// D9: com outro contrato (a v3 da N3), a integração é da vez entre máquinas; aqui nada se decide por ela.
|
|
396
|
+
if (conteudo.contrato !== noctum_envelope_1.CONTRATO_ENVELOPE_V2) {
|
|
397
|
+
bloqueios.push({ trabalho: trabalho.id, thread: trabalho.thread, motivo: 'noctum.entrega.exige-vez-da-n3',
|
|
398
|
+
detalhe: `o envelope ${conteudo.contrato} integra pela vez entre máquinas (integrarComVez da N3), com integradorDaEntrega` });
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
let lido;
|
|
402
|
+
try {
|
|
403
|
+
lido = entradaDoTrabalho(carregado, estado, trabalho, opcoes, remoto, leituras);
|
|
404
|
+
}
|
|
405
|
+
catch (e) {
|
|
406
|
+
// A leitura que falhou neste trabalho: ele espera, e os outros seguem.
|
|
407
|
+
if (!(e instanceof noctum_provas_1.RecusaDaEntrega))
|
|
408
|
+
throw e;
|
|
409
|
+
espera ??= { acao: 'aguardar', trabalho: trabalho.id, thread: trabalho.thread, motivo: e.motivo, detalhe: e.message, ate: depoisDe(quando, exports.ESPERA_DO_CI_MS) };
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const decisao = decidirEntrega(lido.entrada);
|
|
413
|
+
const ids = { trabalho: trabalho.id, thread: lido.t.id };
|
|
414
|
+
if (decisao.acao === 'nada')
|
|
415
|
+
continue;
|
|
416
|
+
if (decisao.acao === 'aguardar') {
|
|
417
|
+
espera ??= { acao: 'aguardar', ...ids, motivo: decisao.motivo, detalhe: decisao.porque, ate: depoisDe(quando, exports.ESPERA_DO_CI_MS) };
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (decisao.acao === 'bloqueado') {
|
|
421
|
+
bloqueios.push({ ...ids, motivo: decisao.motivo, detalhe: decisao.porque });
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const { resultado, efeito } = executar(carregado, arco, estado, trabalho, lido, decisao.acao, opcoes, remoto, quando);
|
|
425
|
+
// Um efeito por chamada (A1 da rodada 1): o efeito feito, ou de resultado incerto, encerra o passo; a recusa de antes de
|
|
426
|
+
// qualquer efeito deixa o trabalho seguinte tentar, e o trabalho travado não prende a fila.
|
|
427
|
+
if (efeito)
|
|
428
|
+
return resultado;
|
|
429
|
+
if (resultado.acao === 'aguardar') {
|
|
430
|
+
espera ??= resultado;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (resultado.acao === 'bloqueado') {
|
|
434
|
+
bloqueios.push({ trabalho: resultado.trabalho, thread: resultado.thread, motivo: resultado.motivo, detalhe: resultado.detalhe });
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
return resultado;
|
|
438
|
+
}
|
|
439
|
+
if (espera)
|
|
440
|
+
return espera;
|
|
441
|
+
if (bloqueios.length) {
|
|
442
|
+
const [primeiro, ...outros] = bloqueios;
|
|
443
|
+
return { acao: 'bloqueado', ...primeiro, outros: outros.map(o => ({ trabalho: o.trabalho, motivo: o.motivo })) };
|
|
444
|
+
}
|
|
445
|
+
return { acao: 'nada', motivo: 'sem-entrega', detalhe: 'os trabalhos na entrega já estão entregues e com MASTER' };
|
|
446
|
+
}
|
|
447
|
+
/** As recusas que esperam a cadência, em vez de bloquear: a trava, a leitura e o push que ninguém sabe se chegou. */
|
|
448
|
+
const RECUSAS_QUE_ESPERAM = ['noctum.entrega.integracao-ocupada', 'noctum.entrega.base-sem-leitura',
|
|
449
|
+
'noctum.entrega.push-incerto', 'noctum.entrega.git-sem-leitura', 'noctum.entrega.ocupada', 'noctum.entrega.git-falhou',
|
|
450
|
+
'noctum.entrega.supervisao-sem-leitura'];
|
|
451
|
+
function executar(carregado, arco, estado, trabalho, lido, acao, opcoes, remoto, quando) {
|
|
452
|
+
const ids = { trabalho: trabalho.id, thread: lido.t.id };
|
|
453
|
+
const recusa = (motivo, detalhe, aguardar = false) => aguardar
|
|
454
|
+
? { acao: 'aguardar', ...ids, motivo, detalhe, ate: depoisDe(quando, exports.ESPERA_DO_CI_MS) }
|
|
455
|
+
: { acao: 'bloqueado', ...ids, motivo, detalhe, outros: [] };
|
|
456
|
+
const rastro = { efeito: false };
|
|
457
|
+
try {
|
|
458
|
+
switch (acao) {
|
|
459
|
+
case 'master': {
|
|
460
|
+
const a = (0, master_1.aceitarPorOmissao)(carregado.raiz, lido.t.id, { arco });
|
|
461
|
+
return { resultado: { acao: 'master', ...ids, indice: a.indice.valor }, efeito: true };
|
|
462
|
+
}
|
|
463
|
+
case 'reconciliar': {
|
|
464
|
+
const r = reconciliarEntrega(carregado, arco, estado, lido.t.id, remoto, lido.destino, lido.base);
|
|
465
|
+
return { resultado: { acao: 'reconciliou', ...ids, mergeSha: r.mergeSha, pontaDaBase: r.pontaDaBase }, efeito: true };
|
|
466
|
+
}
|
|
467
|
+
case 'atualizar': return { resultado: atualizarCandidato(carregado, arco, trabalho, lido.t, lido.base, opcoes, rastro), efeito: true };
|
|
468
|
+
case 'revisar': {
|
|
469
|
+
const resultado = despacharRevisao(carregado, arco, trabalho, lido.t, lido.entrada.revisoesDespachadas, opcoes, rastro);
|
|
470
|
+
return { resultado, efeito: rastro.efeito };
|
|
471
|
+
}
|
|
472
|
+
case 'verificar': {
|
|
473
|
+
const r = verificarAFonte(carregado, arco, lido.t.id, opcoes);
|
|
474
|
+
return { resultado: { acao: 'verificou', ...ids, commit: lido.entrada.provas?.fonte ?? '', ok: r.ok }, efeito: true };
|
|
475
|
+
}
|
|
476
|
+
case 'publicar': {
|
|
477
|
+
const resultado = publicar(carregado, arco, estado, trabalho, lido, opcoes, remoto, rastro);
|
|
478
|
+
return { resultado, efeito: rastro.efeito };
|
|
479
|
+
}
|
|
480
|
+
case 'integrar': {
|
|
481
|
+
// B2 da rodada 1: sem o instante deste passo; a integração confere o envelope com o relógio da hora do push.
|
|
482
|
+
const r = integrarNoDestino(carregado, arco, lido.t.id, { ...opcoes, remoto });
|
|
483
|
+
return { resultado: { acao: 'integrou', ...ids, mergeSha: r.mergeSha, pontaDaBase: r.pontaDaBase, docs: r.docs.commit, provas: r.provas }, efeito: true };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
catch (e) {
|
|
488
|
+
// Só o push de resultado incerto, ou o que o executor marcou, é efeito: as outras recusas vêm antes de qualquer um (a
|
|
489
|
+
// atualização recusada desfaz o merge local).
|
|
490
|
+
const efeito = rastro.efeito || (e instanceof noctum_provas_1.RecusaDaEntrega && e.motivo === 'noctum.entrega.push-incerto');
|
|
491
|
+
if (e instanceof noctum_provas_1.RecusaDaEntrega)
|
|
492
|
+
return { resultado: recusa(e.motivo, e.message, RECUSAS_QUE_ESPERAM.includes(e.motivo)), efeito };
|
|
493
|
+
if (e instanceof noctum_envelope_1.RecusaDoArco)
|
|
494
|
+
return { resultado: recusa(e.motivo, e.message), efeito };
|
|
495
|
+
if (e instanceof conducao_1.ErroDeConducao || e instanceof fila_pesada_1.ErroDaFilaPesada)
|
|
496
|
+
return { resultado: recusa('noctum.entrega.ocupada', mensagem(e), true), efeito };
|
|
497
|
+
if (e instanceof Error && /^HITL ocupado/.test(e.message))
|
|
498
|
+
return { resultado: recusa('noctum.entrega.ocupada', mensagem(e), true), efeito };
|
|
499
|
+
throw e;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// ---------------------------------------------------------------------------
|
|
503
|
+
// Os efeitos.
|
|
504
|
+
// ---------------------------------------------------------------------------
|
|
505
|
+
/** D4: as entradas do índice com conflito e os estágios de cada uma (`git ls-files -u`); a leitura que falha é lacuna. */
|
|
506
|
+
function conflitosDoIndice(wt) {
|
|
507
|
+
const r = git(wt, ['ls-files', '-u', '-z']);
|
|
508
|
+
if (!r.ok)
|
|
509
|
+
throw lacuna(`os conflitos do índice não se leram: ${primeira(r.stderr) || `código ${r.code}`}`);
|
|
510
|
+
const mapa = new Map();
|
|
511
|
+
for (const entrada of r.stdout.split('\0').filter(Boolean)) {
|
|
512
|
+
const m = /^\d+ [0-9a-f]+ ([123])\t(.+)$/s.exec(entrada);
|
|
513
|
+
if (m)
|
|
514
|
+
mapa.set(m[2], (mapa.get(m[2]) ?? new Set()).add(m[1]));
|
|
515
|
+
}
|
|
516
|
+
return mapa;
|
|
517
|
+
}
|
|
518
|
+
/** S7 da rodada 1: o arquivo comum da worktree, sem seguir link simbólico; `null` quando falta ou não é arquivo comum. */
|
|
519
|
+
function lerArquivoDaWorktree(wt, arquivo) {
|
|
520
|
+
let fd;
|
|
521
|
+
try {
|
|
522
|
+
fd = fs.openSync(path.join(wt, arquivo), fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
523
|
+
}
|
|
524
|
+
catch (e) {
|
|
525
|
+
const codigo = e.code;
|
|
526
|
+
if (codigo === 'ENOENT' || codigo === 'ELOOP')
|
|
527
|
+
return null;
|
|
528
|
+
throw e;
|
|
529
|
+
}
|
|
530
|
+
try {
|
|
531
|
+
return fs.fstatSync(fd).isFile() ? fs.readFileSync(fd) : null;
|
|
532
|
+
}
|
|
533
|
+
finally {
|
|
534
|
+
fs.closeSync(fd);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/** S7 da rodada 1: grava o arquivo comum da worktree sem seguir link simbólico. */
|
|
538
|
+
function gravarArquivoDaWorktree(wt, arquivo, texto) {
|
|
539
|
+
const fd = fs.openSync(path.join(wt, arquivo), fs.constants.O_WRONLY | fs.constants.O_TRUNC | fs.constants.O_NOFOLLOW);
|
|
540
|
+
try {
|
|
541
|
+
fs.writeFileSync(fd, texto);
|
|
542
|
+
}
|
|
543
|
+
finally {
|
|
544
|
+
fs.closeSync(fd);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* D4: atualiza o candidato contra a base lida no remoto, na worktree limpa da thread e sob a condução do verify: merge
|
|
549
|
+
* diff3, conflito só aditivo pela regra do núcleo, a entrada da thread em "Não publicado", nenhuma linha da base perdida, o
|
|
550
|
+
* bundle regravado e o verify logo depois. A recusa aborta o merge, grava a base recusada e bloqueia; a leitura que falha
|
|
551
|
+
* aborta e espera, sem gravar recusa.
|
|
552
|
+
*/
|
|
553
|
+
function atualizarCandidato(carregado, arco, trabalho, t, base, opcoes, rastro) {
|
|
554
|
+
const ids = { trabalho: trabalho.id, thread: t.id };
|
|
555
|
+
const wt = t.worktree;
|
|
556
|
+
if (!wt || !fs.existsSync(wt))
|
|
557
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.fonte-ausente', `a thread ${t.id} não tem a worktree dela`);
|
|
558
|
+
const conducao = { canal: (0, conducao_1.canalDoProcesso)(), correlacao: `noctum:${arco}`, filaPesada: { esperarMs: opcoes.esperarMs } };
|
|
559
|
+
const pedido = { canal: conducao.canal, correlacao: conducao.correlacao, operacao: 'verify', prazoMs: 30 * 60_000 };
|
|
560
|
+
return (0, conducao_1.comConducao)(carregado.raiz, t.id, pedido, () => {
|
|
561
|
+
const branch = (0, worktree_1.branchDaWorktree)(t);
|
|
562
|
+
const ler = (args, oQue) => {
|
|
563
|
+
const r = git(wt, args);
|
|
564
|
+
if (!r.ok)
|
|
565
|
+
throw lacuna(`${oQue} não se leu: ${primeira(r.stderr) || `código ${r.code}`}`);
|
|
566
|
+
return r.stdout;
|
|
567
|
+
};
|
|
568
|
+
const ref = git(wt, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
|
|
569
|
+
if (!ref.ok || ref.stdout.trim() !== branch) {
|
|
570
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.worktree-suja', `a worktree da thread não está na branch ${branch}`);
|
|
571
|
+
}
|
|
572
|
+
if (ler(['status', '--porcelain'], 'o estado da worktree').trim()) {
|
|
573
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.worktree-suja', 'a worktree da thread tem mudança que não foi commitada');
|
|
574
|
+
}
|
|
575
|
+
const antes = ler(['rev-parse', 'HEAD'], 'o HEAD da worktree').trim();
|
|
576
|
+
const contida = git(wt, ['merge-base', '--is-ancestor', base, antes]);
|
|
577
|
+
if (contida.code !== 0 && contida.code !== 1)
|
|
578
|
+
throw lacuna(`não se leu se a base ${curto(base)} já está na branch`);
|
|
579
|
+
let conflitos = [];
|
|
580
|
+
if (contida.code === 1) {
|
|
581
|
+
conflitos = mesclarABase(carregado, t, wt, branch, antes, base, arco);
|
|
582
|
+
rastro.efeito = true;
|
|
583
|
+
}
|
|
584
|
+
const daBranch = (0, ship_publicar_1.manifestoDaBranch)(carregado, ler(['rev-parse', 'HEAD'], 'o HEAD depois do merge').trim());
|
|
585
|
+
const arquivo = (0, ci_1.prepararBundleCi)(daBranch, t.id);
|
|
586
|
+
const relativo = path.relative(wt, arquivo);
|
|
587
|
+
const bundleMudou = ler(['status', '--porcelain', '--', relativo], 'o estado do bundle').trim() !== '';
|
|
588
|
+
if (bundleMudou) {
|
|
589
|
+
// R3-A4 da rodada 3: o bundle que não commita (um gancho, a trava do índice) volta ao que a branch tinha, e o passo
|
|
590
|
+
// espera, como no commit do merge (R2-A2), sem deixar a worktree suja para o passo seguinte.
|
|
591
|
+
const desfazerBundle = () => {
|
|
592
|
+
git(wt, ['reset', '-q', '--', relativo]);
|
|
593
|
+
const naBranch = git(wt, ['cat-file', '-p', `HEAD:${relativo}`]);
|
|
594
|
+
if (naBranch.ok)
|
|
595
|
+
fs.writeFileSync(path.join(wt, relativo), naBranch.stdout);
|
|
596
|
+
else
|
|
597
|
+
fs.rmSync(path.join(wt, relativo), { force: true });
|
|
598
|
+
};
|
|
599
|
+
const a = git(wt, ['add', '--', relativo]);
|
|
600
|
+
if (!a.ok) {
|
|
601
|
+
desfazerBundle();
|
|
602
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.git-falhou', `o git add do bundle ${relativo} falhou: ${primeira(a.stderr)}`);
|
|
603
|
+
}
|
|
604
|
+
const c = git(wt, ['commit', '-q', '-m', `ci(${t.id}): bundle regravado na atualização do arco ${arco}`]);
|
|
605
|
+
if (!c.ok) {
|
|
606
|
+
desfazerBundle();
|
|
607
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.git-falhou', `o commit do bundle falhou: ${primeira(c.stderr)}`);
|
|
608
|
+
}
|
|
609
|
+
rastro.efeito = true;
|
|
610
|
+
}
|
|
611
|
+
const depois = ler(['rev-parse', 'HEAD'], 'o HEAD depois do bundle').trim();
|
|
612
|
+
// O verify que não teve a vez na fila pesada não desfaz a atualização: ele fica para o passo seguinte (`verify: null`).
|
|
613
|
+
let verify = null;
|
|
614
|
+
try {
|
|
615
|
+
verify = (opcoes.verificar ?? ((k, id, c) => (0, verify_1.verificar)(k, id, { conducao: c })))(carregado, t.id, conducao).ok;
|
|
616
|
+
}
|
|
617
|
+
catch (e) {
|
|
618
|
+
if (!(e instanceof fila_pesada_1.ErroDaFilaPesada || e instanceof conducao_1.ErroDeConducao))
|
|
619
|
+
throw e;
|
|
620
|
+
}
|
|
621
|
+
return { acao: 'atualizou', ...ids, de: antes, para: depois, conflitos, bundle: bundleMudou, verify };
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* O merge da base na branch, com a regra do núcleo; devolve os arquivos que conflitaram. O conflito que a regra não
|
|
626
|
+
* resolve aborta e grava a base recusada; qualquer outra saída antes do commit (a lacuna, o git que falha sem conflito,
|
|
627
|
+
* o erro de disco) só aborta, e o passo seguinte tenta de novo a mesma base (R2-A2 da rodada 2).
|
|
628
|
+
*/
|
|
629
|
+
function mesclarABase(carregado, t, wt, branch, antes, base, arco) {
|
|
630
|
+
const msg = `Merge da base (${curto(base)}) na ${branch}: atualização do arco ${arco}`;
|
|
631
|
+
const m = git(wt, ['-c', 'merge.conflictStyle=diff3', 'merge', '--no-ff', '--no-commit', '-m', msg, base], 300000);
|
|
632
|
+
const abortar = () => { git(wt, ['merge', '--abort']); };
|
|
633
|
+
const recusar = (detalhe, arquivos) => {
|
|
634
|
+
abortar();
|
|
635
|
+
(0, ledger_1.registrar)((0, thread_1.dirThread)(carregado.raiz, t.id), t.id, exports.EVENTO_DA_ATUALIZACAO_RECUSADA, { arco, base, de: antes, arquivos: arquivos.slice(0, 50),
|
|
636
|
+
motivo: detalhe.slice(0, 300) });
|
|
637
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.conflito-nao-aditivo', detalhe);
|
|
638
|
+
};
|
|
639
|
+
try {
|
|
640
|
+
const estagios = conflitosDoIndice(wt);
|
|
641
|
+
const conflitos = [...estagios.keys()];
|
|
642
|
+
// R2-A2 da rodada 2: o merge que falha sem conflito (a trava do índice que outro git deixou, o prazo) não é recusa da
|
|
643
|
+
// base: aborta e espera a cadência.
|
|
644
|
+
if (!m.ok && !conflitos.length) {
|
|
645
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.git-falhou', `o merge da base falhou sem conflito de conteúdo: ${primeira(m.stderr || m.stdout)}`);
|
|
646
|
+
}
|
|
647
|
+
const semConteudo = conflitos.find(a => estagios.get(a).size !== 3);
|
|
648
|
+
if (semConteudo)
|
|
649
|
+
recusar(`${semConteudo}: conflito que não é de conteúdo (apagado, renomeado ou criado dos dois lados)`, conflitos);
|
|
650
|
+
for (const arquivo of conflitos) {
|
|
651
|
+
const bytes = lerArquivoDaWorktree(wt, arquivo);
|
|
652
|
+
if (bytes === null)
|
|
653
|
+
recusar(`${arquivo}: o conflito não está num arquivo comum (link simbólico ou outro tipo)`, conflitos);
|
|
654
|
+
// B3 da rodada 1: binário ou fora de UTF-8 não tem a regra do núcleo; ler como texto e gravar trocaria os bytes.
|
|
655
|
+
const texto = (0, noctum_provas_1.textoDe)(bytes);
|
|
656
|
+
if (texto === null)
|
|
657
|
+
recusar(`${arquivo}: conflito em arquivo que não é texto UTF-8 (binário): o núcleo não escolhe um dos lados`, conflitos);
|
|
658
|
+
const resolvido = (0, noctum_changelog_1.resolverConflitosAditivos)(texto);
|
|
659
|
+
if (!resolvido.ok)
|
|
660
|
+
recusar(`${arquivo}: ${resolvido.recusa}`, conflitos);
|
|
661
|
+
else if (resolvido.conflitos === 0)
|
|
662
|
+
recusar(`${arquivo}: o git marcou conflito sem marcador de texto`, conflitos);
|
|
663
|
+
else
|
|
664
|
+
gravarArquivoDaWorktree(wt, arquivo, resolvido.texto);
|
|
665
|
+
}
|
|
666
|
+
// D5: a entrada da thread em "Não publicado", com ou sem conflito no CHANGELOG.
|
|
667
|
+
const atualBytes = lerArquivoDaWorktree(wt, noctum_changelog_1.ARQUIVO_DO_CHANGELOG);
|
|
668
|
+
if (atualBytes !== null) {
|
|
669
|
+
const atual = (0, noctum_provas_1.textoDe)(atualBytes);
|
|
670
|
+
if (atual === null)
|
|
671
|
+
recusar('o CHANGELOG depois do merge não é texto UTF-8', conflitos);
|
|
672
|
+
const ancestral = git(carregado.raiz, ['merge-base', antes, base]);
|
|
673
|
+
if (!ancestral.ok)
|
|
674
|
+
throw lacuna('o ancestral comum da branch e da base não se leu');
|
|
675
|
+
const noAncestral = (0, noctum_provas_1.blobEm)(carregado.raiz, ancestral.stdout.trim(), noctum_changelog_1.ARQUIVO_DO_CHANGELOG) ?? '';
|
|
676
|
+
const naThread = (0, noctum_provas_1.blobEm)(carregado.raiz, antes, noctum_changelog_1.ARQUIVO_DO_CHANGELOG) ?? '';
|
|
677
|
+
const daBase = (0, noctum_provas_1.blobEm)(carregado.raiz, base, noctum_changelog_1.ARQUIVO_DO_CHANGELOG) ?? '';
|
|
678
|
+
const pos = (0, noctum_changelog_1.posicionarEntradas)(atual, (0, noctum_changelog_1.entradasDaThread)(noAncestral, naThread), daBase);
|
|
679
|
+
if (pos.perdidas)
|
|
680
|
+
recusar(`${pos.perdidas} entrada(s) da thread sumiram do CHANGELOG no merge`, conflitos);
|
|
681
|
+
// A3 da rodada 1: a linha antiga que a própria thread corrigiu desde o ancestral comum não é perda da base.
|
|
682
|
+
const perdidas = (0, noctum_changelog_1.linhasDaBasePerdidas)(daBase, pos.texto, (0, noctum_changelog_1.linhasRemovidas)(noAncestral, naThread));
|
|
683
|
+
if (perdidas.length)
|
|
684
|
+
recusar(`${perdidas.length} linha(s) do CHANGELOG da base sumiriam no merge`, conflitos);
|
|
685
|
+
if (pos.texto !== atual)
|
|
686
|
+
gravarArquivoDaWorktree(wt, noctum_changelog_1.ARQUIVO_DO_CHANGELOG, pos.texto);
|
|
687
|
+
}
|
|
688
|
+
const aAdicionar = [...new Set([...conflitos, ...(atualBytes !== null ? [noctum_changelog_1.ARQUIVO_DO_CHANGELOG] : [])])];
|
|
689
|
+
if (aAdicionar.length) {
|
|
690
|
+
const a = git(wt, ['add', '--', ...aAdicionar]);
|
|
691
|
+
if (!a.ok)
|
|
692
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.git-falhou', `o git add da resolução falhou: ${primeira(a.stderr)}`);
|
|
693
|
+
}
|
|
694
|
+
const c = git(wt, ['commit', '-q', '--no-edit']);
|
|
695
|
+
if (!c.ok)
|
|
696
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.git-falhou', `o commit do merge falhou: ${primeira(c.stderr)}`);
|
|
697
|
+
return conflitos;
|
|
698
|
+
}
|
|
699
|
+
catch (e) {
|
|
700
|
+
// A recusa já abortou e gravou a base; o resto aborta aqui, sem marcar a base como recusada.
|
|
701
|
+
if (!(e instanceof noctum_provas_1.RecusaDaEntrega && e.motivo === 'noctum.entrega.conflito-nao-aditivo'))
|
|
702
|
+
abortar();
|
|
703
|
+
throw e;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/** O verify da fonte, na fila pesada e sob a condução da thread. */
|
|
707
|
+
function verificarAFonte(carregado, arco, threadId, opcoes) {
|
|
708
|
+
const conducao = { canal: (0, conducao_1.canalDoProcesso)(), correlacao: `noctum:${arco}`, filaPesada: { esperarMs: opcoes.esperarMs } };
|
|
709
|
+
return (opcoes.verificar ?? ((k, id, c) => (0, verify_1.verificar)(k, id, { conducao: c })))(carregado, threadId, conducao);
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* R4-B1 da rodada 4: o título e o corpo do PR que o arco abre ficam públicos na forja. O corpo é o `pr-body.md` da thread,
|
|
713
|
+
* fora do git e escrito pelas sessões, então a varredura da história não o lê: o padrão de segredo nele bloqueia, com o nome
|
|
714
|
+
* do padrão e a linha (nunca o trecho), e o texto que não chegou à conferência não abre PR.
|
|
715
|
+
*/
|
|
716
|
+
function conferirTextoDoPr(pr) {
|
|
717
|
+
if (!pr)
|
|
718
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.nao-provada', 'o título e o corpo do PR não chegaram à conferência: o arco não abre PR sem lê-los');
|
|
719
|
+
const achados = [...(0, policies_1.procurarSegredos)(pr.titulo).map(a => `${a.nome} no título`),
|
|
720
|
+
...(0, policies_1.procurarSegredos)(pr.corpo).map(a => `${a.nome} na linha ${a.linha} do corpo`)];
|
|
721
|
+
if (achados.length) {
|
|
722
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.segredo', `padrão de segredo no PR que o arco abriria (o pr-body.md da thread): ${achados.join('; ')}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* A conferência da N1 de um efeito, na forma que o `publicarEntrega` recebe; guarda o motivo da recusa do envelope e marca
|
|
727
|
+
* o rastro quando a conferência passa (o efeito vem logo depois dela).
|
|
728
|
+
*/
|
|
729
|
+
function conferirParaPublicar(raiz, arco, estado, destino, branch, trabalho, fonte, relogio, rastro) {
|
|
730
|
+
const recusado = { motivo: null };
|
|
731
|
+
const conferir = (efeito, pr) => {
|
|
732
|
+
try {
|
|
733
|
+
// R2-S2 da rodada 2: o push leva a fonte que as provas viram; a branch que andou depois delas espera o passo seguinte.
|
|
734
|
+
if (efeito === 'push') {
|
|
735
|
+
const ponta = git(raiz, ['rev-parse', '--verify', '--quiet', `refs/heads/${branch}^{commit}`]).stdout.trim();
|
|
736
|
+
if (!fonte || ponta !== fonte) {
|
|
737
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.fonte-mudou', `a branch ${branch} está em ${curto(ponta)}, e as provas são da fonte ${curto(fonte)}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (efeito === 'pr')
|
|
741
|
+
conferirTextoDoPr(pr);
|
|
742
|
+
// R2-B1 da rodada 2: a supervisão não isolou o trabalho nem parou o arco desde a leitura da fila.
|
|
743
|
+
conferirSupervisao(raiz, arco, [trabalho]);
|
|
744
|
+
(0, noctum_arco_1.conferirAntesDoEfeito)(raiz, { arco, envelopeSha256: estado.envelopeSha256 ?? '', acao: efeito, repositorio: destino.repositorio,
|
|
745
|
+
branch: efeito === 'push' ? branch : destino.base }, relogio?.());
|
|
746
|
+
rastro.efeito = true;
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
catch (e) {
|
|
750
|
+
if (e instanceof noctum_provas_1.RecusaDaEntrega) {
|
|
751
|
+
recusado.motivo = e.motivo;
|
|
752
|
+
return { recusa: e.message, correcao: e.motivo === 'noctum.entrega.segredo'
|
|
753
|
+
? 'tire o padrão de segredo do pr-body.md da thread; o passo seguinte confere de novo' : 'o passo seguinte relê as provas e a supervisão' };
|
|
754
|
+
}
|
|
755
|
+
if (!(e instanceof noctum_envelope_1.RecusaDoArco))
|
|
756
|
+
throw e;
|
|
757
|
+
recusado.motivo = e.motivo;
|
|
758
|
+
return { recusa: e.message, correcao: 'o efeito fica para um envelope vigente que o autorize' };
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
return { conferir, recusado };
|
|
762
|
+
}
|
|
763
|
+
function publicar(carregado, arco, estado, trabalho, lido, opcoes, remoto, rastro) {
|
|
764
|
+
const ids = { trabalho: trabalho.id, thread: lido.t.id };
|
|
765
|
+
const envelope = conferirParaPublicar(carregado.raiz, arco, estado, lido.destino, (0, worktree_1.branchDaWorktree)(lido.t), trabalho.id, lido.entrada.provas?.fonte ?? null, opcoes.relogio, rastro);
|
|
766
|
+
const r = (0, ship_publicar_1.publicarEntrega)(carregado, lido.t.id, { remoto, forja: opcoes.forja, executorGh: opcoes.executorGh, antesDoEfeito: envelope.conferir,
|
|
767
|
+
doArco: arco });
|
|
768
|
+
const ate = depoisDe(opcoes.quando ?? new Date().toISOString(), exports.ESPERA_DO_CI_MS);
|
|
769
|
+
if (r.acao === 'publicou')
|
|
770
|
+
return { acao: 'publicou', ...ids, pr: r.pr?.numero ?? null, head: r.head };
|
|
771
|
+
if (r.acao === 'ja-publicada')
|
|
772
|
+
return { acao: 'aguardar', ...ids, motivo: 'noctum.entrega.ci-pendente', detalhe: r.detalhe, ate };
|
|
773
|
+
// A recusa do envelope sai com o motivo dele (revogado, expirado, fora do destino); a da supervisão e a da fonte que
|
|
774
|
+
// andou, com o da entrega; a do publicar, com o dele.
|
|
775
|
+
const motivo = envelope.recusado.motivo ?? `publicar.${r.motivo ?? 'recusada'}`;
|
|
776
|
+
const detalhe = `${r.detalhe}${r.correcao ? ` (${r.correcao})` : ''}`;
|
|
777
|
+
if (motivo === 'noctum.entrega.fonte-mudou' || RECUSAS_QUE_ESPERAM.includes(motivo)) {
|
|
778
|
+
return { acao: 'aguardar', ...ids, motivo, detalhe, ate };
|
|
779
|
+
}
|
|
780
|
+
return { acao: 'bloqueado', ...ids, motivo, detalhe, outros: [] };
|
|
781
|
+
}
|
|
782
|
+
// ---------------------------------------------------------------------------
|
|
783
|
+
// O despacho do CHECK independente (D1, D2): o caminho curto do despacho da N2, com as primitivas exportadas.
|
|
784
|
+
// ---------------------------------------------------------------------------
|
|
785
|
+
function vezDeDespachar(raiz, esperarMs) {
|
|
786
|
+
const principal = (0, estado_thread_1.raizDoEstado)(raiz);
|
|
787
|
+
(0, estado_thread_1.exigirEstadoSemLink)(principal, ['.orkastery', 'noctum']);
|
|
788
|
+
const dir = path.join(principal, '.orkastery', 'noctum');
|
|
789
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
790
|
+
const prazo = Date.now() + esperarMs;
|
|
791
|
+
for (;;) {
|
|
792
|
+
const fd = (0, fila_pesada_1.tentarTravaExclusiva)(path.join(dir, 'despacho.lock'));
|
|
793
|
+
if (fd !== null)
|
|
794
|
+
return fd;
|
|
795
|
+
if (Date.now() >= prazo)
|
|
796
|
+
return null;
|
|
797
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
function perfisDisponiveis(raiz, agoraMs) {
|
|
801
|
+
try {
|
|
802
|
+
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 }));
|
|
803
|
+
}
|
|
804
|
+
catch {
|
|
805
|
+
return [];
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* O CHECK numa sessão nova, pela reserva de revisão do arco. Tudo o que decide sai sob a trava de despacho (A10 da rodada
|
|
810
|
+
* 1): o arco relido do disco, a contagem de revisões do trabalho e o relógio de agora (B2: a espera pela vez pode passar do
|
|
811
|
+
* fim da janela). O rastro marca o efeito quando o despacho chega ao núcleo, e volta a falso quando ele recusa antes de lançar.
|
|
812
|
+
*/
|
|
813
|
+
function despacharRevisao(carregado, arco, trabalho, t, revisoesNaDecisao, opcoes, rastro) {
|
|
814
|
+
const { raiz } = carregado;
|
|
815
|
+
const ids = { trabalho: trabalho.id, thread: t.id };
|
|
816
|
+
// R2-A4 da rodada 2: o relógio da hora da reserva, e não o instante de quem chamou o passo.
|
|
817
|
+
const agora = () => opcoes.relogio?.() ?? new Date().toISOString();
|
|
818
|
+
const aguardar = (motivo, detalhe) => ({ acao: 'aguardar', ...ids, motivo, detalhe, ate: depoisDe(agora(), exports.ESPERA_DO_CI_MS) });
|
|
819
|
+
const bloquear = (motivo, detalhe) => ({ acao: 'bloqueado', ...ids, motivo, detalhe, outros: [] });
|
|
820
|
+
if (!(0, fila_pesada_1.kernelDisponivel)())
|
|
821
|
+
return aguardar('noctum.entrega.sem-trava-do-kernel', 'o arco só despacha com /usr/bin/flock');
|
|
822
|
+
const fd = vezDeDespachar(raiz, opcoes.esperarMs ?? 30_000);
|
|
823
|
+
if (fd === null)
|
|
824
|
+
return aguardar('noctum.entrega.despacho-em-curso', 'outro passo de arco desta máquina segura a vez de despachar');
|
|
825
|
+
try {
|
|
826
|
+
const quando = agora();
|
|
827
|
+
let estado;
|
|
828
|
+
try {
|
|
829
|
+
estado = (0, noctum_arco_1.lerArco)(raiz, arco, quando);
|
|
830
|
+
}
|
|
831
|
+
catch (e) {
|
|
832
|
+
if (!(e instanceof noctum_envelope_1.RecusaDoArco))
|
|
833
|
+
throw e;
|
|
834
|
+
return bloquear(e.motivo, e.message);
|
|
835
|
+
}
|
|
836
|
+
const conteudo = estado?.conteudo;
|
|
837
|
+
if (!estado || !conteudo || !(0, noctum_envelope_1.envelopeComTrabalhos)(conteudo))
|
|
838
|
+
return bloquear('noctum.entrega.fora-do-envelope', 'o arco relido sob a trava não tem envelope v2');
|
|
839
|
+
if (revisoesDoTrabalho(estado, trabalho.id) !== revisoesNaDecisao) {
|
|
840
|
+
return aguardar('noctum.entrega.revisao-em-curso', 'outro passo despachou o CHECK deste trabalho enquanto este esperava a vez');
|
|
841
|
+
}
|
|
842
|
+
// R2-B1 da rodada 2: sob a trava, a supervisão relida: o arco parado e o trabalho isolado não despacham CHECK, e a
|
|
843
|
+
// conta que o arco tirou não entra na escolha.
|
|
844
|
+
let sup;
|
|
845
|
+
try {
|
|
846
|
+
sup = supervisaoDoArco(raiz, arco);
|
|
847
|
+
}
|
|
848
|
+
catch (e) {
|
|
849
|
+
if (!(e instanceof noctum_provas_1.RecusaDaEntrega))
|
|
850
|
+
throw e;
|
|
851
|
+
return aguardar(e.motivo, e.message);
|
|
852
|
+
}
|
|
853
|
+
if (sup.parada)
|
|
854
|
+
return bloquear('noctum.entrega.arco-parado', `a supervisão parou o arco (${sup.parada.motivo}): ${sup.parada.detalhe}`);
|
|
855
|
+
const isolado = sup.isolados.get(trabalho.id);
|
|
856
|
+
if (isolado)
|
|
857
|
+
return bloquear('noctum.entrega.isolado', `o trabalho ${trabalho.id} foi isolado pela supervisão (${isolado.motivo}): ${isolado.detalhe}`);
|
|
858
|
+
const fase = 'CHECK';
|
|
859
|
+
const runtime = (0, phase_1.resolverDespacho)(carregado.manifesto, {}, (0, setup_1.configDoBloco)((0, setup_1.lerSetup)(raiz), t.modo, fase)).runtime;
|
|
860
|
+
const capacidade = (0, noctum_capacidade_1.fatosDeCapacidade)(carregado, { quando, consultar: opcoes.consultarSessoes });
|
|
861
|
+
const perfis = (opcoes.perfis ?? (() => perfisDisponiveis(raiz, Date.parse(quando))))().filter(p => !(0, noctum_supervisao_1.contaForaAgora)(sup, p.id, quando));
|
|
862
|
+
const doRuntime = perfis.filter(p => p.runtime === runtime).map(p => p.id);
|
|
863
|
+
const conta = (0, noctum_capacidade_1.escolherConta)(conteudo.capacidade, capacidade, doRuntime)
|
|
864
|
+
?? conteudo.capacidade.porConta.find(c => doRuntime.includes(c.conta))?.conta ?? null;
|
|
865
|
+
if (!conta)
|
|
866
|
+
return aguardar('capacidade.conta-nao-autorizada', `nenhuma conta do envelope é perfil disponível do runtime ${runtime}`);
|
|
867
|
+
const admissao = (0, noctum_capacidade_1.admitir)({ runtime, conta, revisao: true }, conteudo.capacidade, capacidade, (0, board_1.vagaDoDespacho)(carregado, t.id, quando));
|
|
868
|
+
if (!admissao.ok)
|
|
869
|
+
return aguardar(admissao.motivo, admissao.detalhe);
|
|
870
|
+
let reserva;
|
|
871
|
+
try {
|
|
872
|
+
reserva = (0, noctum_arco_1.reservarDespacho)(raiz, { arco, envelopeSha256: estado.envelopeSha256 ?? '', trabalho: trabalho.id, thread: t.id, fase,
|
|
873
|
+
runtime, conta, revisao: true }, quando).seq;
|
|
874
|
+
}
|
|
875
|
+
catch (e) {
|
|
876
|
+
if (!(e instanceof noctum_envelope_1.RecusaDoArco))
|
|
877
|
+
throw e;
|
|
878
|
+
return bloquear(e.motivo, e.message);
|
|
879
|
+
}
|
|
880
|
+
const devolver = (motivo, detalhe, bloqueia) => {
|
|
881
|
+
(0, noctum_arco_1.concluirDespacho)(raiz, arco, reserva, { motivo });
|
|
882
|
+
rastro.efeito = false;
|
|
883
|
+
return bloqueia ? bloquear(motivo, detalhe) : aguardar(motivo, detalhe);
|
|
884
|
+
};
|
|
885
|
+
const pedido = (0, thread_1.pedidoDaThread)(raiz, t);
|
|
886
|
+
if (!pedido)
|
|
887
|
+
return devolver('noctum.entrega.pedido-ausente', `a thread ${t.id} não guardou o pedido`, true);
|
|
888
|
+
const antes = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, t.id)).length;
|
|
889
|
+
rastro.efeito = true;
|
|
890
|
+
// RM-058, integração (D5): a sessão da revisão recebe a linha do arco no pedido, como a do passo da N2.
|
|
891
|
+
const r = (opcoes.despachar ?? phase_1.rodarFase)(carregado, t.id, { fase, prompt: (0, noctum_guarda_1.pedidoDoArco)(pedido.texto, arco), perfil: conta,
|
|
892
|
+
canal: (0, conducao_1.canalDoProcesso)(), correlacao: `noctum:${arco}`, baselinePeloDespacho: false });
|
|
893
|
+
if (r.motivo === 'baseline.pendente')
|
|
894
|
+
return devolver('noctum.entrega.baseline-ausente', 'o despacho do CHECK pediu a baseline', true);
|
|
895
|
+
if (r.bloqueado)
|
|
896
|
+
return devolver(r.motivo ?? 'noctum.entrega.despacho-recusado', r.erro ?? 'o núcleo recusou o despacho do CHECK', false);
|
|
897
|
+
if (r.sessionId || r.idempotente) {
|
|
898
|
+
(0, noctum_arco_1.concluirDespacho)(raiz, arco, reserva, { thread: t.id, sessionId: r.sessionId, idempotente: r.idempotente === true });
|
|
899
|
+
return { acao: 'revisao-pedida', ...ids, sessionId: r.sessionId, reserva, conta, runtime };
|
|
900
|
+
}
|
|
901
|
+
// Como na N2: sem id e com motivo de antes do lançamento, a sessão volta ao orçamento; o resto fica pendente para a N4.
|
|
902
|
+
const novos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, t.id)).slice(antes);
|
|
903
|
+
const idDaFalha = novos.filter(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.despachoFalhou && typeof e.sessionId === 'string').at(-1)?.sessionId ?? null;
|
|
904
|
+
if (!idDaFalha && r.motivo && r.motivo !== 'runtime.unavailable')
|
|
905
|
+
return devolver(r.motivo, r.erro ?? 'o runtime recusou o despacho', false);
|
|
906
|
+
return aguardar('noctum.entrega.despacho-incerto', `${r.erro ?? 'o núcleo não abriu sessão'}; a reserva fica pendente para a N4 reconciliar`);
|
|
907
|
+
}
|
|
908
|
+
finally {
|
|
909
|
+
fs.closeSync(fd);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
/** O arquivo da trava do kernel do destino nesta máquina: um integrador por repositório e base, entre todos os arcos. */
|
|
913
|
+
function caminhoDaTravaDoDestino(raiz, destino) {
|
|
914
|
+
const principal = (0, estado_thread_1.raizDoEstado)(raiz);
|
|
915
|
+
(0, estado_thread_1.exigirEstadoSemLink)(principal, ['.orkastery', 'noctum']);
|
|
916
|
+
const dir = path.join(principal, '.orkastery', 'noctum');
|
|
917
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
918
|
+
const chave = (0, node_crypto_1.createHash)('sha256').update(`${destino.repositorio.toLowerCase()}\n${destino.base}`).digest('hex').slice(0, 16);
|
|
919
|
+
return path.join(dir, `integracao-${chave}.lock`);
|
|
920
|
+
}
|
|
921
|
+
const travaDoDestino = (raiz, destino) => (0, fila_pesada_1.tentarTravaExclusiva)(caminhoDaTravaDoDestino(raiz, destino));
|
|
922
|
+
/** A thread chega ao SHIP quando o ciclo dela tem SHIP, e só avança (como no `ork ship registrar-pr`). */
|
|
923
|
+
function avancarParaShip(raiz, threadId) {
|
|
924
|
+
const t = (0, thread_1.lerThread)(raiz, threadId);
|
|
925
|
+
const fases = t.fases ?? [];
|
|
926
|
+
if (!fases.includes('SHIP') || fases.indexOf(t.faseAtual) >= fases.indexOf('SHIP'))
|
|
927
|
+
return;
|
|
928
|
+
(0, thread_1.gravarThread)(raiz, { ...t, faseAtual: 'SHIP' });
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* D10: a página do item que deixaria a `main` vermelha (`sdlc.thread` desta thread e `estado.codigo` fora de `Mesclado`)
|
|
932
|
+
* recebe o estado derivado num commit em cima do merge: o `docs sincronizar` dos itens dela, numa worktree temporária com uma
|
|
933
|
+
* branch temporária no merge como base, conferido pelo `docs verificar` estrito antes de voltar.
|
|
934
|
+
*/
|
|
935
|
+
function docsDerivados(raiz, threadId, merge, arco) {
|
|
936
|
+
const pendentes = (0, docs_1.docsPendentesDoMerge)(raiz, threadId, merge);
|
|
937
|
+
const itens = [...new Set(pendentes.map(p => p.id))];
|
|
938
|
+
if (!itens.length)
|
|
939
|
+
return { commit: null, itens: [] };
|
|
940
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ork-noctum-docs-'));
|
|
941
|
+
const ramo = `noctum-entrega-${(0, node_crypto_1.randomUUID)().slice(0, 8)}`;
|
|
942
|
+
try {
|
|
943
|
+
if (!git(raiz, ['worktree', 'add', '--detach', tmp, merge]).ok)
|
|
944
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.docs-vermelhos', 'a worktree temporária dos docs não nasceu');
|
|
945
|
+
if (!git(raiz, ['branch', ramo, merge]).ok)
|
|
946
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.docs-vermelhos', 'a branch temporária dos docs não nasceu');
|
|
947
|
+
(0, docs_1.sincronizarDocs)(tmp, { baseBranch: ramo, itens, escrever: true });
|
|
948
|
+
const estado = git(tmp, ['status', '--porcelain', '-z']);
|
|
949
|
+
if (!estado.ok)
|
|
950
|
+
throw lacuna(`o estado da worktree dos docs derivados não se leu: ${primeira(estado.stderr)}`);
|
|
951
|
+
const mudados = estado.stdout.split('\0').filter(Boolean).map(l => l.slice(3));
|
|
952
|
+
if (!mudados.length)
|
|
953
|
+
return { commit: null, itens };
|
|
954
|
+
if (!git(tmp, ['add', '--', ...mudados]).ok)
|
|
955
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.docs-vermelhos', 'o git add dos docs derivados falhou');
|
|
956
|
+
const c = git(tmp, ['commit', '-q', '-m', `docs(${threadId}): ${itens.join(', ')} com o estado depois do merge (arco ${arco})`]);
|
|
957
|
+
if (!c.ok)
|
|
958
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.docs-vermelhos', `o commit dos docs derivados falhou: ${primeira(c.stderr)}`);
|
|
959
|
+
const commit = git(tmp, ['rev-parse', 'HEAD']).stdout.trim();
|
|
960
|
+
if (!SHA.test(commit))
|
|
961
|
+
throw lacuna('o commit dos docs derivados não se leu');
|
|
962
|
+
git(raiz, ['branch', '-f', ramo, commit]);
|
|
963
|
+
const ruins = (0, docs_1.verificarDocs)(tmp, { baseBranch: ramo }).achados
|
|
964
|
+
.filter(a => a.gravidade === 'erro' && ((a.id !== undefined && itens.includes(a.id)) || a.regra === 'docs.paridade.indice'));
|
|
965
|
+
if (ruins.length) {
|
|
966
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.docs-vermelhos', `o estado derivado ainda reprova: ${ruins.slice(0, 3).map(a => `${a.regra} (${a.id ?? a.arquivo})`).join('; ')}`);
|
|
967
|
+
}
|
|
968
|
+
return { commit, itens };
|
|
969
|
+
}
|
|
970
|
+
finally {
|
|
971
|
+
git(raiz, ['worktree', 'remove', '--force', tmp]);
|
|
972
|
+
git(raiz, ['branch', '-D', ramo]);
|
|
973
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
974
|
+
git(raiz, ['worktree', 'prune']);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* D9: a integração amarrada ao SHA, sob a trava do destino. Relê o envelope, a base pelo `ls-remote`, a fonte pela ref
|
|
979
|
+
* local e pela remota, e todas as provas; o merge tem a árvore da fonte e os pais base e fonte; a conferência da N1 com
|
|
980
|
+
* `merge` vem depois do merge e dos docs derivados, imediatamente antes do push; o push vai amarrado à ponta conferida, então
|
|
981
|
+
* a base que andou recusa; o `ls-remote` prova. Lança a recusa tipada.
|
|
982
|
+
*/
|
|
983
|
+
function integrarNoDestino(carregado, arco, threadId, opcoes = {}) {
|
|
984
|
+
const { raiz } = carregado;
|
|
985
|
+
// B2 da rodada 1 e R2-A4 da rodada 2: cada leitura do arco e cada conferência usa o relógio da hora dela; o instante de
|
|
986
|
+
// quem chamou o passo (`quando`) nunca vale aqui.
|
|
987
|
+
const relogio = () => opcoes.relogio?.();
|
|
988
|
+
const remoto = opcoes.remoto ?? 'origin';
|
|
989
|
+
const estado = (0, noctum_arco_1.lerArco)(raiz, arco, relogio());
|
|
990
|
+
if (!estado?.conteudo || !(0, noctum_envelope_1.envelopeComTrabalhos)(estado.conteudo))
|
|
991
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.fora-do-destino', 'o arco não tem envelope v2 vigente');
|
|
992
|
+
if (estado.conteudo.contrato !== noctum_envelope_1.CONTRATO_ENVELOPE_V2 && !opcoes.esperado) {
|
|
993
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.exige-vez-da-n3', 'fora da v2, a integração é da vez entre máquinas (N3)');
|
|
994
|
+
}
|
|
995
|
+
// S5 da rodada 1 e R2-S3 da rodada 2: só integra a thread que é trabalho do envelope, pela regra da fila (a thread
|
|
996
|
+
// fixada, ou o item com o sha256 do pedido).
|
|
997
|
+
const daThread = (0, thread_1.lerThread)(raiz, threadId);
|
|
998
|
+
const trabalhosDoEnvelope = trabalhosDaThread(estado.conteudo, daThread).map(w => w.id);
|
|
999
|
+
if (!trabalhosDoEnvelope.length) {
|
|
1000
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.fora-do-envelope', `a thread ${threadId} (item ${daThread.roadmap ?? 'nenhum'}) não é trabalho do envelope do arco ${arco}`);
|
|
1001
|
+
}
|
|
1002
|
+
// R2-B1 da rodada 2: o trabalho que a supervisão isolou, ou o arco que ela parou, não integra, nem pela vez da N3.
|
|
1003
|
+
conferirSupervisao(raiz, arco, trabalhosDoEnvelope);
|
|
1004
|
+
const d = destinoDoTrabalho(carregado, estado.conteudo.destinos, remoto, opcoes.forja);
|
|
1005
|
+
if (!d.ok)
|
|
1006
|
+
throw new noctum_provas_1.RecusaDaEntrega(d.motivo, d.detalhe);
|
|
1007
|
+
if (!d.podeMerge)
|
|
1008
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.fora-do-destino', 'o envelope não autoriza merge neste destino');
|
|
1009
|
+
const destino = d.destino;
|
|
1010
|
+
if (opcoes.esperado?.repositorio !== undefined && opcoes.esperado.repositorio.toLowerCase() !== destino.repositorio.toLowerCase()) {
|
|
1011
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.fora-do-destino', `o candidato é do repositório ${opcoes.esperado.repositorio}, e o destino é ${destino.repositorio}`);
|
|
1012
|
+
}
|
|
1013
|
+
if (!(0, fila_pesada_1.kernelDisponivel)())
|
|
1014
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.integracao-ocupada', 'sem /usr/bin/flock, nenhuma integração passa');
|
|
1015
|
+
const trava = travaDoDestino(raiz, destino);
|
|
1016
|
+
if (trava === null)
|
|
1017
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.integracao-ocupada', `outro processo desta máquina integra em ${destino.repositorio}, base ${destino.base}`);
|
|
1018
|
+
try {
|
|
1019
|
+
const t = (0, thread_1.lerThread)(raiz, threadId);
|
|
1020
|
+
const branch = (0, worktree_1.branchDaWorktree)(t);
|
|
1021
|
+
const ponta = (0, noctum_provas_1.pontaDaBaseNoRemoto)(raiz, remoto, destino.base);
|
|
1022
|
+
if (!ponta.sha)
|
|
1023
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.base-sem-leitura', ponta.erro ?? 'a base não se leu');
|
|
1024
|
+
const provas = (0, noctum_provas_1.provasDaEntrega)(carregado, threadId, { remoto, base: destino.base, forja: opcoes.forja, executorCi: opcoes.executorCi, pontaDaBase: ponta });
|
|
1025
|
+
const falha = provas.provas.find(p => !p.ok);
|
|
1026
|
+
if (falha)
|
|
1027
|
+
throw new noctum_provas_1.RecusaDaEntrega(falha.motivo ?? 'noctum.entrega.nao-provada', `${falha.id}: ${falha.detalhe}`);
|
|
1028
|
+
const fonte = provas.fonte;
|
|
1029
|
+
if (opcoes.esperado) {
|
|
1030
|
+
if (opcoes.esperado.fonte !== fonte)
|
|
1031
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.fonte-mudou', `a fonte é ${curto(fonte)}, e o candidato ${curto(opcoes.esperado.fonte)}`);
|
|
1032
|
+
if (opcoes.esperado.base !== ponta.sha)
|
|
1033
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.base-mudou', `a base é ${curto(ponta.sha)}, e o candidato ${curto(opcoes.esperado.base)}`);
|
|
1034
|
+
if (opcoes.esperado.provas !== provas.sha256)
|
|
1035
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.nao-provada', 'as provas de agora não são as do candidato');
|
|
1036
|
+
}
|
|
1037
|
+
// O CI rodou no PR da branch: a branch no remoto precisa ser a fonte.
|
|
1038
|
+
const lida = git(raiz, ['ls-remote', '--', remoto, `refs/heads/${branch}`]);
|
|
1039
|
+
if (!lida.ok)
|
|
1040
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.base-sem-leitura', `a branch ${branch} não se leu no remoto: ${primeira(lida.stderr)}`);
|
|
1041
|
+
const remota = lida.stdout.split('\n').map(l => l.trim().split(/\s+/)).find(p => p[1] === `refs/heads/${branch}`)?.[0] ?? null;
|
|
1042
|
+
if (remota !== fonte)
|
|
1043
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.fonte-mudou', `a branch ${branch} no remoto está em ${curto(remota)}, e a fonte provada é ${curto(fonte)}`);
|
|
1044
|
+
const passos = [`git ls-remote ${remoto} refs/heads/${destino.base}`, `git ls-remote ${remoto} refs/heads/${branch}`];
|
|
1045
|
+
const envelopeSha256 = estado.envelopeSha256 ?? '';
|
|
1046
|
+
const assunto = `ship(${threadId}): merge de ${branch} em ${destino.base}`;
|
|
1047
|
+
const corpo = [`thread: ${t.id} (${t.slug})`, `modo: ${(0, modos_1.tagDoModo)(t.modo)}`, `arco: ${arco} (envelope ${envelopeSha256.slice(0, 12)})`,
|
|
1048
|
+
`provas: ${provas.sha256}`, `verificado em: ${fonte}`].join('\n');
|
|
1049
|
+
const merge = git(raiz, ['commit-tree', `${fonte}^{tree}`, '-p', ponta.sha, '-p', fonte, '-m', assunto, '-m', corpo]);
|
|
1050
|
+
const mergeSha = merge.stdout.trim();
|
|
1051
|
+
if (!merge.ok || !SHA.test(mergeSha))
|
|
1052
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.nao-provada', `o commit do merge falhou: ${primeira(merge.stderr)}`);
|
|
1053
|
+
passos.push(`git commit-tree ${curto(fonte)}^{tree} -p ${curto(ponta.sha)} -p ${curto(fonte)}`);
|
|
1054
|
+
const docs = docsDerivados(raiz, threadId, mergeSha, arco);
|
|
1055
|
+
if (docs.commit)
|
|
1056
|
+
passos.push(`ork docs sincronizar --escrever --so ${docs.itens.join(',')} (commit ${curto(docs.commit)})`);
|
|
1057
|
+
const alvo = docs.commit ?? mergeSha;
|
|
1058
|
+
// B2 da rodada 1: a supervisão e a N1 imediatamente antes do push, depois do merge e dos docs derivados, com o relógio
|
|
1059
|
+
// desta hora.
|
|
1060
|
+
conferirSupervisao(raiz, arco, [...trabalhosDoEnvelope]);
|
|
1061
|
+
(0, noctum_arco_1.conferirAntesDoEfeito)(raiz, { arco, envelopeSha256, acao: 'merge', repositorio: destino.repositorio, branch: destino.base }, relogio());
|
|
1062
|
+
// S3 da rodada 1: o lease amarra o push à ponta conferida. O alvo descende dela (o merge a tem como primeiro pai), então o
|
|
1063
|
+
// push é sempre de avanço; a base que andou, ou que foi forçada para trás, recusa.
|
|
1064
|
+
const lease = `--force-with-lease=refs/heads/${destino.base}:${ponta.sha}`;
|
|
1065
|
+
const push = git(raiz, ['push', lease, '--', remoto, `${alvo}:refs/heads/${destino.base}`], 180000);
|
|
1066
|
+
passos.push(`git push --force-with-lease=refs/heads/${destino.base}:${curto(ponta.sha)} -- ${remoto} ${curto(alvo)}:refs/heads/${destino.base}`);
|
|
1067
|
+
const depois = (0, noctum_provas_1.pontaDaBaseNoRemoto)(raiz, remoto, destino.base);
|
|
1068
|
+
passos.push(`git ls-remote ${remoto} refs/heads/${destino.base}`);
|
|
1069
|
+
const incerto = (detalhe) => {
|
|
1070
|
+
(0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, exports.EVENTO_DO_PUSH_INCERTO, { arco, base: destino.base, alvo, mergeSha, fonte, remoto,
|
|
1071
|
+
lido: depois.sha, provas: provas.sha256, falhasRegistradas: provas.falhasRegistradas,
|
|
1072
|
+
erro: primeira(push.stderr || depois.erro || '') });
|
|
1073
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.push-incerto', detalhe);
|
|
1074
|
+
};
|
|
1075
|
+
if (depois.sha !== alvo) {
|
|
1076
|
+
// O push que o git deu por feito sem a ponta no remoto, ou o remoto que não se leu: ninguém sabe se chegou.
|
|
1077
|
+
if (push.ok || !depois.sha)
|
|
1078
|
+
incerto('o push para a base não se provou; o passo seguinte relê o remoto antes de qualquer coisa');
|
|
1079
|
+
if (depois.sha !== ponta.sha) {
|
|
1080
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.base-mudou', `a base andou antes do push (${curto(depois.sha)}): o candidato se refaz sobre a base nova`);
|
|
1081
|
+
}
|
|
1082
|
+
// R2-A3 da rodada 2: só a recusa do remoto bloqueia sem repetir; a falha do transporte (a rede, o endereço, o prazo)
|
|
1083
|
+
// espera e tenta de novo na cadência, com as provas e a conferência refeitas.
|
|
1084
|
+
const linhaDaRecusa = recusaDoRemoto(push.stderr);
|
|
1085
|
+
if (!linhaDaRecusa) {
|
|
1086
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.git-falhou', `o push para ${destino.base} falhou sem recusa do remoto, com a base parada em ${curto(ponta.sha)}: ${primeira(push.stderr) || `código ${push.code}`}`);
|
|
1087
|
+
}
|
|
1088
|
+
// A4 da rodada 1: a base parada e o push recusado (a proteção da branch, um gancho do remoto): repetir não muda nada.
|
|
1089
|
+
(0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, exports.EVENTO_DO_PUSH_RECUSADO, { arco, base: ponta.sha, fonte, alvo, remoto, erro: linhaDaRecusa });
|
|
1090
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.push-recusado', `o remoto recusou o push para ${destino.base} com a base parada em ${curto(ponta.sha)}: ${linhaDaRecusa}`);
|
|
1091
|
+
}
|
|
1092
|
+
if (!git(raiz, ['merge-base', '--is-ancestor', fonte, alvo]).ok)
|
|
1093
|
+
incerto('a ponta da base não mostrou a fonte depois do push');
|
|
1094
|
+
const ci = provas.provas.find(p => p.id === 'ci').evidencia;
|
|
1095
|
+
(0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, ledger_1.TIPOS_DE_EVENTO.shipConcluido, {
|
|
1096
|
+
de: branch, para: destino.base, shaDe: fonte, shaParaAntes: ponta.sha, mergeSha, pontaDaBase: alvo, jaIncorporado: false,
|
|
1097
|
+
remoto, shaRemoto: depois.sha, pushVerificado: true, fonteDaProva: `git ls-remote ${remoto} refs/heads/${destino.base}`,
|
|
1098
|
+
autorizadoPor: `envelope ${envelopeSha256.slice(0, 12)} do arco ${arco} (#Noctum)`, tipoDeAutorizacao: 'noctum',
|
|
1099
|
+
modo: t.modo, tag: (0, modos_1.tagDoModo)(t.modo),
|
|
1100
|
+
evidencia: { arco, envelopeSha256, provas: { sha256: provas.sha256, itens: provas.provas.map(p => ({ id: p.id, ok: p.ok, detalhe: p.detalhe.slice(0, 200) })) },
|
|
1101
|
+
falhasRegistradas: provas.falhasRegistradas, ci, docs, passos },
|
|
1102
|
+
});
|
|
1103
|
+
avancarParaShip(raiz, threadId);
|
|
1104
|
+
(0, fabrica_publicar_1.publicarEmSegundoPlano)(raiz);
|
|
1105
|
+
return { merge: alvo, mergeSha, pontaDaBase: alvo, docs, provas: provas.sha256 };
|
|
1106
|
+
}
|
|
1107
|
+
finally {
|
|
1108
|
+
fs.closeSync(trava);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* A fonte que já está na base remota sem `ship_done` (o push incerto que chegou, ou o merge feito por outro caminho): o
|
|
1113
|
+
* registro sai com o commit que a incorporou e a ponta provada pelo `ls-remote`, sem integrar de novo. O push incerto da
|
|
1114
|
+
* mesma fonte, quando há, empresta ao registro as provas e as falhas que ele guardou (S6 da rodada 1).
|
|
1115
|
+
*/
|
|
1116
|
+
function reconciliarEntrega(carregado, arco, estado, threadId, remoto, destino, ponta) {
|
|
1117
|
+
const { raiz } = carregado;
|
|
1118
|
+
const t = (0, thread_1.lerThread)(raiz, threadId);
|
|
1119
|
+
const branch = (0, worktree_1.branchDaWorktree)(t);
|
|
1120
|
+
const ref = git(raiz, ['rev-parse', '--verify', `refs/heads/${branch}^{commit}`]);
|
|
1121
|
+
const fonte = ref.stdout.trim();
|
|
1122
|
+
if (!ref.ok || !SHA.test(fonte))
|
|
1123
|
+
throw lacuna(`a ponta da branch ${branch} não se leu`);
|
|
1124
|
+
const mergeSha = (0, ship_1.commitQueIncorporou)(raiz, fonte, ponta);
|
|
1125
|
+
if (!mergeSha)
|
|
1126
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.nao-provada', 'o commit que incorporou a fonte na base não se achou');
|
|
1127
|
+
const incerto = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, threadId)).filter(e => e.tipo === exports.EVENTO_DO_PUSH_INCERTO && e.fonte === fonte).at(-1) ?? null;
|
|
1128
|
+
// R2-S4 da rodada 2 e R3-S2 da rodada 3: quem incorporou se lê no commit que incorporou. O merge do arco leva
|
|
1129
|
+
// `arco: <arco> (envelope ...)` no corpo; sem isso, foi outro caminho (o dono, a batida da RM-063, um merge à mão), mesmo
|
|
1130
|
+
// com um push incerto do arco que não chegou.
|
|
1131
|
+
const corpoDoMerge = git(raiz, ['log', '-1', '--format=%B', mergeSha]);
|
|
1132
|
+
const doArco = corpoDoMerge.ok && corpoDoMerge.stdout.includes(`\narco: ${arco} (envelope `);
|
|
1133
|
+
const envelope = `envelope ${(estado.envelopeSha256 ?? '').slice(0, 12)} do arco ${arco} (#Noctum)`;
|
|
1134
|
+
(0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, ledger_1.TIPOS_DE_EVENTO.shipConcluido, {
|
|
1135
|
+
de: branch, para: destino.base, shaDe: fonte, mergeSha, pontaDaBase: ponta, jaIncorporado: true, remoto, shaRemoto: ponta,
|
|
1136
|
+
pushVerificado: true, fonteDaProva: `git merge-base --is-ancestor ${fonte} ${ponta} (ls-remote ${remoto} refs/heads/${destino.base})`,
|
|
1137
|
+
autorizadoPor: doArco ? `${envelope}, reconciliado` : `merge ${curto(mergeSha)} de fora do arco, registrado pelo ${envelope}`,
|
|
1138
|
+
tipoDeAutorizacao: 'noctum', modo: t.modo, tag: (0, modos_1.tagDoModo)(t.modo),
|
|
1139
|
+
evidencia: { arco, reconciliado: true, incorporadaPor: doArco ? 'arco' : 'outro-caminho',
|
|
1140
|
+
pushIncerto: incerto ? { eventId: incerto.eventId, alvo: incerto.alvo, provas: incerto.provas ?? null } : null,
|
|
1141
|
+
falhasRegistradas: Array.isArray(incerto?.falhasRegistradas) ? incerto.falhasRegistradas : [] },
|
|
1142
|
+
});
|
|
1143
|
+
avancarParaShip(raiz, threadId);
|
|
1144
|
+
(0, fabrica_publicar_1.publicarEmSegundoPlano)(raiz);
|
|
1145
|
+
return { mergeSha, pontaDaBase: ponta };
|
|
1146
|
+
}
|
|
1147
|
+
/** O candidato que a N3 leva à vez de integrar: a branch, a fonte, a base e o sha256 das provas. */
|
|
1148
|
+
function candidatoDaEntrega(provas) {
|
|
1149
|
+
return provas.ok && provas.fonte && provas.base.sha ? { branch: provas.branch, fonte: provas.fonte, base: provas.base.sha, provas: provas.sha256 } : null;
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* D9: a mesma integração na forma do `Integrador` da N3 (`integrarComVez`): recebe o remoto, o destino e o candidato já
|
|
1153
|
+
* revalidados pela vez, confere que o repositório, a base, a fonte e as provas são as do candidato e devolve o merge
|
|
1154
|
+
* (`mergeSha`), que a N3 confere no remoto. Os docs derivados, quando há, vão por cima dele no mesmo push, e a ponta da base
|
|
1155
|
+
* não é o merge (A-R3D-1 da revisão da ork-rm058n3).
|
|
1156
|
+
*/
|
|
1157
|
+
function integradorDaEntrega(carregado, arco, threadId, opcoes = {}) {
|
|
1158
|
+
return (c) => {
|
|
1159
|
+
const branch = (0, worktree_1.branchDaWorktree)((0, thread_1.lerThread)(carregado.raiz, threadId));
|
|
1160
|
+
if (c.candidato.branch !== branch)
|
|
1161
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.fonte-mudou', `o candidato é da branch ${c.candidato.branch}, e a thread é ${branch}`);
|
|
1162
|
+
if (c.destino.base !== carregado.manifesto.worktree.base_branch) {
|
|
1163
|
+
throw new noctum_provas_1.RecusaDaEntrega('noctum.entrega.fora-do-destino', `a base ${c.destino.base} não é a do projeto`);
|
|
1164
|
+
}
|
|
1165
|
+
const r = integrarNoDestino(carregado, arco, threadId, { ...opcoes, remoto: c.remoto,
|
|
1166
|
+
esperado: { fonte: c.candidato.fonte, base: c.candidato.base, provas: c.candidato.provas, repositorio: c.destino.repositorio } });
|
|
1167
|
+
return { merge: r.mergeSha };
|
|
1168
|
+
};
|
|
1169
|
+
}
|