@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,599 @@
|
|
|
1
|
+
// green-gate — the mechanized unattended-promotion gate for the release lane (issue #405).
|
|
2
|
+
//
|
|
3
|
+
// `auto-when-green` (references/auto-when-green.md) lets a promotion run unattended, but
|
|
4
|
+
// only behind a real gate. Today an agent evaluates that gate's (a)-(e) conditions by
|
|
5
|
+
// reading prose, at the single most dangerous point in the system: an unattended
|
|
6
|
+
// production traffic shift with no human present. This script makes each condition a
|
|
7
|
+
// DECIDABLE ARTIFACT CHECK that FAILS CLOSED and NAMES the failing condition — so the
|
|
8
|
+
// green light is a comparison against a recorded fact, never a claim the agent makes
|
|
9
|
+
// about its own prior work.
|
|
10
|
+
//
|
|
11
|
+
// The doc has two authorization points, so this has two subcommands:
|
|
12
|
+
// green-gate.mjs promote --manifest <run-manifest.json>
|
|
13
|
+
// Gates the promotion + rc mint on (a)-(d) plus the "force the human even when
|
|
14
|
+
// green" carve-outs (major bump, rollback not armed, irreversible content).
|
|
15
|
+
// green-gate.mjs shift --manifest <run-manifest.json> --verdict <smoke-verdict.json>
|
|
16
|
+
// Gates the traffic shift on (e): the candidate's smoke verdict is ALIVE, on the
|
|
17
|
+
// EXACT candidate URL, was a --no-mutations run, and is still INSIDE the envelope's
|
|
18
|
+
// freshness window. This is the crux — "on that exact candidate" is a compared field
|
|
19
|
+
// (`target === candidateUrl`), never asserted; and "recently enough" is a compared
|
|
20
|
+
// field too (`generatedAt` vs `envelope.verdictFreshnessMinutes`), because the
|
|
21
|
+
// authorization this gate runs after can arrive hours after the validation.
|
|
22
|
+
//
|
|
23
|
+
// Contract:
|
|
24
|
+
// exit 0 = FIRE — every condition holds and no carve-out triggered.
|
|
25
|
+
// exit 1 = HOLD — fall back to a per-step human confirm; the SPECIFIC failing
|
|
26
|
+
// condition ids are named in `failed`.
|
|
27
|
+
// exit 2 = usage / unreadable-input error — bad flags, unknown subcommand, or a
|
|
28
|
+
// manifest that is absent / unparseable / not a resolve-release run.
|
|
29
|
+
//
|
|
30
|
+
// FAIL CLOSED is the invariant: EVERY missing / malformed / wrong-shape condition input
|
|
31
|
+
// is a HOLD, never a FIRE. A missing smoke-verdict.json HOLDs. A `release` field the gate
|
|
32
|
+
// needs but the manifest does not carry HOLDs and is named. The only inputs that produce
|
|
33
|
+
// exit 2 (not HOLD) are the ones that stop the gate from running at all: the CLI flags
|
|
34
|
+
// and the manifest file itself.
|
|
35
|
+
//
|
|
36
|
+
// WHAT A HOLD MEANS, doctrinally (principle 13, issue #439). The behaviour above was always
|
|
37
|
+
// right and was nowhere written down, which left the reading of it to whoever hit it: an
|
|
38
|
+
// APPLICABLE CONDITION WHOSE INPUT COULD NOT BE PRODUCED IS A FAILING CONDITION, NOT A
|
|
39
|
+
// MISSING ONE. The credential the readiness check needed was absent, the service it drives
|
|
40
|
+
// was down, the fixture had drifted — none of that is a step to note and continue past, and
|
|
41
|
+
// none of it is an `n/a` (which says the condition does not apply here, and is decided). So
|
|
42
|
+
// the gate never distinguishes "nobody filled this in" from "nobody could": it cannot, and
|
|
43
|
+
// neither is a result.
|
|
44
|
+
//
|
|
45
|
+
// AND THE HOLD IS SCOPED. It blocks THIS release — this SHA does not promote, this candidate
|
|
46
|
+
// does not take traffic. It is not a reason to pause the merge queue, dequeue an unrelated
|
|
47
|
+
// PR, or lift a branch protection: a fail-closed mechanism that also freezes the repair path
|
|
48
|
+
// is the one people bypass, and a bypassed gate has stopped existing without anyone deciding
|
|
49
|
+
// to remove it. The fix to the harness, the fixture or the credential is enqueued like any
|
|
50
|
+
// other unit and lands the way everything else lands.
|
|
51
|
+
//
|
|
52
|
+
// Every condition is read from an ARTIFACT the release lane already records:
|
|
53
|
+
// - the run manifest — resolve-issues' scripts/run-state.mjs, release variant
|
|
54
|
+
// (references/../resolve-issues/references/run-state.md): `envelope` (the pre-flight
|
|
55
|
+
// authorization block) and `release` (the version/candidate/CI record).
|
|
56
|
+
// - smoke-verdict.json — the smoke skill's verdict output (verdict/target/noMutations/
|
|
57
|
+
// generatedAt, all stamped by smoke's stampTarget).
|
|
58
|
+
// - version.mjs plan's recorded output, carried in `release.versionPlan`
|
|
59
|
+
// ({ bump, collision, ... }) — the gate trusts the plan computed at step 1b rather
|
|
60
|
+
// than re-running git, so the gate reads the same fact the human saw at the cut.
|
|
61
|
+
//
|
|
62
|
+
// NOTE ON THE MANIFEST SCHEMA. run-state.mjs validates the release variant LOOSELY (only
|
|
63
|
+
// `envelope` presence and the `step` enum). The `release.*` and `envelope.*` fields this
|
|
64
|
+
// gate reads are therefore not yet formally in that schema; the gate reads them from the
|
|
65
|
+
// raw JSON and fails closed on any it does not find. `envelope.reversibilityConfirmed` in
|
|
66
|
+
// particular is a field the pre-flight gate does not record today — until it does, the
|
|
67
|
+
// irreversible-content carve-out HOLDs (the safe default: force the human), rather than
|
|
68
|
+
// silently passing an unverifiable carve-out. See the PR body for the full field list.
|
|
69
|
+
|
|
70
|
+
import fs from 'node:fs'
|
|
71
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
72
|
+
import { normalizeReleaseVerdict } from '../../resolve-issues/scripts/release-state-contract.mjs'
|
|
73
|
+
import { evaluateProductionFloor } from './production-endpoints.mjs'
|
|
74
|
+
|
|
75
|
+
// The conservative default freshness window for the step-4a verdict, in minutes (issue #431).
|
|
76
|
+
//
|
|
77
|
+
// (e) bounds WHICH candidate the evidence covers; it did not bound how OLD that evidence is,
|
|
78
|
+
// and step 4b's shape is "validate at 0% → wait for an authorization → shift" with nothing
|
|
79
|
+
// bounding the wait. A four-hour-old ALIVE describes coordinates that may have moved since:
|
|
80
|
+
// a newer build may hold the `candidate` tag, the previous revision may no longer hold 100%.
|
|
81
|
+
// So the shift gate reads the verdict's `generatedAt` stamp and refuses evidence older than
|
|
82
|
+
// the window the envelope declared (pre-flight item 3), defaulting to this when it declared
|
|
83
|
+
// none. Conservative on purpose — the default governs an unattended production traffic shift
|
|
84
|
+
// with no human present, so it must read evidence minted minutes ago, not hours; a release
|
|
85
|
+
// that legitimately needs a longer wait declares the longer window in the envelope, where a
|
|
86
|
+
// human saw it. Expiry routes back to step 4a (re-validate the candidate), not to the shift.
|
|
87
|
+
export const DEFAULT_VERDICT_FRESHNESS_MINUTES = 30
|
|
88
|
+
|
|
89
|
+
// ---- condition helpers (pure) ----
|
|
90
|
+
|
|
91
|
+
const isGreen = (v) => v === 'green'
|
|
92
|
+
|
|
93
|
+
// (b)'s SHA-keyed integration smoke sub-check. The integration environment may or may not
|
|
94
|
+
// run a SHA-keyed recorder (principle 11): the manifest declares which. An explicit
|
|
95
|
+
// "not-recorded" waives the sub-check; a recorded verdict must be ALIVE and on the EXACT
|
|
96
|
+
// promoted SHA (a verdict from another SHA is not evidence for this one). Anything else —
|
|
97
|
+
// absent, or a malformed value — fails closed: the presence of a recorder must be an
|
|
98
|
+
// explicit statement, never an inference from a missing field.
|
|
99
|
+
function integrationSmokeGreen(smoke, candidateSha) {
|
|
100
|
+
if (smoke === 'not-recorded') return true
|
|
101
|
+
if (!smoke || typeof smoke !== 'object') return false
|
|
102
|
+
// Require a real SHA match, not merely equal fields: an absent candidateSha and a
|
|
103
|
+
// sha-less verdict are both `undefined`, and `undefined === undefined` would fail OPEN —
|
|
104
|
+
// firing production on "a verdict for no particular SHA." A truthy-string match on both
|
|
105
|
+
// sides is the fail-closed form.
|
|
106
|
+
return (
|
|
107
|
+
smoke.verdict === 'ALIVE' &&
|
|
108
|
+
typeof candidateSha === 'string' &&
|
|
109
|
+
candidateSha.length > 0 &&
|
|
110
|
+
smoke.sha === candidateSha
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// (c)'s evidence must BE a CHECKABLE LOCATOR, not a prose sentence that merely CONTAINS a
|
|
115
|
+
// locator-shaped token. An unattended production-promotion gate must not accept "looks good"
|
|
116
|
+
// as proof that a readiness row passed — it accepts only evidence a second reader could
|
|
117
|
+
// independently re-open. This is the coverage-checklist.mjs / review-contract evidence
|
|
118
|
+
// convention (ensure-coverage), widened to the forms a release-readiness row actually cites.
|
|
119
|
+
//
|
|
120
|
+
// THE ANCHORING RULE (why containment was a fail-OPEN). Every form below is anchored at `^`:
|
|
121
|
+
// the evidence must PRESENT as a locator — it must wholly BE one, or LEAD with one, with a
|
|
122
|
+
// trailing NOTE (whitespace/punctuation + free text) allowed after the complete token. A
|
|
123
|
+
// locator-shaped token buried inside a prose sentence is NO LONGER sufficient, because that
|
|
124
|
+
// containment let realistic prose fire the gate: a date ("verified 20260721") and a big number
|
|
125
|
+
// ("ran 1000000 iterations") are valid [0-9a-f]{7,40} SHAs; an all-hex English word ("acceded",
|
|
126
|
+
// "defaced") is too; a dotted abbreviation ("e.g.", "i.e.", "U.S."), a glued sentence ("wrapped
|
|
127
|
+
// up.Next steps"), and an email TLD ("a@b.com") all carry a "filename.ext"-shaped substring.
|
|
128
|
+
// Anchoring means only the LEADING token can qualify, so none of those pass. Accepted anchored
|
|
129
|
+
// forms (the trailing-note allowance, issue #412 fix 3, is IDENTICAL for every one — a complete
|
|
130
|
+
// token then end / whitespace / punctuation [ , ; ) ] } ] then any note; '.' is excluded so a
|
|
131
|
+
// leading abbreviation never reads as "file.ext" + a period note):
|
|
132
|
+
// - a file path with a REAL extension docs/release/checklist.md, run-state.mjs
|
|
133
|
+
// and an optional :line / :line-range path/to/file.ext:123 (or :12-20), then the tail.
|
|
134
|
+
// The extension must be LETTER-initial (`.md`, not a `.5` decimal). The name part accepts a
|
|
135
|
+
// leading '@' only at index 0 (npm scope `@scope/pkg/file.ts`); an '@' after index 0 makes
|
|
136
|
+
// it an email, not a path, so "ops@example.com confirmed" does NOT qualify (issue #412 fix
|
|
137
|
+
// 1). An abbreviation's trailing "." ("e.g. ok") has no ext char before the space, so it
|
|
138
|
+
// still fails.
|
|
139
|
+
// - an extensionless named file :line ./Makefile:5, path/to/file:12 — the :line is the
|
|
140
|
+
// discriminator, and the left side must be PATH-LIKE: contain a '/' or a real '.ext' (issue
|
|
141
|
+
// #412 fix 2). A bare "word:number" ("error:500", "attempt:2") is not a locator, and a bare
|
|
142
|
+
// "Makefile:5" must be path-qualified ("./Makefile:5"). The name part must contain a letter,
|
|
143
|
+
// so "12:30" is not a locator.
|
|
144
|
+
// - a URL http://… / https://…
|
|
145
|
+
// - an issue / PR ref #123
|
|
146
|
+
// - a backticked shell command `node --test …`
|
|
147
|
+
// - a bare git SHA 7-40 hex chars, the whole TOKEN (a trailing note is
|
|
148
|
+
// allowed, e.g. "AbCdEf1 rest"), containing BOTH a hex letter AND a digit — the digit+letter
|
|
149
|
+
// requirement is what separates a real SHA from an all-hex English word or an all-digit
|
|
150
|
+
// date/number, each of which is otherwise a valid hex string, and it is tested on the token,
|
|
151
|
+
// not the note. It rejects the ~0.1% of genuine SHAs that are all-letters — the safe
|
|
152
|
+
// direction for a production gate.
|
|
153
|
+
// RESIDUAL (shape-inherent, out of scope). A leading glued sentence or product name that is
|
|
154
|
+
// shape-identical to a real file — "Done.Next steps", "Next.js routes unchanged", "Mrs.Smith
|
|
155
|
+
// approved" — still qualifies: without a filesystem check they are indistinguishable from a
|
|
156
|
+
// legitimate ".js"/".ts"/".md" reference, and rejecting them would reject real file locators.
|
|
157
|
+
// Bare prose ("looks good", "done", "verified", "N/A", "TODO") leads with none of these →
|
|
158
|
+
// NOT a locator → the row is not green and condition (c) HOLDs (forces the human).
|
|
159
|
+
export function isCheckableLocator(s) {
|
|
160
|
+
if (typeof s !== 'string') return false
|
|
161
|
+
const t = s.trim()
|
|
162
|
+
if (t.length === 0) return false
|
|
163
|
+
|
|
164
|
+
// The shared trailing boundary (issue #412 fix 3): a COMPLETE locator token ends at
|
|
165
|
+
// end-of-string, whitespace, or a note-attaching punctuation ( , ; ) ] } ) — the SAME rule
|
|
166
|
+
// for every form below, so "AbCdEf1 rest" (SHA + note) and "CHANGELOG.md:1, note" (locator
|
|
167
|
+
// glued to a comma) qualify exactly as "docs/x.md:12 note" always did. It DELIBERATELY
|
|
168
|
+
// excludes '.' — a leading abbreviation ("e.g.", "U.S.") must never read as "file.ext" + a
|
|
169
|
+
// trailing period note (that is the dotted-abbreviation fail-open the anchoring closed) —
|
|
170
|
+
// and ':' / '-' / '/', which are locator-internal.
|
|
171
|
+
const TAIL = '(?:[\\s,;)\\]}]|$)'
|
|
172
|
+
|
|
173
|
+
// a file path with a letter-initial extension + optional :line, then the tail.
|
|
174
|
+
// The name part accepts a leading '@' at index 0 (npm scope: @scope/pkg/file.ts) but NO '@'
|
|
175
|
+
// after it (issue #412 fix 1): "ops@example.com confirmed" is an email TLD, not a path.
|
|
176
|
+
if (new RegExp('^\\S[^\\s@]*\\.[A-Za-z][\\w-]{0,9}(:\\d+(-\\d+)?)?' + TAIL).test(t)) return true
|
|
177
|
+
|
|
178
|
+
// an extensionless named file with a :line ref. The left side must be PATH-LIKE — contain a
|
|
179
|
+
// '/' or a real '.ext' (issue #412 fix 2) — so bare "word:number" prose ("error:500",
|
|
180
|
+
// "attempt:2", "Makefile:5") is NOT a locator; and the name part must contain a letter
|
|
181
|
+
// ("12:30" is a time, not a locator). A bare extensionless name needs a path: "./Makefile:5".
|
|
182
|
+
if (new RegExp('^(?=[\\w./-]*[A-Za-z])(?=[\\w./-]*(?:/|\\.[A-Za-z]))[\\w./-]+:\\d+(-\\d+)?' + TAIL).test(t)) return true
|
|
183
|
+
|
|
184
|
+
// a URL
|
|
185
|
+
if (/^https?:\/\//.test(t)) return true
|
|
186
|
+
|
|
187
|
+
// an issue / PR ref
|
|
188
|
+
if (/^#\d+/.test(t)) return true
|
|
189
|
+
|
|
190
|
+
// a backticked command / code span
|
|
191
|
+
if (/^`[^`]+`/.test(t)) return true
|
|
192
|
+
|
|
193
|
+
// a bare git SHA: a 7-40 hex token, then the shared tail (issue #412 fix 3 — a SHA + note
|
|
194
|
+
// now qualifies like every other form, not only a whole-string SHA). The digit-AND-hex-letter
|
|
195
|
+
// requirement — what separates a real SHA from an all-hex English word or an all-digit
|
|
196
|
+
// date/number — is tested on the captured TOKEN, never on the trailing note.
|
|
197
|
+
const sha = new RegExp('^([0-9a-f]{7,40})' + TAIL, 'i').exec(t)
|
|
198
|
+
if (sha && /[0-9]/.test(sha[1]) && /[a-f]/i.test(sha[1])) return true
|
|
199
|
+
|
|
200
|
+
return false
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const SECRET_EVIDENCE = [
|
|
204
|
+
/-----BEGIN [^-]*PRIVATE KEY-----/i,
|
|
205
|
+
/(?:^|[?&\s,;])(?:access[_-]?token|id[_-]?token|token|secret|password|passwd|api[_-]?key|authorization|credential)\s*[:=]\s*\S+/i,
|
|
206
|
+
/\b(?:gh[pousr]_[A-Za-z0-9_]{20,}|sk-[A-Za-z0-9_-]{20,}|AIza[A-Za-z0-9_-]{20,})\b/,
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
function diagnosticEvidence(value) {
|
|
210
|
+
let serialized
|
|
211
|
+
try {
|
|
212
|
+
serialized = typeof value === 'string' ? value : JSON.stringify(value)
|
|
213
|
+
} catch {
|
|
214
|
+
serialized = '[UNSERIALIZABLE]'
|
|
215
|
+
}
|
|
216
|
+
if (SECRET_EVIDENCE.some((pattern) => pattern.test(serialized || ''))) {
|
|
217
|
+
return { value: '[REDACTED]', redacted: true }
|
|
218
|
+
}
|
|
219
|
+
return { value: value ?? '', redacted: false }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function releaseReadinessFailures(rows) {
|
|
223
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
224
|
+
return [{
|
|
225
|
+
index: null,
|
|
226
|
+
row: '(releaseReadiness)',
|
|
227
|
+
status: null,
|
|
228
|
+
evidenceValue: '',
|
|
229
|
+
evidenceRedacted: false,
|
|
230
|
+
violations: [{
|
|
231
|
+
rule: 'releaseReadiness-must-be-non-empty-array',
|
|
232
|
+
actual: Array.isArray(rows) ? 'empty-array' : rows == null ? null : typeof rows,
|
|
233
|
+
}],
|
|
234
|
+
}]
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return rows.flatMap((entry, index) => {
|
|
238
|
+
const row = entry && typeof entry === 'object' ? entry : {}
|
|
239
|
+
const evidence = diagnosticEvidence(row.evidence)
|
|
240
|
+
const violations = []
|
|
241
|
+
if (row.status !== 'PASS') violations.push({ rule: 'status-must-be-PASS', actual: row.status ?? null })
|
|
242
|
+
if (!isCheckableLocator(row.evidence)) {
|
|
243
|
+
violations.push({ rule: 'evidence-must-start-with-checkable-locator', actual: evidence.value })
|
|
244
|
+
}
|
|
245
|
+
if (violations.length === 0) return []
|
|
246
|
+
return [{
|
|
247
|
+
index,
|
|
248
|
+
row: typeof row.row === 'string' && row.row ? row.row : `(row ${index})`,
|
|
249
|
+
status: row.status ?? null,
|
|
250
|
+
evidenceValue: evidence.value,
|
|
251
|
+
evidenceRedacted: evidence.redacted,
|
|
252
|
+
violations,
|
|
253
|
+
}]
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// (c) release-readiness: every row decided PASS with a CHECKABLE evidence locator (see
|
|
258
|
+
// isCheckableLocator — prose alone is not evidence), no blank/TODO row (coverage-checklist.mjs
|
|
259
|
+
// validate semantics). Plus the version/changelog row: the cut is committed and the recorded
|
|
260
|
+
// plan reports no collision on the final tag.
|
|
261
|
+
function releaseReadinessGreen(release) {
|
|
262
|
+
const plan = release.versionPlan
|
|
263
|
+
const versionRowGreen =
|
|
264
|
+
typeof release.cutCommit === 'string' &&
|
|
265
|
+
release.cutCommit.length > 0 &&
|
|
266
|
+
!!plan &&
|
|
267
|
+
plan.collision === false
|
|
268
|
+
return releaseReadinessFailures(release.releaseReadiness).length === 0 && versionRowGreen
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ---- promote: (a)-(d) + carve-outs ----
|
|
272
|
+
|
|
273
|
+
export function evaluatePromote(manifest) {
|
|
274
|
+
const release = (manifest && manifest.release) || {}
|
|
275
|
+
const envelope = (manifest && manifest.envelope) || {}
|
|
276
|
+
const failed = []
|
|
277
|
+
const evidence = {}
|
|
278
|
+
const diagnostics = { readiness: releaseReadinessFailures(release.releaseReadiness) }
|
|
279
|
+
|
|
280
|
+
// (a) Fast-forwardable.
|
|
281
|
+
evidence.fastForwardable = release.fastForwardable
|
|
282
|
+
if (release.fastForwardable !== true) failed.push('a')
|
|
283
|
+
|
|
284
|
+
// (b) Gates green: PR-lane CI, integration CI, and (where recorded) the SHA-keyed smoke
|
|
285
|
+
// verdict ALIVE on the exact promoted SHA.
|
|
286
|
+
evidence.prLaneCi = release.prLaneCi
|
|
287
|
+
evidence.integrationCi = release.integrationCi
|
|
288
|
+
evidence.integrationSmoke = release.integrationSmoke
|
|
289
|
+
evidence.candidateSha = release.candidateSha
|
|
290
|
+
if (
|
|
291
|
+
!isGreen(release.prLaneCi) ||
|
|
292
|
+
!isGreen(release.integrationCi) ||
|
|
293
|
+
!integrationSmokeGreen(release.integrationSmoke, release.candidateSha)
|
|
294
|
+
) {
|
|
295
|
+
failed.push('b')
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// (c) Release-readiness fully decided (rows + the version/changelog row).
|
|
299
|
+
evidence.releaseReadiness = Array.isArray(release.releaseReadiness)
|
|
300
|
+
? release.releaseReadiness.map((row) => {
|
|
301
|
+
const safe = diagnosticEvidence(row && row.evidence)
|
|
302
|
+
return { ...row, evidence: safe.value, evidenceRedacted: safe.redacted }
|
|
303
|
+
})
|
|
304
|
+
: release.releaseReadiness
|
|
305
|
+
evidence.cutCommit = release.cutCommit
|
|
306
|
+
evidence.versionPlan = release.versionPlan
|
|
307
|
+
if (!releaseReadinessGreen(release)) failed.push('c')
|
|
308
|
+
|
|
309
|
+
// (d) No unresolved blocking finding on the combined diff (from the integration-review
|
|
310
|
+
// verdict, a recorded manifest field). Absent count fails closed.
|
|
311
|
+
evidence.blockingFindings = release.blockingFindings
|
|
312
|
+
if (release.blockingFindings !== 0) failed.push('d')
|
|
313
|
+
|
|
314
|
+
// Carve-out: a major version bump reaches consumers who already pinned — irreversible
|
|
315
|
+
// for them whatever the rollback does. version.mjs plan names the bump, so it is
|
|
316
|
+
// mechanically checkable. FAIL CLOSED like every other carve-out: clear this ONLY on a
|
|
317
|
+
// positively-known non-major bump. version.mjs only ever emits lowercase major|minor|
|
|
318
|
+
// patch, so an absent versionPlan, an absent bump, or ANY non-canonical value ('Major',
|
|
319
|
+
// 'MAJOR', a typo) is a malformed input the gate cannot read as safe — it must force the
|
|
320
|
+
// human, never take the green branch. A knowably-major bump is named distinctly from a
|
|
321
|
+
// manifest we can't trust to be non-major.
|
|
322
|
+
const bump = release.versionPlan && release.versionPlan.bump
|
|
323
|
+
if (bump === 'major') failed.push('carveout:major-bump')
|
|
324
|
+
else if (bump !== 'patch' && bump !== 'minor') failed.push('malformed-version-plan')
|
|
325
|
+
|
|
326
|
+
// Carve-out: rollback must be armed — auto-rollback policy AND a candidate deletable
|
|
327
|
+
// without confirmation. Without the validate→expose→verify→rollback loop, auto-when-green
|
|
328
|
+
// is not available.
|
|
329
|
+
evidence.rollbackPolicy = envelope.rollbackPolicy
|
|
330
|
+
evidence.candidateDeletableWithoutConfirm = envelope.candidateDeletableWithoutConfirm
|
|
331
|
+
if (envelope.rollbackPolicy !== 'auto-rollback' || envelope.candidateDeletableWithoutConfirm !== true) {
|
|
332
|
+
failed.push('carveout:rollback-not-armed')
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Carve-out: irreversible content always forces the human. This depends on an input the
|
|
336
|
+
// pre-flight gate does not record today, so it fails closed: a positive
|
|
337
|
+
// `reversibilityConfirmed` is REQUIRED to clear it, and an explicit `irreversibleContent`
|
|
338
|
+
// flag forces the human. Absence HOLDs rather than silently passing.
|
|
339
|
+
evidence.reversibilityConfirmed = envelope.reversibilityConfirmed
|
|
340
|
+
evidence.irreversibleContent = envelope.irreversibleContent
|
|
341
|
+
if (envelope.reversibilityConfirmed !== true || envelope.irreversibleContent === true) {
|
|
342
|
+
failed.push('carveout:irreversible-content')
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return { decision: failed.length === 0 ? 'fire' : 'hold', phase: 'promote', failed, evidence, diagnostics }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ---- shift: (e) checked against the artifact, not asserted ----
|
|
349
|
+
|
|
350
|
+
// The freshness window in force for this release: the envelope's declared value when it is a
|
|
351
|
+
// usable one, the conservative default otherwise. Only a positive finite NUMBER counts —
|
|
352
|
+
// a string "240", a 0, a negative, NaN/Infinity, an object are all unreadable, and an
|
|
353
|
+
// unreadable window must never become "no window". Falling back NARROWS rather than widens,
|
|
354
|
+
// so the failure direction is a HOLD (fall back to the human), never an unbounded fire.
|
|
355
|
+
function freshnessWindowMinutes(envelope) {
|
|
356
|
+
const declared = envelope && envelope.verdictFreshnessMinutes
|
|
357
|
+
const usable = typeof declared === 'number' && Number.isFinite(declared) && declared > 0
|
|
358
|
+
return usable ? declared : DEFAULT_VERDICT_FRESHNESS_MINUTES
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Floor readiness before the first traffic shift (#800 / identity-v0.4.1).
|
|
362
|
+
//
|
|
363
|
+
// Applicability is decided before the RC mint by production-endpoints.mjs. The shift gate
|
|
364
|
+
// consumes that record rather than accepting a detached floor assertion: a distinct host cannot
|
|
365
|
+
// be skipped, ALIVE must target the recorded canonical URL, and Cloud Run-only is accepted only
|
|
366
|
+
// through the fingerprint-bound reasoned skip produced from equal endpoint reads.
|
|
367
|
+
export function evaluateFloorReadiness(manifest, now = Date.now()) {
|
|
368
|
+
const release = (manifest && manifest.release) || {}
|
|
369
|
+
const envelope = (manifest && manifest.envelope) || {}
|
|
370
|
+
const floor = release.floorReadiness || null
|
|
371
|
+
const result = evaluateProductionFloor(release.productionEndpoints, floor, {
|
|
372
|
+
now,
|
|
373
|
+
freshnessMinutes: freshnessWindowMinutes(envelope),
|
|
374
|
+
})
|
|
375
|
+
return result.ok
|
|
376
|
+
? { ok: true, id: null, evidence: result.evidence }
|
|
377
|
+
: { ok: false, id: "e:floor-not-ready", evidence: { floorReadiness: floor }, detail: `${result.id}: ${result.detail}` }
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function evaluateShift(manifest, verdict, now = Date.now()) {
|
|
381
|
+
const release = (manifest && manifest.release) || {}
|
|
382
|
+
const envelope = (manifest && manifest.envelope) || {}
|
|
383
|
+
const candidateUrl = release.candidateUrl
|
|
384
|
+
const failed = []
|
|
385
|
+
const evidence = { candidateUrl }
|
|
386
|
+
const normalizedVerdict = normalizeReleaseVerdict(verdict)
|
|
387
|
+
|
|
388
|
+
// (e) The candidate's smoke verdict must be ALIVE, on the EXACT candidate URL, and a
|
|
389
|
+
// --no-mutations run. A missing/malformed verdict, a mismatched target, an EMPTY or DEAD
|
|
390
|
+
// verdict, or a mutating run all HOLD. Comparing the recorded target is the whole point:
|
|
391
|
+
// it turns "on that exact candidate" from an assertion into a check.
|
|
392
|
+
evidence.verdict = normalizedVerdict && normalizedVerdict.state
|
|
393
|
+
evidence.verdictTarget = normalizedVerdict && normalizedVerdict.target
|
|
394
|
+
evidence.noMutations = normalizedVerdict && normalizedVerdict.noMutations
|
|
395
|
+
const green =
|
|
396
|
+
!!normalizedVerdict &&
|
|
397
|
+
typeof candidateUrl === 'string' &&
|
|
398
|
+
candidateUrl.length > 0 &&
|
|
399
|
+
normalizedVerdict.state === 'ALIVE' &&
|
|
400
|
+
normalizedVerdict.target === candidateUrl &&
|
|
401
|
+
normalizedVerdict.noMutations === true
|
|
402
|
+
if (!green) {
|
|
403
|
+
// Issue #823: a CANNOT-RUN (or BLOCKED) verdict is a gate failure, not a candidate failure.
|
|
404
|
+
// Distinguish it so the operator repairs the gate rather than deleting a healthy candidate.
|
|
405
|
+
if (normalizedVerdict && (normalizedVerdict.state === 'CANNOT-RUN' || normalizedVerdict.state === 'BLOCKED')) {
|
|
406
|
+
failed.push('e:cannot-run')
|
|
407
|
+
} else {
|
|
408
|
+
failed.push('e')
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// (e)'s freshness half. Named as its own condition rather than folded into `e` so the
|
|
413
|
+
// operator can tell the two routes apart: a failed `e` means the candidate did not pass
|
|
414
|
+
// (route to step 5's pre-exposure path), while a failed freshness check means the evidence
|
|
415
|
+
// aged out while the authorization was pending — the candidate may be perfectly fine, and
|
|
416
|
+
// the route is back to step 4a to re-validate it, never onward to the shift.
|
|
417
|
+
const windowMinutes = freshnessWindowMinutes(envelope)
|
|
418
|
+
evidence.verdictFreshnessMinutes = windowMinutes
|
|
419
|
+
evidence.generatedAt = normalizedVerdict && normalizedVerdict.generatedAt
|
|
420
|
+
const stampedAt =
|
|
421
|
+
normalizedVerdict && typeof normalizedVerdict.generatedAt === 'string' ? Date.parse(normalizedVerdict.generatedAt) : NaN
|
|
422
|
+
if (!Number.isFinite(stampedAt)) {
|
|
423
|
+
// Absent evidence is not fresh evidence. A verdict with no readable stamp cannot be aged,
|
|
424
|
+
// so it can never be shown to be fresh — the fail-closed reading is HOLD, and it is the
|
|
425
|
+
// one an older smoke build (which stamped no timestamp at all) lands on.
|
|
426
|
+
failed.push('e:unstamped-verdict')
|
|
427
|
+
} else {
|
|
428
|
+
const ageMinutes = (now - stampedAt) / 60_000
|
|
429
|
+
evidence.verdictAgeMinutes = Math.round(ageMinutes * 100) / 100
|
|
430
|
+
// A NEGATIVE age — a stamp ahead of now — is not freshness either: it is a clock the gate
|
|
431
|
+
// cannot trust, and left unchecked a future-dated verdict would read as fresh forever.
|
|
432
|
+
if (ageMinutes < 0 || ageMinutes > windowMinutes) failed.push('e:stale-verdict')
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Floor public-host readiness (#800). Independent of (e): the candidate can be ALIVE on
|
|
436
|
+
// *.run.app while the public host is DEAD for a pre-existing edge reason. Named so the
|
|
437
|
+
// operator can tell "candidate bad" from "floor already broken".
|
|
438
|
+
const floor = evaluateFloorReadiness(manifest, now)
|
|
439
|
+
Object.assign(evidence, floor.evidence)
|
|
440
|
+
if (!floor.ok) failed.push(floor.id)
|
|
441
|
+
|
|
442
|
+
return { decision: failed.length === 0 ? 'fire' : 'hold', phase: 'shift', failed, evidence }
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ---- CLI ----
|
|
446
|
+
|
|
447
|
+
const USAGE = `green-gate — the mechanized unattended-promotion gate for the release lane.
|
|
448
|
+
|
|
449
|
+
Every (a)-(e) condition of the auto-when-green envelope is decided from an ARTIFACT and
|
|
450
|
+
FAILS CLOSED: a missing or malformed input HOLDs, it never fires. The two subcommands
|
|
451
|
+
mirror the doc's two authorization points.
|
|
452
|
+
|
|
453
|
+
Usage:
|
|
454
|
+
green-gate promote --manifest <run-manifest.json>
|
|
455
|
+
Gate the promotion + rc mint on:
|
|
456
|
+
(a) fastForwardable === true
|
|
457
|
+
(b) PR-lane CI green, integration CI green, and (where the integration env records
|
|
458
|
+
one) the SHA-keyed smoke verdict ALIVE on the exact promoted SHA
|
|
459
|
+
(c) every release-readiness row PASS whose evidence BEGINS WITH a CHECKABLE
|
|
460
|
+
locator — a file path (real extension) / path:line / URL / #ref / backticked
|
|
461
|
+
command / mixed-hex SHA; prose that merely mentions a dotted abbreviation, a
|
|
462
|
+
date, or a hex word HOLDs — and the version cut committed with no tag collision
|
|
463
|
+
(d) no unresolved blocking finding on the combined diff
|
|
464
|
+
Carve-outs that force the human even when (a)-(d) hold:
|
|
465
|
+
- a major version bump (version.mjs plan bump === "major")
|
|
466
|
+
- rollback not armed (policy not auto-rollback, or candidate not deletable w/o confirm)
|
|
467
|
+
- irreversible content (destructive migration / unverifiable secret / first deploy)
|
|
468
|
+
|
|
469
|
+
green-gate shift --manifest <run-manifest.json> --verdict <smoke-verdict.json>
|
|
470
|
+
Gate the traffic shift on:
|
|
471
|
+
(e) verdict === "ALIVE" AND target === candidateUrl AND noMutations === true
|
|
472
|
+
(EMPTY, DEAD, missing, CANNOT-RUN/BLOCKED, or an ALIVE from a different target all HOLD)
|
|
473
|
+
e:stale-verdict the verdict's generatedAt is outside the freshness window
|
|
474
|
+
(envelope.verdictFreshnessMinutes; default ${DEFAULT_VERDICT_FRESHNESS_MINUTES} min, and an unreadable
|
|
475
|
+
declared window falls back to that default rather than widening). A future-dated
|
|
476
|
+
stamp is stale too. Route back to step 4a and re-validate the candidate — do NOT
|
|
477
|
+
shift on aged coordinates.
|
|
478
|
+
e:unstamped-verdict the verdict carries no readable generatedAt, so its age cannot
|
|
479
|
+
be bounded — absent evidence is not fresh evidence.
|
|
480
|
+
e:floor-not-ready public-host (prod-active) readiness on the CURRENT production
|
|
481
|
+
floor is missing, DEAD/blocked, or skipped without a reason. Candidate ALIVE on
|
|
482
|
+
*.run.app is not floor readiness — CF Insights / edge scripts only inject on the
|
|
483
|
+
proxied public host (identity-v0.4.1 / #800). Record release.floorReadiness
|
|
484
|
+
{status:"alive", profile:"prod-active", target, checkedAt} or
|
|
485
|
+
{status:"skipped", reason} before the first shift.
|
|
486
|
+
|
|
487
|
+
Exit codes:
|
|
488
|
+
0 FIRE — every condition holds, no carve-out triggered
|
|
489
|
+
1 HOLD — fall back to a per-step human confirm; the failing condition ids are named
|
|
490
|
+
2 usage / unreadable-input error — bad flags, unknown subcommand, or a manifest that is
|
|
491
|
+
absent, unparseable, or not a resolve-release run
|
|
492
|
+
|
|
493
|
+
A condition whose input could not be produced is a FAILING condition, not a missing one: an
|
|
494
|
+
applicable gate that cannot run is a failure, not an absence (principle 13). An absent
|
|
495
|
+
credential, an unavailable service or a drifted fixture HOLDs exactly as a red result does —
|
|
496
|
+
this gate cannot tell "nobody filled it in" from "nobody could", and neither is a result. An
|
|
497
|
+
"n/a" is the other thing: a condition that does not apply here, decided, with a reason.
|
|
498
|
+
|
|
499
|
+
A HOLD blocks THIS release — this SHA does not promote, this candidate takes no traffic. It
|
|
500
|
+
is never a reason to pause the merge queue, dequeue an unrelated PR, or lift a branch
|
|
501
|
+
protection: the repair reaches the repo through that lane, and a block that freezes it too is
|
|
502
|
+
the one people bypass.
|
|
503
|
+
|
|
504
|
+
Output: JSON { decision: "fire"|"hold", phase, failed: [<condition ids>], evidence: {...} }
|
|
505
|
+
|
|
506
|
+
Doctrine (why each condition, the two authorization points, the (e)-must-be-artifact rule,
|
|
507
|
+
the carve-outs): references/auto-when-green.md.`
|
|
508
|
+
|
|
509
|
+
function parseArgs(argv) {
|
|
510
|
+
const opts = {}
|
|
511
|
+
const positional = []
|
|
512
|
+
for (let i = 0; i < argv.length; i++) {
|
|
513
|
+
const a = argv[i]
|
|
514
|
+
if (a === '--help' || a === '-h') opts.help = true
|
|
515
|
+
else if (a.startsWith('--')) opts[a.slice(2)] = argv[++i]
|
|
516
|
+
else positional.push(a)
|
|
517
|
+
}
|
|
518
|
+
return { opts, positional }
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Read + parse the run manifest. Distinguishes the two exit-2 causes (no path given, file
|
|
522
|
+
// unreadable/unparseable, wrong-shape run) from a HOLD: you cannot evaluate ANY condition
|
|
523
|
+
// without a readable resolve-release manifest, so those are operational errors, not
|
|
524
|
+
// condition failures.
|
|
525
|
+
function loadManifest(file) {
|
|
526
|
+
if (!file) return { error: 'usage: --manifest <run-manifest.json> is required' }
|
|
527
|
+
let raw
|
|
528
|
+
try {
|
|
529
|
+
raw = fs.readFileSync(file, 'utf8')
|
|
530
|
+
} catch {
|
|
531
|
+
return { error: `manifest not readable: ${file}` }
|
|
532
|
+
}
|
|
533
|
+
let manifest
|
|
534
|
+
try {
|
|
535
|
+
manifest = JSON.parse(raw)
|
|
536
|
+
} catch {
|
|
537
|
+
return { error: `manifest is not valid JSON: ${file}` }
|
|
538
|
+
}
|
|
539
|
+
if (!manifest || typeof manifest !== 'object' || !manifest.envelope) {
|
|
540
|
+
return { error: `not a resolve-release run manifest (no authorization envelope): ${file}` }
|
|
541
|
+
}
|
|
542
|
+
return { manifest }
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// A verdict file that is absent or unparseable is a HOLD, not an error: a missing
|
|
546
|
+
// smoke-verdict.json must never fire the traffic shift. Returns null on any failure, which
|
|
547
|
+
// evaluateShift treats as a (e) HOLD.
|
|
548
|
+
function loadVerdict(file) {
|
|
549
|
+
if (!file) return null
|
|
550
|
+
try {
|
|
551
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
552
|
+
} catch {
|
|
553
|
+
return null
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export function main(argv) {
|
|
558
|
+
const { opts, positional } = parseArgs(argv)
|
|
559
|
+
const cmd = positional[0]
|
|
560
|
+
|
|
561
|
+
// Only an EXPLICIT help request earns exit 0. A missing subcommand fails CLOSED to the
|
|
562
|
+
// same exit-2 usage error as an unknown one: `green-gate <empty> --manifest …` (or a bare
|
|
563
|
+
// invocation) inside `green-gate … && shift-traffic` must NOT print usage and exit 0 — a
|
|
564
|
+
// 0 there is a FIRE, shifting production traffic on no gate at all.
|
|
565
|
+
if (opts.help || cmd === 'help') {
|
|
566
|
+
console.log(USAGE)
|
|
567
|
+
return 0
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (cmd !== 'promote' && cmd !== 'shift') {
|
|
571
|
+
const what = cmd ? `unknown command "${cmd}"` : 'no subcommand given'
|
|
572
|
+
console.error(`green-gate: ${what}. Use promote|shift (see --help).`)
|
|
573
|
+
return 2
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const loaded = loadManifest(opts.manifest)
|
|
577
|
+
if (loaded.error) {
|
|
578
|
+
console.error(`green-gate: ${loaded.error}`)
|
|
579
|
+
return 2
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
let result
|
|
583
|
+
if (cmd === 'promote') {
|
|
584
|
+
result = evaluatePromote(loaded.manifest)
|
|
585
|
+
} else {
|
|
586
|
+
if (!opts.verdict) {
|
|
587
|
+
console.error('green-gate: usage: shift requires --verdict <smoke-verdict.json>')
|
|
588
|
+
return 2
|
|
589
|
+
}
|
|
590
|
+
result = evaluateShift(loaded.manifest, loadVerdict(opts.verdict))
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
console.log(JSON.stringify(result, null, 2))
|
|
594
|
+
return result.decision === 'fire' ? 0 : 1
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (isMainModule(import.meta.url)) {
|
|
598
|
+
process.exit(main(process.argv.slice(2)))
|
|
599
|
+
}
|