@orkastery/cli 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/README.md +18 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +1 -0
- package/adapters/claude-code/README.md +195 -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/autoridade.json +56 -0
- package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
- package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
- package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
- package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
- package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
- package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
- package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
- package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
- package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
- package/adapters/claude-code/mods/ork-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-mesa/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
- package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
- package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
- package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +121 -0
- package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
- package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
- package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
- package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
- package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
- package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
- package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
- package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
- package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
- package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
- package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
- package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
- package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
- package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
- package/adapters/claude-code/mods/ork-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/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
- package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
- package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
- package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
- package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
- package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
- package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
- package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
- package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
- package/adapters/codex/skills/ork/SKILL.md +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 +3 -3
- package/adapters/openclaw/README.md +83 -10
- 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/docs-en/markdownlint-cli2.jsonc +22 -0
- package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
- package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
- package/assets/docs-en/produto/README.md +14 -0
- package/assets/docs-en/produto/_modelo-feature.md +62 -0
- package/assets/docs-en/roadmap/README.md +14 -0
- package/assets/docs-en/roadmap/_modelo-item.md +75 -0
- package/assets/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 +193 -15
- package/dist/adapters/codex-controller-worker.js +12 -5
- package/dist/adapters/codex-events.js +16 -4
- package/dist/adapters/codex.js +13 -2
- package/dist/agents-md.js +24 -15
- package/dist/atualizar-branch.js +245 -0
- package/dist/auditoria.js +13 -11
- package/dist/auditrun.js +68 -48
- package/dist/autorizacao-duravel.js +327 -0
- package/dist/board.js +98 -43
- package/dist/branch-de-estado.js +39 -9
- package/dist/busca-semantica.js +7 -5
- package/dist/catalogo.js +35 -1
- package/dist/changelog-reconciliacao.js +290 -0
- package/dist/check-agil.js +500 -0
- package/dist/check-modo.js +41 -0
- package/dist/check-paralelo.js +183 -0
- package/dist/ci.js +255 -46
- package/dist/ciclos.js +8 -6
- package/dist/claim-lint.js +12 -8
- package/dist/claims-conferir.js +350 -0
- package/dist/claims.js +13 -10
- package/dist/company-brain-client.js +45 -3
- package/dist/company-brain-journal.js +2 -1
- package/dist/company-brain-migration.js +47 -6
- package/dist/conducao-batida.js +759 -0
- package/dist/conducao-sentinela.js +405 -0
- package/dist/conducao-texto.js +12 -18
- package/dist/conducao.js +206 -59
- package/dist/conhecimento-consulta.js +270 -0
- package/dist/conhecimento.js +514 -0
- 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 +103 -82
- package/dist/doctor-forja.js +345 -0
- package/dist/doctor-pulse-cron.js +294 -27
- package/dist/doctor-rede.js +18 -17
- package/dist/doctor.js +278 -116
- package/dist/dsn-da-memoria.js +417 -0
- package/dist/entrega-por-mod.js +503 -0
- package/dist/entrega-pr.js +158 -57
- package/dist/escopo-escrita.js +10 -1
- package/dist/esgotamentos.js +13 -11
- package/dist/estado-thread.js +42 -17
- package/dist/evalrunner.js +35 -32
- package/dist/experiencia-instalacao.js +45 -25
- package/dist/fabrica-batida.js +323 -0
- package/dist/fabrica-estado.js +49 -16
- package/dist/fabrica-publicar.js +3 -1
- package/dist/fila-pesada.js +444 -0
- package/dist/fix.js +24 -21
- 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 +124 -81
- package/dist/gates.js +3 -28
- package/dist/handoff.js +33 -19
- 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 +53 -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 +72 -3
- package/dist/hitl-sessions.js +62 -9
- package/dist/hitl-tempo-parado.js +4 -4
- package/dist/hitl-texto-livre.js +7 -4
- package/dist/hitl.js +63 -64
- package/dist/horario.js +30 -7
- package/dist/hosts.js +155 -134
- package/dist/impedimento.js +108 -14
- package/dist/index.js +1421 -1019
- package/dist/indice-vetorial.js +14 -12
- package/dist/indice.js +7 -1
- package/dist/init.js +54 -49
- package/dist/instabilidade.js +144 -0
- package/dist/integracoes-locais.js +4 -0
- package/dist/intelligence-graph-cli.js +213 -88
- package/dist/intelligence-graph-contexto.js +56 -17
- package/dist/intelligence-graph-contract.js +2 -1
- package/dist/intelligence-graph-extract-md.js +56 -4
- package/dist/intelligence-graph-extract-ts.js +71 -3
- package/dist/intelligence-graph-query.js +96 -34
- package/dist/leases.js +19 -18
- package/dist/ledger-stats.js +17 -16
- package/dist/ledger.js +46 -2
- package/dist/licoes.js +12 -11
- package/dist/liveness.js +6 -3
- package/dist/locale-accounts.js +220 -0
- package/dist/locale-adapter.js +291 -0
- package/dist/locale-ajuda.js +833 -0
- package/dist/locale-audit.js +199 -0
- package/dist/locale-claims.js +213 -0
- package/dist/locale-conducao.js +138 -0
- package/dist/locale-conhecimento.js +181 -0
- package/dist/locale-decisao.js +205 -0
- package/dist/locale-docs.js +230 -0
- package/dist/locale-estado.js +101 -0
- package/dist/locale-forja.js +290 -0
- package/dist/locale-gerados.js +165 -0
- package/dist/locale-grafo.js +129 -0
- package/dist/locale-master.js +182 -0
- package/dist/locale-network.js +524 -0
- package/dist/locale-onboarding.js +92 -0
- package/dist/locale-operacao-restante.js +931 -0
- package/dist/locale-phase.js +151 -0
- package/dist/locale-projetos.js +100 -0
- package/dist/locale-prompts.js +194 -0
- package/dist/locale-pulse.js +349 -0
- package/dist/locale-reservas.js +87 -0
- package/dist/locale-retry.js +524 -0
- package/dist/locale-roadmap.js +102 -0
- package/dist/locale-security-scan.js +69 -0
- package/dist/locale-setup.js +192 -0
- package/dist/locale-ship.js +597 -0
- package/dist/locale-thread.js +421 -0
- package/dist/locale-verify.js +201 -0
- package/dist/locale-worktree.js +246 -0
- package/dist/locale.js +1258 -0
- package/dist/maestro-runtime.js +22 -3
- package/dist/manifest.js +415 -13
- package/dist/maquina-ociosa.js +211 -0
- package/dist/maquina.js +31 -7
- package/dist/master-digest.js +6 -1
- package/dist/master-nota-local.js +200 -0
- package/dist/master-nota.js +14 -8
- package/dist/master.js +139 -65
- package/dist/mcp-artifacts.js +11 -2
- package/dist/mcp-conhecimento.js +52 -0
- package/dist/mcp-experiencia.js +26 -5
- package/dist/mcp-git.js +23 -6
- package/dist/mcp-grafo.js +7 -4
- package/dist/mcp-install.js +73 -8
- package/dist/mcp-noctum.js +101 -0
- package/dist/mcp-server.js +90 -16
- package/dist/mcp-ship-transporte.js +977 -0
- package/dist/mcp-ship.js +71 -25
- package/dist/mcp-transportes.js +17 -0
- package/dist/mcp-verify.js +6 -1
- package/dist/memoria.js +63 -58
- package/dist/modos-migracao.js +8 -6
- package/dist/modos.js +20 -24
- package/dist/network-roadmap.js +90 -51
- package/dist/noctum-aceite.js +789 -0
- package/dist/noctum-arco.js +1418 -0
- package/dist/noctum-capacidade.js +141 -0
- package/dist/noctum-changelog.js +399 -0
- package/dist/noctum-cli.js +482 -0
- package/dist/noctum-coordenacao.js +1225 -0
- package/dist/noctum-entrega.js +1169 -0
- package/dist/noctum-envelope.js +1046 -0
- package/dist/noctum-execucao.js +376 -0
- package/dist/noctum-fechamento.js +405 -0
- package/dist/noctum-fila.js +314 -0
- package/dist/noctum-guarda.js +256 -0
- package/dist/noctum-integracao.js +507 -0
- package/dist/noctum-maquinas.js +383 -0
- package/dist/noctum-medicao.js +450 -0
- package/dist/noctum-modo.js +362 -0
- package/dist/noctum-pergunta-canal.js +96 -0
- package/dist/noctum-pergunta.js +115 -0
- package/dist/noctum-provas.js +838 -0
- package/dist/noctum-reconciliacao.js +591 -0
- package/dist/noctum-registro.js +215 -0
- package/dist/noctum-relatorio.js +827 -0
- package/dist/noctum-resposta.js +207 -0
- package/dist/noctum-sessoes-em-voo.js +93 -0
- package/dist/noctum-supervisao.js +649 -0
- package/dist/noctum-terminal.js +459 -0
- package/dist/noctum-transporte.js +655 -0
- package/dist/noctum-vigia.js +639 -0
- package/dist/ocupacao.js +32 -3
- package/dist/onboarding.js +56 -26
- package/dist/orkmind.js +48 -25
- package/dist/orquestracao.js +44 -47
- package/dist/parado-no-condutor.js +174 -127
- package/dist/phase.js +144 -63
- package/dist/policies.js +37 -4
- package/dist/portfolio.js +4 -2
- package/dist/postura-local.js +235 -0
- package/dist/preflight.js +11 -10
- package/dist/procedencia.js +220 -0
- package/dist/projeto-alvo.js +186 -24
- package/dist/prompts.js +140 -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 +151 -24
- package/dist/pulse-resposta.js +368 -4
- package/dist/pulse.js +188 -22
- package/dist/ratelimit.js +16 -6
- package/dist/recall.js +14 -13
- package/dist/redacao-saida.js +63 -0
- package/dist/rede-adesao.js +13 -8
- package/dist/rede-forja.js +108 -24
- package/dist/rede-status.js +66 -47
- package/dist/rede.js +86 -46
- package/dist/retomada-checkpoint.js +101 -0
- package/dist/retry.js +451 -212
- package/dist/roadmap-reservas.js +45 -28
- package/dist/roadmap-status.js +129 -33
- package/dist/runtime-ambiente.js +5 -0
- package/dist/runtime-context.js +284 -32
- package/dist/runtime-escada.js +423 -0
- package/dist/runtime-profiles.js +103 -46
- package/dist/runtimes.js +2 -1
- package/dist/saida-segura.js +3 -2
- package/dist/security-scan/agil.js +355 -0
- package/dist/security-scan/cli.js +76 -0
- package/dist/security-scan/compare.js +149 -0
- package/dist/security-scan/config.js +93 -0
- package/dist/security-scan/detect.js +97 -0
- package/dist/security-scan/executor.js +188 -0
- package/dist/security-scan/guard.js +180 -0
- package/dist/security-scan/imagem-de-lint.js +96 -0
- package/dist/security-scan/normalize/actionlint.js +12 -0
- package/dist/security-scan/normalize/eslint.js +12 -0
- package/dist/security-scan/normalize/gitleaks.js +13 -0
- package/dist/security-scan/normalize/golangci-lint.js +13 -0
- package/dist/security-scan/normalize/govulncheck.js +36 -0
- package/dist/security-scan/normalize/index.js +25 -0
- package/dist/security-scan/normalize/npm-audit.js +30 -0
- package/dist/security-scan/normalize/osv-scanner.js +32 -0
- package/dist/security-scan/normalize/oxlint.js +51 -0
- package/dist/security-scan/normalize/pip-audit.js +16 -0
- package/dist/security-scan/normalize/ruff.js +12 -0
- package/dist/security-scan/normalize/semgrep.js +20 -0
- package/dist/security-scan/normalize/trivy.js +33 -0
- package/dist/security-scan/normalize/util.js +204 -0
- package/dist/security-scan/normalize/zizmor.js +19 -0
- package/dist/security-scan/report.js +156 -0
- package/dist/security-scan/run.js +448 -0
- package/dist/security-scan/tools.js +62 -0
- package/dist/security-scan/trabalhador.js +74 -0
- package/dist/security-scan/types.js +15 -0
- package/dist/session-events.js +26 -5
- package/dist/session-watcher-claude.js +81 -20
- package/dist/session-watcher.js +340 -42
- package/dist/sessoes-contas.js +10 -1
- package/dist/sessoes-inventario.js +1 -1
- package/dist/sessoes.js +25 -23
- package/dist/setup.js +59 -58
- package/dist/ship-publicar.js +971 -0
- package/dist/ship.js +243 -97
- package/dist/steal.js +77 -0
- package/dist/superficie.js +14 -11
- package/dist/thread-close.js +12 -11
- package/dist/thread.js +274 -100
- 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 +13 -3
- package/dist/varredura-acoes.js +507 -0
- package/dist/verify.js +608 -76
- package/dist/vida-da-thread.js +254 -0
- package/dist/worktree-local.js +167 -0
- package/dist/worktree.js +99 -83
- package/dist/yaml.js +27 -8
- package/eval/casos/check-quality.json +77 -0
- package/eval/casos/security-auditor.json +148 -0
- package/eval/casos/security-remediation.json +161 -0
- package/eval/fixtures/security-scan/README.md +8 -0
- package/eval/fixtures/security-scan/go/.github/workflows/ci.yml +12 -0
- package/eval/fixtures/security-scan/go/Dockerfile +4 -0
- package/eval/fixtures/security-scan/go/README.md +4 -0
- package/eval/fixtures/security-scan/go/config.go +4 -0
- package/eval/fixtures/security-scan/go/go.mod +5 -0
- package/eval/fixtures/security-scan/go/go.sum +2 -0
- package/eval/fixtures/security-scan/go/main.go +31 -0
- package/eval/fixtures/security-scan/js/.github/workflows/ci.yml +12 -0
- package/eval/fixtures/security-scan/js/Dockerfile +3 -0
- package/eval/fixtures/security-scan/js/README.md +4 -0
- package/eval/fixtures/security-scan/js/package-lock.json +844 -0
- package/eval/fixtures/security-scan/js/package.json +10 -0
- package/eval/fixtures/security-scan/js/src/config.js +4 -0
- package/eval/fixtures/security-scan/js/src/server.js +17 -0
- package/eval/fixtures/security-scan/js/src/util.ts +4 -0
- package/eval/fixtures/security-scan/js/tsconfig.json +4 -0
- package/eval/fixtures/security-scan/python/.github/workflows/ci.yml +12 -0
- package/eval/fixtures/security-scan/python/Dockerfile +3 -0
- package/eval/fixtures/security-scan/python/README.md +4 -0
- package/eval/fixtures/security-scan/python/app/__init__.py +0 -0
- package/eval/fixtures/security-scan/python/app/config.py +2 -0
- package/eval/fixtures/security-scan/python/app/main.py +19 -0
- package/eval/fixtures/security-scan/python/requirements.txt +1 -0
- package/package.json +9 -4
- package/references/security-checklist.md +3 -3
- package/schemas/code-artifact-graph.v1.schema.json +2 -1
- 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,1046 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ID_DO_TRABALHO = exports.ID_DO_ARCO = exports.RecusaDoArco = exports.MOTIVOS_DE_RECUSA = exports.CANAIS_DO_RELATORIO = exports.UNIDADES = exports.EXCLUSOES_FIXAS = exports.PERMISSOES = exports.JANELA_MAXIMA_H = exports.JANELA_MINIMA_H = exports.MAXIMO_DE_SESSOES_DA_MAQUINA = exports.TETO_DOS_PEDIDOS_DO_ARCO = exports.TETO_DO_PEDIDO_DO_TRABALHO = exports.MAXIMO_DE_TRABALHOS = exports.MAXIMO_DE_MAQUINAS = exports.CONTRATO_ENVELOPE_V3 = exports.CONTRATO_ENVELOPE_V2 = exports.CONTRATO_ENVELOPE = void 0;
|
|
4
|
+
exports.nomeDaBranch = nomeDaBranch;
|
|
5
|
+
exports.exclusaoDaAcao = exclusaoDaAcao;
|
|
6
|
+
exports.shaDoPedido = shaDoPedido;
|
|
7
|
+
exports.chavePublicaValida = chavePublicaValida;
|
|
8
|
+
exports.impressaoDaChave = impressaoDaChave;
|
|
9
|
+
exports.envelopeComTrabalhos = envelopeComTrabalhos;
|
|
10
|
+
exports.envelopeMultimaquina = envelopeMultimaquina;
|
|
11
|
+
exports.validarPedidoDeAbertura = validarPedidoDeAbertura;
|
|
12
|
+
exports.canonico = canonico;
|
|
13
|
+
exports.hashDoEnvelope = hashDoEnvelope;
|
|
14
|
+
exports.recusaDeExclusao = recusaDeExclusao;
|
|
15
|
+
exports.avaliarEfeito = avaliarEfeito;
|
|
16
|
+
exports.avaliarJanela = avaliarJanela;
|
|
17
|
+
exports.resumoDoEnvelope = resumoDoEnvelope;
|
|
18
|
+
/**
|
|
19
|
+
* RM-058, fatia N1: o contrato do envelope de autorização de um arco #Noctum.
|
|
20
|
+
*
|
|
21
|
+
* O envelope é o que o dono autoriza ANTES do arco: a janela, os destinos e o que pode em cada
|
|
22
|
+
* um, o orçamento com unidade, o horário e o canal do relatório da manhã. As exclusões não são
|
|
23
|
+
* dele: são fixas, ficam guardadas no envelope para o registro e nenhum pedido as amplia.
|
|
24
|
+
*
|
|
25
|
+
* Nada aqui inventa valor do dono. Campo ausente vira recusa que lista tudo o que falta, de uma
|
|
26
|
+
* vez, e teto ilimitado é recusado sempre (D3 da ork-rm058n1). O módulo é puro: não lê disco nem
|
|
27
|
+
* relógio. Quem persiste, autentica a origem e confere antes do efeito é `noctum-arco.ts`.
|
|
28
|
+
*
|
|
29
|
+
* Nada aqui liga o modo: `#Noctum` continua fora de `MODOS_LEGADOS`.
|
|
30
|
+
*
|
|
31
|
+
* RM-058, fatia N2 (D1 da ork-rm058n2): a `v2` acrescenta o que a `v1` deixou para uma versão nova, a lista
|
|
32
|
+
* fechada de trabalhos do arco e os limites de concorrência. A `v1` segue valendo como estava, byte a byte, e
|
|
33
|
+
* um arco autorizado nela não despacha nada.
|
|
34
|
+
*
|
|
35
|
+
* RM-058, fatia N3 (D2 da ork-rm058n3): a `v3` acrescenta as máquinas do arco, cada uma com a chave pública que assina
|
|
36
|
+
* no ramo de coordenação, o limite de sessões por conta somando as máquinas e a máquina presa de um trabalho. É o que
|
|
37
|
+
* o dono autoriza que faz uma máquina confiar na outra: o retrato da rede é descoberta, nunca autorização.
|
|
38
|
+
*/
|
|
39
|
+
const node_crypto_1 = require("node:crypto");
|
|
40
|
+
const horario_1 = require("./horario");
|
|
41
|
+
const runtime_profiles_1 = require("./runtime-profiles");
|
|
42
|
+
exports.CONTRATO_ENVELOPE = 'ork.noctum-envelope/v1';
|
|
43
|
+
exports.CONTRATO_ENVELOPE_V2 = 'ork.noctum-envelope/v2';
|
|
44
|
+
exports.CONTRATO_ENVELOPE_V3 = 'ork.noctum-envelope/v3';
|
|
45
|
+
/** D2 da ork-rm058n3: as máquinas de um arco são poucas e todas do dono. */
|
|
46
|
+
exports.MAXIMO_DE_MAQUINAS = 8;
|
|
47
|
+
/**
|
|
48
|
+
* D1 da ork-rm058n2: a lista do arco é fechada e curta. O dono lê cada pedido inteiro no resumo que autoriza (A7 do
|
|
49
|
+
* CHECK), então o pedido é texto curto: cada um e a soma deles cabem numa leitura.
|
|
50
|
+
*/
|
|
51
|
+
exports.MAXIMO_DE_TRABALHOS = 30;
|
|
52
|
+
exports.TETO_DO_PEDIDO_DO_TRABALHO = 4 * 1024;
|
|
53
|
+
exports.TETO_DOS_PEDIDOS_DO_ARCO = 16 * 1024;
|
|
54
|
+
/** Mais sessões que isto numa máquina não limita nada: é a ausência do limite escrita como número. */
|
|
55
|
+
exports.MAXIMO_DE_SESSOES_DA_MAQUINA = 64;
|
|
56
|
+
/** D3 do dono (12/10/2026): a janela do arco fica entre 6 e 12 horas. */
|
|
57
|
+
exports.JANELA_MINIMA_H = 6;
|
|
58
|
+
exports.JANELA_MAXIMA_H = 12;
|
|
59
|
+
/** D1 do dono: push, PR e merge, este só com todas as provas no SHA exato (N5). */
|
|
60
|
+
exports.PERMISSOES = ['push', 'pr', 'merge'];
|
|
61
|
+
/** Nunca entram em envelope nenhum, e por isso não são configuráveis (D6 da ork-rm058n1). */
|
|
62
|
+
exports.EXCLUSOES_FIXAS = ['tag-e-publicacao', 'producao', 'segredos', 'acao-destrutiva', 'repositorio-publico-novo'];
|
|
63
|
+
/**
|
|
64
|
+
* Os nomes com que uma ação excluída pode chegar, num pedido de abertura ou antes de um efeito.
|
|
65
|
+
* Ação fora de `PERMISSOES` que não está aqui também é recusada; este mapa só dá o motivo certo.
|
|
66
|
+
*/
|
|
67
|
+
const EXCLUSAO_POR_NOME = Object.freeze({
|
|
68
|
+
'tag-e-publicacao': 'tag-e-publicacao', tag: 'tag-e-publicacao', publicacao: 'tag-e-publicacao',
|
|
69
|
+
publicar: 'tag-e-publicacao', release: 'tag-e-publicacao', 'publicar-pacote': 'tag-e-publicacao',
|
|
70
|
+
'publicar-site': 'tag-e-publicacao',
|
|
71
|
+
producao: 'producao', deploy: 'producao', 'deploy-producao': 'producao', 'atualizar-producao': 'producao',
|
|
72
|
+
segredos: 'segredos', segredo: 'segredos', 'ler-segredo': 'segredos', credencial: 'segredos',
|
|
73
|
+
'acao-destrutiva': 'acao-destrutiva', destrutiva: 'acao-destrutiva', 'force-push': 'acao-destrutiva',
|
|
74
|
+
apagar: 'acao-destrutiva', 'apagar-branch': 'acao-destrutiva', 'reescrever-historico': 'acao-destrutiva',
|
|
75
|
+
'repositorio-publico-novo': 'repositorio-publico-novo', 'criar-repositorio': 'repositorio-publico-novo',
|
|
76
|
+
'criar-repositorio-publico': 'repositorio-publico-novo',
|
|
77
|
+
});
|
|
78
|
+
/** Por que cada exclusão fica fora, na frase que a recusa mostra. */
|
|
79
|
+
const RAZAO_DA_EXCLUSAO = Object.freeze({
|
|
80
|
+
'tag-e-publicacao': 'tag e publicação de versão, pacote ou site nunca entram em envelope',
|
|
81
|
+
producao: 'produção exige autorização explícita própria e fluxo próprio, que não são o envelope',
|
|
82
|
+
segredos: 'acesso ou circulação de segredos nunca entram em envelope',
|
|
83
|
+
'acao-destrutiva': 'ação destrutiva nunca entra em envelope',
|
|
84
|
+
'repositorio-publico-novo': 'criar repositório público nunca entra em envelope',
|
|
85
|
+
});
|
|
86
|
+
/** D3 da ork-rm058n1: lista fechada. Assinatura não é uso ilimitado, e unidade sem nome não mede nada. */
|
|
87
|
+
exports.UNIDADES = ['sessoes', 'minutos-de-sessao', 'tokens', 'percentual-da-cota'];
|
|
88
|
+
/** Contadas em inteiros. Acima do inteiro seguro do JavaScript, o teto deixa de limitar e vira ilimitado na prática. */
|
|
89
|
+
const UNIDADES_INTEIRAS = ['sessoes', 'minutos-de-sessao', 'tokens'];
|
|
90
|
+
/** Os destinos do relatório da manhã que a página da RM-058 nomeia. */
|
|
91
|
+
exports.CANAIS_DO_RELATORIO = ['terminal', 'telegram', 'openclaw'];
|
|
92
|
+
/** D2 da ork-rm058n1: tipo fechado próprio, fora de `MotivoGate`, porque a N1 não abre gate. */
|
|
93
|
+
exports.MOTIVOS_DE_RECUSA = [
|
|
94
|
+
'noctum.envelope.valores-ausentes', 'noctum.envelope.teto-ilimitado', 'noctum.envelope.acao-excluida',
|
|
95
|
+
'noctum.envelope.invalido', 'noctum.origem.sem-prova', 'noctum.efeito.acao-excluida',
|
|
96
|
+
'noctum.efeito.sem-autorizacao', 'noctum.efeito.hash-antigo', 'noctum.efeito.revogado',
|
|
97
|
+
'noctum.efeito.expirado', 'noctum.efeito.fora-da-janela', 'noctum.efeito.fora-do-destino',
|
|
98
|
+
'noctum.estado.adulterado',
|
|
99
|
+
// RM-058 N2 (D11 e D12 da ork-rm058n2): o despacho fora da lista, conta ou runtime do envelope, e o orçamento.
|
|
100
|
+
'noctum.despacho.fora-do-envelope', 'noctum.orcamento.esgotado', 'noctum.orcamento.sem-medicao',
|
|
101
|
+
// RM-058 N3 (ork-rm058n3): a coordenação entre máquinas, a reserva do item e a vez de integrar.
|
|
102
|
+
'noctum.coordenacao.sem-remoto', 'noctum.coordenacao.adulterado', 'noctum.coordenacao.fora-do-envelope',
|
|
103
|
+
'noctum.coordenacao.autoridade-ativa', 'noctum.coordenacao.geracao-vencida', 'noctum.coordenacao.ordem-viva',
|
|
104
|
+
'noctum.coordenacao.ordem-vencida', 'noctum.coordenacao.maquina-sem-batida', 'noctum.coordenacao.vaga-global',
|
|
105
|
+
'noctum.coordenacao.concorrencia', 'noctum.coordenacao.exige-passo-da-maquina', 'noctum.reserva.de-outra-maquina',
|
|
106
|
+
'noctum.integracao.ocupada', 'noctum.integracao.base-mudou', 'noctum.integracao.fonte-mudou',
|
|
107
|
+
'noctum.integracao.ja-entregue', 'noctum.integracao.nao-provada',
|
|
108
|
+
// GO-FIX da revisão da ork-rm058n3: a emenda ainda não autorizada aqui, a máquina revogada, a dependência e a recusa
|
|
109
|
+
// da ordem com motivo próprio, e o item que o roadmap não reconhece.
|
|
110
|
+
'noctum.coordenacao.versao-nao-autorizada', 'noctum.coordenacao.revogada', 'noctum.coordenacao.dependencia-pendente',
|
|
111
|
+
'noctum.coordenacao.maquina-recusou', 'noctum.reserva.item-invalido',
|
|
112
|
+
// Rodada 2 da revisão da ork-rm058n3: a fonte que chegou à base por um merge fora da base provada.
|
|
113
|
+
'noctum.integracao.base-nao-provada',
|
|
114
|
+
// RM-058 N6 (D1 e D2 da ork-rm058n6): o arco encerrado e o #Noctum pedido sem envelope vigente ou como modo de thread.
|
|
115
|
+
'noctum.efeito.encerrado', 'noctum.modo.sem-envelope', 'noctum.modo.lista-fechada',
|
|
116
|
+
// RM-057 (resposta curta, ork-rm057respost): a resposta curta a uma pergunta que o arco não tem, que outra substituiu ou
|
|
117
|
+
// que venceu. Nenhuma delas autoriza nem revoga nada.
|
|
118
|
+
'noctum.pergunta.desconhecida', 'noctum.pergunta.substituida', 'noctum.pergunta.vencida',
|
|
119
|
+
];
|
|
120
|
+
class RecusaDoArco extends Error {
|
|
121
|
+
motivo;
|
|
122
|
+
constructor(motivo, detalhe) {
|
|
123
|
+
super(`${motivo}: ${detalhe}`);
|
|
124
|
+
this.motivo = motivo;
|
|
125
|
+
this.name = 'RecusaDoArco';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.RecusaDoArco = RecusaDoArco;
|
|
129
|
+
exports.ID_DO_ARCO = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
130
|
+
exports.ID_DO_TRABALHO = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
131
|
+
const ITEM_DO_ROADMAP = /^RM-\d{3}$/;
|
|
132
|
+
const ID_DE_THREAD = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/;
|
|
133
|
+
const ID_DE_CONTA = /^[a-z0-9][a-z0-9._-]{0,62}$/i;
|
|
134
|
+
const REPOSITORIO = /^[A-Za-z0-9_.-]{1,100}\/[A-Za-z0-9_.-]{1,100}$/;
|
|
135
|
+
const BRANCH = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,199}$/;
|
|
136
|
+
const DATA_HORA = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
137
|
+
const HORARIO = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
|
|
138
|
+
const PALAVRAS_DE_ILIMITADO = /ilimitad|unlimited|infinit|sem[ -]?(?:teto|limite)/i;
|
|
139
|
+
const objeto = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
140
|
+
/**
|
|
141
|
+
* O nome curto de uma branch, nas regras de `git check-ref-format --branch`, ou nada. Ref completa
|
|
142
|
+
* (`refs/heads/main`), forma curta de outra ref (`heads/`, `tags/`, `remotes/`) e `HEAD` nunca são
|
|
143
|
+
* destino: com o texto cru, um push em `refs/heads/main` passaria pela recusa do push direto na base
|
|
144
|
+
* e `refs/tags/v1` criaria tag, que é exclusão fixa.
|
|
145
|
+
*/
|
|
146
|
+
function nomeDaBranch(v) {
|
|
147
|
+
if (typeof v !== 'string' || !BRANCH.test(v))
|
|
148
|
+
return undefined;
|
|
149
|
+
const minusculo = v.toLowerCase();
|
|
150
|
+
if (/^(?:refs|heads|tags|remotes)\//.test(minusculo) || minusculo === 'head' || v.includes('..') || v.includes('//') ||
|
|
151
|
+
/(?:^|\/)\./.test(v) || /\.lock(?:\/|$)/.test(v) || v.endsWith('/') || v.endsWith('.'))
|
|
152
|
+
return undefined;
|
|
153
|
+
return v;
|
|
154
|
+
}
|
|
155
|
+
const ausente = (v) => v === undefined || v === null || v === '';
|
|
156
|
+
const sha256 = (s) => (0, node_crypto_1.createHash)('sha256').update(s).digest('hex');
|
|
157
|
+
/** A exclusão que um nome de ação representa, quando representa. */
|
|
158
|
+
function exclusaoDaAcao(acao) {
|
|
159
|
+
if (typeof acao !== 'string')
|
|
160
|
+
return undefined;
|
|
161
|
+
const nome = acao.trim().toLowerCase();
|
|
162
|
+
// `constructor` e `__proto__` não são exclusão: a busca é só nas chaves próprias do mapa.
|
|
163
|
+
return Object.hasOwn(EXCLUSAO_POR_NOME, nome) ? EXCLUSAO_POR_NOME[nome] : undefined;
|
|
164
|
+
}
|
|
165
|
+
function chavesConhecidas(v, aceitas, onde, p) {
|
|
166
|
+
for (const k of Object.keys(v)) {
|
|
167
|
+
if (k === 'exclusoes' && onde === 'pedido')
|
|
168
|
+
p.invalidos.push('exclusoes: são fixas do núcleo, o pedido não as declara');
|
|
169
|
+
else if (!aceitas.includes(k))
|
|
170
|
+
p.invalidos.push(`${onde}.${k}: campo desconhecido`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function lerTeto(v, onde, p) {
|
|
174
|
+
if (ausente(v)) {
|
|
175
|
+
p.faltas.push(`${onde} (valor e unidade)`);
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
if (!objeto(v)) {
|
|
179
|
+
p.invalidos.push(`${onde}: precisa ser { valor, unidade }`);
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
chavesConhecidas(v, ['valor', 'unidade'], onde, p);
|
|
183
|
+
let valor;
|
|
184
|
+
if (ausente(v.valor))
|
|
185
|
+
p.faltas.push(`${onde}.valor`);
|
|
186
|
+
else if (typeof v.valor === 'number') {
|
|
187
|
+
// Zero e negativo viram "sem limite" em muita configuração; aqui eles não viram nada.
|
|
188
|
+
if (!Number.isFinite(v.valor) || v.valor <= 0)
|
|
189
|
+
p.ilimitados.push(`${onde}.valor (${String(v.valor)})`);
|
|
190
|
+
else
|
|
191
|
+
valor = v.valor;
|
|
192
|
+
}
|
|
193
|
+
else if (typeof v.valor === 'string' && PALAVRAS_DE_ILIMITADO.test(v.valor))
|
|
194
|
+
p.ilimitados.push(`${onde}.valor ("${v.valor}")`);
|
|
195
|
+
else
|
|
196
|
+
p.invalidos.push(`${onde}.valor: precisa ser número finito maior que zero`);
|
|
197
|
+
let unidade;
|
|
198
|
+
if (ausente(v.unidade))
|
|
199
|
+
p.faltas.push(`${onde}.unidade`);
|
|
200
|
+
else if (!exports.UNIDADES.includes(v.unidade))
|
|
201
|
+
p.invalidos.push(`${onde}.unidade: use ${exports.UNIDADES.join(', ')}`);
|
|
202
|
+
else
|
|
203
|
+
unidade = v.unidade;
|
|
204
|
+
if (unidade === 'percentual-da-cota' && valor !== undefined && valor > 100) {
|
|
205
|
+
p.invalidos.push(`${onde}.valor: percentual da cota vai até 100`);
|
|
206
|
+
valor = undefined;
|
|
207
|
+
}
|
|
208
|
+
if (unidade !== undefined && UNIDADES_INTEIRAS.includes(unidade) && valor !== undefined) {
|
|
209
|
+
if (valor > Number.MAX_SAFE_INTEGER)
|
|
210
|
+
p.ilimitados.push(`${onde}.valor (${String(valor)}, acima do inteiro seguro)`);
|
|
211
|
+
else if (!Number.isInteger(valor))
|
|
212
|
+
p.invalidos.push(`${onde}.valor: ${unidade} se conta em inteiros`);
|
|
213
|
+
if (!Number.isSafeInteger(valor))
|
|
214
|
+
valor = undefined;
|
|
215
|
+
}
|
|
216
|
+
return valor !== undefined && unidade !== undefined ? { valor, unidade } : undefined;
|
|
217
|
+
}
|
|
218
|
+
function lerJanela(v, p) {
|
|
219
|
+
if (ausente(v)) {
|
|
220
|
+
p.faltas.push('janela.inicio', 'janela.fim', 'janela.fuso');
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
if (!objeto(v)) {
|
|
224
|
+
p.invalidos.push('janela: precisa ser { inicio, fim, fuso }');
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
chavesConhecidas(v, ['inicio', 'fim', 'fuso'], 'janela', p);
|
|
228
|
+
const instante = (campo) => {
|
|
229
|
+
const x = v[campo];
|
|
230
|
+
if (ausente(x)) {
|
|
231
|
+
p.faltas.push(`janela.${campo}`);
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
if (typeof x !== 'string' || !DATA_HORA.test(x) || !Number.isFinite(Date.parse(x))) {
|
|
235
|
+
p.invalidos.push(`janela.${campo}: data e hora com deslocamento explícito, como 2026-10-12T22:00:00-03:00`);
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
return x;
|
|
239
|
+
};
|
|
240
|
+
const inicio = instante('inicio'), fim = instante('fim');
|
|
241
|
+
let fuso;
|
|
242
|
+
if (ausente(v.fuso))
|
|
243
|
+
p.faltas.push('janela.fuso');
|
|
244
|
+
else if (!(fuso = (0, horario_1.normalizarFuso)(v.fuso)) || !(fuso === 'UTC' || /^[A-Za-z]+(?:\/[A-Za-z0-9_+-]+)+$/.test(fuso))) {
|
|
245
|
+
p.invalidos.push(`janela.fuso: fuso IANA, como ${horario_1.FUSO_DE_BRASILIA}`);
|
|
246
|
+
fuso = undefined;
|
|
247
|
+
}
|
|
248
|
+
if (inicio && fim) {
|
|
249
|
+
const horas = (Date.parse(fim) - Date.parse(inicio)) / 3600000;
|
|
250
|
+
if (!(horas >= exports.JANELA_MINIMA_H && horas <= exports.JANELA_MAXIMA_H)) {
|
|
251
|
+
p.invalidos.push(`janela: o arco dura de ${exports.JANELA_MINIMA_H} a ${exports.JANELA_MAXIMA_H} h (D3 do dono); este pedido dá ${Math.round(horas * 100) / 100} h`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return inicio && fim && fuso ? { inicio, fim, fuso } : undefined;
|
|
255
|
+
}
|
|
256
|
+
function lerDestinos(v, p) {
|
|
257
|
+
if (ausente(v) || (Array.isArray(v) && v.length === 0)) {
|
|
258
|
+
p.faltas.push('destinos (ao menos um repositório, a base e as permissões)');
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
if (!Array.isArray(v)) {
|
|
262
|
+
p.invalidos.push('destinos: precisa ser lista');
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
const destinos = [];
|
|
266
|
+
const vistos = new Set();
|
|
267
|
+
v.forEach((d, i) => {
|
|
268
|
+
const onde = `destinos[${i}]`;
|
|
269
|
+
if (!objeto(d)) {
|
|
270
|
+
p.invalidos.push(`${onde}: precisa ser { repositorio, base, permissoes }`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
chavesConhecidas(d, ['repositorio', 'base', 'permissoes'], onde, p);
|
|
274
|
+
let ok = true;
|
|
275
|
+
if (ausente(d.repositorio)) {
|
|
276
|
+
p.faltas.push(`${onde}.repositorio`);
|
|
277
|
+
ok = false;
|
|
278
|
+
}
|
|
279
|
+
else if (typeof d.repositorio !== 'string' || !REPOSITORIO.test(d.repositorio) || d.repositorio.includes('..')) {
|
|
280
|
+
p.invalidos.push(`${onde}.repositorio: dono/nome de um repositório que já existe`);
|
|
281
|
+
ok = false;
|
|
282
|
+
}
|
|
283
|
+
else if (vistos.has(d.repositorio.toLowerCase())) {
|
|
284
|
+
p.invalidos.push(`${onde}.repositorio: repetido`);
|
|
285
|
+
ok = false;
|
|
286
|
+
}
|
|
287
|
+
else
|
|
288
|
+
vistos.add(d.repositorio.toLowerCase());
|
|
289
|
+
const base = nomeDaBranch(d.base);
|
|
290
|
+
if (ausente(d.base)) {
|
|
291
|
+
p.faltas.push(`${onde}.base`);
|
|
292
|
+
ok = false;
|
|
293
|
+
}
|
|
294
|
+
else if (base === undefined) {
|
|
295
|
+
p.invalidos.push(`${onde}.base: nome curto de branch, sem refs/, heads/, tags/ ou HEAD`);
|
|
296
|
+
ok = false;
|
|
297
|
+
}
|
|
298
|
+
const permissoes = [];
|
|
299
|
+
if (ausente(d.permissoes) || (Array.isArray(d.permissoes) && d.permissoes.length === 0)) {
|
|
300
|
+
p.faltas.push(`${onde}.permissoes`);
|
|
301
|
+
ok = false;
|
|
302
|
+
}
|
|
303
|
+
else if (!Array.isArray(d.permissoes)) {
|
|
304
|
+
p.invalidos.push(`${onde}.permissoes: precisa ser lista`);
|
|
305
|
+
ok = false;
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
for (const x of d.permissoes) {
|
|
309
|
+
const exclusao = exclusaoDaAcao(x);
|
|
310
|
+
if (exclusao) {
|
|
311
|
+
p.excluidas.push(`${onde}.permissoes: ${String(x)} (${RAZAO_DA_EXCLUSAO[exclusao]})`);
|
|
312
|
+
ok = false;
|
|
313
|
+
}
|
|
314
|
+
else if (!exports.PERMISSOES.includes(x)) {
|
|
315
|
+
p.invalidos.push(`${onde}.permissoes: ${String(x)} fora de ${exports.PERMISSOES.join(', ')}`);
|
|
316
|
+
ok = false;
|
|
317
|
+
}
|
|
318
|
+
else if (!permissoes.includes(x))
|
|
319
|
+
permissoes.push(x);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (ok)
|
|
323
|
+
destinos.push({ repositorio: d.repositorio, base: base,
|
|
324
|
+
permissoes: exports.PERMISSOES.filter(x => permissoes.includes(x)) });
|
|
325
|
+
});
|
|
326
|
+
return destinos.length === v.length ? destinos.sort((a, b) => a.repositorio.localeCompare(b.repositorio, 'en')) : undefined;
|
|
327
|
+
}
|
|
328
|
+
function lerSubtetos(v, chave, onde, aceito, explicacao, p) {
|
|
329
|
+
if (ausente(v) || (Array.isArray(v) && v.length === 0)) {
|
|
330
|
+
p.faltas.push(`${onde} (ao menos um subteto por ${chave})`);
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
if (!Array.isArray(v)) {
|
|
334
|
+
p.invalidos.push(`${onde}: precisa ser lista`);
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|
|
337
|
+
const itens = [];
|
|
338
|
+
const vistos = new Set();
|
|
339
|
+
v.forEach((s, i) => {
|
|
340
|
+
const aqui = `${onde}[${i}]`;
|
|
341
|
+
if (!objeto(s)) {
|
|
342
|
+
p.invalidos.push(`${aqui}: precisa ser { ${chave}, teto }`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
chavesConhecidas(s, [chave, 'teto'], aqui, p);
|
|
346
|
+
const nome = s[chave];
|
|
347
|
+
let ok = true;
|
|
348
|
+
if (ausente(nome)) {
|
|
349
|
+
p.faltas.push(`${aqui}.${chave}`);
|
|
350
|
+
ok = false;
|
|
351
|
+
}
|
|
352
|
+
else if (typeof nome !== 'string' || !aceito(nome)) {
|
|
353
|
+
p.invalidos.push(`${aqui}.${chave}: ${explicacao}`);
|
|
354
|
+
ok = false;
|
|
355
|
+
}
|
|
356
|
+
else if (vistos.has(nome.toLowerCase())) {
|
|
357
|
+
p.invalidos.push(`${aqui}.${chave}: repetido`);
|
|
358
|
+
ok = false;
|
|
359
|
+
}
|
|
360
|
+
else
|
|
361
|
+
vistos.add(nome.toLowerCase());
|
|
362
|
+
const teto = lerTeto(s.teto, `${aqui}.teto`, p);
|
|
363
|
+
if (ok && teto)
|
|
364
|
+
itens.push({ [chave]: nome, teto });
|
|
365
|
+
});
|
|
366
|
+
return itens.length === v.length ? itens.sort((a, b) => a[chave].localeCompare(b[chave], 'en')) : undefined;
|
|
367
|
+
}
|
|
368
|
+
function lerOrcamento(v, p) {
|
|
369
|
+
if (ausente(v)) {
|
|
370
|
+
p.faltas.push('orcamento.arco (teto do arco: valor e unidade)', 'orcamento.porConta (subteto por conta)', 'orcamento.porRuntime (subteto por runtime)');
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
if (!objeto(v)) {
|
|
374
|
+
p.invalidos.push('orcamento: precisa ser { arco, porConta, porRuntime }');
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
chavesConhecidas(v, ['arco', 'porConta', 'porRuntime'], 'orcamento', p);
|
|
378
|
+
const arco = lerTeto(v.arco, 'orcamento.arco', p);
|
|
379
|
+
const porConta = lerSubtetos(v.porConta, 'conta', 'orcamento.porConta', x => ID_DE_CONTA.test(x), 'id de perfil de conta já autenticado', p);
|
|
380
|
+
const porRuntime = lerSubtetos(v.porRuntime, 'runtime', 'orcamento.porRuntime', x => runtime_profiles_1.RUNTIMES_COM_PERFIL.includes(x), `runtime de assinatura: ${runtime_profiles_1.RUNTIMES_COM_PERFIL.join(', ')}`, p);
|
|
381
|
+
// Subteto acima do teto do arco, na mesma unidade, não limita nada: é teto do arco disfarçado.
|
|
382
|
+
for (const [nome, teto] of [...(porConta ?? []).map(s => [`conta ${s.conta}`, s.teto]),
|
|
383
|
+
...(porRuntime ?? []).map(s => [`runtime ${s.runtime}`, s.teto])]) {
|
|
384
|
+
if (arco && teto.unidade === arco.unidade && teto.valor > arco.valor) {
|
|
385
|
+
p.invalidos.push(`orcamento: o subteto da ${nome} passa do teto do arco na mesma unidade`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return arco && porConta && porRuntime ? { arco, porConta, porRuntime } : undefined;
|
|
389
|
+
}
|
|
390
|
+
function lerRelatorio(v, p) {
|
|
391
|
+
if (ausente(v)) {
|
|
392
|
+
p.faltas.push('relatorio.horario (HH:MM da manhã)', 'relatorio.canal');
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
if (!objeto(v)) {
|
|
396
|
+
p.invalidos.push('relatorio: precisa ser { horario, canal }');
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
chavesConhecidas(v, ['horario', 'canal'], 'relatorio', p);
|
|
400
|
+
let horario, canal;
|
|
401
|
+
if (ausente(v.horario))
|
|
402
|
+
p.faltas.push('relatorio.horario (HH:MM da manhã)');
|
|
403
|
+
else if (typeof v.horario !== 'string' || !HORARIO.test(v.horario))
|
|
404
|
+
p.invalidos.push('relatorio.horario: HH:MM, no fuso da janela');
|
|
405
|
+
else
|
|
406
|
+
horario = v.horario;
|
|
407
|
+
if (ausente(v.canal))
|
|
408
|
+
p.faltas.push('relatorio.canal');
|
|
409
|
+
else if (!exports.CANAIS_DO_RELATORIO.includes(v.canal))
|
|
410
|
+
p.invalidos.push(`relatorio.canal: use ${exports.CANAIS_DO_RELATORIO.join(', ')}`);
|
|
411
|
+
else
|
|
412
|
+
canal = v.canal;
|
|
413
|
+
return horario && canal ? { horario, canal } : undefined;
|
|
414
|
+
}
|
|
415
|
+
/** O sha256 do pedido de um trabalho, o mesmo que a criação da thread guarda (`prepararPedido`). */
|
|
416
|
+
function shaDoPedido(texto) {
|
|
417
|
+
return sha256(texto.trim());
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Os caracteres de formato (marcas de direção, largura zero, BOM, hífen suave e as tags do Unicode, que carregam texto
|
|
421
|
+
* que a tela não mostra e o modelo lê) e os de uso privado.
|
|
422
|
+
*/
|
|
423
|
+
const FORMATO_OU_PRIVADO = /[\p{Cf}\p{Co}]/u;
|
|
424
|
+
/** Letras e marcas que não desenham nada: os preenchedores do Hangul, o juntador de grafemas e as vogais mudas do khmer. */
|
|
425
|
+
const SEM_DESENHO = new Set([0x115f, 0x1160, 0x3164, 0xffa0, 0x34f, 0x17b4, 0x17b5]);
|
|
426
|
+
const seletorDeVariacao = (cp) => cp >= 0xfe00 && cp <= 0xfe0f;
|
|
427
|
+
/** O que pode levar o seletor de texto ou de emoji: o pictograma e a base da tecla (dígito, `#` e `*`). */
|
|
428
|
+
const BASE_DE_EMOJI = /^[\p{Extended_Pictographic}0-9#*]$/u;
|
|
429
|
+
/**
|
|
430
|
+
* A7 do CHECK da ork-rm058n2, e o resto que a rodada 2 achou: o caractere que esconde ou reordena texto na tela de quem
|
|
431
|
+
* autoriza. Os controles (fora o tab e a quebra de linha), os separadores de linha do Unicode, os de formato e de uso
|
|
432
|
+
* privado, os que não desenham nada, os seletores de variação suplementares e o seletor fora do emoji: só o de texto ou de
|
|
433
|
+
* emoji (U+FE0E, U+FE0F), um só, logo depois de um pictograma ou da base da tecla. Seletores em série carregariam texto
|
|
434
|
+
* escondido, 4 bits por seletor (S2 da rodada 3). Pelo código e pela categoria, sem escapes no fonte.
|
|
435
|
+
*/
|
|
436
|
+
function caractereQueEsconde(texto) {
|
|
437
|
+
let anterior = '';
|
|
438
|
+
for (const c of texto) {
|
|
439
|
+
const cp = c.codePointAt(0);
|
|
440
|
+
if ((cp < 0x20 && cp !== 0x09 && cp !== 0x0a) || (cp >= 0x7f && cp <= 0x9f) || cp === 0x2028 || cp === 0x2029 ||
|
|
441
|
+
FORMATO_OU_PRIVADO.test(c) || SEM_DESENHO.has(cp) || (cp >= 0xe0100 && cp <= 0xe01ef) ||
|
|
442
|
+
(seletorDeVariacao(cp) && !((cp === 0xfe0e || cp === 0xfe0f) && BASE_DE_EMOJI.test(anterior))))
|
|
443
|
+
return cp;
|
|
444
|
+
anterior = c;
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
const codigoDoCaractere = (cp) => `U+${cp.toString(16).toUpperCase().padStart(4, '0')}`;
|
|
449
|
+
/** O nome de uma máquina, a mesma regra do `maquina.json` (`gravarConfigDaMaquina`). */
|
|
450
|
+
const NOME_DE_MAQUINA = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
451
|
+
const BASE64 = /^[A-Za-z0-9+/]{1,200}={0,2}$/;
|
|
452
|
+
/**
|
|
453
|
+
* D2 da ork-rm058n3: a chave pública Ed25519 em SPKI DER base64, só na forma canônica que o Node exporta, ou nada. Duas
|
|
454
|
+
* escritas da mesma chave dariam dois hashes para o mesmo envelope e driblariam a regra da chave repetida.
|
|
455
|
+
*/
|
|
456
|
+
function chavePublicaValida(v) {
|
|
457
|
+
if (typeof v !== 'string' || !BASE64.test(v))
|
|
458
|
+
return undefined;
|
|
459
|
+
try {
|
|
460
|
+
const k = (0, node_crypto_1.createPublicKey)({ key: Buffer.from(v, 'base64'), format: 'der', type: 'spki' });
|
|
461
|
+
if (k.asymmetricKeyType !== 'ed25519')
|
|
462
|
+
return undefined;
|
|
463
|
+
return k.export({ format: 'der', type: 'spki' }).toString('base64') === v ? v : undefined;
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/** O que o resumo mostra da chave de uma máquina: o começo do sha256 dos bytes dela, para o dono conferir. */
|
|
470
|
+
function impressaoDaChave(chave) {
|
|
471
|
+
return (0, node_crypto_1.createHash)('sha256').update(Buffer.from(chave, 'base64')).digest('hex').slice(0, 16);
|
|
472
|
+
}
|
|
473
|
+
/** D2 da ork-rm058n3: as máquinas do arco, na ordem do dono (desempata a distribuição), sem nome nem chave repetidos. */
|
|
474
|
+
function lerMaquinas(v, p) {
|
|
475
|
+
if (ausente(v) || (Array.isArray(v) && v.length === 0)) {
|
|
476
|
+
p.faltas.push('maquinas (cada máquina do arco: nome e chave pública)');
|
|
477
|
+
return undefined;
|
|
478
|
+
}
|
|
479
|
+
if (!Array.isArray(v)) {
|
|
480
|
+
p.invalidos.push('maquinas: precisa ser lista');
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
483
|
+
if (v.length > exports.MAXIMO_DE_MAQUINAS) {
|
|
484
|
+
p.invalidos.push(`maquinas: até ${exports.MAXIMO_DE_MAQUINAS} por arco`);
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
487
|
+
const lidas = [];
|
|
488
|
+
const nomes = new Set(), chaves = new Set();
|
|
489
|
+
v.forEach((m, i) => {
|
|
490
|
+
const onde = `maquinas[${i}]`;
|
|
491
|
+
if (!objeto(m)) {
|
|
492
|
+
p.invalidos.push(`${onde}: precisa ser { nome, chave }`);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
chavesConhecidas(m, ['nome', 'chave'], onde, p);
|
|
496
|
+
let ok = true;
|
|
497
|
+
if (ausente(m.nome)) {
|
|
498
|
+
p.faltas.push(`${onde}.nome`);
|
|
499
|
+
ok = false;
|
|
500
|
+
}
|
|
501
|
+
else if (typeof m.nome !== 'string' || !NOME_DE_MAQUINA.test(m.nome)) {
|
|
502
|
+
p.invalidos.push(`${onde}.nome: letras, dígitos, ponto, _ ou -, até 64`);
|
|
503
|
+
ok = false;
|
|
504
|
+
}
|
|
505
|
+
else if (nomes.has(m.nome.toLowerCase())) {
|
|
506
|
+
p.invalidos.push(`${onde}.nome: repetido`);
|
|
507
|
+
ok = false;
|
|
508
|
+
}
|
|
509
|
+
else
|
|
510
|
+
nomes.add(m.nome.toLowerCase());
|
|
511
|
+
if (ausente(m.chave)) {
|
|
512
|
+
p.faltas.push(`${onde}.chave (a chave pública da máquina)`);
|
|
513
|
+
ok = false;
|
|
514
|
+
}
|
|
515
|
+
else if (chavePublicaValida(m.chave) === undefined) {
|
|
516
|
+
p.invalidos.push(`${onde}.chave: chave pública Ed25519 em SPKI DER base64, na forma canônica`);
|
|
517
|
+
ok = false;
|
|
518
|
+
}
|
|
519
|
+
else if (chaves.has(m.chave)) {
|
|
520
|
+
p.invalidos.push(`${onde}.chave: a mesma chave em duas máquinas`);
|
|
521
|
+
ok = false;
|
|
522
|
+
}
|
|
523
|
+
else
|
|
524
|
+
chaves.add(m.chave);
|
|
525
|
+
if (ok)
|
|
526
|
+
lidas.push({ nome: m.nome, chave: m.chave });
|
|
527
|
+
});
|
|
528
|
+
return lidas.length === v.length ? lidas : undefined;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* D2 da ork-rm058n3: o limite de cada conta somando as máquinas. Toda conta do orçamento tem o seu e nenhuma outra entra;
|
|
532
|
+
* acima da soma dos limites locais ele não limita nada, como o subteto acima do teto.
|
|
533
|
+
*/
|
|
534
|
+
function lerCapacidadeGlobal(v, orcamento, capacidade, quantasMaquinas, p) {
|
|
535
|
+
const falta = 'capacidadeGlobal.porConta (sessões ao mesmo tempo por conta, somando as máquinas)';
|
|
536
|
+
if (ausente(v)) {
|
|
537
|
+
p.faltas.push(falta);
|
|
538
|
+
return undefined;
|
|
539
|
+
}
|
|
540
|
+
if (!objeto(v)) {
|
|
541
|
+
p.invalidos.push('capacidadeGlobal: precisa ser { porConta }');
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
chavesConhecidas(v, ['porConta'], 'capacidadeGlobal', p);
|
|
545
|
+
const bruto = v.porConta;
|
|
546
|
+
if (ausente(bruto) || (Array.isArray(bruto) && bruto.length === 0)) {
|
|
547
|
+
p.faltas.push(falta);
|
|
548
|
+
return undefined;
|
|
549
|
+
}
|
|
550
|
+
if (!Array.isArray(bruto)) {
|
|
551
|
+
p.invalidos.push('capacidadeGlobal.porConta: precisa ser lista');
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
const doOrcamento = orcamento?.porConta.map(s => s.conta);
|
|
555
|
+
const itens = [];
|
|
556
|
+
const vistos = new Set();
|
|
557
|
+
bruto.forEach((s, i) => {
|
|
558
|
+
const aqui = `capacidadeGlobal.porConta[${i}]`;
|
|
559
|
+
if (!objeto(s)) {
|
|
560
|
+
p.invalidos.push(`${aqui}: precisa ser { conta, sessoes }`);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
chavesConhecidas(s, ['conta', 'sessoes'], aqui, p);
|
|
564
|
+
let ok = true;
|
|
565
|
+
if (ausente(s.conta)) {
|
|
566
|
+
p.faltas.push(`${aqui}.conta`);
|
|
567
|
+
ok = false;
|
|
568
|
+
}
|
|
569
|
+
else if (typeof s.conta !== 'string') {
|
|
570
|
+
p.invalidos.push(`${aqui}.conta: texto`);
|
|
571
|
+
ok = false;
|
|
572
|
+
}
|
|
573
|
+
else if (vistos.has(s.conta.toLowerCase())) {
|
|
574
|
+
p.invalidos.push(`${aqui}.conta: repetida`);
|
|
575
|
+
ok = false;
|
|
576
|
+
}
|
|
577
|
+
else if (doOrcamento && !doOrcamento.includes(s.conta)) {
|
|
578
|
+
p.invalidos.push(`${aqui}.conta: ${s.conta} não está no orçamento, e só entra aqui quem tem teto`);
|
|
579
|
+
ok = false;
|
|
580
|
+
}
|
|
581
|
+
else
|
|
582
|
+
vistos.add(s.conta.toLowerCase());
|
|
583
|
+
const local = typeof s.conta === 'string' ? capacidade?.porConta.find(x => x.conta === s.conta)?.sessoes : undefined;
|
|
584
|
+
const soma = local !== undefined && quantasMaquinas !== undefined ? local * quantasMaquinas : exports.MAXIMO_DE_SESSOES_DA_MAQUINA * exports.MAXIMO_DE_MAQUINAS;
|
|
585
|
+
if (ausente(s.sessoes)) {
|
|
586
|
+
p.faltas.push(`${aqui}.sessoes`);
|
|
587
|
+
ok = false;
|
|
588
|
+
}
|
|
589
|
+
else if (typeof s.sessoes !== 'number' || !Number.isSafeInteger(s.sessoes) || s.sessoes < 1) {
|
|
590
|
+
p.invalidos.push(`${aqui}.sessoes: inteiro a partir de 1`);
|
|
591
|
+
ok = false;
|
|
592
|
+
}
|
|
593
|
+
else if (s.sessoes > soma) {
|
|
594
|
+
p.invalidos.push(`${aqui}.sessoes: passa da soma dos limites locais da conta (${soma}) e não limita nada`);
|
|
595
|
+
ok = false;
|
|
596
|
+
}
|
|
597
|
+
if (ok)
|
|
598
|
+
itens.push({ conta: s.conta, sessoes: s.sessoes });
|
|
599
|
+
});
|
|
600
|
+
for (const conta of (doOrcamento ?? []).filter(n => !vistos.has(n.toLowerCase()))) {
|
|
601
|
+
p.faltas.push(`capacidadeGlobal.porConta (${conta}, que está no orçamento)`);
|
|
602
|
+
}
|
|
603
|
+
return itens.length === bruto.length ? { porConta: itens.sort((a, b) => a.conta.localeCompare(b.conta, 'en')) } : undefined;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* D1 e D2 da ork-rm058n2: a lista fechada de trabalhos. A ordem da lista é do dono e entra no hash, porque a
|
|
607
|
+
* posição desempata a fila (D3); a ordem dentro de `dependeDe` não muda nada e é normalizada.
|
|
608
|
+
*
|
|
609
|
+
* D2 da ork-rm058n3: na `v3`, `v3.maquinas` traz os nomes do envelope (ou `null`, quando eles não se leram) e o trabalho
|
|
610
|
+
* pode prender uma máquina; o de `thread` precisa dela, e o mesmo item preso a duas máquinas é recusado.
|
|
611
|
+
*/
|
|
612
|
+
function lerTrabalhos(v, p, v3 = null) {
|
|
613
|
+
if (ausente(v) || (Array.isArray(v) && v.length === 0)) {
|
|
614
|
+
p.faltas.push('trabalhos (a lista fechada do arco: id, item, prioridade, dependeDe e thread ou pedido)');
|
|
615
|
+
return undefined;
|
|
616
|
+
}
|
|
617
|
+
if (!Array.isArray(v)) {
|
|
618
|
+
p.invalidos.push('trabalhos: precisa ser lista');
|
|
619
|
+
return undefined;
|
|
620
|
+
}
|
|
621
|
+
if (v.length > exports.MAXIMO_DE_TRABALHOS) {
|
|
622
|
+
p.invalidos.push(`trabalhos: até ${exports.MAXIMO_DE_TRABALHOS} por arco`);
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
625
|
+
const lidos = [];
|
|
626
|
+
const ids = new Set(), threads = new Set(), pedidos = new Set();
|
|
627
|
+
v.forEach((t, i) => {
|
|
628
|
+
const onde = `trabalhos[${i}]`;
|
|
629
|
+
if (!objeto(t)) {
|
|
630
|
+
p.invalidos.push(`${onde}: precisa ser { id, item, prioridade, dependeDe, thread ou pedido }`);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
chavesConhecidas(t, ['id', 'item', 'prioridade', 'dependeDe', 'thread', 'pedido', ...(v3 ? ['maquina'] : [])], onde, p);
|
|
634
|
+
let ok = true;
|
|
635
|
+
if (ausente(t.id)) {
|
|
636
|
+
p.faltas.push(`${onde}.id`);
|
|
637
|
+
ok = false;
|
|
638
|
+
}
|
|
639
|
+
else if (typeof t.id !== 'string' || !exports.ID_DO_TRABALHO.test(t.id)) {
|
|
640
|
+
p.invalidos.push(`${onde}.id: letras minúsculas, dígitos e hífen, até 32`);
|
|
641
|
+
ok = false;
|
|
642
|
+
}
|
|
643
|
+
else if (ids.has(t.id)) {
|
|
644
|
+
p.invalidos.push(`${onde}.id: repetido`);
|
|
645
|
+
ok = false;
|
|
646
|
+
}
|
|
647
|
+
else
|
|
648
|
+
ids.add(t.id);
|
|
649
|
+
if (ausente(t.item)) {
|
|
650
|
+
p.faltas.push(`${onde}.item`);
|
|
651
|
+
ok = false;
|
|
652
|
+
}
|
|
653
|
+
else if (typeof t.item !== 'string' || !ITEM_DO_ROADMAP.test(t.item)) {
|
|
654
|
+
p.invalidos.push(`${onde}.item: item do roadmap, como RM-058`);
|
|
655
|
+
ok = false;
|
|
656
|
+
}
|
|
657
|
+
if (ausente(t.prioridade)) {
|
|
658
|
+
p.faltas.push(`${onde}.prioridade`);
|
|
659
|
+
ok = false;
|
|
660
|
+
}
|
|
661
|
+
else if (typeof t.prioridade !== 'number' || !Number.isSafeInteger(t.prioridade) || t.prioridade < 1) {
|
|
662
|
+
p.invalidos.push(`${onde}.prioridade: inteiro a partir de 1 (o 1 vem primeiro)`);
|
|
663
|
+
ok = false;
|
|
664
|
+
}
|
|
665
|
+
// Nem a lista vazia é presumida: o dono diz que o trabalho não depende de nada.
|
|
666
|
+
if (t.dependeDe === undefined || t.dependeDe === null) {
|
|
667
|
+
p.faltas.push(`${onde}.dependeDe (lista, vazia quando não depende de nada)`);
|
|
668
|
+
ok = false;
|
|
669
|
+
}
|
|
670
|
+
else if (!Array.isArray(t.dependeDe) || t.dependeDe.some(d => typeof d !== 'string' || !exports.ID_DO_TRABALHO.test(d))) {
|
|
671
|
+
p.invalidos.push(`${onde}.dependeDe: lista de ids de trabalhos deste arco`);
|
|
672
|
+
ok = false;
|
|
673
|
+
}
|
|
674
|
+
else if (new Set(t.dependeDe).size !== t.dependeDe.length) {
|
|
675
|
+
p.invalidos.push(`${onde}.dependeDe: id repetido`);
|
|
676
|
+
ok = false;
|
|
677
|
+
}
|
|
678
|
+
const comThread = !ausente(t.thread), comPedido = !ausente(t.pedido);
|
|
679
|
+
if (!comThread && !comPedido) {
|
|
680
|
+
p.faltas.push(`${onde}.thread ou ${onde}.pedido`);
|
|
681
|
+
ok = false;
|
|
682
|
+
}
|
|
683
|
+
else if (comThread && comPedido) {
|
|
684
|
+
p.invalidos.push(`${onde}: thread ou pedido, nunca os dois`);
|
|
685
|
+
ok = false;
|
|
686
|
+
}
|
|
687
|
+
else if (comThread) {
|
|
688
|
+
if (typeof t.thread !== 'string' || !ID_DE_THREAD.test(t.thread)) {
|
|
689
|
+
p.invalidos.push(`${onde}.thread: id de uma thread que já existe`);
|
|
690
|
+
ok = false;
|
|
691
|
+
}
|
|
692
|
+
else if (threads.has(t.thread)) {
|
|
693
|
+
p.invalidos.push(`${onde}.thread: a mesma thread em dois trabalhos`);
|
|
694
|
+
ok = false;
|
|
695
|
+
}
|
|
696
|
+
else
|
|
697
|
+
threads.add(t.thread);
|
|
698
|
+
}
|
|
699
|
+
else if (typeof t.pedido !== 'string' || !t.pedido.trim()) {
|
|
700
|
+
p.invalidos.push(`${onde}.pedido: o texto do dono para a thread nova`);
|
|
701
|
+
ok = false;
|
|
702
|
+
}
|
|
703
|
+
else if (Buffer.byteLength(t.pedido.trim(), 'utf8') > exports.TETO_DO_PEDIDO_DO_TRABALHO) {
|
|
704
|
+
p.invalidos.push(`${onde}.pedido: até ${exports.TETO_DO_PEDIDO_DO_TRABALHO} bytes`);
|
|
705
|
+
ok = false;
|
|
706
|
+
}
|
|
707
|
+
else if (caractereQueEsconde(t.pedido) !== null) {
|
|
708
|
+
p.invalidos.push(`${onde}.pedido: o caractere ${codigoDoCaractere(caractereQueEsconde(t.pedido))} esconde ou reordena texto na tela; ` +
|
|
709
|
+
'o pedido é texto, com tab e quebra de linha');
|
|
710
|
+
ok = false;
|
|
711
|
+
}
|
|
712
|
+
else if (typeof t.item === 'string' && pedidos.has(`${t.item}|${shaDoPedido(t.pedido)}`)) {
|
|
713
|
+
// O vínculo da thread aberta é o item mais o sha256 do pedido: dois trabalhos iguais seriam a mesma thread.
|
|
714
|
+
p.invalidos.push(`${onde}.pedido: o mesmo item e o mesmo pedido em dois trabalhos`);
|
|
715
|
+
ok = false;
|
|
716
|
+
}
|
|
717
|
+
else if (typeof t.item === 'string')
|
|
718
|
+
pedidos.add(`${t.item}|${shaDoPedido(t.pedido)}`);
|
|
719
|
+
let maquina;
|
|
720
|
+
if (v3) {
|
|
721
|
+
if (ausente(t.maquina)) {
|
|
722
|
+
if (comThread) {
|
|
723
|
+
p.faltas.push(`${onde}.maquina (a máquina em que a thread mora)`);
|
|
724
|
+
ok = false;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
else if (typeof t.maquina !== 'string' || !NOME_DE_MAQUINA.test(t.maquina)) {
|
|
728
|
+
p.invalidos.push(`${onde}.maquina: nome de uma das máquinas do arco`);
|
|
729
|
+
ok = false;
|
|
730
|
+
}
|
|
731
|
+
else if (v3.maquinas && !v3.maquinas.has(t.maquina)) {
|
|
732
|
+
p.invalidos.push(`${onde}.maquina: ${t.maquina} não está em maquinas`);
|
|
733
|
+
ok = false;
|
|
734
|
+
}
|
|
735
|
+
else
|
|
736
|
+
maquina = t.maquina;
|
|
737
|
+
}
|
|
738
|
+
if (ok) {
|
|
739
|
+
lidos.push({ id: t.id, item: t.item, prioridade: t.prioridade,
|
|
740
|
+
dependeDe: [...t.dependeDe].sort(),
|
|
741
|
+
...(comThread ? { thread: t.thread } : { pedido: t.pedido.trim() }),
|
|
742
|
+
...(maquina ? { maquina } : {}) });
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
if (lidos.length !== v.length)
|
|
746
|
+
return undefined;
|
|
747
|
+
if (v3) {
|
|
748
|
+
// A reserva da RM-047 é do item numa máquina só: o mesmo item preso a duas máquinas nunca andaria nas duas.
|
|
749
|
+
const presas = new Map();
|
|
750
|
+
for (const t of lidos)
|
|
751
|
+
if (t.maquina)
|
|
752
|
+
presas.set(t.item, (presas.get(t.item) ?? new Set()).add(t.maquina));
|
|
753
|
+
for (const [item, maquinas] of presas) {
|
|
754
|
+
if (maquinas.size > 1)
|
|
755
|
+
p.invalidos.push(`trabalhos: o item ${item} está preso a ${[...maquinas].join(' e ')}; a reserva do item é de uma máquina só`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
const somaDosPedidos = lidos.reduce((n, t) => n + (t.pedido ? Buffer.byteLength(t.pedido, 'utf8') : 0), 0);
|
|
759
|
+
if (somaDosPedidos > exports.TETO_DOS_PEDIDOS_DO_ARCO) {
|
|
760
|
+
p.invalidos.push(`trabalhos: os pedidos somam ${somaDosPedidos} bytes; até ${exports.TETO_DOS_PEDIDOS_DO_ARCO} por arco`);
|
|
761
|
+
return undefined;
|
|
762
|
+
}
|
|
763
|
+
for (const t of lidos) {
|
|
764
|
+
for (const d of t.dependeDe) {
|
|
765
|
+
if (d === t.id)
|
|
766
|
+
p.invalidos.push(`trabalho ${t.id}: depende de si mesmo`);
|
|
767
|
+
else if (!ids.has(d))
|
|
768
|
+
p.invalidos.push(`trabalho ${t.id}: depende de ${d}, que não está na lista do arco`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
// Ciclo: nenhum trabalho do ciclo chegaria a ser entregue, e a fila ficaria bloqueada até o fim da janela.
|
|
772
|
+
const porId = new Map(lidos.map(t => [t.id, t]));
|
|
773
|
+
const estado = new Map();
|
|
774
|
+
const ciclos = new Set();
|
|
775
|
+
const visitar = (id, caminho) => {
|
|
776
|
+
if (estado.get(id) === 'feito')
|
|
777
|
+
return;
|
|
778
|
+
if (estado.get(id) === 'visitando') {
|
|
779
|
+
ciclos.add([...caminho.slice(caminho.indexOf(id)), id].join(' -> '));
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
estado.set(id, 'visitando');
|
|
783
|
+
for (const d of porId.get(id)?.dependeDe ?? [])
|
|
784
|
+
if (porId.has(d) && d !== id)
|
|
785
|
+
visitar(d, [...caminho, id]);
|
|
786
|
+
estado.set(id, 'feito');
|
|
787
|
+
};
|
|
788
|
+
for (const t of lidos)
|
|
789
|
+
visitar(t.id, []);
|
|
790
|
+
for (const c of ciclos)
|
|
791
|
+
p.invalidos.push(`trabalhos: dependência em ciclo (${c})`);
|
|
792
|
+
return lidos;
|
|
793
|
+
}
|
|
794
|
+
const inteiroDeSessoes = (v, minimo) => typeof v === 'number' && Number.isSafeInteger(v) && v >= minimo && v <= exports.MAXIMO_DE_SESSOES_DA_MAQUINA;
|
|
795
|
+
/**
|
|
796
|
+
* D7 da ork-rm058n2: os limites de sessões ao mesmo tempo. Cada conta e cada runtime do orçamento tem o seu, e
|
|
797
|
+
* nenhum outro entra: a conta e o runtime que o arco pode usar são os que o dono pôs no envelope.
|
|
798
|
+
*/
|
|
799
|
+
function lerCapacidade(v, orcamento, p) {
|
|
800
|
+
if (ausente(v)) {
|
|
801
|
+
p.faltas.push('capacidade.maquina (sessões ao mesmo tempo nesta máquina)', 'capacidade.porConta (sessões por conta)', 'capacidade.porRuntime (sessões por runtime)', 'capacidade.revisao (slots que só o CHECK usa)');
|
|
802
|
+
return undefined;
|
|
803
|
+
}
|
|
804
|
+
if (!objeto(v)) {
|
|
805
|
+
p.invalidos.push('capacidade: precisa ser { maquina, porConta, porRuntime, revisao }');
|
|
806
|
+
return undefined;
|
|
807
|
+
}
|
|
808
|
+
chavesConhecidas(v, ['maquina', 'porConta', 'porRuntime', 'revisao'], 'capacidade', p);
|
|
809
|
+
let maquina, revisao;
|
|
810
|
+
if (ausente(v.maquina))
|
|
811
|
+
p.faltas.push('capacidade.maquina (sessões ao mesmo tempo nesta máquina)');
|
|
812
|
+
else if (!inteiroDeSessoes(v.maquina, 1))
|
|
813
|
+
p.invalidos.push(`capacidade.maquina: inteiro de 1 a ${exports.MAXIMO_DE_SESSOES_DA_MAQUINA}`);
|
|
814
|
+
else
|
|
815
|
+
maquina = v.maquina;
|
|
816
|
+
if (v.revisao === undefined || v.revisao === null)
|
|
817
|
+
p.faltas.push('capacidade.revisao (slots que só o CHECK usa; 0 é um valor)');
|
|
818
|
+
else if (!inteiroDeSessoes(v.revisao, 0))
|
|
819
|
+
p.invalidos.push('capacidade.revisao: inteiro a partir de 0');
|
|
820
|
+
else if (maquina !== undefined && v.revisao >= maquina) {
|
|
821
|
+
p.invalidos.push('capacidade.revisao: precisa deixar ao menos um slot da máquina para a execução');
|
|
822
|
+
}
|
|
823
|
+
else
|
|
824
|
+
revisao = v.revisao;
|
|
825
|
+
const lerLimites = (bruto, chave, onde, doOrcamento) => {
|
|
826
|
+
if (ausente(bruto) || (Array.isArray(bruto) && bruto.length === 0)) {
|
|
827
|
+
p.faltas.push(`${onde} (sessões por ${chave})`);
|
|
828
|
+
return undefined;
|
|
829
|
+
}
|
|
830
|
+
if (!Array.isArray(bruto)) {
|
|
831
|
+
p.invalidos.push(`${onde}: precisa ser lista`);
|
|
832
|
+
return undefined;
|
|
833
|
+
}
|
|
834
|
+
const itens = [];
|
|
835
|
+
const vistos = new Set();
|
|
836
|
+
bruto.forEach((s, i) => {
|
|
837
|
+
const aqui = `${onde}[${i}]`;
|
|
838
|
+
if (!objeto(s)) {
|
|
839
|
+
p.invalidos.push(`${aqui}: precisa ser { ${chave}, sessoes }`);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
chavesConhecidas(s, [chave, 'sessoes'], aqui, p);
|
|
843
|
+
const nome = s[chave];
|
|
844
|
+
let ok = true;
|
|
845
|
+
if (ausente(nome)) {
|
|
846
|
+
p.faltas.push(`${aqui}.${chave}`);
|
|
847
|
+
ok = false;
|
|
848
|
+
}
|
|
849
|
+
else if (typeof nome !== 'string') {
|
|
850
|
+
p.invalidos.push(`${aqui}.${chave}: texto`);
|
|
851
|
+
ok = false;
|
|
852
|
+
}
|
|
853
|
+
else if (vistos.has(nome.toLowerCase())) {
|
|
854
|
+
p.invalidos.push(`${aqui}.${chave}: repetido`);
|
|
855
|
+
ok = false;
|
|
856
|
+
}
|
|
857
|
+
else if (doOrcamento && !doOrcamento.includes(nome)) {
|
|
858
|
+
p.invalidos.push(`${aqui}.${chave}: ${nome} não está no orçamento, e só entra aqui quem tem teto`);
|
|
859
|
+
ok = false;
|
|
860
|
+
}
|
|
861
|
+
else
|
|
862
|
+
vistos.add(nome.toLowerCase());
|
|
863
|
+
if (ausente(s.sessoes)) {
|
|
864
|
+
p.faltas.push(`${aqui}.sessoes`);
|
|
865
|
+
ok = false;
|
|
866
|
+
}
|
|
867
|
+
else if (!inteiroDeSessoes(s.sessoes, 1)) {
|
|
868
|
+
p.invalidos.push(`${aqui}.sessoes: inteiro de 1 a ${exports.MAXIMO_DE_SESSOES_DA_MAQUINA}`);
|
|
869
|
+
ok = false;
|
|
870
|
+
}
|
|
871
|
+
else if (maquina !== undefined && s.sessoes > maquina) {
|
|
872
|
+
// Limite acima da máquina não limita nada: é o limite da máquina disfarçado, como o subteto acima do teto.
|
|
873
|
+
p.invalidos.push(`${aqui}.sessoes: passa do limite da máquina`);
|
|
874
|
+
ok = false;
|
|
875
|
+
}
|
|
876
|
+
if (ok)
|
|
877
|
+
itens.push({ [chave]: nome, sessoes: s.sessoes });
|
|
878
|
+
});
|
|
879
|
+
for (const falta of (doOrcamento ?? []).filter(n => !vistos.has(n.toLowerCase())))
|
|
880
|
+
p.faltas.push(`${onde} (${falta}, que está no orçamento)`);
|
|
881
|
+
return itens.length === bruto.length ? itens.sort((a, b) => a[chave].localeCompare(b[chave], 'en')) : undefined;
|
|
882
|
+
};
|
|
883
|
+
const porConta = lerLimites(v.porConta, 'conta', 'capacidade.porConta', orcamento?.porConta.map(s => s.conta));
|
|
884
|
+
const porRuntime = lerLimites(v.porRuntime, 'runtime', 'capacidade.porRuntime', orcamento?.porRuntime.map(s => s.runtime));
|
|
885
|
+
return maquina !== undefined && revisao !== undefined && porConta && porRuntime ? { maquina, porConta, porRuntime, revisao } : undefined;
|
|
886
|
+
}
|
|
887
|
+
/** O conteúdo traz a lista e os limites da `v2` (ou da `v3`, que os mantém)? Só ele despacha (D1 da ork-rm058n2). */
|
|
888
|
+
function envelopeComTrabalhos(c) {
|
|
889
|
+
return (c.contrato === exports.CONTRATO_ENVELOPE_V2 || c.contrato === exports.CONTRATO_ENVELOPE_V3) && Array.isArray(c.trabalhos) && !!c.capacidade;
|
|
890
|
+
}
|
|
891
|
+
/** RM-058 N3: o conteúdo é de um arco entre máquinas (`v3`, com as máquinas e o limite global)? */
|
|
892
|
+
function envelopeMultimaquina(c) {
|
|
893
|
+
return c.contrato === exports.CONTRATO_ENVELOPE_V3 && envelopeComTrabalhos(c) && Array.isArray(c.maquinas) && !!c.capacidadeGlobal;
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Valida o pedido de abertura e devolve o conteúdo normalizado que o dono autoriza.
|
|
897
|
+
*
|
|
898
|
+
* Junta todos os problemas antes de recusar, para o erro dizer de uma vez o que falta. O motivo
|
|
899
|
+
* tipado segue a gravidade: ação excluída, depois teto ilimitado, depois valor ausente, depois
|
|
900
|
+
* forma inválida. A mensagem leva todos, não só o do motivo.
|
|
901
|
+
*/
|
|
902
|
+
function validarPedidoDeAbertura(v) {
|
|
903
|
+
const p = { faltas: [], ilimitados: [], excluidas: [], invalidos: [] };
|
|
904
|
+
if (!objeto(v))
|
|
905
|
+
throw new RecusaDoArco('noctum.envelope.invalido', 'o pedido de abertura precisa ser um objeto');
|
|
906
|
+
// Na `v1`, `trabalhos` e `capacidade` continuam campo desconhecido, como antes da N2; fora da `v3`, as máquinas também.
|
|
907
|
+
const v3 = v.contrato === exports.CONTRATO_ENVELOPE_V3;
|
|
908
|
+
const v2 = v.contrato === exports.CONTRATO_ENVELOPE_V2 || v3;
|
|
909
|
+
chavesConhecidas(v, ['contrato', 'arco', 'janela', 'destinos', 'orcamento', 'relatorio', ...(v2 ? ['trabalhos', 'capacidade'] : []),
|
|
910
|
+
...(v3 ? ['maquinas', 'capacidadeGlobal'] : [])], 'pedido', p);
|
|
911
|
+
if (v.contrato !== exports.CONTRATO_ENVELOPE && !v2) {
|
|
912
|
+
p.invalidos.push(`contrato: precisa ser ${exports.CONTRATO_ENVELOPE}, ${exports.CONTRATO_ENVELOPE_V2} ou ${exports.CONTRATO_ENVELOPE_V3}`);
|
|
913
|
+
}
|
|
914
|
+
let arco;
|
|
915
|
+
if (ausente(v.arco))
|
|
916
|
+
p.faltas.push('arco (identificador do arco)');
|
|
917
|
+
else if (typeof v.arco !== 'string' || !exports.ID_DO_ARCO.test(v.arco))
|
|
918
|
+
p.invalidos.push('arco: letras minúsculas, dígitos e hífen, até 63');
|
|
919
|
+
else
|
|
920
|
+
arco = v.arco;
|
|
921
|
+
const janela = lerJanela(v.janela, p);
|
|
922
|
+
const destinos = lerDestinos(v.destinos, p);
|
|
923
|
+
const orcamento = lerOrcamento(v.orcamento, p);
|
|
924
|
+
const relatorio = lerRelatorio(v.relatorio, p);
|
|
925
|
+
const maquinas = v3 ? lerMaquinas(v.maquinas, p) : undefined;
|
|
926
|
+
const trabalhos = v2 ? lerTrabalhos(v.trabalhos, p, v3 ? { maquinas: maquinas ? new Set(maquinas.map(m => m.nome)) : null } : null) : undefined;
|
|
927
|
+
const capacidade = v2 ? lerCapacidade(v.capacidade, orcamento, p) : undefined;
|
|
928
|
+
const capacidadeGlobal = v3 ? lerCapacidadeGlobal(v.capacidadeGlobal, orcamento, capacidade, maquinas?.length, p) : undefined;
|
|
929
|
+
const partes = [
|
|
930
|
+
p.excluidas.length ? `ação excluída: ${p.excluidas.join('; ')}` : '',
|
|
931
|
+
p.ilimitados.length ? `teto ilimitado não é aceito: ${p.ilimitados.join('; ')}` : '',
|
|
932
|
+
p.faltas.length ? `o pedido precisa trazer, do dono: ${p.faltas.join('; ')}` : '',
|
|
933
|
+
p.invalidos.length ? `inválido: ${p.invalidos.join('; ')}` : '',
|
|
934
|
+
].filter(Boolean);
|
|
935
|
+
if (partes.length) {
|
|
936
|
+
const motivo = p.excluidas.length ? 'noctum.envelope.acao-excluida'
|
|
937
|
+
: p.ilimitados.length ? 'noctum.envelope.teto-ilimitado'
|
|
938
|
+
: p.faltas.length ? 'noctum.envelope.valores-ausentes' : 'noctum.envelope.invalido';
|
|
939
|
+
throw new RecusaDoArco(motivo, `${partes.join(' | ')}. Nenhum valor do dono é presumido.`);
|
|
940
|
+
}
|
|
941
|
+
return { contrato: v3 ? exports.CONTRATO_ENVELOPE_V3 : v2 ? exports.CONTRATO_ENVELOPE_V2 : exports.CONTRATO_ENVELOPE, arco: arco, janela: janela,
|
|
942
|
+
destinos: destinos, orcamento: orcamento, relatorio: relatorio, exclusoes: [...exports.EXCLUSOES_FIXAS],
|
|
943
|
+
...(v2 ? { trabalhos: trabalhos, capacidade: capacidade } : {}),
|
|
944
|
+
...(v3 ? { maquinas: maquinas, capacidadeGlobal: capacidadeGlobal } : {}) };
|
|
945
|
+
}
|
|
946
|
+
/** JSON com as chaves em ordem: o mesmo conteúdo dá o mesmo hash, qualquer que seja a ordem de entrada. */
|
|
947
|
+
function canonico(v) {
|
|
948
|
+
const ordenar = (x) => Array.isArray(x) ? x.map(ordenar)
|
|
949
|
+
: objeto(x) ? Object.fromEntries(Object.keys(x).sort().map(k => [k, ordenar(x[k])])) : x;
|
|
950
|
+
return JSON.stringify(ordenar(v));
|
|
951
|
+
}
|
|
952
|
+
function hashDoEnvelope(conteudo) {
|
|
953
|
+
return sha256(canonico(conteudo));
|
|
954
|
+
}
|
|
955
|
+
/** A recusa de uma ação excluída, antes de olhar estado nenhum. */
|
|
956
|
+
function recusaDeExclusao(acao) {
|
|
957
|
+
const exclusao = exclusaoDaAcao(acao);
|
|
958
|
+
return exclusao ? new RecusaDoArco('noctum.efeito.acao-excluida', `${String(acao)}: ${RAZAO_DA_EXCLUSAO[exclusao]}`) : undefined;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Janela e destino de um efeito contra o conteúdo autorizado, no instante dado.
|
|
962
|
+
*
|
|
963
|
+
* Lança a recusa tipada; não devolve nada quando o efeito cabe. O `merge` que passa aqui ainda
|
|
964
|
+
* depende das provas no SHA exato, que são da N5: este contrato é necessário, não suficiente.
|
|
965
|
+
*/
|
|
966
|
+
function avaliarEfeito(conteudo, efeito, quando) {
|
|
967
|
+
const excluida = recusaDeExclusao(efeito.acao);
|
|
968
|
+
if (excluida)
|
|
969
|
+
throw excluida;
|
|
970
|
+
avaliarJanela(conteudo, quando);
|
|
971
|
+
if (!exports.PERMISSOES.includes(efeito.acao)) {
|
|
972
|
+
throw new RecusaDoArco('noctum.efeito.fora-do-destino', `ação ${String(efeito.acao)} fora de ${exports.PERMISSOES.join(', ')}`);
|
|
973
|
+
}
|
|
974
|
+
const destino = conteudo.destinos.find(d => d.repositorio.toLowerCase() === String(efeito.repositorio).toLowerCase());
|
|
975
|
+
if (!destino)
|
|
976
|
+
throw new RecusaDoArco('noctum.efeito.fora-do-destino', `repositório ${String(efeito.repositorio)} fora do envelope`);
|
|
977
|
+
if (!destino.permissoes.includes(efeito.acao)) {
|
|
978
|
+
throw new RecusaDoArco('noctum.efeito.fora-do-destino', `${efeito.acao} não autorizado em ${destino.repositorio}`);
|
|
979
|
+
}
|
|
980
|
+
const branch = nomeDaBranch(efeito.branch);
|
|
981
|
+
if (branch === undefined) {
|
|
982
|
+
throw new RecusaDoArco('noctum.efeito.fora-do-destino', 'branch inválida: nome curto de branch, nunca ref completa, tag ou HEAD');
|
|
983
|
+
}
|
|
984
|
+
// Sem caixa: `MAIN` e `main` colidem em sistema de arquivos sem caixa, e o push direto na base fica fora de qualquer forma.
|
|
985
|
+
if (efeito.acao === 'push' && branch.toLowerCase() === destino.base.toLowerCase()) {
|
|
986
|
+
throw new RecusaDoArco('noctum.efeito.fora-do-destino', `push direto na base ${destino.base} fica fora do envelope`);
|
|
987
|
+
}
|
|
988
|
+
if (efeito.acao !== 'push' && branch !== destino.base) {
|
|
989
|
+
throw new RecusaDoArco('noctum.efeito.fora-do-destino', `${efeito.acao} só para a base ${destino.base}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
/** Só a janela: o instante precisa estar entre o início e o fim, e nada renova o fim. Lança a recusa tipada. */
|
|
993
|
+
function avaliarJanela(conteudo, quando) {
|
|
994
|
+
const agora = Date.parse(quando);
|
|
995
|
+
if (!Number.isFinite(agora))
|
|
996
|
+
throw new RecusaDoArco('noctum.efeito.fora-da-janela', 'instante da conferência inválido');
|
|
997
|
+
if (agora < Date.parse(conteudo.janela.inicio)) {
|
|
998
|
+
throw new RecusaDoArco('noctum.efeito.fora-da-janela', `a janela só começa em ${conteudo.janela.inicio}`);
|
|
999
|
+
}
|
|
1000
|
+
if (agora >= Date.parse(conteudo.janela.fim)) {
|
|
1001
|
+
throw new RecusaDoArco('noctum.efeito.expirado', `a janela terminou em ${conteudo.janela.fim}; nada a renova`);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
/** O que o dono lê antes de autorizar: uma linha por item, com o hash inteiro no fim. */
|
|
1005
|
+
function resumoDoEnvelope(conteudo, envelopeSha256) {
|
|
1006
|
+
const teto = (t) => `${t.valor} ${t.unidade}`;
|
|
1007
|
+
return [
|
|
1008
|
+
`Arco ${conteudo.arco}: de ${conteudo.janela.inicio} a ${conteudo.janela.fim} (${conteudo.janela.fuso})`,
|
|
1009
|
+
...conteudo.destinos.map(d => `Destino ${d.repositorio}, base ${d.base}: ${d.permissoes.join(', ')}`),
|
|
1010
|
+
`Teto do arco: ${teto(conteudo.orcamento.arco)}`,
|
|
1011
|
+
...conteudo.orcamento.porConta.map(s => `Subteto da conta ${s.conta}: ${teto(s.teto)}`),
|
|
1012
|
+
...conteudo.orcamento.porRuntime.map(s => `Subteto do runtime ${s.runtime}: ${teto(s.teto)}`),
|
|
1013
|
+
`Relatório da manhã: ${conteudo.relatorio.horario} (${conteudo.janela.fuso}) por ${conteudo.relatorio.canal}`,
|
|
1014
|
+
...(envelopeComTrabalhos(conteudo) ? linhasDaV2(conteudo) : []),
|
|
1015
|
+
...(envelopeMultimaquina(conteudo) ? linhasDaV3(conteudo) : []),
|
|
1016
|
+
`Nunca entram: ${conteudo.exclusoes.join(', ')}`,
|
|
1017
|
+
`Hash do envelope: ${envelopeSha256}`,
|
|
1018
|
+
];
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* O que a `v2` acrescenta ao resumo: cada trabalho, na ordem do dono, e os limites. O pedido vai inteiro, uma linha do
|
|
1022
|
+
* resumo por linha dele (A7 do CHECK): o dono autoriza só o que leu.
|
|
1023
|
+
*/
|
|
1024
|
+
function linhasDaV2(c) {
|
|
1025
|
+
return [
|
|
1026
|
+
...c.trabalhos.flatMap(t => {
|
|
1027
|
+
const cabeca = `Trabalho ${t.id} (${t.item}), prioridade ${t.prioridade}` +
|
|
1028
|
+
`${t.dependeDe.length ? `, depois de ${t.dependeDe.join(', ')}` : ''}${t.maquina ? `, na máquina ${t.maquina}` : ''}: `;
|
|
1029
|
+
if (t.thread)
|
|
1030
|
+
return [`${cabeca}thread ${t.thread}`];
|
|
1031
|
+
const linhas = t.pedido.split('\n');
|
|
1032
|
+
return [`${cabeca}abre uma thread com o pedido abaixo (${linhas.length} linha(s), sha256 ${shaDoPedido(t.pedido).slice(0, 12)})`,
|
|
1033
|
+
...linhas.map(l => ` | ${l}`)];
|
|
1034
|
+
}),
|
|
1035
|
+
`Sessões ao mesmo tempo: ${c.capacidade.maquina} na máquina, ${c.capacidade.revisao} delas só para revisão`,
|
|
1036
|
+
...c.capacidade.porConta.map(s => `Sessões ao mesmo tempo na conta ${s.conta}: ${s.sessoes}`),
|
|
1037
|
+
...c.capacidade.porRuntime.map(s => `Sessões ao mesmo tempo no runtime ${s.runtime}: ${s.sessoes}`),
|
|
1038
|
+
];
|
|
1039
|
+
}
|
|
1040
|
+
/** O que a `v3` acrescenta: cada máquina com a impressão da chave, para o dono conferir, e o limite global de cada conta. */
|
|
1041
|
+
function linhasDaV3(c) {
|
|
1042
|
+
return [
|
|
1043
|
+
...c.maquinas.map(m => `Máquina ${m.nome}: chave ${impressaoDaChave(m.chave)}`),
|
|
1044
|
+
...c.capacidadeGlobal.porConta.map(s => `Sessões ao mesmo tempo na conta ${s.conta}, somando as máquinas: ${s.sessoes}`),
|
|
1045
|
+
];
|
|
1046
|
+
}
|