@1aboveio/skills 0.19.2 → 0.20.1

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 (203) 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/mergify/SKILL.md +1 -1
  8. package/skills/engineering/engineering-runtime/coherence/workflow.json +14 -83
  9. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -6
  10. package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +3 -36
  11. package/skills/engineering/smoke/SKILL.md +1 -1
  12. package/skills/payment/payment-analysis/SKILL.md +13 -7
  13. package/skills/payment/payment-analysis/references/chargebacks.md +43 -18
  14. package/skills/payment/payment-analysis/references/event-layers.md +4 -0
  15. package/skills/payment/payment-analysis/references/metrics.md +9 -7
  16. package/skills/payment/payment-analysis/references/report-template.md +3 -3
  17. package/skills/payment/payment-analysis/references/terminology.md +6 -1
  18. package/skills/payment/payment-analysis/references/visualization.md +1 -1
  19. package/skills/payment/tld-detection/SKILL.md +163 -0
  20. package/skills/payment/tld-detection/agents/openai.yaml +9 -0
  21. package/skills/payment/tld-detection/evals/evals.json +107 -0
  22. package/skills/payment/tld-detection/references/clean.md +51 -0
  23. package/skills/payment/tld-detection/references/clustering.md +219 -0
  24. package/skills/payment/tld-detection/references/decision.md +85 -0
  25. package/skills/payment/tld-detection/references/report-template.md +169 -0
  26. package/skills/payment/tld-detection/references/terminology.md +69 -0
  27. package/skills/payment/tld-detection/references/workflow.md +78 -0
  28. package/skills/payment/tld-detection/scripts/detect_ladders.py +390 -0
  29. package/skills/payment/tld-detection/tests/test_detect_ladders.py +132 -0
  30. package/skills/engineering/resolve-issues/SKILL.md +0 -168
  31. package/skills/engineering/resolve-issues/agents/openai.yaml +0 -9
  32. package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +0 -499
  33. package/skills/engineering/resolve-issues/references/breaker.md +0 -82
  34. package/skills/engineering/resolve-issues/references/deliverables.md +0 -27
  35. package/skills/engineering/resolve-issues/references/delivery.md +0 -108
  36. package/skills/engineering/resolve-issues/references/evidence-lane.md +0 -21
  37. package/skills/engineering/resolve-issues/references/exact-head-ci.md +0 -287
  38. package/skills/engineering/resolve-issues/references/fan-out.md +0 -33
  39. package/skills/engineering/resolve-issues/references/finalization.md +0 -68
  40. package/skills/engineering/resolve-issues/references/guarantees.md +0 -10
  41. package/skills/engineering/resolve-issues/references/high-risk.md +0 -29
  42. package/skills/engineering/resolve-issues/references/incidents/848/README.md +0 -156
  43. package/skills/engineering/resolve-issues/references/intake.md +0 -86
  44. package/skills/engineering/resolve-issues/references/integration-gate.md +0 -53
  45. package/skills/engineering/resolve-issues/references/interference.md +0 -87
  46. package/skills/engineering/resolve-issues/references/loop.md +0 -134
  47. package/skills/engineering/resolve-issues/references/model-catalog.md +0 -9
  48. package/skills/engineering/resolve-issues/references/postmortem.md +0 -27
  49. package/skills/engineering/resolve-issues/references/pre-flight-model-slots.md +0 -41
  50. package/skills/engineering/resolve-issues/references/pre-flight-recording-and-checkout.md +0 -48
  51. package/skills/engineering/resolve-issues/references/pre-flight.md +0 -41
  52. package/skills/engineering/resolve-issues/references/regression-checklist.md +0 -26
  53. package/skills/engineering/resolve-issues/references/run-state.md +0 -288
  54. package/skills/engineering/resolve-issues/references/sandboxed-testing.md +0 -48
  55. package/skills/engineering/resolve-issues/references/spawn-contract.md +0 -96
  56. package/skills/engineering/resolve-issues/references/terminal-evidence-journal.md +0 -40
  57. package/skills/engineering/resolve-issues/references/why.md +0 -653
  58. package/skills/engineering/resolve-issues/schemas/fix-round.schema.json +0 -49
  59. package/skills/engineering/resolve-issues/scripts/combine-and-verify.mjs +0 -721
  60. package/skills/engineering/resolve-issues/scripts/component-candidate.mjs +0 -962
  61. package/skills/engineering/resolve-issues/scripts/contract-revision.mjs +0 -220
  62. package/skills/engineering/resolve-issues/scripts/detect-delivery-mode.mjs +0 -420
  63. package/skills/engineering/resolve-issues/scripts/detect-target-branch.mjs +0 -256
  64. package/skills/engineering/resolve-issues/scripts/detect-workspace-mode.mjs +0 -168
  65. package/skills/engineering/resolve-issues/scripts/discover-models.mjs +0 -9
  66. package/skills/engineering/resolve-issues/scripts/doctrine.mjs +0 -62
  67. package/skills/engineering/resolve-issues/scripts/evidence-lifecycle-contract.mjs +0 -191
  68. package/skills/engineering/resolve-issues/scripts/exact-head-ci.mjs +0 -413
  69. package/skills/engineering/resolve-issues/scripts/exact-head-github-provider.mjs +0 -332
  70. package/skills/engineering/resolve-issues/scripts/finalize.mjs +0 -488
  71. package/skills/engineering/resolve-issues/scripts/fix-rounds.mjs +0 -3307
  72. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-circular-1001.json +0 -16
  73. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-valid-sequencing.json +0 -51
  74. package/skills/engineering/resolve-issues/scripts/fixtures/fmm-express-830-component-candidate.json +0 -17
  75. package/skills/engineering/resolve-issues/scripts/fixtures/head-check-set-1081.json +0 -166
  76. package/skills/engineering/resolve-issues/scripts/gate-value-series.mjs +0 -92
  77. package/skills/engineering/resolve-issues/scripts/guide-index.mjs +0 -73
  78. package/skills/engineering/resolve-issues/scripts/head-check-set.mjs +0 -159
  79. package/skills/engineering/resolve-issues/scripts/interference.mjs +0 -427
  80. package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +0 -9
  81. package/skills/engineering/resolve-issues/scripts/next-operations.mjs +0 -419
  82. package/skills/engineering/resolve-issues/scripts/postmortem.mjs +0 -909
  83. package/skills/engineering/resolve-issues/scripts/preflight-questions.mjs +0 -345
  84. package/skills/engineering/resolve-issues/scripts/reconcile-contained-unit-prs.mjs +0 -415
  85. package/skills/engineering/resolve-issues/scripts/release-state-contract.mjs +0 -697
  86. package/skills/engineering/resolve-issues/scripts/report.mjs +0 -494
  87. package/skills/engineering/resolve-issues/scripts/required-check-plan.mjs +0 -172
  88. package/skills/engineering/resolve-issues/scripts/round-metadata.mjs +0 -79
  89. package/skills/engineering/resolve-issues/scripts/run-state-review6-cases.mjs +0 -334
  90. package/skills/engineering/resolve-issues/scripts/run-state.mjs +0 -4792
  91. package/skills/engineering/resolve-issues/scripts/sandbox-selftest.mjs +0 -395
  92. package/skills/engineering/resolve-issues/scripts/spawn-contract.mjs +0 -290
  93. package/skills/engineering/resolve-issues/scripts/terminal-dispositions.mjs +0 -170
  94. package/skills/engineering/resolve-issues/scripts/terminal-evidence-journal.mjs +0 -293
  95. package/skills/engineering/resolve-issues/scripts/unit-kind.mjs +0 -197
  96. package/skills/engineering/resolve-issues/scripts/unit-lifecycle.mjs +0 -127
  97. package/skills/engineering/resolve-issues/scripts/watch-delivery.mjs +0 -893
  98. package/skills/engineering/resolve-issues/scripts/workspaces.mjs +0 -829
  99. package/skills/engineering/resolve-issues/workflows/independent-review.workflow.js +0 -290
  100. package/skills/engineering/resolve-issues/workflows/prior-art-scan.workflow.js +0 -80
  101. package/skills/engineering/resolve-issues/workflows/workflow-smoke.mjs +0 -102
  102. package/skills/engineering/resolve-release/LICENSE +0 -3
  103. package/skills/engineering/resolve-release/SKILL.md +0 -113
  104. package/skills/engineering/resolve-release/agents/openai.yaml +0 -9
  105. package/skills/engineering/resolve-release/references/assembly.md +0 -137
  106. package/skills/engineering/resolve-release/references/auto-when-green.md +0 -56
  107. package/skills/engineering/resolve-release/references/candidate.md +0 -167
  108. package/skills/engineering/resolve-release/references/exposure.md +0 -178
  109. package/skills/engineering/resolve-release/references/handoff.md +0 -24
  110. package/skills/engineering/resolve-release/references/postmortem.md +0 -230
  111. package/skills/engineering/resolve-release/references/preflight.md +0 -208
  112. package/skills/engineering/resolve-release/references/principles.md +0 -94
  113. package/skills/engineering/resolve-release/references/regression-checklist.md +0 -36
  114. package/skills/engineering/resolve-release/references/related-skills.md +0 -13
  115. package/skills/engineering/resolve-release/references/routing.md +0 -149
  116. package/skills/engineering/resolve-release/references/verified-sha-github-flow.md +0 -285
  117. package/skills/engineering/resolve-release/references/versioning.md +0 -202
  118. package/skills/engineering/resolve-release/references/why.md +0 -53
  119. package/skills/engineering/resolve-release/scripts/adapter-completion-artifact.mjs +0 -389
  120. package/skills/engineering/resolve-release/scripts/build-changes.mjs +0 -209
  121. package/skills/engineering/resolve-release/scripts/candidate-hygiene.mjs +0 -407
  122. package/skills/engineering/resolve-release/scripts/candidate-identity.mjs +0 -904
  123. package/skills/engineering/resolve-release/scripts/candidate-traffic.mjs +0 -81
  124. package/skills/engineering/resolve-release/scripts/checked-adapter-loader.mjs +0 -612
  125. package/skills/engineering/resolve-release/scripts/close-attempt.mjs +0 -135
  126. package/skills/engineering/resolve-release/scripts/closeout-release.mjs +0 -161
  127. package/skills/engineering/resolve-release/scripts/doctrine.mjs +0 -106
  128. package/skills/engineering/resolve-release/scripts/durable-processing.mjs +0 -522
  129. package/skills/engineering/resolve-release/scripts/ensure-target-green.mjs +0 -659
  130. package/skills/engineering/resolve-release/scripts/evidence-bundle.mjs +0 -1014
  131. package/skills/engineering/resolve-release/scripts/finalize-release.mjs +0 -526
  132. package/skills/engineering/resolve-release/scripts/fixtures/durable-processing-adapter.mjs +0 -169
  133. package/skills/engineering/resolve-release/scripts/green-gate.mjs +0 -599
  134. package/skills/engineering/resolve-release/scripts/isolated-adapter-evaluator.mjs +0 -752
  135. package/skills/engineering/resolve-release/scripts/metadata-pr-status.mjs +0 -56
  136. package/skills/engineering/resolve-release/scripts/metadata-sync.mjs +0 -1538
  137. package/skills/engineering/resolve-release/scripts/postmortem.mjs +0 -381
  138. package/skills/engineering/resolve-release/scripts/preflight-probes.mjs +0 -510
  139. package/skills/engineering/resolve-release/scripts/production-endpoints.mjs +0 -326
  140. package/skills/engineering/resolve-release/scripts/rc-circuit-breaker.mjs +0 -272
  141. package/skills/engineering/resolve-release/scripts/report.mjs +0 -417
  142. package/skills/engineering/resolve-release/scripts/reprobe-credentials.mjs +0 -114
  143. package/skills/engineering/resolve-release/scripts/revalidate-candidate.mjs +0 -238
  144. package/skills/engineering/resolve-release/scripts/review-packet.mjs +0 -503
  145. package/skills/engineering/resolve-release/scripts/rollback-floor.mjs +0 -263
  146. package/skills/engineering/resolve-release/scripts/version-assert.mjs +0 -339
  147. package/skills/engineering/resolve-release/scripts/version-postmortem.mjs +0 -485
  148. package/skills/engineering/resolve-release/scripts/version.mjs +0 -1199
  149. package/skills/engineering/resolve-release/scripts/watch-candidate-delivery.mjs +0 -449
  150. package/skills/engineering/resolve-release/vendor/ACORN-LICENSE +0 -21
  151. package/skills/engineering/resolve-release/vendor/README.md +0 -60
  152. package/skills/engineering/resolve-release/vendor/acorn.mjs +0 -6233
  153. package/skills/engineering/resolve-release/vendor/quickjs/core/LICENSE +0 -21
  154. package/skills/engineering/resolve-release/vendor/quickjs/core/README.md +0 -341
  155. package/skills/engineering/resolve-release/vendor/quickjs/core/README.template.md +0 -70
  156. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs +0 -2
  157. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs.map +0 -1
  158. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs +0 -7
  159. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs.map +0 -1
  160. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.mts +0 -2033
  161. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.ts +0 -2033
  162. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js +0 -7
  163. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js.map +0 -1
  164. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs +0 -2
  165. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs.map +0 -1
  166. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs +0 -2
  167. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs.map +0 -1
  168. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs +0 -2
  169. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs.map +0 -1
  170. package/skills/engineering/resolve-release/vendor/quickjs/core/package.json +0 -49
  171. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/LICENSE +0 -21
  172. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/README.md +0 -5
  173. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.mts +0 -549
  174. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.ts +0 -549
  175. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js +0 -2
  176. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js.map +0 -1
  177. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs +0 -2
  178. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs.map +0 -1
  179. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/package.json +0 -36
  180. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/LICENSE +0 -47
  181. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/README.md +0 -82
  182. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.d.ts +0 -11
  183. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.mjs +0 -22
  184. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cjs +0 -21
  185. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.cjs +0 -21
  186. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.d.ts +0 -11
  187. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.d.ts +0 -11
  188. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.mjs +0 -25
  189. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.wasm +0 -0
  190. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.mts +0 -85
  191. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.ts +0 -85
  192. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js +0 -2
  193. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js.map +0 -1
  194. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs +0 -2
  195. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs.map +0 -1
  196. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.mts +0 -20
  197. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.ts +0 -20
  198. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js +0 -2
  199. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js.map +0 -1
  200. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs +0 -2
  201. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs.map +0 -1
  202. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/package.json +0 -61
  203. /package/skills/{engineering/resolve-issues → payment/tld-detection}/LICENSE +0 -0
