@1aboveio/skills 0.19.3 → 0.20.2

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 (206) hide show
  1. package/README.md +9 -4
  2. package/package.json +1 -1
  3. package/runtime/skills/distribution/generated/recipes.json +54 -48
  4. package/runtime/skills/distribution/scripts/bundles.mjs +113 -7
  5. package/runtime/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -6
  6. package/runtime/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +3 -36
  7. package/skills/cicd-pipeline/cloud-build/SKILL.md +6 -6
  8. package/skills/cicd-pipeline/mergify/SKILL.md +1 -1
  9. package/skills/engineering/engineering-runtime/coherence/workflow.json +15 -84
  10. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -6
  11. package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +3 -36
  12. package/skills/engineering/rush-release/SKILL.md +46 -25
  13. package/skills/engineering/rush-release/evals/evals.json +22 -8
  14. package/skills/engineering/rush-release/references/cut.md +21 -4
  15. package/skills/engineering/rush-release/references/preflight.md +25 -6
  16. package/skills/engineering/rush-release/references/promotion.md +70 -0
  17. package/skills/engineering/rush-release/references/publish.md +49 -16
  18. package/skills/engineering/rush-release/scripts/plan.mjs +33 -7
  19. package/skills/engineering/smoke/SKILL.md +4 -4
  20. package/skills/engineering/smoke/references/manifest.md +3 -3
  21. package/skills/payment/payment-analysis/SKILL.md +5 -3
  22. package/skills/payment/tld-detection/SKILL.md +163 -0
  23. package/skills/payment/tld-detection/agents/openai.yaml +9 -0
  24. package/skills/payment/tld-detection/evals/evals.json +107 -0
  25. package/skills/payment/tld-detection/references/clean.md +51 -0
  26. package/skills/payment/tld-detection/references/clustering.md +219 -0
  27. package/skills/payment/tld-detection/references/decision.md +85 -0
  28. package/skills/payment/tld-detection/references/report-template.md +169 -0
  29. package/skills/payment/tld-detection/references/terminology.md +69 -0
  30. package/skills/payment/tld-detection/references/workflow.md +78 -0
  31. package/skills/payment/tld-detection/scripts/detect_ladders.py +390 -0
  32. package/skills/payment/tld-detection/tests/test_detect_ladders.py +132 -0
  33. package/skills/engineering/resolve-issues/SKILL.md +0 -168
  34. package/skills/engineering/resolve-issues/agents/openai.yaml +0 -9
  35. package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +0 -499
  36. package/skills/engineering/resolve-issues/references/breaker.md +0 -82
  37. package/skills/engineering/resolve-issues/references/deliverables.md +0 -27
  38. package/skills/engineering/resolve-issues/references/delivery.md +0 -108
  39. package/skills/engineering/resolve-issues/references/evidence-lane.md +0 -21
  40. package/skills/engineering/resolve-issues/references/exact-head-ci.md +0 -287
  41. package/skills/engineering/resolve-issues/references/fan-out.md +0 -33
  42. package/skills/engineering/resolve-issues/references/finalization.md +0 -68
  43. package/skills/engineering/resolve-issues/references/guarantees.md +0 -10
  44. package/skills/engineering/resolve-issues/references/high-risk.md +0 -29
  45. package/skills/engineering/resolve-issues/references/incidents/848/README.md +0 -156
  46. package/skills/engineering/resolve-issues/references/intake.md +0 -86
  47. package/skills/engineering/resolve-issues/references/integration-gate.md +0 -53
  48. package/skills/engineering/resolve-issues/references/interference.md +0 -87
  49. package/skills/engineering/resolve-issues/references/loop.md +0 -134
  50. package/skills/engineering/resolve-issues/references/model-catalog.md +0 -9
  51. package/skills/engineering/resolve-issues/references/postmortem.md +0 -27
  52. package/skills/engineering/resolve-issues/references/pre-flight-model-slots.md +0 -41
  53. package/skills/engineering/resolve-issues/references/pre-flight-recording-and-checkout.md +0 -48
  54. package/skills/engineering/resolve-issues/references/pre-flight.md +0 -41
  55. package/skills/engineering/resolve-issues/references/regression-checklist.md +0 -26
  56. package/skills/engineering/resolve-issues/references/run-state.md +0 -288
  57. package/skills/engineering/resolve-issues/references/sandboxed-testing.md +0 -48
  58. package/skills/engineering/resolve-issues/references/spawn-contract.md +0 -96
  59. package/skills/engineering/resolve-issues/references/terminal-evidence-journal.md +0 -40
  60. package/skills/engineering/resolve-issues/references/why.md +0 -653
  61. package/skills/engineering/resolve-issues/schemas/fix-round.schema.json +0 -49
  62. package/skills/engineering/resolve-issues/scripts/combine-and-verify.mjs +0 -721
  63. package/skills/engineering/resolve-issues/scripts/component-candidate.mjs +0 -962
  64. package/skills/engineering/resolve-issues/scripts/contract-revision.mjs +0 -220
  65. package/skills/engineering/resolve-issues/scripts/detect-delivery-mode.mjs +0 -420
  66. package/skills/engineering/resolve-issues/scripts/detect-target-branch.mjs +0 -256
  67. package/skills/engineering/resolve-issues/scripts/detect-workspace-mode.mjs +0 -168
  68. package/skills/engineering/resolve-issues/scripts/discover-models.mjs +0 -9
  69. package/skills/engineering/resolve-issues/scripts/doctrine.mjs +0 -62
  70. package/skills/engineering/resolve-issues/scripts/evidence-lifecycle-contract.mjs +0 -191
  71. package/skills/engineering/resolve-issues/scripts/exact-head-ci.mjs +0 -413
  72. package/skills/engineering/resolve-issues/scripts/exact-head-github-provider.mjs +0 -332
  73. package/skills/engineering/resolve-issues/scripts/finalize.mjs +0 -488
  74. package/skills/engineering/resolve-issues/scripts/fix-rounds.mjs +0 -3307
  75. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-circular-1001.json +0 -16
  76. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-valid-sequencing.json +0 -51
  77. package/skills/engineering/resolve-issues/scripts/fixtures/fmm-express-830-component-candidate.json +0 -17
  78. package/skills/engineering/resolve-issues/scripts/fixtures/head-check-set-1081.json +0 -166
  79. package/skills/engineering/resolve-issues/scripts/gate-value-series.mjs +0 -92
  80. package/skills/engineering/resolve-issues/scripts/guide-index.mjs +0 -73
  81. package/skills/engineering/resolve-issues/scripts/head-check-set.mjs +0 -159
  82. package/skills/engineering/resolve-issues/scripts/interference.mjs +0 -427
  83. package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +0 -9
  84. package/skills/engineering/resolve-issues/scripts/next-operations.mjs +0 -419
  85. package/skills/engineering/resolve-issues/scripts/postmortem.mjs +0 -909
  86. package/skills/engineering/resolve-issues/scripts/preflight-questions.mjs +0 -345
  87. package/skills/engineering/resolve-issues/scripts/reconcile-contained-unit-prs.mjs +0 -415
  88. package/skills/engineering/resolve-issues/scripts/release-state-contract.mjs +0 -697
  89. package/skills/engineering/resolve-issues/scripts/report.mjs +0 -494
  90. package/skills/engineering/resolve-issues/scripts/required-check-plan.mjs +0 -172
  91. package/skills/engineering/resolve-issues/scripts/round-metadata.mjs +0 -79
  92. package/skills/engineering/resolve-issues/scripts/run-state-review6-cases.mjs +0 -334
  93. package/skills/engineering/resolve-issues/scripts/run-state.mjs +0 -4792
  94. package/skills/engineering/resolve-issues/scripts/sandbox-selftest.mjs +0 -395
  95. package/skills/engineering/resolve-issues/scripts/spawn-contract.mjs +0 -290
  96. package/skills/engineering/resolve-issues/scripts/terminal-dispositions.mjs +0 -170
  97. package/skills/engineering/resolve-issues/scripts/terminal-evidence-journal.mjs +0 -293
  98. package/skills/engineering/resolve-issues/scripts/unit-kind.mjs +0 -197
  99. package/skills/engineering/resolve-issues/scripts/unit-lifecycle.mjs +0 -127
  100. package/skills/engineering/resolve-issues/scripts/watch-delivery.mjs +0 -893
  101. package/skills/engineering/resolve-issues/scripts/workspaces.mjs +0 -829
  102. package/skills/engineering/resolve-issues/workflows/independent-review.workflow.js +0 -290
  103. package/skills/engineering/resolve-issues/workflows/prior-art-scan.workflow.js +0 -80
  104. package/skills/engineering/resolve-issues/workflows/workflow-smoke.mjs +0 -102
  105. package/skills/engineering/resolve-release/LICENSE +0 -3
  106. package/skills/engineering/resolve-release/SKILL.md +0 -113
  107. package/skills/engineering/resolve-release/agents/openai.yaml +0 -9
  108. package/skills/engineering/resolve-release/references/assembly.md +0 -137
  109. package/skills/engineering/resolve-release/references/auto-when-green.md +0 -56
  110. package/skills/engineering/resolve-release/references/candidate.md +0 -167
  111. package/skills/engineering/resolve-release/references/exposure.md +0 -178
  112. package/skills/engineering/resolve-release/references/handoff.md +0 -24
  113. package/skills/engineering/resolve-release/references/postmortem.md +0 -230
  114. package/skills/engineering/resolve-release/references/preflight.md +0 -208
  115. package/skills/engineering/resolve-release/references/principles.md +0 -94
  116. package/skills/engineering/resolve-release/references/regression-checklist.md +0 -36
  117. package/skills/engineering/resolve-release/references/related-skills.md +0 -13
  118. package/skills/engineering/resolve-release/references/routing.md +0 -149
  119. package/skills/engineering/resolve-release/references/verified-sha-github-flow.md +0 -285
  120. package/skills/engineering/resolve-release/references/versioning.md +0 -202
  121. package/skills/engineering/resolve-release/references/why.md +0 -53
  122. package/skills/engineering/resolve-release/scripts/adapter-completion-artifact.mjs +0 -389
  123. package/skills/engineering/resolve-release/scripts/build-changes.mjs +0 -209
  124. package/skills/engineering/resolve-release/scripts/candidate-hygiene.mjs +0 -407
  125. package/skills/engineering/resolve-release/scripts/candidate-identity.mjs +0 -904
  126. package/skills/engineering/resolve-release/scripts/candidate-traffic.mjs +0 -81
  127. package/skills/engineering/resolve-release/scripts/checked-adapter-loader.mjs +0 -612
  128. package/skills/engineering/resolve-release/scripts/close-attempt.mjs +0 -135
  129. package/skills/engineering/resolve-release/scripts/closeout-release.mjs +0 -161
  130. package/skills/engineering/resolve-release/scripts/doctrine.mjs +0 -106
  131. package/skills/engineering/resolve-release/scripts/durable-processing.mjs +0 -522
  132. package/skills/engineering/resolve-release/scripts/ensure-target-green.mjs +0 -659
  133. package/skills/engineering/resolve-release/scripts/evidence-bundle.mjs +0 -1014
  134. package/skills/engineering/resolve-release/scripts/finalize-release.mjs +0 -526
  135. package/skills/engineering/resolve-release/scripts/fixtures/durable-processing-adapter.mjs +0 -169
  136. package/skills/engineering/resolve-release/scripts/green-gate.mjs +0 -599
  137. package/skills/engineering/resolve-release/scripts/isolated-adapter-evaluator.mjs +0 -752
  138. package/skills/engineering/resolve-release/scripts/metadata-pr-status.mjs +0 -56
  139. package/skills/engineering/resolve-release/scripts/metadata-sync.mjs +0 -1538
  140. package/skills/engineering/resolve-release/scripts/postmortem.mjs +0 -381
  141. package/skills/engineering/resolve-release/scripts/preflight-probes.mjs +0 -510
  142. package/skills/engineering/resolve-release/scripts/production-endpoints.mjs +0 -326
  143. package/skills/engineering/resolve-release/scripts/rc-circuit-breaker.mjs +0 -272
  144. package/skills/engineering/resolve-release/scripts/report.mjs +0 -417
  145. package/skills/engineering/resolve-release/scripts/reprobe-credentials.mjs +0 -114
  146. package/skills/engineering/resolve-release/scripts/revalidate-candidate.mjs +0 -238
  147. package/skills/engineering/resolve-release/scripts/review-packet.mjs +0 -503
  148. package/skills/engineering/resolve-release/scripts/rollback-floor.mjs +0 -263
  149. package/skills/engineering/resolve-release/scripts/version-assert.mjs +0 -339
  150. package/skills/engineering/resolve-release/scripts/version-postmortem.mjs +0 -485
  151. package/skills/engineering/resolve-release/scripts/version.mjs +0 -1199
  152. package/skills/engineering/resolve-release/scripts/watch-candidate-delivery.mjs +0 -449
  153. package/skills/engineering/resolve-release/vendor/ACORN-LICENSE +0 -21
  154. package/skills/engineering/resolve-release/vendor/README.md +0 -60
  155. package/skills/engineering/resolve-release/vendor/acorn.mjs +0 -6233
  156. package/skills/engineering/resolve-release/vendor/quickjs/core/LICENSE +0 -21
  157. package/skills/engineering/resolve-release/vendor/quickjs/core/README.md +0 -341
  158. package/skills/engineering/resolve-release/vendor/quickjs/core/README.template.md +0 -70
  159. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs +0 -2
  160. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs.map +0 -1
  161. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs +0 -7
  162. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs.map +0 -1
  163. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.mts +0 -2033
  164. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.ts +0 -2033
  165. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js +0 -7
  166. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js.map +0 -1
  167. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs +0 -2
  168. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs.map +0 -1
  169. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs +0 -2
  170. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs.map +0 -1
  171. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs +0 -2
  172. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs.map +0 -1
  173. package/skills/engineering/resolve-release/vendor/quickjs/core/package.json +0 -49
  174. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/LICENSE +0 -21
  175. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/README.md +0 -5
  176. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.mts +0 -549
  177. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.ts +0 -549
  178. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js +0 -2
  179. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js.map +0 -1
  180. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs +0 -2
  181. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs.map +0 -1
  182. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/package.json +0 -36
  183. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/LICENSE +0 -47
  184. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/README.md +0 -82
  185. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.d.ts +0 -11
  186. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.mjs +0 -22
  187. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cjs +0 -21
  188. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.cjs +0 -21
  189. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.d.ts +0 -11
  190. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.d.ts +0 -11
  191. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.mjs +0 -25
  192. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.wasm +0 -0
  193. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.mts +0 -85
  194. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.ts +0 -85
  195. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js +0 -2
  196. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js.map +0 -1
  197. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs +0 -2
  198. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs.map +0 -1
  199. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.mts +0 -20
  200. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.ts +0 -20
  201. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js +0 -2
  202. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js.map +0 -1
  203. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs +0 -2
  204. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs.map +0 -1
  205. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/package.json +0 -61
  206. /package/skills/{engineering/resolve-issues → payment/tld-detection}/LICENSE +0 -0
