@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,904 @@
1
+ // candidate-identity — deterministic, fail-closed candidate discovery for the release lane (issue #433).
2
+ //
3
+ // Principle 12's "out" clause used to end at "revision name and URL discoverable", and step 3
4
+ // then had to WARN that the `candidate` tag URL means "the newest candidate", not "the one I
5
+ // built" — a warning an agent must remember to act on, at exactly the point where a wrong
6
+ // answer validates one candidate under another's identity. This script replaces the warning
7
+ // with a comparison: the tag is keyed to the SHA, the revision and URL come from ONE traffic
8
+ // entry, and the running process attests the tuple. A disagreement becomes an absent or
9
+ // ambiguous read the script REFUSES, not a discrepancy a reader has to notice.
10
+ //
11
+ // Three subcommands, because the sequence really has three steps and the middle one is a
12
+ // network read this script must not take on (principle 12 assigns platform reads to the
13
+ // release lane; a script that shells out to gcloud/curl has taken on deploy mechanics):
14
+ //
15
+ // candidate-identity.mjs locate --sha <shipped> --traffic <flattened-traffic>
16
+ // Selects THE candidate traffic entry — the row tagged `candidate-<short-sha>` for the
17
+ // promoted SHA — and emits its tag, url, revision and percent. Missing, ambiguous, or a
18
+ // foreign candidate tag (a second release in flight, principle 7) all REFUSE.
19
+ // candidate-identity.mjs attest --sha <shipped> --locate <locate.json> --health <health.json>
20
+ // Binds the located coordinates to the identity the RUNNING PROCESS attests: source SHA,
21
+ // revision name, image digest. Emits `release.candidateIdentity` for the manifest.
22
+ // candidate-identity.mjs serving --sha <shipped> --identity <candidateIdentity.json> --health <live-health.json>
23
+ // Step 4b's post-shift confirmation: the live service attests the SAME tuple. This is what
24
+ // replaced comparing image-tag strings — the tuple carries the SHA, the revision AND the
25
+ // digest, and it comes from the process rather than from the revision's spec.
26
+ //
27
+ // Every subcommand after the first is handed an artifact the previous one wrote, and each one
28
+ // cross-checks that artifact against the identity it CLAIMS to describe before using it — see
29
+ // `tagBacksGuarantee`. Without that, "the record and the live process agree" is a true statement
30
+ // about the wrong release.
31
+ //
32
+ // Contract:
33
+ // exit 0 = ACCEPT — identity established (or established under a DECLARED weakening).
34
+ // exit 1 = REFUSE — the release stops here; the specific failing ids are named in `failed`.
35
+ // exit 2 = usage / unreadable-input error — bad flags, unknown subcommand, missing required file.
36
+ //
37
+ // FAIL CLOSED is the invariant, and it has a floor: identity rests on two legs — a SHA-keyed
38
+ // tag and a health attestation — and a repo whose pipeline cannot provide one DECLARES that
39
+ // (`--declare-shared-candidate-tag` / `--declare-no-attestation`, pre-flight item 3), which
40
+ // records the weakened guarantee in the artifact instead of leaving it assumed. An UNdeclared
41
+ // missing leg refuses. BOTH legs missing refuses even when both are declared: nothing keys
42
+ // identity at all then, and a declaration is a statement about a weaker guarantee, never a
43
+ // waiver of the whole check.
44
+ //
45
+ // And a declaration owes a SUBSTITUTE, not just a reason — see `evaluateAttest`'s
46
+ // `e:no-fallback-digest`. A declared weakening has to leave the operator with a check they can
47
+ // actually run; a branch that accepts and records a field the doctrine then sends someone to
48
+ // compare against, having set it to `null`, is worse than one that refuses, because it looks
49
+ // complete. That is the rule this file holds in every branch: **every field of a recorded
50
+ // identity is populated in every branch that accepts, or that branch refuses.**
51
+ //
52
+ // Its other half, and the one that was missing: a field is not made trustworthy by the branch
53
+ // that WROTE it, because the artifact carrying it can be stale, truncated or hand-edited by the
54
+ // time the next subcommand reads it. So **every field an identity record carries is re-derived or
55
+ // revalidated at the hand-off that carries it** — see the field walk below `tagBacksGuarantee`.
56
+ // Re-deriving only the SHA and the tag left an accepted locate artifact with `url: null` minting
57
+ // a candidateIdentity with a hole in it, at exit 0.
58
+
59
+ import fs from 'node:fs'
60
+ import { parseTraffic, normalizeTrafficPercents, str } from './candidate-traffic.mjs'
61
+
62
+ export { parseTraffic, normalizeTrafficPercents } from './candidate-traffic.mjs'
63
+ import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
64
+
65
+ // A short SHA is 7 hex chars (Cloud Build's $SHORT_SHA, git's default abbreviation). Below
66
+ // that, a "prefix match" stops being evidence of anything — 4 hex chars collide across a
67
+ // day's builds — so a shorter tag suffix is not treated as SHA-keyed at all.
68
+ export const MIN_SHORT_SHA = 7
69
+
70
+ // The floor lives HERE and nowhere else: every SHA this script reads — the shipped SHA, a tag
71
+ // suffix, an attested `sourceSha` — goes through this predicate, so the minimum is enforced
72
+ // once rather than re-asserted per call site. (It was re-asserted in shaMatches at first, where
73
+ // it was unreachable — both operands are already ≥ MIN_SHORT_SHA by the time they get there —
74
+ // and a guard that cannot fire reads as protection while providing none.)
75
+ const SHA_RE = new RegExp(`^[0-9a-f]{${MIN_SHORT_SHA},40}$`, 'i')
76
+ const isSha = (s) => typeof s === 'string' && SHA_RE.test(s)
77
+
78
+ // Two SHAs identify the same commit when one is a prefix of the other. Health endpoints
79
+ // commonly report $SHORT_SHA while the release lane carries the full one, so an equality-only
80
+ // comparison would fail closed on every healthy release — the wrong kind of strict.
81
+ export function shaMatches(a, b) {
82
+ if (!isSha(a) || !isSha(b)) return false
83
+ const [x, y] = [a.toLowerCase(), b.toLowerCase()]
84
+ const short = x.length <= y.length ? x : y
85
+ const long = short === x ? y : x
86
+ return long.startsWith(short)
87
+ }
88
+
89
+ // An image DIGEST, not an image tag. `algorithm:hex` is the registry-wide form, and requiring
90
+ // it is the point of the field: `myapp:latest` and `myapp:$SHA` are tags — mutable pointers a
91
+ // registry can re-point — and accepting one here would re-admit exactly the image-TAG string
92
+ // comparison the attested tuple replaced.
93
+ const isDigest = (s) => typeof s === 'string' && /^[a-z0-9][a-z0-9+.-]*:[0-9a-f]{32,}$/i.test(s)
94
+
95
+ // Digest hex is case-insensitive, and `isDigest` accepts either case — so the comparison must
96
+ // too. An endpoint echoing `SHA256:AB…` is naming the SAME artifact, and a strict `!==` would
97
+ // refuse a healthy release on a rendering choice. It fails closed, so it is safe; it is still
98
+ // wrong, and a fail-closed bug spends a release cycle to discover.
99
+ const digestMatches = (a, b) =>
100
+ typeof a === 'string' && typeof b === 'string' && a.toLowerCase() === b.toLowerCase()
101
+
102
+ // The candidate's tagged endpoint. A shape, not mere non-emptiness: this is the field the
103
+ // doctrine sends the operator to curl, and `""`, `"n/a"` and `"-"` are all truthy-or-nearly so
104
+ // while naming nothing. A field that cannot be dereferenced is the same defect as a null one —
105
+ // it just survives a truthiness check.
106
+ const isUrl = (s) => typeof s === 'string' && /^https?:\/\/\S+$/i.test(s.trim())
107
+
108
+ // ---- locate ----
109
+
110
+ // Does this tag key the candidate to a specific commit, and is that commit ours?
111
+ const DEFAULT_CANDIDATE_TAG_PREFIX = 'candidate-'
112
+
113
+ function validCandidateTagPrefix(prefix) {
114
+ return typeof prefix === 'string' && /^[a-z][a-z0-9-]{0,30}-$/.test(prefix)
115
+ }
116
+
117
+ function classifyTag(tag, sha, candidateTagPrefix = DEFAULT_CANDIDATE_TAG_PREFIX) {
118
+ if (tag === 'candidate') return 'shared'
119
+ if (!validCandidateTagPrefix(candidateTagPrefix) || !tag.startsWith(candidateTagPrefix)) {
120
+ return 'other'
121
+ }
122
+ const suffix = tag.slice(candidateTagPrefix.length)
123
+ if (!/^[0-9a-f]{4,40}$/i.test(suffix)) return 'other'
124
+ // A suffix below MIN_SHORT_SHA is not SHA-keyed at all (isSha rejects it, so shaMatches is
125
+ // false) — and it is not a rival either: `candidate-abc123` collides across a day of builds,
126
+ // so calling it another release's tag would refuse healthy releases on a coincidence.
127
+ if (!isSha(suffix)) return 'other'
128
+ return shaMatches(suffix, sha) ? 'ours' : 'foreign'
129
+ }
130
+
131
+ // ---- the cross-artifact rule (used by attest AND serving) ----
132
+ //
133
+ // THE CLASS: **every identity artifact that flows between subcommands is cross-checked against
134
+ // the identity it claims to describe — in every branch, including the DECLARED weakenings,
135
+ // which are exactly the branches where the other comparisons are absent.**
136
+ //
137
+ // A subcommand that reads a `guarantee` field and believes it has verified nothing. The tag and
138
+ // the SHA carried in the same artifact are the evidence for that claim, so both are re-derived
139
+ // at every hand-off rather than taken at their word. Two comparisons, one per leg:
140
+ //
141
+ // - the artifact's own SHA against the SHA this subcommand was given — a stale artifact from
142
+ // an earlier rc describes a different commit, and nothing else notices;
143
+ // - the artifact's tag against the guarantee it claims — `shaKeyedTag: true` is backed only by
144
+ // a tag keyed to THIS commit (`ours`), and `shaKeyedTag: false` only by the shared
145
+ // `candidate` tag. Anything else is an artifact whose claim its own coordinates contradict.
146
+ //
147
+ // What it was missing (the bug this closes): `attest` never compared its `--sha` against the
148
+ // locate artifact's. In the attested branch the tuple comparisons masked it; under
149
+ // `--declare-no-attestation` nothing did. rc.1 dies at SHA_X after `locate` wrote its artifact,
150
+ // the retry mints rc.2 at SHA_Y, a resumed session runs `attest --sha $SHIPPED` (now Y) against
151
+ // X's artifact — and it ACCEPTED, recording `sourceSha: Y` over X's revision, url and tag under
152
+ // `shaKeyedTag: true`. Step 4b then verified X's tag→revision agreement and the digest fallback
153
+ // had no digest to compare, so the release validated one candidate under another's identity.
154
+ export function tagBacksGuarantee(
155
+ tag,
156
+ sha,
157
+ shaKeyedTag,
158
+ candidateTagPrefix = DEFAULT_CANDIDATE_TAG_PREFIX,
159
+ ) {
160
+ const kind = classifyTag(str(tag), sha, candidateTagPrefix)
161
+ return shaKeyedTag ? kind === 'ours' : kind === 'shared'
162
+ }
163
+
164
+ // ---- the field walk (the general form of the rule above) ----
165
+ //
166
+ // THE RULE the table below exists to serve: **every field an identity record carries is
167
+ // re-derived or revalidated at the hand-off that carries it — never carried across on the
168
+ // strength of the upstream artifact's `decision`.** How far that rule is MECHANIZED is stated
169
+ // under the table, and it is not as far as the rule reaches.
170
+ //
171
+ // `tagBacksGuarantee` closed two fields, the SHA and the tag, and stopping there is what let the
172
+ // rest through. An ACCEPTED locate artifact whose `candidate.url` is `null` — stale, hand-edited,
173
+ // or written by an older build of this script — walked straight past `attest`, which re-derived
174
+ // the SHA and the tag, found both fine, and minted a candidateIdentity carrying `url: null` at
175
+ // exit 0. Every downstream reader treats that record as complete, and the one field the operator
176
+ // is sent to dereference is the hole. "Check the SHA and the tag" was never the rule; it was two
177
+ // instances of it.
178
+ //
179
+ // So the walk is expressed as a table rather than a run of `if`s: a field added to either record
180
+ // has to appear here, or it is being carried on trust again. Each row is (field, predicate,
181
+ // failure id) — the predicate takes the value and the context needed to re-derive it, because
182
+ // "revalidate" for `tag` means re-deriving it against the promoted SHA, not checking that a
183
+ // string is present.
184
+ //
185
+ // HOW FAR THIS GOES. The table is HAND-KEPT and nothing detects an omission from it: a field a
186
+ // record carries with no row here crosses UNWALKED, and an ACCEPT says nothing about it. "Every
187
+ // field is revalidated" is therefore the obligation on whoever adds a field, not a property this
188
+ // file enforces — which is why `--help` states the rule at the table's width and sends the reader
189
+ // to regression-checklist row 4a rather than to this exit code. #452 replaces the table with a
190
+ // structurally-validated schema, in which an unrevalidated field cannot exist to be omitted.
191
+
192
+ // Fields of a locate artifact's candidate entry that flow into candidateIdentity.
193
+ const LOCATE_HANDOFF_FIELDS = [
194
+ ['tag', (v, { sha, shaKeyedTag, candidateTagPrefix }) =>
195
+ tagBacksGuarantee(v, sha, shaKeyedTag, candidateTagPrefix), 'e:locate-guarantee-unbacked'],
196
+ ['url', (v) => isUrl(v), 'e:locate-entry-incomplete'],
197
+ ['revision', (v) => str(v).length > 0, 'e:locate-entry-incomplete'],
198
+ ]
199
+
200
+ // Fields of a candidateIdentity record, revalidated where `serving` consumes it.
201
+ const IDENTITY_RECORD_FIELDS = [
202
+ // Two different questions, deliberately two ids: `sourceSha` asks whether this record is THIS
203
+ // release's, and everything below asks whether the record is self-consistent — so the tag is
204
+ // checked against the record's OWN sourceSha, not the promoted one, or a stale artifact would
205
+ // report its every field as broken instead of reporting that it is the wrong artifact.
206
+ ['sourceSha', (v, { sha }) => shaMatches(v, sha), 'e:identity-sha-mismatch'],
207
+ ['tag', (v, { recordSha, shaKeyedTag, candidateTagPrefix }) =>
208
+ tagBacksGuarantee(v, recordSha, shaKeyedTag, candidateTagPrefix), 'e:identity-guarantee-unbacked'],
209
+ ['imageDigest', (v) => isDigest(v), 'e:identity-guarantee-unbacked'],
210
+ // WHERE the digest came from is the difference the weakening makes, so it is backed by the
211
+ // guarantee like everything else: an attested record whose digest is labelled a revision spec
212
+ // (or the reverse) is telling a reader which comparison they got, wrongly.
213
+ ['imageDigestSource', (v, { attested }) => v === (attested ? 'attestation' : 'candidate-revision-spec'),
214
+ 'e:identity-guarantee-unbacked'],
215
+ ['url', (v) => isUrl(v), 'e:identity-record-incomplete'],
216
+ ['revision', (v) => str(v).length > 0, 'e:identity-record-incomplete'],
217
+ ]
218
+
219
+ // The legs a weakening can be declared on, and the `guarantee` flag each one weakens.
220
+ const WEAKENING_LEGS = [
221
+ ['sha-keyed-tag', 'shaKeyedTag'],
222
+ ['health-attestation', 'attested'],
223
+ ]
224
+
225
+ // `guarantee` and `weakenings` are two halves of one statement — the flag and the reason — so
226
+ // each is the evidence for the other and neither is carried alone. A record claiming
227
+ // `shaKeyedTag: false` with an empty `weakenings` is a weakening with no reason recorded, which
228
+ // is the assumption the declaration flags exist to replace; a `weakenings` entry with no matching
229
+ // flag claims a weaker guarantee than the record admits to. Both are stated-vs-actual gaps of the
230
+ // same kind as an unbacked tag.
231
+ export function weakeningsBackGuarantee(weakenings, guarantee, legs = WEAKENING_LEGS) {
232
+ if (!Array.isArray(weakenings)) return false
233
+ const g = guarantee && typeof guarantee === 'object' ? guarantee : {}
234
+ for (const w of weakenings) {
235
+ if (!w || typeof w !== 'object') return false
236
+ if (!legs.some(([leg]) => leg === w.leg)) return false
237
+ // A declaration's REASON is the whole value of declaring — the same rule `declaration()`
238
+ // enforces at the CLI, re-enforced here because a hand-edited artifact never went through it.
239
+ if (str(w.reason).length === 0) return false
240
+ }
241
+ for (const [leg, flag] of legs) {
242
+ if (!(flag in g)) continue
243
+ if (weakenings.some((w) => w.leg === leg) !== (g[flag] === false)) return false
244
+ }
245
+ return true
246
+ }
247
+
248
+ // Run a field table, appending each distinct failure id once and recording what was walked, so
249
+ // the artifact shows which fields were revalidated rather than only which ones failed.
250
+ function walkFields(table, source, ctx) {
251
+ const failed = []
252
+ const walked = {}
253
+ for (const [field, ok, id] of table) {
254
+ const passed = !!ok(source ? source[field] : undefined, ctx)
255
+ walked[field] = passed
256
+ if (!passed && !failed.includes(id)) failed.push(id)
257
+ }
258
+ return { failed, walked }
259
+ }
260
+
261
+ // The locate -> attest hand-off, walked field by field. `locate.decision` is trusted (this side
262
+ // has no traffic read to re-derive it from) but every FACT that decision gated is re-established
263
+ // here, so a stale, truncated or edited artifact refuses instead of being believed.
264
+ export function walkLocateArtifact({ locate, sha }) {
265
+ const located = locate.evidence.candidate
266
+ const shaKeyedTag = !!(locate.guarantee && locate.guarantee.shaKeyedTag)
267
+ const candidateTagPrefix =
268
+ str(locate.guarantee && locate.guarantee.candidateTagPrefix) || DEFAULT_CANDIDATE_TAG_PREFIX
269
+ const failed = []
270
+
271
+ if (!validCandidateTagPrefix(candidateTagPrefix)) failed.push('e:locate-guarantee-unbacked')
272
+
273
+ // The artifact's own subject, first: everything below is only meaningful once this entry is
274
+ // known to describe the commit being released.
275
+ const locatedSha = str(locate.evidence && locate.evidence.sha)
276
+ if (!shaMatches(locatedSha, sha)) failed.push('e:locate-sha-mismatch')
277
+
278
+ const fields = walkFields(LOCATE_HANDOFF_FIELDS, located, {
279
+ sha,
280
+ shaKeyedTag,
281
+ candidateTagPrefix,
282
+ })
283
+ for (const id of fields.failed) if (!failed.includes(id)) failed.push(id)
284
+
285
+ // `percent` does not reach the identity record, but the 0% contract is a fact `locate.decision`
286
+ // gated (principle 12's out clause), and an artifact whose entry now reads otherwise is not the
287
+ // one that was accepted. Re-derived like the rest rather than inherited from the decision.
288
+ if (located.percent !== 0) failed.push('e:locate-traffic-moved')
289
+
290
+ if (!weakeningsBackGuarantee(locate.weakenings || [], locate.guarantee, [WEAKENING_LEGS[0]])) {
291
+ failed.push('e:locate-weakenings-unbacked')
292
+ }
293
+
294
+ return { failed, shaKeyedTag, walked: { sha: shaMatches(locatedSha, sha), ...fields.walked }, locatedSha }
295
+ }
296
+
297
+ // The attest -> serving hand-off, the same walk over the other record.
298
+ export function walkIdentityRecord({ id, sha }) {
299
+ const shaKeyedTag = !!(id.guarantee && id.guarantee.shaKeyedTag)
300
+ const attested = !!(id.guarantee && id.guarantee.attested)
301
+ const candidateTagPrefix =
302
+ str(id.guarantee && id.guarantee.candidateTagPrefix) || DEFAULT_CANDIDATE_TAG_PREFIX
303
+ const fields = walkFields(IDENTITY_RECORD_FIELDS, id, {
304
+ sha,
305
+ recordSha: id.sourceSha,
306
+ shaKeyedTag,
307
+ attested,
308
+ candidateTagPrefix,
309
+ })
310
+ const failed = [...fields.failed]
311
+ if (!validCandidateTagPrefix(candidateTagPrefix)) failed.push('e:identity-guarantee-unbacked')
312
+ if (!weakeningsBackGuarantee(id.weakenings || [], id.guarantee)) failed.push('e:identity-weakenings-unbacked')
313
+ return { failed, walked: fields.walked }
314
+ }
315
+
316
+ export function evaluateLocate({
317
+ sha,
318
+ traffic,
319
+ declareSharedTag = null,
320
+ candidateTagPrefix = DEFAULT_CANDIDATE_TAG_PREFIX,
321
+ }) {
322
+ const failed = []
323
+ const weakenings = []
324
+ if (!isSha(sha)) {
325
+ return { decision: 'refuse', phase: 'locate', failed: ['e:unusable-shipped-sha'], evidence: { sha } }
326
+ }
327
+ if (!validCandidateTagPrefix(candidateTagPrefix)) {
328
+ return {
329
+ decision: 'refuse',
330
+ phase: 'locate',
331
+ failed: ['e:invalid-candidate-tag-prefix'],
332
+ evidence: { sha, candidateTagPrefix },
333
+ }
334
+ }
335
+
336
+ // Normalize here too so a caller that built `traffic.rows` by hand (or re-read a capture
337
+ // that skipped parseTraffic) still gets the Cloud Run 0%-blank reconstruction.
338
+ const rows = normalizeTrafficPercents(traffic.rows)
339
+ const classified = rows.map((r) => ({
340
+ ...r,
341
+ kind: classifyTag(r.tag, sha, candidateTagPrefix),
342
+ }))
343
+ const ours = classified.filter((r) => r.kind === 'ours')
344
+ const shared = classified.filter((r) => r.kind === 'shared')
345
+ const foreign = classified.filter((r) => r.kind === 'foreign')
346
+
347
+ let candidate = null
348
+ let shaKeyedTag = true
349
+
350
+ if (ours.length > 1) {
351
+ // Two traffic entries claiming the same SHA-keyed tag is not a thing a healthy platform
352
+ // produces; whatever produced it, the read no longer names ONE artifact.
353
+ failed.push('e:ambiguous-candidate')
354
+ } else if (ours.length === 1) {
355
+ candidate = ours[0]
356
+ } else if (shared.length > 0) {
357
+ // The pipeline publishes a shared `candidate` tag rather than a SHA-keyed one. That is a
358
+ // real topology, but it is the WEAKER one — the tag means "the newest candidate" — so it
359
+ // is usable only when pre-flight item 3 declared it.
360
+ if (declareSharedTag) {
361
+ if (shared.length > 1) failed.push('e:ambiguous-candidate')
362
+ else {
363
+ candidate = shared[0]
364
+ shaKeyedTag = false
365
+ weakenings.push({ leg: 'sha-keyed-tag', reason: declareSharedTag })
366
+ }
367
+ } else {
368
+ failed.push('e:no-sha-keyed-tag')
369
+ }
370
+ } else {
371
+ failed.push('e:candidate-absent')
372
+ }
373
+
374
+ // A candidate tag keyed to a DIFFERENT commit is a second release in flight (principle 7).
375
+ // Under the shared-tag weakening this can never fire, which is precisely the guarantee the
376
+ // declaration gives up: there, two releases in flight are indistinguishable from one.
377
+ const rivals = foreign.map((r) => r.tag)
378
+ if (shaKeyedTag && candidate) {
379
+ for (const r of shared) if (r.revision && r.revision !== candidate.revision) rivals.push(r.tag)
380
+ }
381
+ if (rivals.length > 0) failed.push('e:second-candidate-in-flight')
382
+
383
+ // Revision AND url must come from the SAME entry — that is what makes them describe one
384
+ // deployment. An entry missing either cannot do that job, however present the tag is. The
385
+ // percent is read from that same entry and held to principle 12's out clause: the pipeline's
386
+ // contracted output is a candidate at **0% traffic**, and "the build succeeded but traffic
387
+ // already moved" is a pipeline defect the release stops on. That stop was prose while the
388
+ // number sat in this row as evidence for a reader to notice — the one contract fact discovery
389
+ // read and did not enforce. It is a comparison now, like every other fact here.
390
+ if (candidate) {
391
+ if (
392
+ !isUrl(candidate.url) || !candidate.revision || candidate.percent === null ||
393
+ !Number.isFinite(candidate.percent) || candidate.percent < 0 || candidate.percent > 100
394
+ ) {
395
+ failed.push('e:incomplete-traffic-entry')
396
+ } else if (candidate.percent !== 0) {
397
+ failed.push('e:candidate-traffic-moved')
398
+ }
399
+ }
400
+
401
+ const evidence = {
402
+ sha,
403
+ trafficEntries: classified.length,
404
+ rivalTags: rivals,
405
+ candidate: candidate
406
+ ? { tag: candidate.tag, url: candidate.url, revision: candidate.revision, percent: candidate.percent }
407
+ : null,
408
+ }
409
+ return {
410
+ decision: failed.length === 0 ? 'accept' : 'refuse',
411
+ phase: 'locate',
412
+ failed,
413
+ guarantee: { shaKeyedTag, candidateTagPrefix },
414
+ weakenings,
415
+ evidence,
416
+ }
417
+ }
418
+
419
+ // ---- attestation reading (shared by attest + serving) ----
420
+
421
+ // The tuple a health endpoint attests. Field aliases are deliberately narrow: the point is to
422
+ // read a real endpoint's JSON, not to accept anything tuple-shaped.
423
+ export function readAttestation(health) {
424
+ if (!health || typeof health !== 'object') return { error: 'e:unattested' }
425
+ const sourceSha = str(health.sourceSha ?? health.sha)
426
+ const revision = str(health.revision ?? health.revisionName)
427
+ const imageDigest = str(health.imageDigest ?? health.digest)
428
+ const missing = []
429
+ if (!isSha(sourceSha)) missing.push('sourceSha')
430
+ if (!revision) missing.push('revision')
431
+ if (!isDigest(imageDigest)) missing.push('imageDigest')
432
+ if (missing.length > 0) return { error: 'e:incomplete-attestation', missing, read: { sourceSha, revision, imageDigest } }
433
+ return { tuple: { sourceSha, revision, imageDigest } }
434
+ }
435
+
436
+ // ---- attest ----
437
+
438
+ export function evaluateAttest({ sha, locate, health, declareNoAttestation = null, candidateImageDigest = null }) {
439
+ const failed = []
440
+ const evidence = {}
441
+
442
+ const located = locate && locate.evidence && locate.evidence.candidate
443
+ const locateAccepted = !!locate && locate.decision === 'accept' && !!located
444
+ if (!locateAccepted) {
445
+ return {
446
+ decision: 'refuse',
447
+ phase: 'attest',
448
+ failed: ['e:no-accepted-locate'],
449
+ evidence: { locateDecision: locate && locate.decision },
450
+ }
451
+ }
452
+ if (!isSha(sha)) {
453
+ return { decision: 'refuse', phase: 'attest', failed: ['e:unusable-shipped-sha'], evidence: { sha } }
454
+ }
455
+
456
+ const shaKeyedTag = !!(locate.guarantee && locate.guarantee.shaKeyedTag)
457
+ const weakenings = [...(locate.weakenings || [])]
458
+ let attested = true
459
+ let tuple = null
460
+
461
+ // Walk the locate artifact field by field BEFORE anything downstream reads its coordinates —
462
+ // see the field walk above for the rule and the failures it closes. Not "check the SHA and the
463
+ // tag": every field that flows into the record below, plus the 0% contract the artifact's
464
+ // `decision` gated, is re-established here.
465
+ const walk = walkLocateArtifact({ locate, sha })
466
+ for (const id of walk.failed) failed.push(id)
467
+ evidence.locatedSha = walk.locatedSha || null
468
+ evidence.locateFieldWalk = walk.walked
469
+
470
+ const read = readAttestation(health)
471
+ if (read.error) {
472
+ if (!declareNoAttestation) {
473
+ failed.push(read.error)
474
+ if (read.missing) evidence.missingAttestationFields = read.missing
475
+ } else if (!shaKeyedTag) {
476
+ // The floor. A declaration weakens a guarantee; it never removes the last leg. With no
477
+ // SHA-keyed tag AND no attestation there is nothing left that ties this URL to this
478
+ // commit, so "discovery" would be the agent's belief with a JSON key on it.
479
+ failed.push('e:unverifiable-identity')
480
+ } else if (!isDigest(candidateImageDigest)) {
481
+ // A declaration owes a SUBSTITUTE. Step 4b's fallback for this branch compares the serving
482
+ // revision's image digest against `candidateIdentity.imageDigest` — and this branch used to
483
+ // record that field as `null`, so the check the weakest-pipeline repos are sent to could
484
+ // never match, and an agent reading `null` reads "not applicable" and skips it. The digest
485
+ // is available without an attesting endpoint: it is the CANDIDATE REVISION's configured
486
+ // image, a platform read the release lane is already taking beside this one. So the
487
+ // weakening is declarable only with that substitute in hand.
488
+ failed.push('e:no-fallback-digest')
489
+ } else {
490
+ attested = false
491
+ weakenings.push({
492
+ leg: 'health-attestation',
493
+ reason: declareNoAttestation,
494
+ // What is compared INSTEAD, recorded next to why — so the weaker guarantee is legible in
495
+ // the artifact rather than reconstructed from which fields happen to be set.
496
+ substitute: { imageDigest: candidateImageDigest, source: 'candidate-revision-spec' },
497
+ })
498
+ }
499
+ } else {
500
+ tuple = read.tuple
501
+ evidence.attested = tuple
502
+ if (!shaMatches(tuple.sourceSha, sha)) failed.push('e:sha-mismatch')
503
+ if (tuple.revision !== located.revision) failed.push('e:revision-mismatch')
504
+ }
505
+
506
+ const identity = {
507
+ sourceSha: tuple ? tuple.sourceSha : sha,
508
+ revision: located.revision,
509
+ imageDigest: tuple ? tuple.imageDigest : isDigest(candidateImageDigest) ? candidateImageDigest : null,
510
+ // WHERE the digest came from, because that is the whole difference the weakening makes: the
511
+ // attested one is what the running process reports, the declared one is what the revision was
512
+ // configured with. Step 4b compares the same kind on both sides either way; a reader of this
513
+ // record must be able to tell which comparison they are getting without inferring it.
514
+ imageDigestSource: tuple ? 'attestation' : 'candidate-revision-spec',
515
+ url: located.url,
516
+ tag: located.tag,
517
+ guarantee: {
518
+ shaKeyedTag,
519
+ candidateTagPrefix:
520
+ str(locate.guarantee && locate.guarantee.candidateTagPrefix) || DEFAULT_CANDIDATE_TAG_PREFIX,
521
+ attested,
522
+ },
523
+ weakenings,
524
+ }
525
+ evidence.identity = identity
526
+ evidence.locatedRevision = located.revision
527
+
528
+ return {
529
+ decision: failed.length === 0 ? 'accept' : 'refuse',
530
+ phase: 'attest',
531
+ failed,
532
+ guarantee: identity.guarantee,
533
+ weakenings,
534
+ identity: failed.length === 0 ? identity : null,
535
+ evidence,
536
+ }
537
+ }
538
+
539
+ // ---- serving (step 4b, after the shift) ----
540
+
541
+ export function evaluateServing({ identity, health, sha = null, servingImageDigest = null }) {
542
+ const failed = []
543
+ const evidence = {}
544
+ const id = identity && identity.identity ? identity.identity : identity
545
+ if (!id || typeof id !== 'object' || !id.revision) {
546
+ return { decision: 'refuse', phase: 'serving', failed: ['e:no-recorded-identity'], evidence: {} }
547
+ }
548
+ evidence.recorded = id
549
+
550
+ // The same cross-artifact rule `attest` owes its locate input, and this is the mirror gap it
551
+ // had: comparing the live tuple against the recorded one proves the record and the process
552
+ // agree, never that the record is THIS release's. Both branches below depend on it —
553
+ // including the declared fallback, which refuses to `e:no-attested-tuple` and sends the
554
+ // operator off to compare digests against *this* record's `imageDigest`. So it runs first: a
555
+ // record describing another commit is a mismatch to stop on, not a routing decision.
556
+ // The recorded `guarantee` has two legs and each is backed by a coordinate in the same record:
557
+ // `shaKeyedTag` by the tag, and BOTH values of `attested` by a usable digest. The digest
558
+ // requirement is unconditional on purpose: it used to apply only when `attested` was true, which
559
+ // left the un-attested branch — the one whose whole remaining check IS a digest comparison —
560
+ // as the single branch permitted to carry `imageDigest: null`. A record with no digest cannot
561
+ // be confirmed either way, so it refuses here rather than routing to a comparison with nothing
562
+ // on one side. The rest of the record is walked with it, same rule, same table.
563
+ const walk = walkIdentityRecord({ id, sha })
564
+ for (const f of walk.failed) failed.push(f)
565
+ evidence.identityFieldWalk = walk.walked
566
+ if (failed.length > 0) return { decision: 'refuse', phase: 'serving', failed, evidence }
567
+
568
+ const weakened = { guarantee: id.guarantee, weakenings: id.weakenings || [] }
569
+
570
+ if (!(id.guarantee && id.guarantee.attested)) {
571
+ // This release declared it has no attesting endpoint (pre-flight item 3), so there is no
572
+ // tuple to re-read — the declared fallback runs instead: the SERVING revision's configured
573
+ // image digest against the CANDIDATE revision's, both recorded, both real. It is weaker in a
574
+ // stated way (a revision's spec is not the running process) and it is a comparison, which is
575
+ // what the branch was missing: the fallback used to be a `# must equal` comment beside a
576
+ // field this branch left null.
577
+ if (!isDigest(servingImageDigest)) {
578
+ return { decision: 'refuse', phase: 'serving', failed: ['e:no-attested-tuple'], ...weakened, evidence }
579
+ }
580
+ evidence.fallback = {
581
+ recordedImageDigest: id.imageDigest,
582
+ recordedImageDigestSource: id.imageDigestSource ?? null,
583
+ servingImageDigest,
584
+ compares: 'revision-spec image digests — not the running process (declared weakening)',
585
+ }
586
+ if (!digestMatches(servingImageDigest, id.imageDigest)) failed.push('e:digest-mismatch')
587
+ return { decision: failed.length === 0 ? 'accept' : 'refuse', phase: 'serving', failed, ...weakened, evidence }
588
+ }
589
+
590
+ const read = readAttestation(health)
591
+ if (read.error) {
592
+ failed.push(read.error)
593
+ if (read.missing) evidence.missingAttestationFields = read.missing
594
+ return { decision: 'refuse', phase: 'serving', failed, evidence }
595
+ }
596
+ evidence.serving = read.tuple
597
+
598
+ if (!shaMatches(read.tuple.sourceSha, id.sourceSha)) failed.push('e:sha-mismatch')
599
+ if (read.tuple.revision !== id.revision) failed.push('e:revision-mismatch')
600
+ if (!digestMatches(read.tuple.imageDigest, id.imageDigest)) failed.push('e:digest-mismatch')
601
+
602
+ return { decision: failed.length === 0 ? 'accept' : 'refuse', phase: 'serving', failed, evidence }
603
+ }
604
+
605
+ // ---- CLI ----
606
+
607
+ const USAGE = `candidate-identity — deterministic, fail-closed candidate discovery for the release lane.
608
+
609
+ Principle 12's candidate handle is keyed to the promoted SHA and attested by the running
610
+ process, so "the newest candidate" and "the one I built" cannot be different things. Every
611
+ missing, ambiguous, or mismatched identity REFUSES; it is never a warning to notice.
612
+
613
+ Usage:
614
+ candidate-identity locate --sha <shipped> --traffic <flattened-traffic-read>
615
+ [--candidate-tag-prefix <prefix-ending-in-hyphen>]
616
+ [--declare-shared-candidate-tag <reason>]
617
+ Select THE candidate traffic entry — the row tagged \`candidate-<short-sha>\` for the
618
+ promoted SHA — and emit its tag/url/revision/percent from that ONE entry.
619
+ --candidate-tag-prefix use a repository-declared SHA-keyed namespace such as \`a-\`;
620
+ defaults to \`candidate-\` and is recorded for downstream
621
+ attest/serving cross-artifact validation
622
+ e:invalid-candidate-tag-prefix prefix is not a safe lowercase Cloud Run tag prefix
623
+ e:candidate-absent no traffic entry carries this SHA's candidate tag
624
+ e:no-sha-keyed-tag only a shared \`candidate\` tag exists and pre-flight item 3
625
+ declared no weakening — declare it or fix the pipeline
626
+ e:ambiguous-candidate more than one entry claims the candidate tag
627
+ e:second-candidate-in-flight a candidate tag keyed to another commit is present, or a
628
+ shared \`candidate\` tag resolves elsewhere (principle 7)
629
+ e:incomplete-traffic-entry the entry lacks a url, a revision or a percent, so they
630
+ cannot be read from the same deployment
631
+ e:candidate-traffic-moved the candidate is not at 0% traffic — principle 12's out
632
+ clause is a pipeline contract, so a build that shifted
633
+ traffic is a pipeline defect, not a release to continue
634
+ Input (preferred): gcloud run services describe … --format=json (full service object or
635
+ a JSON array of {tag,url,revisionName,percent}; every omitted percent is
636
+ reconstructed as 0 only when all explicit targets are valid and sum to 100%.
637
+ Legacy: --flatten='status.traffic[]' --format='value(…tag,url,revisionName,percent)'
638
+ is still accepted; prefer JSON so Cloud Run's blank 0% column is not the default.
639
+
640
+ candidate-identity attest --sha <shipped> --locate <locate.json> --health <health.json>
641
+ [--declare-no-attestation <reason> --candidate-image-digest <digest>]
642
+ Bind the located coordinates to the tuple the RUNNING PROCESS attests — source SHA,
643
+ revision name, image digest — and emit \`release.candidateIdentity\` for the manifest.
644
+ e:unattested the health read is missing or not an object
645
+ e:incomplete-attestation a tuple field is absent or unusable (imageDigest must be a
646
+ real \`algorithm:hex\` DIGEST — an image TAG is not one)
647
+ e:sha-mismatch the attested source SHA is not the promoted SHA
648
+ e:revision-mismatch the attested revision is not the located revision
649
+ e:locate-sha-mismatch the locate artifact describes a DIFFERENT commit than --sha
650
+ (a stale artifact from an earlier rc; see the cross-artifact
651
+ rule below)
652
+ e:locate-guarantee-unbacked the locate artifact's \`guarantee\` is contradicted by its own
653
+ tag — a claim, not a weaker guarantee
654
+ e:locate-entry-incomplete the artifact's candidate entry is missing a url or a revision,
655
+ so a field of the identity record would be recorded unset
656
+ e:locate-traffic-moved the artifact's candidate entry no longer reads 0% — the fact
657
+ its own \`decision\` was granted on
658
+ e:locate-weakenings-unbacked the artifact's \`weakenings\` and \`guarantee\` disagree: a
659
+ weakening with no flag, or a weakened flag with no reason
660
+ e:unverifiable-identity both legs declared away: nothing keys identity at all
661
+ e:no-fallback-digest --declare-no-attestation with no usable
662
+ --candidate-image-digest: the weakening would record
663
+ \`imageDigest: null\` and step 4b's fallback compares against
664
+ that field. Read it from the CANDIDATE REVISION's spec —
665
+ \`gcloud run revisions describe <candidate-revision> --region <r>
666
+ --format='value(status.imageDigest)'\` — which needs no
667
+ attesting endpoint.
668
+ e:no-accepted-locate the locate step did not accept
669
+
670
+ candidate-identity serving --sha <shipped> --identity <candidateIdentity.json>
671
+ --health <live-health.json> | --serving-image-digest <digest>
672
+ Step 4b's post-shift confirmation: the LIVE service attests the same tuple (SHA,
673
+ revision, digest). Replaces comparing image-tag strings.
674
+ Under a record carrying the no-attestation weakening there is no tuple to re-read, so pass
675
+ \`--serving-image-digest\` instead — the SERVING revision's configured image digest, compared
676
+ against the candidate revision's digest recorded by \`attest\`. Weaker in a stated way (a
677
+ revision's spec is not the running process), and still a comparison rather than a note.
678
+ e:no-recorded-identity no usable candidateIdentity was handed in
679
+ e:identity-sha-mismatch the recorded identity describes a different commit than
680
+ --sha, so agreeing with the live process proves nothing
681
+ e:identity-guarantee-unbacked the record's \`guarantee\` is contradicted by its own tag,
682
+ by a missing/unusable digest, or by an \`imageDigestSource\`
683
+ naming the other branch's comparison — in EITHER branch: a
684
+ record with no digest cannot be confirmed by the tuple or by
685
+ the fallback, so it stops here
686
+ e:identity-record-incomplete the record is missing a url or a revision
687
+ e:identity-weakenings-unbacked the record's \`weakenings\` and \`guarantee\` disagree
688
+ e:no-attested-tuple this release declared no attesting endpoint and no
689
+ --serving-image-digest was supplied: nothing was compared.
690
+ Take that read and re-run.
691
+ e:sha-mismatch / e:revision-mismatch / e:digest-mismatch
692
+
693
+ The cross-artifact rule, and where it stops: an identity artifact handed between subcommands is
694
+ walked against a NAMED TABLE of fields — its own SHA against the --sha it is used with, its
695
+ \`guarantee\` against the tag, digest and \`imageDigestSource\` backing it, its \`weakenings\`
696
+ against that same \`guarantee\`, and the recorded \`url\` and \`revision\` against the predicate
697
+ that makes each usable. Those fields are re-derived here rather than carried across on the
698
+ strength of the upstream artifact's \`decision\`, and the table is applied in EVERY branch,
699
+ including the declared weakenings, which are precisely the branches where the tuple comparisons
700
+ are absent. The table is also the whole of the rule: a field an artifact carries that is not
701
+ listed in it crosses UNWALKED. So an ACCEPT is not a verification of the whole record — read the
702
+ fields you are about to act on, and run regression-checklist row 4a's identity question yourself
703
+ rather than treating this exit code as having answered it.
704
+
705
+ Declared weakenings (pre-flight item 3): a repo whose pipeline cannot publish a SHA-keyed tag,
706
+ or has no attesting health endpoint, declares it with the matching --declare-* flag and its
707
+ reason. The weakening is then recorded in \`guarantee\`/\`weakenings\` — stated, not assumed. An
708
+ UNdeclared missing leg refuses, and both legs missing refuses even when both are declared.
709
+
710
+ A declaration also owes a SUBSTITUTE, not only a reason: it must leave the operator a check they
711
+ can run. --declare-no-attestation therefore requires --candidate-image-digest, so the fallback
712
+ step 4b routes to has a real digest on both sides. Every field of an emitted identity is
713
+ populated in every branch that accepts — a branch that cannot populate one refuses instead, since
714
+ a null field the doctrine sends someone to compare against reads as "not applicable" and the
715
+ check silently does not happen.
716
+
717
+ Exit codes:
718
+ 0 ACCEPT — identity established (possibly under a declared weakening)
719
+ 1 REFUSE — the release stops here; the failing ids are named in \`failed\`
720
+ 2 usage / unreadable-input error
721
+
722
+ Output: JSON { decision: "accept"|"refuse", phase, failed: [...], guarantee, weakenings, … }
723
+
724
+ Doctrine (why the tag is SHA-keyed, why one traffic entry, what the tuple proves): principle 12
725
+ and step 3 of SKILL.md.`
726
+
727
+ function parseArgs(argv) {
728
+ const opts = {}
729
+ const positional = []
730
+ for (let i = 0; i < argv.length; i++) {
731
+ const a = argv[i]
732
+ if (a === '--help' || a === '-h') opts.help = true
733
+ else if (a.startsWith('--')) opts[a.slice(2)] = argv[++i]
734
+ else positional.push(a)
735
+ }
736
+ return { opts, positional }
737
+ }
738
+
739
+ // A declaration's REASON is the whole value of declaring: "we have no SHA-keyed tag" recorded
740
+ // with no reason is the assumption again. A flag whose value is missing (it swallowed the next
741
+ // flag) or empty is a usage error, never a silently-accepted weakening.
742
+ function declaration(opts, flag) {
743
+ if (!(flag in opts)) return { value: null }
744
+ const v = opts[flag]
745
+ if (typeof v !== 'string' || v.trim().length === 0 || v.startsWith('--')) {
746
+ return { error: `--${flag} requires a reason (the weakening is recorded, not assumed)` }
747
+ }
748
+ return { value: v.trim() }
749
+ }
750
+
751
+ function readJson(file, what) {
752
+ if (!file) return { error: `usage: --${what} <file> is required` }
753
+ let raw
754
+ try {
755
+ raw = fs.readFileSync(file, 'utf8')
756
+ } catch {
757
+ return { error: `${what} not readable: ${file}` }
758
+ }
759
+ try {
760
+ return { value: JSON.parse(raw) }
761
+ } catch {
762
+ return { error: `${what} is not valid JSON: ${file}` }
763
+ }
764
+ }
765
+
766
+ export function main(argv) {
767
+ const { opts, positional } = parseArgs(argv)
768
+ const cmd = positional[0]
769
+
770
+ if (opts.help || cmd === 'help') {
771
+ console.log(USAGE)
772
+ return 0
773
+ }
774
+
775
+ // A missing subcommand fails to the same exit-2 usage error as an unknown one. Exit 0 here
776
+ // would read as ACCEPT to `candidate-identity … && <continue the release>`.
777
+ if (cmd !== 'locate' && cmd !== 'attest' && cmd !== 'serving') {
778
+ const what = cmd ? `unknown command "${cmd}"` : 'no subcommand given'
779
+ console.error(`candidate-identity: ${what}. Use locate|attest|serving (see --help).`)
780
+ return 2
781
+ }
782
+
783
+ let result
784
+ if (cmd === 'locate') {
785
+ // The declaration is validated FIRST so that a flag which swallowed the next flag as its
786
+ // "reason" (`--declare-shared-candidate-tag --traffic f`) is diagnosed as the missing
787
+ // reason it is, rather than as whichever required flag it ate.
788
+ const declared = declaration(opts, 'declare-shared-candidate-tag')
789
+ if (declared.error) {
790
+ console.error(`candidate-identity: ${declared.error}`)
791
+ return 2
792
+ }
793
+ if (!opts.sha) {
794
+ console.error('candidate-identity: usage: locate requires --sha <shipped>')
795
+ return 2
796
+ }
797
+ if (
798
+ 'candidate-tag-prefix' in opts &&
799
+ (typeof opts['candidate-tag-prefix'] !== 'string' ||
800
+ !opts['candidate-tag-prefix'] ||
801
+ opts['candidate-tag-prefix'].startsWith('--'))
802
+ ) {
803
+ console.error('candidate-identity: --candidate-tag-prefix requires a value')
804
+ return 2
805
+ }
806
+ if (!opts.traffic) {
807
+ console.error('candidate-identity: usage: locate requires --traffic <flattened-traffic-read>')
808
+ return 2
809
+ }
810
+ let text
811
+ try {
812
+ text = fs.readFileSync(opts.traffic, 'utf8')
813
+ } catch {
814
+ console.error(`candidate-identity: traffic read not readable: ${opts.traffic}`)
815
+ return 2
816
+ }
817
+ const traffic = parseTraffic(text)
818
+ if (traffic.error) {
819
+ console.error(`candidate-identity: ${traffic.error}`)
820
+ return 2
821
+ }
822
+ result = evaluateLocate({
823
+ sha: opts.sha,
824
+ traffic,
825
+ declareSharedTag: declared.value,
826
+ candidateTagPrefix: opts['candidate-tag-prefix'] || DEFAULT_CANDIDATE_TAG_PREFIX,
827
+ })
828
+ } else if (cmd === 'attest') {
829
+ const declared = declaration(opts, 'declare-no-attestation')
830
+ if (declared.error) {
831
+ console.error(`candidate-identity: ${declared.error}`)
832
+ return 2
833
+ }
834
+ if (!opts.sha) {
835
+ console.error('candidate-identity: usage: attest requires --sha <shipped>')
836
+ return 2
837
+ }
838
+ const locate = readJson(opts.locate, 'locate')
839
+ if (locate.error) {
840
+ console.error(`candidate-identity: ${locate.error}`)
841
+ return 2
842
+ }
843
+ // An unreadable health file is a REFUSE, not an error: "the endpoint did not answer" is
844
+ // exactly the case that must never fall through to a shift, and it is decided by the
845
+ // e:unattested condition, not by the CLI.
846
+ // The substitute belongs to the declaration, so offering it without one is a mistake worth
847
+ // naming: with an attesting endpoint the digest comes from the attestation, and an input this
848
+ // command silently ignored would be exactly the "looks complete" shape the flag exists to fix.
849
+ if ('candidate-image-digest' in opts && !declared.value) {
850
+ console.error(
851
+ 'candidate-identity: --candidate-image-digest is the substitute for --declare-no-attestation; ' +
852
+ 'with an attesting endpoint the digest comes from the attestation.',
853
+ )
854
+ return 2
855
+ }
856
+ const health = opts.health ? readJson(opts.health, 'health') : { error: 'absent' }
857
+ result = evaluateAttest({
858
+ sha: opts.sha,
859
+ locate: locate.value,
860
+ health: health.error ? null : health.value,
861
+ declareNoAttestation: declared.value,
862
+ candidateImageDigest: opts['candidate-image-digest'] ?? null,
863
+ })
864
+ } else {
865
+ // `serving` takes the promoted SHA for the same reason `attest` does: without it this
866
+ // subcommand can only prove the recorded identity and the live process agree, which is
867
+ // worth nothing when the record describes another release's candidate.
868
+ if (!opts.sha) {
869
+ console.error('candidate-identity: usage: serving requires --sha <shipped>')
870
+ return 2
871
+ }
872
+ const identity = readJson(opts.identity, 'identity')
873
+ if (identity.error) {
874
+ console.error(`candidate-identity: ${identity.error}`)
875
+ return 2
876
+ }
877
+ // Same rule on this side: the fallback read is for a record that declared the weakening. On an
878
+ // attested record the live tuple is available and stronger, so a supplied digest would be an
879
+ // ignored input standing in for a check that did not run.
880
+ const record = identity.value && identity.value.identity ? identity.value.identity : identity.value
881
+ const recordAttested = !!(record && record.guarantee && record.guarantee.attested)
882
+ if ('serving-image-digest' in opts && recordAttested) {
883
+ console.error(
884
+ 'candidate-identity: --serving-image-digest is the declared fallback for a record with ' +
885
+ '`guarantee.attested: false`; this record is attested, so pass --health and compare the tuple.',
886
+ )
887
+ return 2
888
+ }
889
+ const health = opts.health ? readJson(opts.health, 'health') : { error: 'absent' }
890
+ result = evaluateServing({
891
+ identity: identity.value,
892
+ health: health.error ? null : health.value,
893
+ sha: opts.sha,
894
+ servingImageDigest: opts['serving-image-digest'] ?? null,
895
+ })
896
+ }
897
+
898
+ console.log(JSON.stringify(result, null, 2))
899
+ return result.decision === 'accept' ? 0 : 1
900
+ }
901
+
902
+ if (isMainModule(import.meta.url)) {
903
+ process.exit(main(process.argv.slice(2)))
904
+ }