@1aboveio/skills 0.19.3 → 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.
- package/README.md +9 -4
- package/package.json +1 -1
- package/runtime/skills/distribution/generated/recipes.json +54 -48
- package/runtime/skills/distribution/scripts/bundles.mjs +113 -7
- package/runtime/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -6
- package/runtime/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +3 -36
- package/skills/cicd-pipeline/mergify/SKILL.md +1 -1
- package/skills/engineering/engineering-runtime/coherence/workflow.json +14 -83
- package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +4 -6
- package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +3 -36
- package/skills/engineering/smoke/SKILL.md +1 -1
- package/skills/payment/payment-analysis/SKILL.md +5 -3
- package/skills/payment/tld-detection/SKILL.md +163 -0
- package/skills/payment/tld-detection/agents/openai.yaml +9 -0
- package/skills/payment/tld-detection/evals/evals.json +107 -0
- package/skills/payment/tld-detection/references/clean.md +51 -0
- package/skills/payment/tld-detection/references/clustering.md +219 -0
- package/skills/payment/tld-detection/references/decision.md +85 -0
- package/skills/payment/tld-detection/references/report-template.md +169 -0
- package/skills/payment/tld-detection/references/terminology.md +69 -0
- package/skills/payment/tld-detection/references/workflow.md +78 -0
- package/skills/payment/tld-detection/scripts/detect_ladders.py +390 -0
- package/skills/payment/tld-detection/tests/test_detect_ladders.py +132 -0
- package/skills/engineering/resolve-issues/SKILL.md +0 -168
- package/skills/engineering/resolve-issues/agents/openai.yaml +0 -9
- package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +0 -499
- package/skills/engineering/resolve-issues/references/breaker.md +0 -82
- package/skills/engineering/resolve-issues/references/deliverables.md +0 -27
- package/skills/engineering/resolve-issues/references/delivery.md +0 -108
- package/skills/engineering/resolve-issues/references/evidence-lane.md +0 -21
- package/skills/engineering/resolve-issues/references/exact-head-ci.md +0 -287
- package/skills/engineering/resolve-issues/references/fan-out.md +0 -33
- package/skills/engineering/resolve-issues/references/finalization.md +0 -68
- package/skills/engineering/resolve-issues/references/guarantees.md +0 -10
- package/skills/engineering/resolve-issues/references/high-risk.md +0 -29
- package/skills/engineering/resolve-issues/references/incidents/848/README.md +0 -156
- package/skills/engineering/resolve-issues/references/intake.md +0 -86
- package/skills/engineering/resolve-issues/references/integration-gate.md +0 -53
- package/skills/engineering/resolve-issues/references/interference.md +0 -87
- package/skills/engineering/resolve-issues/references/loop.md +0 -134
- package/skills/engineering/resolve-issues/references/model-catalog.md +0 -9
- package/skills/engineering/resolve-issues/references/postmortem.md +0 -27
- package/skills/engineering/resolve-issues/references/pre-flight-model-slots.md +0 -41
- package/skills/engineering/resolve-issues/references/pre-flight-recording-and-checkout.md +0 -48
- package/skills/engineering/resolve-issues/references/pre-flight.md +0 -41
- package/skills/engineering/resolve-issues/references/regression-checklist.md +0 -26
- package/skills/engineering/resolve-issues/references/run-state.md +0 -288
- package/skills/engineering/resolve-issues/references/sandboxed-testing.md +0 -48
- package/skills/engineering/resolve-issues/references/spawn-contract.md +0 -96
- package/skills/engineering/resolve-issues/references/terminal-evidence-journal.md +0 -40
- package/skills/engineering/resolve-issues/references/why.md +0 -653
- package/skills/engineering/resolve-issues/schemas/fix-round.schema.json +0 -49
- package/skills/engineering/resolve-issues/scripts/combine-and-verify.mjs +0 -721
- package/skills/engineering/resolve-issues/scripts/component-candidate.mjs +0 -962
- package/skills/engineering/resolve-issues/scripts/contract-revision.mjs +0 -220
- package/skills/engineering/resolve-issues/scripts/detect-delivery-mode.mjs +0 -420
- package/skills/engineering/resolve-issues/scripts/detect-target-branch.mjs +0 -256
- package/skills/engineering/resolve-issues/scripts/detect-workspace-mode.mjs +0 -168
- package/skills/engineering/resolve-issues/scripts/discover-models.mjs +0 -9
- package/skills/engineering/resolve-issues/scripts/doctrine.mjs +0 -62
- package/skills/engineering/resolve-issues/scripts/evidence-lifecycle-contract.mjs +0 -191
- package/skills/engineering/resolve-issues/scripts/exact-head-ci.mjs +0 -413
- package/skills/engineering/resolve-issues/scripts/exact-head-github-provider.mjs +0 -332
- package/skills/engineering/resolve-issues/scripts/finalize.mjs +0 -488
- package/skills/engineering/resolve-issues/scripts/fix-rounds.mjs +0 -3307
- package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-circular-1001.json +0 -16
- package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-valid-sequencing.json +0 -51
- package/skills/engineering/resolve-issues/scripts/fixtures/fmm-express-830-component-candidate.json +0 -17
- package/skills/engineering/resolve-issues/scripts/fixtures/head-check-set-1081.json +0 -166
- package/skills/engineering/resolve-issues/scripts/gate-value-series.mjs +0 -92
- package/skills/engineering/resolve-issues/scripts/guide-index.mjs +0 -73
- package/skills/engineering/resolve-issues/scripts/head-check-set.mjs +0 -159
- package/skills/engineering/resolve-issues/scripts/interference.mjs +0 -427
- package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +0 -9
- package/skills/engineering/resolve-issues/scripts/next-operations.mjs +0 -419
- package/skills/engineering/resolve-issues/scripts/postmortem.mjs +0 -909
- package/skills/engineering/resolve-issues/scripts/preflight-questions.mjs +0 -345
- package/skills/engineering/resolve-issues/scripts/reconcile-contained-unit-prs.mjs +0 -415
- package/skills/engineering/resolve-issues/scripts/release-state-contract.mjs +0 -697
- package/skills/engineering/resolve-issues/scripts/report.mjs +0 -494
- package/skills/engineering/resolve-issues/scripts/required-check-plan.mjs +0 -172
- package/skills/engineering/resolve-issues/scripts/round-metadata.mjs +0 -79
- package/skills/engineering/resolve-issues/scripts/run-state-review6-cases.mjs +0 -334
- package/skills/engineering/resolve-issues/scripts/run-state.mjs +0 -4792
- package/skills/engineering/resolve-issues/scripts/sandbox-selftest.mjs +0 -395
- package/skills/engineering/resolve-issues/scripts/spawn-contract.mjs +0 -290
- package/skills/engineering/resolve-issues/scripts/terminal-dispositions.mjs +0 -170
- package/skills/engineering/resolve-issues/scripts/terminal-evidence-journal.mjs +0 -293
- package/skills/engineering/resolve-issues/scripts/unit-kind.mjs +0 -197
- package/skills/engineering/resolve-issues/scripts/unit-lifecycle.mjs +0 -127
- package/skills/engineering/resolve-issues/scripts/watch-delivery.mjs +0 -893
- package/skills/engineering/resolve-issues/scripts/workspaces.mjs +0 -829
- package/skills/engineering/resolve-issues/workflows/independent-review.workflow.js +0 -290
- package/skills/engineering/resolve-issues/workflows/prior-art-scan.workflow.js +0 -80
- package/skills/engineering/resolve-issues/workflows/workflow-smoke.mjs +0 -102
- package/skills/engineering/resolve-release/LICENSE +0 -3
- package/skills/engineering/resolve-release/SKILL.md +0 -113
- package/skills/engineering/resolve-release/agents/openai.yaml +0 -9
- package/skills/engineering/resolve-release/references/assembly.md +0 -137
- package/skills/engineering/resolve-release/references/auto-when-green.md +0 -56
- package/skills/engineering/resolve-release/references/candidate.md +0 -167
- package/skills/engineering/resolve-release/references/exposure.md +0 -178
- package/skills/engineering/resolve-release/references/handoff.md +0 -24
- package/skills/engineering/resolve-release/references/postmortem.md +0 -230
- package/skills/engineering/resolve-release/references/preflight.md +0 -208
- package/skills/engineering/resolve-release/references/principles.md +0 -94
- package/skills/engineering/resolve-release/references/regression-checklist.md +0 -36
- package/skills/engineering/resolve-release/references/related-skills.md +0 -13
- package/skills/engineering/resolve-release/references/routing.md +0 -149
- package/skills/engineering/resolve-release/references/verified-sha-github-flow.md +0 -285
- package/skills/engineering/resolve-release/references/versioning.md +0 -202
- package/skills/engineering/resolve-release/references/why.md +0 -53
- package/skills/engineering/resolve-release/scripts/adapter-completion-artifact.mjs +0 -389
- package/skills/engineering/resolve-release/scripts/build-changes.mjs +0 -209
- package/skills/engineering/resolve-release/scripts/candidate-hygiene.mjs +0 -407
- package/skills/engineering/resolve-release/scripts/candidate-identity.mjs +0 -904
- package/skills/engineering/resolve-release/scripts/candidate-traffic.mjs +0 -81
- package/skills/engineering/resolve-release/scripts/checked-adapter-loader.mjs +0 -612
- package/skills/engineering/resolve-release/scripts/close-attempt.mjs +0 -135
- package/skills/engineering/resolve-release/scripts/closeout-release.mjs +0 -161
- package/skills/engineering/resolve-release/scripts/doctrine.mjs +0 -106
- package/skills/engineering/resolve-release/scripts/durable-processing.mjs +0 -522
- package/skills/engineering/resolve-release/scripts/ensure-target-green.mjs +0 -659
- package/skills/engineering/resolve-release/scripts/evidence-bundle.mjs +0 -1014
- package/skills/engineering/resolve-release/scripts/finalize-release.mjs +0 -526
- package/skills/engineering/resolve-release/scripts/fixtures/durable-processing-adapter.mjs +0 -169
- package/skills/engineering/resolve-release/scripts/green-gate.mjs +0 -599
- package/skills/engineering/resolve-release/scripts/isolated-adapter-evaluator.mjs +0 -752
- package/skills/engineering/resolve-release/scripts/metadata-pr-status.mjs +0 -56
- package/skills/engineering/resolve-release/scripts/metadata-sync.mjs +0 -1538
- package/skills/engineering/resolve-release/scripts/postmortem.mjs +0 -381
- package/skills/engineering/resolve-release/scripts/preflight-probes.mjs +0 -510
- package/skills/engineering/resolve-release/scripts/production-endpoints.mjs +0 -326
- package/skills/engineering/resolve-release/scripts/rc-circuit-breaker.mjs +0 -272
- package/skills/engineering/resolve-release/scripts/report.mjs +0 -417
- package/skills/engineering/resolve-release/scripts/reprobe-credentials.mjs +0 -114
- package/skills/engineering/resolve-release/scripts/revalidate-candidate.mjs +0 -238
- package/skills/engineering/resolve-release/scripts/review-packet.mjs +0 -503
- package/skills/engineering/resolve-release/scripts/rollback-floor.mjs +0 -263
- package/skills/engineering/resolve-release/scripts/version-assert.mjs +0 -339
- package/skills/engineering/resolve-release/scripts/version-postmortem.mjs +0 -485
- package/skills/engineering/resolve-release/scripts/version.mjs +0 -1199
- package/skills/engineering/resolve-release/scripts/watch-candidate-delivery.mjs +0 -449
- package/skills/engineering/resolve-release/vendor/ACORN-LICENSE +0 -21
- package/skills/engineering/resolve-release/vendor/README.md +0 -60
- package/skills/engineering/resolve-release/vendor/acorn.mjs +0 -6233
- package/skills/engineering/resolve-release/vendor/quickjs/core/LICENSE +0 -21
- package/skills/engineering/resolve-release/vendor/quickjs/core/README.md +0 -341
- package/skills/engineering/resolve-release/vendor/quickjs/core/README.template.md +0 -70
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs +0 -7
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.mts +0 -2033
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.ts +0 -2033
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js +0 -7
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/core/package.json +0 -49
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/LICENSE +0 -21
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/README.md +0 -5
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.mts +0 -549
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.ts +0 -549
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/package.json +0 -36
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/LICENSE +0 -47
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/README.md +0 -82
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.d.ts +0 -11
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.mjs +0 -22
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cjs +0 -21
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.cjs +0 -21
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.d.ts +0 -11
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.d.ts +0 -11
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.mjs +0 -25
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.wasm +0 -0
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.mts +0 -85
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.ts +0 -85
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.mts +0 -20
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.ts +0 -20
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs +0 -2
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs.map +0 -1
- package/skills/engineering/resolve-release/vendor/quickjs/release-sync/package.json +0 -61
- /package/skills/{engineering/resolve-issues → payment/tld-detection}/LICENSE +0 -0
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
// candidate-hygiene — pre-locate cleanup of stale candidate-* traffic tags.
|
|
2
|
-
//
|
|
3
|
-
// Previous successful releases can leave `candidate-<sha>` tags on the now-100% revision
|
|
4
|
-
// (the incumbent) or on an orphan 0% revision. Those tags are NOT a second release in flight,
|
|
5
|
-
// but they make `candidate-identity.mjs locate` report `e:second-candidate-in-flight`. This
|
|
6
|
-
// script reads the same traffic capture `locate` uses, classifies the foreign tags, and emits the
|
|
7
|
-
// `gcloud run services update-traffic --remove-tags` command that removes only the stale ones.
|
|
8
|
-
//
|
|
9
|
-
// A foreign tag is stale only when the shared traffic parser proves it has 0%, matching
|
|
10
|
-
// terminal release evidence identifies the same tag and revision, and the product-scoped lease
|
|
11
|
-
// report is valid and empty. Traffic, ambiguous traffic, missing/nonterminal evidence, or a
|
|
12
|
-
// held/unknown lease remains a competing release and refuses. A missing lease report after the
|
|
13
|
-
// other proofs reports `decision: "needs-lease-report"` and exits 3.
|
|
14
|
-
//
|
|
15
|
-
// The script is PURE: it does not shell out to gcloud or change production. The release lane runs
|
|
16
|
-
// the emitted command, then re-runs `candidate-identity.mjs locate` on the cleaned traffic.
|
|
17
|
-
//
|
|
18
|
-
// Usage:
|
|
19
|
-
// candidate-hygiene locate --sha <shipped> --traffic <file>
|
|
20
|
-
// [--candidate-tag-prefix <prefix-ending-in-hyphen>]
|
|
21
|
-
// [--lease-report <file>] [--terminal-evidence <file>] [--service <svc>] [--region <r>]
|
|
22
|
-
//
|
|
23
|
-
// Input `--traffic` is the same flattened / JSON form accepted by `candidate-identity.mjs`.
|
|
24
|
-
// `--lease-report` is the JSON output of `run-state.mjs check-release-lease-scoped` for the
|
|
25
|
-
// same product. `--terminal-evidence` is a JSON array of canonical resolve-release manifests
|
|
26
|
-
// or immutable release evidence bundles. Each stale candidate must match one verified terminal
|
|
27
|
-
// attempt by exact tag and revision. Without either proof, cleanup is not authorized.
|
|
28
|
-
//
|
|
29
|
-
// Exit codes:
|
|
30
|
-
// 0 clean / no-action — only stale tags (or none), and no true rival
|
|
31
|
-
// 1 refuse — a true second release is present; the failing ids are in `refuse`
|
|
32
|
-
// 2 usage / unreadable input
|
|
33
|
-
// 3 needs-lease-report — a 0% foreign candidate requires a lease report to classify; supply one and re-run
|
|
34
|
-
|
|
35
|
-
import fs from 'node:fs'
|
|
36
|
-
import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
|
|
37
|
-
import { parseTraffic, normalizeTrafficPercents } from './candidate-traffic.mjs'
|
|
38
|
-
import { MIN_SHORT_SHA } from './candidate-identity.mjs'
|
|
39
|
-
import { RELEASE_STATE_CONTRACT } from '../../resolve-issues/scripts/release-state-contract.mjs'
|
|
40
|
-
import { validateBundle } from './evidence-bundle.mjs'
|
|
41
|
-
|
|
42
|
-
const SHA_RE = new RegExp(`^[0-9a-f]{${MIN_SHORT_SHA},40}$`, 'i')
|
|
43
|
-
const isSha = (s) => typeof s === 'string' && SHA_RE.test(s)
|
|
44
|
-
|
|
45
|
-
const shaMatches = (a, b) => {
|
|
46
|
-
if (!isSha(a) || !isSha(b)) return false
|
|
47
|
-
const [x, y] = [a.toLowerCase(), b.toLowerCase()]
|
|
48
|
-
const short = x.length <= y.length ? x : y
|
|
49
|
-
const long = short === x ? y : x
|
|
50
|
-
return long.startsWith(short)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const DEFAULT_CANDIDATE_TAG_PREFIX = 'candidate-'
|
|
54
|
-
|
|
55
|
-
function validCandidateTagPrefix(prefix) {
|
|
56
|
-
return typeof prefix === 'string' && /^[a-z][a-z0-9-]{0,30}-$/.test(prefix)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function classifyTag(tag, sha, candidateTagPrefix = DEFAULT_CANDIDATE_TAG_PREFIX) {
|
|
60
|
-
if (tag === 'candidate') return 'shared'
|
|
61
|
-
if (!validCandidateTagPrefix(candidateTagPrefix) || !tag.startsWith(candidateTagPrefix)) {
|
|
62
|
-
return 'other'
|
|
63
|
-
}
|
|
64
|
-
const suffix = tag.slice(candidateTagPrefix.length)
|
|
65
|
-
if (!/^[0-9a-f]{4,40}$/i.test(suffix) || !isSha(suffix)) return 'other'
|
|
66
|
-
return shaMatches(suffix, sha) ? 'ours' : 'foreign'
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function readJson(file, what) {
|
|
70
|
-
if (!file) return { error: `usage: --${what} <file> is required` }
|
|
71
|
-
let raw
|
|
72
|
-
try {
|
|
73
|
-
raw = fs.readFileSync(file, 'utf8')
|
|
74
|
-
} catch {
|
|
75
|
-
return { error: `${what} not readable: ${file}` }
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
return { value: JSON.parse(raw) }
|
|
79
|
-
} catch {
|
|
80
|
-
return { error: `${what} is not valid JSON: ${file}` }
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const isObject = (value) => value != null && typeof value === 'object' && !Array.isArray(value)
|
|
85
|
-
const hasText = (value) => typeof value === 'string' && value.trim().length > 0
|
|
86
|
-
|
|
87
|
-
function matchingTerminalEvidence(terminalEvidence, candidate) {
|
|
88
|
-
if (!Array.isArray(terminalEvidence)) return null
|
|
89
|
-
const { leaseClosingStates, closeCauses } = RELEASE_STATE_CONTRACT.attempt
|
|
90
|
-
for (const evidence of terminalEvidence) {
|
|
91
|
-
if (!isObject(evidence)) continue
|
|
92
|
-
|
|
93
|
-
if (evidence.skill === 'resolve-release') {
|
|
94
|
-
const release = isObject(evidence.release) ? evidence.release : null
|
|
95
|
-
const attempt = release && isObject(release.attempt) ? release.attempt : null
|
|
96
|
-
const identity = release && isObject(release.candidateIdentity) ? release.candidateIdentity : null
|
|
97
|
-
if (
|
|
98
|
-
attempt && identity && hasText(attempt.id) && leaseClosingStates.includes(attempt.state) &&
|
|
99
|
-
attempt.closedBy === closeCauses[attempt.state] && hasText(attempt.evidence) &&
|
|
100
|
-
identity.tag === candidate.tag && identity.revision === candidate.revision
|
|
101
|
-
) {
|
|
102
|
-
return { attemptId: attempt.id, source: 'manifest' }
|
|
103
|
-
}
|
|
104
|
-
continue
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const bundle = validateBundle(evidence)
|
|
108
|
-
if (!bundle.valid) continue
|
|
109
|
-
const receipt = isObject(evidence.candidateReceipt) ? evidence.candidateReceipt : null
|
|
110
|
-
if (
|
|
111
|
-
hasText(evidence.attemptId) && receipt && leaseClosingStates.includes(evidence.terminalState) &&
|
|
112
|
-
evidence.closedBy === closeCauses[evidence.terminalState] &&
|
|
113
|
-
receipt.tag === candidate.tag && receipt.revision === candidate.revision
|
|
114
|
-
) {
|
|
115
|
-
return { attemptId: evidence.attemptId, source: 'evidence-bundle' }
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return null
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function classifyLeaseReport(leaseReport) {
|
|
122
|
-
if (leaseReport === null || leaseReport === undefined) return { state: 'missing' }
|
|
123
|
-
if (!Array.isArray(leaseReport)) return { state: 'unknown', reason: 'product lease report is not an array' }
|
|
124
|
-
if (leaseReport.length === 0) return { state: 'clear' }
|
|
125
|
-
|
|
126
|
-
let held = false
|
|
127
|
-
for (const entry of leaseReport) {
|
|
128
|
-
if (!isObject(entry) || !isObject(entry.lease) || !isObject(entry.productMatch)) {
|
|
129
|
-
return { state: 'unknown', reason: 'product lease report contains an unreadable matching entry' }
|
|
130
|
-
}
|
|
131
|
-
if (entry.productMatch.repo === 'unknown' || entry.productMatch.service === 'unknown') {
|
|
132
|
-
return { state: 'unknown', reason: 'product lease identity is unknown' }
|
|
133
|
-
}
|
|
134
|
-
if (entry.productMatch.repo === false || entry.productMatch.service === false) {
|
|
135
|
-
return { state: 'unknown', reason: 'product lease report contains an entry outside its declared scope' }
|
|
136
|
-
}
|
|
137
|
-
if (entry.lease.code === 2 || entry.lease.state === 'unknown' || entry.lease.state === 'ambiguous') {
|
|
138
|
-
return { state: 'unknown', reason: entry.lease.reason || 'matching product lease is ambiguous' }
|
|
139
|
-
}
|
|
140
|
-
if (entry.lease.code === 1 || entry.lease.state === 'held') held = true
|
|
141
|
-
else return { state: 'unknown', reason: 'product lease report contains an unrecognized lease state' }
|
|
142
|
-
}
|
|
143
|
-
return held
|
|
144
|
-
? { state: 'held', reason: 'a release attempt holds the matching product lease' }
|
|
145
|
-
: { state: 'unknown', reason: 'product lease report did not prove the matching product free' }
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function evaluateHygiene({
|
|
149
|
-
sha,
|
|
150
|
-
traffic,
|
|
151
|
-
leaseReport = null,
|
|
152
|
-
terminalEvidence = null,
|
|
153
|
-
incumbentRevision = null,
|
|
154
|
-
candidateTagPrefix = DEFAULT_CANDIDATE_TAG_PREFIX,
|
|
155
|
-
}) {
|
|
156
|
-
if (!isSha(sha)) {
|
|
157
|
-
return { decision: 'usage', refuse: [], sha, evidence: { error: 'e:unusable-shipped-sha' } }
|
|
158
|
-
}
|
|
159
|
-
if (!validCandidateTagPrefix(candidateTagPrefix)) {
|
|
160
|
-
return {
|
|
161
|
-
decision: 'usage',
|
|
162
|
-
refuse: [],
|
|
163
|
-
sha,
|
|
164
|
-
evidence: { error: 'e:invalid-candidate-tag-prefix', candidateTagPrefix },
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
const rows = normalizeTrafficPercents(traffic.rows)
|
|
168
|
-
const classified = rows.map((row) => ({
|
|
169
|
-
...row,
|
|
170
|
-
kind: classifyTag(row.tag, sha, candidateTagPrefix),
|
|
171
|
-
}))
|
|
172
|
-
const ours = classified.filter((row) => row.kind === 'ours')
|
|
173
|
-
const foreign = classified.filter((row) => row.kind === 'foreign')
|
|
174
|
-
|
|
175
|
-
let incumbent = incumbentRevision
|
|
176
|
-
if (!incumbent) {
|
|
177
|
-
const hundredRows = classified.filter((row) => row.percent === 100 && row.revision)
|
|
178
|
-
if (hundredRows.length === 1) incumbent = hundredRows[0].revision
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const lease = classifyLeaseReport(leaseReport)
|
|
182
|
-
const staleTags = []
|
|
183
|
-
const rivals = []
|
|
184
|
-
let needsLeaseReport = false
|
|
185
|
-
|
|
186
|
-
for (const row of foreign) {
|
|
187
|
-
if (
|
|
188
|
-
row.percent === null || !Number.isFinite(row.percent) ||
|
|
189
|
-
row.percent < 0 || row.percent > 100
|
|
190
|
-
) {
|
|
191
|
-
rivals.push({ ...candidateConflict(row, 'foreign candidate has ambiguous traffic') })
|
|
192
|
-
continue
|
|
193
|
-
}
|
|
194
|
-
if (row.percent !== 0) {
|
|
195
|
-
rivals.push({ ...candidateConflict(row, 'foreign candidate has non-zero traffic') })
|
|
196
|
-
continue
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const terminal = matchingTerminalEvidence(terminalEvidence, row)
|
|
200
|
-
if (!terminal) {
|
|
201
|
-
rivals.push({ ...candidateConflict(row, 'foreign zero-traffic candidate has no matching terminal release evidence') })
|
|
202
|
-
continue
|
|
203
|
-
}
|
|
204
|
-
if (lease.state === 'missing') {
|
|
205
|
-
needsLeaseReport = true
|
|
206
|
-
continue
|
|
207
|
-
}
|
|
208
|
-
if (lease.state === 'held' || lease.state === 'unknown') {
|
|
209
|
-
rivals.push({ ...candidateConflict(row, `foreign zero-traffic candidate is competing because the matching product lease is ${lease.state}: ${lease.reason}`) })
|
|
210
|
-
continue
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
staleTags.push({
|
|
214
|
-
tag: row.tag,
|
|
215
|
-
revision: row.revision,
|
|
216
|
-
percent: row.percent,
|
|
217
|
-
kind: row.revision && row.revision === incumbent ? 'stale-on-incumbent' : 'stale-foreign-zero',
|
|
218
|
-
terminalAttempt: terminal.attemptId,
|
|
219
|
-
})
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const shared = classified.filter((row) => row.kind === 'shared')
|
|
223
|
-
if (ours.length > 0) {
|
|
224
|
-
for (const row of shared) {
|
|
225
|
-
if (row.revision && row.revision !== ours[0].revision) {
|
|
226
|
-
rivals.push(candidateConflict(row, 'shared candidate tag resolves to a different revision'))
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
if (ours.length > 1) {
|
|
231
|
-
rivals.push({
|
|
232
|
-
tag: ours.map((row) => row.tag).join(', '),
|
|
233
|
-
revision: null,
|
|
234
|
-
percent: null,
|
|
235
|
-
reason: 'multiple entries claim this SHA-keyed candidate tag',
|
|
236
|
-
})
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const cleanupCommand = staleTags.length > 0
|
|
240
|
-
? `gcloud run services update-traffic <service> --region <region> --remove-tags ${staleTags.map((tag) => tag.tag).join(',')}`
|
|
241
|
-
: null
|
|
242
|
-
const evidence = {
|
|
243
|
-
trafficEntries: classified.length,
|
|
244
|
-
ourTagCount: ours.length,
|
|
245
|
-
staleTagCount: staleTags.length,
|
|
246
|
-
...(rivals.length > 0 ? { rivalCount: rivals.length } : {}),
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (rivals.length > 0) {
|
|
250
|
-
return {
|
|
251
|
-
decision: 'refuse', sha, incumbentRevision: incumbent || null, staleTags, rivals,
|
|
252
|
-
cleanupCommand: null, refuse: ['e:second-candidate-in-flight'], evidence,
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (needsLeaseReport) {
|
|
256
|
-
return {
|
|
257
|
-
decision: 'needs-lease-report', sha, incumbentRevision: incumbent || null, staleTags, rivals,
|
|
258
|
-
cleanupCommand: null, refuse: [], evidence: { ...evidence, foreignZeroWithoutLeaseReport: true },
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return {
|
|
262
|
-
decision: staleTags.length > 0 ? 'clean' : 'no-action',
|
|
263
|
-
sha,
|
|
264
|
-
incumbentRevision: incumbent || null,
|
|
265
|
-
staleTags,
|
|
266
|
-
rivals,
|
|
267
|
-
cleanupCommand,
|
|
268
|
-
refuse: [],
|
|
269
|
-
evidence,
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function candidateConflict(row, reason) {
|
|
274
|
-
return { tag: row.tag, revision: row.revision, percent: row.percent, reason }
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const USAGE = `candidate-hygiene — pre-locate cleanup of stale candidate-* traffic tags.
|
|
278
|
-
|
|
279
|
-
Reads the same normalized traffic capture as candidate identity. A foreign \`candidate-*\`
|
|
280
|
-
tag is stale only with proven zero traffic, matching terminal release evidence, and a valid
|
|
281
|
-
empty product-scoped lease report; every ambiguous or competing shape refuses.
|
|
282
|
-
|
|
283
|
-
Usage:
|
|
284
|
-
candidate-hygiene locate --sha <shipped> --traffic <file>
|
|
285
|
-
[--candidate-tag-prefix <prefix-ending-in-hyphen>]
|
|
286
|
-
[--lease-report <file>] [--terminal-evidence <file>]
|
|
287
|
-
[--service <svc>] [--region <r>]
|
|
288
|
-
|
|
289
|
-
Options:
|
|
290
|
-
--sha <shipped> the promoted SHA
|
|
291
|
-
--traffic <file> the flattened or JSON traffic capture (same form as locate)
|
|
292
|
-
--candidate-tag-prefix repository SHA-keyed namespace; defaults to candidate-
|
|
293
|
-
--lease-report <file> JSON output of run-state.mjs check-release-lease-scoped for the
|
|
294
|
-
same product; without it, a 0% foreign candidate cannot be
|
|
295
|
-
classified as stale and the script reports "needs-lease-report"
|
|
296
|
-
--terminal-evidence <file> JSON array of canonical resolve-release manifests or immutable
|
|
297
|
-
evidence bundles; exact candidate tag/revision must match a verified
|
|
298
|
-
terminal close
|
|
299
|
-
--service <svc> Cloud Run service name (used to render a concrete cleanup command)
|
|
300
|
-
--region <r> Cloud Run region (used to render a concrete cleanup command)
|
|
301
|
-
|
|
302
|
-
Exit codes:
|
|
303
|
-
0 clean or no-action — stale tags were found (or none) and no true rival
|
|
304
|
-
1 refuse — a true second release is present; \`refuse\` contains e:second-candidate-in-flight
|
|
305
|
-
2 usage / unreadable input
|
|
306
|
-
3 needs-lease-report — a 0% foreign candidate requires a lease report to classify; supply one and re-run
|
|
307
|
-
`
|
|
308
|
-
|
|
309
|
-
function parseArgs(argv) {
|
|
310
|
-
const opts = {}
|
|
311
|
-
const positional = []
|
|
312
|
-
for (let i = 0; i < argv.length; i++) {
|
|
313
|
-
const a = argv[i]
|
|
314
|
-
if (a === '--help' || a === '-h') opts.help = true
|
|
315
|
-
else if (a.startsWith('--')) opts[a.slice(2)] = argv[++i]
|
|
316
|
-
else positional.push(a)
|
|
317
|
-
}
|
|
318
|
-
return { opts, positional }
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export function main(argv) {
|
|
322
|
-
const { opts, positional } = parseArgs(argv)
|
|
323
|
-
const cmd = positional[0]
|
|
324
|
-
if (opts.help || cmd === 'help') {
|
|
325
|
-
console.log(USAGE)
|
|
326
|
-
return 0
|
|
327
|
-
}
|
|
328
|
-
if (cmd !== 'locate') {
|
|
329
|
-
const what = cmd ? `unknown command "${cmd}"` : 'no subcommand given'
|
|
330
|
-
console.error(`candidate-hygiene: ${what}. Use locate (see --help).`)
|
|
331
|
-
return 2
|
|
332
|
-
}
|
|
333
|
-
if (!opts.sha) {
|
|
334
|
-
console.error('candidate-hygiene: usage: locate requires --sha <shipped>')
|
|
335
|
-
return 2
|
|
336
|
-
}
|
|
337
|
-
if (
|
|
338
|
-
'candidate-tag-prefix' in opts &&
|
|
339
|
-
(typeof opts['candidate-tag-prefix'] !== 'string' ||
|
|
340
|
-
!opts['candidate-tag-prefix'] ||
|
|
341
|
-
opts['candidate-tag-prefix'].startsWith('--'))
|
|
342
|
-
) {
|
|
343
|
-
console.error('candidate-hygiene: --candidate-tag-prefix requires a value')
|
|
344
|
-
return 2
|
|
345
|
-
}
|
|
346
|
-
if (!opts.traffic) {
|
|
347
|
-
console.error('candidate-hygiene: usage: locate requires --traffic <file>')
|
|
348
|
-
return 2
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
let text
|
|
352
|
-
try {
|
|
353
|
-
text = fs.readFileSync(opts.traffic, 'utf8')
|
|
354
|
-
} catch {
|
|
355
|
-
console.error(`candidate-hygiene: traffic read not readable: ${opts.traffic}`)
|
|
356
|
-
return 2
|
|
357
|
-
}
|
|
358
|
-
const traffic = parseTraffic(text)
|
|
359
|
-
if (traffic.error) {
|
|
360
|
-
console.error(`candidate-hygiene: ${traffic.error}`)
|
|
361
|
-
return 2
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
let leaseReport = null
|
|
365
|
-
let terminalEvidence = null
|
|
366
|
-
if (opts['lease-report']) {
|
|
367
|
-
const read = readJson(opts['lease-report'], 'lease-report')
|
|
368
|
-
if (read.error) {
|
|
369
|
-
console.error(`candidate-hygiene: ${read.error}`)
|
|
370
|
-
return 2
|
|
371
|
-
}
|
|
372
|
-
leaseReport = read.value
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (opts['terminal-evidence']) {
|
|
376
|
-
const read = readJson(opts['terminal-evidence'], 'terminal-evidence')
|
|
377
|
-
if (read.error) {
|
|
378
|
-
console.error(`candidate-hygiene: ${read.error}`)
|
|
379
|
-
return 2
|
|
380
|
-
}
|
|
381
|
-
terminalEvidence = read.value
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const result = evaluateHygiene({
|
|
385
|
-
sha: opts.sha,
|
|
386
|
-
traffic,
|
|
387
|
-
leaseReport,
|
|
388
|
-
terminalEvidence,
|
|
389
|
-
incumbentRevision: opts['incumbent-revision'] || null,
|
|
390
|
-
candidateTagPrefix: opts['candidate-tag-prefix'] || DEFAULT_CANDIDATE_TAG_PREFIX,
|
|
391
|
-
})
|
|
392
|
-
|
|
393
|
-
// Replace the <service>/<region> placeholders with concrete values if the caller supplied them.
|
|
394
|
-
if (result.cleanupCommand && opts.service && opts.region) {
|
|
395
|
-
result.cleanupCommand = result.cleanupCommand.replace('<service>', opts.service).replace('<region>', opts.region)
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
console.log(JSON.stringify(result, null, 2))
|
|
399
|
-
if (result.decision === 'refuse') return 1
|
|
400
|
-
if (result.decision === 'usage') return 2
|
|
401
|
-
if (result.decision === 'needs-lease-report') return 3
|
|
402
|
-
return 0
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (isMainModule(import.meta.url)) {
|
|
406
|
-
process.exit(main(process.argv.slice(2)))
|
|
407
|
-
}
|