@@ -8,10 +8,16 @@ along for the ride.
8
8
  ```bash
9
9
  node <skillsRoot>/rush-release/scripts/plan.mjs --sha <candidateSha> --json \
10
10
  > /tmp/rush-release-plan.json
11
+ # Add --cloud-run when preflight selected cloud-run-staged.
11
12
  ```
12
13
 
13
14
  The plan names `baselineTag`, `currentVersion`, `bump`, `reason`,
14
- `nextVersion`, `tag`, `changelogMarkdown`, and `versionFiles`.
15
+ `nextVersion`, final `tag`, optional `candidateTag`, `changelogMarkdown`, and
16
+ `versionFiles`.
17
+
18
+ Before showing the plan, use `git ls-remote --tags origin` to prove the proposed
19
+ candidate and final tags are both unused remotely. Local tag discovery chooses
20
+ the next RC ordinal; remote absence is the collision authority.
15
21
 
16
22
  SemVer (0.x treats breaking as minor unless the human asks for strict):
17
23
 
@@ -26,13 +32,24 @@ Changelog entries come from conventional-commit subjects in
26
32
  tag). Keep an existing `## [Unreleased]` body — derived entries merge with it,
27
33
  they do not replace it.
28
34
 
29
- ## 2. Confirm
35
+ ## 2. Confirm once
30
36
 
