@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,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
5
|
+
|
|
6
|
+
export const LIFECYCLE_PHASES = [
|
|
7
|
+
'pr-head-authorization',
|
|
8
|
+
'queue-admission',
|
|
9
|
+
'synthetic-queue-head-execution',
|
|
10
|
+
'target-branch-ancestry',
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
const PHASES = {
|
|
14
|
+
'pr-head-authorization': {
|
|
15
|
+
rank: 0,
|
|
16
|
+
subjectKinds: ['pr-head-sha'],
|
|
17
|
+
transitions: ['authorize-enqueue'],
|
|
18
|
+
},
|
|
19
|
+
'queue-admission': {
|
|
20
|
+
rank: 1,
|
|
21
|
+
subjectKinds: ['pr-head-sha'],
|
|
22
|
+
transitions: ['enqueue'],
|
|
23
|
+
},
|
|
24
|
+
'synthetic-queue-head-execution': {
|
|
25
|
+
rank: 2,
|
|
26
|
+
subjectKinds: ['synthetic-queue-head-sha'],
|
|
27
|
+
transitions: ['record-queue-execution'],
|
|
28
|
+
},
|
|
29
|
+
'target-branch-ancestry': {
|
|
30
|
+
rank: 3,
|
|
31
|
+
subjectKinds: ['merge-commit-sha'],
|
|
32
|
+
transitions: ['finalize-delivery'],
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const TRANSITION_RANK = {
|
|
37
|
+
'authorize-enqueue': 0,
|
|
38
|
+
enqueue: 1,
|
|
39
|
+
'record-queue-execution': 2,
|
|
40
|
+
'finalize-delivery': 3,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const PROVIDERS = {
|
|
44
|
+
github: {
|
|
45
|
+
'read-exact-pr-head-checks': {
|
|
46
|
+
phase: 'pr-head-authorization', subjectKind: 'pr-head-sha', producer: 'github-actions',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
'resolve-issues': {
|
|
50
|
+
'evaluate-enqueue-eligibility': {
|
|
51
|
+
phase: 'queue-admission', subjectKind: 'pr-head-sha', producer: 'resolve-issues-orchestrator',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
mergify: {
|
|
55
|
+
'read-synthetic-queue-checks': {
|
|
56
|
+
phase: 'synthetic-queue-head-execution', subjectKind: 'synthetic-queue-head-sha', producer: 'mergify-queue',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
git: {
|
|
60
|
+
'fetch-target-and-check-ancestry': {
|
|
61
|
+
phase: 'target-branch-ancestry', subjectKind: 'merge-commit-sha', producer: 'target-branch',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const FAILURE_BEHAVIORS = ['block-transition', 'keep-candidate-frozen', 'mark-delivery-unproven']
|
|
67
|
+
const ROOT_FIELDS = ['version', 'discoveredConditions', 'conditions']
|
|
68
|
+
const CONDITION_FIELDS = [
|
|
69
|
+
'id', 'phase', 'subjectKind', 'producer', 'provider', 'acquisitionOperation',
|
|
70
|
+
'consumingTransition', 'failureBehavior',
|
|
71
|
+
]
|
|
72
|
+
const nonEmpty = (value) => typeof value === 'string' && value.trim().length > 0
|
|
73
|
+
|
|
74
|
+
export function validateEvidenceLifecycleContract(artifact) {
|
|
75
|
+
const errors = []
|
|
76
|
+
if (artifact == null || typeof artifact !== 'object' || Array.isArray(artifact)) {
|
|
77
|
+
return { ok: false, errors: ['artifact must be a JSON object'] }
|
|
78
|
+
}
|
|
79
|
+
for (const field of Object.keys(artifact)) {
|
|
80
|
+
if (!ROOT_FIELDS.includes(field)) errors.push(`artifact has unknown property: ${field}`)
|
|
81
|
+
}
|
|
82
|
+
if (artifact.version !== 1) errors.push('artifact.version must be 1')
|
|
83
|
+
|
|
84
|
+
const discovered = Array.isArray(artifact.discoveredConditions) ? artifact.discoveredConditions : []
|
|
85
|
+
if (!Array.isArray(artifact.discoveredConditions) || discovered.length === 0) {
|
|
86
|
+
errors.push('artifact.discoveredConditions must be a non-empty array')
|
|
87
|
+
}
|
|
88
|
+
if (discovered.some((id) => !nonEmpty(id))) errors.push('artifact.discoveredConditions entries must be non-empty strings')
|
|
89
|
+
const discoveredSet = new Set(discovered)
|
|
90
|
+
if (discoveredSet.size !== discovered.length) errors.push('artifact.discoveredConditions entries must be unique')
|
|
91
|
+
|
|
92
|
+
const conditions = Array.isArray(artifact.conditions) ? artifact.conditions : []
|
|
93
|
+
if (!Array.isArray(artifact.conditions) || conditions.length === 0) {
|
|
94
|
+
errors.push('artifact.conditions must be a non-empty array')
|
|
95
|
+
}
|
|
96
|
+
const conditionIds = []
|
|
97
|
+
for (const [index, condition] of conditions.entries()) {
|
|
98
|
+
const at = `conditions[${index}]`
|
|
99
|
+
if (condition == null || typeof condition !== 'object' || Array.isArray(condition)) {
|
|
100
|
+
errors.push(`${at} must be an object`)
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
for (const field of Object.keys(condition)) {
|
|
104
|
+
if (!CONDITION_FIELDS.includes(field)) errors.push(`${at} has unknown property: ${field}`)
|
|
105
|
+
}
|
|
106
|
+
for (const field of CONDITION_FIELDS) {
|
|
107
|
+
if (!nonEmpty(condition[field])) errors.push(`${at}.${field} is required`)
|
|
108
|
+
}
|
|
109
|
+
if (nonEmpty(condition.id)) conditionIds.push(condition.id)
|
|
110
|
+
|
|
111
|
+
const phase = PHASES[condition.phase]
|
|
112
|
+
if (!phase && nonEmpty(condition.phase)) errors.push(`${at} has unknown phase: ${condition.phase}`)
|
|
113
|
+
const provider = PROVIDERS[condition.provider]
|
|
114
|
+
if (!provider && nonEmpty(condition.provider)) errors.push(`${at} has unknown provider: ${condition.provider}`)
|
|
115
|
+
|
|
116
|
+
const transitionRank = TRANSITION_RANK[condition.consumingTransition]
|
|
117
|
+
if (phase && transitionRank !== undefined && phase.rank > transitionRank) {
|
|
118
|
+
errors.push(`${at}: phase ${condition.phase} occurs after consuming transition ${condition.consumingTransition}; this requires future evidence before the transition that makes it available`)
|
|
119
|
+
}
|
|
120
|
+
if (phase && nonEmpty(condition.subjectKind) && !phase.subjectKinds.includes(condition.subjectKind)) {
|
|
121
|
+
errors.push(`${at}.subjectKind ${condition.subjectKind} does not match phase ${condition.phase}`)
|
|
122
|
+
}
|
|
123
|
+
if (phase && nonEmpty(condition.consumingTransition) && !phase.transitions.includes(condition.consumingTransition)) {
|
|
124
|
+
errors.push(`${at}.consumingTransition ${condition.consumingTransition} does not match phase ${condition.phase}`)
|
|
125
|
+
}
|
|
126
|
+
if (nonEmpty(condition.consumingTransition) && transitionRank === undefined) {
|
|
127
|
+
errors.push(`${at} has unknown consumingTransition: ${condition.consumingTransition}`)
|
|
128
|
+
}
|
|
129
|
+
if (nonEmpty(condition.failureBehavior) && !FAILURE_BEHAVIORS.includes(condition.failureBehavior)) {
|
|
130
|
+
errors.push(`${at} has unknown failureBehavior: ${condition.failureBehavior}`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (provider && nonEmpty(condition.acquisitionOperation)) {
|
|
134
|
+
const operation = provider[condition.acquisitionOperation]
|
|
135
|
+
if (!operation) {
|
|
136
|
+
errors.push(`${at} has unsupported acquisition operation ${condition.acquisitionOperation} for provider ${condition.provider}`)
|
|
137
|
+
} else {
|
|
138
|
+
if (nonEmpty(condition.producer) && condition.producer !== operation.producer) {
|
|
139
|
+
errors.push(`${at}.producer ${condition.producer} does not match ${condition.provider}/${condition.acquisitionOperation} producer ${operation.producer}`)
|
|
140
|
+
}
|
|
141
|
+
if (nonEmpty(condition.phase) && condition.phase !== operation.phase) {
|
|
142
|
+
errors.push(`${at}.phase ${condition.phase} does not match ${condition.provider}/${condition.acquisitionOperation} phase ${operation.phase}`)
|
|
143
|
+
}
|
|
144
|
+
if (nonEmpty(condition.subjectKind) && condition.subjectKind !== operation.subjectKind) {
|
|
145
|
+
errors.push(`${at}.subjectKind ${condition.subjectKind} does not match ${condition.provider}/${condition.acquisitionOperation} subject ${operation.subjectKind}`)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const conditionSet = new Set(conditionIds)
|
|
152
|
+
if (conditionSet.size !== conditionIds.length) errors.push('artifact.conditions ids must be unique')
|
|
153
|
+
for (const id of discoveredSet) {
|
|
154
|
+
if (!conditionSet.has(id)) errors.push(`discovered condition ${id} has no lifecycle row`)
|
|
155
|
+
}
|
|
156
|
+
for (const id of conditionSet) {
|
|
157
|
+
if (!discoveredSet.has(id)) errors.push(`condition ${id} was not declared as discovered`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { ok: errors.length === 0, errors }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function usage() {
|
|
164
|
+
return 'usage: evidence-lifecycle-contract.mjs validate <artifact.json>\n'
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function runCli(argv = process.argv.slice(2)) {
|
|
168
|
+
if (argv.length === 1 && ['--help', '-h', 'help'].includes(argv[0])) {
|
|
169
|
+
process.stdout.write(usage())
|
|
170
|
+
return 0
|
|
171
|
+
}
|
|
172
|
+
if (argv.length !== 2 || argv[0] !== 'validate') {
|
|
173
|
+
process.stderr.write(usage())
|
|
174
|
+
return 2
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const artifact = JSON.parse(fs.readFileSync(argv[1], 'utf8'))
|
|
178
|
+
const result = validateEvidenceLifecycleContract(artifact)
|
|
179
|
+
if (!result.ok) {
|
|
180
|
+
process.stderr.write(`evidence-lifecycle-contract: INVALID\n${result.errors.map((error) => ` - ${error}`).join('\n')}\n`)
|
|
181
|
+
return 1
|
|
182
|
+
}
|
|
183
|
+
process.stdout.write(`evidence-lifecycle-contract: VALID (${artifact.conditions?.length ?? 0} conditions)\n`)
|
|
184
|
+
return 0
|
|
185
|
+
} catch (error) {
|
|
186
|
+
process.stderr.write(`evidence-lifecycle-contract: ${error.message}\n`)
|
|
187
|
+
return 2
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (isMainModule(import.meta.url)) process.exitCode = runCli()
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
// exact-head-ci — transactional PR choreography for collecting CI evidence at one
|
|
2
|
+
// immutable pull-request head. The provider command is the only external boundary:
|
|
3
|
+
// it accepts one JSON operation on stdin and returns one JSON object on stdout.
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import { execFile } from 'node:child_process'
|
|
7
|
+
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
8
|
+
import {
|
|
9
|
+
EXACT_HEAD_ARTIFACT_VERSION,
|
|
10
|
+
EXACT_HEAD_COLLECTION_MODES,
|
|
11
|
+
providerWorkflowIdentityMatches,
|
|
12
|
+
sameExactHeadState,
|
|
13
|
+
validateExactHeadArtifact,
|
|
14
|
+
} from '../../engineering-runtime/scripts/exact-head-artifact.mjs'
|
|
15
|
+
import { checksForPhase, locallyReproducedJobs, validatePublishOnlyEvidence, validateRequiredCheckPlan } from '../../engineering-runtime/scripts/required-check-plan.mjs'
|
|
16
|
+
|
|
17
|
+
export { EXACT_HEAD_ARTIFACT_VERSION, EXACT_HEAD_COLLECTION_MODES, validateExactHeadArtifact }
|
|
18
|
+
export const PROVIDER_OPERATIONS = Object.freeze([
|
|
19
|
+
'snapshot',
|
|
20
|
+
'retarget',
|
|
21
|
+
'set-open',
|
|
22
|
+
'set-draft',
|
|
23
|
+
'set-labels',
|
|
24
|
+
'set-queue-inactive',
|
|
25
|
+
'dispatch-workflow',
|
|
26
|
+
'read-workflow',
|
|
27
|
+
'read-publish-only-checks',
|
|
28
|
+
'read-queue-execution',
|
|
29
|
+
])
|
|
30
|
+
export const CHOREOGRAPHY_ACTIONS = Object.freeze(['retarget', 'close', 'reopen', 'dispatch'])
|
|
31
|
+
export const JOB_CONCLUSIONS = Object.freeze([
|
|
32
|
+
'success', 'failure', 'cancelled', 'skipped', 'neutral', 'timed_out',
|
|
33
|
+
'action_required', 'stale', 'startup_failure', null,
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
function isObject(value) {
|
|
37
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function uniqueStrings(value) {
|
|
41
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'string' && item.trim()) && new Set(value).size === value.length
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function validateRequest(request) {
|
|
45
|
+
const errors = []
|
|
46
|
+
if (!isObject(request)) return { ok: false, errors: ['request must be an object'] }
|
|
47
|
+
if (request.version !== 1) errors.push('request.version must be 1')
|
|
48
|
+
if (!EXACT_HEAD_COLLECTION_MODES.includes(request.collectionMode)) errors.push(`collectionMode must be one of ${EXACT_HEAD_COLLECTION_MODES.join('|')}`)
|
|
49
|
+
if (typeof request.repository !== 'string' || !/^[^/\s]+\/[^/\s]+$/.test(request.repository)) errors.push('repository must be owner/name')
|
|
50
|
+
if (!Number.isInteger(request.pr) || request.pr < 1) errors.push('pr must be a positive integer')
|
|
51
|
+
if (typeof request.expectedHeadSha !== 'string' || !/^[0-9a-f]{40}$/i.test(request.expectedHeadSha)) errors.push('expectedHeadSha must be a full 40-character commit SHA')
|
|
52
|
+
if (!isObject(request.provider) || typeof request.provider.command !== 'string' || !request.provider.command.trim()) errors.push('provider.command must be a non-empty string')
|
|
53
|
+
if (request.provider?.args !== undefined && !uniqueStrings(request.provider.args)) errors.push('provider.args must be an array of unique non-empty strings')
|
|
54
|
+
if (request.provider?.config !== undefined && !isObject(request.provider.config)) errors.push('provider.config must be an object')
|
|
55
|
+
if (!isObject(request.workflow) || typeof request.workflow.id !== 'string' || !request.workflow.id.trim()) errors.push('workflow.id must be a non-empty string')
|
|
56
|
+
if (request.collectionMode === 'choreographed') {
|
|
57
|
+
if (typeof request.temporaryBase !== 'string' || !request.temporaryBase.trim()) errors.push('temporaryBase must be a non-empty string')
|
|
58
|
+
if (!Array.isArray(request.choreography) || request.choreography.length === 0) errors.push('choreography must be a non-empty array')
|
|
59
|
+
else {
|
|
60
|
+
for (const action of request.choreography) if (!CHOREOGRAPHY_ACTIONS.includes(action)) errors.push(`unsupported choreography action "${action}"`)
|
|
61
|
+
if (request.choreography.filter((action) => action === 'dispatch').length !== 1) errors.push('choreography must contain dispatch exactly once')
|
|
62
|
+
if (!request.choreography.includes('retarget')) errors.push('choreography must contain retarget')
|
|
63
|
+
}
|
|
64
|
+
if (typeof request.workflow?.ref !== 'string' || !request.workflow.ref.trim()) errors.push('workflow.ref must be a non-empty string')
|
|
65
|
+
if (request.workflow?.inputs !== undefined && !isObject(request.workflow.inputs)) errors.push('workflow.inputs must be an object')
|
|
66
|
+
if (Object.hasOwn(request.workflow || {}, 'runId')) errors.push('choreographed workflow must not supply runId; dispatch acquires provider run identity')
|
|
67
|
+
}
|
|
68
|
+
if (request.collectionMode === 'observed-existing-run') {
|
|
69
|
+
if (Object.hasOwn(request, 'temporaryBase')) errors.push('observed-existing-run must not contain temporaryBase')
|
|
70
|
+
if (Object.hasOwn(request, 'choreography')) errors.push('observed-existing-run must not contain choreography')
|
|
71
|
+
if (typeof request.workflow?.runId !== 'string' && typeof request.workflow?.runId !== 'number') errors.push('observed-existing-run workflow.runId must identify the existing provider run')
|
|
72
|
+
if (Object.hasOwn(request.workflow || {}, 'ref') || Object.hasOwn(request.workflow || {}, 'inputs')) errors.push('observed-existing-run workflow must contain only existing workflow/run identity, not dispatch ref or inputs')
|
|
73
|
+
}
|
|
74
|
+
if (!isObject(request.policy)) errors.push('policy must be an object')
|
|
75
|
+
if (Object.hasOwn(request.policy || {}, 'requiredJobs')) errors.push('policy.requiredJobs is legacy; put every live condition in requiredCheckPlan and derive local jobs from it')
|
|
76
|
+
if (Object.hasOwn(request.policy || {}, 'allowedSkippedJobs')) errors.push('policy.allowedSkippedJobs is legacy; put explicit allowed skips in requiredCheckPlan')
|
|
77
|
+
const plan = validateRequiredCheckPlan(request.requiredCheckPlan, { expectedHeadSha: request.expectedHeadSha })
|
|
78
|
+
errors.push(...plan.errors.map((error) => `requiredCheckPlan: ${error}`))
|
|
79
|
+
for (const field of ['pollIntervalMs', 'timeoutMs']) {
|
|
80
|
+
if (!Number.isInteger(request.policy?.[field]) || request.policy[field] < 1) errors.push(`policy.${field} must be a positive integer`)
|
|
81
|
+
}
|
|
82
|
+
if (Number.isInteger(request.policy?.pollIntervalMs) && Number.isInteger(request.policy?.timeoutMs) && request.policy.pollIntervalMs > request.policy.timeoutMs) {
|
|
83
|
+
errors.push('policy.pollIntervalMs cannot exceed policy.timeoutMs')
|
|
84
|
+
}
|
|
85
|
+
return { ok: errors.length === 0, errors }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function validateSnapshot(snapshot) {
|
|
89
|
+
const errors = []
|
|
90
|
+
if (!isObject(snapshot)) return { ok: false, errors: ['provider snapshot must be an object'] }
|
|
91
|
+
if (typeof snapshot.base !== 'string' || !snapshot.base.trim()) errors.push('snapshot.base must be a non-empty string')
|
|
92
|
+
if (typeof snapshot.headSha !== 'string' || !/^[0-9a-f]{40}$/i.test(snapshot.headSha)) errors.push('snapshot.headSha must be a full 40-character commit SHA')
|
|
93
|
+
if (typeof snapshot.open !== 'boolean') errors.push('snapshot.open must be boolean')
|
|
94
|
+
if (typeof snapshot.draft !== 'boolean') errors.push('snapshot.draft must be boolean')
|
|
95
|
+
if (!uniqueStrings(snapshot.labels)) errors.push('snapshot.labels must be an array of unique non-empty strings')
|
|
96
|
+
if (!isObject(snapshot.queue)) errors.push('snapshot.queue must be an object')
|
|
97
|
+
else if (typeof snapshot.queue.active !== 'boolean') errors.push('snapshot.queue.active must be boolean')
|
|
98
|
+
if (!isObject(snapshot.reviews)) errors.push('snapshot.reviews must be an object')
|
|
99
|
+
return { ok: errors.length === 0, errors }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function classifyJobs(jobs, policy) {
|
|
103
|
+
const normalized = []
|
|
104
|
+
const errors = []
|
|
105
|
+
for (const [index, job] of (Array.isArray(jobs) ? jobs : []).entries()) {
|
|
106
|
+
if (!isObject(job) || (typeof job.id !== 'number' && typeof job.id !== 'string') || typeof job.name !== 'string' || !job.name.trim()) {
|
|
107
|
+
errors.push(`jobs[${index}] requires id and name`)
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
const conclusion = job.conclusion == null ? null : String(job.conclusion).toLowerCase()
|
|
111
|
+
if (!JOB_CONCLUSIONS.includes(conclusion)) errors.push(`job "${job.name}" has unsupported conclusion "${job.conclusion}"`)
|
|
112
|
+
normalized.push({ id: job.id, name: job.name, status: job.status ?? null, conclusion })
|
|
113
|
+
}
|
|
114
|
+
if (!Array.isArray(jobs)) errors.push('workflow jobs must be an array')
|
|
115
|
+
const byName = new Map()
|
|
116
|
+
for (const job of normalized) {
|
|
117
|
+
if (byName.has(job.name)) errors.push(`workflow jobs have duplicate/ambiguous evidence for ${job.name}`)
|
|
118
|
+
else byName.set(job.name, job)
|
|
119
|
+
}
|
|
120
|
+
const requiredMissing = policy.requiredJobs.filter((name) => !byName.has(name))
|
|
121
|
+
const requiredNotSuccessful = policy.requiredJobs.filter((name) => byName.has(name) && byName.get(name).conclusion !== 'success')
|
|
122
|
+
const allowedSkipped = new Set(policy.allowedSkippedJobs)
|
|
123
|
+
const disallowedSkipped = normalized.filter((job) => job.conclusion === 'skipped' && !allowedSkipped.has(job.name)).map((job) => job.name)
|
|
124
|
+
const failed = normalized.filter((job) => !['success', 'skipped'].includes(job.conclusion)).map((job) => `${job.name} (${job.conclusion ?? 'pending'})`)
|
|
125
|
+
const skipped = normalized.filter((job) => job.conclusion === 'skipped').map((job) => job.name)
|
|
126
|
+
let outcome = 'success'
|
|
127
|
+
if (normalized.length === 0) outcome = 'zero-job-workflow-failure'
|
|
128
|
+
else if (normalized.some((job) => job.conclusion === 'cancelled')) outcome = 'cancelled'
|
|
129
|
+
else if (failed.length || requiredMissing.length || requiredNotSuccessful.length || disallowedSkipped.length || errors.length) outcome = 'failure'
|
|
130
|
+
else if (skipped.length) outcome = 'success-with-skipped'
|
|
131
|
+
return {
|
|
132
|
+
ok: outcome === 'success' || outcome === 'success-with-skipped',
|
|
133
|
+
jobs: normalized,
|
|
134
|
+
errors,
|
|
135
|
+
summary: { outcome, requiredMissing, requiredNotSuccessful, disallowedSkipped, failed, skipped },
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sleep(ms) {
|
|
140
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function providerCall(request, operation, fields = {}) {
|
|
144
|
+
if (!PROVIDER_OPERATIONS.includes(operation)) throw new Error(`forbidden provider operation "${operation}"`)
|
|
145
|
+
const payload = { version: 1, operation, repository: request.repository, pr: request.pr, providerConfig: request.provider.config || {}, ...fields }
|
|
146
|
+
const stdout = await new Promise((resolve, reject) => {
|
|
147
|
+
const child = execFile(request.provider.command, request.provider.args || [], {
|
|
148
|
+
encoding: 'utf8', timeout: request.policy.timeoutMs, maxBuffer: 16 * 1024 * 1024,
|
|
149
|
+
}, (error, output) => error ? reject(error) : resolve(output))
|
|
150
|
+
child.stdin.end(`${JSON.stringify(payload)}\n`)
|
|
151
|
+
})
|
|
152
|
+
let response
|
|
153
|
+
try { response = JSON.parse(stdout) } catch { throw new Error(`provider operation "${operation}" returned invalid JSON`) }
|
|
154
|
+
if (!isObject(response)) throw new Error(`provider operation "${operation}" must return an object`)
|
|
155
|
+
return response
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function assertHead(snapshot, expected, at) {
|
|
159
|
+
if (snapshot.headSha !== expected) throw new Error(`head-drift at ${at}: expected ${expected}, observed ${snapshot.headSha}`)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function mutate(request, operation, fields) {
|
|
163
|
+
const result = await providerCall(request, operation, fields)
|
|
164
|
+
if (result.ok !== true) throw new Error(`provider operation "${operation}" did not confirm success`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function restore(request, snapshot, { mutateState }) {
|
|
168
|
+
const operations = []
|
|
169
|
+
const errors = []
|
|
170
|
+
const attempt = async (operation, fields) => {
|
|
171
|
+
try {
|
|
172
|
+
await mutate(request, operation, fields)
|
|
173
|
+
operations.push({ operation, status: 'restored' })
|
|
174
|
+
} catch (error) {
|
|
175
|
+
const message = String(error.message || error)
|
|
176
|
+
operations.push({ operation, status: 'failed', error: message })
|
|
177
|
+
errors.push(`${operation}: ${message}`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (mutateState) {
|
|
181
|
+
await attempt('set-open', { open: true })
|
|
182
|
+
await attempt('set-draft', { draft: snapshot.draft })
|
|
183
|
+
await attempt('retarget', { base: snapshot.base })
|
|
184
|
+
if (!snapshot.queue.active) await attempt('set-queue-inactive', {})
|
|
185
|
+
await attempt('set-labels', { labels: snapshot.labels })
|
|
186
|
+
if (!snapshot.open) await attempt('set-open', { open: false })
|
|
187
|
+
}
|
|
188
|
+
let observed = null
|
|
189
|
+
try { observed = await providerCall(request, 'snapshot') } catch (error) { errors.push(`snapshot: ${String(error.message || error)}`) }
|
|
190
|
+
const valid = validateSnapshot(observed)
|
|
191
|
+
errors.push(...valid.errors.map((error) => `restoration ${error}`))
|
|
192
|
+
const proved = valid.ok && sameExactHeadState(snapshot, observed)
|
|
193
|
+
if (valid.ok && !proved) errors.push('restoration observation differs from original snapshot')
|
|
194
|
+
return { attempted: true, operations, observed, proved, errors }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export async function runExactHead(request, { now = () => new Date().toISOString(), interruption = () => null } = {}) {
|
|
198
|
+
const requestVerdict = validateRequest(request)
|
|
199
|
+
if (!requestVerdict.ok) throw new Error(`invalid exact-head request: ${requestVerdict.errors.join('; ')}`)
|
|
200
|
+
const derivedPolicy = {
|
|
201
|
+
requiredJobs: locallyReproducedJobs(request.requiredCheckPlan),
|
|
202
|
+
allowedSkippedJobs: request.requiredCheckPlan.allowedSkippedJobs,
|
|
203
|
+
}
|
|
204
|
+
let snapshot = null
|
|
205
|
+
let observationAfter = null
|
|
206
|
+
let workflow = {
|
|
207
|
+
id: request.workflow.id,
|
|
208
|
+
provider: null,
|
|
209
|
+
runId: request.collectionMode === 'observed-existing-run' ? request.workflow.runId : null,
|
|
210
|
+
status: request.collectionMode === 'observed-existing-run' ? 'existing' : 'not-created',
|
|
211
|
+
conclusion: null,
|
|
212
|
+
headSha: request.expectedHeadSha,
|
|
213
|
+
}
|
|
214
|
+
let jobs = []
|
|
215
|
+
let publishOnlyEvidence = []
|
|
216
|
+
let summary = { outcome: request.collectionMode === 'choreographed' ? 'workflow-creation-failure' : 'workflow-observation-failure', requiredMissing: [], requiredNotSuccessful: [], disallowedSkipped: [], failed: [], skipped: [] }
|
|
217
|
+
const errors = []
|
|
218
|
+
let restoration = { attempted: false, operations: [], observed: null, proved: false, errors: [] }
|
|
219
|
+
let mutated = false
|
|
220
|
+
const checkpoint = (at) => {
|
|
221
|
+
const signal = interruption()
|
|
222
|
+
if (signal) throw new Error(`interrupted by ${signal} at ${at}`)
|
|
223
|
+
}
|
|
224
|
+
const applyMutation = async (operation, fields) => {
|
|
225
|
+
mutated = true
|
|
226
|
+
await mutate(request, operation, fields)
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
snapshot = await providerCall(request, 'snapshot')
|
|
230
|
+
const snapshotVerdict = validateSnapshot(snapshot)
|
|
231
|
+
if (!snapshotVerdict.ok) throw new Error(`invalid provider snapshot: ${snapshotVerdict.errors.join('; ')}`)
|
|
232
|
+
assertHead(snapshot, request.expectedHeadSha, 'initial snapshot')
|
|
233
|
+
if (request.collectionMode === 'choreographed') {
|
|
234
|
+
if (snapshot.queue.active) throw new Error('active queue state cannot be restored because this adapter must never enqueue; dequeue the PR explicitly before exact-head CI')
|
|
235
|
+
for (const action of request.choreography) {
|
|
236
|
+
checkpoint(`before ${action}`)
|
|
237
|
+
if (action === 'retarget') await applyMutation('retarget', { base: request.temporaryBase })
|
|
238
|
+
else if (action === 'close') await applyMutation('set-open', { open: false })
|
|
239
|
+
else if (action === 'reopen') await applyMutation('set-open', { open: true })
|
|
240
|
+
else if (action === 'dispatch') {
|
|
241
|
+
const beforeDispatch = await providerCall(request, 'snapshot')
|
|
242
|
+
const beforeVerdict = validateSnapshot(beforeDispatch)
|
|
243
|
+
if (!beforeVerdict.ok) throw new Error(`invalid pre-dispatch snapshot: ${beforeVerdict.errors.join('; ')}`)
|
|
244
|
+
assertHead(beforeDispatch, request.expectedHeadSha, 'before workflow dispatch')
|
|
245
|
+
const created = await providerCall(request, 'dispatch-workflow', {
|
|
246
|
+
workflow: request.workflow.id,
|
|
247
|
+
ref: request.workflow.ref,
|
|
248
|
+
inputs: request.workflow.inputs || {},
|
|
249
|
+
headSha: request.expectedHeadSha,
|
|
250
|
+
})
|
|
251
|
+
if (typeof created.runId !== 'string' && typeof created.runId !== 'number') throw new Error('workflow-creation-failure: provider returned no runId')
|
|
252
|
+
workflow = { ...workflow, runId: created.runId, status: 'created' }
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const deadline = Date.now() + request.policy.timeoutMs
|
|
257
|
+
while (true) {
|
|
258
|
+
checkpoint('before workflow read')
|
|
259
|
+
const expectedRunId = workflow.runId
|
|
260
|
+
const observed = await providerCall(request, 'read-workflow', {
|
|
261
|
+
runId: expectedRunId,
|
|
262
|
+
workflowId: request.workflow.id,
|
|
263
|
+
headSha: request.expectedHeadSha,
|
|
264
|
+
})
|
|
265
|
+
workflow = {
|
|
266
|
+
...workflow,
|
|
267
|
+
provider: observed.workflow ?? null,
|
|
268
|
+
runId: observed.runId,
|
|
269
|
+
status: observed.status ?? null,
|
|
270
|
+
conclusion: observed.conclusion ?? null,
|
|
271
|
+
headSha: observed.headSha ?? null,
|
|
272
|
+
}
|
|
273
|
+
if (typeof observed.runId !== 'string' && typeof observed.runId !== 'number') throw new Error('provider workflow observation returned no runId')
|
|
274
|
+
if (String(observed.runId) !== String(expectedRunId)) throw new Error(`workflow run identity mismatch: expected ${expectedRunId}, observed ${observed.runId}`)
|
|
275
|
+
if (!providerWorkflowIdentityMatches(request.workflow.id, observed.workflow)) {
|
|
276
|
+
const providerIdentity = observed.workflow?.name || observed.workflow?.path || observed.workflow?.id || 'missing'
|
|
277
|
+
throw new Error(`workflow identity mismatch: expected ${request.workflow.id}, observed ${providerIdentity}`)
|
|
278
|
+
}
|
|
279
|
+
if (workflow.headSha !== request.expectedHeadSha) throw new Error(`head-drift in workflow: expected ${request.expectedHeadSha}, observed ${workflow.headSha}`)
|
|
280
|
+
if (observed.status === 'completed') {
|
|
281
|
+
const classified = classifyJobs(observed.jobs, derivedPolicy)
|
|
282
|
+
jobs = classified.jobs
|
|
283
|
+
summary = classified.summary
|
|
284
|
+
errors.push(...classified.errors)
|
|
285
|
+
if (observed.conclusion === 'cancelled') summary.outcome = 'cancelled'
|
|
286
|
+
else if (observed.conclusion && observed.conclusion !== 'success' && summary.outcome !== 'zero-job-workflow-failure') summary.outcome = 'failure'
|
|
287
|
+
const publishOnly = checksForPhase(request.requiredCheckPlan, 'pr-head').filter((check) => check.classification === 'publish-only')
|
|
288
|
+
if (publishOnly.length) {
|
|
289
|
+
const observedPublishOnly = await providerCall(request, 'read-publish-only-checks', { headSha: request.expectedHeadSha, names: publishOnly.map((check) => check.name) })
|
|
290
|
+
if (!Array.isArray(observedPublishOnly.checks)) throw new Error('provider publish-only observation must return checks array')
|
|
291
|
+
publishOnlyEvidence = observedPublishOnly.checks
|
|
292
|
+
}
|
|
293
|
+
break
|
|
294
|
+
}
|
|
295
|
+
if (Date.now() >= deadline) throw new Error(`workflow timed out after ${request.policy.timeoutMs}ms`)
|
|
296
|
+
await sleep(request.policy.pollIntervalMs)
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
const message = String(error.message || error)
|
|
300
|
+
errors.push(message)
|
|
301
|
+
if (/interrupted by /.test(message)) summary.outcome = 'interrupted'
|
|
302
|
+
else if (/head-drift/.test(message)) summary.outcome = 'head-drift'
|
|
303
|
+
else if (workflow.runId == null) summary.outcome = 'workflow-creation-failure'
|
|
304
|
+
} finally {
|
|
305
|
+
if (snapshot && request.collectionMode === 'choreographed') {
|
|
306
|
+
restoration = await restore(request, snapshot, { mutateState: mutated })
|
|
307
|
+
errors.push(...restoration.errors)
|
|
308
|
+
}
|
|
309
|
+
if (snapshot && request.collectionMode === 'observed-existing-run') {
|
|
310
|
+
try {
|
|
311
|
+
observationAfter = await providerCall(request, 'snapshot')
|
|
312
|
+
const finalVerdict = validateSnapshot(observationAfter)
|
|
313
|
+
if (!finalVerdict.ok) throw new Error(`invalid final provider snapshot: ${finalVerdict.errors.join('; ')}`)
|
|
314
|
+
assertHead(observationAfter, request.expectedHeadSha, 'after provider evidence acquisition')
|
|
315
|
+
} catch (error) {
|
|
316
|
+
const message = String(error.message || error)
|
|
317
|
+
errors.push(message)
|
|
318
|
+
if (/head-drift/.test(message)) summary.outcome = 'head-drift'
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const publishOnlyVerdict = validatePublishOnlyEvidence(request.requiredCheckPlan, publishOnlyEvidence, {
|
|
323
|
+
expectedHeadSha: request.expectedHeadSha,
|
|
324
|
+
expectedRepository: request.repository,
|
|
325
|
+
expectedPr: request.pr,
|
|
326
|
+
})
|
|
327
|
+
errors.push(...publishOnlyVerdict.errors)
|
|
328
|
+
const provisional = {
|
|
329
|
+
version: EXACT_HEAD_ARTIFACT_VERSION,
|
|
330
|
+
kind: 'exact-head-ci',
|
|
331
|
+
collectionMode: request.collectionMode,
|
|
332
|
+
repository: request.repository,
|
|
333
|
+
pr: request.pr,
|
|
334
|
+
headSha: request.expectedHeadSha,
|
|
335
|
+
publishOnlyEvidence,
|
|
336
|
+
generatedAt: now(),
|
|
337
|
+
provider: { command: request.provider.command },
|
|
338
|
+
...(request.collectionMode === 'choreographed'
|
|
339
|
+
? { choreography: request.choreography, snapshot }
|
|
340
|
+
: { observation: { before: snapshot, after: observationAfter } }),
|
|
341
|
+
workflow,
|
|
342
|
+
jobs,
|
|
343
|
+
requiredCheckPlan: request.requiredCheckPlan,
|
|
344
|
+
policy: {
|
|
345
|
+
requiredJobs: derivedPolicy.requiredJobs,
|
|
346
|
+
allowedSkippedJobs: derivedPolicy.allowedSkippedJobs,
|
|
347
|
+
},
|
|
348
|
+
summary,
|
|
349
|
+
...(request.collectionMode === 'choreographed' ? { restoration } : {}),
|
|
350
|
+
errors,
|
|
351
|
+
status: 'fail',
|
|
352
|
+
}
|
|
353
|
+
const provenanceProved = request.collectionMode === 'choreographed'
|
|
354
|
+
? restoration.proved
|
|
355
|
+
: observationAfter?.headSha === request.expectedHeadSha
|
|
356
|
+
const passing = provenanceProved && errors.length === 0 && ['success', 'success-with-skipped'].includes(summary.outcome)
|
|
357
|
+
return { ...provisional, status: passing ? 'pass' : 'fail' }
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function parseArgs(argv) {
|
|
361
|
+
const options = {}
|
|
362
|
+
const positional = []
|
|
363
|
+
for (let index = 0; index < argv.length; index++) {
|
|
364
|
+
const arg = argv[index]
|
|
365
|
+
if (arg === '--input') options.input = argv[++index]
|
|
366
|
+
else if (arg === '--out') options.out = argv[++index]
|
|
367
|
+
else if (arg === '--head') options.head = argv[++index]
|
|
368
|
+
else positional.push(arg)
|
|
369
|
+
}
|
|
370
|
+
return { command: positional[0], file: positional[1], options }
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export async function runCli(argv) {
|
|
374
|
+
const { command, file, options } = parseArgs(argv)
|
|
375
|
+
if (command === '--help' || command === '-h' || command === 'help') {
|
|
376
|
+
console.log('usage: exact-head-ci run --input <request.json> --out <artifact.json> | validate <artifact.json> --head <sha>')
|
|
377
|
+
return 0
|
|
378
|
+
}
|
|
379
|
+
if (command === 'run') {
|
|
380
|
+
if (!options.input || !options.out) { console.error('exact-head-ci run requires --input <request.json> --out <artifact.json>'); return 2 }
|
|
381
|
+
let interrupted = null
|
|
382
|
+
const onSignal = (signal) => { interrupted = signal }
|
|
383
|
+
process.on('SIGINT', onSignal)
|
|
384
|
+
process.on('SIGTERM', onSignal)
|
|
385
|
+
try {
|
|
386
|
+
const artifact = await runExactHead(JSON.parse(fs.readFileSync(options.input, 'utf8')), { interruption: () => interrupted })
|
|
387
|
+
fs.writeFileSync(options.out, `${JSON.stringify(artifact, null, 2)}\n`)
|
|
388
|
+
const provenance = artifact.collectionMode === 'choreographed'
|
|
389
|
+
? `restoration ${artifact.restoration?.proved ? 'proved' : 'unproved'}`
|
|
390
|
+
: `live head observed before and after provider evidence acquisition${artifact.observation?.after?.headSha === artifact.headSha ? '' : ' (unproved)'}`
|
|
391
|
+
console.log(`exact-head-ci: ${artifact.status === 'pass' ? 'PASS' : 'FAIL'} at ${artifact.headSha} — ${artifact.summary.outcome}; collection ${artifact.collectionMode}; ${provenance}`)
|
|
392
|
+
return artifact.status === 'pass' ? 0 : 1
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error(`exact-head-ci: ${String(error.message || error)}`)
|
|
395
|
+
return 2
|
|
396
|
+
} finally {
|
|
397
|
+
process.off('SIGINT', onSignal)
|
|
398
|
+
process.off('SIGTERM', onSignal)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (command === 'validate') {
|
|
402
|
+
if (!file || !options.head) { console.error('exact-head-ci validate <artifact.json> --head <sha>'); return 2 }
|
|
403
|
+
const artifact = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
404
|
+
const verdict = validateExactHeadArtifact(artifact, { expectedHeadSha: options.head })
|
|
405
|
+
if (verdict.ok) console.log(`exact-head-ci: valid PASS ${artifact.collectionMode} artifact at ${options.head}`)
|
|
406
|
+
else verdict.errors.forEach((error) => console.error(`error: ${error}`))
|
|
407
|
+
return verdict.ok ? 0 : 1
|
|
408
|
+
}
|
|
409
|
+
console.error('usage: exact-head-ci run --input <request.json> --out <artifact.json> | validate <artifact.json> --head <sha>')
|
|
410
|
+
return 2
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (isMainModule(import.meta.url)) process.exit(await runCli(process.argv.slice(2)))
|