@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,697 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
// Canonical vocabulary for a resolve-release run manifest and its durable evidence.
|
|
4
|
+
|
|
5
|
+
export const RELEASE_STEPS = Object.freeze([
|
|
6
|
+
'intake', 'merge', 'promote', 'deploy', 'validate', 'expose', 'verify', 'discard',
|
|
7
|
+
'rollback', 'done',
|
|
8
|
+
])
|
|
9
|
+
export const RELEASE_TERMINAL_STEPS = Object.freeze(
|
|
10
|
+
RELEASE_STEPS.filter((step) => ['discard', 'rollback', 'done'].includes(step)),
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
export const ATTEMPT_NONTERMINAL_STATE = 'open'
|
|
14
|
+
export const ATTEMPT_TERMINAL_STATES = Object.freeze([
|
|
15
|
+
'released', 'failed-pre-promotion', 'rolled-back', 'revoked', 'blocked-manual',
|
|
16
|
+
])
|
|
17
|
+
export const ATTEMPT_STATES = Object.freeze([
|
|
18
|
+
ATTEMPT_NONTERMINAL_STATE,
|
|
19
|
+
...ATTEMPT_TERMINAL_STATES,
|
|
20
|
+
])
|
|
21
|
+
export const ATTEMPT_ID_KEY_SAFE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/
|
|
22
|
+
export const ATTEMPT_LEASE_CLOSING_STATES = Object.freeze([
|
|
23
|
+
'released', 'failed-pre-promotion', 'rolled-back', 'revoked',
|
|
24
|
+
])
|
|
25
|
+
export const ATTEMPT_LEASE_HOLDING_STATES = Object.freeze([
|
|
26
|
+
ATTEMPT_NONTERMINAL_STATE,
|
|
27
|
+
'blocked-manual',
|
|
28
|
+
])
|
|
29
|
+
export const ATTEMPT_CLOSE_CAUSES = Object.freeze({
|
|
30
|
+
released: 'verified-release',
|
|
31
|
+
'failed-pre-promotion': 'verified-candidate-cleanup',
|
|
32
|
+
'rolled-back': 'verified-rollback',
|
|
33
|
+
revoked: 'verified-revocation',
|
|
34
|
+
})
|
|
35
|
+
export const ATTEMPT_TAG_DISPOSITIONS = Object.freeze(['retained'])
|
|
36
|
+
export const ATTEMPT_IN_FLIGHT_STEPS = Object.freeze([
|
|
37
|
+
'deploy', 'validate', 'expose', 'verify', 'discard', 'rollback',
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
export const RELEASE_VERDICT_STATES = Object.freeze([
|
|
41
|
+
'ALIVE', 'DEAD', 'EMPTY', 'CANNOT-RUN', 'BLOCKED',
|
|
42
|
+
])
|
|
43
|
+
|
|
44
|
+
export const RELEASE_EVENT_KINDS = Object.freeze(['state', 'intent', 'result'])
|
|
45
|
+
export const RELEASE_EVENT_ACTIONS = Object.freeze([
|
|
46
|
+
'tag', 'candidate', 'traffic', 'processing-control', 'github-release', 'metadata-delivery',
|
|
47
|
+
])
|
|
48
|
+
export const RELEASE_EVENT_OUTCOMES = Object.freeze([
|
|
49
|
+
'succeeded', 'failed', 'blocked', 'cancelled',
|
|
50
|
+
])
|
|
51
|
+
export const RELEASE_HISTORY_LIMIT = 64
|
|
52
|
+
export const RELEASE_REPLAY_LIMIT = 64
|
|
53
|
+
export const RELEASE_ARTIFACT_LIMIT = 8
|
|
54
|
+
export const RELEASE_LOCATOR_LIMIT = 2048
|
|
55
|
+
|
|
56
|
+
export const CANONICAL_RELEASE_MANIFEST_FIELDS = Object.freeze([
|
|
57
|
+
'runSlug', 'skill', 'createdAt', 'updatedAt', 'step', 'envelope', 'release',
|
|
58
|
+
'verification', 'rollback', 'deploy', 'promotion', 'promotionPr', 'shippedSha',
|
|
59
|
+
'backMerge', 'targetBranch', 'skillsRoot', 'originalBranch', 'worktree', 'intake',
|
|
60
|
+
'merges', 'closedIssues', 'epic', 'deliveryMode', 'queueProvider', 'enqueueTrigger',
|
|
61
|
+
'evidence', 'status', 'notes', 'note', 'productionBranch', 'terminalState',
|
|
62
|
+
'durableEvidenceDir', 'recheckFence', 'quiescence', 'mergedSha', 'rollbackTarget',
|
|
63
|
+
'decisions', 'deferrals', 'models', 'units',
|
|
64
|
+
'releaseHistory',
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
export const CANONICAL_RELEASE_ENVELOPE_FIELDS = Object.freeze([
|
|
68
|
+
'repo', 'service', 'version', 'mode', 'deployTrigger', 'deployTriggerReason',
|
|
69
|
+
'displayVersionPolicy', 'displayVersionPolicyException', 'durableProcessing',
|
|
70
|
+
'exposureIdentity', 'floorReadiness', 'rollbackPolicy',
|
|
71
|
+
'candidateDeletableWithoutConfirm', 'reversibilityConfirmed', 'irreversibleContent',
|
|
72
|
+
'verdictFreshnessMinutes',
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
export const CANONICAL_RELEASE_FIELDS = Object.freeze([
|
|
76
|
+
'attempt', 'backMerge', 'blockingFindings', 'burned', 'candidateIdentity',
|
|
77
|
+
'candidateRevision', 'candidateSha', 'candidateUrl', 'candidateVerdict', 'cutCommit',
|
|
78
|
+
'ensureGreen', 'fastForwardable', 'floorReadiness', 'integrationCi',
|
|
79
|
+
'integrationSmoke', 'metadataPr', 'previousRevision', 'prLaneCi', 'quiescence',
|
|
80
|
+
'productionEndpoints', 'rcTag', 'recheckFence', 'releaseReadiness',
|
|
81
|
+
'servingConfirmation', 'liveVerdict', 'circuitBreaker', 'tagged', 'tagSha', 'version',
|
|
82
|
+
'versionPlan',
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
export const CANONICAL_RELEASE_VERIFICATION_FIELDS = Object.freeze([
|
|
86
|
+
'finalizedAt', 'finalizedBy', 'candidate', 'candidateVerdict', 'live', 'liveVerdict',
|
|
87
|
+
'serving', 'servingConfirmation', 'recheckFence', 'quiescence',
|
|
88
|
+
])
|
|
89
|
+
|
|
90
|
+
export const RELEASE_EVIDENCE_FIELDS = Object.freeze([
|
|
91
|
+
'attemptId', 'sha', 'buildIdentity', 'terminalState', 'closedBy', 'rcTag',
|
|
92
|
+
'deployTrigger', 'candidateReceipt', 'candidateVerdict', 'approval',
|
|
93
|
+
'trafficTransition', 'liveVerdict', 'disposition', 'releaseTag', 'releaseRef',
|
|
94
|
+
'verification', 'durableProcessing', 'tagDisposition', 'reason', 'failureOwner',
|
|
95
|
+
'failurePhase', 'emittedAt',
|
|
96
|
+
])
|
|
97
|
+
|
|
98
|
+
export const RELEASE_STATE_CONTRACT = Object.freeze({
|
|
99
|
+
terminalSteps: RELEASE_TERMINAL_STEPS,
|
|
100
|
+
steps: RELEASE_STEPS,
|
|
101
|
+
attempt: Object.freeze({
|
|
102
|
+
nonterminalState: ATTEMPT_NONTERMINAL_STATE,
|
|
103
|
+
terminalStates: ATTEMPT_TERMINAL_STATES,
|
|
104
|
+
states: ATTEMPT_STATES,
|
|
105
|
+
leaseClosingStates: ATTEMPT_LEASE_CLOSING_STATES,
|
|
106
|
+
leaseHoldingStates: ATTEMPT_LEASE_HOLDING_STATES,
|
|
107
|
+
closeCauses: ATTEMPT_CLOSE_CAUSES,
|
|
108
|
+
tagDispositions: ATTEMPT_TAG_DISPOSITIONS,
|
|
109
|
+
inFlightSteps: ATTEMPT_IN_FLIGHT_STEPS,
|
|
110
|
+
}),
|
|
111
|
+
verdictStates: RELEASE_VERDICT_STATES,
|
|
112
|
+
history: Object.freeze({
|
|
113
|
+
field: 'releaseHistory',
|
|
114
|
+
limit: RELEASE_HISTORY_LIMIT,
|
|
115
|
+
replayLimit: RELEASE_REPLAY_LIMIT,
|
|
116
|
+
kinds: RELEASE_EVENT_KINDS,
|
|
117
|
+
actions: RELEASE_EVENT_ACTIONS,
|
|
118
|
+
outcomes: RELEASE_EVENT_OUTCOMES,
|
|
119
|
+
}),
|
|
120
|
+
fields: Object.freeze({
|
|
121
|
+
manifest: CANONICAL_RELEASE_MANIFEST_FIELDS,
|
|
122
|
+
envelope: CANONICAL_RELEASE_ENVELOPE_FIELDS,
|
|
123
|
+
release: CANONICAL_RELEASE_FIELDS,
|
|
124
|
+
verification: CANONICAL_RELEASE_VERIFICATION_FIELDS,
|
|
125
|
+
evidence: RELEASE_EVIDENCE_FIELDS,
|
|
126
|
+
}),
|
|
127
|
+
identity: Object.freeze({
|
|
128
|
+
repository: Object.freeze(['envelope.repo']),
|
|
129
|
+
service: Object.freeze(['envelope.service']),
|
|
130
|
+
model: Object.freeze([
|
|
131
|
+
'models.implementation', 'models.review', 'units[].implementationModel',
|
|
132
|
+
]),
|
|
133
|
+
}),
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const isObject = (value) => value != null && typeof value === 'object' && !Array.isArray(value)
|
|
137
|
+
const nonEmpty = (value) => typeof value === 'string' && value.trim() !== ''
|
|
138
|
+
const validIso = (value) => nonEmpty(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value
|
|
139
|
+
|
|
140
|
+
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/
|
|
141
|
+
const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/
|
|
142
|
+
const SAFE_SHA = /^[0-9a-fA-F]{7,64}$/
|
|
143
|
+
const SAFE_REVISION = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,125}[A-Za-z0-9])?$/
|
|
144
|
+
const SAFE_PR = /^(?:[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)?#[1-9][0-9]*$/
|
|
145
|
+
|
|
146
|
+
export const RELEASE_ARTIFACT_TYPES = Object.freeze({
|
|
147
|
+
'release-state': Object.freeze(['manifest', 'tag']),
|
|
148
|
+
tag: Object.freeze(['manifest', 'tag', 'sha']),
|
|
149
|
+
candidate: Object.freeze(['manifest', 'tag', 'sha', 'revision', 'url']),
|
|
150
|
+
traffic: Object.freeze(['manifest', 'revision', 'url', 'service']),
|
|
151
|
+
'processing-control': Object.freeze(['manifest', 'service', 'url']),
|
|
152
|
+
'github-release': Object.freeze(['manifest', 'tag', 'sha', 'url']),
|
|
153
|
+
'metadata-delivery': Object.freeze(['manifest', 'pull-request', 'sha', 'url', 'tag']),
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
const MANIFEST_LOCATORS = Object.freeze({
|
|
157
|
+
'release-state': Object.freeze(['step', 'release.attempt', 'release.attempt.evidence']),
|
|
158
|
+
tag: Object.freeze(['release.rcTag', 'release.attempt.rcTag', 'release.tagged', 'release.tagSha']),
|
|
159
|
+
candidate: Object.freeze([
|
|
160
|
+
'release.candidateIdentity', 'release.candidateRevision', 'release.candidateSha',
|
|
161
|
+
'release.candidateUrl', 'release.candidateVerdict',
|
|
162
|
+
]),
|
|
163
|
+
traffic: Object.freeze([
|
|
164
|
+
'release.recheckFence', 'release.previousRevision', 'verification.live',
|
|
165
|
+
'verification.liveVerdict', 'verification.serving',
|
|
166
|
+
]),
|
|
167
|
+
'processing-control': Object.freeze(['envelope.durableProcessing', 'release.quiescence']),
|
|
168
|
+
'github-release': Object.freeze(['release.tagged', 'release.tagSha', 'evidence.releaseRef']),
|
|
169
|
+
'metadata-delivery': Object.freeze(['release.metadataPr']),
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
function canonicalJson(value) {
|
|
173
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`
|
|
174
|
+
if (isObject(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`
|
|
175
|
+
return JSON.stringify(value)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function releaseReplayDigest(entry) {
|
|
179
|
+
const { digest, ...payload } = entry
|
|
180
|
+
return createHash('sha256').update(canonicalJson(payload)).digest('hex')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function canonicalVersions(manifest) {
|
|
184
|
+
const candidates = [
|
|
185
|
+
manifest?.release?.version,
|
|
186
|
+
typeof manifest?.envelope?.version === 'string' ? manifest.envelope.version : null,
|
|
187
|
+
manifest?.envelope?.version?.next,
|
|
188
|
+
manifest?.release?.versionPlan?.version,
|
|
189
|
+
manifest?.release?.versionPlan?.next,
|
|
190
|
+
]
|
|
191
|
+
return candidates.filter(nonEmpty).map((value) => value.replace(/^v/, ''))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function uniqueLocators(values) {
|
|
195
|
+
return [...new Set(values.filter(nonEmpty))]
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function finalTags(manifest) {
|
|
199
|
+
return uniqueLocators(canonicalVersions(manifest).map((version) => `v${version}`))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function canonicalLocators(manifest, subject, type) {
|
|
203
|
+
const release = manifest?.release || {}
|
|
204
|
+
const envelope = manifest?.envelope || {}
|
|
205
|
+
const identity = release.candidateIdentity || {}
|
|
206
|
+
const metadata = release.metadataPr || {}
|
|
207
|
+
const releaseRef = manifest?.evidence?.releaseRef
|
|
208
|
+
const releaseRefTag = isObject(releaseRef) ? releaseRef.tag : null
|
|
209
|
+
const releaseRefUrl = isObject(releaseRef) ? releaseRef.url : releaseRef
|
|
210
|
+
const rcTags = [release.rcTag, release.attempt?.rcTag]
|
|
211
|
+
|
|
212
|
+
const bySubject = {
|
|
213
|
+
'release-state': {
|
|
214
|
+
tag: rcTags,
|
|
215
|
+
},
|
|
216
|
+
tag: {
|
|
217
|
+
tag: [...rcTags, ...finalTags(manifest)],
|
|
218
|
+
sha: [release.candidateSha, release.tagSha, manifest?.shippedSha],
|
|
219
|
+
},
|
|
220
|
+
candidate: {
|
|
221
|
+
tag: rcTags,
|
|
222
|
+
sha: [release.candidateSha, identity.sourceSha],
|
|
223
|
+
revision: [release.candidateRevision, identity.revision],
|
|
224
|
+
url: [release.candidateUrl, identity.url],
|
|
225
|
+
},
|
|
226
|
+
traffic: {
|
|
227
|
+
revision: [
|
|
228
|
+
release.candidateRevision,
|
|
229
|
+
release.previousRevision,
|
|
230
|
+
release.recheckFence?.candidateResolvesTo,
|
|
231
|
+
manifest?.verification?.live?.revision,
|
|
232
|
+
manifest?.verification?.serving?.revision,
|
|
233
|
+
],
|
|
234
|
+
url: [
|
|
235
|
+
release.recheckFence?.trafficUrl,
|
|
236
|
+
manifest?.verification?.live?.target,
|
|
237
|
+
manifest?.verification?.liveVerdict?.target,
|
|
238
|
+
manifest?.verification?.serving?.target,
|
|
239
|
+
],
|
|
240
|
+
service: [envelope.service],
|
|
241
|
+
},
|
|
242
|
+
'processing-control': {
|
|
243
|
+
service: [envelope.service, envelope.durableProcessing?.service, release.quiescence?.service],
|
|
244
|
+
url: [envelope.durableProcessing?.url, release.quiescence?.url],
|
|
245
|
+
},
|
|
246
|
+
'github-release': {
|
|
247
|
+
tag: [...finalTags(manifest), releaseRefTag],
|
|
248
|
+
sha: [release.tagSha, manifest?.shippedSha],
|
|
249
|
+
url: [releaseRefUrl],
|
|
250
|
+
},
|
|
251
|
+
'metadata-delivery': {
|
|
252
|
+
tag: nonEmpty(metadata.version) ? [`v${metadata.version.replace(/^v/, '')}`] : [],
|
|
253
|
+
sha: [metadata.sha],
|
|
254
|
+
url: [metadata.url],
|
|
255
|
+
'pull-request': Number.isSafeInteger(metadata.number) && metadata.number > 0
|
|
256
|
+
? [`#${metadata.number}`, nonEmpty(envelope.repo) ? `${envelope.repo}#${metadata.number}` : null]
|
|
257
|
+
: [],
|
|
258
|
+
},
|
|
259
|
+
}
|
|
260
|
+
return uniqueLocators(bySubject[subject]?.[type] || [])
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function tagErrors(locator, label) {
|
|
264
|
+
const errors = []
|
|
265
|
+
if (!SAFE_NAME.test(locator) || locator.length > 255 || locator.includes('..') || locator.includes('@{') || /[~^:?*\[\\]/.test(locator) || locator.endsWith('.lock')) {
|
|
266
|
+
return [`${label} is not a safe Git tag name`]
|
|
267
|
+
}
|
|
268
|
+
return errors
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const UNSAFE_URL_CODE_POINTS = /[\\\u2215\u2044\uff0f\u29f8\u2024\uff0e\ufe52\uff61]/u
|
|
272
|
+
|
|
273
|
+
function urlQueryErrors(locator, label) {
|
|
274
|
+
const queryStart = locator.indexOf('?')
|
|
275
|
+
if (queryStart === -1) return []
|
|
276
|
+
let decodedQuery = locator.slice(queryStart + 1).split('#', 1)[0]
|
|
277
|
+
while (true) {
|
|
278
|
+
if (CONTROL_CHARACTERS.test(decodedQuery) || decodedQuery.includes('\\')) {
|
|
279
|
+
return [`${label} query contains a control character or backslash`]
|
|
280
|
+
}
|
|
281
|
+
if (!decodedQuery.includes('%')) return []
|
|
282
|
+
if (/%(?![0-9A-Fa-f]{2})/.test(decodedQuery)) return [`${label} query contains invalid percent encoding`]
|
|
283
|
+
let next
|
|
284
|
+
try {
|
|
285
|
+
next = decodeURIComponent(decodedQuery)
|
|
286
|
+
} catch {
|
|
287
|
+
return [`${label} query contains invalid percent encoding`]
|
|
288
|
+
}
|
|
289
|
+
if (next === decodedQuery) return []
|
|
290
|
+
decodedQuery = next
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function urlPathErrors(locator, label) {
|
|
295
|
+
const authority = locator.match(/^https:\/\/[^/?#]*/i)?.[0]
|
|
296
|
+
let pathAndQuery = authority ? locator.slice(authority.length) : locator
|
|
297
|
+
const queryOrFragment = pathAndQuery.search(/[?#]/)
|
|
298
|
+
let decodedPath = queryOrFragment === -1 ? pathAndQuery : pathAndQuery.slice(0, queryOrFragment)
|
|
299
|
+
while (true) {
|
|
300
|
+
if (CONTROL_CHARACTERS.test(decodedPath) || UNSAFE_URL_CODE_POINTS.test(decodedPath)) {
|
|
301
|
+
return [`${label} contains an unsafe path separator, control, or lookalike`]
|
|
302
|
+
}
|
|
303
|
+
if (decodedPath.split('/').some((segment) => segment === '.' || segment === '..')) {
|
|
304
|
+
return [`${label} may not contain traversal segments`]
|
|
305
|
+
}
|
|
306
|
+
if (!decodedPath.includes('%')) return []
|
|
307
|
+
if (/%(?![0-9A-Fa-f]{2})/.test(decodedPath)) return [`${label} contains invalid percent encoding`]
|
|
308
|
+
let next
|
|
309
|
+
try {
|
|
310
|
+
next = decodeURIComponent(decodedPath)
|
|
311
|
+
} catch {
|
|
312
|
+
return [`${label} contains invalid percent encoding`]
|
|
313
|
+
}
|
|
314
|
+
if (next.split('/').length !== decodedPath.split('/').length || next.includes('\\')) {
|
|
315
|
+
return [`${label} percent encoding may not decode to a path separator`]
|
|
316
|
+
}
|
|
317
|
+
if (next === decodedPath) return []
|
|
318
|
+
decodedPath = next
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function urlErrors(locator, label) {
|
|
323
|
+
let url
|
|
324
|
+
try {
|
|
325
|
+
url = new URL(locator)
|
|
326
|
+
} catch {
|
|
327
|
+
return [`${label} must be an absolute HTTPS URL`]
|
|
328
|
+
}
|
|
329
|
+
const errors = []
|
|
330
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.hash || !url.hostname) errors.push(`${label} must be an absolute credential-free HTTPS URL without a fragment`)
|
|
331
|
+
errors.push(...urlPathErrors(locator, label))
|
|
332
|
+
errors.push(...urlQueryErrors(locator, label))
|
|
333
|
+
return errors
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function locatorErrors(artifact, label, manifest, subject) {
|
|
337
|
+
const { type, locator } = artifact
|
|
338
|
+
if (typeof locator !== 'string' || locator.length === 0 || locator !== locator.trim()) return [`${label}.locator must be a non-empty unpadded string`]
|
|
339
|
+
if (locator.length > RELEASE_LOCATOR_LIMIT) return [`${label}.locator exceeds ${RELEASE_LOCATOR_LIMIT} characters`]
|
|
340
|
+
if (CONTROL_CHARACTERS.test(locator)) return [`${label}.locator contains control characters`]
|
|
341
|
+
if (type !== 'url' && (locator.startsWith('/') || /^[A-Za-z]:[\\/]/.test(locator))) return [`${label}.locator may not be an absolute local path`]
|
|
342
|
+
if (type !== 'url' && locator.split(/[\\/]/).some((segment) => segment === '.' || segment === '..')) return [`${label}.locator may not contain traversal segments`]
|
|
343
|
+
if (type === 'manifest') {
|
|
344
|
+
return MANIFEST_LOCATORS[subject]?.includes(locator) ? [] : [`${label}.locator is not a canonical ${subject} manifest field`]
|
|
345
|
+
}
|
|
346
|
+
let errors = []
|
|
347
|
+
if (type === 'tag') errors = tagErrors(locator, `${label}.locator`)
|
|
348
|
+
else if (type === 'url') errors = urlErrors(locator, `${label}.locator`)
|
|
349
|
+
if (type === 'sha') {
|
|
350
|
+
if (!SAFE_SHA.test(locator)) return [`${label}.locator is not a Git SHA`]
|
|
351
|
+
} else if (type === 'revision' || type === 'service') {
|
|
352
|
+
if (!SAFE_REVISION.test(locator)) return [`${label}.locator is not a safe ${type} name`]
|
|
353
|
+
} else if (type === 'pull-request') {
|
|
354
|
+
if (!SAFE_PR.test(locator)) return [`${label}.locator is not a pull request reference`]
|
|
355
|
+
} else if (!['tag', 'url', 'sha'].includes(type)) {
|
|
356
|
+
return [`${label}.type is unknown`]
|
|
357
|
+
}
|
|
358
|
+
if (errors.length) return errors
|
|
359
|
+
if (subject === 'release-state' && type === 'tag') return []
|
|
360
|
+
const canonical = canonicalLocators(manifest, subject, type)
|
|
361
|
+
if (canonical.length === 0) return [`${label}.locator has no canonical ${subject} ${type} identity in the manifest`]
|
|
362
|
+
if (!canonical.includes(locator)) return [`${label}.locator does not match the canonical ${subject} ${type} identity`]
|
|
363
|
+
return []
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function artifactErrors(artifacts, label, manifest, subject) {
|
|
367
|
+
if (!Array.isArray(artifacts) || artifacts.length === 0) return [`${label}.artifacts must be a non-empty array`]
|
|
368
|
+
if (artifacts.length > RELEASE_ARTIFACT_LIMIT) return [`${label}.artifacts exceeds ${RELEASE_ARTIFACT_LIMIT}`]
|
|
369
|
+
const errors = []
|
|
370
|
+
for (const [index, artifact] of artifacts.entries()) {
|
|
371
|
+
if (!isObject(artifact) || !nonEmpty(artifact.type) || !nonEmpty(artifact.locator)) {
|
|
372
|
+
errors.push(`${label}.artifacts[${index}] must be {type, locator} with non-empty strings`)
|
|
373
|
+
} else if (Object.keys(artifact).some((key) => !['type', 'locator'].includes(key))) {
|
|
374
|
+
errors.push(`${label}.artifacts[${index}] contains unknown fields`)
|
|
375
|
+
} else if (!RELEASE_ARTIFACT_TYPES[subject]?.includes(artifact.type)) {
|
|
376
|
+
errors.push(`${label}.artifacts[${index}].type ${artifact.type} is not allowed for ${subject}`)
|
|
377
|
+
} else {
|
|
378
|
+
errors.push(...locatorErrors(artifact, `${label}.artifacts[${index}]`, manifest, subject))
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return errors
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function stateShapeErrors(value, label) {
|
|
385
|
+
if (!isObject(value)) return [`${label} must be an object`]
|
|
386
|
+
const errors = []
|
|
387
|
+
if (value.step !== null && !RELEASE_STEPS.includes(value.step)) errors.push(`${label}.step is not canonical`)
|
|
388
|
+
if (value.attemptState !== null && !ATTEMPT_STATES.includes(value.attemptState)) errors.push(`${label}.attemptState is not canonical`)
|
|
389
|
+
if (Object.keys(value).some((key) => !['step', 'attemptState'].includes(key))) errors.push(`${label} contains unknown fields`)
|
|
390
|
+
return errors
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function eventErrors(event, expectedSequence, label, manifest) {
|
|
394
|
+
if (!isObject(event)) return [`${label} must be an object`]
|
|
395
|
+
const errors = []
|
|
396
|
+
const common = ['id', 'sequence', 'at', 'kind', 'subject', 'artifacts']
|
|
397
|
+
if (event.id !== `release-event-${expectedSequence}`) errors.push(`${label}.id must equal release-event-${expectedSequence}`)
|
|
398
|
+
if (event.sequence !== expectedSequence) errors.push(`${label}.sequence must be contiguous (${expectedSequence})`)
|
|
399
|
+
if (!validIso(event.at)) errors.push(`${label}.at must be a canonical ISO writer timestamp`)
|
|
400
|
+
if (!RELEASE_EVENT_KINDS.includes(event.kind)) errors.push(`${label}.kind is not canonical`)
|
|
401
|
+
if (event.kind === 'state') {
|
|
402
|
+
errors.push(...artifactErrors(event.artifacts, label, manifest, 'release-state'))
|
|
403
|
+
const allowed = [...common, 'prior', 'next']
|
|
404
|
+
if (event.subject !== 'release-state') errors.push(`${label}.subject must be release-state`)
|
|
405
|
+
errors.push(...stateShapeErrors(event.prior, `${label}.prior`))
|
|
406
|
+
errors.push(...stateShapeErrors(event.next, `${label}.next`))
|
|
407
|
+
if (JSON.stringify(event.prior) === JSON.stringify(event.next)) errors.push(`${label} state delta must change`)
|
|
408
|
+
if (Object.keys(event).some((key) => !allowed.includes(key))) errors.push(`${label} contains unknown fields`)
|
|
409
|
+
} else {
|
|
410
|
+
errors.push(...artifactErrors(event.artifacts, label, manifest, event.subject))
|
|
411
|
+
const allowed = [...common, 'correlationId', 'state', ...(event.kind === 'result' ? ['outcome'] : [])]
|
|
412
|
+
if (!RELEASE_EVENT_ACTIONS.includes(event.subject)) errors.push(`${label}.subject is not a canonical outward action`)
|
|
413
|
+
if (!nonEmpty(event.correlationId)) errors.push(`${label}.correlationId must be non-empty`)
|
|
414
|
+
errors.push(...stateShapeErrors(event.state, `${label}.state`))
|
|
415
|
+
if (event.kind === 'result' && !RELEASE_EVENT_OUTCOMES.includes(event.outcome)) errors.push(`${label}.outcome is not canonical`)
|
|
416
|
+
if (event.kind === 'intent' && event.outcome !== undefined) errors.push(`${label}.intent may not carry outcome`)
|
|
417
|
+
if (Object.keys(event).some((key) => !allowed.includes(key))) errors.push(`${label} contains unknown fields`)
|
|
418
|
+
}
|
|
419
|
+
return errors
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function replayPayload(event) {
|
|
423
|
+
const payload = { state: event?.state, artifacts: event?.artifacts }
|
|
424
|
+
if (event?.kind === 'result') payload.outcome = event.outcome
|
|
425
|
+
return payload
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function replayLedgerErrors(manifest, history) {
|
|
429
|
+
const actionEvents = history.events.filter((event) => event?.kind === 'intent' || event?.kind === 'result')
|
|
430
|
+
const summarizedActions = RELEASE_EVENT_ACTIONS.reduce(
|
|
431
|
+
(total, subject) => total + (history.truncated?.bySubject?.[subject] || 0), 0,
|
|
432
|
+
)
|
|
433
|
+
const ledger = history.replayLedger
|
|
434
|
+
if (ledger === undefined) {
|
|
435
|
+
return actionEvents.length || summarizedActions
|
|
436
|
+
? ['releaseHistory.replayLedger is required for outward-action replay protection']
|
|
437
|
+
: []
|
|
438
|
+
}
|
|
439
|
+
if (!Array.isArray(ledger) || ledger.length > RELEASE_REPLAY_LIMIT) {
|
|
440
|
+
return [`releaseHistory.replayLedger must be an array bounded at ${RELEASE_REPLAY_LIMIT}`]
|
|
441
|
+
}
|
|
442
|
+
const errors = []
|
|
443
|
+
const correlations = new Set()
|
|
444
|
+
const identities = new Set()
|
|
445
|
+
for (const [index, entry] of ledger.entries()) {
|
|
446
|
+
const label = `releaseHistory.replayLedger[${index}]`
|
|
447
|
+
if (!isObject(entry)) {
|
|
448
|
+
errors.push(`${label} must be an object`)
|
|
449
|
+
continue
|
|
450
|
+
}
|
|
451
|
+
const allowed = ['subject', 'correlationId', 'intentSequence', 'intentAt', 'intent', 'resultSequence', 'resultAt', 'result', 'digest']
|
|
452
|
+
if (Object.keys(entry).some((key) => !allowed.includes(key))) errors.push(`${label} contains unknown fields`)
|
|
453
|
+
if (!RELEASE_EVENT_ACTIONS.includes(entry.subject)) errors.push(`${label}.subject is not canonical`)
|
|
454
|
+
if (!nonEmpty(entry.correlationId) || CONTROL_CHARACTERS.test(entry.correlationId) || entry.correlationId.length > 255) errors.push(`${label}.correlationId is malformed`)
|
|
455
|
+
const identity = `${entry.subject}:${entry.correlationId}`
|
|
456
|
+
if (identities.has(identity)) errors.push(`${label} duplicates action identity ${identity}`)
|
|
457
|
+
if (correlations.has(entry.correlationId)) errors.push(`${label} collides on correlationId ${entry.correlationId}`)
|
|
458
|
+
identities.add(identity)
|
|
459
|
+
correlations.add(entry.correlationId)
|
|
460
|
+
if (!Number.isSafeInteger(entry.intentSequence) || entry.intentSequence < 1 || !validIso(entry.intentAt)) errors.push(`${label} has malformed intent sequence/time`)
|
|
461
|
+
if (!isObject(entry.intent)) errors.push(`${label}.intent must retain canonical payload evidence`)
|
|
462
|
+
else {
|
|
463
|
+
errors.push(...stateShapeErrors(entry.intent.state, `${label}.intent.state`))
|
|
464
|
+
errors.push(...artifactErrors(entry.intent.artifacts, `${label}.intent`, manifest, entry.subject))
|
|
465
|
+
if (Object.keys(entry.intent).some((key) => !['state', 'artifacts'].includes(key))) errors.push(`${label}.intent contains unknown fields`)
|
|
466
|
+
}
|
|
467
|
+
if (entry.result === null) {
|
|
468
|
+
if (entry.resultSequence !== null || entry.resultAt !== null) errors.push(`${label} unresolved intent must have null result sequence/time`)
|
|
469
|
+
} else if (!isObject(entry.result)) {
|
|
470
|
+
errors.push(`${label}.result must be null or canonical payload evidence`)
|
|
471
|
+
} else {
|
|
472
|
+
if (!Number.isSafeInteger(entry.resultSequence) || entry.resultSequence <= entry.intentSequence || !validIso(entry.resultAt) || Date.parse(entry.resultAt) < Date.parse(entry.intentAt)) errors.push(`${label} has malformed result sequence/time`)
|
|
473
|
+
errors.push(...stateShapeErrors(entry.result.state, `${label}.result.state`))
|
|
474
|
+
errors.push(...artifactErrors(entry.result.artifacts, `${label}.result`, manifest, entry.subject))
|
|
475
|
+
if (!RELEASE_EVENT_OUTCOMES.includes(entry.result.outcome)) errors.push(`${label}.result.outcome is not canonical`)
|
|
476
|
+
if (Object.keys(entry.result).some((key) => !['state', 'artifacts', 'outcome'].includes(key))) errors.push(`${label}.result contains unknown fields`)
|
|
477
|
+
}
|
|
478
|
+
if (!/^[0-9a-f]{64}$/.test(entry.digest) || releaseReplayDigest(entry) !== entry.digest) errors.push(`${label}.digest does not match canonical replay evidence`)
|
|
479
|
+
}
|
|
480
|
+
const sequenceOwners = new Map()
|
|
481
|
+
for (const [index, entry] of ledger.entries()) {
|
|
482
|
+
if (!isObject(entry)) continue
|
|
483
|
+
for (const kind of ['intent', 'result']) {
|
|
484
|
+
const sequence = entry[`${kind}Sequence`]
|
|
485
|
+
if (sequence === null || !Number.isSafeInteger(sequence)) continue
|
|
486
|
+
if (sequenceOwners.has(sequence)) errors.push(`releaseHistory.replayLedger[${index}].${kind}Sequence collides with ${sequenceOwners.get(sequence)}`)
|
|
487
|
+
sequenceOwners.set(sequence, `replayLedger[${index}].${kind}Sequence`)
|
|
488
|
+
const retained = history.events.find((event) => event.sequence === sequence)
|
|
489
|
+
if (sequence > (history.truncated?.throughSequence || 0) && !retained) errors.push(`releaseHistory.replayLedger[${index}].${kind}Sequence is neither compacted nor retained`)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const byIdentity = new Map(ledger.filter(isObject).map((entry) => [`${entry.subject}:${entry.correlationId}`, entry]))
|
|
493
|
+
for (const [index, event] of history.events.entries()) {
|
|
494
|
+
if (event?.kind !== 'intent' && event?.kind !== 'result') continue
|
|
495
|
+
const entry = byIdentity.get(`${event.subject}:${event.correlationId}`)
|
|
496
|
+
const kind = event.kind
|
|
497
|
+
if (!entry || canonicalJson(entry[kind]) !== canonicalJson(replayPayload(event)) || entry[`${kind}Sequence`] !== event.sequence || entry[`${kind}At`] !== event.at) {
|
|
498
|
+
errors.push(`releaseHistory.events[${index}] does not match replay ledger evidence`)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (history.truncated) {
|
|
502
|
+
const through = history.truncated.throughSequence
|
|
503
|
+
const compactedBySubject = Object.fromEntries(RELEASE_EVENT_ACTIONS.map((subject) => [subject, 0]))
|
|
504
|
+
const compactedByKind = { intent: 0, result: 0 }
|
|
505
|
+
const compactedByOutcome = {}
|
|
506
|
+
let completedWaitMs = 0
|
|
507
|
+
let failedWaitMs = 0
|
|
508
|
+
for (const entry of ledger.filter(isObject)) {
|
|
509
|
+
if (entry.intentSequence <= through) { compactedBySubject[entry.subject] += 1; compactedByKind.intent += 1 }
|
|
510
|
+
if (entry.resultSequence !== null && entry.resultSequence <= through) {
|
|
511
|
+
compactedBySubject[entry.subject] += 1; compactedByKind.result += 1
|
|
512
|
+
compactedByOutcome[entry.result.outcome] = (compactedByOutcome[entry.result.outcome] || 0) + 1
|
|
513
|
+
const wait = Date.parse(entry.resultAt) - Date.parse(entry.intentAt)
|
|
514
|
+
completedWaitMs += wait; if (entry.result.outcome === 'failed') failedWaitMs += wait
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
for (const subject of RELEASE_EVENT_ACTIONS) {
|
|
518
|
+
if (compactedBySubject[subject] !== (history.truncated.bySubject?.[subject] || 0)) errors.push(`releaseHistory.truncated.bySubject.${subject} does not match replay ledger sequence evidence`)
|
|
519
|
+
}
|
|
520
|
+
for (const kind of ['intent', 'result']) {
|
|
521
|
+
if (compactedByKind[kind] !== (history.truncated.byKind?.[kind] || 0)) errors.push(`releaseHistory.truncated.byKind.${kind} does not match replay ledger sequence evidence`)
|
|
522
|
+
}
|
|
523
|
+
for (const outcome of RELEASE_EVENT_OUTCOMES) {
|
|
524
|
+
if ((compactedByOutcome[outcome] || 0) !== (history.truncated.byOutcome?.[outcome] || 0)) errors.push(`releaseHistory.truncated.byOutcome.${outcome} does not match replay ledger sequence evidence`)
|
|
525
|
+
}
|
|
526
|
+
if (completedWaitMs !== history.truncated.completedWaitMs) errors.push('releaseHistory.truncated.completedWaitMs does not match replay ledger intervals')
|
|
527
|
+
if (failedWaitMs !== history.truncated.failedWaitMs) errors.push('releaseHistory.truncated.failedWaitMs does not match replay ledger intervals')
|
|
528
|
+
const expectedOpen = ledger.filter((entry) => isObject(entry) && entry.intentSequence <= through && (entry.resultSequence === null || entry.resultSequence > through)).map((entry) => ({ subject: entry.subject, correlationId: entry.correlationId, at: entry.intentAt }))
|
|
529
|
+
if (canonicalJson(expectedOpen) !== canonicalJson(history.truncated.openIntents || [])) errors.push('releaseHistory.truncated.openIntents does not match its compaction boundary')
|
|
530
|
+
}
|
|
531
|
+
return errors
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export function validateReleaseHistory(manifest) {
|
|
535
|
+
if (manifest?.skill !== undefined && manifest.skill !== 'resolve-release') return []
|
|
536
|
+
const history = manifest?.releaseHistory
|
|
537
|
+
if (history === undefined || history === null) return []
|
|
538
|
+
if (!isObject(history)) return ['releaseHistory must be an object']
|
|
539
|
+
const errors = []
|
|
540
|
+
if (!Array.isArray(history.events)) return ['releaseHistory.events must be an array']
|
|
541
|
+
if (history.events.length > RELEASE_HISTORY_LIMIT) errors.push(`releaseHistory.events exceeds ${RELEASE_HISTORY_LIMIT}`)
|
|
542
|
+
errors.push(...replayLedgerErrors(manifest, history))
|
|
543
|
+
const truncated = history.truncated
|
|
544
|
+
if (truncated !== undefined) {
|
|
545
|
+
const allowed = ['count', 'firstSequence', 'firstAt', 'throughSequence', 'throughAt', 'boundary', 'byKind', 'bySubject', 'byOutcome', 'completedWaitMs', 'failedWaitMs', 'openIntents']
|
|
546
|
+
if (!isObject(truncated) || !Number.isSafeInteger(truncated.count) || truncated.count < 1) {
|
|
547
|
+
errors.push('releaseHistory.truncated must summarize at least one removed event')
|
|
548
|
+
} else {
|
|
549
|
+
if (truncated.firstSequence !== 1 || truncated.throughSequence !== truncated.count) errors.push('releaseHistory.truncated sequence range is inconsistent')
|
|
550
|
+
if (!validIso(truncated.firstAt) || !validIso(truncated.throughAt)) {
|
|
551
|
+
errors.push('releaseHistory.truncated timestamps must be canonical ISO values')
|
|
552
|
+
} else if (Date.parse(truncated.firstAt) > Date.parse(truncated.throughAt)) {
|
|
553
|
+
errors.push('releaseHistory.truncated timestamp range is reversed')
|
|
554
|
+
}
|
|
555
|
+
errors.push(...stateShapeErrors(truncated.boundary, 'releaseHistory.truncated.boundary'))
|
|
556
|
+
const counterKeys = {
|
|
557
|
+
byKind: RELEASE_EVENT_KINDS,
|
|
558
|
+
bySubject: ['release-state', ...RELEASE_EVENT_ACTIONS],
|
|
559
|
+
byOutcome: RELEASE_EVENT_OUTCOMES,
|
|
560
|
+
}
|
|
561
|
+
for (const [field, allowedKeys] of Object.entries(counterKeys)) {
|
|
562
|
+
if (!isObject(truncated[field]) || Object.values(truncated[field]).some((count) => !Number.isSafeInteger(count) || count < 1)) {
|
|
563
|
+
errors.push(`releaseHistory.truncated.${field} must contain positive counters`)
|
|
564
|
+
} else if (Object.keys(truncated[field]).some((key) => !allowedKeys.includes(key))) {
|
|
565
|
+
errors.push(`releaseHistory.truncated.${field} contains an unknown counter`)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (Object.keys(truncated).some((key) => !allowed.includes(key))) errors.push('releaseHistory.truncated contains unknown fields')
|
|
569
|
+
const counterTotal = (field) => isObject(truncated[field])
|
|
570
|
+
? Object.values(truncated[field]).reduce((total, count) => total + (Number.isSafeInteger(count) ? count : 0), 0)
|
|
571
|
+
: 0
|
|
572
|
+
if (counterTotal('byKind') !== truncated.count || counterTotal('bySubject') !== truncated.count) errors.push('releaseHistory.truncated event counters do not equal count')
|
|
573
|
+
if (counterTotal('byOutcome') !== (truncated.byKind?.result || 0)) errors.push('releaseHistory.truncated outcome counters do not equal result count')
|
|
574
|
+
|
|
575
|
+
if (!Number.isSafeInteger(truncated.completedWaitMs) || truncated.completedWaitMs < 0) errors.push('releaseHistory.truncated.completedWaitMs must be a non-negative integer')
|
|
576
|
+
if (!Number.isSafeInteger(truncated.failedWaitMs) || truncated.failedWaitMs < 0 || truncated.failedWaitMs > truncated.completedWaitMs) errors.push('releaseHistory.truncated.failedWaitMs must be between zero and completedWaitMs')
|
|
577
|
+
if (!Array.isArray(truncated.openIntents) || truncated.openIntents.length > RELEASE_EVENT_ACTIONS.length) {
|
|
578
|
+
errors.push('releaseHistory.truncated.openIntents must be bounded by the action vocabulary')
|
|
579
|
+
} else {
|
|
580
|
+
const subjects = new Set()
|
|
581
|
+
for (const [index, intent] of truncated.openIntents.entries()) {
|
|
582
|
+
if (!isObject(intent) || !RELEASE_EVENT_ACTIONS.includes(intent.subject) || !nonEmpty(intent.correlationId) || !validIso(intent.at)) {
|
|
583
|
+
errors.push(`releaseHistory.truncated.openIntents[${index}] is malformed`)
|
|
584
|
+
} else if (subjects.has(intent.subject)) {
|
|
585
|
+
errors.push(`releaseHistory.truncated.openIntents duplicates subject ${intent.subject}`)
|
|
586
|
+
}
|
|
587
|
+
subjects.add(intent?.subject)
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const firstSequence = truncated?.throughSequence ? truncated.throughSequence + 1 : 1
|
|
593
|
+
let boundary = isObject(truncated?.boundary) ? truncated.boundary : null
|
|
594
|
+
history.events.forEach((event, index) => {
|
|
595
|
+
errors.push(...eventErrors(event, firstSequence + index, `releaseHistory.events[${index}]`, manifest))
|
|
596
|
+
const priorAt = index === 0 ? truncated?.throughAt : history.events[index - 1]?.at
|
|
597
|
+
if (validIso(event?.at) && validIso(priorAt) && Date.parse(event.at) < Date.parse(priorAt)) {
|
|
598
|
+
errors.push(`releaseHistory.events[${index}].at precedes the prior writer event`)
|
|
599
|
+
}
|
|
600
|
+
const eventPrior = event?.kind === 'state' ? event.prior : event?.state
|
|
601
|
+
if (boundary && JSON.stringify(eventPrior) !== JSON.stringify(boundary)) {
|
|
602
|
+
errors.push(`releaseHistory.events[${index}] does not continue from the prior state boundary`)
|
|
603
|
+
}
|
|
604
|
+
if (isObject(eventPrior) && boundary === null) boundary = eventPrior
|
|
605
|
+
const eventNext = event?.kind === 'state' ? event.next : event?.state
|
|
606
|
+
if (isObject(eventNext)) boundary = eventNext
|
|
607
|
+
})
|
|
608
|
+
const outstanding = new Map((truncated?.openIntents || []).map((intent) => [intent.subject, intent]))
|
|
609
|
+
for (const [index, event] of history.events.entries()) {
|
|
610
|
+
if (event.kind === 'intent') {
|
|
611
|
+
if (outstanding.has(event.subject)) errors.push(`releaseHistory.events[${index}] duplicates unresolved ${event.subject} intent`)
|
|
612
|
+
outstanding.set(event.subject, event)
|
|
613
|
+
}
|
|
614
|
+
if (event.kind === 'result') {
|
|
615
|
+
const intent = outstanding.get(event.subject)
|
|
616
|
+
if (!intent || intent.correlationId !== event.correlationId) {
|
|
617
|
+
errors.push(`releaseHistory.events[${index}] result has no matching unresolved intent`)
|
|
618
|
+
} else {
|
|
619
|
+
outstanding.delete(event.subject)
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
const allowedHistory = ['events', 'truncated', 'replayLedger']
|
|
624
|
+
if (Object.keys(history).some((key) => !allowedHistory.includes(key))) errors.push('releaseHistory contains unknown fields')
|
|
625
|
+
return errors
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export function normalizeReleaseVerdict(value) {
|
|
629
|
+
if (!isObject(value)) return null
|
|
630
|
+
const state = value.state ?? value.verdict
|
|
631
|
+
if (value.state != null && value.verdict != null && value.state !== value.verdict) return null
|
|
632
|
+
if (!RELEASE_VERDICT_STATES.includes(state)) return null
|
|
633
|
+
const { verdict, ...rest } = value
|
|
634
|
+
return { ...rest, state }
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export function classifyReleaseManifest(manifest) {
|
|
638
|
+
const step = manifest?.step
|
|
639
|
+
const attemptState = manifest?.release?.attempt?.state
|
|
640
|
+
if (step === 'done' && attemptState === 'released') return 'done'
|
|
641
|
+
if (step === 'discard' && attemptState === 'failed-pre-promotion') return 'discarded'
|
|
642
|
+
if (step === 'rollback' && attemptState === 'rolled-back') return 'rolled-back'
|
|
643
|
+
if (attemptState === 'released') return 'released-but-not-finalized'
|
|
644
|
+
return 'incomplete'
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export function validateReleaseStateVocabulary(manifest) {
|
|
648
|
+
if (manifest?.skill !== 'resolve-release') return []
|
|
649
|
+
const errors = []
|
|
650
|
+
const envelope = isObject(manifest.envelope) ? manifest.envelope : {}
|
|
651
|
+
const release = isObject(manifest.release) ? manifest.release : {}
|
|
652
|
+
for (const field of ['repo', 'service']) {
|
|
653
|
+
if (envelope[field] != null && (typeof envelope[field] !== 'string' || envelope[field].trim() === '')) {
|
|
654
|
+
errors.push(`envelope.${field} must be a non-empty canonical identity string`)
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (release.candidateVerdict != null && normalizeReleaseVerdict(release.candidateVerdict) === null) {
|
|
658
|
+
errors.push('release.candidateVerdict must carry one recognized ALIVE|DEAD|EMPTY|CANNOT-RUN|BLOCKED value via state or verdict; aliases may not conflict')
|
|
659
|
+
}
|
|
660
|
+
if (release.productionEndpoints != null) {
|
|
661
|
+
const endpoints = release.productionEndpoints
|
|
662
|
+
let canonical = null
|
|
663
|
+
let service = null
|
|
664
|
+
try {
|
|
665
|
+
canonical = new URL(endpoints.canonicalProductionUrl)
|
|
666
|
+
service = new URL(endpoints.cloudRunServiceUrl)
|
|
667
|
+
} catch {}
|
|
668
|
+
const canonicalUrl = canonical && canonical.protocol === 'https:'
|
|
669
|
+
? `${canonical.origin}${canonical.pathname.replace(/\/+$/, '')}`
|
|
670
|
+
: null
|
|
671
|
+
const serviceUrl = service && service.protocol === 'https:'
|
|
672
|
+
? `${service.origin}${service.pathname.replace(/\/+$/, '')}`
|
|
673
|
+
: null
|
|
674
|
+
const distinct = canonical && service
|
|
675
|
+
? canonical.hostname.toLowerCase() !== service.hostname.toLowerCase()
|
|
676
|
+
: null
|
|
677
|
+
const fingerprint = canonicalUrl && serviceUrl
|
|
678
|
+
? createHash('sha256').update(JSON.stringify({
|
|
679
|
+
canonicalProductionUrl: canonicalUrl,
|
|
680
|
+
cloudRunServiceUrl: serviceUrl,
|
|
681
|
+
})).digest('hex')
|
|
682
|
+
: null
|
|
683
|
+
if (!isObject(endpoints)
|
|
684
|
+
|| endpoints.schemaVersion !== 1
|
|
685
|
+
|| canonicalUrl !== endpoints.canonicalProductionUrl
|
|
686
|
+
|| serviceUrl !== endpoints.cloudRunServiceUrl
|
|
687
|
+
|| endpoints.distinctPublicEdgeHost !== distinct
|
|
688
|
+
|| endpoints.publicEdgeHostname !== (distinct ? canonical.hostname.toLowerCase() : null)
|
|
689
|
+
|| endpoints.configurationFingerprint !== fingerprint
|
|
690
|
+
|| endpoints.canonicalSource !== 'deploy-trigger-config'
|
|
691
|
+
|| endpoints.serviceSource !== 'cloud-run-service'
|
|
692
|
+
|| !Number.isFinite(Date.parse(endpoints.checkedAt))) {
|
|
693
|
+
errors.push('release.productionEndpoints must be the canonical schemaVersion-1 preflight record with derived HTTPS URLs, applicability, sources, fingerprint, and checkedAt')
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return errors
|
|
697
|
+
}
|