31
- Show the frozen SHA, proposed version/tag, bump reason, changelog, version
32
- files, and publisher. Wait for confirmation. A different bump the human names
37
+ Show the frozen SHA, proposed version/tag(s), bump reason, changelog, version
38
+ files, publisher/deployment mode, and the complete outward-action sequence: create the cut
39
+ branch/commit, push it, deliver its metadata PR through the queue, push the
40
+ immutable tag(s), validate and conditionally promote Cloud Run when applicable,
41
+ create a GitHub Release when required, and watch the publisher.
42
+ Ask exactly one question. Its answer both settles any displayed unresolved
43
+ choice and authorizes that entire sequence. A different bump the human names
33
44
  wins, as long as it is still SemVer.
34
45
 
35
46
  Do not create a branch, edit files, or tag before that confirmation.
47
+ After it, proceed through publication without asking separate cut, merge, RC,
48
+ exposure, tag, finalization, or publish questions. Repository/provider checks
49
+ are gates the orchestrator evaluates, not reasons to ask the human to confirm
50
+ facts. Ask again only by replacing the whole plan when its frozen SHA,
51
+ version/tag, metadata diff, publisher, or outward actions change. A retry or
52
+ resume of the unchanged plan inherits the original authorization.
36
53
 
37
54
  ## 3. Apply and commit
38
55
 
@@ -1,12 +1,15 @@
1
1
  # Preflight
2
2
 
3
- Preflight has three outputs: trunk `main`, the publisher to watch, and the
4
- files that carry the version. It does not pick the SHA, bump the version, or
5
- push.
3
+ Preflight outputs trunk `main`, publishers, deployment mode, release commands,
4
+ and the files that carry the version. It does not pick the SHA, bump the
5
+ version, or push. Resolve these facts from repository and provider state without asking the
6
+ human to confirm each one. If a required choice cannot be inferred, carry it
7
+ into the single complete-plan question in [cut.md](cut.md); do not create a
8
+ separate preflight confirmation.
6
9
 
7
10
  ## 1. Trunk
8
11
 
9
- Fetch `origin`. Confirm the release trunk is `main` from those fetched refs
12
+ Fetch `origin/main` and all remote tags. Verify the release trunk is `main` from those fetched refs
10
13
  (`origin/main` exists). A repo whose production branch is `master` or `release`
11
14
  is not this workflow.
12
15
 
@@ -17,12 +20,28 @@ otherwise look at the tree:
17
20
 
18
21
  | Signal | Publisher |
19
22
  |---|---|
20
- | `cloudbuild.yaml` / `cloudbuild.json`, or a Cloud Build trigger on `v*` / `v*-rc.*` tags | `cloudbuild` |
23
+ | Cloud Build production trigger matching only `v*-rc.*`, whose prod path emits immutable candidate coordinates after `--no-traffic` | `cloudbuild`, mode `cloud-run-staged` |
21
24
  | `package.json` with a publish workflow, `publishConfig`, or an explicit npm release | `npm` |
22
25
  | both | watch both |
23
26
  | neither, and the human did not name one | refuse — nothing to watch |
24
27
 
25
- Record `publisher` as `cloudbuild`, `npm`, or `both`. If a GitHub Actions
28
+ Cloud Build presence alone is not a staged contract. For `cloud-run-staged`,
29
+ verify from repository policy and live trigger configuration:
30
+
31
+ - the production trigger matches RC tags and excludes the final `vX.Y.Z` tag;
32
+ - each affected service deploys an immutable revision with `--no-traffic` and
33
+ publishes one receipt binding RC tag, merged SHA, revision, image digest, and
34
+ candidate URL;
35
+ - `smoke.manifest.json` defines read-only `prod-preview` and `prod-active`
36
+ profiles with at least one eligible path;
37
+ - exact inspect, traffic-shift, rollback, and candidate-delete commands are
38
+ documented, including service dependency order;
39
+ - any durable processing affected by a shift has exact pause, quiescence,
40
+ resume, and restoration checks.
41
+
42
+ Refuse staged release when any applicable item is absent; never improvise
43
+ production commands. Record `publisher` as `cloudbuild`, `npm`, or `both` and
44
+ `deploymentMode` as `artifact-only` or `cloud-run-staged`. If a GitHub Actions
26
45
  workflow publishes on `release: published` rather than on tag push, note that
27
46
  `gh release create` is required at publish time.
28
47
 
