@orkastery/cli 0.5.6 → 0.6.1
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 +123 -9
- 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/adapters/grok-events.js +227 -0
- package/dist/adapters/grok-mcp-servidor.js +612 -0
- package/dist/adapters/grok-mcp.js +178 -0
- package/dist/adapters/grok.js +686 -0
- package/dist/agendador-cpu.js +92 -0
- 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 +23 -10
- 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 +528 -0
- package/dist/check-modo.js +41 -0
- package/dist/check-paralelo.js +238 -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 +89 -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 +89 -89
- package/dist/doctor-forja.js +343 -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 +154 -54
- 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 +33 -21
- 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 +20 -1
- package/dist/hosts.js +104 -134
- package/dist/index.js +864 -554
- package/dist/indice-vetorial.js +14 -12
- package/dist/init.js +11 -2
- package/dist/integracoes-locais.js +4 -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 +228 -0
- package/dist/locale-adapter.js +291 -0
- package/dist/locale-ajuda.js +86 -10
- 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 +288 -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 +183 -6
- package/dist/locale-security-scan.js +69 -0
- package/dist/locale-setup.js +192 -0
- package/dist/locale-ship.js +177 -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 +293 -12
- 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 +90 -15
- package/dist/mcp-noctum.js +101 -0
- package/dist/mcp-server.js +116 -22
- 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/merge-da-entrega.js +85 -0
- package/dist/modos-migracao.js +8 -6
- package/dist/modos.js +17 -18
- package/dist/network-roadmap.js +94 -54
- package/dist/noctum-aceite.js +929 -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 +539 -0
- package/dist/noctum-coordenacao.js +1225 -0
- package/dist/noctum-entrega.js +1217 -0
- package/dist/noctum-envelope.js +253 -17
- package/dist/noctum-execucao.js +100 -24
- package/dist/noctum-fechamento.js +416 -0
- package/dist/noctum-fila.js +49 -10
- package/dist/noctum-guarda.js +333 -0
- package/dist/noctum-integracao.js +507 -0
- package/dist/noctum-maquinas.js +387 -0
- package/dist/noctum-medicao.js +450 -0
- package/dist/noctum-modo.js +390 -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 +594 -0
- package/dist/noctum-rede.js +102 -0
- package/dist/noctum-registro.js +215 -0
- package/dist/noctum-relatorio.js +861 -0
- package/dist/noctum-resposta.js +207 -0
- package/dist/noctum-retomada-rede.js +71 -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 +679 -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 +160 -139
- package/dist/phase.js +96 -42
- package/dist/policies.js +33 -0
- package/dist/postura-local.js +11 -6
- package/dist/preflight.js +2 -1
- 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 +13 -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 +326 -78
- package/dist/roadmap-reservas.js +32 -32
- package/dist/runtime-ambiente.js +5 -0
- package/dist/runtime-context.js +279 -85
- package/dist/runtime-escada.js +429 -0
- package/dist/runtime-profiles.js +130 -63
- package/dist/runtimes.js +42 -5
- package/dist/saida-segura.js +3 -2
- package/dist/security-scan/agil.js +366 -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 +196 -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-grok.js +425 -0
- package/dist/session-watcher.js +174 -13
- package/dist/sessoes-contas.js +42 -5
- package/dist/sessoes-inventario.js +1 -1
- package/dist/sessoes.js +25 -23
- package/dist/setup.js +67 -66
- 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 +500 -0
- package/dist/verify.js +491 -32
- 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 +10 -5
- 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,1225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.motivoDaReservaDeFora = exports.conferirCoordenacao = exports.TETO_DE_VAGAS_NO_CHECKPOINT = exports.ordemViva = exports.notaDaReserva = exports.BATIDA_VENCIDA_MS = void 0;
|
|
4
|
+
exports.motivoPublico = motivoPublico;
|
|
5
|
+
exports.chaveDoDestino = chaveDoDestino;
|
|
6
|
+
exports.seloDaGeracao = seloDaGeracao;
|
|
7
|
+
exports.ordemDaRaiz = ordemDaRaiz;
|
|
8
|
+
exports.derivarCoordenacao = derivarCoordenacao;
|
|
9
|
+
exports.lerCoordenacao = lerCoordenacao;
|
|
10
|
+
exports.gravarNaCoordenacao = gravarNaCoordenacao;
|
|
11
|
+
exports.situacaoDoTrabalho = situacaoDoTrabalho;
|
|
12
|
+
exports.entreguesNaRede = entreguesNaRede;
|
|
13
|
+
exports.encerradosNaRede = encerradosNaRede;
|
|
14
|
+
exports.batidaRecente = batidaRecente;
|
|
15
|
+
exports.assumirAutoridade = assumirAutoridade;
|
|
16
|
+
exports.cederAutoridade = cederAutoridade;
|
|
17
|
+
exports.reservasDoArco = reservasDoArco;
|
|
18
|
+
exports.vistasDasReservas = vistasDasReservas;
|
|
19
|
+
exports.emitirOrdem = emitirOrdem;
|
|
20
|
+
exports.distribuirOrdens = distribuirOrdens;
|
|
21
|
+
exports.registrosDaDistribuicao = registrosDaDistribuicao;
|
|
22
|
+
exports.registrosDosAbandonos = registrosDosAbandonos;
|
|
23
|
+
exports.cancelarOrdem = cancelarOrdem;
|
|
24
|
+
exports.respostaAOrdem = respostaAOrdem;
|
|
25
|
+
exports.registrosDasRespostas = registrosDasRespostas;
|
|
26
|
+
exports.responderOrdem = responderOrdem;
|
|
27
|
+
exports.concluirOrdem = concluirOrdem;
|
|
28
|
+
exports.encerrarOrdem = encerrarOrdem;
|
|
29
|
+
exports.bater = bater;
|
|
30
|
+
exports.revogarNaRede = revogarNaRede;
|
|
31
|
+
exports.contasSemVagaGlobal = contasSemVagaGlobal;
|
|
32
|
+
exports.tomarVagaGlobal = tomarVagaGlobal;
|
|
33
|
+
exports.soltarVagaGlobal = soltarVagaGlobal;
|
|
34
|
+
exports.reservaDoItem = reservaDoItem;
|
|
35
|
+
exports.tomarReservaDoTrabalho = tomarReservaDoTrabalho;
|
|
36
|
+
exports.soltarReservaDoArco = soltarReservaDoArco;
|
|
37
|
+
exports.planejarReconciliacao = planejarReconciliacao;
|
|
38
|
+
exports.reconciliarCoordenacao = reconciliarCoordenacao;
|
|
39
|
+
/**
|
|
40
|
+
* RM-058, fatia N3 (D3, D4 e D8 a D12 da ork-rm058n3): a coordenação entre as máquinas de um arco #Noctum.
|
|
41
|
+
*
|
|
42
|
+
* Tudo aqui é regra sobre o ramo de coordenação que o transporte (`noctum-transporte.ts`) lê conferido e grava por troca
|
|
43
|
+
* atômica. O estado é derivado dos registros a cada leitura, nada fica em memória:
|
|
44
|
+
*
|
|
45
|
+
* - **autoridade por geração** (D8, D15): a primeira máquina que assume fica com a geração 1; a troca vem por cessão
|
|
46
|
+
* assinada da autoridade vigente ou por tomada forçada, com motivo, quando ela não dá sinal há 15 min ou saiu do
|
|
47
|
+
* envelope. A geração nova sela os registros da anterior, e o que a autoridade vencida gravar depois reprova a leitura;
|
|
48
|
+
* - **ordens, recibos e cancelamento** (D9, D10): uma ordem viva por trabalho, emitida só pela autoridade vigente, com a
|
|
49
|
+
* reserva do item lida agora (D19); o executor não age antes do aceite gravado no remoto, e o aceite e o cancelamento
|
|
50
|
+
* disputam a mesma troca atômica;
|
|
51
|
+
* - **transferência só com prova** (D4, D20): cancelada antes do aceite, recusada, concluída ou encerrada pelo executor, a
|
|
52
|
+
* ordem libera o trabalho; aceita e cancelada sem o fim confirmado, o trabalho fica isolado. Falta de sinal nunca solta
|
|
53
|
+
* ordem, vaga nem reserva; a vez de integrar de quem não dá sinal é abandonada pela autoridade, sem transferir nada;
|
|
54
|
+
* - **revogação em todas as máquinas** (D21): a revogação do dono numa máquina vai ao ramo, e nada novo começa em nenhuma;
|
|
55
|
+
* - **vagas e orçamento somando as máquinas** (D3, D12): cada despacho toma uma vaga global, que é também o consumo do
|
|
56
|
+
* arco, da conta e do runtime; a recusa antes do lançamento devolve. A conta vai ao remoto pela posição dela na lista do
|
|
57
|
+
* envelope (D14), nunca pelo nome;
|
|
58
|
+
* - **reserva do item** (D11, D19): a da RM-047, tomada depois do aceite, sem forçar, e revalidada com leitura nova; o
|
|
59
|
+
* motivo gravado leva só o item, nunca quem reservou.
|
|
60
|
+
*
|
|
61
|
+
* Ninguém chama estas funções ainda: o passo da máquina (`noctum-maquinas.ts`) as junta, e quem chama o passo é o vigia
|
|
62
|
+
* da N4. A N4 usa a mesma interface para a retomada em outra máquina (seção N3 da página da RM-058).
|
|
63
|
+
*/
|
|
64
|
+
const noctum_arco_1 = require("./noctum-arco");
|
|
65
|
+
const noctum_envelope_1 = require("./noctum-envelope");
|
|
66
|
+
const noctum_fila_1 = require("./noctum-fila");
|
|
67
|
+
const node_crypto_1 = require("node:crypto");
|
|
68
|
+
const maquina_1 = require("./maquina");
|
|
69
|
+
const noctum_transporte_1 = require("./noctum-transporte");
|
|
70
|
+
const roadmap_reservas_1 = require("./roadmap-reservas");
|
|
71
|
+
/** D8: a batida de outra máquina vence depois de três cadências do vigia (D3 do dono), 15 min. */
|
|
72
|
+
exports.BATIDA_VENCIDA_MS = 3 * noctum_fila_1.CADENCIA_MS;
|
|
73
|
+
/** A nota da reserva da RM-047 que o arco toma: é por ela que a reconciliação sabe qual reserva é do arco. */
|
|
74
|
+
const notaDaReserva = (arco) => `noctum:${arco}`;
|
|
75
|
+
exports.notaDaReserva = notaDaReserva;
|
|
76
|
+
const DESFECHOS = ['entregue', 'invalidado', 'desistiu', 'abandonada', 'sem-prova'];
|
|
77
|
+
const VIVAS = ['emitida', 'aceita', 'isolada'];
|
|
78
|
+
const ordemViva = (o) => VIVAS.includes(o.estado);
|
|
79
|
+
exports.ordemViva = ordemViva;
|
|
80
|
+
const adulterado = (detalhe) => new noctum_envelope_1.RecusaDoArco('noctum.coordenacao.adulterado', `${detalhe}; o ramo de coordenação exige inspeção`);
|
|
81
|
+
const recusa = (motivo, detalhe) => new noctum_envelope_1.RecusaDoArco(motivo, detalhe);
|
|
82
|
+
const objeto = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
83
|
+
const inteiro = (v, minimo = 1) => typeof v === 'number' && Number.isSafeInteger(v) && v >= minimo;
|
|
84
|
+
const linha = (v, teto = 300) => typeof v === 'string' && v.length <= teto && !/[\r\n]/.test(v);
|
|
85
|
+
const sha = (v) => typeof v === 'string' && /^[0-9a-f]{40,64}$/.test(v);
|
|
86
|
+
/**
|
|
87
|
+
* A3 da rodada 3: o motivo de quem chama a interface vai ao ramo de coordenação, que mora no remoto do projeto e pode ser
|
|
88
|
+
* público. Ele vai numa linha de até 300 caracteres e sem caminho absoluto, e-mail, id de sessão nem segredo nomeado; quem
|
|
89
|
+
* chama manda um motivo tipado, como os que a N3 grava (`noctum.*` e um detalhe só com ids).
|
|
90
|
+
*/
|
|
91
|
+
const FORA_DO_RAMO = [
|
|
92
|
+
[/(^|[\s"'(=:,;])~?\/[\w.-]/, 'um caminho absoluto'],
|
|
93
|
+
[/[\w.+-]+@[\w-]+\.[\w.-]+/, 'um e-mail'],
|
|
94
|
+
[/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i, 'um id de sessão'],
|
|
95
|
+
[/\b(token|secret|senha|password|passwd|api[_-]?key)\b\s*[=:]|\bbearer\s+\S{8,}/i, 'um segredo'],
|
|
96
|
+
];
|
|
97
|
+
function motivoPublico(motivo, oQue) {
|
|
98
|
+
const m = String(motivo ?? '').replace(/[\r\n]+/g, ' ').trim().slice(0, 300);
|
|
99
|
+
if (!m)
|
|
100
|
+
throw recusa('noctum.envelope.invalido', `${oQue} exige motivo`);
|
|
101
|
+
const fora = FORA_DO_RAMO.find(([re]) => re.test(m));
|
|
102
|
+
if (fora) {
|
|
103
|
+
throw recusa('noctum.envelope.invalido', `${oQue}: o motivo vai ao ramo de coordenação, que pode ser público, e traz ${fora[1]}; ` +
|
|
104
|
+
'mande um motivo tipado, só com ids');
|
|
105
|
+
}
|
|
106
|
+
return m;
|
|
107
|
+
}
|
|
108
|
+
const COMUNS = ['contrato', 'tipo', 'arco', 'envelopeSha256', 'por', 'em'];
|
|
109
|
+
/** A chave do destino de integração no ramo: o repositório sem caixa e a base. */
|
|
110
|
+
function chaveDoDestino(destino) {
|
|
111
|
+
return (0, node_crypto_1.createHash)('sha256').update(`ork.noctum-destino/v1|${destino.repositorio.toLowerCase()}|${destino.base}`).digest('hex').slice(0, 16);
|
|
112
|
+
}
|
|
113
|
+
/** Os campos do registro são exatamente os comuns, os obrigatórios e os opcionais presentes; cada um confere. */
|
|
114
|
+
function campos(d, caminho, obrigatorios, opcionais = {}) {
|
|
115
|
+
for (const k of Object.keys(d)) {
|
|
116
|
+
if (COMUNS.includes(k))
|
|
117
|
+
continue;
|
|
118
|
+
const regra = obrigatorios[k] ?? opcionais[k];
|
|
119
|
+
if (!regra)
|
|
120
|
+
throw adulterado(`${caminho} com campo fora do contrato (${k.slice(0, 40)})`);
|
|
121
|
+
if (!regra(d[k]))
|
|
122
|
+
throw adulterado(`${caminho} com ${k} fora do formato`);
|
|
123
|
+
}
|
|
124
|
+
for (const k of Object.keys(obrigatorios))
|
|
125
|
+
if (!(k in d))
|
|
126
|
+
throw adulterado(`${caminho} sem ${k}`);
|
|
127
|
+
}
|
|
128
|
+
const maquinasDe = (c) => (0, noctum_envelope_1.envelopeMultimaquina)(c) ? c.maquinas : [];
|
|
129
|
+
const trabalhoDe = (c, id) => c.trabalhos?.find(t => t.id === id);
|
|
130
|
+
/** O selo de uma geração: quantos e quais registros a autoridade dela assinou, pelo sha256 da lista ordenada (B1 da revisão). */
|
|
131
|
+
function seloDaGeracao(ids) {
|
|
132
|
+
const lista = [...ids].sort();
|
|
133
|
+
return { registros: lista.length, sha256: (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(lista)).digest('hex') };
|
|
134
|
+
}
|
|
135
|
+
const seloValido = (v) => objeto(v) && Object.keys(v).sort().join(',') === 'registros,sha256' &&
|
|
136
|
+
inteiro(v.registros, 0) && typeof v.sha256 === 'string' && /^[0-9a-f]{64}$/.test(v.sha256);
|
|
137
|
+
exports.TETO_DE_VAGAS_NO_CHECKPOINT = 64;
|
|
138
|
+
const checkpointValido = (v) => objeto(v) && Object.keys(v).every(k => k === 'thread' || k === 'vagasSoltas') &&
|
|
139
|
+
(v.thread === undefined || v.thread === null || linha(v.thread, 80)) &&
|
|
140
|
+
(v.vagasSoltas === undefined || (Array.isArray(v.vagasSoltas) && v.vagasSoltas.length <= exports.TETO_DE_VAGAS_NO_CHECKPOINT &&
|
|
141
|
+
v.vagasSoltas.every(x => typeof x === 'string' && noctum_transporte_1.ID_DA_VAGA.test(x))));
|
|
142
|
+
/**
|
|
143
|
+
* G8-6 do CHECK de 15/10: a continuidade da chave nos registros presos a uma ordem (recibo, vaga, vaga solta, vez, fim da vez
|
|
144
|
+
* e entrega). Assinado sob uma versão anterior à da ordem, o registro só vale com a chave que a versão da ordem dá ao executor:
|
|
145
|
+
* a chave que uma emenda aposentou ainda fecha o que é dela sob a versão antiga (D18), mas não fecha, solta nem entrega a
|
|
146
|
+
* ordem emitida depois da troca. Sob a versão da ordem ou uma posterior vale a sucessão: a raiz que a emenda pôs no lugar
|
|
147
|
+
* fecha o que a chave antiga deixou. A ordem das versões é a das autorizações, a mesma que dá a vigente.
|
|
148
|
+
*/
|
|
149
|
+
function chaveContinua(versoes, registro, ordem) {
|
|
150
|
+
const i = versoes.findIndex(v => v.envelopeSha256 === registro.envelopeSha256);
|
|
151
|
+
const j = versoes.findIndex(v => v.envelopeSha256 === ordem.envelopeSha256);
|
|
152
|
+
if (i < 0 || j < 0)
|
|
153
|
+
return false;
|
|
154
|
+
if (i >= j)
|
|
155
|
+
return true;
|
|
156
|
+
const chave = maquinasDe(versoes[i].conteudo).find(m => m.nome === registro.por)?.chave;
|
|
157
|
+
return chave !== undefined && chave === maquinasDe(versoes[j].conteudo).find(m => m.nome === ordem.maquina)?.chave;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* A ordem é desta raiz, que assina como `eu`? A máquina dela tem o mesmo nome, e o que esta raiz assinar sobre ela passa na
|
|
161
|
+
* continuidade da chave (G8-6): a raiz assina sob a última versão em que está com a mesma chave, a vigente ou, depois de uma
|
|
162
|
+
* emenda que a tirou, a anterior (D18). A raiz antiga que volta depois de uma troca de chave deixa a ordem da nova para ela.
|
|
163
|
+
*/
|
|
164
|
+
function ordemDaRaiz(estado, o, eu) {
|
|
165
|
+
if (o.maquina !== eu.nome)
|
|
166
|
+
return false;
|
|
167
|
+
const assina = [...estado.versoes].reverse().find(v => maquinasDe(v.conteudo).some(m => m.nome === eu.nome && m.chave === eu.chave));
|
|
168
|
+
return !!assina && chaveContinua(estado.versoes, { envelopeSha256: assina.envelopeSha256, por: eu.nome }, o);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* G8-1: o que a vez de integrar diz do fim de uma ordem, a mesma regra no `encerrarOrdem` e no plano da reconciliação. A
|
|
172
|
+
* vez ativa espera, porque o merge pode ter chegado à base; sem entrega do trabalho, a fonte que chegou sem prova espera a
|
|
173
|
+
* inspeção do dono, e a vez abandonada espera a reconciliação ler a base sem a fonte (`conferidas`, que só ela tem).
|
|
174
|
+
*/
|
|
175
|
+
function esperaDaVez(estado, o, conferidas, semProva) {
|
|
176
|
+
const dasVezes = [...estado.vezes.values()].flat().filter(v => v.ordem === o.ordem);
|
|
177
|
+
if (dasVezes.some(v => !v.fim))
|
|
178
|
+
return 'ativa';
|
|
179
|
+
if (estado.entregas.has(o.trabalho))
|
|
180
|
+
return null;
|
|
181
|
+
if (dasVezes.some(v => v.fim?.desfecho === 'sem-prova' || semProva?.has(`${v.chave}/${v.n}`)))
|
|
182
|
+
return 'sem-prova';
|
|
183
|
+
if (dasVezes.some(v => v.fim?.desfecho === 'abandonada' && !conferidas?.has(`${v.chave}/${v.n}`)))
|
|
184
|
+
return 'abandonada';
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* O estado da coordenação, derivado da leitura conferida. Confere o contrato de cada tipo e a coerência entre eles (quem
|
|
189
|
+
* assina o quê, gerações contíguas e seladas, uma ordem viva por trabalho, uma vez ativa por destino, recibos que não se
|
|
190
|
+
* contradizem); qualquer falha é `noctum.coordenacao.adulterado`. A mesma leitura deriva uma vez só.
|
|
191
|
+
*/
|
|
192
|
+
function derivarCoordenacao(leitura) {
|
|
193
|
+
const ja = derivadas.get(leitura);
|
|
194
|
+
if (ja)
|
|
195
|
+
return ja;
|
|
196
|
+
const estado = derivar(leitura);
|
|
197
|
+
derivadas.set(leitura, estado);
|
|
198
|
+
return estado;
|
|
199
|
+
}
|
|
200
|
+
const derivadas = new WeakMap();
|
|
201
|
+
function derivar(leitura) {
|
|
202
|
+
const versoes = new Map(leitura.versoes.map(v => [v.envelopeSha256, v]));
|
|
203
|
+
const vigente = leitura.versoes.at(-1);
|
|
204
|
+
const daVersao = (d) => versoes.get(d.envelopeSha256).conteudo;
|
|
205
|
+
const exigirChaveDaOrdem = (d, caminho, o) => {
|
|
206
|
+
if (!chaveContinua(leitura.versoes, d, o)) {
|
|
207
|
+
throw adulterado(`${caminho} assinado sob uma versão anterior à da ordem ${o.ordem}, por uma chave diferente da que o envelope ` +
|
|
208
|
+
`dela dá ao executor ${o.maquina}`);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const porTipo = (tipo) => leitura.registros.filter(r => r.dados.tipo === tipo);
|
|
212
|
+
const daGeracao = new Map();
|
|
213
|
+
const selar = (g, id) => daGeracao.set(g, [...(daGeracao.get(g) ?? []), id]);
|
|
214
|
+
// Autoridade e cessões.
|
|
215
|
+
const cessoes = new Map();
|
|
216
|
+
for (const { caminho, dados: d } of porTipo('cessao')) {
|
|
217
|
+
campos(d, caminho, { geracao: v => inteiro(v), para: v => typeof v === 'string' && noctum_transporte_1.NOME_DE_MAQUINA.test(v), motivo: v => linha(v) });
|
|
218
|
+
cessoes.set(d.geracao, { para: d.para, em: d.em, motivo: d.motivo, por: d.por });
|
|
219
|
+
}
|
|
220
|
+
const brutas = porTipo('autoridade').map(({ caminho, dados: d }) => {
|
|
221
|
+
campos(d, caminho, { geracao: v => inteiro(v), modo: v => v === 'primeira' || v === 'cessao' || v === 'forcada', motivo: v => linha(v),
|
|
222
|
+
anterior: v => v === null || (objeto(v) && inteiro(v.geracao) && typeof v.maquina === 'string' && Object.keys(v).length === 2),
|
|
223
|
+
selo: v => v === null || seloValido(v) }, { batidaDaAnterior: v => v === null || (typeof v === 'string' && Number.isFinite(Date.parse(v))) });
|
|
224
|
+
return d;
|
|
225
|
+
}).sort((a, b) => a.geracao - b.geracao);
|
|
226
|
+
const autoridades = [];
|
|
227
|
+
brutas.forEach((d, i) => {
|
|
228
|
+
const g = d.geracao, anterior = d.anterior;
|
|
229
|
+
if (g !== i + 1)
|
|
230
|
+
throw adulterado(`as gerações da autoridade não são contíguas (falta a ${i + 1})`);
|
|
231
|
+
if (g === 1 ? d.modo !== 'primeira' || anterior !== null : anterior?.geracao !== g - 1 || anterior.maquina !== autoridades[g - 2].maquina) {
|
|
232
|
+
throw adulterado(`a geração ${g} da autoridade não aponta a anterior`);
|
|
233
|
+
}
|
|
234
|
+
if ((g === 1) !== (d.selo === null))
|
|
235
|
+
throw adulterado(`a geração ${g} da autoridade ${g === 1 ? 'traz selo' : 'não traz o selo da anterior'}`);
|
|
236
|
+
if (d.modo === 'cessao' && cessoes.get(g - 1)?.para !== d.por)
|
|
237
|
+
throw adulterado(`a geração ${g} diz cessão sem a cessão da ${g - 1} para ${d.por}`);
|
|
238
|
+
if (d.modo === 'forcada' && !d.motivo.trim())
|
|
239
|
+
throw adulterado(`a geração ${g} foi tomada à força sem motivo`);
|
|
240
|
+
autoridades.push({ geracao: g, maquina: d.por, desde: d.em, modo: d.modo, motivo: d.motivo });
|
|
241
|
+
});
|
|
242
|
+
for (const [g, c] of cessoes) {
|
|
243
|
+
if (!autoridades[g - 1] || autoridades[g - 1].maquina !== c.por)
|
|
244
|
+
throw adulterado(`cessão da geração ${g} assinada por quem não era a autoridade`);
|
|
245
|
+
}
|
|
246
|
+
const autoridadeDa = (g) => autoridades[g - 1];
|
|
247
|
+
const daAutoridade = (d, caminho, oque) => {
|
|
248
|
+
if (!autoridadeDa(d.geracao) || autoridadeDa(d.geracao).maquina !== d.por) {
|
|
249
|
+
throw adulterado(`${caminho} ${oque} por quem não era a autoridade da geração ${String(d.geracao)}`);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
// Ordens, cancelamentos e recibos.
|
|
253
|
+
const ordens = new Map();
|
|
254
|
+
for (const { caminho, dados: d } of porTipo('ordem')) {
|
|
255
|
+
campos(d, caminho, { ordem: v => typeof v === 'string' && noctum_transporte_1.ID_DA_ORDEM.test(v), trabalho: v => typeof v === 'string' && noctum_envelope_1.ID_DO_TRABALHO.test(v),
|
|
256
|
+
item: v => typeof v === 'string' && /^RM-\d{3}$/.test(v), maquina: v => typeof v === 'string' && noctum_transporte_1.NOME_DE_MAQUINA.test(v),
|
|
257
|
+
geracao: v => inteiro(v), n: v => inteiro(v) });
|
|
258
|
+
if (d.ordem !== `O-${String(d.trabalho)}-${String(d.n)}`)
|
|
259
|
+
throw adulterado(`${caminho} com id que não é o do trabalho e do número`);
|
|
260
|
+
daAutoridade(d, caminho, 'assinada');
|
|
261
|
+
selar(d.geracao, `ordem:${String(d.ordem)}`);
|
|
262
|
+
const conteudo = daVersao(d), t = trabalhoDe(conteudo, d.trabalho);
|
|
263
|
+
if (!t || t.item !== d.item)
|
|
264
|
+
throw adulterado(`${caminho} para um trabalho fora do envelope dela`);
|
|
265
|
+
if (!maquinasDe(conteudo).some(m => m.nome === d.maquina) || (t.maquina && t.maquina !== d.maquina)) {
|
|
266
|
+
throw adulterado(`${caminho} para uma máquina que o envelope dela não admite nesse trabalho`);
|
|
267
|
+
}
|
|
268
|
+
ordens.set(d.ordem, { ordem: d.ordem, trabalho: d.trabalho, item: d.item, maquina: d.maquina,
|
|
269
|
+
geracao: d.geracao, n: d.n, envelopeSha256: d.envelopeSha256, emitidaEm: d.em, cancelada: null, recibos: {},
|
|
270
|
+
estado: 'emitida', resultado: null });
|
|
271
|
+
}
|
|
272
|
+
for (const { caminho, dados: d } of porTipo('cancelamento')) {
|
|
273
|
+
campos(d, caminho, { ordem: v => typeof v === 'string' && noctum_transporte_1.ID_DA_ORDEM.test(v), motivo: v => linha(v), geracao: v => inteiro(v) });
|
|
274
|
+
const o = ordens.get(d.ordem);
|
|
275
|
+
if (!o)
|
|
276
|
+
throw adulterado(`${caminho} de uma ordem que não existe`);
|
|
277
|
+
daAutoridade(d, caminho, 'assinado');
|
|
278
|
+
selar(d.geracao, `cancelamento:${String(d.ordem)}`);
|
|
279
|
+
o.cancelada = { em: d.em, motivo: d.motivo, por: d.por };
|
|
280
|
+
}
|
|
281
|
+
for (const { caminho, dados: d } of porTipo('recibo')) {
|
|
282
|
+
campos(d, caminho, { ordem: v => typeof v === 'string' && noctum_transporte_1.ID_DA_ORDEM.test(v), recibo: v => noctum_transporte_1.RECIBOS.includes(v),
|
|
283
|
+
motivo: v => linha(v) }, { resultado: v => v === 'entregue' || v === 'encerrado-sem-entrega', thread: v => v === null || linha(v, 80),
|
|
284
|
+
checkpoint: v => checkpointValido(v) });
|
|
285
|
+
const o = ordens.get(d.ordem);
|
|
286
|
+
if (!o)
|
|
287
|
+
throw adulterado(`${caminho} de uma ordem que não existe`);
|
|
288
|
+
if (o.maquina !== d.por)
|
|
289
|
+
throw adulterado(`${caminho} assinado por quem não é o executor da ordem`);
|
|
290
|
+
exigirChaveDaOrdem(d, caminho, o);
|
|
291
|
+
if ((d.recibo === 'concluida') !== (d.resultado !== undefined))
|
|
292
|
+
throw adulterado(`${caminho}: só a conclusão traz resultado, e sempre`);
|
|
293
|
+
o.recibos[d.recibo] = d;
|
|
294
|
+
}
|
|
295
|
+
for (const o of ordens.values()) {
|
|
296
|
+
const r = o.recibos;
|
|
297
|
+
if ((r.aceita && r.recusada) || (r.concluida && r.encerrada) || ((r.concluida || r.encerrada) && !r.aceita)) {
|
|
298
|
+
throw adulterado(`os recibos da ordem ${o.ordem} se contradizem`);
|
|
299
|
+
}
|
|
300
|
+
o.estado = r.concluida ? 'concluida' : r.encerrada ? 'encerrada' : r.recusada ? 'recusada'
|
|
301
|
+
: r.aceita ? (o.cancelada ? 'isolada' : 'aceita') : o.cancelada ? 'cancelada-antes' : 'emitida';
|
|
302
|
+
o.resultado = r.concluida?.resultado ?? null;
|
|
303
|
+
}
|
|
304
|
+
// Uma ordem viva por trabalho, e os números de cada trabalho contíguos (D9).
|
|
305
|
+
const doTrabalho = new Map();
|
|
306
|
+
for (const o of ordens.values())
|
|
307
|
+
doTrabalho.set(o.trabalho, [...(doTrabalho.get(o.trabalho) ?? []), o]);
|
|
308
|
+
for (const [trabalho, lista] of doTrabalho) {
|
|
309
|
+
lista.sort((a, b) => a.n - b.n);
|
|
310
|
+
if (lista.some((o, i) => o.n !== i + 1))
|
|
311
|
+
throw adulterado(`as ordens do trabalho ${trabalho} não são contíguas`);
|
|
312
|
+
if (lista.filter(exports.ordemViva).length > 1)
|
|
313
|
+
throw adulterado(`o trabalho ${trabalho} tem duas ordens vivas`);
|
|
314
|
+
}
|
|
315
|
+
// Entregas.
|
|
316
|
+
const entregas = new Map();
|
|
317
|
+
for (const { caminho, dados: d } of porTipo('entrega')) {
|
|
318
|
+
campos(d, caminho, { trabalho: v => typeof v === 'string' && noctum_envelope_1.ID_DO_TRABALHO.test(v), ordem: v => typeof v === 'string' && noctum_transporte_1.ID_DA_ORDEM.test(v),
|
|
319
|
+
destino: v => objeto(v) && typeof v.repositorio === 'string' && typeof v.base === 'string' && Object.keys(v).length === 2,
|
|
320
|
+
fonte: v => sha(v), base: v => sha(v), merge: v => sha(v) });
|
|
321
|
+
const o = ordens.get(d.ordem);
|
|
322
|
+
if (!o || o.maquina !== d.por || o.trabalho !== d.trabalho || !o.recibos.aceita) {
|
|
323
|
+
throw adulterado(`${caminho} de outra máquina, de outro trabalho ou de uma ordem que ela não aceitou`);
|
|
324
|
+
}
|
|
325
|
+
exigirChaveDaOrdem(d, caminho, o);
|
|
326
|
+
entregas.set(d.trabalho, d);
|
|
327
|
+
}
|
|
328
|
+
// Vagas.
|
|
329
|
+
const vagas = [];
|
|
330
|
+
const porVaga = new Map();
|
|
331
|
+
for (const { caminho, dados: d } of porTipo('vaga')) {
|
|
332
|
+
campos(d, caminho, { vaga: v => typeof v === 'string' && noctum_transporte_1.ID_DA_VAGA.test(v), conta: v => inteiro(v, 0), runtime: v => linha(v, 40),
|
|
333
|
+
ordem: v => typeof v === 'string' && noctum_transporte_1.ID_DA_ORDEM.test(v), trabalho: v => typeof v === 'string' && noctum_envelope_1.ID_DO_TRABALHO.test(v), reserva: v => inteiro(v) });
|
|
334
|
+
if (d.vaga !== `V-${d.por}-${String(d.reserva)}`)
|
|
335
|
+
throw adulterado(`${caminho} com id que não é o da máquina e da reserva`);
|
|
336
|
+
const o = ordens.get(d.ordem);
|
|
337
|
+
// Nenhum efeito antes do aceite (D10): vaga de ordem que o executor não aceitou só existe num ramo escrito à mão.
|
|
338
|
+
if (!o || o.maquina !== d.por || o.trabalho !== d.trabalho || !o.recibos.aceita) {
|
|
339
|
+
throw adulterado(`${caminho} de quem não é o executor da ordem, ou de uma ordem que ele não aceitou`);
|
|
340
|
+
}
|
|
341
|
+
exigirChaveDaOrdem(d, caminho, o);
|
|
342
|
+
const conteudo = daVersao(d);
|
|
343
|
+
const conta = (0, noctum_envelope_1.envelopeMultimaquina)(conteudo) ? conteudo.capacidadeGlobal.porConta[d.conta]?.conta : undefined;
|
|
344
|
+
if (!conta || !conteudo.orcamento.porRuntime.some(r => r.runtime === d.runtime))
|
|
345
|
+
throw adulterado(`${caminho} com conta ou runtime fora do envelope dela`);
|
|
346
|
+
const v = { vaga: d.vaga, maquina: d.por, conta, runtime: d.runtime, ordem: d.ordem,
|
|
347
|
+
trabalho: d.trabalho, reserva: d.reserva, chave: maquinasDe(conteudo).find(m => m.nome === d.por).chave, desde: d.em,
|
|
348
|
+
solta: null };
|
|
349
|
+
vagas.push(v);
|
|
350
|
+
porVaga.set(v.vaga, v);
|
|
351
|
+
}
|
|
352
|
+
for (const { caminho, dados: d } of porTipo('vaga-solta')) {
|
|
353
|
+
campos(d, caminho, { vaga: v => typeof v === 'string' && noctum_transporte_1.ID_DA_VAGA.test(v), consumiu: v => typeof v === 'boolean', motivo: v => linha(v) });
|
|
354
|
+
const v = porVaga.get(d.vaga);
|
|
355
|
+
if (!v || v.maquina !== d.por)
|
|
356
|
+
throw adulterado(`${caminho} de uma vaga que não existe ou não é de quem solta`);
|
|
357
|
+
exigirChaveDaOrdem(d, caminho, ordens.get(v.ordem));
|
|
358
|
+
v.solta = { em: d.em, consumiu: d.consumiu, motivo: d.motivo };
|
|
359
|
+
}
|
|
360
|
+
const consumo = { arco: 0, porConta: {}, porRuntime: {} };
|
|
361
|
+
for (const v of vagas) {
|
|
362
|
+
if (v.solta && !v.solta.consumiu)
|
|
363
|
+
continue;
|
|
364
|
+
consumo.arco += 1;
|
|
365
|
+
consumo.porConta[v.conta] = (consumo.porConta[v.conta] ?? 0) + 1;
|
|
366
|
+
consumo.porRuntime[v.runtime] = (consumo.porRuntime[v.runtime] ?? 0) + 1;
|
|
367
|
+
}
|
|
368
|
+
// Vezes de integrar.
|
|
369
|
+
const vezes = new Map();
|
|
370
|
+
for (const { caminho, dados: d } of porTipo('vez')) {
|
|
371
|
+
campos(d, caminho, { chave: v => typeof v === 'string' && noctum_transporte_1.CHAVE_DO_DESTINO.test(v), n: v => inteiro(v),
|
|
372
|
+
destino: v => objeto(v) && typeof v.repositorio === 'string' && typeof v.base === 'string' && Object.keys(v).length === 2,
|
|
373
|
+
ordem: v => typeof v === 'string' && noctum_transporte_1.ID_DA_ORDEM.test(v), trabalho: v => typeof v === 'string' && noctum_envelope_1.ID_DO_TRABALHO.test(v),
|
|
374
|
+
candidato: v => objeto(v) && typeof v.branch === 'string' && sha(v.fonte) && sha(v.base) && typeof v.provas === 'string' &&
|
|
375
|
+
/^[0-9a-f]{64}$/.test(v.provas) && Object.keys(v).length === 4 });
|
|
376
|
+
const destino = d.destino;
|
|
377
|
+
if (chaveDoDestino(destino) !== d.chave)
|
|
378
|
+
throw adulterado(`${caminho} com chave que não é a do destino`);
|
|
379
|
+
const o = ordens.get(d.ordem);
|
|
380
|
+
if (!o || o.maquina !== d.por || o.trabalho !== d.trabalho || !o.recibos.aceita) {
|
|
381
|
+
throw adulterado(`${caminho} de quem não é o executor da ordem, ou de uma ordem que ele não aceitou`);
|
|
382
|
+
}
|
|
383
|
+
exigirChaveDaOrdem(d, caminho, o);
|
|
384
|
+
vezes.set(d.chave, [...(vezes.get(d.chave) ?? []), { chave: d.chave, n: d.n, maquina: d.por,
|
|
385
|
+
ordem: d.ordem, trabalho: d.trabalho, destino, candidato: d.candidato, desde: d.em, fim: null }]);
|
|
386
|
+
}
|
|
387
|
+
for (const { caminho, dados: d } of porTipo('vez-fim')) {
|
|
388
|
+
campos(d, caminho, { chave: v => typeof v === 'string' && noctum_transporte_1.CHAVE_DO_DESTINO.test(v), n: v => inteiro(v),
|
|
389
|
+
desfecho: v => DESFECHOS.includes(v), motivo: v => linha(v) }, { merge: v => v === null || sha(v), geracao: v => inteiro(v), sinalDoDono: v => v === null || (typeof v === 'string' && Number.isFinite(Date.parse(v))) });
|
|
390
|
+
const vez = vezes.get(d.chave)?.find(x => x.n === d.n);
|
|
391
|
+
if (!vez)
|
|
392
|
+
throw adulterado(`${caminho} de uma vez que não existe`);
|
|
393
|
+
if (d.desfecho === 'abandonada') {
|
|
394
|
+
// A7 da revisão: só a autoridade da geração abandona a vez de outra máquina, e o abandono entra no selo da geração.
|
|
395
|
+
daAutoridade(d, caminho, 'assinado');
|
|
396
|
+
if (vez.maquina === d.por || d.merge !== undefined)
|
|
397
|
+
throw adulterado(`${caminho}: o abandono é da vez de outra máquina, sem merge`);
|
|
398
|
+
selar(d.geracao, `abandono:${String(d.chave)}/${String(d.n)}`);
|
|
399
|
+
}
|
|
400
|
+
else if (vez.maquina !== d.por || d.geracao !== undefined || d.sinalDoDono !== undefined) {
|
|
401
|
+
throw adulterado(`${caminho} de uma vez que não é de quem a encerra`);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
exigirChaveDaOrdem(d, caminho, ordens.get(vez.ordem));
|
|
405
|
+
}
|
|
406
|
+
vez.fim = { em: d.em, desfecho: d.desfecho, motivo: d.motivo, merge: d.merge ?? null, por: d.por };
|
|
407
|
+
}
|
|
408
|
+
for (const [chave, lista] of vezes) {
|
|
409
|
+
lista.sort((a, b) => a.n - b.n);
|
|
410
|
+
if (lista.some((v, i) => v.n !== i + 1))
|
|
411
|
+
throw adulterado(`as vezes do destino ${chave} não são contíguas`);
|
|
412
|
+
if (lista.filter(v => !v.fim).length > 1)
|
|
413
|
+
throw adulterado(`o destino ${chave} tem duas vezes ativas`);
|
|
414
|
+
}
|
|
415
|
+
// B1 da revisão: cada geração vencida tem exatamente os registros que a autoridade seguinte selou ao assumir. A
|
|
416
|
+
// autoridade antiga que grava depois de perder a vez (um núcleo divergente, um agente com a chave dela) reprova a leitura.
|
|
417
|
+
for (let g = 1; g < autoridades.length; g++) {
|
|
418
|
+
const esperado = brutas[g].selo;
|
|
419
|
+
const visto = seloDaGeracao(daGeracao.get(g) ?? []);
|
|
420
|
+
if (visto.registros !== esperado.registros || visto.sha256 !== esperado.sha256) {
|
|
421
|
+
throw adulterado(`a geração ${g} tem ${visto.registros} registro(s) da autoridade, e a geração ${g + 1} selou ${esperado.registros}: ` +
|
|
422
|
+
'a autoridade vencida gravou depois de perder a vez');
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// Batidas, sinais de vida e revogação.
|
|
426
|
+
const batidas = new Map();
|
|
427
|
+
for (const { caminho, dados: d } of porTipo('batida')) {
|
|
428
|
+
campos(d, caminho, {});
|
|
429
|
+
batidas.set(d.por, d.em);
|
|
430
|
+
}
|
|
431
|
+
const sinais = new Map();
|
|
432
|
+
for (const { dados: d } of leitura.registros) {
|
|
433
|
+
const antes = sinais.get(d.por);
|
|
434
|
+
if (!antes || Date.parse(d.em) > Date.parse(antes))
|
|
435
|
+
sinais.set(d.por, d.em);
|
|
436
|
+
}
|
|
437
|
+
let revogada = null;
|
|
438
|
+
for (const { caminho, dados: d } of porTipo('revogacao')) {
|
|
439
|
+
campos(d, caminho, { motivo: v => linha(v) });
|
|
440
|
+
if (!revogada || Date.parse(d.em) < Date.parse(revogada.em))
|
|
441
|
+
revogada = { por: d.por, em: d.em, motivo: d.motivo };
|
|
442
|
+
}
|
|
443
|
+
// A situação de cada trabalho do envelope vigente.
|
|
444
|
+
const trabalhos = new Map();
|
|
445
|
+
for (const t of vigente.conteudo.trabalhos ?? []) {
|
|
446
|
+
const lista = doTrabalho.get(t.id) ?? [];
|
|
447
|
+
const ultima = lista.at(-1) ?? null;
|
|
448
|
+
const entregue = entregas.get(t.id)?.em ?? lista.find(o => o.resultado === 'entregue')?.recibos.concluida?.em ?? null;
|
|
449
|
+
const estado = entregue ? 'entregue' : lista.some(o => o.resultado === 'encerrado-sem-entrega') ? 'encerrado'
|
|
450
|
+
: !ultima || !(0, exports.ordemViva)(ultima) ? 'livre' : ultima.estado === 'emitida' ? 'ordenado' : ultima.estado === 'aceita' ? 'em-execucao' : 'isolado';
|
|
451
|
+
trabalhos.set(t.id, { trabalho: t.id, estado, ordem: ultima, entregueEm: entregue });
|
|
452
|
+
}
|
|
453
|
+
return { arco: leitura.arco, remoto: leitura.remoto, ponta: leitura.ponta, lidoEm: leitura.lidoEm, versoes: leitura.versoes, vigente,
|
|
454
|
+
autoridade: autoridades.at(-1) ?? null, autoridades,
|
|
455
|
+
cessoes: new Map([...cessoes].map(([g, c]) => [g, { para: c.para, em: c.em, motivo: c.motivo }])),
|
|
456
|
+
ordens, trabalhos, vagas, consumo, vezes, entregas, batidas, sinais, revogada, daGeracao };
|
|
457
|
+
}
|
|
458
|
+
/** A2 da revisão: a coerência inteira confere antes de a leitura avançar a última ponta vista desta máquina. */
|
|
459
|
+
const conferirCoordenacao = (leitura) => { derivarCoordenacao(leitura); };
|
|
460
|
+
exports.conferirCoordenacao = conferirCoordenacao;
|
|
461
|
+
/** A leitura conferida e derivada, nova do remoto. */
|
|
462
|
+
function lerCoordenacao(carregado, arco, opcoes = {}) {
|
|
463
|
+
return derivarCoordenacao((0, noctum_transporte_1.lerRamo)(carregado, arco, opcoes, exports.conferirCoordenacao));
|
|
464
|
+
}
|
|
465
|
+
/** A gravação no ramo com a coerência conferida em toda leitura: é a única que a coordenação, a integração e o passo usam. */
|
|
466
|
+
function gravarNaCoordenacao(carregado, arco, opcoes, regra) {
|
|
467
|
+
return (0, noctum_transporte_1.gravarNoRamo)(carregado, arco, opcoes, regra, exports.conferirCoordenacao);
|
|
468
|
+
}
|
|
469
|
+
/** A situação de um trabalho na rede: é por ela que a N4 sabe se pode transferir (só `livre`). */
|
|
470
|
+
function situacaoDoTrabalho(estado, trabalho) {
|
|
471
|
+
return estado.trabalhos.get(trabalho) ?? null;
|
|
472
|
+
}
|
|
473
|
+
/** Os trabalhos entregues em qualquer máquina, com o instante: a N2 os conta como entregues nas dependências. */
|
|
474
|
+
function entreguesNaRede(estado) {
|
|
475
|
+
return new Map([...estado.trabalhos.values()].filter(s => s.estado === 'entregue').map(s => [s.trabalho, s.entregueEm]));
|
|
476
|
+
}
|
|
477
|
+
/** Os trabalhos encerrados sem entrega em alguma máquina do arco (S1 da rodada 3): terminais em todas, como os entregues. */
|
|
478
|
+
function encerradosNaRede(estado) {
|
|
479
|
+
return new Set([...estado.trabalhos.values()].filter(s => s.estado === 'encerrado').map(s => s.trabalho));
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* A máquina deu sinal de vida há pouco, no instante dado? O sinal é o registro mais novo que ela assinou, inclusive a
|
|
483
|
+
* autoridade que ela acabou de assumir e a vez que ela tomou (S2 da revisão). A própria máquina, rodando agora, sempre deu.
|
|
484
|
+
*/
|
|
485
|
+
function batidaRecente(estado, maquina, eu, quando) {
|
|
486
|
+
if (maquina === eu)
|
|
487
|
+
return true;
|
|
488
|
+
const em = estado.sinais.get(maquina);
|
|
489
|
+
return !!em && Date.parse(quando) - Date.parse(em) <= exports.BATIDA_VENCIDA_MS;
|
|
490
|
+
}
|
|
491
|
+
// ---------------------------------------------------------------------------
|
|
492
|
+
// As conferências comuns.
|
|
493
|
+
// ---------------------------------------------------------------------------
|
|
494
|
+
/** O arco está vigente aqui (autorizado, na janela, não revogado)? Senão, a recusa da N1 que diz por quê. */
|
|
495
|
+
function exigirArcoVigente(carregado, arco, quando) {
|
|
496
|
+
const e = (0, noctum_arco_1.lerArco)(carregado.raiz, arco, quando);
|
|
497
|
+
if (!e || !e.conteudo)
|
|
498
|
+
throw recusa('noctum.efeito.sem-autorizacao', `arco ${arco} sem envelope autorizado nesta máquina`);
|
|
499
|
+
if (e.situacao === 'revogado')
|
|
500
|
+
throw recusa('noctum.efeito.revogado', `arco ${arco} revogado pelo dono`);
|
|
501
|
+
// RM-058 N6: o arco que o fechamento encerrou nesta máquina é terminal, como o revogado; nada novo começa aqui.
|
|
502
|
+
if (e.situacao === 'encerrado') {
|
|
503
|
+
throw recusa('noctum.efeito.encerrado', `arco ${arco} encerrado nesta máquina (${e.encerramento?.motivo ?? 'fechado'}); nada novo começa aqui`);
|
|
504
|
+
}
|
|
505
|
+
if (e.situacao === 'expirado')
|
|
506
|
+
throw recusa('noctum.efeito.expirado', `a janela do arco ${arco} terminou em ${e.conteudo.janela.fim}`);
|
|
507
|
+
if (e.situacao !== 'vigente')
|
|
508
|
+
throw recusa('noctum.efeito.fora-da-janela', `a janela do arco ${arco} só começa em ${e.conteudo.janela.inicio}`);
|
|
509
|
+
}
|
|
510
|
+
/** A8 da revisão: a revogação gravada no ramo por uma máquina do arco vale aqui também; nada novo começa. */
|
|
511
|
+
function exigirNaoRevogada(estado) {
|
|
512
|
+
if (estado.revogada) {
|
|
513
|
+
throw recusa('noctum.coordenacao.revogada', `o arco ${estado.arco} foi revogado na máquina ${estado.revogada.por} em ${estado.revogada.em}; ` +
|
|
514
|
+
'nenhuma ordem, vaga ou vez nova sai, e cada máquina só fecha o que é dela');
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
/** Esta máquina é a autoridade vigente? Senão, `geracao-vencida` (já foi) ou `autoridade-ativa`. */
|
|
518
|
+
function exigirAutoridade(estado, eu) {
|
|
519
|
+
const a = estado.autoridade;
|
|
520
|
+
if (a?.maquina === eu)
|
|
521
|
+
return a;
|
|
522
|
+
if (estado.autoridades.some(x => x.maquina === eu)) {
|
|
523
|
+
throw recusa('noctum.coordenacao.geracao-vencida', `esta máquina já foi a autoridade; a vigente é ${a.maquina}, geração ${a.geracao}`);
|
|
524
|
+
}
|
|
525
|
+
throw recusa('noctum.coordenacao.autoridade-ativa', a ? `a autoridade vigente é ${a.maquina}, geração ${a.geracao}` : 'o arco ainda não tem autoridade: assuma antes');
|
|
526
|
+
}
|
|
527
|
+
function exigirOrdem(estado, ordem) {
|
|
528
|
+
const o = estado.ordens.get(ordem);
|
|
529
|
+
if (!o)
|
|
530
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a ordem ${ordem} não existe neste arco`);
|
|
531
|
+
return o;
|
|
532
|
+
}
|
|
533
|
+
const nomeDe = (opcoes) => (0, maquina_1.nomeDaMaquina)(opcoes.maquina);
|
|
534
|
+
// ---------------------------------------------------------------------------
|
|
535
|
+
// Autoridade por geração (D8).
|
|
536
|
+
// ---------------------------------------------------------------------------
|
|
537
|
+
/**
|
|
538
|
+
* Assume a autoridade do arco. Sem autoridade, a geração 1 (dois que disputam: um vence pela troca atômica). A própria é
|
|
539
|
+
* idempotente. A de outra máquina só com a cessão dela para esta, ou com `forcar` e motivo quando ela não dá sinal há
|
|
540
|
+
* mais de 15 min ou saiu do envelope vigente; senão, `noctum.coordenacao.autoridade-ativa`. A geração nova sela os
|
|
541
|
+
* registros da anterior (B1 da revisão): o que a autoridade antiga gravar depois reprova a leitura de todos.
|
|
542
|
+
*/
|
|
543
|
+
function assumirAutoridade(carregado, arco, opcoes = {}) {
|
|
544
|
+
const quando = opcoes.quando ?? new Date().toISOString();
|
|
545
|
+
const motivoForcado = opcoes.forcar ? motivoPublico(opcoes.forcar.motivo, 'a tomada forçada da autoridade') : null;
|
|
546
|
+
exigirArcoVigente(carregado, arco, quando);
|
|
547
|
+
const r = gravarNaCoordenacao(carregado, arco, { ...opcoes, quando }, (leitura) => {
|
|
548
|
+
const estado = derivarCoordenacao(leitura);
|
|
549
|
+
const { eu, vigente } = (0, noctum_transporte_1.euNaVersaoVigente)(carregado.raiz, leitura, opcoes);
|
|
550
|
+
const a = estado.autoridade;
|
|
551
|
+
const nova = (geracao, modo, motivo) => ({ geracao, maquina: eu.nome, desde: quando, modo, motivo });
|
|
552
|
+
if (!a) {
|
|
553
|
+
return { novos: [{ tipo: 'autoridade', geracao: 1, modo: 'primeira', motivo: 'primeira autoridade do arco', anterior: null, selo: null }],
|
|
554
|
+
resultado: nova(1, 'primeira', 'primeira autoridade do arco') };
|
|
555
|
+
}
|
|
556
|
+
if (a.maquina === eu.nome)
|
|
557
|
+
return { novos: [], resultado: a };
|
|
558
|
+
const anterior = { geracao: a.geracao, maquina: a.maquina };
|
|
559
|
+
const selo = seloDaGeracao(estado.daGeracao.get(a.geracao) ?? []);
|
|
560
|
+
if (estado.cessoes.get(a.geracao)?.para === eu.nome) {
|
|
561
|
+
const motivo = estado.cessoes.get(a.geracao).motivo;
|
|
562
|
+
return { novos: [{ tipo: 'autoridade', geracao: a.geracao + 1, modo: 'cessao', motivo, anterior, selo }], resultado: nova(a.geracao + 1, 'cessao', motivo) };
|
|
563
|
+
}
|
|
564
|
+
if (motivoForcado) {
|
|
565
|
+
const motivo = motivoForcado;
|
|
566
|
+
const fora = !(0, noctum_envelope_1.envelopeMultimaquina)(vigente.conteudo) || !vigente.conteudo.maquinas.some(m => m.nome === a.maquina);
|
|
567
|
+
if (!fora && batidaRecente(estado, a.maquina, eu.nome, quando)) {
|
|
568
|
+
throw recusa('noctum.coordenacao.autoridade-ativa', `a autoridade ${a.maquina} deu sinal há menos de ${exports.BATIDA_VENCIDA_MS / 60000} min; tomar à força exige o sinal vencido`);
|
|
569
|
+
}
|
|
570
|
+
return { novos: [{ tipo: 'autoridade', geracao: a.geracao + 1, modo: 'forcada', motivo, anterior, selo,
|
|
571
|
+
batidaDaAnterior: estado.sinais.get(a.maquina) ?? null }], resultado: nova(a.geracao + 1, 'forcada', motivo) };
|
|
572
|
+
}
|
|
573
|
+
throw recusa('noctum.coordenacao.autoridade-ativa', `a autoridade vigente é ${a.maquina}, geração ${a.geracao}; troca só por cessão dela ou tomada forçada`);
|
|
574
|
+
});
|
|
575
|
+
return r.resultado;
|
|
576
|
+
}
|
|
577
|
+
/** A autoridade vigente cede a vez a outra máquina do envelope; a outra assume com `assumirAutoridade`. */
|
|
578
|
+
function cederAutoridade(carregado, arco, para, motivo, opcoes = {}) {
|
|
579
|
+
const doCedente = motivoPublico(motivo, 'a cessão');
|
|
580
|
+
gravarNaCoordenacao(carregado, arco, opcoes, (leitura) => {
|
|
581
|
+
const estado = derivarCoordenacao(leitura);
|
|
582
|
+
const { eu, vigente } = (0, noctum_transporte_1.euNaVersaoVigente)(carregado.raiz, leitura, opcoes);
|
|
583
|
+
const a = exigirAutoridade(estado, eu.nome);
|
|
584
|
+
if (para === eu.nome || !maquinasDe(vigente.conteudo).some(m => m.nome === para)) {
|
|
585
|
+
throw recusa('noctum.coordenacao.fora-do-envelope', `a cessão vai a outra máquina do envelope vigente, não a ${para}`);
|
|
586
|
+
}
|
|
587
|
+
if (estado.cessoes.get(a.geracao)?.para === para)
|
|
588
|
+
return { novos: [], resultado: null };
|
|
589
|
+
if (estado.cessoes.has(a.geracao))
|
|
590
|
+
throw recusa('noctum.coordenacao.concorrencia', `a geração ${a.geracao} já foi cedida`);
|
|
591
|
+
return { novos: [{ tipo: 'cessao', geracao: a.geracao, para, motivo: doCedente }], resultado: null };
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* As reservas da branch da RM-047 lidas agora do remoto, vistas pela coordenação; `null` quando a leitura não veio. A
|
|
596
|
+
* reserva de uma máquina do envelope (a do arco ou a que o dono tomou à mão nela, como na N2) leva a ordem só para ela; a
|
|
597
|
+
* de fora do arco segura a ordem até ser solta. Só o item, a máquina e o instante saem daqui: o nome de quem reservou nunca
|
|
598
|
+
* vai ao ramo de coordenação.
|
|
599
|
+
*/
|
|
600
|
+
function reservasDoArco(carregado, opcoes = {}) {
|
|
601
|
+
let painel;
|
|
602
|
+
try {
|
|
603
|
+
painel = (0, roadmap_reservas_1.listarReservas)(carregado.raiz, { remoto: opcoes.remoto ?? carregado.manifesto.fabrica.remoto, rede: opcoes.rede });
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
return painel.atualizado ? vistasDasReservas(painel.reservas) : null;
|
|
609
|
+
}
|
|
610
|
+
/** As reservas de um painel lido agora, vistas pela coordenação (a mesma de `reservasDoArco`, sem ler de novo). */
|
|
611
|
+
function vistasDasReservas(reservas) {
|
|
612
|
+
return new Map(reservas.map(r => [r.item, { maquina: r.maquina, atualizadaEm: r.atualizadaEm }]));
|
|
613
|
+
}
|
|
614
|
+
// ---------------------------------------------------------------------------
|
|
615
|
+
// Ordens, cancelamento e recibos (D9 e D10).
|
|
616
|
+
// ---------------------------------------------------------------------------
|
|
617
|
+
/** As vagas de execução de uma máquina: as da máquina menos as que só o CHECK usa. */
|
|
618
|
+
const vagasDeExecucao = (c) => (c.capacidade?.maquina ?? 0) - (c.capacidade?.revisao ?? 0);
|
|
619
|
+
const comMotivo = (r, motivo) => String(r?.motivo ?? '').startsWith(motivo);
|
|
620
|
+
/**
|
|
621
|
+
* A máquina já recusou este trabalho nesta versão do envelope, ou o encerrou porque o roadmap dela não tem o item? Ela não
|
|
622
|
+
* recebe a mesma ordem de novo: sem isso, a ordem iria e voltaria a cada passo. A recusa e o fim pela reserva não contam
|
|
623
|
+
* aqui (A1 da revisão): solta ou trocada a reserva, o trabalho sai de novo (`recusouPelaReserva`).
|
|
624
|
+
*/
|
|
625
|
+
function recusou(estado, trabalho, maquina) {
|
|
626
|
+
return [...estado.ordens.values()].some(o => o.trabalho === trabalho && o.maquina === maquina &&
|
|
627
|
+
o.envelopeSha256 === estado.vigente.envelopeSha256 &&
|
|
628
|
+
((o.estado === 'recusada' && !comMotivo(o.recibos.recusada, 'noctum.reserva.de-outra-maquina')) ||
|
|
629
|
+
(o.estado === 'encerrada' && comMotivo(o.recibos.encerrada, 'noctum.reserva.item-invalido'))));
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* A2 da rodada 3: a autoridade cancelou a ordem deste trabalho nesta máquina, nesta versão do envelope? A distribuição
|
|
633
|
+
* automática não a devolve à mesma máquina (a retomada é em outra); a emissão explícita (`emitirOrdem`, da N4) escolhe.
|
|
634
|
+
*/
|
|
635
|
+
function canceladaNela(estado, trabalho, maquina) {
|
|
636
|
+
return [...estado.ordens.values()].some(o => o.trabalho === trabalho && o.maquina === maquina && !!o.cancelada &&
|
|
637
|
+
o.envelopeSha256 === estado.vigente.envelopeSha256);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* A máquina recusou ou encerrou este trabalho pela reserva do item, e a reserva continua a mesma desde então (nem solta,
|
|
641
|
+
* nem trocada)? A mesma ordem voltaria a cair nela: espera a reserva mudar.
|
|
642
|
+
*/
|
|
643
|
+
function recusouPelaReserva(estado, trabalho, maquina, dono) {
|
|
644
|
+
return [...estado.ordens.values()].some(o => {
|
|
645
|
+
if (o.trabalho !== trabalho || o.maquina !== maquina || o.envelopeSha256 !== estado.vigente.envelopeSha256)
|
|
646
|
+
return false;
|
|
647
|
+
const fim = o.estado === 'recusada' ? o.recibos.recusada : o.estado === 'encerrada' ? o.recibos.encerrada : undefined;
|
|
648
|
+
return !!fim && comMotivo(fim, 'noctum.reserva.de-outra-maquina') && Date.parse(fim.em) >= Date.parse(dono.atualizadaEm);
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
/** Por que a ordem do trabalho para a máquina não sai agora; `null` quando ela pode sair. */
|
|
652
|
+
function porQueNaoOrdenar(estado, trabalho, maquina, eu, quando, reservas, emitidasAgora = []) {
|
|
653
|
+
if (estado.revogada)
|
|
654
|
+
return recusa('noctum.coordenacao.revogada', `o arco foi revogado na máquina ${estado.revogada.por}`);
|
|
655
|
+
const c = estado.vigente.conteudo;
|
|
656
|
+
const t = trabalhoDe(c, trabalho);
|
|
657
|
+
if (!t)
|
|
658
|
+
return recusa('noctum.despacho.fora-do-envelope', `o trabalho ${trabalho} não está no envelope vigente`);
|
|
659
|
+
if (!maquinasDe(c).some(m => m.nome === maquina))
|
|
660
|
+
return recusa('noctum.coordenacao.fora-do-envelope', `a máquina ${maquina} não está no envelope vigente`);
|
|
661
|
+
if (t.maquina && t.maquina !== maquina)
|
|
662
|
+
return recusa('noctum.coordenacao.fora-do-envelope', `o trabalho ${trabalho} está preso à máquina ${t.maquina}`);
|
|
663
|
+
const s = estado.trabalhos.get(trabalho);
|
|
664
|
+
if (s.estado !== 'livre')
|
|
665
|
+
return recusa('noctum.coordenacao.ordem-viva', `o trabalho ${trabalho} está ${s.estado}${s.ordem ? ` (ordem ${s.ordem.ordem})` : ''}`);
|
|
666
|
+
if (emitidasAgora.some(x => x.trabalho === trabalho))
|
|
667
|
+
return recusa('noctum.coordenacao.ordem-viva', `o trabalho ${trabalho} acabou de receber ordem`);
|
|
668
|
+
const faltam = t.dependeDe.filter(d => estado.trabalhos.get(d)?.estado !== 'entregue');
|
|
669
|
+
if (faltam.length)
|
|
670
|
+
return recusa('noctum.coordenacao.dependencia-pendente', `o trabalho ${trabalho} depende de ${faltam.join(', ')}, ainda não entregue(s)`);
|
|
671
|
+
const doItem = [...[...estado.ordens.values()].filter(exports.ordemViva).map(o => ({ trabalho: o.trabalho, item: o.item, maquina: o.maquina })),
|
|
672
|
+
...emitidasAgora].find(o => o.item === t.item && o.trabalho !== trabalho);
|
|
673
|
+
if (doItem && doItem.maquina !== maquina) {
|
|
674
|
+
return recusa('noctum.coordenacao.ordem-viva', `o item ${t.item} está com a máquina ${doItem.maquina} (trabalho ${doItem.trabalho}); a reserva é de uma máquina só`);
|
|
675
|
+
}
|
|
676
|
+
// A1 da revisão: a reserva da RM-047, lida agora, decide para onde a ordem vai, ou se ela espera.
|
|
677
|
+
const dono = reservas.get(t.item);
|
|
678
|
+
if (dono && !maquinasDe(c).some(m => m.nome === dono.maquina)) {
|
|
679
|
+
return recusa('noctum.reserva.de-outra-maquina', `o item ${t.item} está reservado fora do arco; a ordem espera a reserva ser solta`);
|
|
680
|
+
}
|
|
681
|
+
if (dono && dono.maquina !== maquina) {
|
|
682
|
+
return recusa('noctum.reserva.de-outra-maquina', `o item ${t.item} está reservado pela máquina ${dono.maquina} do arco; a ordem vai para ela`);
|
|
683
|
+
}
|
|
684
|
+
if (dono && recusouPelaReserva(estado, trabalho, maquina, dono)) {
|
|
685
|
+
return recusa('noctum.reserva.de-outra-maquina', `a máquina ${maquina} não pôde usar a reserva do item ${t.item}; a ordem espera a reserva mudar`);
|
|
686
|
+
}
|
|
687
|
+
if (!batidaRecente(estado, maquina, eu, quando)) {
|
|
688
|
+
return recusa('noctum.coordenacao.maquina-sem-batida', `a máquina ${maquina} não dá sinal há mais de ${exports.BATIDA_VENCIDA_MS / 60000} min; ela não recebe ordem nova`);
|
|
689
|
+
}
|
|
690
|
+
if (recusou(estado, trabalho, maquina)) {
|
|
691
|
+
return recusa('noctum.coordenacao.maquina-recusou', `a máquina ${maquina} já recusou o trabalho ${trabalho} nesta versão do envelope`);
|
|
692
|
+
}
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
function camposDaOrdem(estado, trabalho, maquina, geracao) {
|
|
696
|
+
const t = trabalhoDe(estado.vigente.conteudo, trabalho);
|
|
697
|
+
const n = [...estado.ordens.values()].filter(o => o.trabalho === trabalho).length + 1;
|
|
698
|
+
return { tipo: 'ordem', ordem: `O-${trabalho}-${n}`, trabalho, item: t.item, maquina, geracao, n };
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Emite a ordem de um trabalho para uma máquina; só a autoridade vigente, com o arco vigente aqui e as reservas lidas agora
|
|
702
|
+
* (o item reservado fora do arco, ou por outra máquina dele, segura a ordem).
|
|
703
|
+
*/
|
|
704
|
+
function emitirOrdem(carregado, arco, pedido, opcoes = {}) {
|
|
705
|
+
const quando = opcoes.quando ?? new Date().toISOString();
|
|
706
|
+
exigirArcoVigente(carregado, arco, quando);
|
|
707
|
+
return gravarNaCoordenacao(carregado, arco, { ...opcoes, quando }, (leitura) => {
|
|
708
|
+
const estado = derivarCoordenacao(leitura);
|
|
709
|
+
const { eu } = (0, noctum_transporte_1.euNaVersaoVigente)(carregado.raiz, leitura, opcoes);
|
|
710
|
+
const a = exigirAutoridade(estado, eu.nome);
|
|
711
|
+
// S3 da rodada 2: a ordem viva deste trabalho para a mesma máquina, emitida por esta geração e não cancelada, é a mesma
|
|
712
|
+
// ordem: repetir depois de um push de resultado incerto devolve o id dela, sem gravar de novo.
|
|
713
|
+
const ja = estado.trabalhos.get(pedido.trabalho)?.ordem;
|
|
714
|
+
if (ja && (ja.estado === 'emitida' || ja.estado === 'aceita') && ja.maquina === pedido.maquina && ja.geracao === a.geracao &&
|
|
715
|
+
ja.envelopeSha256 === estado.vigente.envelopeSha256) {
|
|
716
|
+
return { novos: [], resultado: ja.ordem };
|
|
717
|
+
}
|
|
718
|
+
const reservas = reservasDoArco(carregado, opcoes);
|
|
719
|
+
if (!reservas)
|
|
720
|
+
throw recusa('noctum.coordenacao.sem-remoto', 'as reservas não se leram agora; sem elas, nenhuma ordem sai');
|
|
721
|
+
const nao = porQueNaoOrdenar(estado, pedido.trabalho, pedido.maquina, eu.nome, quando, reservas);
|
|
722
|
+
if (nao)
|
|
723
|
+
throw nao;
|
|
724
|
+
const campos = camposDaOrdem(estado, pedido.trabalho, pedido.maquina, a.geracao);
|
|
725
|
+
return { novos: [campos], resultado: campos.ordem };
|
|
726
|
+
}).resultado;
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* D9: a política da autoridade, pura. Na ordem do dono (prioridade e posição no envelope), cada trabalho `livre` com as
|
|
730
|
+
* dependências entregues vai à máquina presa, à do mesmo item (pela ordem viva ou pela reserva lida agora) ou à máquina
|
|
731
|
+
* com sinal recente e menos ordens vivas, até as vagas de execução dela; desempate pela ordem das máquinas no envelope. O
|
|
732
|
+
* item reservado fora do arco espera, e nada sai com o arco revogado (A1 e A8 da revisão, em `porQueNaoOrdenar`).
|
|
733
|
+
*/
|
|
734
|
+
function distribuirOrdens(estado, eu, quando, reservas) {
|
|
735
|
+
const a = estado.autoridade;
|
|
736
|
+
if (!a || a.maquina !== eu)
|
|
737
|
+
return [];
|
|
738
|
+
const c = estado.vigente.conteudo;
|
|
739
|
+
if (!(0, noctum_envelope_1.envelopeMultimaquina)(c))
|
|
740
|
+
return [];
|
|
741
|
+
const limite = vagasDeExecucao(c);
|
|
742
|
+
const vivas = new Map(c.maquinas.map(m => [m.nome, [...estado.ordens.values()].filter(o => (0, exports.ordemViva)(o) && o.maquina === m.nome).length]));
|
|
743
|
+
const emitidas = [];
|
|
744
|
+
const fila = c.trabalhos.map((t, i) => ({ t, i })).sort((x, y) => x.t.prioridade - y.t.prioridade || x.i - y.i);
|
|
745
|
+
for (const { t } of fila) {
|
|
746
|
+
if (estado.trabalhos.get(t.id)?.estado !== 'livre')
|
|
747
|
+
continue;
|
|
748
|
+
const doItem = [...[...estado.ordens.values()].filter(exports.ordemViva).map(o => ({ item: o.item, maquina: o.maquina })), ...emitidas]
|
|
749
|
+
.find(o => o.item === t.item)?.maquina;
|
|
750
|
+
const candidatas = t.maquina ? [t.maquina] : doItem ? [doItem]
|
|
751
|
+
: c.maquinas.map(m => m.nome).sort((x, y) => vivas.get(x) - vivas.get(y) || c.maquinas.findIndex(m => m.nome === x) - c.maquinas.findIndex(m => m.nome === y));
|
|
752
|
+
const alvo = candidatas.find(m => !canceladaNela(estado, t.id, m) && (vivas.get(m) ?? limite) < limite &&
|
|
753
|
+
porQueNaoOrdenar(estado, t.id, m, eu, quando, reservas, emitidas) === null);
|
|
754
|
+
if (!alvo)
|
|
755
|
+
continue;
|
|
756
|
+
emitidas.push({ trabalho: t.id, item: t.item, maquina: alvo });
|
|
757
|
+
vivas.set(alvo, vivas.get(alvo) + 1);
|
|
758
|
+
}
|
|
759
|
+
return emitidas.map(({ trabalho, maquina }) => ({ trabalho, maquina }));
|
|
760
|
+
}
|
|
761
|
+
/** Os registros das ordens que a política distribui agora, com os números certos mesmo para vários do mesmo trabalho. */
|
|
762
|
+
function registrosDaDistribuicao(estado, eu, quando, reservas) {
|
|
763
|
+
const a = estado.autoridade;
|
|
764
|
+
if (!a || a.maquina !== eu)
|
|
765
|
+
return [];
|
|
766
|
+
return distribuirOrdens(estado, eu, quando, reservas).map(({ trabalho, maquina }) => camposDaOrdem(estado, trabalho, maquina, a.geracao));
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* A7 da revisão: a autoridade abandona a vez de integrar da máquina que não dá sinal há mais de 15 min, para o destino
|
|
770
|
+
* voltar a andar. O trabalho dela não muda de máquina sem prova; na volta, a reconciliação dela registra a entrega se o
|
|
771
|
+
* merge tiver chegado à base.
|
|
772
|
+
*/
|
|
773
|
+
function registrosDosAbandonos(estado, eu, quando) {
|
|
774
|
+
const a = estado.autoridade;
|
|
775
|
+
if (!a || a.maquina !== eu)
|
|
776
|
+
return [];
|
|
777
|
+
return [...estado.vezes.values()].flat().filter(v => !v.fim && v.maquina !== eu && !batidaRecente(estado, v.maquina, eu, quando))
|
|
778
|
+
.map(v => ({ tipo: 'vez-fim', chave: v.chave, n: v.n, desfecho: 'abandonada', geracao: a.geracao, sinalDoDono: estado.sinais.get(v.maquina) ?? null,
|
|
779
|
+
motivo: `a máquina ${v.maquina} não dá sinal há mais de ${exports.BATIDA_VENCIDA_MS / 60000} min; o destino volta a andar, e o trabalho dela não muda de máquina sem prova` }));
|
|
780
|
+
}
|
|
781
|
+
/** Cancela uma ordem viva; só a autoridade vigente. Antes do aceite, o trabalho fica livre na hora; depois, isolado. */
|
|
782
|
+
function cancelarOrdem(carregado, arco, ordem, motivo, opcoes = {}) {
|
|
783
|
+
const doCancelamento = motivoPublico(motivo, 'o cancelamento');
|
|
784
|
+
return gravarNaCoordenacao(carregado, arco, opcoes, (leitura) => {
|
|
785
|
+
const estado = derivarCoordenacao(leitura);
|
|
786
|
+
const { eu } = (0, noctum_transporte_1.euNaVersaoVigente)(carregado.raiz, leitura, opcoes);
|
|
787
|
+
const a = exigirAutoridade(estado, eu.nome);
|
|
788
|
+
const o = exigirOrdem(estado, ordem);
|
|
789
|
+
if (o.cancelada)
|
|
790
|
+
return { novos: [], resultado: o.estado };
|
|
791
|
+
if (!(0, exports.ordemViva)(o))
|
|
792
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a ordem ${ordem} já terminou (${o.estado})`);
|
|
793
|
+
return { novos: [{ tipo: 'cancelamento', ordem, motivo: doCancelamento, geracao: a.geracao }],
|
|
794
|
+
resultado: o.estado === 'emitida' ? 'cancelada-antes' : 'isolada' };
|
|
795
|
+
}).resultado;
|
|
796
|
+
}
|
|
797
|
+
/** O motivo tipado da reserva de fora, igual em todo registro: só o item, nunca quem reservou (A1 da revisão). */
|
|
798
|
+
const motivoDaReservaDeFora = (item) => `noctum.reserva.de-outra-maquina: o item ${item} está reservado por outra máquina ou pessoa`;
|
|
799
|
+
exports.motivoDaReservaDeFora = motivoDaReservaDeFora;
|
|
800
|
+
/** A resposta desta máquina a uma ordem emitida para ela: aceita ou recusada com o motivo (D10). */
|
|
801
|
+
function respostaAOrdem(estado, o, eu, fatos) {
|
|
802
|
+
if (o.envelopeSha256 !== estado.vigente.envelopeSha256)
|
|
803
|
+
return { recibo: 'recusada', motivo: 'noctum.efeito.hash-antigo: a ordem é de outra versão do envelope' };
|
|
804
|
+
if (estado.revogada)
|
|
805
|
+
return { recibo: 'recusada', motivo: `noctum.coordenacao.revogada: o arco foi revogado na máquina ${estado.revogada.por}` };
|
|
806
|
+
if (fatos.arcoEncerrado)
|
|
807
|
+
return { recibo: 'recusada', motivo: 'noctum.efeito.encerrado: o arco encerrou nesta máquina' };
|
|
808
|
+
if (!fatos.arcoVigente)
|
|
809
|
+
return { recibo: 'recusada', motivo: 'noctum.efeito.fora-da-janela: o arco não está vigente nesta máquina' };
|
|
810
|
+
const t = trabalhoDe(estado.vigente.conteudo, o.trabalho);
|
|
811
|
+
if (!t)
|
|
812
|
+
return { recibo: 'recusada', motivo: 'noctum.despacho.fora-do-envelope: o trabalho não está no envelope vigente' };
|
|
813
|
+
if (t.maquina && t.maquina !== eu)
|
|
814
|
+
return { recibo: 'recusada', motivo: `noctum.coordenacao.fora-do-envelope: o trabalho está preso à máquina ${t.maquina}` };
|
|
815
|
+
if (fatos.itensDeOutros?.has(t.item))
|
|
816
|
+
return { recibo: 'recusada', motivo: (0, exports.motivoDaReservaDeFora)(t.item) };
|
|
817
|
+
if (t.thread) {
|
|
818
|
+
// A thread que o trabalho nomeia precisa morar aqui, ser do item dele e de #Auto: senão a fila da N2 nunca a despacharia.
|
|
819
|
+
const th = fatos.threadsLocais.get(t.thread);
|
|
820
|
+
if (!th)
|
|
821
|
+
return { recibo: 'recusada', motivo: `noctum.despacho.fora-do-envelope: a thread ${t.thread} não existe nesta máquina` };
|
|
822
|
+
if (th.roadmap !== t.item)
|
|
823
|
+
return { recibo: 'recusada', motivo: `noctum.despacho.fora-do-envelope: a thread ${t.thread} é do item ${th.roadmap ?? 'nenhum'}, não do ${t.item}` };
|
|
824
|
+
if (th.modo !== 'auto')
|
|
825
|
+
return { recibo: 'recusada', motivo: `noctum.despacho.fora-do-envelope: a thread ${t.thread} está em ${th.modo}; o arco só conduz #Auto` };
|
|
826
|
+
}
|
|
827
|
+
return { recibo: 'aceita', motivo: 'ordem aceita' };
|
|
828
|
+
}
|
|
829
|
+
/** Os recibos de aceite ou recusa das ordens emitidas para esta máquina que ainda não têm resposta. */
|
|
830
|
+
function registrosDasRespostas(estado, eu, fatos) {
|
|
831
|
+
return [...estado.ordens.values()].filter(o => o.maquina === eu && o.estado === 'emitida')
|
|
832
|
+
.map(o => ({ tipo: 'recibo', ordem: o.ordem, ...respostaAOrdem(estado, o, eu, fatos) }));
|
|
833
|
+
}
|
|
834
|
+
/** Responde uma ordem desta máquina. Só a ordem dela, emitida e não cancelada; o aceite exige o arco vigente aqui e na rede. */
|
|
835
|
+
function responderOrdem(carregado, arco, ordem, resposta, motivo, opcoes = {}) {
|
|
836
|
+
const quando = opcoes.quando ?? new Date().toISOString();
|
|
837
|
+
const doRecibo = motivo ? motivoPublico(motivo, 'o recibo') : '';
|
|
838
|
+
if (resposta === 'aceita')
|
|
839
|
+
exigirArcoVigente(carregado, arco, quando);
|
|
840
|
+
return gravarNaCoordenacao(carregado, arco, { ...opcoes, quando }, (leitura) => {
|
|
841
|
+
const estado = derivarCoordenacao(leitura);
|
|
842
|
+
const { eu } = (0, noctum_transporte_1.euNaVersaoVigente)(carregado.raiz, leitura, opcoes);
|
|
843
|
+
const o = exigirOrdem(estado, ordem);
|
|
844
|
+
if (o.maquina !== eu.nome)
|
|
845
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a ordem ${ordem} é da máquina ${o.maquina}`);
|
|
846
|
+
if (o.recibos[resposta])
|
|
847
|
+
return { novos: [], resultado: o.estado };
|
|
848
|
+
if (o.estado !== 'emitida')
|
|
849
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a ordem ${ordem} está ${o.estado}`);
|
|
850
|
+
if (resposta === 'aceita' && o.envelopeSha256 !== estado.vigente.envelopeSha256) {
|
|
851
|
+
throw recusa('noctum.efeito.hash-antigo', `a ordem ${ordem} é de outra versão do envelope`);
|
|
852
|
+
}
|
|
853
|
+
if (resposta === 'aceita')
|
|
854
|
+
exigirNaoRevogada(estado);
|
|
855
|
+
return { novos: [{ tipo: 'recibo', ordem, recibo: resposta, motivo: doRecibo }], resultado: resposta === 'aceita' ? 'aceita' : 'recusada' };
|
|
856
|
+
}).resultado;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* G8-6: a vaga que outra chave da máquina tomou tem o despacho no log de outra raiz; daqui não se prova a parada dela. Vale
|
|
860
|
+
* no fim e na conclusão (N1 da terceira passada do CHECK-REVERIFY): os dois podem tirar a última ordem viva do item, e
|
|
861
|
+
* outro trabalho dele iria a outra máquina.
|
|
862
|
+
*/
|
|
863
|
+
function exigirSemVagaDeOutraChave(estado, o, chave, eu) {
|
|
864
|
+
const deOutra = estado.vagas.find(v => v.ordem === o.ordem && !v.solta && v.chave !== chave);
|
|
865
|
+
if (deOutra) {
|
|
866
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a vaga ${deOutra.vaga} da ordem ${o.ordem} é de outra chave da máquina ${eu}; ` +
|
|
867
|
+
'só a raiz dela prova a parada da sessão');
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* O executor conclui a ordem pela thread local: entregue ou fechada sem entrega. Vale também depois do cancelamento e
|
|
872
|
+
* depois de uma emenda que tirou esta máquina do arco (A4 da revisão: o fim sai sob a versão em que ela estava).
|
|
873
|
+
*/
|
|
874
|
+
function concluirOrdem(carregado, arco, ordem, conclusao, opcoes = {}) {
|
|
875
|
+
if (conclusao.resultado !== 'entregue' && conclusao.resultado !== 'encerrado-sem-entrega') {
|
|
876
|
+
throw recusa('noctum.envelope.invalido', 'a conclusão é entregue ou encerrado-sem-entrega');
|
|
877
|
+
}
|
|
878
|
+
if (conclusao.thread !== null && !linha(conclusao.thread, 80))
|
|
879
|
+
throw recusa('noctum.envelope.invalido', 'a thread da conclusão vai em uma linha de até 80 caracteres');
|
|
880
|
+
const chave = (0, noctum_transporte_1.identidadeDaMaquina)(carregado.raiz, opcoes.maquina).chave;
|
|
881
|
+
gravarNaCoordenacao(carregado, arco, opcoes, (leitura) => {
|
|
882
|
+
const estado = derivarCoordenacao(leitura);
|
|
883
|
+
const eu = nomeDe(opcoes);
|
|
884
|
+
const o = exigirOrdem(estado, ordem);
|
|
885
|
+
if (o.maquina !== eu)
|
|
886
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a ordem ${ordem} é da máquina ${o.maquina}`);
|
|
887
|
+
if (!ordemDaRaiz(estado, o, { nome: eu, chave }))
|
|
888
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a ordem ${ordem} é de outra chave da máquina ${eu}`);
|
|
889
|
+
if (o.estado === 'concluida')
|
|
890
|
+
return { novos: [], resultado: null };
|
|
891
|
+
if (o.estado !== 'aceita' && o.estado !== 'isolada')
|
|
892
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a ordem ${ordem} está ${o.estado}`);
|
|
893
|
+
exigirSemVagaDeOutraChave(estado, o, chave, eu);
|
|
894
|
+
return { novos: [{ tipo: 'recibo', ordem, recibo: 'concluida', motivo: 'thread local ' + conclusao.resultado, resultado: conclusao.resultado,
|
|
895
|
+
thread: conclusao.thread }], resultado: null };
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* O executor confirma o fim da ordem (cancelada, de outra versão, com o arco fora da janela ou revogado, ou sem a reserva
|
|
900
|
+
* do item) com o checkpoint do que soltou. Só depois de nenhuma sessão dela viver nesta máquina: é este recibo que prova
|
|
901
|
+
* que o executor antigo parou.
|
|
902
|
+
*/
|
|
903
|
+
function encerrarOrdem(carregado, arco, ordem, fim, opcoes = {}) {
|
|
904
|
+
if (!checkpointValido(fim.checkpoint)) {
|
|
905
|
+
throw recusa('noctum.envelope.invalido', `o checkpoint do fim leva só a thread (uma linha de até 80 caracteres) e as vagas soltas (até ${exports.TETO_DE_VAGAS_NO_CHECKPOINT}); ` +
|
|
906
|
+
'sessão, caminho e texto livre não vão ao ramo');
|
|
907
|
+
}
|
|
908
|
+
const checkpoint = { thread: fim.checkpoint.thread ?? null, vagasSoltas: fim.checkpoint.vagasSoltas ?? [] };
|
|
909
|
+
const doFim = motivoPublico(fim.motivo, 'o fim da ordem');
|
|
910
|
+
const chave = (0, noctum_transporte_1.identidadeDaMaquina)(carregado.raiz, opcoes.maquina).chave;
|
|
911
|
+
gravarNaCoordenacao(carregado, arco, opcoes, (leitura) => {
|
|
912
|
+
const estado = derivarCoordenacao(leitura);
|
|
913
|
+
const eu = nomeDe(opcoes);
|
|
914
|
+
const o = exigirOrdem(estado, ordem);
|
|
915
|
+
if (o.maquina !== eu)
|
|
916
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a ordem ${ordem} é da máquina ${o.maquina}`);
|
|
917
|
+
if (!ordemDaRaiz(estado, o, { nome: eu, chave }))
|
|
918
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a ordem ${ordem} é de outra chave da máquina ${eu}`);
|
|
919
|
+
if (o.estado === 'encerrada')
|
|
920
|
+
return { novos: [], resultado: null };
|
|
921
|
+
if (o.estado !== 'aceita' && o.estado !== 'isolada')
|
|
922
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a ordem ${ordem} está ${o.estado}`);
|
|
923
|
+
exigirSemVagaDeOutraChave(estado, o, chave, eu);
|
|
924
|
+
const espera = esperaDaVez(estado, o);
|
|
925
|
+
if (espera === 'ativa') {
|
|
926
|
+
throw recusa('noctum.integracao.nao-provada', `a ordem ${ordem} tem uma vez de integrar ativa; o merge pode ter chegado à base, e a reconciliação decide antes do fim`);
|
|
927
|
+
}
|
|
928
|
+
if (espera === 'sem-prova') {
|
|
929
|
+
throw recusa('noctum.integracao.base-nao-provada', `a ordem ${ordem} tem uma fonte que chegou à base fora da cerca; o trabalho espera a inspeção do dono`);
|
|
930
|
+
}
|
|
931
|
+
if (espera === 'abandonada') {
|
|
932
|
+
throw recusa('noctum.integracao.nao-provada', `a ordem ${ordem} tem uma vez abandonada cuja base ainda precisa ser conferida pela reconciliação`);
|
|
933
|
+
}
|
|
934
|
+
return { novos: [{ tipo: 'recibo', ordem, recibo: 'encerrada', motivo: doFim, checkpoint }], resultado: null };
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
/** A batida desta máquina: é ela que diz à autoridade que a máquina está viva para receber ordem nova. */
|
|
938
|
+
function bater(carregado, arco, opcoes = {}) {
|
|
939
|
+
gravarNaCoordenacao(carregado, arco, opcoes, () => ({ novos: [{ tipo: 'batida' }], resultado: null }));
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* A8 da revisão: o dono revogou o arco nesta máquina, e a revogação vai ao ramo para valer em todas. Só com o arco revogado
|
|
943
|
+
* aqui pela N1; uma vez por máquina. Vale para sempre: nenhuma ordem, aceite, vaga ou vez nova sai depois dela.
|
|
944
|
+
*/
|
|
945
|
+
function revogarNaRede(carregado, arco, opcoes = {}) {
|
|
946
|
+
const quando = opcoes.quando ?? new Date().toISOString();
|
|
947
|
+
if ((0, noctum_arco_1.lerArco)(carregado.raiz, arco, quando)?.situacao !== 'revogado') {
|
|
948
|
+
throw recusa('noctum.envelope.invalido', `o arco ${arco} não foi revogado pelo dono nesta máquina; a revogação na rede sai só depois da dela`);
|
|
949
|
+
}
|
|
950
|
+
return gravarNaCoordenacao(carregado, arco, { ...opcoes, quando }, (leitura) => {
|
|
951
|
+
const eu = nomeDe(opcoes);
|
|
952
|
+
if (leitura.registros.some(r => r.dados.tipo === 'revogacao' && r.dados.por === eu))
|
|
953
|
+
return { novos: [], resultado: false };
|
|
954
|
+
return { novos: [{ tipo: 'revogacao', motivo: 'o dono revogou o arco nesta máquina' }], resultado: true };
|
|
955
|
+
}).resultado;
|
|
956
|
+
}
|
|
957
|
+
/** Por que a vaga não cabe agora, somando as máquinas; `null` quando cabe. */
|
|
958
|
+
function porQueSemVaga(estado, p) {
|
|
959
|
+
const c = estado.vigente.conteudo;
|
|
960
|
+
if (!(0, noctum_envelope_1.envelopeMultimaquina)(c))
|
|
961
|
+
return recusa('noctum.coordenacao.fora-do-envelope', 'o envelope vigente não é de várias máquinas');
|
|
962
|
+
const limite = c.capacidadeGlobal.porConta.find(x => x.conta === p.conta)?.sessoes;
|
|
963
|
+
if (limite === undefined)
|
|
964
|
+
return recusa('noctum.coordenacao.fora-do-envelope', `a conta ${p.conta} não está no envelope vigente`);
|
|
965
|
+
if (!c.orcamento.porRuntime.some(r => r.runtime === p.runtime))
|
|
966
|
+
return recusa('noctum.coordenacao.fora-do-envelope', `o runtime ${p.runtime} não está no envelope vigente`);
|
|
967
|
+
const ativas = estado.vagas.filter(v => !v.solta && v.conta === p.conta);
|
|
968
|
+
if (ativas.length >= limite) {
|
|
969
|
+
return recusa('noctum.coordenacao.vaga-global', `${ativas.length} sessão(ões) na conta ${p.conta} somando as máquinas, e o limite é ${limite}` +
|
|
970
|
+
` (${[...new Set(ativas.map(v => v.maquina))].join(', ')})`);
|
|
971
|
+
}
|
|
972
|
+
const tetos = [['do arco', c.orcamento.arco, estado.consumo.arco],
|
|
973
|
+
[`da conta ${p.conta}`, c.orcamento.porConta.find(x => x.conta === p.conta)?.teto, estado.consumo.porConta[p.conta] ?? 0],
|
|
974
|
+
[`do runtime ${p.runtime}`, c.orcamento.porRuntime.find(x => x.runtime === p.runtime)?.teto, estado.consumo.porRuntime[p.runtime] ?? 0]];
|
|
975
|
+
for (const [nome, teto, usado] of tetos) {
|
|
976
|
+
if (!teto)
|
|
977
|
+
return recusa('noctum.coordenacao.fora-do-envelope', `sem teto ${nome} no envelope vigente`);
|
|
978
|
+
if (teto.unidade !== 'sessoes') {
|
|
979
|
+
// RM-058 N6: a medição das outras unidades é desta máquina; somar as máquinas só sabe contar sessões.
|
|
980
|
+
return recusa('noctum.orcamento.sem-medicao', `o teto ${nome} está em ${teto.unidade}, e a soma entre máquinas só mede sessoes: consumo desconhecido nunca vale zero`);
|
|
981
|
+
}
|
|
982
|
+
if (usado + 1 > teto.valor)
|
|
983
|
+
return recusa('noctum.orcamento.esgotado', `o teto ${nome} é de ${teto.valor} sessoes e ${usado} já foram reservadas somando as máquinas`);
|
|
984
|
+
}
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
/** As contas sem vaga global agora, somando as máquinas: o passo da máquina as tira da escolha da N2. */
|
|
988
|
+
function contasSemVagaGlobal(estado) {
|
|
989
|
+
const c = estado.vigente.conteudo;
|
|
990
|
+
if (!(0, noctum_envelope_1.envelopeMultimaquina)(c))
|
|
991
|
+
return new Set();
|
|
992
|
+
return new Set(c.capacidadeGlobal.porConta.filter(x => estado.vagas.filter(v => !v.solta && v.conta === x.conta).length >= x.sessoes).map(x => x.conta));
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Toma a vaga global de um despacho desta máquina, depois da reserva local (o `seq` dela) e antes do lançamento: a ordem é
|
|
996
|
+
* desta máquina, aceita e não cancelada, o arco está vigente aqui e não revogado na rede, a conta tem vaga somando as
|
|
997
|
+
* máquinas e os tetos do arco, da conta e do runtime cabem mais uma sessão. Idempotente pela reserva local.
|
|
998
|
+
*/
|
|
999
|
+
function tomarVagaGlobal(carregado, arco, p, opcoes = {}) {
|
|
1000
|
+
const quando = opcoes.quando ?? new Date().toISOString();
|
|
1001
|
+
exigirArcoVigente(carregado, arco, quando);
|
|
1002
|
+
return gravarNaCoordenacao(carregado, arco, { ...opcoes, quando }, (leitura) => {
|
|
1003
|
+
const estado = derivarCoordenacao(leitura);
|
|
1004
|
+
const { eu, vigente } = (0, noctum_transporte_1.euNaVersaoVigente)(carregado.raiz, leitura, opcoes);
|
|
1005
|
+
const vaga = `V-${eu.nome}-${p.reserva}`;
|
|
1006
|
+
const ja = estado.vagas.find(v => v.vaga === vaga);
|
|
1007
|
+
if (ja) {
|
|
1008
|
+
if (ja.ordem !== p.ordem || ja.conta !== p.conta || ja.runtime !== p.runtime) {
|
|
1009
|
+
throw recusa('noctum.coordenacao.concorrencia', `a vaga ${vaga} já existe para outro despacho`);
|
|
1010
|
+
}
|
|
1011
|
+
// S2 da rodada 2: a vaga já solta com o mesmo `seq` é um `seq` local repetido (raiz de estado nova), não esta reserva.
|
|
1012
|
+
if (ja.solta)
|
|
1013
|
+
throw recusa('noctum.coordenacao.concorrencia', `a vaga ${vaga} já foi tomada e solta; o seq local ${p.reserva} se repetiu`);
|
|
1014
|
+
return { novos: [], resultado: vaga };
|
|
1015
|
+
}
|
|
1016
|
+
exigirNaoRevogada(estado);
|
|
1017
|
+
const o = exigirOrdem(estado, p.ordem);
|
|
1018
|
+
if (o.maquina !== eu.nome || o.estado !== 'aceita') {
|
|
1019
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a ordem ${p.ordem} não é uma ordem aceita e viva desta máquina (${o.estado})`);
|
|
1020
|
+
}
|
|
1021
|
+
if (o.envelopeSha256 !== vigente.envelopeSha256)
|
|
1022
|
+
throw recusa('noctum.efeito.hash-antigo', `a ordem ${p.ordem} é de outra versão do envelope`);
|
|
1023
|
+
const nao = porQueSemVaga(estado, p);
|
|
1024
|
+
if (nao)
|
|
1025
|
+
throw nao;
|
|
1026
|
+
const c = vigente.conteudo;
|
|
1027
|
+
return { novos: [{ tipo: 'vaga', vaga, conta: c.capacidadeGlobal.porConta.findIndex(x => x.conta === p.conta), runtime: p.runtime,
|
|
1028
|
+
ordem: p.ordem, trabalho: o.trabalho, reserva: p.reserva }], resultado: vaga };
|
|
1029
|
+
}).resultado;
|
|
1030
|
+
}
|
|
1031
|
+
/** Solta a vaga global desta máquina, dizendo se a sessão consumiu (a recusa antes do lançamento não consome). */
|
|
1032
|
+
function soltarVagaGlobal(carregado, arco, vaga, soltura, opcoes = {}) {
|
|
1033
|
+
const daSoltura = motivoPublico(soltura.motivo, 'a soltura da vaga');
|
|
1034
|
+
const chave = (0, noctum_transporte_1.identidadeDaMaquina)(carregado.raiz, opcoes.maquina).chave;
|
|
1035
|
+
gravarNaCoordenacao(carregado, arco, opcoes, (leitura) => {
|
|
1036
|
+
const estado = derivarCoordenacao(leitura);
|
|
1037
|
+
const eu = nomeDe(opcoes);
|
|
1038
|
+
const v = estado.vagas.find(x => x.vaga === vaga);
|
|
1039
|
+
if (!v || v.maquina !== eu)
|
|
1040
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a vaga ${vaga} não é desta máquina`);
|
|
1041
|
+
// G8-6: só a raiz da chave que tomou a vaga tem o despacho dela no log local, e só ela a solta.
|
|
1042
|
+
if (v.chave !== chave)
|
|
1043
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a vaga ${vaga} é de outra chave da máquina ${eu}; só a raiz dela a solta`);
|
|
1044
|
+
if (v.solta)
|
|
1045
|
+
return { novos: [], resultado: null };
|
|
1046
|
+
return { novos: [{ tipo: 'vaga-solta', vaga, consumiu: soltura.consumiu, motivo: daSoltura }], resultado: null };
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* A reserva do item, lida do remoto agora: livre, desta máquina e de quem a usa (`minha`, com qualquer nota, como na N2:
|
|
1051
|
+
* a que o dono tomou à mão aqui também vale para o arco daqui), e se é a do arco (`doArco`, pela nota). `null` quando a
|
|
1052
|
+
* leitura não veio.
|
|
1053
|
+
*/
|
|
1054
|
+
function reservaDoItem(carregado, arco, item, opcoes = {}) {
|
|
1055
|
+
const { raiz } = carregado;
|
|
1056
|
+
const eu = (0, roadmap_reservas_1.quemSouEu)(raiz, { maquina: opcoes.maquina });
|
|
1057
|
+
let painel;
|
|
1058
|
+
try {
|
|
1059
|
+
painel = (0, roadmap_reservas_1.listarReservas)(raiz, { remoto: opcoes.remoto ?? carregado.manifesto.fabrica.remoto, rede: opcoes.rede });
|
|
1060
|
+
}
|
|
1061
|
+
catch {
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
if (!painel.atualizado)
|
|
1065
|
+
return null;
|
|
1066
|
+
const r = painel.reservas.find(x => x.item === item);
|
|
1067
|
+
const minha = !!r && r.maquina === eu.maquina && r.por === eu.por;
|
|
1068
|
+
return { livre: !r, minha, doArco: minha && r.nota === (0, exports.notaDaReserva)(arco) };
|
|
1069
|
+
}
|
|
1070
|
+
/** O erro do `pegarItem`, tipado (A6 da revisão): reserva de fora, item que o roadmap daqui não tem, ou rede. */
|
|
1071
|
+
function motivoDoPegar(e) {
|
|
1072
|
+
const msg = String(e?.message ?? e).split('\n')[0].slice(0, 300);
|
|
1073
|
+
if (msg.startsWith('roadmap.reservado'))
|
|
1074
|
+
return { motivo: 'noctum.reserva.de-outra-maquina', detalhe: 'o item está com outra máquina ou pessoa' };
|
|
1075
|
+
if (msg.startsWith('roadmap.item'))
|
|
1076
|
+
return { motivo: 'noctum.reserva.item-invalido', detalhe: msg };
|
|
1077
|
+
return { motivo: 'noctum.coordenacao.sem-remoto', detalhe: msg };
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Toma a reserva do item da ordem aceita desta máquina e a revalida com leitura nova. Nunca força: a reserva de outra
|
|
1081
|
+
* máquina ou pessoa volta `noctum.reserva.de-outra-maquina`, e quem chama encerra a ordem que nunca despachou. A que já é
|
|
1082
|
+
* desta máquina vale como está, sem regravar (a nota do dono fica, e a reconciliação não a solta). O item que o roadmap
|
|
1083
|
+
* desta máquina não tem volta `noctum.reserva.item-invalido`.
|
|
1084
|
+
*/
|
|
1085
|
+
function tomarReservaDoTrabalho(carregado, arco, ordem, thread, opcoes = {}) {
|
|
1086
|
+
const estado = lerCoordenacao(carregado, arco, opcoes);
|
|
1087
|
+
const eu = nomeDe(opcoes);
|
|
1088
|
+
const o = exigirOrdem(estado, ordem);
|
|
1089
|
+
if (o.maquina !== eu || o.estado !== 'aceita')
|
|
1090
|
+
throw recusa('noctum.coordenacao.ordem-vencida', `a ordem ${ordem} não é uma ordem aceita e viva desta máquina`);
|
|
1091
|
+
const antes = reservaDoItem(carregado, arco, o.item, opcoes);
|
|
1092
|
+
if (antes === null)
|
|
1093
|
+
return { ok: false, motivo: 'noctum.coordenacao.sem-remoto', detalhe: 'as reservas não se leram agora' };
|
|
1094
|
+
let acao = 'revalidada';
|
|
1095
|
+
if (!antes.minha) {
|
|
1096
|
+
if (!antes.livre)
|
|
1097
|
+
return { ok: false, motivo: 'noctum.reserva.de-outra-maquina', detalhe: `o item ${o.item} está com outra máquina ou pessoa` };
|
|
1098
|
+
try {
|
|
1099
|
+
// `threadEsperada: null`: quem tomou o item com thread entre a leitura e a gravação faz o pegar voltar sem gravar.
|
|
1100
|
+
acao = (0, roadmap_reservas_1.pegarItem)(carregado.raiz, o.item, { remoto: opcoes.remoto ?? carregado.manifesto.fabrica.remoto, maquina: eu, thread,
|
|
1101
|
+
nota: (0, exports.notaDaReserva)(arco), rede: opcoes.rede, threadEsperada: null }).acao;
|
|
1102
|
+
}
|
|
1103
|
+
catch (e) {
|
|
1104
|
+
return { ok: false, ...motivoDoPegar(e) };
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
const agora = reservaDoItem(carregado, arco, o.item, opcoes);
|
|
1108
|
+
if (agora === null)
|
|
1109
|
+
return { ok: false, motivo: 'noctum.coordenacao.sem-remoto', detalhe: 'a revalidação da reserva não leu o remoto' };
|
|
1110
|
+
if (!agora.minha)
|
|
1111
|
+
return { ok: false, motivo: 'noctum.reserva.de-outra-maquina', detalhe: `o item ${o.item} está com outra máquina ou pessoa depois de tomar` };
|
|
1112
|
+
return { ok: true, acao };
|
|
1113
|
+
}
|
|
1114
|
+
/** Solta a reserva do item quando ela é desta máquina e do arco (pela nota); a de outra máquina ou do dono fica. */
|
|
1115
|
+
function soltarReservaDoArco(carregado, arco, item, opcoes = {}) {
|
|
1116
|
+
const agora = reservaDoItem(carregado, arco, item, opcoes);
|
|
1117
|
+
if (!agora?.doArco)
|
|
1118
|
+
return false;
|
|
1119
|
+
const eu = nomeDe(opcoes);
|
|
1120
|
+
// A sonda D do CHECK de 15/10: o item que ganhou ordem viva desta máquina depois do plano da reconciliação fica com a
|
|
1121
|
+
// reserva, mesmo para quem compõe a reconciliação fora da trava da máquina; a leitura é nova.
|
|
1122
|
+
if ([...lerCoordenacao(carregado, arco, opcoes).ordens.values()].some(o => o.maquina === eu && o.item === item && (0, exports.ordemViva)(o)))
|
|
1123
|
+
return false;
|
|
1124
|
+
return (0, roadmap_reservas_1.soltarItem)(carregado.raiz, item, { remoto: opcoes.remoto ?? carregado.manifesto.fabrica.remoto, maquina: eu, rede: opcoes.rede }).acao === 'soltou';
|
|
1125
|
+
}
|
|
1126
|
+
/** A vaga ainda tem sessão viva (ou incerta) nesta máquina? */
|
|
1127
|
+
function vagaViva(v, fatos) {
|
|
1128
|
+
const d = fatos.despachos.get(v.reserva);
|
|
1129
|
+
if (!d || d.desfecho === 'pendente')
|
|
1130
|
+
return true;
|
|
1131
|
+
if (d.desfecho === 'recusado')
|
|
1132
|
+
return false;
|
|
1133
|
+
// A2 da rodada 2: o despacho idempotente não abriu sessão (a fase já corria); a vaga dele não espera sessão nenhuma e é
|
|
1134
|
+
// solta com consumo, como a N2 conta a reserva concluída.
|
|
1135
|
+
if (d.idempotente)
|
|
1136
|
+
return false;
|
|
1137
|
+
return fatos.sessoesVivas === null || d.sessionId === null || fatos.sessoesVivas.has(d.sessionId);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* O plano puro da reconciliação desta máquina: conclui as ordens cuja thread local terminou, encerra as vencidas sem sessão
|
|
1141
|
+
* viva (soltando as vagas delas) e solta as vagas cujo despacho local foi recusado ou cuja sessão acabou. A pendente fica,
|
|
1142
|
+
* contada, até a N4, e a vaga que outra chave da máquina tomou conta como viva (G8-6), também na conclusão. Os itens para
|
|
1143
|
+
* soltar a reserva são os das ordens desta máquina, de qualquer versão do envelope.
|
|
1144
|
+
*/
|
|
1145
|
+
function planejarReconciliacao(estado, eu, fatos) {
|
|
1146
|
+
const plano = { novos: [], encerradas: [], concluidas: [], soltas: [], aguardando: [], itensSemOrdemViva: [] };
|
|
1147
|
+
const daRaiz = (o) => o.maquina === eu && (fatos.chave === undefined || ordemDaRaiz(estado, o, { nome: eu, chave: fatos.chave }));
|
|
1148
|
+
// G8-6: a vaga que outra chave da máquina tomou (a da raiz antiga, depois de uma troca de chave) tem o despacho no log de
|
|
1149
|
+
// outra raiz: aqui ela conta como viva, e só a raiz dela a solta.
|
|
1150
|
+
const daChave = (v) => v.maquina === eu && (fatos.chave === undefined || v.chave === fatos.chave);
|
|
1151
|
+
const minhas = estado.vagas.filter(v => daChave(v) && !v.solta);
|
|
1152
|
+
const soltar = (v, motivo) => {
|
|
1153
|
+
if (plano.soltas.includes(v.vaga))
|
|
1154
|
+
return;
|
|
1155
|
+
const d = fatos.despachos.get(v.reserva);
|
|
1156
|
+
plano.novos.push({ tipo: 'vaga-solta', vaga: v.vaga, consumiu: d?.desfecho !== 'recusado', motivo });
|
|
1157
|
+
plano.soltas.push(v.vaga);
|
|
1158
|
+
};
|
|
1159
|
+
for (const o of estado.ordens.values()) {
|
|
1160
|
+
if (!daRaiz(o) || (o.estado !== 'aceita' && o.estado !== 'isolada'))
|
|
1161
|
+
continue;
|
|
1162
|
+
const th = fatos.threads.get(o.trabalho);
|
|
1163
|
+
if (th && (th.entregue || th.fechada)) {
|
|
1164
|
+
// N1 da terceira passada do CHECK-REVERIFY: a conclusão também libera o item, e a vaga viva de outra chave da máquina
|
|
1165
|
+
// espera a raiz dela, como no fim.
|
|
1166
|
+
const deOutraChave = estado.vagas.filter(v => v.ordem === o.ordem && !v.solta && !daChave(v));
|
|
1167
|
+
if (deOutraChave.length) {
|
|
1168
|
+
plano.aguardando.push({ ordem: o.ordem, porque: `a thread local terminou, e a ordem tem ${deOutraChave.length} vaga(s) viva(s) de outra raiz desta máquina` });
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
plano.novos.push({ tipo: 'recibo', ordem: o.ordem, recibo: 'concluida', motivo: `thread local ${th.entregue ? 'entregue' : 'fechada sem entrega'}`,
|
|
1172
|
+
resultado: th.entregue ? 'entregue' : 'encerrado-sem-entrega', thread: th.id });
|
|
1173
|
+
plano.concluidas.push(o.ordem);
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
const porque = o.cancelada ? `cancelada pela autoridade: ${o.cancelada.motivo}`
|
|
1177
|
+
: o.envelopeSha256 !== estado.vigente.envelopeSha256 ? 'a ordem é de uma versão anterior do envelope'
|
|
1178
|
+
: estado.revogada ? `o arco foi revogado na máquina ${estado.revogada.por}`
|
|
1179
|
+
: !fatos.arcoVigente ? 'o arco não está vigente nesta máquina' : null;
|
|
1180
|
+
if (!porque)
|
|
1181
|
+
continue;
|
|
1182
|
+
// Uma vez de integrar ativa é um merge que pode ter acontecido: a reconciliação da integração decide antes (D13). B-R2A-1
|
|
1183
|
+
// da rodada 2: a fonte que chegou à base sem prova sobre a base provada não é refeita em outra máquina, e o trabalho espera
|
|
1184
|
+
// a inspeção do dono. A vez abandonada espera a base lida sem a fonte (A7 da revisão).
|
|
1185
|
+
const espera = esperaDaVez(estado, o, fatos.vezesConferidas, fatos.vezesSemProva);
|
|
1186
|
+
if (espera) {
|
|
1187
|
+
const daVez = { ativa: 'a vez de integrar dela está ativa, e o merge pode ter chegado à base',
|
|
1188
|
+
'sem-prova': 'a fonte dela chegou à base por um merge fora da base provada, e o trabalho espera a inspeção',
|
|
1189
|
+
abandonada: 'a vez de integrar dela foi abandonada pela autoridade, e o merge pode ter chegado à base' }[espera];
|
|
1190
|
+
plano.aguardando.push({ ordem: o.ordem, porque: `${porque}; ${daVez}` });
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1193
|
+
const dela = estado.vagas.filter(v => v.ordem === o.ordem && !v.solta);
|
|
1194
|
+
const vivas = dela.filter(v => !daChave(v) || vagaViva(v, fatos));
|
|
1195
|
+
if (vivas.length) {
|
|
1196
|
+
plano.aguardando.push({ ordem: o.ordem, porque: `${porque}; ${vivas.length} sessão(ões) dela ainda viva(s), incerta(s) ou de outra raiz desta máquina` });
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
for (const v of dela)
|
|
1200
|
+
soltar(v, 'fim da ordem');
|
|
1201
|
+
plano.novos.push({ tipo: 'recibo', ordem: o.ordem, recibo: 'encerrada', motivo: porque.slice(0, 300),
|
|
1202
|
+
checkpoint: { thread: th?.id ?? null, vagasSoltas: dela.map(v => v.vaga).slice(0, exports.TETO_DE_VAGAS_NO_CHECKPOINT) } });
|
|
1203
|
+
plano.encerradas.push(o.ordem);
|
|
1204
|
+
}
|
|
1205
|
+
for (const v of minhas) {
|
|
1206
|
+
const d = fatos.despachos.get(v.reserva);
|
|
1207
|
+
if (d && !vagaViva(v, fatos))
|
|
1208
|
+
soltar(v, d.desfecho === 'recusado' ? 'o despacho local foi recusado antes do lançamento' : 'a sessão do despacho acabou');
|
|
1209
|
+
}
|
|
1210
|
+
const fim = new Set([...plano.concluidas, ...plano.encerradas]);
|
|
1211
|
+
// A5 da revisão: os itens de todas as ordens desta máquina, de qualquer versão do envelope (a reserva do arco só nasce de
|
|
1212
|
+
// uma ordem aceita dela); o item que uma emenda tirou do envelope também é solto.
|
|
1213
|
+
const minhasOrdens = [...estado.ordens.values()].filter(o => o.maquina === eu);
|
|
1214
|
+
const itensVivos = new Set(minhasOrdens.filter(o => (0, exports.ordemViva)(o) && !fim.has(o.ordem)).map(o => o.item));
|
|
1215
|
+
plano.itensSemOrdemViva = [...new Set(minhasOrdens.map(o => o.item))].filter(i => !itensVivos.has(i)).sort();
|
|
1216
|
+
return plano;
|
|
1217
|
+
}
|
|
1218
|
+
/** Grava o plano da reconciliação, refeito sobre a leitura nova a cada tentativa. */
|
|
1219
|
+
function reconciliarCoordenacao(carregado, arco, fatos, opcoes = {}) {
|
|
1220
|
+
const chave = fatos.chave ?? (0, noctum_transporte_1.identidadeDaMaquina)(carregado.raiz, opcoes.maquina).chave;
|
|
1221
|
+
return gravarNaCoordenacao(carregado, arco, opcoes, (leitura) => {
|
|
1222
|
+
const plano = planejarReconciliacao(derivarCoordenacao(leitura), nomeDe(opcoes), { ...fatos, chave });
|
|
1223
|
+
return { novos: plano.novos, resultado: plano };
|
|
1224
|
+
}).resultado;
|
|
1225
|
+
}
|