@1aboveio/skills 0.10.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/LICENSE +3 -0
- package/README.md +90 -0
- package/bin/1aboveio-skills.mjs +18 -0
- package/package.json +28 -0
- package/runtime/skills/distribution/generated/recipes.json +1189 -0
- package/runtime/skills/distribution/scripts/bundles.mjs +280 -0
- package/runtime/skills/engineering/engineering-runtime/scripts/main-module.mjs +80 -0
- package/skills/backend/airflow-dag-develop/LICENSE +3 -0
- package/skills/backend/airflow-dag-develop/SKILL.md +111 -0
- package/skills/backend/app-debug/LICENSE +3 -0
- package/skills/backend/app-debug/SKILL.md +109 -0
- package/skills/backend/app-debug/references/common-errors.md +128 -0
- package/skills/backend/python-backend/LICENSE +3 -0
- package/skills/backend/python-backend/SKILL.md +326 -0
- package/skills/cicd-pipeline/cloud-build/LICENSE +3 -0
- package/skills/cicd-pipeline/cloud-build/SKILL.md +707 -0
- package/skills/cicd-pipeline/cloud-debug/LICENSE +3 -0
- package/skills/cicd-pipeline/cloud-debug/SKILL.md +316 -0
- package/skills/cicd-pipeline/cloud-debug/references/build-failures.md +238 -0
- package/skills/cicd-pipeline/cloud-debug/references/deploy-failures.md +376 -0
- package/skills/cicd-pipeline/cloud-debug/references/pipeline-failures.md +378 -0
- package/skills/cicd-pipeline/cloud-deploy/LICENSE +3 -0
- package/skills/cicd-pipeline/cloud-deploy/SKILL.md +229 -0
- package/skills/cicd-pipeline/cloud-deploy/references/config-templates.md +257 -0
- package/skills/cicd-pipeline/docker/LICENSE +3 -0
- package/skills/cicd-pipeline/docker/SKILL.md +126 -0
- package/skills/cicd-pipeline/google-cloud/LICENSE +3 -0
- package/skills/cicd-pipeline/google-cloud/SKILL.md +118 -0
- package/skills/cicd-pipeline/google-cloud/references/gcs.md +469 -0
- package/skills/cicd-pipeline/google-cloud/references/iam.md +451 -0
- package/skills/cicd-pipeline/google-cloud/references/project.md +349 -0
- package/skills/cicd-pipeline/google-cloud/references/secrets.md +336 -0
- package/skills/cicd-pipeline/google-cloud/references/vpc.md +312 -0
- package/skills/cicd-pipeline/google-cloud/scripts/create-sa.sh +36 -0
- package/skills/cicd-pipeline/google-cloud/scripts/gcp-config.sh +31 -0
- package/skills/cicd-pipeline/google-cloud/scripts/grant-iap.sh +41 -0
- package/skills/cicd-pipeline/google-cloud/scripts/setup-secrets.sh +48 -0
- package/skills/cicd-pipeline/mergify/LICENSE +3 -0
- package/skills/cicd-pipeline/mergify/SKILL.md +138 -0
- package/skills/cicd-pipeline/mergify/assets/templates/mergify.yml +237 -0
- package/skills/cicd-pipeline/mergify/assets/templates/ruleset.json +46 -0
- package/skills/cicd-pipeline/mergify/references/branch-protection.md +277 -0
- package/skills/cicd-pipeline/mergify/references/configuration.md +183 -0
- package/skills/cicd-pipeline/mergify/references/diagnosis.md +73 -0
- package/skills/cicd-pipeline/mergify/references/traps.md +78 -0
- package/skills/cicd-pipeline/mergify/references/watch-contract.md +218 -0
- package/skills/cicd-pipeline/mergify/scripts/audit-core.mjs +131 -0
- package/skills/cicd-pipeline/mergify/scripts/audit.mjs +4 -0
- package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery-core.mjs +663 -0
- package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery.mjs +4 -0
- package/skills/cicd-pipeline/podman/LICENSE +3 -0
- package/skills/cicd-pipeline/podman/SKILL.md +70 -0
- package/skills/cicd-pipeline/podman/agents/openai.yaml +4 -0
- package/skills/cicd-pipeline/podman/assets/templates/podman-compose-socket-directory.yml +6 -0
- package/skills/cicd-pipeline/podman/assets/templates/podman-service-override.conf +3 -0
- package/skills/cicd-pipeline/podman/references/compose-compatibility.md +70 -0
- package/skills/cicd-pipeline/podman/references/networking-and-ports.md +74 -0
- package/skills/cicd-pipeline/podman/references/rootless-services-and-sockets.md +156 -0
- package/skills/cicd-pipeline/podman/references/troubleshooting.md +98 -0
- package/skills/engineering/e2e-test/LICENSE +3 -0
- package/skills/engineering/e2e-test/SKILL.md +156 -0
- package/skills/engineering/e2e-test/assets/ci-gates.cloudbuild.yaml +272 -0
- package/skills/engineering/e2e-test/assets/ci-gates.github.yml +451 -0
- package/skills/engineering/e2e-test/assets/e2e-workflow.yml +282 -0
- package/skills/engineering/e2e-test/references/authoring/auth-flows.md +159 -0
- package/skills/engineering/e2e-test/references/authoring/playwright-config.md +71 -0
- package/skills/engineering/e2e-test/references/authoring/playwright-patterns.md +219 -0
- package/skills/engineering/e2e-test/references/authoring/test-skipping.md +44 -0
- package/skills/engineering/e2e-test/references/ci-integration.md +121 -0
- package/skills/engineering/e2e-test/references/ci-playwright-container.md +280 -0
- package/skills/engineering/e2e-test/references/debugging.md +36 -0
- package/skills/engineering/e2e-test/references/presentation-sweep.md +131 -0
- package/skills/engineering/e2e-test/references/reviewing.md +39 -0
- package/skills/engineering/e2e-test/references/route-discovery.md +50 -0
- package/skills/engineering/e2e-test/references/route-manifest.md +44 -0
- package/skills/engineering/e2e-test/scripts/detect-routes-fastapi.py +290 -0
- package/skills/engineering/e2e-test/scripts/detect-routes-nextjs.mjs +200 -0
- package/skills/engineering/e2e-test/scripts/post-visual-evidence.mjs +158 -0
- package/skills/engineering/e2e-test/scripts/presentation-checks.mjs +171 -0
- package/skills/engineering/e2e-test/scripts/presentation-perceivability.mjs +179 -0
- package/skills/engineering/e2e-test/scripts/presentation-reachability.mjs +154 -0
- package/skills/engineering/e2e-test/scripts/presentation-render-health.mjs +141 -0
- package/skills/engineering/e2e-test/scripts/presentation-sweep.mjs +148 -0
- package/skills/engineering/e2e-test/scripts/presentation-temporal.mjs +127 -0
- package/skills/engineering/e2e-test/scripts/presentation-visual.mjs +84 -0
- package/skills/engineering/e2e-test/scripts/project-route-manifest.mjs +75 -0
- package/skills/engineering/e2e-test/scripts/validate-manifest.mjs +106 -0
- package/skills/engineering/engineering-runtime/LICENSE +3 -0
- package/skills/engineering/engineering-runtime/SKILL.md +10 -0
- package/skills/engineering/engineering-runtime/agents/openai.yaml +6 -0
- package/skills/engineering/engineering-runtime/coherence/workflow.json +553 -0
- package/skills/engineering/engineering-runtime/scripts/exact-head-artifact.mjs +131 -0
- package/skills/engineering/engineering-runtime/scripts/head-check-set.mjs +398 -0
- package/skills/engineering/engineering-runtime/scripts/main-module.mjs +80 -0
- package/skills/engineering/engineering-runtime/scripts/mergify-yaml.mjs +11 -0
- package/skills/engineering/engineering-runtime/scripts/package-lock.json +43 -0
- package/skills/engineering/engineering-runtime/scripts/package.json +10 -0
- package/skills/engineering/engineering-runtime/scripts/required-check-plan.mjs +223 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +576 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +166 -0
- package/skills/engineering/ensure-coverage/LICENSE +3 -0
- package/skills/engineering/ensure-coverage/SKILL.md +136 -0
- package/skills/engineering/ensure-coverage/evals/evals.json +125 -0
- package/skills/engineering/ensure-coverage/references/breadth/coverage-ledger.md +91 -0
- package/skills/engineering/ensure-coverage/references/breadth/inventory-contract.md +83 -0
- package/skills/engineering/ensure-coverage/references/breadth/surface-baseline.md +44 -0
- package/skills/engineering/ensure-coverage/references/breadth/surface-discovery.md +16 -0
- package/skills/engineering/ensure-coverage/references/depth/characterization.md +68 -0
- package/skills/engineering/ensure-coverage/references/depth/coverage.config.example.json +25 -0
- package/skills/engineering/ensure-coverage/references/depth/grading.md +35 -0
- package/skills/engineering/ensure-coverage/references/depth/mock-policy.md +87 -0
- package/skills/engineering/ensure-coverage/references/depth/test-smells.md +23 -0
- package/skills/engineering/ensure-coverage/references/enforcement/ci-contract.md +164 -0
- package/skills/engineering/ensure-coverage/references/enforcement/hooks.md +85 -0
- package/skills/engineering/ensure-coverage/references/examples/coverage-ledger.md +109 -0
- package/skills/engineering/ensure-coverage/references/examples/refund-flow.md +33 -0
- package/skills/engineering/ensure-coverage/references/presentation/axis.md +78 -0
- package/skills/engineering/ensure-coverage/references/presentation/runner-contract.md +74 -0
- package/skills/engineering/ensure-coverage/references/process/audit-mode.md +33 -0
- package/skills/engineering/ensure-coverage/references/process/output-template.md +139 -0
- package/skills/engineering/ensure-coverage/references/process/review-contract-template.md +119 -0
- package/skills/engineering/ensure-coverage/references/process/scope-class.md +178 -0
- package/skills/engineering/ensure-coverage/references/process/test-strategy.md +55 -0
- package/skills/engineering/ensure-coverage/schemas/coverage-config.schema.json +45 -0
- package/skills/engineering/ensure-coverage/schemas/coverage-file.schema.json +93 -0
- package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.mjs +178 -0
- package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.mjs +101 -0
- package/skills/engineering/ensure-coverage/scripts/ci-audit.mjs +358 -0
- package/skills/engineering/ensure-coverage/scripts/coverage-checklist.mjs +494 -0
- package/skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs +663 -0
- package/skills/engineering/ensure-coverage/scripts/design-parity.mjs +591 -0
- package/skills/engineering/ensure-coverage/scripts/evidence-block.mjs +367 -0
- package/skills/engineering/ensure-coverage/scripts/lint-tests.mjs +269 -0
- package/skills/engineering/ensure-coverage/scripts/mock-policy-config.mjs +176 -0
- package/skills/engineering/ensure-coverage/scripts/package-lock.json +76 -0
- package/skills/engineering/ensure-coverage/scripts/package.json +19 -0
- package/skills/engineering/ensure-coverage/scripts/scope-class.mjs +554 -0
- package/skills/engineering/harness-runtime/LICENSE +3 -0
- package/skills/engineering/harness-runtime/SKILL.md +18 -0
- package/skills/engineering/harness-runtime/agents/openai.yaml +6 -0
- package/skills/engineering/harness-runtime/bin/discover-models.mjs +4 -0
- package/skills/engineering/harness-runtime/bin/model-catalog.mjs +4 -0
- package/skills/engineering/harness-runtime/contracts.md +15 -0
- package/skills/engineering/harness-runtime/discover-models.mjs +392 -0
- package/skills/engineering/harness-runtime/fixtures/native-question-schemas.json +33 -0
- package/skills/engineering/harness-runtime/fixtures/question-responses.json +54 -0
- package/skills/engineering/harness-runtime/index.mjs +767 -0
- package/skills/engineering/harness-runtime/model-catalog.mjs +787 -0
- package/skills/engineering/harness-runtime/native-question-contracts.md +37 -0
- package/skills/engineering/harness-runtime/references/model-catalog-seed.json +159 -0
- package/skills/engineering/harness-runtime/references/model-catalog.md +57 -0
- package/skills/engineering/implement-and-pr/LICENSE +3 -0
- package/skills/engineering/implement-and-pr/SKILL.md +176 -0
- package/skills/engineering/implement-and-pr/references/ci-iteration.md +10 -0
- package/skills/engineering/implement-and-pr/references/closeout.md +27 -0
- package/skills/engineering/implement-and-pr/references/contract-complete-fix-rounds.md +34 -0
- package/skills/engineering/implement-and-pr/references/evidence-rules.md +39 -0
- package/skills/engineering/implement-and-pr/references/incremental-plan.md +16 -0
- package/skills/engineering/implement-and-pr/references/self-review.md +23 -0
- package/skills/engineering/implement-and-pr/references/tdd-mode.md +18 -0
- package/skills/engineering/resolve-issues/LICENSE +3 -0
- package/skills/engineering/resolve-issues/SKILL.md +167 -0
- package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +448 -0
- package/skills/engineering/resolve-issues/references/breaker.md +82 -0
- package/skills/engineering/resolve-issues/references/deliverables.md +27 -0
- package/skills/engineering/resolve-issues/references/delivery.md +108 -0
- package/skills/engineering/resolve-issues/references/evidence-lane.md +21 -0
- package/skills/engineering/resolve-issues/references/exact-head-ci.md +287 -0
- package/skills/engineering/resolve-issues/references/fan-out.md +33 -0
- package/skills/engineering/resolve-issues/references/finalization.md +68 -0
- package/skills/engineering/resolve-issues/references/guarantees.md +10 -0
- package/skills/engineering/resolve-issues/references/high-risk.md +29 -0
- package/skills/engineering/resolve-issues/references/incidents/848/README.md +156 -0
- package/skills/engineering/resolve-issues/references/intake.md +86 -0
- package/skills/engineering/resolve-issues/references/integration-gate.md +53 -0
- package/skills/engineering/resolve-issues/references/interference.md +87 -0
- package/skills/engineering/resolve-issues/references/loop.md +134 -0
- package/skills/engineering/resolve-issues/references/model-catalog.md +9 -0
- package/skills/engineering/resolve-issues/references/postmortem.md +27 -0
- package/skills/engineering/resolve-issues/references/pre-flight-model-slots.md +41 -0
- package/skills/engineering/resolve-issues/references/pre-flight-recording-and-checkout.md +48 -0
- package/skills/engineering/resolve-issues/references/pre-flight.md +41 -0
- package/skills/engineering/resolve-issues/references/regression-checklist.md +26 -0
- package/skills/engineering/resolve-issues/references/run-state.md +288 -0
- package/skills/engineering/resolve-issues/references/sandboxed-testing.md +48 -0
- package/skills/engineering/resolve-issues/references/spawn-contract.md +96 -0
- package/skills/engineering/resolve-issues/references/terminal-evidence-journal.md +40 -0
- package/skills/engineering/resolve-issues/references/why.md +653 -0
- package/skills/engineering/resolve-issues/schemas/fix-round.schema.json +49 -0
- package/skills/engineering/resolve-issues/scripts/combine-and-verify.mjs +721 -0
- package/skills/engineering/resolve-issues/scripts/component-candidate.mjs +962 -0
- package/skills/engineering/resolve-issues/scripts/contract-revision.mjs +220 -0
- package/skills/engineering/resolve-issues/scripts/detect-delivery-mode.mjs +420 -0
- package/skills/engineering/resolve-issues/scripts/detect-target-branch.mjs +256 -0
- package/skills/engineering/resolve-issues/scripts/detect-workspace-mode.mjs +168 -0
- package/skills/engineering/resolve-issues/scripts/discover-models.mjs +9 -0
- package/skills/engineering/resolve-issues/scripts/doctrine.mjs +62 -0
- package/skills/engineering/resolve-issues/scripts/evidence-lifecycle-contract.mjs +191 -0
- package/skills/engineering/resolve-issues/scripts/exact-head-ci.mjs +413 -0
- package/skills/engineering/resolve-issues/scripts/exact-head-github-provider.mjs +332 -0
- package/skills/engineering/resolve-issues/scripts/finalize.mjs +488 -0
- package/skills/engineering/resolve-issues/scripts/fix-rounds.mjs +3307 -0
- package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-circular-1001.json +16 -0
- package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-valid-sequencing.json +51 -0
- package/skills/engineering/resolve-issues/scripts/fixtures/fmm-express-830-component-candidate.json +17 -0
- package/skills/engineering/resolve-issues/scripts/fixtures/head-check-set-1081.json +166 -0
- package/skills/engineering/resolve-issues/scripts/gate-value-series.mjs +92 -0
- package/skills/engineering/resolve-issues/scripts/guide-index.mjs +73 -0
- package/skills/engineering/resolve-issues/scripts/head-check-set.mjs +159 -0
- package/skills/engineering/resolve-issues/scripts/interference.mjs +427 -0
- package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +9 -0
- package/skills/engineering/resolve-issues/scripts/next-operations.mjs +419 -0
- package/skills/engineering/resolve-issues/scripts/postmortem.mjs +909 -0
- package/skills/engineering/resolve-issues/scripts/preflight-questions.mjs +322 -0
- package/skills/engineering/resolve-issues/scripts/reconcile-contained-unit-prs.mjs +415 -0
- package/skills/engineering/resolve-issues/scripts/release-state-contract.mjs +697 -0
- package/skills/engineering/resolve-issues/scripts/report.mjs +494 -0
- package/skills/engineering/resolve-issues/scripts/required-check-plan.mjs +172 -0
- package/skills/engineering/resolve-issues/scripts/round-metadata.mjs +79 -0
- package/skills/engineering/resolve-issues/scripts/run-state-review6-cases.mjs +334 -0
- package/skills/engineering/resolve-issues/scripts/run-state.mjs +4784 -0
- package/skills/engineering/resolve-issues/scripts/sandbox-selftest.mjs +395 -0
- package/skills/engineering/resolve-issues/scripts/spawn-contract.mjs +290 -0
- package/skills/engineering/resolve-issues/scripts/terminal-dispositions.mjs +170 -0
- package/skills/engineering/resolve-issues/scripts/terminal-evidence-journal.mjs +293 -0
- package/skills/engineering/resolve-issues/scripts/unit-kind.mjs +197 -0
- package/skills/engineering/resolve-issues/scripts/unit-lifecycle.mjs +127 -0
- package/skills/engineering/resolve-issues/scripts/watch-delivery.mjs +893 -0
- package/skills/engineering/resolve-issues/scripts/workspaces.mjs +829 -0
- package/skills/engineering/resolve-issues/workflows/independent-review.workflow.js +290 -0
- package/skills/engineering/resolve-issues/workflows/prior-art-scan.workflow.js +80 -0
- package/skills/engineering/resolve-issues/workflows/workflow-smoke.mjs +102 -0
- package/skills/engineering/resolve-release/LICENSE +3 -0
- package/skills/engineering/resolve-release/SKILL.md +112 -0
- package/skills/engineering/resolve-release/references/assembly.md +137 -0
- package/skills/engineering/resolve-release/references/auto-when-green.md +56 -0
- package/skills/engineering/resolve-release/references/candidate.md +167 -0
- package/skills/engineering/resolve-release/references/exposure.md +178 -0
- package/skills/engineering/resolve-release/references/handoff.md +24 -0
- package/skills/engineering/resolve-release/references/postmortem.md +230 -0
- package/skills/engineering/resolve-release/references/preflight.md +207 -0
- package/skills/engineering/resolve-release/references/principles.md +94 -0
- package/skills/engineering/resolve-release/references/regression-checklist.md +36 -0
- package/skills/engineering/resolve-release/references/related-skills.md +12 -0
- package/skills/engineering/resolve-release/references/routing.md +149 -0
- package/skills/engineering/resolve-release/references/verified-sha-github-flow.md +285 -0
- package/skills/engineering/resolve-release/references/versioning.md +202 -0
- package/skills/engineering/resolve-release/references/why.md +53 -0
- package/skills/engineering/resolve-release/scripts/adapter-completion-artifact.mjs +389 -0
- package/skills/engineering/resolve-release/scripts/build-changes.mjs +209 -0
- package/skills/engineering/resolve-release/scripts/candidate-hygiene.mjs +407 -0
- package/skills/engineering/resolve-release/scripts/candidate-identity.mjs +904 -0
- package/skills/engineering/resolve-release/scripts/candidate-traffic.mjs +81 -0
- package/skills/engineering/resolve-release/scripts/checked-adapter-loader.mjs +612 -0
- package/skills/engineering/resolve-release/scripts/close-attempt.mjs +135 -0
- package/skills/engineering/resolve-release/scripts/closeout-release.mjs +161 -0
- package/skills/engineering/resolve-release/scripts/doctrine.mjs +106 -0
- package/skills/engineering/resolve-release/scripts/durable-processing.mjs +522 -0
- package/skills/engineering/resolve-release/scripts/ensure-target-green.mjs +659 -0
- package/skills/engineering/resolve-release/scripts/evidence-bundle.mjs +1014 -0
- package/skills/engineering/resolve-release/scripts/finalize-release.mjs +526 -0
- package/skills/engineering/resolve-release/scripts/fixtures/durable-processing-adapter.mjs +169 -0
- package/skills/engineering/resolve-release/scripts/green-gate.mjs +599 -0
- package/skills/engineering/resolve-release/scripts/isolated-adapter-evaluator.mjs +752 -0
- package/skills/engineering/resolve-release/scripts/metadata-pr-status.mjs +56 -0
- package/skills/engineering/resolve-release/scripts/metadata-sync.mjs +1538 -0
- package/skills/engineering/resolve-release/scripts/postmortem.mjs +381 -0
- package/skills/engineering/resolve-release/scripts/preflight-probes.mjs +498 -0
- package/skills/engineering/resolve-release/scripts/production-endpoints.mjs +326 -0
- package/skills/engineering/resolve-release/scripts/rc-circuit-breaker.mjs +272 -0
- package/skills/engineering/resolve-release/scripts/report.mjs +417 -0
- package/skills/engineering/resolve-release/scripts/reprobe-credentials.mjs +114 -0
- package/skills/engineering/resolve-release/scripts/revalidate-candidate.mjs +238 -0
- package/skills/engineering/resolve-release/scripts/review-packet.mjs +503 -0
- package/skills/engineering/resolve-release/scripts/rollback-floor.mjs +263 -0
- package/skills/engineering/resolve-release/scripts/version-assert.mjs +339 -0
- package/skills/engineering/resolve-release/scripts/version-postmortem.mjs +485 -0
- package/skills/engineering/resolve-release/scripts/version.mjs +1199 -0
- package/skills/engineering/resolve-release/scripts/watch-candidate-delivery.mjs +449 -0
- package/skills/engineering/resolve-release/vendor/ACORN-LICENSE +21 -0
- package/skills/engineering/resolve-release/vendor/README.md +60 -0
- package/skills/engineering/resolve-release/vendor/acorn.mjs +6233 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/LICENSE +21 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/README.md +341 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/README.template.md +70 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs +7 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.mts +2033 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.ts +2033 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js +7 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/core/package.json +49 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/LICENSE +21 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/README.md +5 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.mts +549 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.ts +549 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/package.json +36 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/LICENSE +47 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/README.md +82 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.d.ts +11 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.mjs +22 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cjs +21 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.cjs +21 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.d.ts +11 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.d.ts +11 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.mjs +25 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.wasm +0 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.mts +85 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.ts +85 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.mts +20 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.ts +20 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs +2 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs.map +1 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/package.json +61 -0
- package/skills/engineering/review-pr/LICENSE +3 -0
- package/skills/engineering/review-pr/SKILL.md +123 -0
- package/skills/engineering/review-pr/references/adversarial-reviewer-prompt.md +42 -0
- package/skills/engineering/review-pr/references/code-correctness.md +5 -0
- package/skills/engineering/review-pr/references/contract-freshness.md +9 -0
- package/skills/engineering/review-pr/references/coordination.md +18 -0
- package/skills/engineering/review-pr/references/domain-hazards.md +123 -0
- package/skills/engineering/review-pr/references/finding-themes.md +7 -0
- package/skills/engineering/review-pr/references/github-posting.md +98 -0
- package/skills/engineering/review-pr/references/golden-path-smoke.md +5 -0
- package/skills/engineering/review-pr/references/incremental-output.md +16 -0
- package/skills/engineering/review-pr/references/inputs-and-discovery.md +31 -0
- package/skills/engineering/review-pr/references/output-format.md +99 -0
- package/skills/engineering/review-pr/references/over-mock-screen.md +7 -0
- package/skills/engineering/review-pr/references/promotion-prs.md +16 -0
- package/skills/engineering/review-pr/references/re-review.md +18 -0
- package/skills/engineering/review-pr/references/review-method.md +199 -0
- package/skills/engineering/review-pr/references/review-mode.md +30 -0
- package/skills/engineering/review-pr/references/review-posture.md +53 -0
- package/skills/engineering/review-pr/references/round1-depth.md +62 -0
- package/skills/engineering/review-pr/references/scripts.md +17 -0
- package/skills/engineering/review-pr/references/workflow.md +16 -0
- package/skills/engineering/review-pr/schemas/findings.schema.json +282 -0
- package/skills/engineering/review-pr/scripts/finding-contract.mjs +285 -0
- package/skills/engineering/review-pr/scripts/post-review.mjs +405 -0
- package/skills/engineering/review-pr/scripts/pr-context.mjs +207 -0
- package/skills/engineering/review-pr/scripts/scan-diff.mjs +365 -0
- package/skills/engineering/review-pr/scripts/theme-contract.mjs +57 -0
- package/skills/engineering/smoke/LICENSE +3 -0
- package/skills/engineering/smoke/SKILL.md +131 -0
- package/skills/engineering/smoke/assets/smoke.manifest.example.json +53 -0
- package/skills/engineering/smoke/references/manifest.md +192 -0
- package/skills/engineering/smoke/scripts/smoke.mjs +713 -0
- package/skills/fullstack/better-auth/LICENSE +3 -0
- package/skills/fullstack/better-auth/SKILL.md +601 -0
- package/skills/fullstack/better-auth/references/feishu-api.md +270 -0
- package/skills/fullstack/monorepo/LICENSE +3 -0
- package/skills/fullstack/monorepo/SKILL.md +465 -0
- package/skills/fullstack/nextjs-fullstack/LICENSE +3 -0
- package/skills/fullstack/nextjs-fullstack/SKILL.md +210 -0
- package/skills/fullstack/nextjs-fullstack/conventions.md +318 -0
- package/skills/fullstack/nextjs-fullstack/frontend-conventions.md +61 -0
- package/skills/fullstack/nextjs-fullstack/nextjs16.md +287 -0
- package/skills/fullstack/nextjs-fullstack/server-actions.md +409 -0
- package/skills/fullstack/prisma-setup/LICENSE +3 -0
- package/skills/fullstack/prisma-setup/SKILL.md +180 -0
- package/skills/fullstack/prisma-setup/nextjs.md +258 -0
- package/skills/fullstack/prisma-setup/turborepo.md +301 -0
- package/skills/fullstack/shadcn/LICENSE +3 -0
- package/skills/fullstack/shadcn/SKILL.md +119 -0
- package/skills/fullstack/shadcn/assets/shadcn-small.png +0 -0
- package/skills/fullstack/shadcn/assets/shadcn.png +0 -0
- package/skills/fullstack/shadcn/cli.md +411 -0
- package/skills/fullstack/shadcn/customization.md +224 -0
- package/skills/fullstack/shadcn/evals/evals.json +90 -0
- package/skills/fullstack/shadcn/mcp.md +101 -0
- package/skills/fullstack/shadcn/rules/base-vs-radix.md +323 -0
- package/skills/fullstack/shadcn/rules/component-selection.md +67 -0
- package/skills/fullstack/shadcn/rules/composition.md +195 -0
- package/skills/fullstack/shadcn/rules/data-table.md +201 -0
- package/skills/fullstack/shadcn/rules/forms.md +255 -0
- package/skills/fullstack/shadcn/rules/icons.md +103 -0
- package/skills/fullstack/shadcn/rules/styling.md +167 -0
- package/skills/fullstack/zod-v4/LICENSE +3 -0
- package/skills/fullstack/zod-v4/SKILL.md +287 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import { execFileSync } from 'node:child_process'
|
|
5
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
6
|
+
import { normalizeReleaseVerdict } from '../../resolve-issues/scripts/release-state-contract.mjs'
|
|
7
|
+
|
|
8
|
+
export const PRODUCTION_ENDPOINTS_SCHEMA_VERSION = 1
|
|
9
|
+
export const DEFAULT_FLOOR_FRESHNESS_MINUTES = 30
|
|
10
|
+
|
|
11
|
+
function nonEmpty(value) {
|
|
12
|
+
return typeof value === 'string' && value.trim().length > 0
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function normalizeProductionUrl(value) {
|
|
16
|
+
if (!nonEmpty(value)) throw new Error('endpoint URL must be a non-empty HTTPS URL')
|
|
17
|
+
let parsed
|
|
18
|
+
try {
|
|
19
|
+
parsed = new URL(value.trim())
|
|
20
|
+
} catch {
|
|
21
|
+
throw new Error('endpoint URL must be a non-empty HTTPS URL')
|
|
22
|
+
}
|
|
23
|
+
if (parsed.protocol !== 'https:' || parsed.username || parsed.password || parsed.search || parsed.hash) {
|
|
24
|
+
throw new Error('endpoint URL must use HTTPS and must not contain credentials, query, or fragment')
|
|
25
|
+
}
|
|
26
|
+
const pathname = parsed.pathname.replace(/\/+$/, '')
|
|
27
|
+
return `${parsed.origin}${pathname}`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function endpointRecord(canonicalProductionUrl, cloudRunServiceUrl, now) {
|
|
31
|
+
const canonical = normalizeProductionUrl(canonicalProductionUrl)
|
|
32
|
+
const service = normalizeProductionUrl(cloudRunServiceUrl)
|
|
33
|
+
const canonicalHost = new URL(canonical).hostname.toLowerCase()
|
|
34
|
+
const serviceHost = new URL(service).hostname.toLowerCase()
|
|
35
|
+
const configurationFingerprint = createHash('sha256')
|
|
36
|
+
.update(JSON.stringify({ canonicalProductionUrl: canonical, cloudRunServiceUrl: service }))
|
|
37
|
+
.digest('hex')
|
|
38
|
+
return {
|
|
39
|
+
schemaVersion: PRODUCTION_ENDPOINTS_SCHEMA_VERSION,
|
|
40
|
+
canonicalProductionUrl: canonical,
|
|
41
|
+
cloudRunServiceUrl: service,
|
|
42
|
+
distinctPublicEdgeHost: canonicalHost !== serviceHost,
|
|
43
|
+
publicEdgeHostname: canonicalHost !== serviceHost ? canonicalHost : null,
|
|
44
|
+
configurationFingerprint,
|
|
45
|
+
canonicalSource: "deploy-trigger-config",
|
|
46
|
+
serviceSource: "cloud-run-service",
|
|
47
|
+
checkedAt: new Date(now).toISOString(),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function validateProductionEndpointRecord(record) {
|
|
52
|
+
if (!record || record.schemaVersion !== PRODUCTION_ENDPOINTS_SCHEMA_VERSION) {
|
|
53
|
+
return { ok: false, id: 'production-endpoints-missing', detail: 'preflight production endpoint record is missing or malformed' }
|
|
54
|
+
}
|
|
55
|
+
let expected
|
|
56
|
+
try { expected = endpointRecord(record.canonicalProductionUrl, record.cloudRunServiceUrl, Date.parse(record.checkedAt)) } catch (error) {
|
|
57
|
+
return { ok: false, id: 'production-endpoints-invalid', detail: error.message }
|
|
58
|
+
}
|
|
59
|
+
if (!Number.isFinite(Date.parse(record.checkedAt))
|
|
60
|
+
|| record.canonicalSource !== expected.canonicalSource
|
|
61
|
+
|| record.serviceSource !== expected.serviceSource
|
|
62
|
+
|| record.canonicalProductionUrl !== expected.canonicalProductionUrl
|
|
63
|
+
|| record.cloudRunServiceUrl !== expected.cloudRunServiceUrl
|
|
64
|
+
|| record.distinctPublicEdgeHost !== expected.distinctPublicEdgeHost
|
|
65
|
+
|| record.publicEdgeHostname !== expected.publicEdgeHostname
|
|
66
|
+
|| record.configurationFingerprint !== expected.configurationFingerprint) {
|
|
67
|
+
return { ok: false, id: 'production-endpoints-invalid', detail: 'endpoint topology or fingerprint does not match the recorded URLs' }
|
|
68
|
+
}
|
|
69
|
+
return { ok: true, record }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function usableFreshness(value) {
|
|
73
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
74
|
+
? value
|
|
75
|
+
: DEFAULT_FLOOR_FRESHNESS_MINUTES
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function freshnessFailure(checkedAt, now, freshnessMinutes) {
|
|
79
|
+
const timestamp = typeof checkedAt === "string" ? Date.parse(checkedAt) : NaN
|
|
80
|
+
if (!Number.isFinite(timestamp)) return "floor-verdict-unstamped"
|
|
81
|
+
const ageMinutes = (now - timestamp) / 60_000
|
|
82
|
+
if (ageMinutes < 0 || ageMinutes > usableFreshness(freshnessMinutes)) return "floor-verdict-stale"
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function evaluateProductionFloor(record, floorReadiness, {
|
|
87
|
+
now = Date.now(), freshnessMinutes = DEFAULT_FLOOR_FRESHNESS_MINUTES,
|
|
88
|
+
} = {}) {
|
|
89
|
+
const recordValidation = validateProductionEndpointRecord(record)
|
|
90
|
+
if (!recordValidation.ok) return recordValidation
|
|
91
|
+
if (!floorReadiness || typeof floorReadiness !== "object") {
|
|
92
|
+
return { ok: false, id: "floor-verdict-missing", detail: "floor readiness was not recorded at endpoint preflight" }
|
|
93
|
+
}
|
|
94
|
+
if (floorReadiness.endpointFingerprint !== record.configurationFingerprint) {
|
|
95
|
+
return { ok: false, id: "floor-endpoint-configuration-mismatch", detail: "floor readiness describes a different endpoint configuration" }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const status = String(floorReadiness.status || "").toLowerCase()
|
|
99
|
+
if (!record.distinctPublicEdgeHost) {
|
|
100
|
+
if (status !== "skipped" || !nonEmpty(floorReadiness.reason)) {
|
|
101
|
+
return { ok: false, id: "cloud-run-only-skip-missing", detail: "Cloud Run-only topology requires the reasoned preflight skip" }
|
|
102
|
+
}
|
|
103
|
+
return { ok: true, evidence: { floorReadiness } }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (status !== "alive") {
|
|
107
|
+
return { ok: false, id: "floor-not-alive", detail: "a distinct public/edge host requires prod-active ALIVE and cannot be skipped" }
|
|
108
|
+
}
|
|
109
|
+
if (floorReadiness.profile !== "prod-active") {
|
|
110
|
+
return { ok: false, id: "floor-profile-mismatch", detail: "distinct-host floor evidence must use profile prod-active" }
|
|
111
|
+
}
|
|
112
|
+
let target
|
|
113
|
+
try { target = normalizeProductionUrl(floorReadiness.target) } catch {
|
|
114
|
+
return { ok: false, id: "floor-target-mismatch", detail: "floor target is not the canonical configured production URL" }
|
|
115
|
+
}
|
|
116
|
+
if (target !== record.canonicalProductionUrl) {
|
|
117
|
+
return { ok: false, id: "floor-target-mismatch", detail: "floor target is not the canonical configured production URL" }
|
|
118
|
+
}
|
|
119
|
+
if (floorReadiness.noMutations !== true) {
|
|
120
|
+
return { ok: false, id: "floor-not-read-only", detail: "prod-active floor evidence must be read-only" }
|
|
121
|
+
}
|
|
122
|
+
const freshness = freshnessFailure(floorReadiness.checkedAt, now, freshnessMinutes)
|
|
123
|
+
if (freshness) return { ok: false, id: freshness, detail: "prod-active floor evidence is missing a fresh timestamp" }
|
|
124
|
+
return { ok: true, evidence: { floorReadiness } }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function buildProductionEndpointRecord({
|
|
128
|
+
canonicalProductionUrl, cloudRunServiceUrl, floorVerdict = null,
|
|
129
|
+
now = Date.now(), freshnessMinutes = DEFAULT_FLOOR_FRESHNESS_MINUTES,
|
|
130
|
+
} = {}) {
|
|
131
|
+
let record
|
|
132
|
+
try {
|
|
133
|
+
record = endpointRecord(canonicalProductionUrl, cloudRunServiceUrl, now)
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return { ok: false, failed: ["endpoint-read-invalid"], detail: error.message }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!record.distinctPublicEdgeHost) {
|
|
139
|
+
const floorReadiness = {
|
|
140
|
+
status: "skipped",
|
|
141
|
+
reason: "canonical configured production URL has the same hostname as the Cloud Run service URL; no distinct public/edge hostname exists",
|
|
142
|
+
endpointFingerprint: record.configurationFingerprint,
|
|
143
|
+
checkedAt: record.checkedAt,
|
|
144
|
+
}
|
|
145
|
+
return { ok: true, record, floorReadiness, failed: [] }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const normalized = normalizeReleaseVerdict(floorVerdict)
|
|
149
|
+
const floorReadiness = normalized && {
|
|
150
|
+
status: String(normalized.state).toLowerCase(),
|
|
151
|
+
profile: normalized.profile,
|
|
152
|
+
target: normalized.target,
|
|
153
|
+
noMutations: normalized.noMutations,
|
|
154
|
+
checkedAt: normalized.generatedAt,
|
|
155
|
+
endpointFingerprint: record.configurationFingerprint,
|
|
156
|
+
}
|
|
157
|
+
const evaluation = evaluateProductionFloor(record, floorReadiness, { now, freshnessMinutes })
|
|
158
|
+
return evaluation.ok
|
|
159
|
+
? { ok: true, record, floorReadiness, failed: [] }
|
|
160
|
+
: { ok: false, record, floorReadiness, failed: [evaluation.id], detail: evaluation.detail }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function revalidateProductionEndpoints(stored, {
|
|
164
|
+
canonicalProductionUrl, cloudRunServiceUrl, now = Date.now(),
|
|
165
|
+
} = {}) {
|
|
166
|
+
const storedValidation = validateProductionEndpointRecord(stored)
|
|
167
|
+
if (!storedValidation.ok) return { ...storedValidation, detail: `resume requires a valid preflight endpoint record: ${storedValidation.detail}` }
|
|
168
|
+
let current
|
|
169
|
+
try { current = endpointRecord(canonicalProductionUrl, cloudRunServiceUrl, now) } catch (error) {
|
|
170
|
+
return { ok: false, id: "endpoint-read-invalid", detail: error.message }
|
|
171
|
+
}
|
|
172
|
+
if (stored.configurationFingerprint !== current.configurationFingerprint) {
|
|
173
|
+
return { ok: false, id: "endpoint-configuration-changed", previous: stored, current }
|
|
174
|
+
}
|
|
175
|
+
return { ok: true, id: null, previous: stored, current }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function verifyCanonicalProductionTarget(record, target) {
|
|
179
|
+
const recordValidation = validateProductionEndpointRecord(record)
|
|
180
|
+
if (!recordValidation.ok) return recordValidation
|
|
181
|
+
let normalized
|
|
182
|
+
try { normalized = normalizeProductionUrl(target) } catch {
|
|
183
|
+
return { ok: false, id: "canonical-production-target-mismatch" }
|
|
184
|
+
}
|
|
185
|
+
if (normalized !== record.canonicalProductionUrl) {
|
|
186
|
+
return { ok: false, id: "canonical-production-target-mismatch", expected: record.canonicalProductionUrl, actual: normalized }
|
|
187
|
+
}
|
|
188
|
+
return { ok: true, target: normalized }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function runCommand(command) {
|
|
192
|
+
try {
|
|
193
|
+
const stdout = execFileSync('/bin/sh', ['-c', command], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 60_000 })
|
|
194
|
+
const lines = stdout.split("\n").map((line) => line.trim()).filter(Boolean)
|
|
195
|
+
if (lines.length !== 1) return { ok: false, detail: `command must emit exactly one URL line (got ${lines.length})` }
|
|
196
|
+
return { ok: true, value: lines[0] }
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return { ok: false, detail: error.stderr?.toString?.().trim() || error.message }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function readJson(file, label) {
|
|
203
|
+
try { return { ok: true, value: JSON.parse(fs.readFileSync(file, "utf8")) } } catch (error) {
|
|
204
|
+
return { ok: false, detail: `${label} is unreadable or invalid JSON: ${error.message}` }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const VALUE_FLAGS = new Set([
|
|
209
|
+
"canonical-url-command", "service-url-command", "floor-verdict", "manifest", "target",
|
|
210
|
+
"now", "freshness-minutes",
|
|
211
|
+
])
|
|
212
|
+
|
|
213
|
+
function parseArgs(argv) {
|
|
214
|
+
const opts = {}, positional = [], errors = []
|
|
215
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
216
|
+
const arg = argv[i]
|
|
217
|
+
if (arg === "--help" || arg === "-h") opts.help = true
|
|
218
|
+
else if (arg.startsWith("--")) {
|
|
219
|
+
const key = arg.slice(2)
|
|
220
|
+
if (!VALUE_FLAGS.has(key)) { errors.push(`unknown flag ${arg}`); continue }
|
|
221
|
+
const value = argv[++i]
|
|
222
|
+
if (value == null || value.startsWith("--")) {
|
|
223
|
+
errors.push(`missing value for ${arg}`)
|
|
224
|
+
if (value != null) i -= 1
|
|
225
|
+
} else opts[key] = value
|
|
226
|
+
} else positional.push(arg)
|
|
227
|
+
}
|
|
228
|
+
return { command: positional[0], opts, extra: positional.slice(1), errors }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function help() {
|
|
232
|
+
return `production-endpoints — decide production floor applicability before RC mint.
|
|
233
|
+
|
|
234
|
+
Usage:
|
|
235
|
+
production-endpoints preflight --canonical-url-command <cmd> --service-url-command <cmd> [--floor-verdict <json>]
|
|
236
|
+
production-endpoints resume --manifest <json> --canonical-url-command <cmd> --service-url-command <cmd>
|
|
237
|
+
production-endpoints verify-live --manifest <json> --target <url>
|
|
238
|
+
production-endpoints live-target --manifest <json>
|
|
239
|
+
|
|
240
|
+
The canonical command reads the deploy trigger configured production URL (for example
|
|
241
|
+
_APP_BASE_URL). The service command independently reads the active Cloud Run service URL.
|
|
242
|
+
A distinct hostname requires a fresh read-only prod-active ALIVE verdict on the canonical
|
|
243
|
+
URL. Equal hostnames produce a reasoned Cloud Run-only skip. Resume re-runs both reads.
|
|
244
|
+
|
|
245
|
+
Output from preflight is a run-state merge patch. Record it before candidate deployment.
|
|
246
|
+
|
|
247
|
+
Exit: 0 pass; 1 hold/configuration changed; 2 usage or unreadable input.`
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function loadManifest(file) {
|
|
251
|
+
const loaded = readJson(file, "manifest")
|
|
252
|
+
if (!loaded.ok) return loaded
|
|
253
|
+
if (loaded.value?.skill !== "resolve-release") return { ok: false, detail: "manifest is not a resolve-release run" }
|
|
254
|
+
return loaded
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function main(argv) {
|
|
258
|
+
const { command, opts, extra, errors } = parseArgs(argv)
|
|
259
|
+
if (opts.help || command === "help") { console.log(help()); return 0 }
|
|
260
|
+
if (errors.length || extra.length || !["preflight", "resume", "verify-live", "live-target"].includes(command)) {
|
|
261
|
+
if (errors.length) console.error(`production-endpoints: ${errors.join("; ")}`)
|
|
262
|
+
console.error(help())
|
|
263
|
+
return 2
|
|
264
|
+
}
|
|
265
|
+
const now = opts.now ? Date.parse(opts.now) : Date.now()
|
|
266
|
+
if (!Number.isFinite(now)) { console.error("production-endpoints: --now must be an ISO timestamp"); return 2 }
|
|
267
|
+
|
|
268
|
+
if (command === "live-target") {
|
|
269
|
+
if (!opts.manifest) { console.error(help()); return 2 }
|
|
270
|
+
const manifest = loadManifest(opts.manifest)
|
|
271
|
+
if (!manifest.ok) { console.error(`production-endpoints: ${manifest.detail}`); return 2 }
|
|
272
|
+
const record = manifest.value.release?.productionEndpoints
|
|
273
|
+
const result = verifyCanonicalProductionTarget(record, record?.canonicalProductionUrl)
|
|
274
|
+
if (!result.ok) { console.log(JSON.stringify(result, null, 2)); return 1 }
|
|
275
|
+
console.log(result.target)
|
|
276
|
+
return 0
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (command === "verify-live") {
|
|
280
|
+
if (!opts.manifest || !opts.target) { console.error(help()); return 2 }
|
|
281
|
+
const manifest = loadManifest(opts.manifest)
|
|
282
|
+
if (!manifest.ok) { console.error(`production-endpoints: ${manifest.detail}`); return 2 }
|
|
283
|
+
const result = verifyCanonicalProductionTarget(manifest.value.release?.productionEndpoints, opts.target)
|
|
284
|
+
console.log(JSON.stringify(result, null, 2))
|
|
285
|
+
return result.ok ? 0 : 1
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!opts["canonical-url-command"] || !opts["service-url-command"]) { console.error(help()); return 2 }
|
|
289
|
+
const canonical = runCommand(opts["canonical-url-command"])
|
|
290
|
+
const service = runCommand(opts["service-url-command"])
|
|
291
|
+
if (!canonical.ok || !service.ok) {
|
|
292
|
+
console.error(`production-endpoints: endpoint read failed: ${canonical.detail || service.detail}`)
|
|
293
|
+
return 1
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (command === "resume") {
|
|
297
|
+
if (!opts.manifest) { console.error(help()); return 2 }
|
|
298
|
+
const manifest = loadManifest(opts.manifest)
|
|
299
|
+
if (!manifest.ok) { console.error(`production-endpoints: ${manifest.detail}`); return 2 }
|
|
300
|
+
const result = revalidateProductionEndpoints(manifest.value.release?.productionEndpoints, {
|
|
301
|
+
canonicalProductionUrl: canonical.value, cloudRunServiceUrl: service.value, now,
|
|
302
|
+
})
|
|
303
|
+
console.log(JSON.stringify(result, null, 2))
|
|
304
|
+
return result.ok ? 0 : 1
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let floorVerdict = null
|
|
308
|
+
if (opts["floor-verdict"]) {
|
|
309
|
+
const loaded = readJson(opts["floor-verdict"], "floor verdict")
|
|
310
|
+
if (!loaded.ok) { console.error(`production-endpoints: ${loaded.detail}`); return 2 }
|
|
311
|
+
floorVerdict = loaded.value
|
|
312
|
+
}
|
|
313
|
+
const freshnessMinutes = opts["freshness-minutes"] == null ? DEFAULT_FLOOR_FRESHNESS_MINUTES : Number(opts["freshness-minutes"])
|
|
314
|
+
if (!usableFreshness(freshnessMinutes)) { console.error("production-endpoints: --freshness-minutes must be positive"); return 2 }
|
|
315
|
+
const result = buildProductionEndpointRecord({
|
|
316
|
+
canonicalProductionUrl: canonical.value, cloudRunServiceUrl: service.value,
|
|
317
|
+
floorVerdict, now, freshnessMinutes,
|
|
318
|
+
})
|
|
319
|
+
if (!result.ok) { console.log(JSON.stringify(result, null, 2)); return 1 }
|
|
320
|
+
console.log(JSON.stringify({ release: {
|
|
321
|
+
productionEndpoints: result.record, floorReadiness: result.floorReadiness,
|
|
322
|
+
} }, null, 2))
|
|
323
|
+
return 0
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (isMainModule(import.meta.url)) process.exitCode = main(process.argv.slice(2))
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// rc-circuit-breaker — hold repeated release-tooling failures behind a circuit breaker (#962).
|
|
2
|
+
//
|
|
3
|
+
// WHY THIS EXISTS. Forex v0.1.12 minted NINE immutable RCs. After RC.2 and RC.3, two healthy
|
|
4
|
+
// candidates had reached 100% and been rolled back because the lane's own verifier misread valid
|
|
5
|
+
// platform state — and nothing stopped the larger loop in which tooling-only retries kept
|
|
6
|
+
// generating new production attempts. Each attempt's fail-closed behavior was correct; what was
|
|
7
|
+
// missing was a breaker over the LOOP: after enough failures owned by the release tooling or the
|
|
8
|
+
// gate environment, the next immutable RC is held for explicit release-design review instead of
|
|
9
|
+
// being minted on autopilot.
|
|
10
|
+
//
|
|
11
|
+
// WHAT IT READS. The durable evidence bundles only — the same immutable record
|
|
12
|
+
// version-postmortem.mjs aggregates — via `discoverVersionBundles`, classified by #961's
|
|
13
|
+
// `classifyFailureOwner`. It never reads a live service, never a run manifest, and it NEVER
|
|
14
|
+
// writes: the check is read-only over the durable dir, so a hold deletes nothing, moves no tag,
|
|
15
|
+
// and burns no version (the issue's AC). The release lane runs it at step 3, BEFORE
|
|
16
|
+
// `version.mjs rc` mints the next immutable RC (references/candidate.md), and records the
|
|
17
|
+
// decision JSON on the manifest (`release.circuitBreaker`) so the post-mortem can report whether
|
|
18
|
+
// the breaker fired and how it was resolved.
|
|
19
|
+
//
|
|
20
|
+
// THE COUNT. Prior NON-RELEASED attempts for the same proposed version whose normalized failure
|
|
21
|
+
// owner is `release-tooling` or `gate-environment` (BREAKER_OWNERS — the two lane-side owners of
|
|
22
|
+
// #961's taxonomy). Application, configuration and platform failures are real failures with
|
|
23
|
+
// their own routes; they are reported in `ownerCounts` but never consume this threshold, because
|
|
24
|
+
// this breaker exists for exactly one signal: the lane's own machinery failing repeatedly.
|
|
25
|
+
// `released` is not a failure; `revoked`/`blocked-manual` classify to no owner and never count.
|
|
26
|
+
//
|
|
27
|
+
// THE THRESHOLD. Default 2 prior qualifying failures (the issue's approved default).
|
|
28
|
+
// Configurable: `--threshold <n>` beats `$RC_CIRCUIT_BREAKER_THRESHOLD` beats the default, and
|
|
29
|
+
// the decision records which source set it — configuration is DECLARED IN THE RECORD, never
|
|
30
|
+
// silent. What no flag or env var can do is skip the check: the only way past a hold is the
|
|
31
|
+
// recorded human authorization below.
|
|
32
|
+
//
|
|
33
|
+
// THE HOLD. Crossing the threshold returns `hold-for-release-design-review` (exit 1) with the
|
|
34
|
+
// count, the per-owner breakdown, the qualifying attempt ids, their phases, and the bundle files
|
|
35
|
+
// as evidence links. Fail-closed both ways: an unreadable dir or an unparseable bundle is a
|
|
36
|
+
// refusal (exit 2, nothing on stdout) — the same reading evidence-bundle.mjs's mint takes —
|
|
37
|
+
// never an empty history.
|
|
38
|
+
//
|
|
39
|
+
// CONTINUING PAST A HOLD is a human's explicit, recorded decision — not an env bypass. The
|
|
40
|
+
// authorization is a structured JSON record (`--authorization <file>`):
|
|
41
|
+
// {
|
|
42
|
+
// "version": "<the proposed version>", // must match the check's version
|
|
43
|
+
// "againstCount": <current qualifyingCount>, // binds the record to THIS count: one
|
|
44
|
+
// // authorization covers exactly ONE
|
|
45
|
+
// // continuation; a further qualifying
|
|
46
|
+
// // failure re-holds and the stale record
|
|
47
|
+
// // does not carry
|
|
48
|
+
// "authorizedBy": "<the human>",
|
|
49
|
+
// "authorizedAt": "<ISO timestamp>",
|
|
50
|
+
// "rationale": "<why another RC is preferable to simplifying, removing, or externally
|
|
51
|
+
// repairing the gate>",
|
|
52
|
+
// "alternativesConsidered": { "simplify": …, "remove": …, "externalRepair": … } // optional
|
|
53
|
+
// }
|
|
54
|
+
// A valid record turns the hold into `authorized-continue` (exit 0) echoing the record, which
|
|
55
|
+
// the lane merges onto the manifest. An invalid or stale record leaves the hold standing and
|
|
56
|
+
// names why (`authorizationError`).
|
|
57
|
+
|
|
58
|
+
import fs from 'node:fs'
|
|
59
|
+
|
|
60
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
61
|
+
import {
|
|
62
|
+
FAILURE_OWNERS,
|
|
63
|
+
classifyFailureOwner,
|
|
64
|
+
classifyFailurePhase,
|
|
65
|
+
discoverVersionBundles,
|
|
66
|
+
} from './version-postmortem.mjs'
|
|
67
|
+
|
|
68
|
+
// The contract surface. The two lane-side owners, the default of two, the env var name, and the
|
|
69
|
+
// decision strings are what the doctrine (references/candidate.md, references/postmortem.md)
|
|
70
|
+
// states — changing any of them is a breaking change to that contract.
|
|
71
|
+
export const BREAKER_OWNERS = ['release-tooling', 'gate-environment']
|
|
72
|
+
export const DEFAULT_THRESHOLD = 2
|
|
73
|
+
export const THRESHOLD_ENV = 'RC_CIRCUIT_BREAKER_THRESHOLD'
|
|
74
|
+
export const HOLD_DECISION = 'hold-for-release-design-review'
|
|
75
|
+
export const PROCEED_DECISION = 'proceed'
|
|
76
|
+
export const AUTHORIZED_DECISION = 'authorized-continue'
|
|
77
|
+
|
|
78
|
+
const nonEmpty = (v) => typeof v === 'string' && v.trim() !== ''
|
|
79
|
+
|
|
80
|
+
// Flag beats env beats default; an invalid value from any source is a refusal, never a silent
|
|
81
|
+
// fall-back — a threshold nobody can account for is the check not running.
|
|
82
|
+
export function resolveThreshold({ flag = null, env = process.env } = {}) {
|
|
83
|
+
const parse = (raw, source) => {
|
|
84
|
+
const n = Number(raw)
|
|
85
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
86
|
+
throw new Error(`rc-circuit-breaker: invalid threshold ${JSON.stringify(raw)} from ${source} — an integer >= 1 is required`)
|
|
87
|
+
}
|
|
88
|
+
return { threshold: n, thresholdSource: source }
|
|
89
|
+
}
|
|
90
|
+
if (flag != null) return parse(flag, 'flag')
|
|
91
|
+
if (nonEmpty(env[THRESHOLD_ENV])) return parse(env[THRESHOLD_ENV], 'env')
|
|
92
|
+
return { threshold: DEFAULT_THRESHOLD, thresholdSource: 'default' }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// The evaluation: count prior non-released attempts for this proposed version by normalized
|
|
96
|
+
// failure owner, and decide. THROWS on an unreadable dir or an unparseable bundle — the caller
|
|
97
|
+
// (CLI) turns that into the exit-2 refusal; a gate that cannot establish the count does not get
|
|
98
|
+
// to answer "below threshold".
|
|
99
|
+
export function evaluateRcCircuitBreaker({ dir, version, threshold = DEFAULT_THRESHOLD, thresholdSource = 'default' } = {}) {
|
|
100
|
+
const { attempts, unreadable } = discoverVersionBundles(dir, version)
|
|
101
|
+
if (unreadable.length) {
|
|
102
|
+
throw new Error(`cannot establish the failure history for ${version} — ${unreadable.length} durable bundle(s) do not parse (${unreadable.join('; ')}). Repair the record; a gate that cannot read the history does not get to proceed.`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const rows = attempts.map((a) => {
|
|
106
|
+
const failure = classifyFailureOwner(a.bundle)
|
|
107
|
+
return {
|
|
108
|
+
attemptId: a.attemptId,
|
|
109
|
+
ordinal: a.ordinal,
|
|
110
|
+
terminalState: a.terminalState,
|
|
111
|
+
failureOwner: failure?.owner ?? null,
|
|
112
|
+
failureBasis: failure?.basis ?? null,
|
|
113
|
+
failurePhase: classifyFailurePhase(a.bundle),
|
|
114
|
+
evidence: a.file,
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const ownerCounts = Object.fromEntries(FAILURE_OWNERS.map((o) => [o, 0]))
|
|
119
|
+
for (const r of rows) if (r.failureOwner) ownerCounts[r.failureOwner] += 1
|
|
120
|
+
const qualifying = rows.filter((r) => r.failureOwner && BREAKER_OWNERS.includes(r.failureOwner))
|
|
121
|
+
|
|
122
|
+
const held = qualifying.length >= threshold
|
|
123
|
+
return {
|
|
124
|
+
kind: 'rc-circuit-breaker',
|
|
125
|
+
version: String(version),
|
|
126
|
+
threshold,
|
|
127
|
+
thresholdSource,
|
|
128
|
+
countedOwners: BREAKER_OWNERS,
|
|
129
|
+
attempts: rows.length,
|
|
130
|
+
nonReleased: rows.filter((r) => r.terminalState !== 'released').length,
|
|
131
|
+
ownerCounts,
|
|
132
|
+
qualifyingCount: qualifying.length,
|
|
133
|
+
qualifying,
|
|
134
|
+
decision: held ? HOLD_DECISION : PROCEED_DECISION,
|
|
135
|
+
decisionBasis: held
|
|
136
|
+
? `${qualifying.length} prior non-released attempt(s) of ${version} owned by ${BREAKER_OWNERS.join('/')} reach the configured threshold ${threshold} — the loop #962 exists to stop`
|
|
137
|
+
: `${qualifying.length} prior non-released attempt(s) of ${version} owned by ${BREAKER_OWNERS.join('/')}, below the configured threshold ${threshold}`,
|
|
138
|
+
continue: held
|
|
139
|
+
? 'Minting the next immutable RC requires an explicit human authorization (--authorization <file>) recording why another RC is preferable to simplifying, removing, or externally repairing the gate; record this decision JSON at release.circuitBreaker on the manifest. The hold deletes nothing, moves no tag, and burns no version.'
|
|
140
|
+
: null,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Validates the human's continuation record against the CURRENT evaluation. `againstCount`
|
|
145
|
+
// binding is the anti-silent-carry rule: the record authorizes one continuation past exactly the
|
|
146
|
+
// count it was written against, so a further qualifying failure re-holds instead of inheriting
|
|
147
|
+
// yesterday's decision.
|
|
148
|
+
export function validateAuthorization(record, evaluation) {
|
|
149
|
+
const errors = []
|
|
150
|
+
const isObj = record != null && typeof record === 'object' && !Array.isArray(record)
|
|
151
|
+
if (!isObj) return { valid: false, errors: ['authorization must be a JSON object'] }
|
|
152
|
+
if (!nonEmpty(record.authorizedBy)) errors.push('authorizedBy is required (the human authorizing the continuation)')
|
|
153
|
+
if (!nonEmpty(record.authorizedAt) || !Number.isFinite(Date.parse(record.authorizedAt))) errors.push('authorizedAt is required and must be an ISO timestamp')
|
|
154
|
+
if (!nonEmpty(record.rationale)) errors.push('rationale is required — why another RC is preferable to simplifying, removing, or externally repairing the gate')
|
|
155
|
+
if (!nonEmpty(record.version)) errors.push('version is required')
|
|
156
|
+
else if (evaluation && record.version.trim().replace(/^v/, '') !== String(evaluation.version).replace(/^v/, '')) {
|
|
157
|
+
errors.push(`version ${record.version} does not match the held version ${evaluation.version}`)
|
|
158
|
+
}
|
|
159
|
+
if (!Number.isInteger(record.againstCount)) errors.push('againstCount is required (the qualifying count this record was written against)')
|
|
160
|
+
else if (evaluation && record.againstCount !== evaluation.qualifyingCount) {
|
|
161
|
+
errors.push(`againstCount ${record.againstCount} is stale — the current qualifying count is ${evaluation.qualifyingCount}; one authorization covers exactly one continuation`)
|
|
162
|
+
}
|
|
163
|
+
return { valid: errors.length === 0, errors }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ---- CLI ----
|
|
167
|
+
|
|
168
|
+
const USAGE = `rc-circuit-breaker — the release-lane circuit breaker for repeated tooling failures (#962).
|
|
169
|
+
|
|
170
|
+
Run BEFORE minting the next immutable RC (step 3, references/candidate.md). Counts prior
|
|
171
|
+
non-released attempts for the proposed version whose normalized failure owner is release-tooling
|
|
172
|
+
or gate-environment (#961's taxonomy), and holds when the count reaches the threshold.
|
|
173
|
+
|
|
174
|
+
Usage:
|
|
175
|
+
rc-circuit-breaker check --dir <durable-evidence-dir> --version <v> [--threshold <n>] [--authorization <file.json>]
|
|
176
|
+
|
|
177
|
+
--threshold <n> qualifying failures that trip the hold (default ${DEFAULT_THRESHOLD};
|
|
178
|
+
env ${THRESHOLD_ENV}; flag beats env beats default; the source is
|
|
179
|
+
recorded in the decision). No flag or env var skips the check.
|
|
180
|
+
--authorization <f> a structured human authorization (see references/postmortem.md):
|
|
181
|
+
version, againstCount, authorizedBy, authorizedAt, rationale. Valid
|
|
182
|
+
only against the CURRENT count — one authorization, one continuation.
|
|
183
|
+
|
|
184
|
+
Exit codes:
|
|
185
|
+
0 proceed (below threshold) or authorized-continue (record valid) — decision JSON on stdout
|
|
186
|
+
1 hold-for-release-design-review — decision JSON on stdout, with count, owners, attempt ids
|
|
187
|
+
and evidence files
|
|
188
|
+
2 cannot evaluate (usage, unreadable dir, unparseable bundle, invalid threshold) — stderr
|
|
189
|
+
only; fail closed, nothing is minted
|
|
190
|
+
|
|
191
|
+
The check is read-only: a hold deletes nothing, moves no immutable RC tag, and burns no version.`
|
|
192
|
+
|
|
193
|
+
function parseArgs(argv) {
|
|
194
|
+
const opts = { _: [] }
|
|
195
|
+
for (let i = 0; i < argv.length; i++) {
|
|
196
|
+
const a = argv[i]
|
|
197
|
+
if (a === '--help' || a === '-h') opts.help = true
|
|
198
|
+
else if (a.startsWith('--')) opts[a.slice(2)] = argv[++i]
|
|
199
|
+
else opts._.push(a)
|
|
200
|
+
}
|
|
201
|
+
return opts
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function main(argv, env = process.env) {
|
|
205
|
+
const opts = parseArgs(argv)
|
|
206
|
+
if (opts.help) {
|
|
207
|
+
console.log(USAGE)
|
|
208
|
+
return 0
|
|
209
|
+
}
|
|
210
|
+
const [cmd] = opts._
|
|
211
|
+
if (cmd !== 'check' || !nonEmpty(opts.dir) || !nonEmpty(opts.version)) {
|
|
212
|
+
console.error('rc-circuit-breaker: usage: check --dir <durable-evidence-dir> --version <v> are all required (see --help)')
|
|
213
|
+
return 2
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let resolved
|
|
217
|
+
try {
|
|
218
|
+
resolved = resolveThreshold({ flag: opts.threshold ?? null, env })
|
|
219
|
+
} catch (e) {
|
|
220
|
+
console.error(e.message)
|
|
221
|
+
return 2
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let evaluation
|
|
225
|
+
try {
|
|
226
|
+
evaluation = evaluateRcCircuitBreaker({
|
|
227
|
+
dir: opts.dir,
|
|
228
|
+
version: opts.version,
|
|
229
|
+
threshold: resolved.threshold,
|
|
230
|
+
thresholdSource: resolved.thresholdSource,
|
|
231
|
+
})
|
|
232
|
+
} catch (e) {
|
|
233
|
+
console.error(`rc-circuit-breaker: ${e.message}`)
|
|
234
|
+
return 2
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (evaluation.decision !== HOLD_DECISION) {
|
|
238
|
+
process.stdout.write(JSON.stringify(evaluation, null, 2) + '\n')
|
|
239
|
+
return 0
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// The hold stands unless a valid human authorization is presented. A malformed or stale one
|
|
243
|
+
// never turns into a proceed — it is named and the hold stands.
|
|
244
|
+
if (opts.authorization != null) {
|
|
245
|
+
let record = null
|
|
246
|
+
try {
|
|
247
|
+
record = JSON.parse(fs.readFileSync(opts.authorization, 'utf8'))
|
|
248
|
+
} catch (e) {
|
|
249
|
+
process.stdout.write(JSON.stringify({ ...evaluation, authorizationError: `authorization file unreadable: ${e.message}` }, null, 2) + '\n')
|
|
250
|
+
return 1
|
|
251
|
+
}
|
|
252
|
+
const v = validateAuthorization(record, evaluation)
|
|
253
|
+
if (v.valid) {
|
|
254
|
+
process.stdout.write(JSON.stringify({
|
|
255
|
+
...evaluation,
|
|
256
|
+
decision: AUTHORIZED_DECISION,
|
|
257
|
+
decisionBasis: `${evaluation.decisionBasis} — continued under a recorded human authorization`,
|
|
258
|
+
authorization: record,
|
|
259
|
+
}, null, 2) + '\n')
|
|
260
|
+
return 0
|
|
261
|
+
}
|
|
262
|
+
process.stdout.write(JSON.stringify({ ...evaluation, authorizationError: v.errors.join('; ') }, null, 2) + '\n')
|
|
263
|
+
return 1
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
process.stdout.write(JSON.stringify(evaluation, null, 2) + '\n')
|
|
267
|
+
return 1
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (isMainModule(import.meta.url)) {
|
|
271
|
+
process.exitCode = main(process.argv.slice(2))
|
|
272
|
+
}
|