@@ -0,0 +1,70 @@
1
+ # Cloud Run staged promotion
2
+
3
+ Promote one immutable candidate from 0% to 100% under the complete-plan
4
+ authorization. Load `smoke` for both validation profiles and the applicable
5
+ platform skill for repository-declared Cloud Run commands. Do not restore the
6
+ archived `resolve-release` state machine or introduce another confirmation.
7
+
8
+ ## 1. Bind the candidate
9
+
10
+ After the RC-tag build reports `SUCCESS`, read its durable candidate receipt.
11
+ For every affected service, independently verify from one fresh Cloud Run
12
+ snapshot that:
13
+
14
+ - receipt RC tag and source SHA equal the approved plan and `<mergedSha>`;
15
+ - the named immutable revision runs the receipt's image digest;
16
+ - the candidate URL resolves to that revision;
17
+ - the candidate has exactly 0% traffic and the recorded incumbent exactly 100%;
18
+ - no second release or unknown traffic-bearing revision is in flight.
19
+
20
+ Missing, duplicate, stale, or contradictory identity is a pre-exposure failure.
21
+ Delete the candidate by immutable revision only when the preflight contract
22
+ declared that deletion safe; otherwise leave it at 0% and hand back. Never move
23
+ the RC tag.
24
+
25
+ ## 2. Validate at 0%
26
+
27
+ Run the repository's smoke workflow against the exact candidate URL with
28
+ `--profile prod-preview`. Accept only a non-empty `ALIVE` verdict whose stamped
29
+ target is that URL, profile is `prod-preview`, and `noMutations` is `true`.
30
+ Record its `generatedAt`. A failed or unavailable gate leaves production on the
31
+ incumbent and does not consume the final tag.
32
+
33
+ ## 3. Recheck and shift
34
+
35
+ Immediately before exposure, re-read candidate identity and traffic. Require
36
+ the candidate still at 0%, incumbent still at 100%, and the ALIVE verdict still
37
+ within the plan's declared freshness window. If durable processing is affected,
38
+ run the preflight-declared pause order and prove quiescence before traffic moves.
39
+
40
+ Shift all affected services to the candidate in the declared dependency order.
41
+ After each command, read traffic back. If a later service cannot shift, restore
42
+ already-shifted services in reverse order before doing anything else. The plan's
43
+ single confirmation already authorized this conditional shift and restoration.
44
+
45
+ ## 4. Verify live
46
+
47
+ From fresh control-plane and service observations, require for every service:
48
+
49
+ - the exact candidate revision holds 100% and the incumbent 0%;
50
+ - serving source SHA and image digest equal the candidate receipt;
51
+ - the live URL passes a non-empty, read-only `prod-active` smoke as `ALIVE`.
52
+
53
+ Resume durable processing only against the verified serving candidate, then
54
+ prove scheduling/admission and any required publication advancement are healthy.
55
+ Only this state permits the final release tag.
56
+
57
+ ## 5. Failure routing
58
+
59
+ - **Before any traffic moved:** production is untouched. Delete or retain the
60
+ 0% candidate according to the recorded policy and hand back.
61
+ - **After a known failed shift or live check:** automatically restore every
62
+ service to its recorded incumbent in reverse dependency order, restore
63
+ durable processing against that incumbent, and verify incumbent 100%,
64
+ candidate 0%, and `prod-active` ALIVE before hand-back.
65
+ - **Ambiguous traffic, identity, or processing state:** stop all writes and hand
66
+ back the last known-good evidence for operator recovery. Never guess, retry a
67
+ shift, delete a revision, or claim rollback succeeded.
68
+
69
+ The final tag remains unused on every failure path. An RC tag is immutable and
70
+ remains as attempt evidence.
@@ -1,8 +1,16 @@
1
1
  # Publish
2
2
 
3
- Push the cut, tag it, and watch the publisher. The tag is the deploy trigger
4
- in this lane, so a pushed tag that fails to ship **burns the version** — the
5
- next attempt takes the next patch. Do not move or delete the tag to "reuse" it.
3
+ Push the cut, tag it, and watch the publisher. A pushed final tag that fails to
4
+ publish **burns the version**. A Cloud Run RC tag that fails burns that RC
5
+ ordinal, not the final version. Do not move or delete either tag to "reuse" it.
6
+
7
+ The complete-plan confirmation in [cut.md](cut.md) already authorizes every
8
+ action in this procedure and [promotion.md](promotion.md). Do not insert another
9
+ confirmation before the branch push, PR queue, candidate validation, traffic
10
+ shift, rollback, tag push, GitHub Release, or publisher watch. A provider or
11
+ repository gate either passes and the sequence continues, or fails and the run
12
+ hands back. If resolving a failure would change the approved identity or action
13
+ set, return to planning and ask once on the replacement plan.
6
14
 
7
15
  ## 1. Push and merge the metadata PR
8
16
 
@@ -27,7 +35,7 @@ documented release path, a successful fast-forward of `<cutSha>` to `main`
27
35
  may use `<cutSha>` as `<mergedSha>`. Never infer this from local state: re-fetch
28
36
  and verify the remote ref.
29
37
 
30
- ## 2. Verify and tag the merged main SHA
38
+ ## 2. Verify the merged main SHA
31
39
 
32
40
  The release tag must name a commit reachable from the remote default branch.
33
41
  This is the ancestry gate used by release publishers to refuse off-main
@@ -40,8 +48,6 @@ git merge-base --is-ancestor "<mergedSha>" origin/main
40
48
  test "$(git rev-parse "<cutSha>^{tree}")" = \
41
49
  "$(git rev-parse "<mergedSha>^{tree}")"
42
50
  test "$(git show "<mergedSha>:package.json" | jq -r .version)" = "<nextVersion>"
43
- git tag -a "<tag>" "<mergedSha>" -m "Release <tag>"
44
- git push --no-verify origin "refs/tags/<tag>"
45
51
  ```
46
52
 
47
53
  The tree equality check proves the provider merge introduced no post-freeze
@@ -52,6 +58,30 @@ when it is not `package.json`. Re-read the exact required queue/CI result for
52
58
  `<mergedSha>` before tagging. A green cut head is not evidence for a different
53
59
  merge commit.
54
60
 
61
+ ## 3. Publish by mode
62
+
63
+ **Artifact-only:** create and push the final annotated tag on `<mergedSha>`,
64
+ then continue to the publisher watch:
65
+
66
+ ```bash
67
+ git tag -a "<tag>" "<mergedSha>" -m "Release <tag>"
68
+ git push --no-verify origin "refs/tags/<tag>"
69
+ ```
70
+
71
+ **Cloud Run staged:** create and push `<candidateTag>` on `<mergedSha>`. Watch
72
+ Cloud Build until it emits the exact candidate receipt, then execute
73
+ [promotion.md](promotion.md). Only after live verification succeeds, create and
74
+ push the final tag on the same `<mergedSha>`. The final tag must not trigger a
75
+ second Cloud Run build.
76
+
77
+ ```bash
78
+ git tag -a "<candidateTag>" "<mergedSha>" -m "Candidate <candidateTag>"
79
+ git push --no-verify origin "refs/tags/<candidateTag>"
80
+ # After promotion.md succeeds:
81
+ git tag -a "<tag>" "<mergedSha>" -m "Release <tag>"
82
+ git push --no-verify origin "refs/tags/<tag>"
83
+ ```
84
+
55
85
  `--no-verify` is for tag refs, because a Mergify (or similar) pre-push hook
56
86
  that rewrites `git push` into a stack push is correct for branches and wrong
57
87
  for immutable tags.
@@ -64,19 +94,21 @@ If preflight recorded a workflow that publishes only on
64
94
  remote (`gh release create <tag> --notes-file …`). Otherwise the tag push is
65
95
  the trigger.
66
96
 
67
- ## 3. Watch
97
+ ## 4. Watch
68
98
 
69
99
  Launch one background watcher; do not poll in the foreground.
70
100
 
71
- **Cloud Build** (`publisher` `cloudbuild` or `both`):
101
+ **Cloud Build** (`publisher` `cloudbuild` or `both`) is watched first for the RC
102
+ tag and exact merged SHA. `SUCCESS` means only that candidate production
103
+ succeeded; Cloud Run success requires [promotion.md](promotion.md):
72
104
 
73
105
  ```bash
74
106
  gcloud builds list --limit=1 --format='value(id,status,substitutions.TAG_NAME)' \
