@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.
Files changed (397) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +90 -0
  3. package/bin/1aboveio-skills.mjs +18 -0
  4. package/package.json +28 -0
  5. package/runtime/skills/distribution/generated/recipes.json +1189 -0
  6. package/runtime/skills/distribution/scripts/bundles.mjs +280 -0
  7. package/runtime/skills/engineering/engineering-runtime/scripts/main-module.mjs +80 -0
  8. package/skills/backend/airflow-dag-develop/LICENSE +3 -0
  9. package/skills/backend/airflow-dag-develop/SKILL.md +111 -0
  10. package/skills/backend/app-debug/LICENSE +3 -0
  11. package/skills/backend/app-debug/SKILL.md +109 -0
  12. package/skills/backend/app-debug/references/common-errors.md +128 -0
  13. package/skills/backend/python-backend/LICENSE +3 -0
  14. package/skills/backend/python-backend/SKILL.md +326 -0
  15. package/skills/cicd-pipeline/cloud-build/LICENSE +3 -0
  16. package/skills/cicd-pipeline/cloud-build/SKILL.md +707 -0
  17. package/skills/cicd-pipeline/cloud-debug/LICENSE +3 -0
  18. package/skills/cicd-pipeline/cloud-debug/SKILL.md +316 -0
  19. package/skills/cicd-pipeline/cloud-debug/references/build-failures.md +238 -0
  20. package/skills/cicd-pipeline/cloud-debug/references/deploy-failures.md +376 -0
  21. package/skills/cicd-pipeline/cloud-debug/references/pipeline-failures.md +378 -0
  22. package/skills/cicd-pipeline/cloud-deploy/LICENSE +3 -0
  23. package/skills/cicd-pipeline/cloud-deploy/SKILL.md +229 -0
  24. package/skills/cicd-pipeline/cloud-deploy/references/config-templates.md +257 -0
  25. package/skills/cicd-pipeline/docker/LICENSE +3 -0
  26. package/skills/cicd-pipeline/docker/SKILL.md +126 -0
  27. package/skills/cicd-pipeline/google-cloud/LICENSE +3 -0
  28. package/skills/cicd-pipeline/google-cloud/SKILL.md +118 -0
  29. package/skills/cicd-pipeline/google-cloud/references/gcs.md +469 -0
  30. package/skills/cicd-pipeline/google-cloud/references/iam.md +451 -0
  31. package/skills/cicd-pipeline/google-cloud/references/project.md +349 -0
  32. package/skills/cicd-pipeline/google-cloud/references/secrets.md +336 -0
  33. package/skills/cicd-pipeline/google-cloud/references/vpc.md +312 -0
  34. package/skills/cicd-pipeline/google-cloud/scripts/create-sa.sh +36 -0
  35. package/skills/cicd-pipeline/google-cloud/scripts/gcp-config.sh +31 -0
  36. package/skills/cicd-pipeline/google-cloud/scripts/grant-iap.sh +41 -0
  37. package/skills/cicd-pipeline/google-cloud/scripts/setup-secrets.sh +48 -0
  38. package/skills/cicd-pipeline/mergify/LICENSE +3 -0
  39. package/skills/cicd-pipeline/mergify/SKILL.md +138 -0
  40. package/skills/cicd-pipeline/mergify/assets/templates/mergify.yml +237 -0
  41. package/skills/cicd-pipeline/mergify/assets/templates/ruleset.json +46 -0
  42. package/skills/cicd-pipeline/mergify/references/branch-protection.md +277 -0
  43. package/skills/cicd-pipeline/mergify/references/configuration.md +183 -0
  44. package/skills/cicd-pipeline/mergify/references/diagnosis.md +73 -0
  45. package/skills/cicd-pipeline/mergify/references/traps.md +78 -0
  46. package/skills/cicd-pipeline/mergify/references/watch-contract.md +218 -0
  47. package/skills/cicd-pipeline/mergify/scripts/audit-core.mjs +131 -0
  48. package/skills/cicd-pipeline/mergify/scripts/audit.mjs +4 -0
  49. package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery-core.mjs +663 -0
  50. package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery.mjs +4 -0
  51. package/skills/cicd-pipeline/podman/LICENSE +3 -0
  52. package/skills/cicd-pipeline/podman/SKILL.md +70 -0
  53. package/skills/cicd-pipeline/podman/agents/openai.yaml +4 -0
  54. package/skills/cicd-pipeline/podman/assets/templates/podman-compose-socket-directory.yml +6 -0
  55. package/skills/cicd-pipeline/podman/assets/templates/podman-service-override.conf +3 -0
  56. package/skills/cicd-pipeline/podman/references/compose-compatibility.md +70 -0
  57. package/skills/cicd-pipeline/podman/references/networking-and-ports.md +74 -0
  58. package/skills/cicd-pipeline/podman/references/rootless-services-and-sockets.md +156 -0
  59. package/skills/cicd-pipeline/podman/references/troubleshooting.md +98 -0
  60. package/skills/engineering/e2e-test/LICENSE +3 -0
  61. package/skills/engineering/e2e-test/SKILL.md +156 -0
  62. package/skills/engineering/e2e-test/assets/ci-gates.cloudbuild.yaml +272 -0
  63. package/skills/engineering/e2e-test/assets/ci-gates.github.yml +451 -0
  64. package/skills/engineering/e2e-test/assets/e2e-workflow.yml +282 -0
  65. package/skills/engineering/e2e-test/references/authoring/auth-flows.md +159 -0
  66. package/skills/engineering/e2e-test/references/authoring/playwright-config.md +71 -0
  67. package/skills/engineering/e2e-test/references/authoring/playwright-patterns.md +219 -0
  68. package/skills/engineering/e2e-test/references/authoring/test-skipping.md +44 -0
  69. package/skills/engineering/e2e-test/references/ci-integration.md +121 -0
  70. package/skills/engineering/e2e-test/references/ci-playwright-container.md +280 -0
  71. package/skills/engineering/e2e-test/references/debugging.md +36 -0
  72. package/skills/engineering/e2e-test/references/presentation-sweep.md +131 -0
  73. package/skills/engineering/e2e-test/references/reviewing.md +39 -0
  74. package/skills/engineering/e2e-test/references/route-discovery.md +50 -0
  75. package/skills/engineering/e2e-test/references/route-manifest.md +44 -0
  76. package/skills/engineering/e2e-test/scripts/detect-routes-fastapi.py +290 -0
  77. package/skills/engineering/e2e-test/scripts/detect-routes-nextjs.mjs +200 -0
  78. package/skills/engineering/e2e-test/scripts/post-visual-evidence.mjs +158 -0
  79. package/skills/engineering/e2e-test/scripts/presentation-checks.mjs +171 -0
  80. package/skills/engineering/e2e-test/scripts/presentation-perceivability.mjs +179 -0
  81. package/skills/engineering/e2e-test/scripts/presentation-reachability.mjs +154 -0
  82. package/skills/engineering/e2e-test/scripts/presentation-render-health.mjs +141 -0
  83. package/skills/engineering/e2e-test/scripts/presentation-sweep.mjs +148 -0
  84. package/skills/engineering/e2e-test/scripts/presentation-temporal.mjs +127 -0
  85. package/skills/engineering/e2e-test/scripts/presentation-visual.mjs +84 -0
  86. package/skills/engineering/e2e-test/scripts/project-route-manifest.mjs +75 -0
  87. package/skills/engineering/e2e-test/scripts/validate-manifest.mjs +106 -0
  88. package/skills/engineering/engineering-runtime/LICENSE +3 -0
  89. package/skills/engineering/engineering-runtime/SKILL.md +10 -0
  90. package/skills/engineering/engineering-runtime/agents/openai.yaml +6 -0
  91. package/skills/engineering/engineering-runtime/coherence/workflow.json +553 -0
  92. package/skills/engineering/engineering-runtime/scripts/exact-head-artifact.mjs +131 -0
  93. package/skills/engineering/engineering-runtime/scripts/head-check-set.mjs +398 -0
  94. package/skills/engineering/engineering-runtime/scripts/main-module.mjs +80 -0
  95. package/skills/engineering/engineering-runtime/scripts/mergify-yaml.mjs +11 -0
  96. package/skills/engineering/engineering-runtime/scripts/package-lock.json +43 -0
  97. package/skills/engineering/engineering-runtime/scripts/package.json +10 -0
  98. package/skills/engineering/engineering-runtime/scripts/required-check-plan.mjs +223 -0
  99. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +576 -0
  100. package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +166 -0
  101. package/skills/engineering/ensure-coverage/LICENSE +3 -0
  102. package/skills/engineering/ensure-coverage/SKILL.md +136 -0
  103. package/skills/engineering/ensure-coverage/evals/evals.json +125 -0
  104. package/skills/engineering/ensure-coverage/references/breadth/coverage-ledger.md +91 -0
  105. package/skills/engineering/ensure-coverage/references/breadth/inventory-contract.md +83 -0
  106. package/skills/engineering/ensure-coverage/references/breadth/surface-baseline.md +44 -0
  107. package/skills/engineering/ensure-coverage/references/breadth/surface-discovery.md +16 -0
  108. package/skills/engineering/ensure-coverage/references/depth/characterization.md +68 -0
  109. package/skills/engineering/ensure-coverage/references/depth/coverage.config.example.json +25 -0
  110. package/skills/engineering/ensure-coverage/references/depth/grading.md +35 -0
  111. package/skills/engineering/ensure-coverage/references/depth/mock-policy.md +87 -0
  112. package/skills/engineering/ensure-coverage/references/depth/test-smells.md +23 -0
  113. package/skills/engineering/ensure-coverage/references/enforcement/ci-contract.md +164 -0
  114. package/skills/engineering/ensure-coverage/references/enforcement/hooks.md +85 -0
  115. package/skills/engineering/ensure-coverage/references/examples/coverage-ledger.md +109 -0
  116. package/skills/engineering/ensure-coverage/references/examples/refund-flow.md +33 -0
  117. package/skills/engineering/ensure-coverage/references/presentation/axis.md +78 -0
  118. package/skills/engineering/ensure-coverage/references/presentation/runner-contract.md +74 -0
  119. package/skills/engineering/ensure-coverage/references/process/audit-mode.md +33 -0
  120. package/skills/engineering/ensure-coverage/references/process/output-template.md +139 -0
  121. package/skills/engineering/ensure-coverage/references/process/review-contract-template.md +119 -0
  122. package/skills/engineering/ensure-coverage/references/process/scope-class.md +178 -0
  123. package/skills/engineering/ensure-coverage/references/process/test-strategy.md +55 -0
  124. package/skills/engineering/ensure-coverage/schemas/coverage-config.schema.json +45 -0
  125. package/skills/engineering/ensure-coverage/schemas/coverage-file.schema.json +93 -0
  126. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.mjs +178 -0
  127. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.mjs +101 -0
  128. package/skills/engineering/ensure-coverage/scripts/ci-audit.mjs +358 -0
  129. package/skills/engineering/ensure-coverage/scripts/coverage-checklist.mjs +494 -0
  130. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs +663 -0
  131. package/skills/engineering/ensure-coverage/scripts/design-parity.mjs +591 -0
  132. package/skills/engineering/ensure-coverage/scripts/evidence-block.mjs +367 -0
  133. package/skills/engineering/ensure-coverage/scripts/lint-tests.mjs +269 -0
  134. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.mjs +176 -0
  135. package/skills/engineering/ensure-coverage/scripts/package-lock.json +76 -0
  136. package/skills/engineering/ensure-coverage/scripts/package.json +19 -0
  137. package/skills/engineering/ensure-coverage/scripts/scope-class.mjs +554 -0
  138. package/skills/engineering/harness-runtime/LICENSE +3 -0
  139. package/skills/engineering/harness-runtime/SKILL.md +18 -0
  140. package/skills/engineering/harness-runtime/agents/openai.yaml +6 -0
  141. package/skills/engineering/harness-runtime/bin/discover-models.mjs +4 -0
  142. package/skills/engineering/harness-runtime/bin/model-catalog.mjs +4 -0
  143. package/skills/engineering/harness-runtime/contracts.md +15 -0
  144. package/skills/engineering/harness-runtime/discover-models.mjs +392 -0
  145. package/skills/engineering/harness-runtime/fixtures/native-question-schemas.json +33 -0
  146. package/skills/engineering/harness-runtime/fixtures/question-responses.json +54 -0
  147. package/skills/engineering/harness-runtime/index.mjs +767 -0
  148. package/skills/engineering/harness-runtime/model-catalog.mjs +787 -0
  149. package/skills/engineering/harness-runtime/native-question-contracts.md +37 -0
  150. package/skills/engineering/harness-runtime/references/model-catalog-seed.json +159 -0
  151. package/skills/engineering/harness-runtime/references/model-catalog.md +57 -0
  152. package/skills/engineering/implement-and-pr/LICENSE +3 -0
  153. package/skills/engineering/implement-and-pr/SKILL.md +176 -0
  154. package/skills/engineering/implement-and-pr/references/ci-iteration.md +10 -0
  155. package/skills/engineering/implement-and-pr/references/closeout.md +27 -0
  156. package/skills/engineering/implement-and-pr/references/contract-complete-fix-rounds.md +34 -0
  157. package/skills/engineering/implement-and-pr/references/evidence-rules.md +39 -0
  158. package/skills/engineering/implement-and-pr/references/incremental-plan.md +16 -0
  159. package/skills/engineering/implement-and-pr/references/self-review.md +23 -0
  160. package/skills/engineering/implement-and-pr/references/tdd-mode.md +18 -0
  161. package/skills/engineering/resolve-issues/LICENSE +3 -0
  162. package/skills/engineering/resolve-issues/SKILL.md +167 -0
  163. package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +448 -0
  164. package/skills/engineering/resolve-issues/references/breaker.md +82 -0
  165. package/skills/engineering/resolve-issues/references/deliverables.md +27 -0
  166. package/skills/engineering/resolve-issues/references/delivery.md +108 -0
  167. package/skills/engineering/resolve-issues/references/evidence-lane.md +21 -0
  168. package/skills/engineering/resolve-issues/references/exact-head-ci.md +287 -0
  169. package/skills/engineering/resolve-issues/references/fan-out.md +33 -0
  170. package/skills/engineering/resolve-issues/references/finalization.md +68 -0
  171. package/skills/engineering/resolve-issues/references/guarantees.md +10 -0
  172. package/skills/engineering/resolve-issues/references/high-risk.md +29 -0
  173. package/skills/engineering/resolve-issues/references/incidents/848/README.md +156 -0
  174. package/skills/engineering/resolve-issues/references/intake.md +86 -0
  175. package/skills/engineering/resolve-issues/references/integration-gate.md +53 -0
  176. package/skills/engineering/resolve-issues/references/interference.md +87 -0
  177. package/skills/engineering/resolve-issues/references/loop.md +134 -0
  178. package/skills/engineering/resolve-issues/references/model-catalog.md +9 -0
  179. package/skills/engineering/resolve-issues/references/postmortem.md +27 -0
  180. package/skills/engineering/resolve-issues/references/pre-flight-model-slots.md +41 -0
  181. package/skills/engineering/resolve-issues/references/pre-flight-recording-and-checkout.md +48 -0
  182. package/skills/engineering/resolve-issues/references/pre-flight.md +41 -0
  183. package/skills/engineering/resolve-issues/references/regression-checklist.md +26 -0
  184. package/skills/engineering/resolve-issues/references/run-state.md +288 -0
  185. package/skills/engineering/resolve-issues/references/sandboxed-testing.md +48 -0
  186. package/skills/engineering/resolve-issues/references/spawn-contract.md +96 -0
  187. package/skills/engineering/resolve-issues/references/terminal-evidence-journal.md +40 -0
  188. package/skills/engineering/resolve-issues/references/why.md +653 -0
  189. package/skills/engineering/resolve-issues/schemas/fix-round.schema.json +49 -0
  190. package/skills/engineering/resolve-issues/scripts/combine-and-verify.mjs +721 -0
  191. package/skills/engineering/resolve-issues/scripts/component-candidate.mjs +962 -0
  192. package/skills/engineering/resolve-issues/scripts/contract-revision.mjs +220 -0
  193. package/skills/engineering/resolve-issues/scripts/detect-delivery-mode.mjs +420 -0
  194. package/skills/engineering/resolve-issues/scripts/detect-target-branch.mjs +256 -0
  195. package/skills/engineering/resolve-issues/scripts/detect-workspace-mode.mjs +168 -0
  196. package/skills/engineering/resolve-issues/scripts/discover-models.mjs +9 -0
  197. package/skills/engineering/resolve-issues/scripts/doctrine.mjs +62 -0
  198. package/skills/engineering/resolve-issues/scripts/evidence-lifecycle-contract.mjs +191 -0
  199. package/skills/engineering/resolve-issues/scripts/exact-head-ci.mjs +413 -0
  200. package/skills/engineering/resolve-issues/scripts/exact-head-github-provider.mjs +332 -0
  201. package/skills/engineering/resolve-issues/scripts/finalize.mjs +488 -0
  202. package/skills/engineering/resolve-issues/scripts/fix-rounds.mjs +3307 -0
  203. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-circular-1001.json +16 -0
  204. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-valid-sequencing.json +51 -0
  205. package/skills/engineering/resolve-issues/scripts/fixtures/fmm-express-830-component-candidate.json +17 -0
  206. package/skills/engineering/resolve-issues/scripts/fixtures/head-check-set-1081.json +166 -0
  207. package/skills/engineering/resolve-issues/scripts/gate-value-series.mjs +92 -0
  208. package/skills/engineering/resolve-issues/scripts/guide-index.mjs +73 -0
  209. package/skills/engineering/resolve-issues/scripts/head-check-set.mjs +159 -0
  210. package/skills/engineering/resolve-issues/scripts/interference.mjs +427 -0
  211. package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +9 -0
  212. package/skills/engineering/resolve-issues/scripts/next-operations.mjs +419 -0
  213. package/skills/engineering/resolve-issues/scripts/postmortem.mjs +909 -0
  214. package/skills/engineering/resolve-issues/scripts/preflight-questions.mjs +322 -0
  215. package/skills/engineering/resolve-issues/scripts/reconcile-contained-unit-prs.mjs +415 -0
  216. package/skills/engineering/resolve-issues/scripts/release-state-contract.mjs +697 -0
  217. package/skills/engineering/resolve-issues/scripts/report.mjs +494 -0
  218. package/skills/engineering/resolve-issues/scripts/required-check-plan.mjs +172 -0
  219. package/skills/engineering/resolve-issues/scripts/round-metadata.mjs +79 -0
  220. package/skills/engineering/resolve-issues/scripts/run-state-review6-cases.mjs +334 -0
  221. package/skills/engineering/resolve-issues/scripts/run-state.mjs +4784 -0
  222. package/skills/engineering/resolve-issues/scripts/sandbox-selftest.mjs +395 -0
  223. package/skills/engineering/resolve-issues/scripts/spawn-contract.mjs +290 -0
  224. package/skills/engineering/resolve-issues/scripts/terminal-dispositions.mjs +170 -0
  225. package/skills/engineering/resolve-issues/scripts/terminal-evidence-journal.mjs +293 -0
  226. package/skills/engineering/resolve-issues/scripts/unit-kind.mjs +197 -0
  227. package/skills/engineering/resolve-issues/scripts/unit-lifecycle.mjs +127 -0
  228. package/skills/engineering/resolve-issues/scripts/watch-delivery.mjs +893 -0
  229. package/skills/engineering/resolve-issues/scripts/workspaces.mjs +829 -0
  230. package/skills/engineering/resolve-issues/workflows/independent-review.workflow.js +290 -0
  231. package/skills/engineering/resolve-issues/workflows/prior-art-scan.workflow.js +80 -0
  232. package/skills/engineering/resolve-issues/workflows/workflow-smoke.mjs +102 -0
  233. package/skills/engineering/resolve-release/LICENSE +3 -0
  234. package/skills/engineering/resolve-release/SKILL.md +112 -0
  235. package/skills/engineering/resolve-release/references/assembly.md +137 -0
  236. package/skills/engineering/resolve-release/references/auto-when-green.md +56 -0
  237. package/skills/engineering/resolve-release/references/candidate.md +167 -0
  238. package/skills/engineering/resolve-release/references/exposure.md +178 -0
  239. package/skills/engineering/resolve-release/references/handoff.md +24 -0
  240. package/skills/engineering/resolve-release/references/postmortem.md +230 -0
  241. package/skills/engineering/resolve-release/references/preflight.md +207 -0
  242. package/skills/engineering/resolve-release/references/principles.md +94 -0
  243. package/skills/engineering/resolve-release/references/regression-checklist.md +36 -0
  244. package/skills/engineering/resolve-release/references/related-skills.md +12 -0
  245. package/skills/engineering/resolve-release/references/routing.md +149 -0
  246. package/skills/engineering/resolve-release/references/verified-sha-github-flow.md +285 -0
  247. package/skills/engineering/resolve-release/references/versioning.md +202 -0
  248. package/skills/engineering/resolve-release/references/why.md +53 -0
  249. package/skills/engineering/resolve-release/scripts/adapter-completion-artifact.mjs +389 -0
  250. package/skills/engineering/resolve-release/scripts/build-changes.mjs +209 -0
  251. package/skills/engineering/resolve-release/scripts/candidate-hygiene.mjs +407 -0
  252. package/skills/engineering/resolve-release/scripts/candidate-identity.mjs +904 -0
  253. package/skills/engineering/resolve-release/scripts/candidate-traffic.mjs +81 -0
  254. package/skills/engineering/resolve-release/scripts/checked-adapter-loader.mjs +612 -0
  255. package/skills/engineering/resolve-release/scripts/close-attempt.mjs +135 -0
  256. package/skills/engineering/resolve-release/scripts/closeout-release.mjs +161 -0
  257. package/skills/engineering/resolve-release/scripts/doctrine.mjs +106 -0
  258. package/skills/engineering/resolve-release/scripts/durable-processing.mjs +522 -0
  259. package/skills/engineering/resolve-release/scripts/ensure-target-green.mjs +659 -0
  260. package/skills/engineering/resolve-release/scripts/evidence-bundle.mjs +1014 -0
  261. package/skills/engineering/resolve-release/scripts/finalize-release.mjs +526 -0
  262. package/skills/engineering/resolve-release/scripts/fixtures/durable-processing-adapter.mjs +169 -0
  263. package/skills/engineering/resolve-release/scripts/green-gate.mjs +599 -0
  264. package/skills/engineering/resolve-release/scripts/isolated-adapter-evaluator.mjs +752 -0
  265. package/skills/engineering/resolve-release/scripts/metadata-pr-status.mjs +56 -0
  266. package/skills/engineering/resolve-release/scripts/metadata-sync.mjs +1538 -0
  267. package/skills/engineering/resolve-release/scripts/postmortem.mjs +381 -0
  268. package/skills/engineering/resolve-release/scripts/preflight-probes.mjs +498 -0
  269. package/skills/engineering/resolve-release/scripts/production-endpoints.mjs +326 -0
  270. package/skills/engineering/resolve-release/scripts/rc-circuit-breaker.mjs +272 -0
  271. package/skills/engineering/resolve-release/scripts/report.mjs +417 -0
  272. package/skills/engineering/resolve-release/scripts/reprobe-credentials.mjs +114 -0
  273. package/skills/engineering/resolve-release/scripts/revalidate-candidate.mjs +238 -0
  274. package/skills/engineering/resolve-release/scripts/review-packet.mjs +503 -0
  275. package/skills/engineering/resolve-release/scripts/rollback-floor.mjs +263 -0
  276. package/skills/engineering/resolve-release/scripts/version-assert.mjs +339 -0
  277. package/skills/engineering/resolve-release/scripts/version-postmortem.mjs +485 -0
  278. package/skills/engineering/resolve-release/scripts/version.mjs +1199 -0
  279. package/skills/engineering/resolve-release/scripts/watch-candidate-delivery.mjs +449 -0
  280. package/skills/engineering/resolve-release/vendor/ACORN-LICENSE +21 -0
  281. package/skills/engineering/resolve-release/vendor/README.md +60 -0
  282. package/skills/engineering/resolve-release/vendor/acorn.mjs +6233 -0
  283. package/skills/engineering/resolve-release/vendor/quickjs/core/LICENSE +21 -0
  284. package/skills/engineering/resolve-release/vendor/quickjs/core/README.md +341 -0
  285. package/skills/engineering/resolve-release/vendor/quickjs/core/README.template.md +70 -0
  286. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs +2 -0
  287. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs.map +1 -0
  288. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs +7 -0
  289. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs.map +1 -0
  290. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.mts +2033 -0
  291. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.ts +2033 -0
  292. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js +7 -0
  293. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js.map +1 -0
  294. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs +2 -0
  295. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs.map +1 -0
  296. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs +2 -0
  297. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs.map +1 -0
  298. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs +2 -0
  299. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs.map +1 -0
  300. package/skills/engineering/resolve-release/vendor/quickjs/core/package.json +49 -0
  301. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/LICENSE +21 -0
  302. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/README.md +5 -0
  303. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.mts +549 -0
  304. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.ts +549 -0
  305. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js +2 -0
  306. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js.map +1 -0
  307. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs +2 -0
  308. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs.map +1 -0
  309. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/package.json +36 -0
  310. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/LICENSE +47 -0
  311. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/README.md +82 -0
  312. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.d.ts +11 -0
  313. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.mjs +22 -0
  314. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cjs +21 -0
  315. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.cjs +21 -0
  316. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.d.ts +11 -0
  317. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.d.ts +11 -0
  318. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.mjs +25 -0
  319. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.wasm +0 -0
  320. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.mts +85 -0
  321. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.ts +85 -0
  322. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js +2 -0
  323. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js.map +1 -0
  324. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs +2 -0
  325. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs.map +1 -0
  326. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.mts +20 -0
  327. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.ts +20 -0
  328. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js +2 -0
  329. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js.map +1 -0
  330. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs +2 -0
  331. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs.map +1 -0
  332. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/package.json +61 -0
  333. package/skills/engineering/review-pr/LICENSE +3 -0
  334. package/skills/engineering/review-pr/SKILL.md +123 -0
  335. package/skills/engineering/review-pr/references/adversarial-reviewer-prompt.md +42 -0
  336. package/skills/engineering/review-pr/references/code-correctness.md +5 -0
  337. package/skills/engineering/review-pr/references/contract-freshness.md +9 -0
  338. package/skills/engineering/review-pr/references/coordination.md +18 -0
  339. package/skills/engineering/review-pr/references/domain-hazards.md +123 -0
  340. package/skills/engineering/review-pr/references/finding-themes.md +7 -0
  341. package/skills/engineering/review-pr/references/github-posting.md +98 -0
  342. package/skills/engineering/review-pr/references/golden-path-smoke.md +5 -0
  343. package/skills/engineering/review-pr/references/incremental-output.md +16 -0
  344. package/skills/engineering/review-pr/references/inputs-and-discovery.md +31 -0
  345. package/skills/engineering/review-pr/references/output-format.md +99 -0
  346. package/skills/engineering/review-pr/references/over-mock-screen.md +7 -0
  347. package/skills/engineering/review-pr/references/promotion-prs.md +16 -0
  348. package/skills/engineering/review-pr/references/re-review.md +18 -0
  349. package/skills/engineering/review-pr/references/review-method.md +199 -0
  350. package/skills/engineering/review-pr/references/review-mode.md +30 -0
  351. package/skills/engineering/review-pr/references/review-posture.md +53 -0
  352. package/skills/engineering/review-pr/references/round1-depth.md +62 -0
  353. package/skills/engineering/review-pr/references/scripts.md +17 -0
  354. package/skills/engineering/review-pr/references/workflow.md +16 -0
  355. package/skills/engineering/review-pr/schemas/findings.schema.json +282 -0
  356. package/skills/engineering/review-pr/scripts/finding-contract.mjs +285 -0
  357. package/skills/engineering/review-pr/scripts/post-review.mjs +405 -0
  358. package/skills/engineering/review-pr/scripts/pr-context.mjs +207 -0
  359. package/skills/engineering/review-pr/scripts/scan-diff.mjs +365 -0
  360. package/skills/engineering/review-pr/scripts/theme-contract.mjs +57 -0
  361. package/skills/engineering/smoke/LICENSE +3 -0
  362. package/skills/engineering/smoke/SKILL.md +131 -0
  363. package/skills/engineering/smoke/assets/smoke.manifest.example.json +53 -0
  364. package/skills/engineering/smoke/references/manifest.md +192 -0
  365. package/skills/engineering/smoke/scripts/smoke.mjs +713 -0
  366. package/skills/fullstack/better-auth/LICENSE +3 -0
  367. package/skills/fullstack/better-auth/SKILL.md +601 -0
  368. package/skills/fullstack/better-auth/references/feishu-api.md +270 -0
  369. package/skills/fullstack/monorepo/LICENSE +3 -0
  370. package/skills/fullstack/monorepo/SKILL.md +465 -0
  371. package/skills/fullstack/nextjs-fullstack/LICENSE +3 -0
  372. package/skills/fullstack/nextjs-fullstack/SKILL.md +210 -0
  373. package/skills/fullstack/nextjs-fullstack/conventions.md +318 -0
  374. package/skills/fullstack/nextjs-fullstack/frontend-conventions.md +61 -0
  375. package/skills/fullstack/nextjs-fullstack/nextjs16.md +287 -0
  376. package/skills/fullstack/nextjs-fullstack/server-actions.md +409 -0
  377. package/skills/fullstack/prisma-setup/LICENSE +3 -0
  378. package/skills/fullstack/prisma-setup/SKILL.md +180 -0
  379. package/skills/fullstack/prisma-setup/nextjs.md +258 -0
  380. package/skills/fullstack/prisma-setup/turborepo.md +301 -0
  381. package/skills/fullstack/shadcn/LICENSE +3 -0
  382. package/skills/fullstack/shadcn/SKILL.md +119 -0
  383. package/skills/fullstack/shadcn/assets/shadcn-small.png +0 -0
  384. package/skills/fullstack/shadcn/assets/shadcn.png +0 -0
  385. package/skills/fullstack/shadcn/cli.md +411 -0
  386. package/skills/fullstack/shadcn/customization.md +224 -0
  387. package/skills/fullstack/shadcn/evals/evals.json +90 -0
  388. package/skills/fullstack/shadcn/mcp.md +101 -0
  389. package/skills/fullstack/shadcn/rules/base-vs-radix.md +323 -0
  390. package/skills/fullstack/shadcn/rules/component-selection.md +67 -0
  391. package/skills/fullstack/shadcn/rules/composition.md +195 -0
  392. package/skills/fullstack/shadcn/rules/data-table.md +201 -0
  393. package/skills/fullstack/shadcn/rules/forms.md +255 -0
  394. package/skills/fullstack/shadcn/rules/icons.md +103 -0
  395. package/skills/fullstack/shadcn/rules/styling.md +167 -0
  396. package/skills/fullstack/zod-v4/LICENSE +3 -0
  397. 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
+ }