@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.
Files changed (197) 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 +5 -3
  13. package/skills/payment/tld-detection/SKILL.md +163 -0
  14. package/skills/payment/tld-detection/agents/openai.yaml +9 -0
  15. package/skills/payment/tld-detection/evals/evals.json +107 -0
  16. package/skills/payment/tld-detection/references/clean.md +51 -0
  17. package/skills/payment/tld-detection/references/clustering.md +219 -0
  18. package/skills/payment/tld-detection/references/decision.md +85 -0
  19. package/skills/payment/tld-detection/references/report-template.md +169 -0
  20. package/skills/payment/tld-detection/references/terminology.md +69 -0
  21. package/skills/payment/tld-detection/references/workflow.md +78 -0
  22. package/skills/payment/tld-detection/scripts/detect_ladders.py +390 -0
  23. package/skills/payment/tld-detection/tests/test_detect_ladders.py +132 -0
  24. package/skills/engineering/resolve-issues/SKILL.md +0 -168
  25. package/skills/engineering/resolve-issues/agents/openai.yaml +0 -9
  26. package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +0 -499
  27. package/skills/engineering/resolve-issues/references/breaker.md +0 -82
  28. package/skills/engineering/resolve-issues/references/deliverables.md +0 -27
  29. package/skills/engineering/resolve-issues/references/delivery.md +0 -108
  30. package/skills/engineering/resolve-issues/references/evidence-lane.md +0 -21
  31. package/skills/engineering/resolve-issues/references/exact-head-ci.md +0 -287
  32. package/skills/engineering/resolve-issues/references/fan-out.md +0 -33
  33. package/skills/engineering/resolve-issues/references/finalization.md +0 -68
  34. package/skills/engineering/resolve-issues/references/guarantees.md +0 -10
  35. package/skills/engineering/resolve-issues/references/high-risk.md +0 -29
  36. package/skills/engineering/resolve-issues/references/incidents/848/README.md +0 -156
  37. package/skills/engineering/resolve-issues/references/intake.md +0 -86
  38. package/skills/engineering/resolve-issues/references/integration-gate.md +0 -53
  39. package/skills/engineering/resolve-issues/references/interference.md +0 -87
  40. package/skills/engineering/resolve-issues/references/loop.md +0 -134
  41. package/skills/engineering/resolve-issues/references/model-catalog.md +0 -9
  42. package/skills/engineering/resolve-issues/references/postmortem.md +0 -27
  43. package/skills/engineering/resolve-issues/references/pre-flight-model-slots.md +0 -41
  44. package/skills/engineering/resolve-issues/references/pre-flight-recording-and-checkout.md +0 -48
  45. package/skills/engineering/resolve-issues/references/pre-flight.md +0 -41
  46. package/skills/engineering/resolve-issues/references/regression-checklist.md +0 -26
  47. package/skills/engineering/resolve-issues/references/run-state.md +0 -288
  48. package/skills/engineering/resolve-issues/references/sandboxed-testing.md +0 -48
  49. package/skills/engineering/resolve-issues/references/spawn-contract.md +0 -96
  50. package/skills/engineering/resolve-issues/references/terminal-evidence-journal.md +0 -40
  51. package/skills/engineering/resolve-issues/references/why.md +0 -653
  52. package/skills/engineering/resolve-issues/schemas/fix-round.schema.json +0 -49
  53. package/skills/engineering/resolve-issues/scripts/combine-and-verify.mjs +0 -721
  54. package/skills/engineering/resolve-issues/scripts/component-candidate.mjs +0 -962
  55. package/skills/engineering/resolve-issues/scripts/contract-revision.mjs +0 -220
  56. package/skills/engineering/resolve-issues/scripts/detect-delivery-mode.mjs +0 -420
  57. package/skills/engineering/resolve-issues/scripts/detect-target-branch.mjs +0 -256
  58. package/skills/engineering/resolve-issues/scripts/detect-workspace-mode.mjs +0 -168
  59. package/skills/engineering/resolve-issues/scripts/discover-models.mjs +0 -9
  60. package/skills/engineering/resolve-issues/scripts/doctrine.mjs +0 -62
  61. package/skills/engineering/resolve-issues/scripts/evidence-lifecycle-contract.mjs +0 -191
  62. package/skills/engineering/resolve-issues/scripts/exact-head-ci.mjs +0 -413
  63. package/skills/engineering/resolve-issues/scripts/exact-head-github-provider.mjs +0 -332
  64. package/skills/engineering/resolve-issues/scripts/finalize.mjs +0 -488
  65. package/skills/engineering/resolve-issues/scripts/fix-rounds.mjs +0 -3307
  66. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-circular-1001.json +0 -16
  67. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-valid-sequencing.json +0 -51
  68. package/skills/engineering/resolve-issues/scripts/fixtures/fmm-express-830-component-candidate.json +0 -17
  69. package/skills/engineering/resolve-issues/scripts/fixtures/head-check-set-1081.json +0 -166
  70. package/skills/engineering/resolve-issues/scripts/gate-value-series.mjs +0 -92
  71. package/skills/engineering/resolve-issues/scripts/guide-index.mjs +0 -73
  72. package/skills/engineering/resolve-issues/scripts/head-check-set.mjs +0 -159
  73. package/skills/engineering/resolve-issues/scripts/interference.mjs +0 -427
  74. package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +0 -9
  75. package/skills/engineering/resolve-issues/scripts/next-operations.mjs +0 -419
  76. package/skills/engineering/resolve-issues/scripts/postmortem.mjs +0 -909
  77. package/skills/engineering/resolve-issues/scripts/preflight-questions.mjs +0 -345
  78. package/skills/engineering/resolve-issues/scripts/reconcile-contained-unit-prs.mjs +0 -415
  79. package/skills/engineering/resolve-issues/scripts/release-state-contract.mjs +0 -697
  80. package/skills/engineering/resolve-issues/scripts/report.mjs +0 -494
  81. package/skills/engineering/resolve-issues/scripts/required-check-plan.mjs +0 -172
  82. package/skills/engineering/resolve-issues/scripts/round-metadata.mjs +0 -79
  83. package/skills/engineering/resolve-issues/scripts/run-state-review6-cases.mjs +0 -334
  84. package/skills/engineering/resolve-issues/scripts/run-state.mjs +0 -4792
  85. package/skills/engineering/resolve-issues/scripts/sandbox-selftest.mjs +0 -395
  86. package/skills/engineering/resolve-issues/scripts/spawn-contract.mjs +0 -290
  87. package/skills/engineering/resolve-issues/scripts/terminal-dispositions.mjs +0 -170
  88. package/skills/engineering/resolve-issues/scripts/terminal-evidence-journal.mjs +0 -293
  89. package/skills/engineering/resolve-issues/scripts/unit-kind.mjs +0 -197
  90. package/skills/engineering/resolve-issues/scripts/unit-lifecycle.mjs +0 -127
  91. package/skills/engineering/resolve-issues/scripts/watch-delivery.mjs +0 -893
  92. package/skills/engineering/resolve-issues/scripts/workspaces.mjs +0 -829
  93. package/skills/engineering/resolve-issues/workflows/independent-review.workflow.js +0 -290
  94. package/skills/engineering/resolve-issues/workflows/prior-art-scan.workflow.js +0 -80
  95. package/skills/engineering/resolve-issues/workflows/workflow-smoke.mjs +0 -102
  96. package/skills/engineering/resolve-release/LICENSE +0 -3
  97. package/skills/engineering/resolve-release/SKILL.md +0 -113
  98. package/skills/engineering/resolve-release/agents/openai.yaml +0 -9
  99. package/skills/engineering/resolve-release/references/assembly.md +0 -137
  100. package/skills/engineering/resolve-release/references/auto-when-green.md +0 -56
  101. package/skills/engineering/resolve-release/references/candidate.md +0 -167
  102. package/skills/engineering/resolve-release/references/exposure.md +0 -178
  103. package/skills/engineering/resolve-release/references/handoff.md +0 -24
  104. package/skills/engineering/resolve-release/references/postmortem.md +0 -230
  105. package/skills/engineering/resolve-release/references/preflight.md +0 -208
  106. package/skills/engineering/resolve-release/references/principles.md +0 -94
  107. package/skills/engineering/resolve-release/references/regression-checklist.md +0 -36
  108. package/skills/engineering/resolve-release/references/related-skills.md +0 -13
  109. package/skills/engineering/resolve-release/references/routing.md +0 -149
  110. package/skills/engineering/resolve-release/references/verified-sha-github-flow.md +0 -285
  111. package/skills/engineering/resolve-release/references/versioning.md +0 -202
  112. package/skills/engineering/resolve-release/references/why.md +0 -53
  113. package/skills/engineering/resolve-release/scripts/adapter-completion-artifact.mjs +0 -389
  114. package/skills/engineering/resolve-release/scripts/build-changes.mjs +0 -209
  115. package/skills/engineering/resolve-release/scripts/candidate-hygiene.mjs +0 -407
  116. package/skills/engineering/resolve-release/scripts/candidate-identity.mjs +0 -904
  117. package/skills/engineering/resolve-release/scripts/candidate-traffic.mjs +0 -81
  118. package/skills/engineering/resolve-release/scripts/checked-adapter-loader.mjs +0 -612
  119. package/skills/engineering/resolve-release/scripts/close-attempt.mjs +0 -135
  120. package/skills/engineering/resolve-release/scripts/closeout-release.mjs +0 -161
  121. package/skills/engineering/resolve-release/scripts/doctrine.mjs +0 -106
  122. package/skills/engineering/resolve-release/scripts/durable-processing.mjs +0 -522
  123. package/skills/engineering/resolve-release/scripts/ensure-target-green.mjs +0 -659
  124. package/skills/engineering/resolve-release/scripts/evidence-bundle.mjs +0 -1014
  125. package/skills/engineering/resolve-release/scripts/finalize-release.mjs +0 -526
  126. package/skills/engineering/resolve-release/scripts/fixtures/durable-processing-adapter.mjs +0 -169
  127. package/skills/engineering/resolve-release/scripts/green-gate.mjs +0 -599
  128. package/skills/engineering/resolve-release/scripts/isolated-adapter-evaluator.mjs +0 -752
  129. package/skills/engineering/resolve-release/scripts/metadata-pr-status.mjs +0 -56
  130. package/skills/engineering/resolve-release/scripts/metadata-sync.mjs +0 -1538
  131. package/skills/engineering/resolve-release/scripts/postmortem.mjs +0 -381
  132. package/skills/engineering/resolve-release/scripts/preflight-probes.mjs +0 -510
  133. package/skills/engineering/resolve-release/scripts/production-endpoints.mjs +0 -326
  134. package/skills/engineering/resolve-release/scripts/rc-circuit-breaker.mjs +0 -272
  135. package/skills/engineering/resolve-release/scripts/report.mjs +0 -417
  136. package/skills/engineering/resolve-release/scripts/reprobe-credentials.mjs +0 -114
  137. package/skills/engineering/resolve-release/scripts/revalidate-candidate.mjs +0 -238
  138. package/skills/engineering/resolve-release/scripts/review-packet.mjs +0 -503
  139. package/skills/engineering/resolve-release/scripts/rollback-floor.mjs +0 -263
  140. package/skills/engineering/resolve-release/scripts/version-assert.mjs +0 -339
  141. package/skills/engineering/resolve-release/scripts/version-postmortem.mjs +0 -485
  142. package/skills/engineering/resolve-release/scripts/version.mjs +0 -1199
  143. package/skills/engineering/resolve-release/scripts/watch-candidate-delivery.mjs +0 -449
  144. package/skills/engineering/resolve-release/vendor/ACORN-LICENSE +0 -21
  145. package/skills/engineering/resolve-release/vendor/README.md +0 -60
  146. package/skills/engineering/resolve-release/vendor/acorn.mjs +0 -6233
  147. package/skills/engineering/resolve-release/vendor/quickjs/core/LICENSE +0 -21
  148. package/skills/engineering/resolve-release/vendor/quickjs/core/README.md +0 -341
  149. package/skills/engineering/resolve-release/vendor/quickjs/core/README.template.md +0 -70
  150. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs +0 -2
  151. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs.map +0 -1
  152. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs +0 -7
  153. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs.map +0 -1
  154. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.mts +0 -2033
  155. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.ts +0 -2033
  156. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js +0 -7
  157. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js.map +0 -1
  158. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs +0 -2
  159. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs.map +0 -1
  160. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs +0 -2
  161. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs.map +0 -1
  162. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs +0 -2
  163. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs.map +0 -1
  164. package/skills/engineering/resolve-release/vendor/quickjs/core/package.json +0 -49
  165. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/LICENSE +0 -21
  166. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/README.md +0 -5
  167. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.mts +0 -549
  168. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.ts +0 -549
  169. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js +0 -2
  170. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js.map +0 -1
  171. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs +0 -2
  172. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs.map +0 -1
  173. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/package.json +0 -36
  174. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/LICENSE +0 -47
  175. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/README.md +0 -82
  176. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.d.ts +0 -11
  177. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.mjs +0 -22
  178. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cjs +0 -21
  179. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.cjs +0 -21
  180. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.d.ts +0 -11
  181. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.d.ts +0 -11
  182. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.mjs +0 -25
  183. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.wasm +0 -0
  184. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.mts +0 -85
  185. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.ts +0 -85
  186. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js +0 -2
  187. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js.map +0 -1
  188. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs +0 -2
  189. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs.map +0 -1
  190. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.mts +0 -20
  191. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.ts +0 -20
  192. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js +0 -2
  193. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js.map +0 -1
  194. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs +0 -2
  195. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs.map +0 -1
  196. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/package.json +0 -61
  197. /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
- }