75
- --filter="substitutions.TAG_NAME=<tag> OR tags=<tag>"
107
+ --filter="substitutions.TAG_NAME=<candidateTag> OR tags=<candidateTag>"
76
108
  ```
77
109
 
78
- Success is `SUCCESS` for that tag. `FAILURE` / `TIMEOUT` / `CANCELLED` is a
79
- hand-back. Load `cloud-debug` only to diagnose, not to rerun the release.
110
+ `FAILURE` / `TIMEOUT` / `CANCELLED` is a hand-back. Load `cloud-debug` only to
111
+ diagnose, not to rerun the release.
80
112
 
81
113
  **npm** (`publisher` `npm` or `both`):
82
114
 
@@ -92,9 +124,10 @@ When a GitHub Actions workflow is the npm publisher, `gh run list` /
92
124
  `gh run watch` for the tag SHA is the wait; `npm view` remains the proof the
93
125
  artifact exists.
94
126
 
95
- ## 4. Failure
127
+ ## 5. Failure
96
128
 
97
- If the watch fails, leave the tag in place and hand back: cut SHA, tag,
98
- publisher logs, and that the version is burned. Do not delete the tag, do not
99
- roll back production, and do not open a `resolve-release` traffic shift. A
100
- fix ships as the next patch from a new green HEAD.
129
+ If an artifact watch fails after the final tag, leave it in place and hand
130
+ back that the version is burned. If a staged candidate build fails, leave its
131
+ RC tag, keep the final tag unused, and hand back with production untouched.
132
+ Promotion failures follow [promotion.md](promotion.md). A fix starts from a new
133
+ green HEAD and a replacement plan; tags are never moved or deleted.
@@ -2,7 +2,7 @@
2
2
  // Changelog + SemVer plan from baseline tag..SHA.
3
3
  //
4
4
  // Usage:
5
- // node plan.mjs --sha <sha> [--trunk main] [--json] [--strict]
5
+ // node plan.mjs --sha <sha> [--trunk main] [--json] [--strict] [--cloud-run]
6
6
  //
7
7
  // Exit: 0 ok · 1 git/inspect failed · 2 usage
8
8
  import { existsSync, readFileSync } from 'node:fs'
@@ -50,6 +50,16 @@ export function latestReleaseTag(tags) {
50
50
  return parsed[parsed.length - 1]
51
51
  }
52
52
 
53
+ export function nextReleaseCandidateTag(finalTag, tags) {
54
+ const escaped = finalTag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
55
+ const pattern = new RegExp(`^${escaped}-rc\\.([1-9]\\d*)$`)
56
+ const ordinals = tags
57
+ .map((tag) => pattern.exec(tag))
58
+ .filter(Boolean)
59
+ .map((match) => Number(match[1]))
60
+ return `${finalTag}-rc.${ordinals.length ? Math.max(...ordinals) + 1 : 1}`
61
+ }
62
+
53
63
  export function classifySubject(subject, body = '') {
54
64
  const title = String(subject || '').trim()
55
65
  const match = TITLE_RE.exec(title)
@@ -207,9 +217,16 @@ function parseLog(stdout) {
207
217
  })
208
218
  }
209
219
 
210
- export function planRelease({ sha, trunk = 'main', strict = false, cwd = process.cwd(), now = new Date() } = {}) {
220
+ export function planRelease({
221
+ sha,
222
+ trunk = 'main',
223
+ strict = false,
224
+ cloudRun = false,
225
+ cwd = process.cwd(),
226
+ now = new Date(),
227
+ } = {}) {
211
228
  if (!sha) {
212
- const err = new Error('usage: plan.mjs --sha <sha> [--trunk main] [--json] [--strict]')
229
+ const err = new Error('usage: plan.mjs --sha <sha> [--trunk main] [--json] [--strict] [--cloud-run]')
213
230
  err.exitCode = 2
214
231
  throw err
215
232
  }
@@ -230,6 +247,13 @@ export function planRelease({ sha, trunk = 'main', strict = false, cwd = process
230
247
  const { bump, reason } = bumpFor(changes, current, { strict })
231
248
  const next = nextVersion(current, bump)
232
249
  const version = formatVersion(next)
250
+ const tag = `v${version}`
251
+ const candidateTag = cloudRun
252
+ ? nextReleaseCandidateTag(tag, git(['tag', '--list', `${tag}-rc.*`], cwd)
253
+ .split('\n')
254
+ .map((line) => line.trim())
255
+ .filter(Boolean))
256
+ : null
233
257
  const date = now.toISOString().slice(0, 10)
234
258
  return {
235
259
  trunk,
@@ -239,7 +263,8 @@ export function planRelease({ sha, trunk = 'main', strict = false, cwd = process
239
263
  bump,
240
264
  reason,
241
265
  nextVersion: version,
242
- tag: `v${version}`,
266
+ tag,
267
+ candidateTag,
243
268
  versionFiles: files,
244
269
  changes,
245
270
  date,
@@ -248,11 +273,12 @@ export function planRelease({ sha, trunk = 'main', strict = false, cwd = process
248
273
  }
249
274
 
250
275
  function parseArgs(argv) {
251
- const opts = { json: false, strict: false, trunk: 'main', sha: null }
276
+ const opts = { json: false, strict: false, cloudRun: false, trunk: 'main', sha: null }
252
277
  for (let i = 0; i < argv.length; i += 1) {
253
278
  const arg = argv[i]
254
279
  if (arg === '--json') opts.json = true
255
280
  else if (arg === '--strict') opts.strict = true
281
+ else if (arg === '--cloud-run') opts.cloudRun = true
256
282
  else if (arg === '--sha') opts.sha = argv[++i]
257
283
  else if (arg === '--trunk') opts.trunk = argv[++i]
258
284
  else if (arg === '--help' || arg === '-h') opts.help = true
@@ -264,7 +290,7 @@ function parseArgs(argv) {
264
290
  }
265
291
  if (opts.help) return opts
266
292
  if (!opts.sha || !opts.trunk) {
267
- const err = new Error('usage: plan.mjs --sha <sha> [--trunk main] [--json] [--strict]')
293
+ const err = new Error('usage: plan.mjs --sha <sha> [--trunk main] [--json] [--strict] [--cloud-run]')
268
294
  err.exitCode = 2
269
295
  throw err
270
296
  }
@@ -274,7 +300,7 @@ function parseArgs(argv) {
274
300
  export function main(argv = process.argv.slice(2)) {
275
301
  const opts = parseArgs(argv)
276
302
  if (opts.help) {
277
- process.stdout.write('plan.mjs --sha <sha> [--trunk main] [--json] [--strict]\n')
303
+ process.stdout.write('plan.mjs --sha <sha> [--trunk main] [--json] [--strict] [--cloud-run]\n')
278
304
  return 0
279
305
  }
280
306
  const result = planRelease(opts)
@@ -63,7 +63,7 @@ node <smoke>/scripts/smoke.mjs plan --manifest smoke.manifest.json --profile pro
63
63
 
64
64
  `--target` is now **just the address to drive against** (the deployed candidate/service URL; defaults to `local`) — it no longer participates in selection. The profile does. For a `local` profile, boot the build yourself the `/verify` way (a `run-*`/`verifier-*` skill in `.claude/skills/`, else the **`run`** skill, else cold-start from README/package.json, timebox ~15 min → BLOCKED); for a deployed profile, point `--target` at the revision and confirm it is the SHA you mean to verify.
65
65
 
66
- **Prod safety is mechanical, and it lives on the profile.** A path runs in a profile only if it declares that profile AND the profile's `mutationPolicy` admits it. The **production profiles (`prod-preview`, `prod-active`) are read-only** — every `mutates:true` path is excluded, **tenant or not** (a synthetic tenant still writes to the real production database, and a revision at 0% traffic is not a reason those writes are safe: `resolve-release` principle 4). `dev-active` allows a `mutates:true` path **only with a declared synthetic tenant**; the local profiles allow isolated writes. This is the old `--no-mutations` flag turned into a property of the *named profile*, so nobody has to remember to pass it — and because one name drives both the run and the verdict, the two **cannot disagree** about the selected set (the old run/verdict flag-mismatch is unrepresentable).
66
+ **Prod safety is mechanical, and it lives on the profile.** A path runs in a profile only if it declares that profile AND the profile's `mutationPolicy` admits it. The **production profiles (`prod-preview`, `prod-active`) are read-only** — every `mutates:true` path is excluded, **tenant or not** (a synthetic tenant still writes to the real production database, and a revision at 0% traffic is not a reason those writes are safe). `dev-active` allows a `mutates:true` path **only with a declared synthetic tenant**; the local profiles allow isolated writes. This is the old `--no-mutations` flag turned into a property of the *named profile*, so nobody has to remember to pass it — and because one name drives both the run and the verdict, the two **cannot disagree** about the selected set (the old run/verdict flag-mismatch is unrepresentable).
67
67
 
68
68
  **Gate before promote.** On a deployed target the smoke is a gate only if it runs *before* the revision is irreversible — a smoke after traffic shifts is a monitor, not a gate. Deploy the candidate at **0% traffic** (stable, addressable) → smoke that URL under `prod-preview` → **promote only on ALIVE** → confirm the serving revision is the one you smoked, then re-smoke the live service under `prod-active`. Bonus: a DEAD is re-investigable against the same 0%-traffic revision with no rebuild — most of what a deployed smoke catches is config/secret/packaging. **Reuse the platform's deploy notification** (the CI/deploy build-failure → Pub/Sub/webhook path), carrying `smoke-verdict.json` so the alert names the broken path; the concrete placement lives in `cloud-build` / `cloud-deploy`.
69
69
 
@@ -82,7 +82,7 @@ node <smoke>/scripts/smoke.mjs verdict --manifest smoke.manifest.json --profile
82
82
  ```
