@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,207 @@
1
+ # Pre-flight gate: confirm the authorization envelope
2
+
3
+ Step -1 of the release loop, and mandatory. Nothing outward-facing happens until
4
+ the human has confirmed this block. Indexed from
5
+ [SKILL.md](preflight.md#pre-flight-gate-confirm-the-authorization-envelope-mandatory).
6
+
7
+ ## Item 0 — Run the readiness probes before proposing anything
8
+
9
+ ```bash
10
+ node <resolve-release>/scripts/preflight-probes.mjs check \
11
+ --repo <owner/name> --target-branch <prod-branch> \
12
+ --exposure-gate-probe '<the repo own check that its exposure profile can produce a verdict>' \
13
+ --topology-probe '<read-only command emitting the Cloud Build TAG_NAME substitution record>' \
14
+ [--smoke-manifest <smoke.manifest.json> --smoke-secrets-command '<optional per-secret dry-fetch>'] \
15
+ [--gate '<a check-run that must be present and green on the target head>']
16
+ ```
17
+
18
+ Exit **0** ready, **1** not ready (each probe naming its own remedy), **2** usage. Four questions, all of which a release otherwise discovers mid-flight — in pi session `019faea6` all four did:
19
+
20
+ | Probe | What it caught there |
21
+ |---|---|
22
+ | `credential-liveness` | `gcloud` reauthentication expired *after* the rc tag was pushed; every Cloud Build and Cloud Run read failed for forty minutes. The identity is declared at [item 3](#pre-flight-gate-confirm-the-authorization-envelope-mandatory); nothing checked the credential it names still worked. The same probe is re-run at the start of step 4b and on resume when `step` is `validate` or `expose` ([references/exposure.md](exposure.md#4--validate-the-candidate-then-expose-it-skill-smoke)). |
23
+ | `tag-divergence` | `git fetch` refused `v0.3.1-rc.1` as clobbering — local `43fc5398`, remote `06dd1b3d`. Tags are immutable ([principle 10](principles.md#non-negotiable-principles)), so a divergence means one side is lying about what a version names, and `version.mjs` is about to read the local one. |
24
+ | `target-gate-colour` | Post-deploy validation was already red on `main` across four consecutive SHAs. The release became a repair job — the right outcome, reached by surprise. |
25
+ | `exposure-gate-capability` | The repo could not produce a deployed-profile verdict *at all*. Discovered after minting a tag and building a candidate; the attempt parked at `blocked-manual`. Thirty seconds here instead of forty minutes there. |
26
+ | `rc-tag-topology` | The actual production path was a `TAG_NAME` Cloud Build trigger, while `_DEPLOYED_VERSION` had only been passed through a controller-submitted `gcloud builds submit`. The read-only command emits one representative RC record: `prodBuildTrigger`, `versionSource`, `candidateTagSource`, and `substitutions.TAG_NAME` / `_DEPLOYED_VERSION` / `_CANDIDATE_TAG`. It passes only for `tag-trigger` / `TAG_NAME` / `TAG_NAME`, with the final SemVer and RC candidate tag both derived from the RC `TAG_NAME`. |
27
+ | `smoke-secrets` *(optional)* | A smoke secret env var held the resource-name string instead of the secret value, so 4a would have returned `CANNOT-RUN` after the candidate was built. Caught before the rc tag when the exposure executor is this skill (#823). |
28
+
29
+ **`unknown` is not `ok`, and an absent `--exposure-gate-probe` or `--topology-probe` is `unknown`.** An unasked readiness question is [principle 13](principles.md#non-negotiable-principles)'s shape — an applicable gate that cannot run is a failure, not an absence — so a probe that was never supplied blocks the attempt exactly as a failing one does. A repo that never answers cannot silently release on the weaker path, which is the same rule item 3 applies to the candidate identity legs.
30
+
31
+ **The topology probe is a pre-RC artifact, not a controller convenience.** Persist `preflight-probes.mjs check --json` and pass that file to `version.mjs rc --topology-probe <file>`; the mint refuses unless the artifact is ready and carries exactly `prodBuildTrigger: "tag-trigger"`, `versionSource: "TAG_NAME"`, and `candidateTagSource: "TAG_NAME"`. A controller-submitted build may pass `_DEPLOYED_VERSION`, but that does not prove a tag-triggered production build receives it. Likewise, a dev image keyed only by source SHA is informational: it does not satisfy candidate evidence before the RC tag/build/revision/tag binding exists.
32
+
33
+ **`--smoke-manifest` is required when the exposure executor is this skill.** The smoke gate declared in the manifest's `setup.env` needs real secrets; a secret env var that holds the resource-name string instead of the value is a `CANNOT-RUN` gate failure at 4a, but it is cheaper to catch here. Pass `--smoke-secrets-command` if the resource-path heuristic is not enough; the command's `<name>` token is substituted with each env var name.
34
+
35
+ A **blocked** probe is not automatically a refusal; it is a fact the envelope must now account for. A red `target-gate-colour` routes to the **mechanized ensure-target-green phase** ([`scripts/ensure-target-green.mjs`](../scripts/ensure-target-green.mjs), Option 1 + 4, #807) with the attempt's lease held throughout — the release is still in flight and a second one must not start behind it:
36
+
37
+ ```bash
38
+ node <resolve-release>/scripts/ensure-target-green.mjs \
39
+ --repo <owner/name> --target-branch <prod-branch> \
40
+ [--gate '<release-critical check-run>'] \
41
+ [--mode supervised|auto-repair-allowlist] \
42
+ --json
43
+ ```
44
+
45
+ **Sequence:** classify required-gate failures → open allowlisted repair via a resolve-issues brief (or hand a structured human brief for non-allowlisted / supervised mode) → **re-probe the NEW tip** → only then propose/confirm the envelope (or resume the same attempt). Record `release.ensureGreen` (startedAt, attempts, finalHead, outcome, durationMs, failingClasses, repair PRs/issues) — time spent + outcome also surface on the checkpoint.
46
+
47
+ **Forbidden routes** (principle 13's route-around dynamic — a gate people route around has stopped existing without anyone deciding to remove it):
48
+ - proposing the envelope while any of the four probes are unanswered
49
+ - disabling the gate, or claiming green on a different SHA
50
+ - stuffing tip repairs into the promotion PR
51
+ - opening a fresh attempt and leaving the first one's lease dangling
52
+
53
+ **Non-overlap:** tip check-runs only. Live floor / public-host DEAD is #800 (`floorReadiness`); true remote-tip / tag-push honesty is #799.
54
+
55
+ ## Item 0b — Discover and record production endpoints before the envelope
56
+
57
+ For a deployed production shape (`rc-tag`, `branch-push`, or `final-tag`), before proposing the envelope, read the production URL from the deploy trigger configuration
58
+ (the canonical source, for example `_APP_BASE_URL`) and independently read the active Cloud Run
59
+ service URL. Then run:
60
+
61
+ ```bash
62
+ node <resolve-release>/scripts/production-endpoints.mjs preflight \
63
+ --canonical-url-command "<read deploy trigger configured production URL>" \
64
+ --service-url-command "<read Cloud Run service status.url>" \
65
+ [--floor-verdict <fresh-prod-active-verdict.json>]
66
+ # Persist the emitted patch through run-state.mjs merge before continuing.
67
+ ```
68
+
69
+ The command records `release.productionEndpoints` with both URLs, their configuration
70
+ fingerprint, whether the canonical hostname is a distinct public/edge hostname, and the source of
71
+ each read. A distinct host makes `--floor-verdict` mandatory: it must be fresh, read-only,
72
+ `prod-active` ALIVE, and targeted exactly at the canonical configured production URL. It cannot
73
+ be skipped. When both reads have the same hostname, the command records the reasoned
74
+ `release.floorReadiness` skip itself; do not run redundant floor smoke and do not hand-author a
75
+ skip. Missing, malformed, stale, wrong-profile, wrong-target, or mutating evidence refuses.
76
+
77
+ **For every deployed production shape, this record is owed before an RC is minted or any candidate deployment starts.** The `no-deploy` shape keeps item 5's explicit n/a reason and consumer-facing verification instead; it has no Cloud Run endpoint to classify. It turns floor
78
+ applicability into preflight state rather than a late shift-gate discovery. On every resume, run
79
+ `production-endpoints.mjs resume` with the same two read commands before any other release work.
80
+ A changed URL or changed distinct-host applicability returns `endpoint-configuration-changed`;
81
+ stop, re-run `preflight` (including fresh floor evidence where now applicable), persist the new
82
+ patch, and re-confirm any envelope consequence. A matching SHA does not carry endpoint evidence
83
+ across configuration drift (principle 11).
84
+
85
+ ## Item 0c — Prove metadata sync before release exposure
86
+
87
+ When the release topology owes a post-release metadata PR, run the adapter's dry-run
88
+ **before release exposure** and before beginning final closeout. Use the version,
89
+ shipped SHA, changes, package declarations, and root lockfile declaration proposed for
90
+ the release; a planned record is not completion evidence. When a lock declaration is
91
+ present, this dry-run runs the declared lock command and verifies the generated lock
92
+ in a disposable planned execution tree before it records `status:"planned"`:
93
+
94
+ ```bash
95
+ node <resolve-release>/scripts/metadata-sync.mjs open --dry-run --record-plan \
96
+ --manifest <run-manifest-outside-target-repository> \
97
+ --cwd <target-repository> --repo <owner/name> --base <branch> \
98
+ --version <version> --sha <shipped-sha> --date <YYYY-MM-DD> \
99
+ --changes <changes.json> --package-file <package.json> \
100
+ [--lock-file <package-lock.json> --lock-command '<package-manager-lock-command>']
101
+ ```
102
+
103
+ If the dry-run fails, file a tooling issue before metadata PR work starts. Attach the
104
+ refusal and the declared metadata inputs, then repair the adapter and rerun the dry-run. The root npm lockfile adapter defect is app-repo work tracked at
105
+ a consumer product repo's issue #929: it is
106
+ a repository-specific metadata-adapter follow-up, not a reason to bypass the check.
107
+ A manual metadata completion after a failed dry-run is a recovery artifact only; it
108
+ must be adjudicated and linked to its filed issue before final closeout.
109
+
110
+ ## Pre-flight gate: confirm the authorization envelope (MANDATORY)
111
+
112
+ Before touching anything — and after item 0 reports **ready** and item 0b is recorded (or item 5 declares `no-deploy`) — propose the following as one confirmable block, and wait for the human's yes:
113
+
114
+ 1. **Scope** — which PR(s)/epic are being released, in what dependency order, the promoted-content summary (migrations? flags? config/secrets?), and **the originating issue(s) this release closes**. Confirming the close-set now makes closing them at [step 5](routing.md#5--route-on-the-verdict) a pre-authorized outward action rather than an unprompted one (principle 1).
115
+ 2. **Target chain** — the branch/environment path (e.g. `feature → dev → main`) and what "production" concretely is here. Include the `release.productionEndpoints` result from item 0b: canonical configured production URL, Cloud Run service URL, and distinct public/edge applicability. Name the **topology** explicitly, because step 1, the step-5 back-merge, and how much the candidate pin is worth all follow from it: GitFlow (`dev` + `main`), **GitHub Flow** (one `main` doing both jobs — the pin demotes to a pre-filter and there is no back-merge), **trunk + merge queue** (a queue landed the units on trunk before this skill was invoked — [step 1 assembles nothing](assembly.md#1--assemble-the-release-integration--release-branch), and principles 3, 8 and 11 change shape there), or **no integration branch** at all (step 1 assembles a `release/<slug>` as the single combining point). For GitHub Flow, also record whether the human opts into [`verified-sha-github-flow`](verified-sha-github-flow.md#authority-and-selection); absent that explicit selection, the generic GitHub Flow path below remains the default.
116
+
117
+ **The merge-queue topology is *read* from the upstream handoff, never re-decided here.** `resolve-issues` settles `deliveryMode` (`direct` | `queue`) at its own pre-flight with the mechanized probe [`detect-delivery-mode.mjs`](../../resolve-issues/scripts/detect-delivery-mode.mjs), because a queue is a **runtime** fact whose configuration commonly lives dashboard-side rather than in the repo — so re-deriving it here would answer the same question with worse evidence, and a disagreement would mean this lane releasing under a topology the units were not delivered under. `queue` selects this topology; `direct` leaves the three branch topologies above to decide it. **Absent or `null` refuses.** `null` is the probe's UNDETERMINED (exit 2), which exists precisely because "no probe could run" is not "probed and found no queue"; an absent field is that same state arriving with no exit code attached, and the upstream manifest's legacy absent-reads-`direct` default governs *that manifest's* own validation — it is not a topology selection this skill may inherit. Settle it with the human, or send the handoff back to be probed. A release is the last place to guess a merge path, because here the guess is not caught by a failing enqueue: every downstream step still runs, on the wrong pin.
118
+ 3. **Pre-authorized actions** — per step, one of: **pre-authorized** (unattended), **per-step confirm** (the default for the promotion *and* the traffic shift), or **auto-when-green** ([principle 9](../SKILL.md#non-negotiable-principles)). Default conservative: merging into the integration branch pre-authorized, the **promotion individually confirmed**, and — because it is the moment users are actually exposed — the **step-4 traffic shift individually confirmed**. Also settle **who executes the exposure gate** (principle 12): this skill (default) or the pipeline. `auto-when-green` is an explicit opt-in and requires the rollback policy **armed**.
119
+
120
+ **And declare the exposure identity** — *who* performs the traffic shift, as against the candidate's identity legs below, which settle *what* is being exposed. Leave it unanswered and the answer is whichever ambient credential the executor happens to hold, which is the default principle 12 exists to displace. Three answers, recorded together as `envelope.exposureIdentity`. **Which credential shifts traffic**, and whether it is scoped to promotion/rollback alone or shared with candidate validation and revision cleanup — **shared is a declared weakening**, with a reason, not a refusal: one deploy identity is an ordinary repo, an *undeclared* one is a standing grant nobody sized. **Keyless or a stored key**: prefer federated identity (OIDC + workload-identity federation) conditioned to this repository, this workflow file, and release-tag refs, because a stored key is a standing grant that outlives the release. **And whether that credential also reaches production data** — under principle 12's secret boundary it should not, so a release step needing a privileged data operation names here the pre-authorized job it will invoke rather than the secret it would hold. Unlike the candidate legs below, no script consumes any of this — it is an envelope field a human fills, so what a declared weakening leaves behind here is a **sized** grant rather than a refusal id: the [authorization audit](../SKILL.md#definition-of-done-handoff-report) names the identity that shifted traffic and its declared reach on **every** release, not only the weakened ones.
121
+
122
+ **And declare the candidate's identity legs** — principle 12's other half, and the only place a repo gets to say its pipeline is weaker than the contract. Two questions, each answered here rather than discovered mid-release: does the pipeline publish a **SHA-keyed candidate tag** (`candidate-$SHORT_SHA`), and does the service expose a **health endpoint attesting the tuple** (source SHA, revision, image digest)? Both yes is the full guarantee and needs no declaration. A **no** is declared now, with a reason, and carried into step 3 as `candidate-identity.mjs`'s `--declare-shared-candidate-tag <reason>` / `--declare-no-attestation <reason>` — which record the **weakened** guarantee in `release.candidateIdentity` instead of leaving it assumed. State plainly what each one costs — precisely, because an overstated cost is dismissed as easily as an understated one. Without a SHA-keyed tag, `locate` still refuses a *foreign* `candidate-<sha>` tag, so a rival release running the normal scheme is still caught; what becomes invisible is a rival that took **the same shared `candidate` tag** — there is then one row and it may be naming their revision, and for that case principle 7 rests on serialization alone. Without the attestation, step 4b's serving confirmation falls back to comparing the revision's image, which describes the spec rather than the running process. **Both missing is not a declarable state** — nothing would key identity to a commit, and the script refuses. An undeclared missing leg refuses too, so a repo that never answers this cannot silently release on the weaker guarantee.
123
+
124
+ **A declaration owes a substitute, not just a reason.** The point of declaring is that the release still runs a check — a weaker one, named — so `--declare-no-attestation` is accepted only together with **`--candidate-image-digest <digest>`**, the candidate revision's *configured* image digest, which a repo with no attesting endpoint can still read from the platform. Without it `attest` would record `imageDigest: null` and step 4b's fallback would send the operator to compare a live digest against nothing: either it can never match, or `null` reads as "not applicable" and the check silently does not happen. That is a worse outcome than refusing, because it looks complete — and it lands on precisely the repos with the fewest other protections. So the script refuses with `e:no-fallback-digest` instead, naming the read. The general rule, held in every branch: **a declared weakening leaves an operable check behind, or it refuses.** It binds any declaration added later too, and is not inherited: a new `--declare-*` leg ships with its own substitute read and its own refusal id, or it is not a declarable leg. Nothing detects a leg that skips that — pre-flight item 3's two questions are answered by the human here precisely because no check downstream re-asks them.
125
+
126
+ **And propose the verdict freshness window** — `envelope.verdictFreshnessMinutes`, how long the step-4a ALIVE verdict stays usable as authorization evidence. It belongs here because it is the *cost* of the per-step confirm this item just chose: the confirm introduces an unbounded wait between validating the candidate at 0% and shifting traffic to it, and the window is what bounds it. The **default is the one `green-gate.mjs --help` states** — the script that enforces the window is the only place that names it, the same split `auto-when-green.md` takes, so a retune cannot leave a stale number sitting in this sentence. It is short on purpose: long enough for a human to read the evidence and answer, short enough that the coordinates cannot have quietly moved. Propose a longer one only with a reason the human sees (an approval chain that genuinely takes hours), and understand what you are buying: an expired verdict **re-runs step 4a**, which is cheap, while a shift on aged evidence is exposure on coordinates nobody re-checked. `green-gate.mjs shift` reads this field and holds on a verdict older than it — or on one carrying no timestamp at all.
127
+ 4. **Rollback policy** — **for the post-exposure tier only** (principle 6): auto-rollback immediately (default), or page the human first. Identify the mechanism *now* (revision routing, revert commit, pipeline rollback): a release whose rollback path is unknown is not ready to start. The pre-exposure tier needs no policy — it is `gcloud run revisions delete` on a revision no user ever reached — but confirm you *can* delete the candidate revision.
128
+ 5. **Version plan** — *production promotions only; skip for a `dev`/staging release, which is unversioned* (principle 10). Run `node <resolve-release>/scripts/version.mjs plan --target-branch <target> --production-branch <prod> --changes <prs.json>` and propose its output — current version, computed bump **and why**, next version, tag name — plus the CHANGELOG path and **how the production deploy is triggered** — one of four **declarable shapes** ([references/versioning.md](versioning.md) §5), recorded as `envelope.deployTrigger`, and the choice decides what a failed release costs:
129
+ - **`v<version>-rc.<n>` tag** (`rc-tag`, default) — minted at step 3; the final `v<version>` tag waits for verification. No burn.
130
+ - **Branch push** (`branch-push`) — merging the promotion is itself the trigger, so step 3 mints no rc; the final tag still waits for verification. No burn.
131
+ - **Final `v<version>` tag** (`final-tag`, legacy) — the tag *is* the trigger, so it precedes verification and a failed release **burns** the version. A **tag collision** (exit 1) on the *final* tag means that version already reached the repo: resolve it *here*, before anything merges (an existing `-rc.*` tag is not a collision). Never accept the bump silently — a **major bump** needs an explicit yes (and voids `auto-when-green`).
132
+ - **No deployed surface** (`no-deploy`) — the repo ships a **versioned artifact nobody deploys**: a library consumed by tag, skills installed by symlink, a schema package. Nothing is triggered because there is nothing to trigger, so **there is no rc, no candidate revision, and no traffic to shift**. Declaring it makes **steps 3–4 a decided `n/a`** — the rc mint, the 0%-traffic candidate, the 4a smoke, the traffic shift, and the 4b serving confirmation — and it owes `envelope.deployTriggerReason`, the **checkable reason** those steps do not apply (*what* the artifact is and *how* consumers take it). The rest of the loop is unchanged: the promotion is still authorized, the lease is still opened and closed, the final tag still waits for verification, and every terminal exit still emits its evidence bundle. What replaces the deploy observation is stated with it — the consumer-facing read that shows the artifact is actually resolvable at the tag — because a release that observes *nothing* is a claim, and principle 5 does not have a topology exemption.
133
+
134
+ **The shape is declared, never inferred.** All four are named here so that a repo which fits none of the first three has one to pick rather than sliding onto the legacy final-tag path by default — an **undeclared** shape is a **refusal**, exactly like an undeclared identity leg in item 3: a repo that never answers cannot silently release on the weaker path. This is principle 13's distinction applied to the topology itself: `n/a` with a reason is a decided row, a skipped step is not.
135
+
136
+ **And declare `envelope.displayVersionPolicy`** — which version string the running service shows in UI and `/api/health`. Under `rc-tag` the default is `final-semver` (show `vX.Y.Z`, not `vX.Y.Z-rc.N`), because the rc tag is a deploy trigger, not the public version; an omitted policy on `rc-tag` resolves to `final-semver`. Other topologies have no default; the policy must be declared. A repo that intentionally shows the attempt tag chooses `trigger-tag`; one that shows the commit chooses `short-sha`. See [references/versioning.md](versioning.md#6-display-version-is-not-identity-version) for the assertion after the shift and the Cloud Build strip pattern the consumer pipeline should use.
137
+ 6. **Durable processing and the quiescence path** — declare, yes or no, whether the target does work that outlives a request: schedulers, workers draining a queue, a database admission path, cron-triggered jobs, outbox or retry loops ([principle 14](../SKILL.md#non-negotiable-principles)). **`no` is a declaration, not a default** — say it explicitly, record it, and [step 4b](exposure.md#4--validate-the-candidate-then-expose-it-skill-smoke) stays a plain shift with nothing else in this item applying. **`yes` owes three named mechanisms before anything starts**, in the same posture as item 4's rollback rule — *a release whose quiescence path is unknown is not ready to start*:
138
+
139
+ - **How processing pauses**, and **in what order** (the scheduler command, the admission flag) — the order is recorded here because the resume executes as its exact reverse, so it is part of the declaration rather than a decision made mid-shift.
140
+ - **How quiescence is proven** — the *read* that shows the workers actually drained (in-flight count, queue depth, active-execution state), plus the bound after which a queue that will not drain ends the attempt. **Not the pause command's exit code**: naming the pause twice is the failure this item exists to prevent, and it is the easiest declaration to write without noticing.
141
+ - **How each is resumed**, and how a resume is *confirmed* — the same distinction one layer up: an issued resume is a request, and a release that ends with scheduling silently still off is an outage the release lane caused and did not observe.
142
+
143
+ Record as `envelope.durableProcessing` = `{ present, pauseOrder, pause, quiescenceProof, quiescenceBound, resume }`. The pause is an outward-facing action against production (principle 1) and it is not free to discover late: an unknown mechanism found at 4b means improvising one while production is half-quiesced, which is the state principle 14 says nobody may guess at.
144
+
145
+ **Validate the repository declaration through the typed adapter before recording it.** The
146
+ repository owns a versioned release declaration with `repositoryRoot` and an explicit
147
+ `durableProcessing.present`. A `true` declaration names a repository-relative ES module
148
+ exporting `DURABLE_PROCESSING_ADAPTER`; that export owns platform execution and returns the
149
+ canonical receipt schema. The adapter is a self-contained, dependency-free `.mjs` file of at
150
+ most 256 KiB. The CLI resolves the root and adapter to canonical realpaths, opens the canonical
151
+ regular target with no-follow where supported, binds identity and size with `fstat`, and strictly
152
+ decodes UTF-8. Pinned Acorn 8.15.0 parses the acquired bytes before evaluation. The accepted
153
+ adapter subset is ordinary local bindings, objects, functions and control flow; fixed safe
154
+ properties; the explicit language/data globals `Object`, `Array`, `JSON`, `Date`, `Math`,
155
+ `Number`, `String`, `Boolean`, `BigInt`, `RegExp`, the standard `Error` families, `console`,
156
+ `undefined`, `NaN`, and `Infinity`; frozen declaration config;
157
+ and isolated `readTextFile` and `writeTextFile` operations over bounded snapshots of configured
158
+ files. Writes remain guest-local intents until the host validates the receipt and every file
159
+ identity. Repeated intents collapse to one final value per path. The host creates exclusive
160
+ same-directory staged finals and original backups with the target mode, fsyncs staged files and
161
+ directories where supported, revalidates every regular non-symlink target identity immediately
162
+ before the first path-ordered replacement, and reverse-restores replaced targets on later
163
+ failure. A failed restore returns `e:adapter-write-rollback-failed` with
164
+ `effects: "ambiguous"` and affected targets; callers must not claim no effects. This is a
165
+ recoverable host transaction, not cross-file kernel atomicity: process crash, kernel failure,
166
+ or power loss during separate replacements can leave mixed values or staged artifacts requiring
167
+ operator recovery. Local bindings may use any otherwise safe name and may shadow ambient names. Comments, labels,
168
+ non-computed member/property names, and string contents are not global references. Dangerous
169
+ property names and arbitrary computed properties are conservatively rejected, even in data-only
170
+ positions.
171
+
172
+ Approved bytes evaluate in a fresh pinned QuickJS-WASM 0.32.0 runtime and heap with bounded
173
+ memory, stack, execution time, pending jobs, payloads, results, and file intents. No mutable
174
+ Node host-realm global or intrinsic, live host callback, inherited stdio, environment, filesystem,
175
+ network authority, or module loader enters the guest. The lexical-scope-aware positive validation rejects every unresolved identifier not
176
+ named above before evaluation, including ambient network, crypto, URL/encoding, Buffer/runtime,
177
+ timer/microtask, messaging/stream, WebAssembly, shared-memory, reflection, and global-recovery
178
+ capabilities. `Symbol`, `Proxy`, `WeakMap`, `WeakSet`, `WeakRef`, `FinalizationRegistry`,
179
+ `Atomics`, `SharedArrayBuffer`, `Intl`, `Map`, `Set`, typed arrays, and the global `Promise`
180
+ constructor are intentionally absent because the shipped policy does not require them. Language
181
+ syntax such as async functions and `await` remains available without exposing their global
182
+ constructors. Every unresolved `process` reference, including environment reads and writes, is forbidden before evaluation. No ambient configuration global, proxy, snapshot, or mutable host object is supplied; configuration reaches the adapter only through literal/static local values and the frozen `config` argument supplied to `execute`.
183
+
184
+ The policy also rejects imports, export-from, dynamic import, `import.meta`, eval and all
185
+ Function constructor families, constructor/prototype/reflection recovery,
186
+ call/apply/bind/Reflect invocation recovery, process/module/createRequire/VM loader recovery,
187
+ dynamic property synthesis, and WebAssembly code loading before evaluation. It executes only
188
+ the acquired bytes bound to their SHA-256 identity inside the isolated runtime; secondary
189
+ `data:` or `blob:` imports remain forbidden by the AST rule. Later pathname changes cannot
190
+ redirect execution, identical bytes reuse validated metadata, and changed bytes inspect fresh. Traversal, outside/broken links, replacement,
191
+ nonregular/oversize source, invalid UTF-8/syntax, dependency syntax, or forbidden capabilities
192
+ fail before adapter side effects. A canonical in-root symlink remains valid.
193
+ It contains no product command copied into this skill or the run manifest. Run:
194
+
195
+ ```bash
196
+ node <resolve-release>/scripts/durable-processing.mjs preflight \
197
+ --declaration <repository-release-declaration.json> > durable-processing-preflight.json
198
+ ```
199
+
200
+ Exit 0 records the writer-stamped receipt's fixed pause order, exact reverse resume order,
201
+ quiescence timeout, adapter writer, and evidence schema in `envelope.durableProcessing`.
202
+ Exit 1 refuses the release. `present: false` is a decided receipt; an absent declaration is
203
+ `e:durable-processing-undecided`, never an inferred no. See
204
+ `node <resolve-release>/scripts/durable-processing.mjs --help` for the closed request/receipt
205
+ contract.
206
+
207
+ Record the confirmed envelope in the **run manifest** via `resolve-issues`'s `scripts/run-state.mjs` (`init <release-slug> --skill resolve-release`, then `merge '{"envelope":{…}}'`; [run-state.md](../../resolve-issues/references/run-state.md) — through the helper, don't hand-roll JSON) and update `step`/`verification`/etc. at every transition. A resumed release inherits its envelope but revalidates item 0b endpoint configuration before any other work; a new release always re-confirms.
@@ -0,0 +1,94 @@
1
+ # Non-negotiable principles
2
+
3
+ The invariants that bind every step of the release loop. The rules themselves are
4
+ indexed in [SKILL.md](../SKILL.md#non-negotiable-principles); this file is the
5
+ reasoning behind each one — read it before editing a rule or arguing with one.
6
+
7
+ Every principle below leaves machine-generated state history. Canonical state writers derive
8
+ state events from the before/after manifest. Every outward action records intent before execution
9
+ and the observed result afterward, using the contract subjects for tags, candidates, traffic,
10
+ processing controls, GitHub Releases, and metadata delivery. Writer clocks own canonical event
11
+ timestamps; operator prose and `--now` do not. The retained window is bounded, with an explicit
12
+ truncation summary preserving the state boundary, outcome counts, wait/failure totals, and any
13
+ unresolved action. A separate 64-correlation replay ledger never evicts identities: stable replay
14
+ remains a no-op, conflicting evidence refuses, and a 65th identity requires a new release run rather
15
+ than silently expiring protection. Artifact evidence uses subject-bound safe locator types and is
16
+ checked by exact value against that subject/type's explicit manifest-derived identity set; absence
17
+ fails closed and cross-subject reuse refuses. URL paths reject encoded separators, traversal,
18
+ controls, backslashes, malformed escapes, and Unicode lookalikes at every decode depth while query
19
+ values remain data and URL identity itself is not normalized. This is what lets resume and postmortem
20
+ apply the principles without a transcript.
21
+
22
+ ## Non-negotiable principles
23
+
24
+ 1. **Every outward-facing action is covered by explicit authorization.** Merge, promote, deploy, **shift traffic**, roll back — each is either inside the authorization envelope confirmed at pre-flight or individually confirmed before it runs. Approval to merge is not approval to promote, and **approval to promote is not approval to expose**: the traffic shift (step 4b) is the moment users first meet the new code, and it is separately authorized even though a deploy already happened. Approval in one release is not approval in the next — and **an approval authorizes the coordinates it was shown, not whatever they have become since**: the wait between validating a candidate at 0% and shifting to it is unbounded, so step 4b re-checks identity, traffic split, and the validating verdict's freshness *after* the confirm and *before* the shift.
25
+ 2. **Merge-readiness is verified against the tree, never the claim.** Before merging anything, re-check each PR: a fresh independent APPROVE on its *current* HEAD, CI green on the *current* HEAD, no unresolved blocking findings. "resolve-issues said it was ready" is a lead, not evidence.
26
+ 3. **Promotions fast-forward.** A promotion PR whose base has commits the head lacks would silently revert them (almost always hotfixes). Never merge-resolve into a production branch (review-pr, Promotion PRs) — restore fast-forwardability first. **The remedy acts on the promotion head, which under principle 11 is `release/<version>`, not the integration branch**: back-merging `main → dev` alone changes nothing about a release branch already cut, so re-checking after it still reports `false`. Correct sequence:
27
+ - **GitFlow** — back-merge `main → dev` (so `dev` and future candidates carry the hotfix), then **re-cut `release/<version>` from a fresh candidate SHA** that descends from that back-merge. Do **not** rebase the existing release branch: its pin is the whole point, and rebasing silently ships a SHA that never earned the ALIVE verdict while the artifacts still look compliant.
28
+ - **Single-branch (GitHub Flow)** — rebase the release branch onto `main`'s tip and re-run step 2. Cheap (usually one cut commit), and sound here precisely because this topology already treats the pin as a pre-filter rather than the gate.
29
+
30
+ The rule is universal; the remedy is not — and picking the other topology's remedy is not a style slip, it either no-ops or destroys the pin.
31
+ 4. **Nothing the release lane runs against production mutates it — tenant or no tenant.** The promotion fast-forwards, so prod is the exact SHA whose PR-lane gates already passed (under GitHub Flow, the rebased SHA — see principle 11, where the pin weakens and step 4a carries the gate); lean on *that* result, then run only read-only smoke (health + render-health + GET journeys) against production under a **read-only Validation Profile** (`prod-preview` at step 4a, `prod-active` at step 4b), which excludes every `mutates:true` path regardless of declared tenant. Read-only is a property of the *named profile*, not a `--no-mutations` flag the caller must remember — a production profile that was anything but read-only fails smoke's own `validate`. This is stricter than the synthetic-tenant rule that governs other deployed targets, and deliberately so: the candidate revision is wired to the **real production database**, so "it isn't serving traffic yet" says nothing about whether a write is safe.
32
+
33
+ **What this governs is the traffic this lane drives — validation and verification — and there it is absolute.** It is not the claim that a release never changes production data, which would contradict a release *carrying* a migration: promoted content is executed by the **pipeline** at deploy, where principle 9's carve-out already makes a destructive migration force the human confirm. Same for a privileged data operation a release step needs — it is **invoked as a pre-authorized job whose credential this lane never holds** (principle 12's secret boundary). The two rules meet at the same place: **the release agent's own credential never writes production**, whether the write would be a smoke path or a data fix.
34
+ 5. **The loop exits on observed production state, never on a claim.** A green deploy job is not a verified release. Under principle 12 the observation happens **twice**: once against the **candidate revision before any traffic reaches it** (the gate), and once against the **live service after the shift** (the exit evidence — confirm the revision serving traffic is the promoted SHA, and that it is healthy at 100%).
35
+
36
+ **The account this principle keeps is per-VERSION, not per-attempt — which is why the post-mortem aggregates.** Each terminal exit leaves its immutable bundle (checklist invariant 10) so that one attempt's observations outlive their session. But a version that takes nine attempts is not nine independent stories: the durable bundles for one version are *one* account of what happened to production on the way to that version, and reading only the last attempt's record is exactly how Forex `v0.1.12` reported `long-release` while eight earlier failures — all owned by the release tooling, none by the application — sat in durable bundles nobody aggregated. `version-postmortem.mjs` is the aggregate reader of those observations: it invents no evidence, it reads the bundles the attempts already emitted, and its repeated-owner finding is the per-version shape of this principle's question — *what did production actually go through, and what kept stopping it?* The classification it assigns (`references/postmortem.md` § failure ownership) is derived from the recorded verdicts and is disbelievable against the bundle itself, which keeps it this principle's child rather than a claim about the world.
37
+ 6. **Rollback over live debugging — but most failures never need one.** Two tiers, decided by whether users were ever exposed:
38
+ - **Pre-exposure** (the candidate failed validation at step 4, traffic never moved): **delete the 0%-traffic candidate revision** and stop. Production is untouched, no user saw it, no version is burned, and there is no incident. Hand the evidence to `app-debug`/`diagnose` and retry as the next rc. This is the common case and must not be dressed up as an outage.
39
+ - **Post-exposure** (traffic shifted, then production went unhealthy): execute the **rollback policy confirmed at pre-flight** — auto-rollback by default, or page-the-human-first — restore production, confirm restoration, file the **incident** issue, hand off diagnosis.
40
+
41
+ Either way the fix re-enters through the normal PR lane (`resolve-issues`) — never a hot patch applied directly to prod.
42
+
43
+ **Both tiers presuppose a state somebody can describe, and there is a third case that has none.** Where the target has durable processing ([principle 14](#non-negotiable-principles)), a release can end with production *undescribed* rather than merely broken: nobody can say whether the workers actually drained, which revision is serving, or whether scheduling and database admission came back. That is neither tier. Pre-exposure's "delete the candidate, nobody was exposed, this is routine, not an outage" is a **claim that production is untouched**, and it is exactly the claim nobody is in a position to make here. **Ambiguous quiescence, traffic, or restoration state stops and pages a human** — it is never routed as a routine discard, and the routine framing above must not be carried into it.
44
+ 7. **One release in flight per environment — the Release Attempt Lease.** Serialization is not a discipline to remember; it is recorded state a resumed session can read. The manifest carries `release.attempt`, distinct from `step`: **`step` is progress** (where the run got to), **the lease is liveness** (is that attempt still open?). Nothing about "the run reached `validate`" says whether that attempt closed — a crashed session leaves a manifest that looks finished — so the two are separate fields. The attempt has one nonterminal state, `open`, and five terminal ones: `released`, `failed-pre-promotion`, `rolled-back`, `revoked`, and `blocked-manual`. **The load-bearing rule — the whole difference between a lease and a status field — is that only a verified release, verified candidate cleanup, verified rollback, or verified revocation closes the lease; workflow completion, failure, timeout, and cancellation never do.** Those four events are the ones that leave an attempt *looking* finished when it is not, so the mechanism refuses them by name. `blocked-manual` is terminal-valued but **holds the lease** until an operator records recovery — a release parked on a human is still in flight. Revocation revokes the *attempt* but **retains the rc tag** ([principle 10](#non-negotiable-principles) — tags are immutable and are never moved or deleted). Mechanized in `run-state.mjs`: `check-release-lease` reads the lease (0 free, 1 held, 2 ambiguous, 3 no-manifest) and `record-release-attempt` transitions it, with `run-state.mjs validate` enforcing the enum and the closing-cause set exactly as it does `step`. **The lease is an *advisory* check-then-act read, not a concurrency-safe lock**: it turns "one release in flight" from a sentence into recorded state, but two racing releases can both read it free — making the take atomic under concurrency (a filesystem lock, acquire-or-fail, serialized generic writers) is deliberately split out to **issue #509** and is not built here. A second promotion does not start while an attempt is open, blocked-manual, or ambiguous.
45
+ 8. **Combine into one promotion — never N individual promotions to production.** N merge-ready PRs reach prod as *one* assembled branch (step 1) promoted *once*, producing **one candidate and one rc tag**. Mandatory because each promotion mints its own rc, each rc fires its own production build, and concurrent builds can finish out of order — leaving prod on an arbitrary revision. One combined promotion makes the deployed revision deterministic, gives step 4a a single artifact to validate, and makes rollback a single hop. (The **retry** loop is not a violation: a failed candidate produces `-rc.2` for the *same* release, serialized behind the first — one release in flight, principle 7.) **This constrains production, not staging** — merging *into* the integration branch is incremental (step 1, one at a time, CI watched), and PRs already merged individually to `dev` are fine (`dev`'s tip *is* the assembled branch). Forbidden is N separate promotions into the **production** branch.
46
+ 9. **`auto-when-green` pre-approves only behind a real gate, never on CI color alone.** Under this envelope mode (pre-flight item 3) the **promotion** runs unattended *only* while a four-part green-gate holds (fast-forwardable, gates green, release-readiness fully decided, no blocking finding), and the **traffic shift** only on a fifth: an **ALIVE verdict on that exact candidate**. Any miss falls back to a per-step human confirm. It also requires the rollback policy **armed**, and irreversible content (destructive migration, unverifiable secret/config change, first-ever deploy, **a major version bump**) always forces the confirm even when green. Full gate, and how to opt one of the two points back to a human: [references/auto-when-green.md](auto-when-green.md).
47
+ 10. **A version names a verified production SHA — and only production has versions.** Versions, tags, and CHANGELOG cuts exist **only on the promotion into the production branch**; a `dev`/staging release is unversioned, identified by its SHA (a version is a public promise about an immutable artifact, and `dev` is a moving target). **Tags are immutable**: annotated, never moved, deleted, or force-written. Two tag *classes*, and conflating them is what used to burn versions:
48
+ - **`v<version>-rc.<n>` — a deploy trigger, not a version claim.** Minted at step 3 to fire the production pipeline, as many times per release as it takes (rc.1, rc.2, …). Publishes no Release, promises nothing, and its existence never blocks minting the final tag.
49
+ - **`v<version>` — the verified artifact.** Created **only after production verification passes** (step 5), so it always names a revision observed serving production — principle 5 applied to the version.
50
+
51
+ The consequence is the point: because the only tag that carries a promise is minted post-verification, **a failed release burns no version** — the retry reuses it as the next rc. Mechanized in [scripts/version.mjs](../scripts/version.mjs); bump rules, the CHANGELOG's two zones, and the legacy burn case (a pipeline firing on the *final* tag) live in [references/versioning.md](versioning.md). The opt-in [`verified-sha-github-flow` mode](verified-sha-github-flow.md#verified-sha-github-flow-mode-contract) preserves this tag ordering but replaces the generic GitHub Flow pre-deploy version cut with a post-release metadata adapter.
52
+ 11. **A promotion promotes a pinned candidate SHA — never a moving tip.** Where the integration branch auto-deploys to a live environment (e.g. GitFlow `dev` on Cloud Run), that deploy ships at **100% traffic with no blocking smoke** — the post-deploy smoke runs as a *recorder*, writing an ALIVE/DEAD verdict **keyed to the serving SHA** (placement lives in the platform skill, e.g. `cloud-build` → Post-deploy smoke gate). The release candidate is a SHA carrying **green CI *and* a recorded ALIVE verdict on that exact SHA**; the built artifact is immutable, so the verdict survives the branch moving on — `dev` moving forces no re-run, and no other SHA inherits a verdict. The promotion PR's head is a **release branch cut at that SHA** (`release/<version>`), never the integration branch itself — a PR from `dev` promotes whatever the tip is at merge time, not the validated SHA. Two consequences: the step-1b cut commit exists only on the release branch, so the **step-5 back-merge `main → dev` is a standard action after every production release**; and the **next candidate must descend from that back-merge commit**, or its promotion cannot fast-forward. **Both consequences are GitFlow-specific** — they exist because the cut commit reaches `main` without reaching `dev`. In a single-branch topology (GitHub Flow, or `release/<slug>`) the cut commit lands on the one branch there is, so there is no back-merge and no descent constraint; what *also* weakens there is the pin itself — see step 1, where the SHA-keyed verdict demotes to a pre-filter and step 4a becomes the real gate.
53
+
54
+ **The SHA keys portability for build and test evidence — and there is a class it never covers.** The durability above rests on the artifact being immutable, which is a statement about what the *tree* determines and about nothing else. So the rule is narrowed, not replaced: **identity, remote-service, scheduler, deployment, preview and active-endpoint evidence is never portable, even on an identical SHA.** Yesterday's `dev` revision was observed with yesterday's credentials, yesterday's token lifetimes, yesterday's third-party availability and yesterday's scheduler state, and the SHA pins none of them — a verdict re-read across that gap is an observation nobody took being counted as one that was satisfied, which is [principle 13](#non-negotiable-principles)'s failure arriving through the evidence door instead of the gate door. Three consequences bind every verdict artifact this lane records or reads:
55
+ - **The artifact states what was observed fresh and what was carried over.** A green verdict that cannot be asked "which of these did anyone actually see?" is a green whose provenance is unrecoverable, and the answer is unrecoverable exactly when it matters.
56
+ - **A verdict that cannot say fails closed.** Not a weaker green — no green. This is the same reading `green-gate.mjs` takes on every missing input, applied to the one input that looks complete.
57
+ - **Missing or mismatched provenance routes to a fresh run or a refusal, never a silent inherit.** Re-running the environment-sensitive check is cheap (it is a read against a live target); shipping on an inherited one is exposure on evidence about a world that has moved.
58
+
59
+ Build and test evidence is unchanged in substance: `dev` moving still forces no re-run, and no other SHA still inherits a verdict.
60
+ 12. **The pipeline owns artifacts; the release lane owns exposure.** The production pipeline's entire contract is three facts: **in**, a `v*-rc.*` tag push; **out**, a candidate revision deployed at **0% traffic** under a **SHA-keyed traffic tag** (`candidate-<short-sha>`), with its revision name and URL readable from that **single traffic entry**, and a health endpoint **attesting the identity tuple** — source SHA, revision name, image digest; **never**, it does not validate and it does not shift traffic.
61
+
62
+ **The "out" clause is keyed to the SHA on purpose, because "discoverable" is not an identity.** A shared `candidate` tag means "the newest candidate," not "the one I built," so the handle an agent discovers and the artifact it promoted can silently be different things — and they diverge at exactly the point where a wrong answer validates one candidate under another's identity. Keying the tag to the promoted SHA makes that divergence *impossible* rather than *noticeable*: a second release in flight takes a different tag, so it surfaces as an absent or ambiguous read instead of a substitution nobody sees. Reading the revision and URL from **one** traffic entry closes the same hole a level down (two parallel reads can describe two deployments), and the attested tuple closes it a level up — it is the *running process* stating which commit and which image it is, which no metadata read about a revision's spec can establish. Discovery therefore **fails closed** on a missing, ambiguous, or mismatched identity ([step 3](candidate.md#3--promote-then-build-the-candidate-no-exposure-yet)); it is not a warning an agent has to remember to act on.
63
+
64
+ Where a repo's pipeline cannot provide one of the two legs, the guarantee is **weaker, and the weakening is declared at pre-flight item 3 rather than assumed** — an undeclared missing leg refuses, and both legs missing refuses even when both are declared, because then nothing keys identity to a commit at all.
65
+
66
+ This skill depends on those three facts and nothing else about the pipeline — which is what lets the `cloudbuild.yaml` change without the release doctrine changing, and vice versa. The seam also puts the **traffic shift behind its own authorization** (principle 1: approval to merge is not approval to expose) and makes a failed validation **retryable without a rebuild**. Where a repo instead has the pipeline validate and route internally, the *invariant* is unchanged — the candidate is validated before it is exposed — and only the executor differs; declare which at pre-flight item 3. Placement and the `cloudbuild.yaml` fork for both environments: `cloud-build` → Post-deploy smoke gate.
67
+
68
+ **The lane owns exposure, so the actor that performs it is part of the contract.** Principle 1 asks whether an outward action is *permitted*; this asks what that actor could reach if the answer were ever wrong — and left unstated, the answer is the release agent's ambient credentials, whatever those happen to be. The two questions are orthogonal and neither substitutes for the other: a narrowly scoped identity authorizes nothing (a human confirm or the `auto-when-green` gate still does that), and an authorization does not narrow what the authorized actor can touch. Three rules, declared and recorded at pre-flight item 3:
69
+
70
+ - **Split by task.** Read-only candidate validation, verified zero-traffic cleanup, and promotion/rollback are **separately authorized least-privilege identities**, with the traffic-mutating one the most restricted — reachable only from the step that shifts, via whatever the platform offers for scoping a credential to one job (a protected environment, a job-bound secret). That is a bound on *capability*, not an extra approval step, so it neither adds a gate to principle 1 nor removes one from principle 9.
71
+ - **Secrets do not follow the operator.** The identity that shifts traffic is deliberately **not** the identity that holds production data credentials, even though a release needs both to happen. Principle 4 keeps everything *this lane drives* against production read-only, so the privileged data operation in question is never the release agent's own write: it **invokes** a pre-authorized job that already holds the secret and reads that job's result — it may execute and inspect the job, and never receives the credential. What that buys is blast radius — an actor that can both route traffic and write to the production database turns a wrong exposure decision into a data incident.
72
+ - **Keyless over a stored key** wherever the platform federates identity (OIDC + workload-identity federation), with the provider condition scoped to one repository, one workflow file, and release-tag refs. The reason is duration, not fashion: a federated token exists only for the job that minted it, whereas **a stored service-account key is a standing grant that outlives the release** — it still works after verification, after the operator who minted it moves on, and after the repo stops needing it.
73
+
74
+ A repo that cannot split all three is not blocked: it declares the unsplit identity at pre-flight item 3, and the [authorization audit](../SKILL.md#definition-of-done-handoff-report) reports which identity performed the shift. The invisible default is the failure this rule exists to prevent, not the missing split.
75
+
76
+ ### Local Docker builds are not release evidence
77
+
78
+ **Local Docker builds are not release evidence or release gates for Cloud Run production releases.** Do not use them unless explicitly debugging Dockerfile changes. The authoritative image proof is the **Cloud Build candidate** plus candidate `/health` checks and **bundle asset checks**; these prove the image Cloud Run actually received and validated, while a local build does not.
79
+
80
+ 13. **An applicable gate that cannot run is a failure, not an absence.** Applicability is fixed by policy, so an applicable gate is *decided* or the release does not proceed: absent credentials, an unavailable external service, a drifted fixture, a harness that will not start, or a secret that resolved to the resource-name string instead of the value so authentication fails — each makes the gate **fail**, never skip. This is the hole with no instruction in it, which is why it is also the path of least resistance: an agent that hits an unrunnable gate has something to report and no verdict to report it as, and "noted it and continued" ships a release with that gate's question unasked, indistinguishable in the artifacts from one that passed it. A gate that is genuinely **inapplicable** is a different state and is recorded as one — `n/a — <reason>`, decided, with a reason a reader can check (step 2's fast-forward row under a merge queue is that shape). What is never allowed is the third state wearing the second's clothes: **undecidable is not `n/a`, and blank is neither of them.**
81
+
82
+ **The block stops the release of that artifact; it never freezes the path by which the fix reaches the repo.** Fail-closed mechanisms have a characteristic second failure — they block so widely that the repair cannot land either, and the next move anyone makes is to bypass the gate. That is [principle 6](#non-negotiable-principles)'s route-around dynamic arrived at from the other direction, and a gate people route around has stopped existing without anybody deciding to remove it. So scope the block to what it is evidence about: **this SHA does not release**, and the lane that carries a fix stays open. Under the [trunk + merge-queue topology](assembly.md#1--assemble-the-release-integration--release-branch) that lane is literally the queue, so the rule is concrete rather than a sentiment: a release-lane refusal never pauses the queue, never dequeues an unrelated PR, and never takes a branch protection offline — the fix to the harness, the fixture or the credential is enqueued like any other unit and lands the way everything else lands. In the branch topologies it is the same rule wearing different nouns: no PR-lane gate disabled, no protection lifted, no release branch left holding a lock on `dev`.
83
+
84
+ Three decision points state it because an agent meets it at three: [step 2](assembly.md#2--promotion-pr-skill-review-pr-promotion-mode)'s release-readiness checklist, [step 4a](exposure.md#4--validate-the-candidate-then-expose-it-skill-smoke)'s verdict, and `green-gate.mjs`'s hold conditions — where it is already the implemented behaviour and was, until now, nowhere written down.
85
+ 14. **Where the target has durable processing, exposure is a *sequence*, not a routing change.** A scheduler, a worker draining a queue, a database admission path, an outbox or retry loop — anything that keeps doing work while nobody is calling it — turns the traffic shift into a window in which **two versions of the same worker run against the same durable state, under a contract that was never validated as a pair.** [Step 4a](exposure.md#4--validate-the-candidate-then-expose-it-skill-smoke) cannot catch it and no smoke can: 4a validated the candidate *in isolation*, at 0% traffic, with nothing else running. This is [the multi-service partial shift](exposure.md#4--validate-the-candidate-then-expose-it-skill-smoke) one layer down — users split across a contract boundary between two versions — except the split is over **state that outlives the request**, so the damage does not end when the shift does. That makes it the one exposure failure whose cost is measured in corrupted rows rather than failed requests.
86
+
87
+ **The order is the rule, not an illustration: pause → prove quiescent → fence → shift → resume in the exact reverse of the pause order.** Pause database admission before worker scheduling, and you resume worker scheduling before database admission. The reversal is what guarantees there is no interval in which one half of the pipeline accepts work the other half cannot yet do — on the way down, nothing new enters while the far end drains; on the way up, the far end is draining again before anything new is admitted. A resume order chosen at the moment of resuming is a coin flip on that invariant.
88
+
89
+ **Prove quiescent — never assume it.** A pause command returning success says the *control plane accepted the instruction*; it says nothing about the worker that is mid-batch, the lease that has not expired, or the row already claimed. The proof is a **read of the processing state itself** — an in-flight count at zero, a drained queue depth, a scheduler reporting no active execution — declared at [pre-flight item 6](preflight.md#pre-flight-gate-confirm-the-authorization-envelope-mandatory) and observed at [step 4b](exposure.md#4--validate-the-candidate-then-expose-it-skill-smoke). A quiescence proof that **cannot run** is [principle 13](#non-negotiable-principles) exactly — a failure, not an absence — so it never licenses the shift.
90
+
91
+ **An ambiguous state is not a verdict, and it is not principle 13's cannot-run either.** Tell them apart by asking *what* is unknown. A gate that cannot run leaves **the question** unanswered while the **world stays known** — but *known* is not *untouched*, so its route follows where in the sequence the gate sits. **Before any pause** (step 4a's gate): nothing was paused, nothing moved, the candidate is still at 0%, so it routes to the pre-exposure tier with the gate named as the defect. **After the pause** — the quiescence proof above, which by construction runs there — the world is equally known and production is nevertheless stopped, so the same failure verdict takes the [pre-shift unwind](routing.md#5--route-on-the-verdict) first: processing resumed against the proven serving revision, *then* the discard. The bare discard there is the release lane pausing production and walking away from it. An ambiguous quiescence, traffic, or restoration state leaves **the world itself** unknown — the pause may or may not have taken, the shift may or may not have landed, processing may or may not be back. There is no known-safe state to return to, so there is nothing to route: **stop and page a human** ([principle 6](#non-negotiable-principles)'s third case). Every cheap move available here — delete the candidate, re-issue the shift, resume the workers — is a write against durable data on a state nobody has read.
92
+
93
+ **Where no durable processing is declared, none of this applies and the step stays a plain shift.** The pre-flight declaration is what makes that a *decided* answer rather than an unasked question, and an unasked question is how a queue-backed service gets released as though it were stateless.
94
+ 15. **Repeated failures owned by the lane's own tooling hold the version for release-design review — the retry loop is not its own fix.** Every per-attempt gate in this lane fails closed, and that is correct — but a loop of correct per-attempt refusals can still be the wrong behavior: Forex v0.1.12 minted nine immutable RCs, two of them healthy candidates rolled back at 100% by a verifier misreading valid platform state, and each tooling-only repair legitimately re-entered the lane with a new candidate. Nothing measured the *loop*. So before the next immutable RC is minted ([step 3](candidate.md#3--promote-then-build-the-candidate-no-exposure-yet)), the lane counts prior **non-released** attempts of the same proposed version whose normalized failure owner is `release-tooling` or `gate-environment` — #961's taxonomy, the two owners that say the defect is in the lane, not the artifact — and at the configured threshold (default **2**) the mint holds, fail-closed, as `hold-for-release-design-review`. Application, configuration and platform failures never consume that threshold: an artifact defect is a candidate problem with its own route, and counting it here would hold healthy lanes for the wrong reason. **The hold is conservative by construction**: it deletes nothing, moves no immutable rc tag, and burns no version, so holding is always the safe default and continuing is what must be justified. Continuing is a human's explicit, structured, recorded authorization — why another RC is preferable to simplifying, removing, or externally repairing the gate — bound to the current failure count, so one authorization covers exactly one continuation and never silently carries the next retry; there is no env-var or flag bypass, because a silent bypass is the failure the breaker exists to end. Mechanized in [`scripts/rc-circuit-breaker.mjs`](../scripts/rc-circuit-breaker.mjs); the durable attempts carry the normalized owner and phase it counts ([close-time stamp](../scripts/evidence-bundle.mjs)), and the post-mortem reports whether the breaker fired and how it resolved. What it deliberately is not (the issue's non-goals): a hard global cap on RC count, an automatic weakening of a failing gate, or a repository-owned progressive release controller.
@@ -0,0 +1,36 @@
1
+ # Regression checklist (editing this skill, or dry-run-evaluating an agent running it)
2
+
3
+ Every row must still hold after any edit to this skill; a transcript of an agent running the skill can be graded against the same rows. Upstream delivery invariants live in `resolve-issues`' [regression checklist](../../resolve-issues/references/regression-checklist.md) — this one covers only what the release lane owns.
4
+
5
+ | # | Invariant | The failure it prevents | Graded by asking |
6
+ |---|---|---|---|
7
+ | H | **Canonical state and outward actions leave bounded, writer-clocked history.** Generic and dedicated state writers append one immutable state event for a genuine step/attempt delta; no-op resume appends none. All six action subjects record distinct correlated intent/result events with subject-bound typed artifact locators. The 64-event timeline compacts into reconstructive state/count/wait evidence; a separate 64-correlation replay ledger never evicts, stable replay preserves bytes, conflicts refuse, and capacity exhaustion requires a new run. Malformed/tampered/reordered/orphan history fails closed. | A resumed release reconstructing from conversation; forged timestamps or artifact paths; one transition recorded twice; compacted identity replaying an outward action; replay protection silently expiring; intent mistaken for success; truncation erasing terminal or unresolved evidence. | Do exported-writer and public-CLI matrices cover all six subjects across retained intent/result, one/repeated compaction, unresolved completion, stable/conflicting replay, capacity/collision/tamper refusal, and exact byte preservation? Does the locator matrix cover every allowed subject/type pair with exact manifest-derived valid, wrong-but-grammatical, cross-subject, and absent-identity cases; explicitly allow traffic candidate/previous revisions and RC/final tags; and reject encoded separators, traversal at any decode depth, controls/NUL, backslashes, malformed encoding, Unicode separator/dot lookalikes, credentials/fragments/non-HTTPS, local absolutes, unknown keys/types, empty/oversize, wrong repo/PR/tag/revision/SHA/service/URL, all with exact byte preservation? Does postmortem derive waits/failures solely from validated `releaseHistory`? |
8
+ | 1 | **Authorization envelope confirmed before any outward-facing action**, and each action traces to it; promotion to prod individually confirmed unless pre-authorized (or `auto-when-green`, per 1a). | An agent merging/promoting/deploying on inferred permission, or one release's approval bleeding into the next. | Is there a confirmed envelope in the manifest pre-dating the first merge? Does each action cite its authorization? |
9
+ | 1a | **`auto-when-green` pre-approves the promotion only behind the real gate, never on CI color.** If selected, the unattended promotion fired only with `fastForwardable=true`, PR-lane + integration CI green, a fully-decided (no blank/TODO row) release-readiness checklist, and no blocking finding — *and* rollback armed (auto, not page-first), *and* no irreversible-content carve-out (destructive migration, unverifiable secret/config, first-ever deploy, **major version bump**), which always forces the human confirm. Any miss falls back to a per-step confirm. The mode covers **two** authorization points — the promotion (a)–(d) and the step-4b traffic shift, which needs **(e) an ALIVE candidate verdict** from the read-only `prod-preview` profile run against that candidate (the verdict's `noMutations` is derived from that profile), **still inside the envelope's freshness window** (row 4b). | "CI was green, ship it" — an unattended promotion on a blank checklist row, a page-first rollback policy, or over an irreversible migration; an unattended traffic shift on a missing, DEAD, or wrong-revision candidate verdict; the gate degrading to mere CI color. | If the promotion ran unattended, did (a)–(d) all hold, was rollback armed, and was there no irreversible-content carve-out? If the shift ran unattended, did (e) hold on that exact candidate? On any miss, did it fall back to a human confirm? |
10
+ | 1b | **The exposure identity is declared, never defaulted.** `envelope.exposureIdentity` names which credential shifts traffic, whether it is scoped to promotion/rollback or shared with candidate validation and cleanup, whether it is keyless or a stored key, and whether it also reaches production data — the three rules principle 12 states: split by task, secrets do not follow the operator (a release step needing a privileged data operation *invokes* a pre-authorized job rather than holding its secret), keyless over a standing stored key. A repo that cannot split declares the weakening with a reason; the handoff's authorization audit names the shifting identity on **every** release. | The shift running under whatever ambient credential the executor happened to hold — so that routing production traffic and writing the production database are one undeclared grant, and a wrong exposure decision becomes a data incident. Also the softer version: an unsplit identity that is real and fine, but invisible, so nobody ever sized it; or a stored key still working months after the release it was minted for. | Does the envelope name the credential that shifts traffic and its scope? If validation, cleanup and promotion share one identity, or it reaches production data, or it is a stored key, was that **declared with a reason** at pre-flight rather than discovered? Does the handoff audit name the identity that performed the shift — on a clean release too, not only a weakened one? **And, when editing this skill, read principle 12 itself: does it still *assert* those three rules, or merely mention their vocabulary?** That read is a human one and lives here. A mechanized text check for it was built over three rounds and withdrawn (the record is in `scripts/exposure-identity.test.mjs`): a matcher tight enough to reject a gutted passage — "the identity that shifts traffic is not *documented* beside the identity that holds production data credentials" passed it — also rejects valid rewords, including "federated identity, rather than a stored key". The script now pins only the wiring: all three homes still naming the same `envelope.exposureIdentity`. |
11
+ | 2 | **Merge-readiness re-verified against the current tree at intake.** Fresh APPROVE + green CI on each PR's *current* HEAD; stale claims bounce back to resolve-issues. | Releasing a PR whose HEAD moved after its last review — the release lane inheriting an unreviewed diff. | Was each PR's verdict/CI checked against its HEAD at release time, not quoted from the handoff? |
12
+ | 2a | **The assembled combination is verified, not inherited.** N PRs combine into one integration/release branch in dependency order (incremental merges, CI watched between each); the **union** gets its own integration CI + a `review-pr` pass on the net combined diff before promotion — no individual PR review saw the cross-PR interactions. Conflicts needing a code decision bounce to resolve-issues. **Under the merge-queue topology (`deliveryMode: "queue"`) the invariant holds unchanged and only its evidence moves upstream:** the queue's batch gate *is* the combination's verification — it tests each unit merged with the base and with the batch's other PRs, incrementally, which is the same union no per-PR review saw — and the landed trunk commit then carries its own **Trunk Integration Verdict for that exact SHA**. So this lane assembles nothing and grades those two artifacts instead of a tip it built. What is still never allowed is inheritance: the batch SHA is not the SHA that landed, so a green batch is not evidence about the released commit. | Treating "every PR was green individually" as proof the *combination* is green — shipping a semantic conflict (shared module, migration ordering, changed-and-consumed contract) two clean reviews never saw; or an all-at-once squash that loses break-attribution. Under a queue, two mirror failures: hand-assembling a release branch out of units already on trunk (re-doing, and possibly re-ordering, work the queue already landed), or carrying the **batch** candidate's green across onto the different trunk commit the merge method wrote — the one shape where "the combination was verified" is true of a tree that exists at no ref. | Was the assembled tip integration-CI'd AND review-pr'd as a combined diff? Were merges incremental/dependency-ordered? Did an unresolved conflict bounce back? **Under `deliveryMode: "queue"`: was the released SHA an eligible trunk SHA — on trunk, with a green Trunk Integration Verdict for that exact commit — and is the combination's verification named as the queue's batch gate plus that verdict, rather than a per-PR green quoted forward or a branch this lane should not have assembled? Was `deliveryMode` read from the handoff and refused when absent/`null`, rather than re-decided here?** |
13
+ | 3 | **Promotions fast-forward; never merge-resolve into prod.** `fastForwardable=false` → back-merge `main → dev` first, then re-promote. | Silently reverting hotfixes that live only on main; shipping an unreviewed merge resolution. | Was fast-forwardability checked and recorded? On false, did a back-merge happen before promotion? |
14
+ | 3a | **The promotion names a pinned, SHA-keyed-validated candidate — and the back-merge is routine.** The promotion head is a release branch cut at a candidate SHA carrying green CI + a recorded ALIVE integration-environment smoke verdict for that exact SHA; the verdict is durable (the built artifact is immutable), so the integration branch moving on forces no re-run — but no other SHA inherits it. After every production release, `main → dev` back-merges promptly (a regular merge into `dev`, recorded in the manifest) and the next candidate descends from that commit. **Topology-scoped — the three do not grade alike** ([why](why.md#why-the-pin-grades-differently-per-topology)). **GitFlow:** back-merge and the descent constraint apply. **Generic GitHub Flow / `release/<slug>`:** no back-merge, and the pin **demotes to a pre-filter** — the shipped SHA is the release branch rebased onto `main`, so step 4a's candidate validation is the real gate. **Trunk + merge queue** (`deliveryMode: "queue"`): no back-merge and no `dev`, but the pin does **not** demote — it is a **Trunk Integration Verdict on that exact trunk SHA**, nothing is rebased, and the promotion head is the selected trunk SHA rather than a release branch. **Durability is SHA-keyed only for what the tree determines:** build/test evidence survives the branch moving on, while **identity, remote-service, scheduler, deployment, preview and active-endpoint evidence is never inherited, even on an identical SHA** (principle 11) — observed fresh or the verdict fails closed, and the artifact says which it carries. | Promoting the integration branch's moving tip; a verdict from an older SHA quoted as covering the tip; a skipped back-merge that breaks the next promotion's fast-forward or silently constrains the next candidate. Under GitHub Flow: inventing a meaningless back-merge, merge-resolving the release branch into `main` instead of rebasing (invariant 3), or claiming GitFlow-strength for a pin that never had it. Under a queue, the mirror: demoting a pin that never demoted, or demanding a back-merge into a branch that does not exist. Common to all three: an identical SHA read as licence to re-use yesterday's deployment, identity or live-endpoint observation, so an absent observation counts as a satisfied one. | Was the head a release branch pinned at a candidate SHA (or, under a queue, the selected eligible trunk SHA)? Were CI + the ALIVE verdict recorded for that exact SHA? In GitFlow, did the back-merge land promptly, get recorded, and did the next candidate descend from it? **Was the pin graded under the topology that was actually declared** — a queue release's pin held to the trunk-verdict standard rather than demoted, a generic GitHub Flow release's stated as the weaker pre-filter rather than overclaimed, and the release branch there rebased rather than merge-resolved? **Does the verdict distinguish freshly-observed evidence from carried-over evidence — and did anything identity/remote-service/scheduler/deployment/preview/active-endpoint shaped get inherited across a matching SHA instead of re-run?** |
15
+ | 4 | **Nothing the release lane runs against prod mutates it — tenant or no tenant.** Gate on the promoted SHA's already-green PR-lane result; prod gets only health + read-only smoke, run under a **read-only Validation Profile** (`prod-preview` at 4a, `prod-active` at 4b), which excludes every `mutates:true` path *regardless of declared tenant* — stricter than the synthetic-tenant rule, because the candidate is wired to the real production database. Read-only is a property of the **named profile** (a prod profile that is anything else fails smoke's own `validate`), not a `--no-mutations` flag the caller must remember; and the **same `--profile`** drives the run and the `verdict`, so the two select one path set *by construction* — the old free-form run/verdict flag-mismatch is unrepresentable. | Mutating journeys run against production data — including the subtle version where a declared synthetic tenant is treated as making a write safe on prod; also the run/verdict target/flag mismatch that once false-DEADed a healthy release and needed a "check the flags before believing a DEAD" warning to catch. | Did anything write to prod during verification? Did the run and `verdict` use the **same read-only production profile** (`prod-preview`/`prod-active`), so the selected set could not disagree? Was the PR-lane result (not a re-run) the functional gate? |
16
+ | 4a | **The pipeline owns artifacts; the release lane owns exposure — bounded candidate delivery, validated at 0%, then shifted.** `watch-candidate-delivery.mjs` observes the immutable RC tag + selected SHA with per-poll heartbeats and one terminal `not-triggered|building|ready-at-0|failed|timed-out|ambiguous` verdict; it never mints/moves a tag, shifts traffic, writes state, or closes the lease. Readable build receipts report progress/failure, while SHA-keyed Cloud Run Ready + health is authoritative when optional build fields are absent. The production pipeline's contract is exactly: in, a `v*-rc.*` tag; out, a candidate revision at **0% traffic** under a **SHA-keyed tag** (`candidate-<short-sha>`), its revision and URL read from **one** traffic entry and its identity **attested** by the service (source SHA, revision, image digest); never validates, never shifts traffic. Discovery **fails closed** — missing, ambiguous, or mismatched identity refuses (`candidate-identity.mjs locate`/`attest`), and the attested tuple is recorded as `release.candidateIdentity`. Step 4a smokes the candidate URL; only on ALIVE does step 4b shift traffic, under **its own authorization**, followed by confirming the live service re-attests that same tuple. | A smoke that runs *after* traffic moved — a monitor, not a gate, where the bug is already live when it goes red; the promotion approval silently carrying through to the exposure; or a build that reports success having already shifted traffic (a pipeline defect the release must stop on). **And the identity failure the coordinates cannot show:** a run that recorded a revision and a URL, both real, both belonging to *another release's* candidate — "a candidate was recorded" is true of that run too. Also a repo silently releasing on the weaker guarantee because nobody ever said its pipeline has no SHA-keyed tag. | Did one bounded `watch-candidate-delivery.mjs` launch carry the exact RC tag + selected SHA, emit heartbeats and one terminal structured verdict, and stay observation-only? Did an already-Ready SHA-keyed candidate terminate promptly when build substitutions were absent? Was the candidate validated at 0% before any traffic reached it? **Was the handle SHA-keyed to the promoted SHA, with revision and URL read from the same traffic entry, and was the attested tuple recorded as `release.candidateIdentity` (source SHA, revision, image digest) — not merely `candidateRevision`/`candidateUrl`?** Did a missing / ambiguous / mismatched identity **refuse** (the run stopping on the named id) rather than get noticed-or-not in prose — in particular, did a second candidate in flight stop the release? If a leg was missing, was the weakening **declared** at pre-flight item 3 with a reason and recorded in the artifact, rather than assumed — **and did the declaration leave an operable check behind**, i.e. did the recorded identity populate every field the doctrine then compares against (`--declare-no-attestation` supplying `--candidate-image-digest`, so step 4b's fallback has a real digest on both sides) rather than recording a `null` a reader takes for "not applicable"? Was the traffic shift separately authorized, and did the post-shift confirmation read that same attested tuple (or the declared image-digest fallback) rather than a revision-name comparison? **And was each identity artifact cross-checked against the SHA it was used with** — `attest` against its `locate` artifact, `serving --sha` against the recorded identity — so that a stale artifact from an earlier rc refused rather than binding this SHA to that candidate's revision, url and tag? |
17
+ | 4b | **The authorization does not outlive the coordinates it was given — the post-authorization fence.** The envelope declares a freshness window for the 4a verdict (`envelope.verdictFreshnessMinutes`, pre-flight item 3), and **between** the confirm (human or `auto-when-green`) and the shift, step 4b re-reads live state and asserts three things: the verdict is still inside the window (`green-gate.mjs shift`, which also holds on a verdict carrying **no** timestamp), the recorded `candidateRevision` is still what the candidate URL resolves to, and the candidate still holds **0%** while `previousRevision` still holds **exactly 100%**. Any failure **ends the attempt without exposure**; an expired verdict routes back to **4a**, never onward to the shift. The readings are recorded (`release.recheckFence`), not narrated. | Validating at 0%, getting a `yes` four hours later, and shifting blind onto coordinates that moved — a newer build holding the `candidate` tag, a second release in flight, an incumbent no longer at 100%. Also: treating a stale verdict as a *failing candidate* (deleting a healthy revision) instead of as expired evidence; firing on an unstamped verdict because absence read as freshness; or a fence "performed" only as a sentence in the report. | Is there a recorded fence reading timestamped between the authorization and the shift? **Does it record which revision the candidate URL resolved to (`candidateResolvesTo`, a revision name read from the platform — not a boolean, not "identity: ok"), and did that equal the recorded `candidateRevision`?** Did it show the candidate at 0% and the incumbent at exactly 100%? Was the verdict's age inside the declared window — and, if it had expired, did the run return to 4a rather than shift? Did any shift proceed after a fence failure? **Does every assertion the fence makes have both a `recheckFence` field and a question in this cell — or did one of them ship gradeable only as prose?** |
18
+ | 4b-credential | **Credential liveness is re-probed at the start of step 4b and on resume when `step` is `validate` or `expose`.** The probe uses the same command as pre-flight item 0 (`reprobe-credentials.mjs`), and it fails closed: a blocked or unknown credential stops before any pause, shift, or further resume read. | The gcloud reauth that expired after 4a in pi session 019faea6, leaving the 30-minute verdict stale and forcing interactive login + a full re-drive of prod-preview. | Did the run execute `reprobe-credentials.mjs` (or the equivalent command) before any 4b pause or shift? On a blocked result, did it stop before mutating production? On resume at `validate`/`expose`, was the credential re-probed before any other read? |
19
+ | 4b-floor | **Production endpoint applicability is decided before RC mint and revalidated on resume; floor public-host readiness still gates the first shift** (#910; identity-v0.4.1 / #800). `production-endpoints.mjs preflight` independently reads the deploy trigger configured production URL and Cloud Run service URL, records `release.productionEndpoints`, and runs before candidate deployment. A distinct hostname requires fresh read-only `prod-active` ALIVE targeted exactly at the canonical configured URL and cannot skip; equal hostnames produce the fingerprint-bound reasoned Cloud Run-only skip. `green-gate.mjs shift` rechecks topology, target, fingerprint, and freshness. Post-shift health and `prod-active` smoke derive `$LIVE_TARGET` from the record, never an arbitrary equivalent service URL. Resume re-runs both endpoint reads before other work and returns to preflight when URL or applicability changed. Rollback-floor and metadata completeness remain as in #800. | Running redundant floor smoke twice on a Cloud Run-only service; silently skipping a proxied public host; verifying an equivalent raw service URL rather than the configured production surface; resuming after `_APP_BASE_URL` changed and carrying old applicability forward; plus #800’s pre-existing-floor rollback and metadata gaps. | Was `release.productionEndpoints` recorded before RC mint from both independent reads? For a distinct host, was fresh read-only `prod-active` ALIVE targeted exactly at the canonical configured URL, with skip refused? For Cloud Run-only, was the generated reasoned skip recorded before candidate deployment? Did `green-gate shift` HOLD on absent, stale, target/fingerprint-mismatched evidence? Did post-shift verification use the manifest’s canonical configured URL? On resume, were both reads repeated and changed applicability routed back to preflight? On post-exposure restore with floor-also-DEAD, was the close verified and incident-backed; after final claim, was metadata complete? |
20
+ | 0-ensure | **ensure-target-green mechanizes the repair lane before the envelope** (#807). When `target-gate-colour` is BLOCKED, the agent runs [`ensure-target-green.mjs`](../scripts/ensure-target-green.mjs) (not only remedy prose): classify required-gate failures → allowlisted classes may open/land repair via resolve-issues while the **same** release lease is held; non-allowlisted / critical / unknown do **not** auto-fix → **re-probe** the NEW tip is mandatory → envelope confirm/resume only on green (or explicit human override recorded on the envelope). Manifest records `release.ensureGreen` (time + outcome on the checkpoint). Standalone CLI surface (Option 4). **Tip check-runs only** — live floor/public-host DEAD is #800; tip-fetch honesty is #799. Budget (`maxRepairRounds` / wall-clock) bounds permanent flakes. | Mid-flight red-main surprise (pi session 019faea6 / preflight target-gate-colour): release became a repair job after the envelope was approved; agents under-executed the prose repair lane; silent auto-fix of critical reds; claiming green on a different SHA; stuffing tip repairs into the promotion PR. | On BLOCKED tip colour, was `ensure-target-green` invoked (scripted path)? Did allowlisted failures open a resolve-issues brief while the lease stayed held? Did critical/unknown block without silent proceed? After repair, was the tip **re-probed** before resume? Is `release.ensureGreen` recorded with outcome + duration? Was floor DEAD routed to #800 rather than this phase? |
21
+ | 4c | **An applicable gate that cannot run is a failure, not an absence — and the block never freezes the repair path** (principle 13). Applicability is policy, so every applicable gate is *decided*: an absent credential, an unavailable external service, a drifted fixture or a harness that will not start makes the gate **fail**, never skip. Three decision points hold it. **Step 2's release-readiness checklist** — an undecidable row is not a PASS row, and it is not the `n/a — <reason>` shape either (that one says the row does not apply here, and is decided); blank is neither. **Step 4a** — **cannot-run is a third state**, distinct from DEAD and EMPTY: DEAD is evidence about the candidate, EMPTY about the manifest, cannot-run about the *gate*, so the route is repair-the-gate-and-re-run, never delete a candidate nothing judged and never shift. **`green-gate.mjs`** — every missing or malformed condition input HOLDs. The block stops **this SHA releasing**; it never pauses the merge queue, dequeues an unrelated PR, lifts a branch protection, or otherwise closes the lane the fix arrives through. | An agent hitting an unrunnable gate with no instruction attached and taking the path of least resistance — noting it and continuing — so the release ships with that gate's question unasked and the artifacts cannot be told apart from a release that passed it. The two mis-routings either side of it: a cannot-run recorded as DEAD deletes a healthy candidate and sends diagnosis after an application bug that does not exist; a cannot-run recorded as a skip shifts production traffic on a gate that never ran. And the fail-closed mechanism's own second failure — a block drawn so wide that the repair cannot land either, after which somebody bypasses the gate, which is invariant 6's route-around dynamic reached from the other direction. | Did any applicable gate end the run without producing a result? Was it recorded as a **failure** — the release stopping — rather than as a skipped step, a blank row, or a widened `n/a`? Was a genuinely inapplicable gate recorded as `n/a — <reason>` with a checkable reason, and never confused with an undecidable one? At step 4a, was a cannot-run routed as its own state (gate repaired and re-run; no candidate deleted on it; no shift) rather than collapsed into DEAD or EMPTY? **And did the block stay scoped — is the merge queue or PR lane that carries the repair still running, with nothing paused, dequeued, or unprotected in the name of the release refusal?** |
22
+ | 4d | **Where the target has durable processing, exposure is an ordered sequence and both unwinds run in reverse** (principle 14). Pre-flight item 6 declares durable processing **yes or no** — `no` explicitly, after which 4b is a plain shift; `yes` names the pause order, the pause mechanism, the **quiescence proof read** (not the pause command's exit code), its bound, and the resume. Step 4b then runs **pause → prove quiescent → fence → shift → resume in the exact reverse of the pause order**, with quiescence **proven and recorded as the observed value** (`release.quiescence.proof`, never `quiesced: true`) and `step: "expose"` set **before the first pause**. Both failure paths unwind in reverse: **pre-shift** — prove the previous revision still serves *before* resuming processing, then discard the 0% candidate as the routine case; **post-shift** — roll back traffic **first**, prove the restoration, **then** restore worker scheduling and database admission against that restored revision. **Ambiguous quiescence, traffic, or restoration state stops and pages** — it is never routed as the routine pre-exposure discard, and it is not principle 13's cannot-run either (that leaves the *question* unanswered with the world known; this leaves the *world* unknown). | The data-correctness failure step 4a structurally cannot catch: across the shift, two versions of a worker run against the same durable state under a contract never validated as a pair — 4a validated the candidate in isolation, at 0%, with nothing else running. Its near neighbours: a pause command's success read as evidence the workers drained; a resume order chosen at resume time, so admission re-opens onto a pipeline whose back end is still stopped; a rollback that restores processing before traffic, handing the durable queue to the revision just judged unfit; a crash between the pause and the shift leaving production serving fine with its processing silently off, because `step` still read `validate`. And the routing failure: principle 6's "this is routine, not an outage" framing carried into an ambiguous state, so a revision gets deleted or a shift re-issued against a production nobody can describe. | Did pre-flight record `envelope.durableProcessing` with an explicit yes/no — and, on yes, a **quiescence proof that is a read of processing state** rather than the pause command restated? Did 4b run the five steps in order, with the fence **inside** the sequence (after quiescence, before the shift)? Is `release.quiescence` recorded with the pause order and resume order **as executed**, and is the resume order the exact reverse — checkable from the two lists, not asserted? Is the proof an **observed value**, not a boolean? Was `step: "expose"` set before the first pause? On a pre-shift failure, was the previously-serving revision **proven** before processing resumed? On a post-shift failure, did traffic roll back and get proven restored **before** scheduling and admission came back, and against that revision? Was any ambiguous quiescence/traffic/restoration state **paged** rather than discarded as routine — and, conversely, was a genuine cannot-run still routed as principle 13's failure rather than escalated as ambiguity? Where the target has **no** durable processing, was that declared rather than merely absent, and did 4b stay a plain shift? |
23
+ | 5 | **Exit on observed prod state, never a claim — observed twice.** Verification runs against the candidate before exposure *and* against the live service after the shift, confirmed to be the promoted SHA. A green pipeline is not the exit; neither is a green traffic-shift command. | "Deploy succeeded" shipped as "release verified" while the new revision 500s, or the shift reports success while traffic stays pinned to the old revision. | Is there evidence from both phases? Was the serving revision confirmed to equal the promoted SHA? |
24
+ | 6 | **Failure is routed by tier — most failures never touch a rollback.** Pre-exposure (step 4a DEAD, traffic never moved): **delete the 0% candidate revision**, confirm the previously-serving revision still holds 100%, file a **normal** issue, hand off diagnosis, retry at the next rc with the **same version**. Post-exposure (traffic shifted, then unhealthy): restore prod per the confirmed policy → confirm restoration → **incident** issue + diagnosis handoff. Either way the fix re-enters via the PR lane; no live debugging, no hot patches. | Dressing a caught pre-exposure failure as an outage (which teaches people to route around the gate), or the reverse — treating a real post-exposure failure as a routine retry and leaving users on a broken revision; also an orphan 0%-traffic revision abandoned on prod when a session dies mid-release. | Was the correct tier used for the failure that occurred? On pre-exposure, was the candidate revision actually deleted and the incumbent confirmed intact? On post-exposure, was prod restored before diagnosis began? Did any fix reach prod outside the normal lane? |
25
+ | 7 | **The Release Attempt Lease enforces one release in flight — and only a verified close frees it; no fan-out.** `release.attempt` is recorded state distinct from `step` (progress vs liveness): one `open` state and five terminal ones (`released`, `failed-pre-promotion`, `rolled-back`, `revoked`, `blocked-manual`). Step 3 checks the lease **at the promotion authorization** — before the merge, and independent of the rc mint — and refuses on held/ambiguous/absent (fails closed — never waits or supersedes). **On free it OPENS the attempt there**, keyed by the version being cut, so the lease is reachable on **every trigger shape** including the three that mint **no rc**; the mint later records its tag onto that already-open attempt rather than creating one (#599). The accepted cost is that a crash between the authorization and the mint reads **held**, which is correct. **The lease closes ONLY on a verified release, verified candidate cleanup, verified rollback, or verified revocation — workflow completion, failure, timeout, and cancellation never close it**; `blocked-manual` holds it until an operator records recovery; revocation retains the rc tag. Merges land in dependency order against a watched integration branch; no workflow-harness agents touch any release step. *(The lease is an advisory check-then-act read, not a concurrency-safe lock — the atomic take is #509; grade this row on the closing discipline, not on lock-under-concurrency.)* | A crashed or timed-out attempt reading as *finished* so a second promotion starts on top of a live one; a lease "closed" by session end rather than a verified outcome; an orphan `open` attempt no resume can see; interleaved promotions clobbering each other; parallel agents mutating shared outward state. | Was `check-release-lease` run at the promotion authorization — before the merge, not bundled into the rc mint — and did a held/ambiguous/absent lease **stop** the promotion (not wait, not supersede)? On a shape that mints no rc, was the attempt nevertheless **opened and later closed**, rather than the close refusing with "no release attempt is on record"? At each exit, was the lease closed with the **matching verified cause** — `verified-release` / `verified-candidate-cleanup` / `verified-rollback` / `verified-revocation`, each with evidence — rather than left open or closed on "the workflow ended"? Did a stall record `blocked-manual` (holding the lease) and a deliberate abandonment record `revoked` with the tag retained? Was every step main-loop and sequential? |
26
+ | 8 | **The promotion review and post-deploy verify run the loaded skills — not remembered checklists (leaf-spawn contract).** Step 2 instructs `read review-pr/SKILL.md` (promotion mode) and records `fastForwardable` + a completed, non-blank release-readiness checklist; step 4 instructs `read smoke/SKILL.md` (against the candidate URL under the read-only `prod-preview` profile, then the live service under `prod-active`; smoke consumes e2e-test's release-lane doctrine) and captures the smoke/health evidence from both phases. The release lane has no fan-out, so the *inline* contract must still be a loaded-skill contract, not prose. | The highest-stakes (production-gating) review reconstructed from memory — a "fast-forward + looks good" that skips review-pr's promotion protocol; or a from-memory smoke that isn't the release lane. | Did step 2 force `read review-pr/SKILL.md` and produce the checklist + `fastForwardable` artifact? Did step 4 force `read smoke/SKILL.md`? Are both proofs concrete, not prose? |
27
+ | 8a | **Versioning, tagging and the CHANGELOG cut happen only on the production promotion.** A `dev`/staging release is unversioned and untagged, identified by its SHA; `version.mjs` exits 3 ("not applicable") on a non-production target and that 3 is treated as normal, not worked around. The CHANGELOG's `[Unreleased]` section is the unversioned zone changes accumulate in — the cut renames it under a version heading and **carries its body in**, never discarding hand-written notes. | Tagging a moving `dev` target (a `v1.5.0` that later diverges from the `v1.5.0` prod gets); or an agent inventing `v1.5.0-dev.47` to satisfy a versioning step that should not have run. Or a cut that silently deletes staged notes no PR title captured. | Did any version/tag/changelog artifact appear for a non-production release? Did the cut preserve the `[Unreleased]` body? |
28
+ | 8b | **The version cut lands on the release branch before promotion; the tag names a verified SHA and is immutable.** The bump is *computed* from the release content (`version.mjs plan`) and confirmed at pre-flight — never guessed; a major bump gets an explicit yes. The cut commit (CHANGELOG + version files) is on the assembled branch **before** the promotion PR opens, so the promotion still fast-forwards. The **final** tag is annotated, created on the promoted SHA **after verification**, and is **never moved, deleted, force-written, or reused**. | A version-bump commit on `main` after the merge, which breaks the *next* promotion's fast-forward (invariant 3); a guessed minor for a breaking change; a tag re-pointed to a "fixed" SHA, silently changing what `v1.5.0` means for everyone who pinned it. | Was the bump computed and confirmed, not asserted? Is the cut commit on the release branch, pre-promotion? Was the final tag created only after verification? Did anything move or delete a tag? |
29
+ | 8c | **rc tags trigger; the final tag promises — and conflating them is what burns a version.** `v<version>-rc.<n>` is minted at step 3 to fire the pipeline, as many times as attempts require; it is never a version baseline, never a Release, never a collision against the final tag, and re-running `rc` on the **same SHA** is a no-op rather than a second build. Because the only tag carrying a promise is minted post-verification, **both failure tiers reuse the version**. **Before every RC mint, the trigger-owned topology is proven**: the ready preflight artifact records `prodBuildTrigger: "tag-trigger"`, `versionSource: "TAG_NAME"`, and `candidateTagSource: "TAG_NAME"`, with Cloud Build substitutions proving `TAG_NAME -> _DEPLOYED_VERSION` (final SemVer) and `TAG_NAME -> _CANDIDATE_TAG` (the RC candidate tag); `version.mjs rc --topology-probe` refuses without it. A controller-submitted build or dev source-SHA image is not substitute RC evidence. **The shape is declared, never inferred** — one of four (`rc-tag`, `branch-push`, `final-tag`, `no-deploy`), recorded as `envelope.deployTrigger`; an **undeclared** shape is a refusal, not a fallback onto the legacy row. **No deployed surface** (a versioned artifact nobody deploys) makes steps 3–4 a decided `n/a` with `envelope.deployTriggerReason`, never an undeclared skip. A burn applies only to the legacy shape where the pipeline fires on the *final* tag: there the version is burned by **either door — a failed build (never deployed) or a failed verification (deployed, rolled back)** — no Release is published, the retry takes the **next patch**, and the stranded entry gets a `> **Withdrawn**` note linking the incident. | Minting an RC after testing only controller-submitted substitutions, where the real `TAG_NAME` trigger has no deployed version; treating a dev source-SHA image as an already-published RC candidate; minting a fresh rc on every resume, re-firing production builds; treating an existing `-rc.1` as a collision blocking `v1.5.0`; a trigger pattern that also matches the final tag, firing a pointless second prod build at step 5; or, on the legacy shape, deleting the tag and "re-releasing" `v1.5.0` from a different SHA so two artifacts claim one version. | Before `rc`, did the ready probe record exactly tag-trigger / `TAG_NAME` / `TAG_NAME`, with final SemVer and RC candidate tag demonstrably derived from the RC `TAG_NAME`; did `version.mjs rc` consume that artifact and refuse when missing or mismatched? Was a dev source-SHA image treated only as informational until an RC tag/build/revision/tag binding existed? Was the trigger shape **declared** at pre-flight (one of the four), and did an **undeclared** one refuse rather than default onto the legacy path? Where `no-deploy` was declared, were steps 3–4 recorded `n/a` **with the reason**, rather than skipped? Did a resumed step 3 reuse the existing rc rather than mint a new one? Did an `-rc.*` tag ever block the final tag? On the legacy shape, did the retry take the next patch, with the tag left alone and the withdrawn entry annotated rather than removed? |
30
+ | 8d | **Ancestry, tag push, and 0% traffic reads survive the identity-v0.4.1 failure class.** `version.mjs rc|tag` fetches the production branch and checks ancestry against `origin/<branch>` (a stale local `main` must not refuse a SHA already on the remote; a **failed fetch refuses** rather than trusting a pre-existing remote-tracking tip); `rc|tag --push` pushes `refs/tags/*` with `--no-verify` so a Mergify stacks pre-push hook cannot block an immutable tag; and candidate identity plus hygiene consume one Cloud Run parser that reconstructs every omitted `percent` as 0 only when the full observation has valid explicit targets summing to 100%. A foreign normalized-zero tag is cleanable only with exact matching terminal evidence and a valid empty product lease report; traffic, ambiguity, or a held/unknown matching lease still refuses. | identity-v0.4.1 production cut: refused a ship SHA because local main was stale; tag push blocked by `mergify stack push`; locate refused a real 0% candidate with blank percent. | Did `rc`/`tag` mint against a freshly-fetched remote tip rather than a local branch — and refuse when that fetch failed even though `origin/<branch>` already resolved? Did `--push` succeed without a manual `--no-verify` rediscovery? Did identity and hygiene agree on the same full capture, reconstruct omitted zeros only from a complete 100% conservation proof, clean a terminal stale tag only with no held product lease, and still refuse traffic, ambiguity, or held/unknown lease evidence? |
31
+ | 8e | **The display-version assertion is a standalone mandatory publication gate.** `envelope.displayVersionPolicy` is declared at pre-flight: `rc-tag` defaults to `final-semver` (show `vX.Y.Z`, not `vX.Y.Z-rc.N`), because the rc tag is a deploy trigger, not the public version. After the shift, step 4b runs `version-assert.mjs serving` as its **own process** over `/health`, `/settings`, and frontend asset-version JSON; all three match the final SemVer under `final-semver`, or an explicit `--record-mismatch <reason>` exception is recorded in the evidence bundle. Its JSON result and exit code are recorded as `release.versionAssert`. Exit 1 (refuse) or exit 2 (cannot run) **stops** the release: the final-tag mint and GitHub Release publication paths refuse unless this gate accepted with exit 0. No `&&`, `||`, `;`, `|`, smoke command, or informational command follows `version-assert.mjs` in a shell chain; a JSON refusal is never human-interpreted into permission to publish. | `/health`, `/settings`, or frontend asset still showing `v0.5.0-rc.1` after the final claim is `v0.5.0`; the v1.6.0 defect where `version-assert.mjs` returned `decision:"refuse"` / `e:no-display-version` yet a shell separator continued into final-tag and GitHub Release publication; an accidental mismatch silently accepted because nobody checked. | Was `displayVersionPolicy` declared at pre-flight (with `rc-tag` defaulting to `final-semver`)? Did the standalone `version-assert.mjs serving` process receive post-shift `/health`, `/settings`, and frontend asset evidence with policy, final version, trigger-tag and SHA, and record its exit code plus JSON result? Do all three match the final SemVer, absent an explicit recorded exception? On exit 1 or 2, did the release stop before final-tag mint and GitHub Release creation, with no shell-chain continuation after the assertion? |
32
+ | 8f | **Version and CHANGELOG outputs are composable and explicit.** `rc` and `tag` default to the same one-tag stdout contract; `--json` emits `{tag, sha, pushed, resumed}` on first execution and every resume-safe no-op. `changelog --dry-run` requires `--format markdown|json`; either payload is stdout-only and diagnostics are stderr-only. Documentation consumes only those declared modes. | A successful remote tag push followed by a local JSON parse failure makes the release look failed after the irreversible mutation already succeeded; a dry-run plan mixed with diagnostics cannot be parsed or reviewed reliably; resumes silently change schema. | Do command substitution tests prove both pushed RC and final tags return only the tag? Do first-run and local/remote resume tests compare the exact four-key JSON shape and status values? Can a caller parse the JSON dry-run plan, and does Markdown mode emit the same planned file without writing it? Do examples opt into the mode they consume? |
33
+ | 9 | **Originating issues close on verified-done only, never on merge — and a rollback leaves them open.** On a Verified verdict (step 5), the issues confirmed in the pre-flight scope are closed, each with a comment linking the shipped SHA + verification evidence; closing is pre-authorized via the envelope. A merge alone never closes an issue (GitHub's `Closes #<n>` auto-close also doesn't fire for GitFlow `dev`-based PRs, so this explicit step is what closes them). Neither failure tier closes anything — a deleted candidate never shipped, and a rolled-back release was un-shipped. | An issue closed at merge time (or by a stray auto-close) while the release is unverified or later rolled back, marking un-shipped work "done"; or issues left open forever because nobody closed the GitFlow-merged PRs. | Were issues closed only after the verified-done verdict, with a SHA/evidence comment? Did a rollback leave them open and cross-linked to the incident? Was the close-set drawn from the confirmed envelope? |
34
+ | 10 | **Every terminal exit emits a durable, immutable release-evidence bundle keyed by `(attempt id, terminal state)`** (principle 5; #441) — the manifest is working state, the bundle is the record. The key is the **attempt id**, `<version>-attempt.<n>` on every shape, minted by `evidence-bundle.mjs next-attempt-id` off the bundles already written: unique per *attempt*, so a retry at the same version and terminal state gets its own record instead of overwriting the attempt it retries. That mint is collision-free against **closed** attempts only — in-flight ones are held off by the lease precondition (mint on `free`), concurrent mints are #509 — and it **fails closed on a dir it cannot read** (exit 2, empty stdout; only ENOENT means "nothing emitted yet"), as does `path`. Three of the four declarable shapes mint no rc, so `rcTag` is required where an rc exists and **declared absent** by the shape otherwise; a declared no-rc shape carrying one refuses. At each of the five terminal exits (`released`, `failed-pre-promotion`, `rolled-back`, `revoked`, `blocked-manual` — #432's `ATTEMPT_TERMINAL_STATES`, reused rather than re-invented) `evidence-bundle.mjs emit` writes one bundle: attempt id, rc tag where one exists, SHA, build identity, the candidate receipt/verdicts where the attempt reached a candidate, the approval/traffic transition where it exposed, the cleanup-or-rollback record, the terminal state. An attempt ending **before candidate creation** records `failurePhase: "pre-candidate"` and carries no candidate or exposure observation; `close-attempt.mjs` validates and emits it before the terminal manifest close, so `version-postmortem.mjs` and `rc-circuit-breaker.mjs` cannot miss it. It is **validated and fail-closed** (incomplete, or claiming an observation its state never took, refuses) and **immutable per `(id, state)`** — different content refuses, identical is a resume-safe no-op, and two *different* states for one attempt are two legal bundles. The required set is a property of **`(terminal state × trigger shape)`** (#604): the three deployed shapes are unchanged, **`no-deploy`** owes `releaseTag`/`releaseRef`/`verification` bound to the attempt's version and **none** of the five deployment artifacts at any state, and an **unknown or undeclared** shape takes the deployed set. A field written `null` is **absent** — `"rcTag": null` is no rc tag — and never satisfies a requirement. The verified path attaches the bundle to the **GitHub Release**; failed attempts stay reachable via `evidence-bundle.mjs path`, and the DoD reports the location on every exit. → [why](why.md#why-the-bundle-key-is-the-attempt-not-the-version) | Verification evidence dying with the session — a failed attempt leaving only a deleted revision, a normal issue, and nothing linking them; its degradations are workflow logs alone, a mutable summary, and final-tag-only evidence, which loses every failed attempt. Then the **collision class**, which survived two review rounds because each route was fixed alone: a version-keyed retry overwriting the attempt it retries; a `validate` accepting a key `emit` cannot write; a mint failing **open** on an unreadable dir and handing back a spent ordinal; a `path` reporting "no bundles" off a dir it never read. And the required **content** left keyed to a deployment after the **key** was freed of one — a no-rc, no-deploy shape owing five artifacts only a deployment produces, so every terminal exit is unsatisfiable and the release ships with no bundle at all — plus the opposite widening, a per-shape table letting a **deployed** shape omit what it owes. | Did **each** terminal exit emit a bundle, failure paths included? Is `terminalState` keyed off #432's `ATTEMPT_TERMINAL_STATES` rather than a second list? Immutable per `(attempt id, state)` — different content **refuses**, identical no-ops, two *different* states stay two legal bundles? On a no-rc shape, did the exit still emit, with the absent `rcTag` **declared** (and a declared no-rc shape carrying one refused)? Is the id unique per **attempt**, so a same-version same-state retry gets its own bundle — and do `validate` and `emit` agree on which ids can key a file? Take the collision as a **class**: does the mint refuse an unreadable dir (`chmod 000` with a bundle inside; `--dir` a plain file) with exit 2 and empty stdout, while a not-yet-created dir still answers ordinal 1 — and does `path` refuse that same dir rather than listing nothing at exit 0? Does the doctrine state the mint's scope **honestly** (collision-free against *closed* attempts; in-flight held by the lease; concurrent deferred to #509) rather than claiming a blanket guarantee the code cannot deliver? Does a failed attempt carry its state's full required set and reject fabricated exposure evidence? Is that set keyed to **`(state × shape)`** — can a `no-deploy` release at `released` emit a valid bundle, does a `no-deploy` bundle carrying a `trafficTransition` refuse, does every **deployed** shape still refuse a bundle missing any of the five, and does an **unknown** shape take the deployed set? Does `"rcTag": null` behave exactly as an absent one? Does the verified path attach to the GitHub Release, and the DoD report the location on every exit? |
35
+ | 11 | **The version-level post-mortem aggregates every durable bundle a version left behind, and only those bundles** (principle 5; #961). `version-postmortem.mjs` discovers all `<version>-attempt.<n>--<state>.json` bundles in the durable evidence dir (legacy `v<version>-rc.<n>` keys included), orders attempts by the immutable attempt id **numerically** (attempt.10 follows attempt.9), and reports: attempt/rc counts, terminal-state counts, maximum exposure per attempt, traffic mutations (each shift stage plus each rollback restore), rollback count, and reused-vs-burned. Failure ownership is normalized to exactly `application` / `configuration` / `platform` / `release-tooling` / `gate-environment` — the documented, ordered derivation in `references/postmortem.md`, with a bundle-declared `failureOwner` winning; **that enum and order are a stable contract #962 consumes**. Candidates healthy/ready when rolled back are reported **separately** from the rest. One owner repeating across ≥2 failed attempts fires `repeated-failure-owner` with the bundle files as evidence. The run-level single-manifest findings are unchanged; the version-level summary rides the same `report.mjs` terminal save and persists as `kind: "release-version"` in the shared series. It reads durable bundles only — never a live service, never host state in tests — tolerates schema drift (absent signals reported absent, never invented), faults an unparseable bundle (`unreadable-version-bundle`), and refuses an unreadable dir rather than reporting an empty history. It never treats a rollback as a process failure, and it never adjudicates. | The failure #961 closed: nine Forex `0.1.12` attempts each leaving a clean-looking single-manifest post-mortem while the version's actual story — eight non-released attempts, release tooling as the repeated owner, two healthy-at-100% rollbacks — existed only as nine files nobody aggregated. Its degradations: a lexicographic attempt ordering silently rewriting history; a classification renamed or reordered after #962 started consuming it; healthy-at-rollback candidates conflated with unhealthy ones so "the tooling cried wolf" reads as "the release failed"; a missing `durableEvidenceDir` or empty discovery rendered as a fabricated zero-attempt measurement; the aggregate pass weakening the run-level findings or the bundle schema to make its own job easier. | Does the aggregate discover ALL bundles for the version — including a legacy rc-keyed one and an attempt.10 — and order them numerically? Are the counts derivable from the bundles shown (exposure, mutations, rollbacks, reused/burned)? Is every failed attempt classified into exactly one of the five documented owners, with its basis stated, and does a declared `failureOwner` win? Are healthy-at-rollback candidates listed separately? Does a repeated owner fire with bundle evidence? Are the run-level findings byte-for-byte intact on a manifest with no `durableEvidenceDir`? Does an unparseable bundle surface as a finding and an unreadable dir refuse? Do the fixtures model Forex `0.1.12` (nine attempts, eight non-released, six release-tooling, two healthy-at-100% rollbacks) without touching live host state? |
36
+ | 12 | **Repeated failures owned by release tooling or the gate environment hold the version before the next immutable RC — the rc circuit breaker** (principle 15; #962). Before `version.mjs rc` mints (including a `--retry-from` retry), `rc-circuit-breaker.mjs check` counts prior **non-released** attempts of the same proposed version whose normalized failure owner is `release-tooling` or `gate-environment` — #961's taxonomy consumed, never re-derived — and at the configured threshold (default **2**, `--threshold` / `$RC_CIRCUIT_BREAKER_THRESHOLD`, source recorded) returns fail-closed `hold-for-release-design-review` with the count, per-owner breakdown, qualifying attempt ids, phases, and bundle files as evidence. Application/configuration/platform failures are reported but never consume the threshold. The decision JSON is recorded at `release.circuitBreaker` on **every** outcome, proceed included, and the run-level post-mortem reports whether the breaker fired and how it resolved. The check is **read-only**: a hold deletes nothing, moves no immutable rc tag, burns no version. Continuing is a structured human authorization (`version` + `againstCount` + `authorizedBy` + `authorizedAt` + `rationale` — why another RC beats simplifying/removing/externally repairing the gate), bound to the current count so one authorization covers exactly ONE continuation; a further qualifying failure re-holds, and no env var or flag skips the check. Exit 2 (unreadable dir, unparseable bundle, invalid threshold) is a refusal, never a proceed. Durable failure bundles carry the normalized owner and phase stamped at close time (`evidence-bundle.mjs emit`; a declared owner wins). | The failure #962 closed: Forex v0.1.12 minting nine immutable RCs — two healthy candidates rolled back at 100% by verifier misreads — with nothing stopping the tooling-only retry loop, because every gate failed closed per attempt and no gate watched the loop. Its degradations: a breaker that counted application defects and held healthy lanes for the wrong reason; a silent env/flag bypass re-creating the loop with a quieter record; one blanket authorization carrying every later retry; a "hold" that moved tags or burned the version, turning the safe default into a destructive one; a gate that read an unreadable or unparseable durable history as an empty one and proceeded; and durable attempts whose owner/phase existed only as re-derivation, so the record itself never said who failed. | Was the breaker run BEFORE the rc mint, on the retry path too, and its decision recorded at `release.circuitBreaker` either way? Is the count scoped to the proposed version, non-released attempts, and the two lane-side owners only — an application defect never tripping it? On a hold: did the lane stop, and did any continuation carry a structured authorization whose `againstCount` matched the current count — with a further tooling failure re-holding rather than inheriting it? Did the hold leave every prior rc tag untouched and the version unburned? Does the post-mortem say whether the breaker fired and how it resolved? Do emitted failure bundles carry `failureOwner`/`failurePhase`, with a declared owner winning? Does the regression fixture model Forex RC.2 + RC.3 as the threshold-crossing sequence (proceed after one tooling failure, hold after two) from durable-bundle fixtures, with no live host state? |
@@ -0,0 +1,12 @@
1
+ # What this lane hands to, and what it consumes
2
+
3
+ [SKILL.md](../SKILL.md) routes the phases; this file is the seam with every other skill — who
4
+ produces this lane's input, who owns the gates it runs, and where a failure goes.
5
+
6
+ - `resolve-issues` — the **upstream** producer; its handoff is this skill's input. For an epic that is one integration-verified deliverable per [component](../../resolve-issues/references/integration-gate.md), each a combined PR whose tip step 1 re-verifies (principle 2) rather than re-merges.
7
+ - `review-pr` — twice: **step 1** on the combined integration diff, **step 2** in promotion mode (fast-forward + release-readiness checklist).
8
+ - `smoke` — the **exposure gate**: step 4a against the 0%-traffic candidate under read-only **`prod-preview`**, step 4b against the live service under **`prod-active`** (principle 4). The named [Validation Profile](../../smoke/references/manifest.md#validation-profiles-the-profiles-registry) owns the mutation policy, so both production profiles are mechanically read-only. Also the engine behind the SHA-keyed verdict recorder the candidate pin reads (principle 11).
9
+ - `e2e-test` — the release-lane doctrine `smoke` consumes.
10
+ - **Platform skills** (`cloud-build` / `cloud-deploy`) — how this repo deploys, and the owner of the principle-12 pipeline contract. This skill sequences and gates; they execute.
11
+ - `app-debug` / `diagnose` — where a failed validation goes, after the candidate is deleted (pre-exposure) or production restored (post-exposure).
12
+