@@ -1,510 +0,0 @@
1
- #!/usr/bin/env node
2
- // Pre-flight readiness probes: the questions a release discovers the hard way if nobody asks
3
- // them first.
4
- //
5
- // Every one of these comes from pi session 019faea6, where each was found mid-flight instead of
6
- // before the attempt opened:
7
- //
8
- // 1. CREDENTIAL LIVENESS. `gcloud` reauthentication expired after the rc tag was pushed, so
9
- // every Cloud Build and Cloud Run read failed for the next forty minutes. Pre-flight item 3
10
- // already declares WHICH identity performs the exposure; nothing checked that the credential
11
- // it names still works. A release whose credential dies mid-flight cannot verify what it
12
- // deployed, which is principle 5 with no way to satisfy it.
13
- //
14
- // 2. TAG DIVERGENCE. `git fetch` refused `v0.3.1-rc.1` as a clobbering update — the local tag
15
- // pointed at `43fc5398`, the remote at `06dd1b3d`. Tags are immutable by principle 10, so a
16
- // divergence means one of the two is lying about what a version names, and version.mjs is
17
- // about to read the local one.
18
- //
19
- // 3. TARGET-BRANCH GATE COLOUR. Post-deploy validation was already red on `main` for four
20
- // consecutive SHAs before the release began. The release turned into a repair job — correct
21
- // behaviour, but it should be a decision taken at pre-flight with the state named, not a
22
- // surprise after the envelope was approved.
23
- //
24
- // 4. EXPOSURE-GATE CAPABILITY. The run reached a healthy candidate at 0% traffic and only then
25
- // discovered the repo could not produce a deployed-profile verdict at all, so the attempt
26
- // parked at `blocked-manual` having minted a tag and burned a build. The gate that cannot
27
- // run is principle 13, and principle 13 is much cheaper to hit in the first thirty seconds.
28
- //
29
- // WHY A SCRIPT RATHER THAN SIX MORE SENTENCES OF PROSE. Each of these was already implied by an
30
- // existing rule; prose did not stop any of them, because "confirm the envelope" reads as a
31
- // conversation and these are commands. A probe that returns `blocked` with a remedy is not a
32
- // reminder to check — it is the check. This is the same reasoning as `run-state.mjs` mechanizing
33
- // the manifest rules instead of describing them.
34
- //
35
- // FAIL CLOSED, AND SAY WHICH KIND. `unknown` is not `ok`. A probe that could not run leaves the
36
- // question unanswered, and an unanswered readiness question is exactly principle 13's shape — so
37
- // `summarize` treats `unknown` as not-ready, and the CLI exits non-zero. What it does NOT do is
38
- // pretend to know the remedy: an `unknown` reports the raw failure so an operator can see whether
39
- // the probe or the world is broken.
40
- //
41
- // The exposure-gate probe (4) is a COMMAND THE CALLER SUPPLIES, not something inferred here.
42
- // Whether a repo can emit a deployed-profile verdict is a fact about that repo's smoke tooling,
43
- // and guessing at it from file paths is how a probe becomes confidently wrong. So the repo names
44
- // the command (`--exposure-gate-probe`), this script runs it, and an ABSENT command is `unknown`
45
- // rather than `ok` — a repo that never answers cannot silently release on the weaker path, the
46
- // same rule pre-flight item 3 applies to the identity legs.
47
- //
48
- // Issue #823: when the exposure executor is this skill (the smoke gate runs here, not in the
49
- // pipeline), a smoke secret that resolved to its resource-name string instead of its value will
50
- // fail at 4a with 401/403. That is a cannot-run, not a DEAD candidate — but it is still cheaper
51
- // to catch before the candidate is built. Probe declared smoke secrets with one dry fetch.
52
- import fs from 'node:fs'
53
- import { execFileSync } from 'node:child_process'
54
- import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
55
- // Same blocking predicate as ensure-target-green — pending / empty-conclusion required
56
- // gates must never silent-green on either surface (#807 class pending-checks-not-green).
57
- import { isCheckBlocking } from './ensure-target-green.mjs'
58
-
59
- export const PROBE_STATES = { OK: 'ok', BLOCKED: 'blocked', UNKNOWN: 'unknown' }
60
-
61
- const probe = (id, state, detail, remedy = null) => ({ id, state, detail, remedy })
62
-
63
- // ---- 1. credential liveness -------------------------------------------------------------------
64
- //
65
- // The signal is specific and worth matching rather than treating any failure alike: gcloud's
66
- // expired-credential path prints "Reauthentication failed" / "cannot prompt during
67
- // non-interactive execution" and tells you to run `gcloud auth login`. That is BLOCKED with a
68
- // known remedy. A gcloud that is missing entirely, or fails some other way, is UNKNOWN — the
69
- // difference matters because the first is fixed by a human logging in and the second may mean the
70
- // probe is wrong about the platform.
71
- export function classifyCredential({ ok, stdout = '', stderr = '', missing = false } = {}) {
72
- const text = `${stdout}\n${stderr}`
73
- if (missing) {
74
- return probe('credential-liveness', PROBE_STATES.UNKNOWN,
75
- 'the credential tool is not on PATH, so its liveness could not be read',
76
- 'install/configure the platform CLI, or declare a different exposure identity at pre-flight item 3')
77
- }
78
- if (ok) {
79
- return probe('credential-liveness', PROBE_STATES.OK, 'the credential answered a token request')
80
- }
81
- if (/reauthentication failed|cannot prompt during non-interactive|auth login|invalid_grant|credentials.*expired/i.test(text)) {
82
- return probe('credential-liveness', PROBE_STATES.BLOCKED,
83
- `the credential requires interactive reauthentication: ${firstLine(text)}`,
84
- 'run `gcloud auth login` (or the platform equivalent) BEFORE opening the attempt — a credential that dies mid-release cannot verify what it deployed')
85
- }
86
- return probe('credential-liveness', PROBE_STATES.UNKNOWN,
87
- `the credential check failed for a reason this probe does not recognise: ${firstLine(text)}`,
88
- 'read the error; an unrecognised failure is not evidence the credential is good')
89
- }
90
-
91
- // ---- 2. tag divergence ------------------------------------------------------------------------
92
- //
93
- // Compares local tag SHAs against the remote's. Only tags present on BOTH sides can diverge: a
94
- // local-only tag is an unpushed rc (ordinary mid-release state), and a remote-only tag is simply
95
- // not fetched yet. Neither is a lie about a version; a *differing* SHA is.
96
- export function classifyTagDivergence({ local = {}, remote = {}, readable = true } = {}) {
97
- if (!readable) {
98
- return probe('tag-divergence', PROBE_STATES.UNKNOWN,
99
- 'local or remote tags could not be listed',
100
- 'fix the git remote read; unlistable tags cannot be shown to agree')
101
- }
102
- const diverged = []
103
- for (const [tag, sha] of Object.entries(local)) {
104
- const remoteSha = remote[tag]
105
- if (remoteSha && remoteSha !== sha) diverged.push({ tag, local: sha, remote: remoteSha })
106
- }
107
- if (diverged.length === 0) {
108
- return probe('tag-divergence', PROBE_STATES.OK, `${Object.keys(local).length} local tag(s) agree with the remote`)
109
- }
110
- const named = diverged.map((d) => `${d.tag} (local ${d.local.slice(0, 8)} vs remote ${d.remote.slice(0, 8)})`).join('; ')
111
- return probe('tag-divergence', PROBE_STATES.BLOCKED,
112
- `tag(s) name different commits locally and remotely: ${named}`,
113
- 'tags are immutable (principle 10), so one side is wrong about what a version names — delete the local tag and re-fetch, and resolve it BEFORE version.mjs reads it')
114
- }
115
-
116
- // ---- 3. target-branch gate colour -------------------------------------------------------------
117
- //
118
- // A red target branch is not automatically a refusal — the release may legitimately be the thing
119
- // that repairs it, which is the repair lane. What it must not be is a discovery. So this reports
120
- // BLOCKED with the failing gates named, and the caller decides at pre-flight whether to repair
121
- // first or hand back. `--gate` narrows it to the gates that actually gate a release; with no
122
- // `--gate` every failing check-run counts, which is the conservative reading.
123
- export function classifyTargetGate({ checks, readable = true, gates = [] } = {}) {
124
- if (!readable || !Array.isArray(checks)) {
125
- return probe('target-gate-colour', PROBE_STATES.UNKNOWN,
126
- 'the target branch check-runs could not be read',
127
- 'fix the read; an unread gate is not a green gate')
128
- }
129
- const relevant = gates.length > 0 ? checks.filter((c) => gates.includes(c.name)) : checks
130
- if (gates.length > 0) {
131
- const missing = gates.filter((g) => !checks.some((c) => c.name === g))
132
- if (missing.length > 0) {
133
- // An ABSENT required gate reads as permanently unmet, which is a documented trap in this
134
- // toolchain — never silently the same as passing.
135
- return probe('target-gate-colour', PROBE_STATES.BLOCKED,
136
- `required gate(s) reported no check-run on the target head: ${missing.join(', ')}`,
137
- 'an absent check is not a passing check — find out why it did not run before releasing off this head')
138
- }
139
- }
140
- // Share isCheckBlocking with ensure-target-green so empty conclusion / IN_PROGRESS /
141
- // non-COMPLETED status cannot silent-green here while that surface blocks (class:
142
- // pending-checks-not-green). Pre-flight item 0 short-circuits before ensure-target-green
143
- // when this probe reports ok — so this surface MUST use the same predicate.
144
- const failing = relevant.filter((c) => isCheckBlocking(c))
145
- if (failing.length === 0) {
146
- return probe('target-gate-colour', PROBE_STATES.OK, `${relevant.length} check-run(s) on the target head are green or skipped`)
147
- }
148
- const label = (c) => {
149
- const conclusion = String(c.conclusion || '').trim()
150
- const status = String(c.status || '').trim()
151
- if (conclusion) return `${c.name}=${conclusion}`
152
- if (status) return `${c.name}=${status}`
153
- return `${c.name}=PENDING`
154
- }
155
- return probe('target-gate-colour', PROBE_STATES.BLOCKED,
156
- `the target branch head is red: ${failing.map(label).join(', ')}`,
157
- 'decide at pre-flight: run ensure-target-green (classify → repair/brief → re-probe) or hand back — do not discover it after the envelope is approved')
158
- }
159
-
160
- // ---- 4. tag-trigger topology -----------------------------------------------------------------
161
- //
162
- // The production build is owned by Cloud Build's tag trigger, not a controller-submitted
163
- // `gcloud builds submit`. The repository supplies a read-only command that evaluates its trigger
164
- // substitutions for one representative RC and prints the structured record below. This probe
165
- // verifies the specific path that failed in fmm-express: TAG_NAME → _DEPLOYED_VERSION (final
166
- // SemVer) and TAG_NAME → RC candidate tag. A source-SHA image can coexist, but cannot substitute
167
- // for this evidence.
168
- const RC_TAG_RE = /^v?(\d+\.\d+\.\d+)-rc\.[1-9]\d*$/
169
-
170
- export function classifyTagTopology({ topology = null, readable = true, deployTrigger = null } = {}) {
171
- if (deployTrigger === 'no-deploy') {
172
- return {
173
- ...probe('rc-tag-topology', PROBE_STATES.OK,
174
- 'no RC tag is minted for the explicitly declared no-deploy release shape'),
175
- applicable: false,
176
- deployTrigger,
177
- }
178
- }
179
- if (!readable || topology == null || typeof topology !== 'object' || Array.isArray(topology)) {
180
- return probe('rc-tag-topology', PROBE_STATES.UNKNOWN,
181
- 'the production build topology could not be read as a Cloud Build substitution record',
182
- 'run --topology-probe with the repository read that emits prodBuildTrigger, versionSource, candidateTagSource, and substitutions; an unread topology cannot authorize an RC tag')
183
- }
184
-
185
- const prodBuildTrigger = topology.prodBuildTrigger
186
- const versionSource = topology.versionSource
187
- const candidateTagSource = topology.candidateTagSource
188
- const substitutions = topology.substitutions
189
- const fields = { prodBuildTrigger, versionSource, candidateTagSource }
190
- if (prodBuildTrigger !== 'tag-trigger' || versionSource !== 'TAG_NAME' || candidateTagSource !== 'TAG_NAME') {
191
- return {
192
- ...probe('rc-tag-topology', PROBE_STATES.BLOCKED,
193
- 'the production build topology is not tag-trigger / TAG_NAME / TAG_NAME',
194
- 'fix the trigger-owned Cloud Build derivation before minting an RC; controller-submitted substitutions and source-SHA images are not RC candidate evidence'),
195
- ...fields,
196
- }
197
- }
198
- if (substitutions == null || typeof substitutions !== 'object' || Array.isArray(substitutions)) {
199
- return {
200
- ...probe('rc-tag-topology', PROBE_STATES.BLOCKED,
201
- 'the topology record has no Cloud Build substitutions to prove the TAG_NAME derivation',
202
- 'emit TAG_NAME, _DEPLOYED_VERSION, and _CANDIDATE_TAG from the tag-trigger configuration before minting an RC'),
203
- ...fields,
204
- }
205
- }
206
-
207
- const tagName = String(substitutions.TAG_NAME || '')
208
- const deployedVersion = String(substitutions._DEPLOYED_VERSION || '')
209
- const candidateTag = String(substitutions._CANDIDATE_TAG || '')
210
- const match = RC_TAG_RE.exec(tagName)
211
- if (!match || deployedVersion !== match[1] || candidateTag !== tagName) {
212
- return {
213
- ...probe('rc-tag-topology', PROBE_STATES.BLOCKED,
214
- `TAG_NAME does not derive the final _DEPLOYED_VERSION and RC candidate tag (TAG_NAME=${tagName || '(missing)'}, _DEPLOYED_VERSION=${deployedVersion || '(missing)'}, _CANDIDATE_TAG=${candidateTag || '(missing)'})`,
215
- 'make the tag-trigger build derive final SemVer and the RC candidate tag from TAG_NAME; a plain source-SHA image is informational, not candidate evidence'),
216
- ...fields,
217
- }
218
- }
219
- return {
220
- ...probe('rc-tag-topology', PROBE_STATES.OK,
221
- 'Cloud Build tag trigger derives final SemVer and the RC candidate tag from TAG_NAME'),
222
- ...fields,
223
- substitutions: { TAG_NAME: tagName, _DEPLOYED_VERSION: deployedVersion, _CANDIDATE_TAG: candidateTag },
224
- }
225
- }
226
-
227
- // ---- 5. exposure-gate capability --------------------------------------------------------------
228
- //
229
- // Asks the repo's own tooling whether the exposure profile can produce a verdict, by running the
230
- // command the repo names. Exit 0 is capable. A non-zero exit is BLOCKED — and the output is
231
- // quoted verbatim rather than interpreted, because the tool's own refusal id (`BLOCKED`,
232
- // `DEPLOYED_PROFILE_UNAUTHORIZED`, …) is the diagnosis and paraphrasing it loses the one string an
233
- // operator can search for.
234
- // ---- 5. smoke secrets (optional — only when exposure executor is this skill) ---------------
235
- //
236
- // Issue #823. When this skill runs the smoke gate (pre-flight item 3: exposure executor = skill),
237
- // the smoke manifest's `setup.env` names secrets/credentials the gate needs. A common failure
238
- // is an env var that contains the secret *resource name* (`projects/.../secrets/...`) instead
239
- // of the secret *value*, which produces 401/403 at drive time. A pre-flight dry fetch catches
240
- // that before a tag is minted and a candidate is built.
241
- //
242
- // The probe is intentionally platform-agnostic: by default it checks that each declared env
243
- // var is set and that its value does not look like a secret resource path. A repo that wants a
244
- // stronger check supplies `--smoke-secrets-command '<cmd> <name>'` and the probe substitutes
245
- // `<name>` with each env var name. If the command exits non-zero, the secret is BLOCKED.
246
- //
247
- // A missing manifest or absent env var is UNKNOWN, not ok: the question was not answered.
248
- const SECRET_RESOURCE_PATH_RE = /^projects\/[^/]+\/secrets\/[^/]+(\/versions\/[^/]+)?$/i
249
-
250
- export function classifySmokeSecrets({
251
- manifestPath = null,
252
- env = [],
253
- values = {},
254
- command = null,
255
- // When the caller supplied a path but could not read/parse it, fail closed — never
256
- // treat that as "no secrets to verify" (the empty-env OK path). Issue #823 review.
257
- manifestError = null,
258
- } = {}) {
259
- if (!manifestPath) {
260
- return probe('smoke-secrets', PROBE_STATES.UNKNOWN,
261
- 'no smoke manifest was supplied, so declared smoke secrets were not dry-fetched',
262
- 'pass --smoke-manifest <path> when the exposure executor is this skill, so declared smoke secrets can be verified before the candidate is built')
263
- }
264
- if (manifestError) {
265
- return probe('smoke-secrets', PROBE_STATES.UNKNOWN,
266
- `smoke manifest at ${manifestPath} could not be read or parsed: ${manifestError}`,
267
- 'fix the --smoke-manifest path (file must exist and be valid JSON with setup.env) and re-run — an unreadable manifest is not "no secrets to verify"')
268
- }
269
- if (!Array.isArray(env) || env.length === 0) {
270
- return probe('smoke-secrets', PROBE_STATES.OK, 'smoke manifest declares no env/secrets to verify')
271
- }
272
- const bad = []
273
- for (const name of env) {
274
- const value = values[name]
275
- if (value === undefined || value === null || value === '') {
276
- bad.push({ name, reason: 'not set' })
277
- continue
278
- }
279
- if (typeof value === 'string' && SECRET_RESOURCE_PATH_RE.test(value.trim())) {
280
- bad.push({ name, reason: 'value looks like a secret resource path, not the secret value' })
281
- }
282
- }
283
- if (command) {
284
- for (const name of env) {
285
- const result = sh(command.replace(/<name>/g, name), 30_000)
286
- if (!result.ok) {
287
- bad.push({ name, reason: `dry-fetch command failed: ${firstLine(`${result.stdout}\n${result.stderr}`)}` })
288
- }
289
- }
290
- }
291
- if (bad.length === 0) {
292
- return probe('smoke-secrets', PROBE_STATES.OK, `${env.length} declared smoke secret(s) look resolvable`)
293
- }
294
- const named = bad.map((b) => `${b.name} (${b.reason})`).join('; ')
295
- return probe('smoke-secrets', PROBE_STATES.BLOCKED,
296
- `declared smoke secret(s) are misconfigured or could not be dry-fetched: ${named}`,
297
- 'fix the smoke secret (the env var must hold the secret value, not its resource name) and re-run — a 401/403 at 4a is a CANNOT-RUN gate failure, not a DEAD candidate')
298
- }
299
-
300
- export function classifyExposureGate({ command = null, ok = false, stdout = '', stderr = '' } = {}) {
301
- if (!command) {
302
- return probe('exposure-gate-capability', PROBE_STATES.UNKNOWN,
303
- 'no exposure-gate probe command was supplied, so whether this repo can produce a deployed-profile verdict is unknown',
304
- 'pass --exposure-gate-probe "<command>" (the repo\'s own check that its exposure profile is authorized). Unknown is not ok: a repo that cannot produce the verdict parks at blocked-manual AFTER minting a tag and building a candidate')
305
- }
306
- if (ok) {
307
- return probe('exposure-gate-capability', PROBE_STATES.OK, 'the repo reports its exposure profile can produce a verdict')
308
- }
309
- return probe('exposure-gate-capability', PROBE_STATES.BLOCKED,
310
- `the repo cannot produce a verdict for its exposure profile: ${firstLine(`${stdout}\n${stderr}`)}`,
311
- 'this release can only end blocked-manual — close the capability gap first (the deployment verifier / authorized profile), or declare a different exposure executor at pre-flight item 3')
312
- }
313
-
314
- function firstLine(text) {
315
- const line = String(text || '').split('\n').map((l) => l.trim()).find((l) => l.length > 0)
316
- return line ? line.slice(0, 300) : '(no output)'
317
- }
318
-
319
- // `unknown` counts as not-ready, deliberately. The whole failure this script addresses is a
320
- // question nobody asked, and "we could not ask it" leaves the release in exactly that state.
321
- export function summarize(probes) {
322
- const list = Array.isArray(probes) ? probes : []
323
- const blocked = list.filter((p) => p.state === PROBE_STATES.BLOCKED)
324
- const unknown = list.filter((p) => p.state === PROBE_STATES.UNKNOWN)
325
- return {
326
- ready: list.length > 0 && blocked.length === 0 && unknown.length === 0,
327
- blocked: blocked.map((p) => p.id),
328
- unknown: unknown.map((p) => p.id),
329
- probes: list,
330
- }
331
- }
332
-
333
- export function parseArgs(argv) {
334
- const args = { gates: [] }
335
- for (let i = 0; i < argv.length; i += 1) {
336
- const a = argv[i]
337
- if (a === '--repo') args.repo = argv[++i]
338
- else if (a === '--target-branch') args.targetBranch = argv[++i]
339
- else if (a === '--credential-command') args.credentialCommand = argv[++i]
340
- else if (a === '--deploy-trigger') args.deployTrigger = argv[++i]
341
- else if (a === '--exposure-gate-probe') args.exposureGateProbe = argv[++i]
342
- else if (a === '--topology-probe') args.topologyProbe = argv[++i]
343
- else if (a === '--smoke-manifest') args.smokeManifest = argv[++i]
344
- else if (a === '--smoke-secrets-command') args.smokeSecretsCommand = argv[++i]
345
- else if (a === '--gate') args.gates.push(argv[++i])
346
- else if (a === '--json') args.json = true
347
- else if (a === '--help' || a === '-h') args.help = true
348
- else args.unknown = a
349
- }
350
- return args
351
- }
352
-
353
- export const DEFAULT_CREDENTIAL_COMMAND = 'gcloud auth print-access-token'
354
-
355
- // Exported so the `-d` can be asserted. The bug it prevents lives in the COMMAND, not the
356
- // parser, so no amount of parser testing catches its removal — see the comment at the call site.
357
- export const LOCAL_TAGS_COMMAND = 'git show-ref --tags -d'
358
-
359
- export function sh(command, timeout = 60_000) {
360
- try {
361
- const stdout = execFileSync('/bin/sh', ['-c', command], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout })
362
- return { ok: true, stdout, stderr: '' }
363
- } catch (error) {
364
- return {
365
- ok: false,
366
- stdout: error.stdout?.toString?.() || '',
367
- stderr: error.stderr?.toString?.() || error.message || '',
368
- missing: /not found|ENOENT/i.test(error.message || ''),
369
- }
370
- }
371
- }
372
-
373
- function help() {
374
- return `preflight-probes check --repo <owner/name> --target-branch <branch> [options]
375
-
376
- The readiness questions a release otherwise discovers mid-flight. Run BEFORE opening the
377
- attempt; the first four were found the hard way in pi session 019faea6, the topology probe catches
378
- tag-trigger drift before an RC is minted (#1025), and smoke-secrets catches misconfiguration before
379
- a candidate is built (#823).
380
-
381
- Options:
382
- --credential-command <cmd> liveness check, default: ${DEFAULT_CREDENTIAL_COMMAND}
383
- --deploy-trigger <shape> explicit release shape. no-deploy records RC topology as decided
384
- not-applicable; omitting it retains the fail-closed RC topology read.
385
- --exposure-gate-probe <cmd> the repo's own check that its exposure profile can produce a
386
- verdict. ABSENT is "unknown", which is NOT ready.
387
- --topology-probe <cmd> read-only repository command that prints JSON for one representative
388
- RC: prodBuildTrigger, versionSource, candidateTagSource, and
389
- substitutions.TAG_NAME/_DEPLOYED_VERSION/_CANDIDATE_TAG. ABSENT is
390
- "unknown", which is NOT ready.
391
- --smoke-manifest <path> smoke manifest to read declared env/secrets from. Use when the
392
- exposure executor is this skill.
393
- --smoke-secrets-command <cmd> optional per-secret dry-fetch command; '<name>' is substituted
394
- with each env var name declared in the smoke manifest.
395
- --gate <name> a gate that must be present and green on the target head
396
- (repeatable). With none, every failing check-run counts.
397
- --json machine-readable summary on stdout
398
-
399
- Exit codes:
400
- 0 ready — every probe ok
401
- 1 not ready — at least one blocked or unknown probe, each with a remedy
402
- 2 usage error
403
-
404
- "unknown" is never "ok": an unasked readiness question is the state this script exists to end.`
405
- }
406
-
407
- export async function runCli(argv) {
408
- const args = parseArgs(argv)
409
- if (args.help) { console.log(help()); return 0 }
410
- const cmd = argv[0]
411
- if (args.unknown && args.unknown !== cmd) { console.error(help()); return 2 }
412
- if (cmd !== 'check') { console.error(help()); return 2 }
413
- if (!args.repo || !args.targetBranch) { console.error(help()); return 2 }
414
-
415
- const probes = []
416
-
417
- const cred = sh(args.credentialCommand || DEFAULT_CREDENTIAL_COMMAND)
418
- probes.push(classifyCredential(cred))
419
-
420
- // `-d` (--dereference) is load-bearing, not tidiness: without it `show-ref --tags` prints only
421
- // the TAG OBJECT sha for an annotated tag, while `ls-remote --tags` prints the tag object AND
422
- // the `^{}` commit. Comparing one side's tag object against the other side's commit reports a
423
- // divergence for every annotated tag in the repo — verified live against this skills repo, which
424
- // returned six false positives (v0.1.0 "local 88493d4f vs remote a4afcc5d" is the same tag:
425
- // 88493d4f is its tag object, a4afcc5d the commit). A probe that cries wolf on every release is
426
- // a probe someone switches off, so both sides must dereference the same way.
427
- const localTags = sh(`${LOCAL_TAGS_COMMAND} || true`)
428
- const remoteTags = sh(`git ls-remote --tags ${shellQuote(`https://github.com/${args.repo}.git`)} || git ls-remote --tags origin`)
429
- probes.push(classifyTagDivergence({
430
- local: parseShowRef(localTags.stdout),
431
- remote: parseLsRemote(remoteTags.stdout),
432
- readable: localTags.ok && remoteTags.ok,
433
- }))
434
-
435
- // Fetch status alongside conclusion — IN_PROGRESS/QUEUED gates have conclusion:"" and
436
- // must be classifiable by isCheckBlocking (same shape ensure-target-green fetchTip uses).
437
- const checks = sh(`gh api repos/${args.repo}/commits/${shellQuote(args.targetBranch)}/check-runs --jq '[.check_runs[] | {name: .name, status: (.status // ""), conclusion: (.conclusion // "")}]'`)
438
- probes.push(classifyTargetGate({
439
- checks: checks.ok ? safeJson(checks.stdout) : null,
440
- readable: checks.ok,
441
- gates: args.gates,
442
- }))
443
-
444
- const topologyRead = args.topologyProbe ? sh(args.topologyProbe, 300_000) : {}
445
- probes.push(classifyTagTopology({
446
- topology: topologyRead.ok ? safeJson(topologyRead.stdout) : null,
447
- readable: Boolean(args.topologyProbe) && topologyRead.ok,
448
- deployTrigger: args.deployTrigger || null,
449
- }))
450
-
451
- const exposure = args.exposureGateProbe ? sh(args.exposureGateProbe, 300_000) : {}
452
- probes.push(classifyExposureGate({ command: args.exposureGateProbe || null, ...exposure }))
453
-
454
- // Issue #823: optional smoke-secrets dry fetch when this skill runs the exposure gate.
455
- // We read the manifest and the current process env values; the optional command lets a repo
456
- // do a platform-specific real fetch if it wants one stronger than the resource-path heuristic.
457
- if (args.smokeManifest) {
458
- let manifest = null
459
- let manifestError = null
460
- try {
461
- manifest = JSON.parse(fs.readFileSync(args.smokeManifest, 'utf8'))
462
- } catch (err) {
463
- // Prefer a short, actionable detail over a stack.
464
- if (err && err.code === 'ENOENT') manifestError = 'file not found'
465
- else if (err instanceof SyntaxError) manifestError = 'invalid JSON'
466
- else manifestError = err && err.message ? err.message : String(err)
467
- }
468
- const env = !manifestError && manifest && Array.isArray(manifest.setup && manifest.setup.env)
469
- ? manifest.setup.env
470
- : []
471
- probes.push(classifySmokeSecrets({
472
- manifestPath: args.smokeManifest,
473
- env,
474
- values: Object.fromEntries(env.map((name) => [name, process.env[name]])),
475
- command: args.smokeSecretsCommand || null,
476
- manifestError,
477
- }))
478
- }
479
-
480
- const summary = summarize(probes)
481
- if (args.json) console.log(JSON.stringify(summary, null, 2))
482
- else {
483
- for (const p of summary.probes) {
484
- console.log(`${p.state.toUpperCase().padEnd(7)} ${p.id} — ${p.detail}`)
485
- if (p.remedy && p.state !== PROBE_STATES.OK) console.log(` remedy: ${p.remedy}`)
486
- }
487
- console.log(summary.ready ? '\nREADY — every probe ok' : `\nNOT READY — blocked: [${summary.blocked}] unknown: [${summary.unknown}]`)
488
- }
489
- return summary.ready ? 0 : 1
490
- }
491
-
492
- const shellQuote = (s) => `'${String(s).replace(/'/g, "'\\''")}'`
493
- const safeJson = (t) => { try { return JSON.parse(t) } catch { return null } }
494
-
495
- export function parseShowRef(text) {
496
- const out = {}
497
- for (const line of String(text || '').split('\n')) {
498
- const m = /^([0-9a-f]{40})\s+refs\/tags\/(.+?)(\^\{\})?$/.exec(line.trim())
499
- // Annotated tags list twice: the tag object, then `^{}` for the commit it points at. The
500
- // dereferenced line is the comparable one, so it wins where both appear.
501
- if (m) { if (m[3] || !(m[2] in out)) out[m[2]] = m[1] }
502
- }
503
- return out
504
- }
505
-
506
- export const parseLsRemote = parseShowRef
507
-
508
- if (isMainModule(import.meta.url)) {
509
- runCli(process.argv.slice(2)).then((code) => { process.exitCode = code })
510
- }