83
83
  **ALIVE** iff every selected path PASSED. Any `fail`, `blocked`, or `missing` → **DEAD**, naming the path(s). Exit code is the gate (0 = ALIVE, 1 = DEAD). Selection is a pure function of `--profile` + the manifest, so the run and the verdict select the **same** set by construction — there is no second flag assembly for the verdict to disagree with, which is the whole point: the run/verdict mismatch that free-form targeting permitted is now unrepresentable.
84
84
 
85
- `smoke-verdict.json` is stamped with the facts an automated consumer needs to judge the evidence rather than trust it: **`profile`** (the Validation Profile it selected under), **`target`** (which URL it ran against), **`noMutations`** (DERIVED from the profile's `mutationPolicy` — `true` for the read-only production profiles), and **`generatedAt`** (an ISO-8601 instant — *when*, so a consumer can bound how old the evidence is). `resolve-release`'s traffic-shift gate compares these; an ALIVE from the wrong target, a mutating run, or too long ago is not a green light.
85
+ `smoke-verdict.json` is stamped with the facts an automated consumer needs to judge the evidence rather than trust it: **`profile`** (the Validation Profile it selected under), **`target`** (which URL it ran against), **`noMutations`** (DERIVED from the profile's `mutationPolicy` — `true` for the read-only production profiles), and **`generatedAt`** (an ISO-8601 instant — *when*, so a consumer can bound how old the evidence is). `rush-release`'s traffic-shift gate compares these; an ALIVE from the wrong target, a mutating run, or too long ago is not a green light.
86
86
 
87
87
  **`generatedAt` dates the verdict, not the run.** `verdict` reduces whatever `--results` file you hand it, however old, and stamps the current instant on the result — so re-running `verdict` alone re-mints freshness without re-validating anything, and a consumer bounding the age cannot tell the difference. That is not a substitute for a stale gate's re-check: when what expired is the *evidence about a live target*, re-drive the paths into a new `smoke-results.json` — re-minting a verdict is not re-validation.
88
88
 
@@ -113,8 +113,8 @@ The **per-PR golden-path liveness floor** (`ensure-coverage` CI contract, gate 8
113
113
  - **CI (gate 8, blocking)** — wired in `e2e-test/assets/ci-gates.*`; boots the app, drives the paths into `smoke-results.json`, runs `smoke.mjs verdict`. The mechanical backstop.
114
114
  - **`resolve-issues`** — final step of the [epic integration gate](../resolve-issues/references/deliverables.md#deliverables-the-shippable-component-not-the-whole-epic): boot the assembled integration branch and run the golden paths; a DEAD whose broken paths are `fail` belongs to a specific unit → reopen its fix loop. **A DEAD built from `blocked`/`missing` paths is a gate-environment problem, not a unit's defect** — report it and fix the environment; reopening a fix loop on it spends a producer spawn and an adversarial review on code that is fine, and the round lands in the unit's budget all the same.
115
115
  - **`/smoke`** — standalone, any time, against `local` or a URL.
116
- - **`resolve-release`** [the exposure gate](../resolve-release/SKILL.md#the-release-loop): the `prod-preview` profile against the 0%-traffic candidate URL (read-only, so it excludes every mutating path), then the `prod-active` profile against the live service after the shift. A DEAD **before** exposure deletes the candidate (no rollback, no user impact); a DEAD after it triggers the rollback policy.
117
- - **The dev-deploy recorder** (`cloud-build` → Post-deploy smoke gate) — the integration pipeline runs this gate after its 100%-traffic deploy and publishes the verdict as a **SHA-keyed commit status**, never failing the build; that recorded ALIVE is what `resolve-release` requires when pinning a release candidate (its principle 11).
116
+ - **`rush-release` Cloud Run flow** the `prod-preview` profile against the 0%-traffic candidate URL (read-only, so it excludes every mutating path), then the `prod-active` profile against the live service after the shift. A DEAD **before** exposure leaves production untouched; a DEAD after it triggers the recorded rollback policy.
117
+ - **The dev-deploy recorder** (`cloud-build` → Post-deploy smoke gate) — the integration pipeline runs this gate after its 100%-traffic deploy and publishes the verdict as a **SHA-keyed commit status**, never failing the build; when configured as required CI, that recorded ALIVE helps `rush-release` select its green trunk SHA.
118
118
 
119
119
  ## Reference Map
120
120
 
@@ -82,7 +82,7 @@ JSON (`smoke.manifest.json`). A repo may author in YAML and project to JSON —
82
82
  | `command` | cli | the command to run, for `cli` paths (alternative to `start`) |
83
83
  | `observable` | api/cli | the **concrete thing that proves the path worked** — the 200 body, the exit code. Required for `api`/`cli` (e2e-test doesn't cover them); for `browser` the referenced journey's assertions *are* the observable. |
84
84
  | `intent` | no | optional one-line human summary of what the browser journey proves — documentation only, not enforced. |
85
- | `mutates` | no | `true` if the path writes durable state. Forces a `tenant` and gates it out of read-only profiles. Under a **read-only profile** (any production profile), a tenant grants nothing: every `mutates:true` path is excluded regardless (`resolve-release` principle 4). |
85
+ | `mutates` | no | `true` if the path writes durable state. Forces a `tenant` and gates it out of read-only profiles. Under a **read-only profile** (any production profile), a tenant grants nothing: every `mutates:true` path is excluded regardless. |
86
86
  | `tenant` | when `mutates` | a **synthetic/test tenant** id so the mutating path can run under a `synthetic-tenant` profile (e.g. `dev-active`) without touching real data. A `mutates:true` path with no `tenant` is excluded from a `synthetic-tenant` profile. This is **not** a production permit: the production database is real whether or not the revision serves traffic, so the read-only production profiles exclude the path either way. Name it with the **`e2e-` prefix** (`e2e-synthetic`) per the e2e-* account convention (`e2e-test` → `references/authoring/auth-flows.md`) — `validate` warns on an unprefixed tenant, because that's the one mistaken for live data. |
87
87
  | `profiles` | **when a `profiles` registry exists** | the [Validation Profiles](#validation-profiles-the-profiles-registry) this path is required in — a non-empty array of names from the registry. A path is only ever selected in a profile it declares; a path that declares none is a silent non-runner, so `validate` **fails closed** on it once a registry is present. (A per-path array, rather than the inverse profile→paths mapping, so a path's selection facts — `mutates`, `tenant`, `profiles` — all sit together on the path they govern.) |
88
88
 
@@ -123,11 +123,11 @@ Read-only paths always run in any profile they declare.
123
123
  | `prod-preview` | `pre-promotion` | `prod` | `preview` | `read-only` |
124
124
  | `prod-active` | `post-promotion` | `prod` | `active` | `read-only` |
125
125
 
126
- `resolve-release` consumes `prod-preview` at step 4a (validate the candidate at 0% traffic) and `prod-active` at step 4b (re-validate the live service after the shift). Each smoke **path** declares the profiles it is required in (`"profiles": ["pr-local", "prod-preview"]`); a path selected by zero eligible paths under a profile yields the existing **EMPTY** verdict — a gate that proves nothing must not look like a pass.
126
+ `rush-release` consumes `prod-preview` before shifting the candidate from 0% traffic and `prod-active` after the shift. Each smoke **path** declares the profiles it is required in (`"profiles": ["pr-local", "prod-preview"]`); a path selected by zero eligible paths under a profile yields the existing **EMPTY** verdict — a gate that proves nothing must not look like a pass.
127
127
 
128
128
  **Recommended, not mandatory (for now).** A manifest with no `profiles` registry still `validate`s (legacy manifests don't break) but `validate` warns, and `plan`/`verdict --profile` cannot run against it. The moment a registry is present it is fully checked, and every path must declare its profiles.
129
129
 
130
- **Reserved names are pinned to their gate.** The five shipped names are consumed by `resolve-release` (and the other lanes) by **hardcoded string** — step 4a drives `--profile prod-preview`, 4b drives `prod-active` — so the name is itself a safety key, not just a label. `validate` therefore **pins each reserved name to its gate**: `pr-local`→`pull-request`, `queue-local`→`merge-queue`, `dev-active`→`trunk-integration`, `prod-preview`→`pre-promotion`, `prod-active`→`post-promotion`. From the gate, the `(environment, endpoint)` table and the prod-read-only rule force the rest — so an entry *named* `prod-preview` but *bound* to a writable dev gate **fails closed**, instead of letting `plan --profile prod-preview` inherit a `synthetic-tenant` write policy against the production candidate.
130
+ **Reserved names are pinned to their gate.** The five shipped names are consumed by `rush-release` (and the other lanes) by **hardcoded string** — staged promotion drives `--profile prod-preview` before exposure and `prod-active` after — so the name is itself a safety key, not just a label. `validate` therefore **pins each reserved name to its gate**: `pr-local`→`pull-request`, `queue-local`→`merge-queue`, `dev-active`→`trunk-integration`, `prod-preview`→`pre-promotion`, `prod-active`→`post-promotion`. From the gate, the `(environment, endpoint)` table and the prod-read-only rule force the rest — so an entry *named* `prod-preview` but *bound* to a writable dev gate **fails closed**, instead of letting `plan --profile prod-preview` inherit a `synthetic-tenant` write policy against the production candidate.
131
131
 
132
132
  | Hazard | Failure mode | What to prove |
133
133
  |---|---|---|
@@ -12,7 +12,8 @@ description: >
12
12
  decline mix, BIN/issuer-country contribution, settlement GMV, payment journey,
13
13
  Sankey funnel, FX-to-USD, chargeback rate, 运营影响, or "business analysis of
14
14
  transactions" — even if they do not say payment-analysis. Do NOT use for
15
- supervised fraud-rule mining (that is fraud-analysis).
15
+ supervised fraud-rule mining (fraud-analysis) or TLD / MCC-miscoding
16
+ clusters (tld-detection).
16
17
  ---
17
18
 
18
19
  # Payment Analysis
@@ -31,7 +32,7 @@ optional topic fuel, not required for the journey core.
31
32
 
32
33
  **Produce:** Markdown + preferred Chinese/bilingual HTML under
33
34
  `reports/payment-analysis/`.
34
- **Do not produce:** supervised precision/recall packages (`fraud-analysis`).
35
+ **Do not produce:** supervised precision/recall packages (`fraud-analysis`) or TLD clusters (`tld-detection`).
35
36
 
36
37
  Read the playbook for the step you are on. Paths under `references/` are
37
38
  relative to this skill.
@@ -106,12 +107,13 @@ Do not change numbers, joins, or chart data.
106
107
  - [ ] 运营影响 only from auth-rate-actions.md when triggered
107
108
  - [ ] Language matches terminology.md; banned strings have no unexplained hits
108
109
 
109
- ## Boundary with fraud-analysis
110
+ ## Boundary with fraud-analysis and tld-detection
110
111
 
111
112
  | Need | Skill |
112
113
  |---|---|
113
114
  | Volume, journey Sankey, auth rate, decline mix, settlement overview, 运营影响 routing | `payment-analysis` |
114
115
  | Proxy/confirmed fraud labels, holdout precision/recall, 3DS/block **packages** | `fraud-analysis` |
116
+ | Potential TLD clusters, currency-amount-pair ladders, MCC miscoding, stuffing | `tld-detection` |
115
117
 
116
118
  Routing 3DS / 风控拦截 / retry from this extract is not a fraud-analysis rule
117
119
  package.
@@ -0,0 +1,163 @@
1
+ ---
2
+ name: tld-detection
3
+ description: >
4
+ Explicit-only. Potential TLD (transaction-laundering) clusters from
5
+ descriptor partitions, then exact currency-amount pairs, with same-descriptor
6
+ baseline contrasts, high/medium/low risk tiers, exposure sizing, and a
7
+ ≤3-sentence decision.
8
+ Not payment-analysis or fraud-analysis.
9
+ disable-model-invocation: true
10
+ ---
11
+
12
+ # TLD Detection
13
+
14
+ Find **potential TLD clusters** in an authorization extract. TLD here is
15
+ transaction laundering, not a domain name: hidden activity stuffed under a
16
+ normal MCC, often mixed across descriptors.
17
+
18
+ First partition the cleaned authorizations by **descriptor**. Inside each
19
+ descriptor, the clustering key is the **currency-amount-pair**. Geometric
20
+ relationships between exact anchors are the stuffing signal (掺量). BIN
21
+ country is not a membership key; report it in the cluster-versus-baseline
22
+ comparison after membership is frozen.
23
+
24
+ **Produce:** cluster results and a separate run-specific methodology document
25
+ under `reports/tld-detection/`.
26
+ **Do not produce:** confirmed laundering, a legal finding, or supervised
27
+ fraud-rule packages (`fraud-analysis`).
28
+
29
+ Paths under `references/` are relative to this skill.
30
+
31
+ ## Workflow
32
+
33
+ Follow these steps in order. Detail:
34
+ [references/workflow.md](references/workflow.md).
35
+
36
+ | Step | Do | Gate |
37
+ |---|---|---|
38
+ | 0 Pre-flight | File, grain, columns, window | Stop if auth grain, descriptor, amount, or currency column is missing; caption missing BIN country |
39
+ | 1 Clean | Dedup | Record drop counts; do not filter BIN country |
40
+ | 2 Cluster | Partition; rank pairs; identify anchors; freeze exact keys; compare with same-descriptor baseline | Partition = descriptor; ≥3 levels; adjacent levels = 2x; ≥100 attempts; absolute residual cap per candidate currency |
41
+ | 3 Risk | Assign high/medium/low investigation priority | Mixed BIN countries are neutral; exact 100% single-country concentration is a standout |
42
+ | 4 Decision | Conclusion + basis for the book | ≤3 sentences; no new evidence |
43
+ | 5 Report | Answer highest-risk, cluster-characteristic, and exposure questions; write + proofread | Sort high → medium → low; no confirmed-TLD wording |
44
+
45
+ ### 0 — Pre-flight
46
+
47
+ Authorization extract, one row per auth attempt. Record paths, time field,
48
+ amount, currency, BIN/issuer country, descriptor, MCC, response. Grain and
49
+ column aliases: [workflow.md](references/workflow.md).
50
+
51
+ ### 1 — Clean
52
+
53
+ [references/clean.md](references/clean.md). Deduplicate first. Do not
54
+ FX-convert the clustering key. Do not drop non-US BIN rows.
55
+
56
+ ### 2 — Cluster
57
+
58
+ [references/clustering.md](references/clustering.md). Build normalized
59
+ descriptor partitions, then rank currency-amount-pairs inside each descriptor. Use
60
+ `scripts/detect_ladders.py` to detect geometric anchor relationships with
61
+ adjacent, currency-specific absolute residual caps. Every cluster must contain
62
+ at least three theoretical levels with a fixed 2x relationship between adjacent
63
+ levels (for example 1x, 2x, 4x, 8x); search both up and down from every candidate
64
+ seed. A theoretical level may
65
+ keep multiple exact anchors, but each additional anchor must clear the stated
66
+ portfolio support floor. Freeze membership by exact descriptor, currency,
67
+ and amount; keep only clusters with ≥100 authorization attempts. Compare each
68
+ cluster with the baseline formed by removing that cluster from the same
69
+ descriptor. Do not invent clusters from MCC or comparison features.
70
+
71
+ ### 3 — Risk
72
+
73
+ [references/decision.md](references/decision.md). Assign every identified
74
+ currency-amount pair exactly one investigation-priority tier: High, Medium,
75
+ or Low. BIN-country mixture is neutral and never downgrades a cluster. Treat
76
+ an exact 100% single-BIN-country cluster as a standout; separately report the
77
+ cluster's share of that descriptor-country slice without using an arbitrary
78
+ coverage threshold or risk points.
79
+
80
+ ### 4 — Decision
81
+
82
+ Write the conclusion and the basis in **at most 3 sentences** for the whole
83
+ book. A one-line table per cluster is allowed under that.
84
+
85
+ ### 5 — Report
86
+
87
+ [references/report-template.md](references/report-template.md). Terms:
88
+ [references/terminology.md](references/terminology.md).
89
+
90
+ ```text
91
+ reports/tld-detection/<scope>_clusters.md
92
+ reports/tld-detection/<scope>_clusters.zh.md
93
+ reports/tld-detection/<scope>_clusters.zh.html
94
+ reports/tld-detection/<scope>_methodology.md
95
+ reports/tld-detection/<scope>_methodology.zh.md
96
+ reports/tld-detection/<scope>_methodology.zh.html
97
+ ```
98
+
99
+ Keep detection results in the cluster reports. Lead with the highest-risk
100
+ clusters, characterize every identified cluster and its descriptor share, then
101
+ quantify exposure by risk tier, descriptor, and across all authorization
102
+ attempts. Sort High → Medium → Low, then by evidence strength and attempts
103
+ inside each tier. Put the
104
+ actual run's cleaning, partitioning, parameter selection, sensitivity evidence,
105
+ baseline definition, and audit details in the methodology documents; link the
106
+ paired files in both directions. HTML is preferred when there are charts. Grep
107
+ banned strings after writing.
108
+
109
+ ## Progress
110
+
111
+ Track a task plan. One item is `in_progress` at a time.
112
+
113
+ 1. Pass pre-flight
114
+ 2. Freeze cleaned universe and descriptor partitions
115
+ 3. Assign High / Medium / Low risk tiers
116
+ 4. Publish descriptor currency-amount pairs + same-descriptor baselines
117
+ 5. Write ≤3-sentence decision
118
+ 6. Write report + separate run methodology and proofread
119
+
120
+ ## Boundary
121
+
122
+ | Need | Skill |
123
+ |---|---|
124
+ | Potential TLD clusters, currency-amount pairs, stuffing / 掺量, MCC miscoding, descriptor mixing | `tld-detection` |
125
+ | Volume, journey Sankey, auth rate, decline mix, settlement overview | `payment-analysis` |
126
+ | Proxy/confirmed fraud labels, holdout precision/recall, rule packages | `fraud-analysis` |
127
+
128
+ A suspected-fraud *rate contrast* in this skill is not a fraud-analysis
129
+ rule package.
130
+
131
+ ## Reference index
132
+
133
+ | Need | Read |
134
+ |---|---|
135
+ | Grain, columns, window | [workflow.md](references/workflow.md) |
136
+ | Dedup | [clean.md](references/clean.md) |
137
+ | Currency-amount pairs, anchors, same-descriptor baseline | [clustering.md](references/clustering.md) |
138
+ | ≤3-sentence conclusion | [decision.md](references/decision.md) |
139
+ | Report chapters | [report-template.md](references/report-template.md) |
140
+ | Language / banned strings | [terminology.md](references/terminology.md) |
141
+
142
+ ## Sanity check
143
+
144
+ - [ ] Auth grain; dedup counts are stated; no BIN-country row filter
145
+ - [ ] Partition key is normalized descriptor; missing descriptor is an explicit bucket
146
+ - [ ] Clustering key is original currency + amount, not USD
147
+ - [ ] Each rung-pool currency has an explicit adjacent absolute-residual cap and sensitivity evidence
148
+ - [ ] Multi-anchor levels state their portfolio support floor; final membership uses exact selected amount keys
149
+ - [ ] Cluster attempts ≥100
150
+ - [ ] Every cluster has a High / Medium / Low risk tier and results sort in that order
151
+ - [ ] Currency-amount pairs show seed and representative anchors; `*` marks a multi-amount level
152
+ - [ ] Same amount pair may recur across descriptor partitions; never merge them automatically
153
+ - [ ] BIN country is post-membership reporting data, not a membership key; Mixed is neutral
154
+ - [ ] Exact 100% single-country concentration is a standout; descriptor-country coverage is shown without a risk threshold
155
+ - [ ] Each cluster baseline is the same descriptor after removing that cluster
156
+ - [ ] Definition summary contains only descriptor, seed, anchors, and attempts
157
+ - [ ] Anchor table reports attempts, authorization success rate, and suspected-fraud share for every level, including n < 30
158
+ - [ ] Characteristics contain descriptor total, cluster attempts/share, MCC, and cluster-vs-baseline auth rate, suspected-fraud share, top 3 banks, and top 3 BIN countries
159
+ - [ ] Decision prose is ≤3 sentences (conclusion + basis)
160
+ - [ ] Report answers highest-risk, identified-cluster characteristics, and by-tier/by-descriptor exposure
161
+ - [ ] Run-specific methodology is separate from results; report and methodology link to each other
162
+ - [ ] Zero families → ranking only; no descriptor-invented clusters
163
+ - [ ] Language matches terminology.md; no confirmed-laundering claim