@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.
- 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 +13 -7
- package/skills/payment/payment-analysis/references/chargebacks.md +43 -18
- package/skills/payment/payment-analysis/references/event-layers.md +4 -0
- package/skills/payment/payment-analysis/references/metrics.md +9 -7
- package/skills/payment/payment-analysis/references/report-template.md +3 -3
- package/skills/payment/payment-analysis/references/terminology.md +6 -1
- package/skills/payment/payment-analysis/references/visualization.md +1 -1
- 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,415 +0,0 @@
|
|
|
1
|
-
import { execFileSync, spawnSync } from 'node:child_process'
|
|
2
|
-
|
|
3
|
-
const SHA_RE = /^[0-9a-f]{7,64}$/i
|
|
4
|
-
const isObject = (value) => value != null && typeof value === 'object' && !Array.isArray(value)
|
|
5
|
-
const nonEmpty = (value) => typeof value === 'string' && value.trim().length > 0
|
|
6
|
-
const clone = (value) => structuredClone(value)
|
|
7
|
-
const same = (left, right) => JSON.stringify(left) === JSON.stringify(right)
|
|
8
|
-
const refNumber = (value) => {
|
|
9
|
-
if (Number.isInteger(value) && value > 0) return value
|
|
10
|
-
const match = String(value ?? '').match(/(?:^|#)(\d+)$/)
|
|
11
|
-
return match ? Number(match[1]) : null
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const COMPONENT_OUTCOMES = new Set([
|
|
15
|
-
'component-merged',
|
|
16
|
-
'component-unreadable',
|
|
17
|
-
'component-not-merged',
|
|
18
|
-
'component-merge-sha-mismatch',
|
|
19
|
-
])
|
|
20
|
-
const UNIT_OUTCOMES = new Set([
|
|
21
|
-
'closed-superseded',
|
|
22
|
-
'already-closed',
|
|
23
|
-
'already-merged',
|
|
24
|
-
'head-drift',
|
|
25
|
-
'not-contained',
|
|
26
|
-
'unit-unreadable',
|
|
27
|
-
'ancestry-unreadable',
|
|
28
|
-
'preclose-unreadable',
|
|
29
|
-
'preclose-head-drift',
|
|
30
|
-
'preclose-not-open',
|
|
31
|
-
'close-failed',
|
|
32
|
-
'blocked-component',
|
|
33
|
-
])
|
|
34
|
-
const TERMINAL_UNIT_OUTCOMES = new Set(['closed-superseded', 'already-closed', 'already-merged'])
|
|
35
|
-
const FAILED_UNIT_OUTCOMES = new Set([
|
|
36
|
-
'head-drift',
|
|
37
|
-
'not-contained',
|
|
38
|
-
'unit-unreadable',
|
|
39
|
-
'ancestry-unreadable',
|
|
40
|
-
'preclose-unreadable',
|
|
41
|
-
'preclose-head-drift',
|
|
42
|
-
'preclose-not-open',
|
|
43
|
-
'close-failed',
|
|
44
|
-
'blocked-component',
|
|
45
|
-
])
|
|
46
|
-
|
|
47
|
-
function normalizedPr(kind, requestedNumber, live) {
|
|
48
|
-
if (!isObject(live)) throw new Error(`${kind} #${requestedNumber} returned no structured observation`)
|
|
49
|
-
if (!Number.isInteger(live.number) || live.number <= 0) throw new Error(`${kind} #${requestedNumber} returned missing or malformed numeric identity`)
|
|
50
|
-
if (live.number !== requestedNumber) throw new Error(`${kind} #${requestedNumber} returned identity ${live.number}`)
|
|
51
|
-
if (!nonEmpty(live.state)) throw new Error(`${kind} #${requestedNumber} returned no state`)
|
|
52
|
-
return { ...clone(live), number: requestedNumber }
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function candidateUnitEvidence(component, generation) {
|
|
56
|
-
const candidate = component?.candidate
|
|
57
|
-
if (!isObject(candidate) || candidate.generation !== generation || candidate.frozen?.generation !== generation) {
|
|
58
|
-
throw new Error(`component ${component?.id ?? '(unknown)'} has no frozen candidate evidence for delivered generation ${generation}`)
|
|
59
|
-
}
|
|
60
|
-
if (!Array.isArray(candidate.frozen.unitEvidence)) {
|
|
61
|
-
throw new Error(`component ${component.id} frozen unitEvidence is missing`)
|
|
62
|
-
}
|
|
63
|
-
return candidate.frozen.unitEvidence
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function reconciliationTargets(manifest) {
|
|
67
|
-
if (manifest?.finalization !== undefined) throw new Error('contained unit PR reconciliation is pre-finalization only')
|
|
68
|
-
if (!nonEmpty(manifest?.repository)) throw new Error('repository is required for contained unit PR reconciliation')
|
|
69
|
-
if (!nonEmpty(manifest?.targetBranch)) throw new Error('targetBranch is required for contained unit PR reconciliation')
|
|
70
|
-
const units = Array.isArray(manifest?.units) ? manifest.units : []
|
|
71
|
-
const byId = new Map(units.map((unit) => [unit?.id, unit]))
|
|
72
|
-
const targets = []
|
|
73
|
-
|
|
74
|
-
for (const component of Array.isArray(manifest?.components) ? manifest.components : []) {
|
|
75
|
-
const delivery = component?.delivery
|
|
76
|
-
if (!isObject(delivery) || delivery.outcome !== 'merged') continue
|
|
77
|
-
const componentPr = refNumber(delivery.pr)
|
|
78
|
-
const publishedPr = refNumber(component.pr)
|
|
79
|
-
if (componentPr == null || publishedPr !== componentPr) {
|
|
80
|
-
throw new Error(`component ${component?.id ?? '(unknown)'} has no unambiguous published combined PR`)
|
|
81
|
-
}
|
|
82
|
-
if (!Number.isSafeInteger(delivery.generation) || delivery.generation < 1 || !SHA_RE.test(delivery.mergedSha ?? '')) {
|
|
83
|
-
throw new Error(`component ${component.id} has malformed delivered generation or merge SHA`)
|
|
84
|
-
}
|
|
85
|
-
const evidence = candidateUnitEvidence(component, delivery.generation)
|
|
86
|
-
const evidenceById = new Map(evidence.map((entry) => [entry?.unitId, entry]))
|
|
87
|
-
const linkedUnits = []
|
|
88
|
-
for (const unitId of Array.isArray(component.units) ? component.units : []) {
|
|
89
|
-
const unit = byId.get(unitId)
|
|
90
|
-
if (!unit || !same(unit.deliveryRef, { componentId: component.id, generation: delivery.generation })) continue
|
|
91
|
-
const unitPr = refNumber(unit.pr)
|
|
92
|
-
if (unitPr == null || unitPr === componentPr) continue
|
|
93
|
-
const reviewed = evidenceById.get(unitId)
|
|
94
|
-
if (!isObject(reviewed) || refNumber(reviewed.pr) !== unitPr) continue
|
|
95
|
-
if (!SHA_RE.test(reviewed.headSha ?? '') || reviewed.preliminaryReview?.verdict !== 'PASS'
|
|
96
|
-
|| reviewed.preliminaryReview?.headSha !== reviewed.headSha) {
|
|
97
|
-
throw new Error(`component ${component.id} unit ${unitId} has no exact reviewed unit head in publication evidence`)
|
|
98
|
-
}
|
|
99
|
-
linkedUnits.push({ unitId, pr: unitPr, reviewedHeadSha: reviewed.headSha })
|
|
100
|
-
}
|
|
101
|
-
if (linkedUnits.length) targets.push({ component, delivery, componentPr, linkedUnits })
|
|
102
|
-
}
|
|
103
|
-
return targets
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function supersessionComment({ componentPr, reviewedHeadSha, targetRef, mergedSha }) {
|
|
107
|
-
return `Closing this unit PR as superseded by combined PR #${componentPr}. The exact reviewed unit head ${reviewedHeadSha} is contained in freshly fetched ${targetRef}; the combined component merged as ${mergedSha}.`
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function componentRecord(target, fetchedTarget, at) {
|
|
111
|
-
return {
|
|
112
|
-
at,
|
|
113
|
-
target: {
|
|
114
|
-
branch: fetchedTarget.branch,
|
|
115
|
-
ref: fetchedTarget.ref,
|
|
116
|
-
headSha: fetchedTarget.headSha,
|
|
117
|
-
...(nonEmpty(fetchedTarget.fetchedAt) ? { fetchedAt: fetchedTarget.fetchedAt } : {}),
|
|
118
|
-
},
|
|
119
|
-
componentPr: target.componentPr,
|
|
120
|
-
generation: target.delivery.generation,
|
|
121
|
-
mergedSha: target.delivery.mergedSha,
|
|
122
|
-
component: null,
|
|
123
|
-
outcome: null,
|
|
124
|
-
units: target.linkedUnits.map((unit) => ({
|
|
125
|
-
...unit,
|
|
126
|
-
live: null,
|
|
127
|
-
headMatches: null,
|
|
128
|
-
ancestry: null,
|
|
129
|
-
outcome: 'blocked-component',
|
|
130
|
-
})),
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export async function reconcileContainedUnitPrs(manifest, boundary) {
|
|
135
|
-
const out = clone(manifest)
|
|
136
|
-
const targets = reconciliationTargets(out)
|
|
137
|
-
let fetchedTarget
|
|
138
|
-
try {
|
|
139
|
-
fetchedTarget = await boundary.fetchTarget(out.targetBranch)
|
|
140
|
-
} catch (error) {
|
|
141
|
-
return { ok: false, errors: [`fresh target fetch failed: ${error.message ?? error}`], manifest: out, target: null, reconciliations: [] }
|
|
142
|
-
}
|
|
143
|
-
if (!isObject(fetchedTarget) || fetchedTarget.branch !== out.targetBranch || !nonEmpty(fetchedTarget.ref) || !nonEmpty(fetchedTarget.headSha)) {
|
|
144
|
-
return { ok: false, errors: ['fresh target fetch returned no matching branch/ref/head SHA'], manifest: out, target: fetchedTarget ?? null, reconciliations: [] }
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const at = boundary.now()
|
|
148
|
-
const records = targets.map((target) => componentRecord(target, fetchedTarget, at))
|
|
149
|
-
const errors = []
|
|
150
|
-
|
|
151
|
-
// Phase one observes every component and unit head before any outward mutation.
|
|
152
|
-
for (let index = 0; index < targets.length; index++) {
|
|
153
|
-
const target = targets[index]
|
|
154
|
-
const record = records[index]
|
|
155
|
-
try {
|
|
156
|
-
record.component = normalizedPr('component PR', target.componentPr, await boundary.readPr(target.componentPr, out.repository))
|
|
157
|
-
} catch (error) {
|
|
158
|
-
record.outcome = 'component-unreadable'
|
|
159
|
-
errors.push(`component ${target.component.id} PR #${target.componentPr} could not be verified live: ${error.message ?? error}`)
|
|
160
|
-
continue
|
|
161
|
-
}
|
|
162
|
-
if (record.component.state !== 'MERGED' || record.component.merged !== true) {
|
|
163
|
-
record.outcome = 'component-not-merged'
|
|
164
|
-
errors.push(`component ${target.component.id} PR #${target.componentPr} is not live-verified merged`)
|
|
165
|
-
continue
|
|
166
|
-
}
|
|
167
|
-
if (record.component.mergeCommitSha !== target.delivery.mergedSha) {
|
|
168
|
-
record.outcome = 'component-merge-sha-mismatch'
|
|
169
|
-
errors.push(`component ${target.component.id} PR #${target.componentPr} merge SHA is ${record.component.mergeCommitSha ?? 'missing'}, expected ${target.delivery.mergedSha}`)
|
|
170
|
-
continue
|
|
171
|
-
}
|
|
172
|
-
record.outcome = 'component-merged'
|
|
173
|
-
|
|
174
|
-
for (const unit of record.units) {
|
|
175
|
-
try {
|
|
176
|
-
unit.live = normalizedPr('unit PR', unit.pr, await boundary.readPr(unit.pr, out.repository))
|
|
177
|
-
} catch (error) {
|
|
178
|
-
unit.outcome = 'unit-unreadable'
|
|
179
|
-
unit.error = error.message ?? String(error)
|
|
180
|
-
errors.push(`unit ${unit.unitId} PR #${unit.pr} could not be verified live: ${unit.error}`)
|
|
181
|
-
continue
|
|
182
|
-
}
|
|
183
|
-
unit.headMatches = unit.live.headSha === unit.reviewedHeadSha
|
|
184
|
-
if (unit.live.state === 'MERGED' || unit.live.merged === true) {
|
|
185
|
-
unit.outcome = 'already-merged'
|
|
186
|
-
continue
|
|
187
|
-
}
|
|
188
|
-
if (unit.live.state === 'CLOSED') {
|
|
189
|
-
unit.outcome = 'already-closed'
|
|
190
|
-
continue
|
|
191
|
-
}
|
|
192
|
-
if (unit.live.state !== 'OPEN') {
|
|
193
|
-
unit.outcome = 'unit-unreadable'
|
|
194
|
-
unit.error = `unsupported live state ${unit.live.state}`
|
|
195
|
-
errors.push(`unit ${unit.unitId} PR #${unit.pr} returned ${unit.error}`)
|
|
196
|
-
continue
|
|
197
|
-
}
|
|
198
|
-
if (!unit.headMatches) {
|
|
199
|
-
unit.outcome = 'head-drift'
|
|
200
|
-
errors.push(`unit ${unit.unitId} PR #${unit.pr} head ${unit.live.headSha ?? 'missing'} does not equal reviewed head ${unit.reviewedHeadSha}`)
|
|
201
|
-
continue
|
|
202
|
-
}
|
|
203
|
-
let contained
|
|
204
|
-
try {
|
|
205
|
-
contained = await boundary.isAncestor(unit.reviewedHeadSha, fetchedTarget.ref)
|
|
206
|
-
} catch (error) {
|
|
207
|
-
unit.outcome = 'ancestry-unreadable'
|
|
208
|
-
unit.error = error.message ?? String(error)
|
|
209
|
-
errors.push(`unit ${unit.unitId} ancestry could not be verified: ${unit.error}`)
|
|
210
|
-
continue
|
|
211
|
-
}
|
|
212
|
-
unit.ancestry = { targetRef: fetchedTarget.ref, contained: contained === true }
|
|
213
|
-
if (contained !== true) {
|
|
214
|
-
unit.outcome = 'not-contained'
|
|
215
|
-
errors.push(`unit ${unit.unitId} reviewed head ${unit.reviewedHeadSha} is not an ancestor of freshly fetched ${fetchedTarget.ref}`)
|
|
216
|
-
continue
|
|
217
|
-
}
|
|
218
|
-
unit.outcome = 'pending-close'
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Phase two re-reads each eligible head immediately before the close mutation.
|
|
223
|
-
for (let index = 0; index < targets.length; index++) {
|
|
224
|
-
const target = targets[index]
|
|
225
|
-
const record = records[index]
|
|
226
|
-
if (record.outcome !== 'component-merged') continue
|
|
227
|
-
for (const unit of record.units.filter((entry) => entry.outcome === 'pending-close')) {
|
|
228
|
-
let preclose
|
|
229
|
-
try {
|
|
230
|
-
preclose = normalizedPr('unit PR', unit.pr, await boundary.readPr(unit.pr, out.repository))
|
|
231
|
-
} catch (error) {
|
|
232
|
-
unit.outcome = 'preclose-unreadable'
|
|
233
|
-
unit.error = error.message ?? String(error)
|
|
234
|
-
errors.push(`unit ${unit.unitId} PR #${unit.pr} pre-close read failed: ${unit.error}`)
|
|
235
|
-
continue
|
|
236
|
-
}
|
|
237
|
-
if (preclose.headSha !== unit.reviewedHeadSha) {
|
|
238
|
-
unit.outcome = 'preclose-head-drift'
|
|
239
|
-
unit.live = preclose
|
|
240
|
-
unit.headMatches = false
|
|
241
|
-
errors.push(`unit ${unit.unitId} PR #${unit.pr} moved to ${preclose.headSha ?? 'missing'} before close`)
|
|
242
|
-
continue
|
|
243
|
-
}
|
|
244
|
-
if (preclose.state !== 'OPEN' || preclose.merged === true) {
|
|
245
|
-
unit.outcome = preclose.state === 'MERGED' || preclose.merged === true ? 'already-merged' : preclose.state === 'CLOSED' ? 'already-closed' : 'preclose-not-open'
|
|
246
|
-
unit.live = preclose
|
|
247
|
-
if (unit.outcome === 'preclose-not-open') errors.push(`unit ${unit.unitId} PR #${unit.pr} is ${preclose.state}, not OPEN, before close`)
|
|
248
|
-
continue
|
|
249
|
-
}
|
|
250
|
-
unit.comment = supersessionComment({
|
|
251
|
-
componentPr: target.componentPr,
|
|
252
|
-
reviewedHeadSha: unit.reviewedHeadSha,
|
|
253
|
-
targetRef: fetchedTarget.ref,
|
|
254
|
-
mergedSha: target.delivery.mergedSha,
|
|
255
|
-
})
|
|
256
|
-
try {
|
|
257
|
-
const closed = normalizedPr('closed unit PR', unit.pr, await boundary.closePr(unit.pr, out.repository, unit.comment))
|
|
258
|
-
if (!['CLOSED', 'MERGED'].includes(closed.state)) throw new Error(`close returned state ${closed.state}`)
|
|
259
|
-
unit.live = closed
|
|
260
|
-
unit.outcome = closed.state === 'MERGED' || closed.merged === true ? 'already-merged' : 'closed-superseded'
|
|
261
|
-
} catch (error) {
|
|
262
|
-
unit.outcome = 'close-failed'
|
|
263
|
-
unit.error = error.message ?? String(error)
|
|
264
|
-
errors.push(`unit ${unit.unitId} PR #${unit.pr} close failed: ${unit.error}`)
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
for (let index = 0; index < targets.length; index++) {
|
|
270
|
-
const delivery = targets[index].component.delivery
|
|
271
|
-
delivery.reconciliations = [...(Array.isArray(delivery.reconciliations) ? delivery.reconciliations : []), records[index]]
|
|
272
|
-
}
|
|
273
|
-
const recordErrors = reconciliationErrors(out)
|
|
274
|
-
if (recordErrors.length) throw new Error(`invalid reconciliation result: ${recordErrors[0]}`)
|
|
275
|
-
return {
|
|
276
|
-
ok: errors.length === 0 && records.every((record) => record.outcome === 'component-merged'
|
|
277
|
-
&& record.units.every((unit) => !FAILED_UNIT_OUTCOMES.has(unit.outcome))),
|
|
278
|
-
errors,
|
|
279
|
-
manifest: out,
|
|
280
|
-
target: fetchedTarget,
|
|
281
|
-
reconciliations: records,
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export function unitPrReconciliationStatus(manifest) {
|
|
286
|
-
let targets
|
|
287
|
-
try {
|
|
288
|
-
targets = reconciliationTargets(manifest)
|
|
289
|
-
} catch (error) {
|
|
290
|
-
return { state: 'invalid', required: true, complete: false, reason: error.message ?? String(error), components: [] }
|
|
291
|
-
}
|
|
292
|
-
if (!targets.length) return { state: 'not-required', required: false, complete: true, reason: 'no delivered combined component has explicitly published, distinct unit PRs', components: [] }
|
|
293
|
-
const components = targets.map((target) => {
|
|
294
|
-
const latest = Array.isArray(target.delivery.reconciliations) ? target.delivery.reconciliations.at(-1) : null
|
|
295
|
-
const observed = new Map((Array.isArray(latest?.units) ? latest.units : []).map((unit) => [unit?.unitId, unit]))
|
|
296
|
-
const pendingUnits = target.linkedUnits
|
|
297
|
-
.filter((unit) => !TERMINAL_UNIT_OUTCOMES.has(observed.get(unit.unitId)?.outcome))
|
|
298
|
-
.map((unit) => unit.unitId)
|
|
299
|
-
return { componentId: target.component.id, componentPr: target.componentPr, pendingUnits, complete: pendingUnits.length === 0 }
|
|
300
|
-
})
|
|
301
|
-
const complete = components.every((component) => component.complete)
|
|
302
|
-
return {
|
|
303
|
-
state: complete ? 'complete' : 'required',
|
|
304
|
-
required: true,
|
|
305
|
-
complete,
|
|
306
|
-
reason: complete ? 'all linked unit PRs have terminal live observations' : 'delivered combined components still have unreconciled unit PRs',
|
|
307
|
-
components,
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
export function reconciliationErrors(manifest) {
|
|
312
|
-
const errors = []
|
|
313
|
-
const units = new Map((Array.isArray(manifest?.units) ? manifest.units : []).map((unit) => [unit?.id, unit]))
|
|
314
|
-
for (const [componentIndex, component] of (Array.isArray(manifest?.components) ? manifest.components : []).entries()) {
|
|
315
|
-
const reconciliations = component?.delivery?.reconciliations
|
|
316
|
-
if (reconciliations === undefined) continue
|
|
317
|
-
const label = `components[${componentIndex}] (${component?.id ?? 'unknown'}).delivery.reconciliations`
|
|
318
|
-
if (!Array.isArray(reconciliations)) { errors.push(`${label} must be an array`); continue }
|
|
319
|
-
for (const [recordIndex, record] of reconciliations.entries()) {
|
|
320
|
-
const at = `${label}[${recordIndex}]`
|
|
321
|
-
if (!isObject(record)) { errors.push(`${at} must be an object`); continue }
|
|
322
|
-
const unknown = Object.keys(record).filter((key) => !['at', 'target', 'componentPr', 'generation', 'mergedSha', 'component', 'outcome', 'units'].includes(key))
|
|
323
|
-
if (unknown.length) errors.push(`${at} has unknown property ${unknown[0]}`)
|
|
324
|
-
if (!nonEmpty(record.at) || !nonEmpty(record.target?.ref) || !SHA_RE.test(record.target?.headSha ?? '')) errors.push(`${at} requires timestamp and fresh target ref/head`)
|
|
325
|
-
if (record.target?.branch !== manifest.targetBranch || record.target?.ref !== `origin/${manifest.targetBranch}`) errors.push(`${at}.target must equal the freshly fetched origin targetBranch`)
|
|
326
|
-
if (record.componentPr !== refNumber(component.delivery?.pr) || record.generation !== component.delivery?.generation || record.mergedSha !== component.delivery?.mergedSha) {
|
|
327
|
-
errors.push(`${at} must bind the canonical component delivery PR, generation, and merge SHA`)
|
|
328
|
-
}
|
|
329
|
-
if (!COMPONENT_OUTCOMES.has(record.outcome)) errors.push(`${at}.outcome is invalid`)
|
|
330
|
-
if (record.component != null && record.component.number !== record.componentPr) errors.push(`${at}.component identity is stale`)
|
|
331
|
-
if (record.outcome === 'component-merged'
|
|
332
|
-
&& (record.component?.state !== 'MERGED' || record.component?.merged !== true || record.component?.mergeCommitSha !== record.mergedSha)) {
|
|
333
|
-
errors.push(`${at}.component does not prove the recorded merged outcome at the canonical merge SHA`)
|
|
334
|
-
}
|
|
335
|
-
if (!Array.isArray(record.units)) { errors.push(`${at}.units must be an array`); continue }
|
|
336
|
-
const publishedEvidence = new Map((Array.isArray(component.candidate?.frozen?.unitEvidence) ? component.candidate.frozen.unitEvidence : [])
|
|
337
|
-
.map((entry) => [entry?.unitId, entry]))
|
|
338
|
-
const seen = new Set()
|
|
339
|
-
for (const [unitIndex, row] of record.units.entries()) {
|
|
340
|
-
const unitAt = `${at}.units[${unitIndex}]`
|
|
341
|
-
if (!isObject(row)) { errors.push(`${unitAt} must be an object`); continue }
|
|
342
|
-
const unknownUnit = Object.keys(row).filter((key) => !['unitId', 'pr', 'reviewedHeadSha', 'live', 'headMatches', 'ancestry', 'outcome', 'comment', 'error'].includes(key))
|
|
343
|
-
if (unknownUnit.length) errors.push(`${unitAt} has unknown property ${unknownUnit[0]}`)
|
|
344
|
-
if (!nonEmpty(row.unitId) || seen.has(row.unitId)) errors.push(`${unitAt}.unitId must be non-empty and unique`)
|
|
345
|
-
seen.add(row.unitId)
|
|
346
|
-
if (!UNIT_OUTCOMES.has(row.outcome)) errors.push(`${unitAt}.outcome is invalid`)
|
|
347
|
-
if (!SHA_RE.test(row.reviewedHeadSha ?? '') || refNumber(row.pr) == null) errors.push(`${unitAt} requires unit PR and reviewed head`)
|
|
348
|
-
const unit = units.get(row.unitId)
|
|
349
|
-
if (!unit || !same(unit.deliveryRef, { componentId: component.id, generation: record.generation })) errors.push(`${unitAt} is not linked to this delivered component generation`)
|
|
350
|
-
const published = publishedEvidence.get(row.unitId)
|
|
351
|
-
if (!published || refNumber(published.pr) !== row.pr || published.headSha !== row.reviewedHeadSha
|
|
352
|
-
|| published.preliminaryReview?.verdict !== 'PASS' || published.preliminaryReview?.headSha !== row.reviewedHeadSha) {
|
|
353
|
-
errors.push(`${unitAt} does not match the component's exact published reviewed-unit evidence`)
|
|
354
|
-
}
|
|
355
|
-
if (row.live != null && row.live.number !== row.pr) errors.push(`${unitAt}.live identity is stale`)
|
|
356
|
-
if (row.ancestry != null && (row.ancestry.targetRef !== record.target?.ref || typeof row.ancestry.contained !== 'boolean')) errors.push(`${unitAt}.ancestry is stale`)
|
|
357
|
-
if (row.outcome === 'closed-superseded'
|
|
358
|
-
&& (row.live?.state !== 'CLOSED' || row.headMatches !== true || row.ancestry?.contained !== true)) {
|
|
359
|
-
errors.push(`${unitAt} closed-superseded outcome lacks exact-head, ancestry, or terminal-state proof`)
|
|
360
|
-
}
|
|
361
|
-
if (row.outcome === 'already-closed' && row.live?.state !== 'CLOSED') errors.push(`${unitAt} already-closed outcome lacks CLOSED state`)
|
|
362
|
-
if (row.outcome === 'already-merged' && row.live?.state !== 'MERGED' && row.live?.merged !== true) errors.push(`${unitAt} already-merged outcome lacks MERGED state`)
|
|
363
|
-
if (row.outcome === 'head-drift' && row.headMatches !== false) errors.push(`${unitAt} head-drift outcome lacks a mismatched live head`)
|
|
364
|
-
if (row.outcome === 'not-contained' && row.ancestry?.contained !== false) errors.push(`${unitAt} not-contained outcome lacks negative fresh-target ancestry`)
|
|
365
|
-
if (row.outcome === 'closed-superseded') {
|
|
366
|
-
for (const expected of [`#${record.componentPr}`, row.reviewedHeadSha, record.target.ref, record.mergedSha]) {
|
|
367
|
-
if (!nonEmpty(row.comment) || !row.comment.includes(expected)) errors.push(`${unitAt}.comment must name combined PR, reviewed head, target ref, and merge SHA`)
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return errors
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function runJson(command, args, cwd) {
|
|
377
|
-
return JSON.parse(execFileSync(command, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }))
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export function createLiveReconciliationBoundary({ cwd = process.cwd(), now = () => new Date().toISOString() } = {}) {
|
|
381
|
-
const readPr = async (id, repository) => {
|
|
382
|
-
if (!Number.isInteger(id) || !nonEmpty(repository)) throw new Error('PR number and repository are required')
|
|
383
|
-
const view = runJson('gh', ['pr', 'view', String(id), '--repo', repository, '--json', 'number,state,mergedAt,mergeCommit,url,headRefOid'], cwd)
|
|
384
|
-
return {
|
|
385
|
-
number: view.number,
|
|
386
|
-
state: view.state,
|
|
387
|
-
merged: view.state === 'MERGED' || nonEmpty(view.mergedAt),
|
|
388
|
-
mergedAt: view.mergedAt ?? null,
|
|
389
|
-
mergeCommitSha: view.mergeCommit?.oid ?? null,
|
|
390
|
-
headSha: view.headRefOid ?? null,
|
|
391
|
-
url: view.url,
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return {
|
|
395
|
-
now,
|
|
396
|
-
fetchTarget: async (branch) => {
|
|
397
|
-
if (!nonEmpty(branch)) throw new Error('targetBranch is not recorded')
|
|
398
|
-
execFileSync('git', ['fetch', 'origin', branch], { cwd, stdio: ['ignore', 'ignore', 'pipe'] })
|
|
399
|
-
const ref = `origin/${branch}`
|
|
400
|
-
const headSha = execFileSync('git', ['rev-parse', ref], { cwd, encoding: 'utf8' }).trim()
|
|
401
|
-
return { remote: 'origin', branch, ref, headSha, fetchedAt: now() }
|
|
402
|
-
},
|
|
403
|
-
isAncestor: async (headSha, ref) => {
|
|
404
|
-
const result = spawnSync('git', ['merge-base', '--is-ancestor', headSha, ref], { cwd, encoding: 'utf8' })
|
|
405
|
-
if (result.status === 0) return true
|
|
406
|
-
if (result.status === 1) return false
|
|
407
|
-
throw new Error(result.stderr?.trim() || `git merge-base --is-ancestor exited ${result.status ?? 'without status'}`)
|
|
408
|
-
},
|
|
409
|
-
readPr,
|
|
410
|
-
closePr: async (id, repository, comment) => {
|
|
411
|
-
execFileSync('gh', ['pr', 'close', String(id), '--repo', repository, '--comment', comment], { cwd, stdio: ['ignore', 'ignore', 'pipe'] })
|
|
412
|
-
return readPr(id, repository)
|
|
413
|
-
},
|
|
414
|
-
}
|
|
415
|
-
}
|