@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,522 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import crypto from 'node:crypto'
4
- import fs from 'node:fs'
5
- import path from 'node:path'
6
- import { CheckedAdapterError, loadCheckedAdapter } from './checked-adapter-loader.mjs'
7
- import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
8
-
9
- export const ADAPTER_EXPORT = 'DURABLE_PROCESSING_ADAPTER'
10
- export const ADAPTER_SCHEMA_VERSION = 1
11
- export const RECEIPT_SCHEMA = 'resolve-release/durable-processing-receipt@1'
12
- export const PAUSE_ORDER = Object.freeze(['database-admission', 'worker-scheduling'])
13
- export const RESUME_ORDER = Object.freeze([...PAUSE_ORDER].reverse())
14
-
15
- const CLI_WRITER = Object.freeze({ id: 'resolve-release/durable-processing' })
16
-
17
- const USAGE = `durable-processing.mjs - drive a repository-declared durable-processing adapter
18
-
19
- Usage:
20
- durable-processing.mjs preflight --declaration <release.json>
21
- durable-processing.mjs status --declaration <release.json> [options]
22
- durable-processing.mjs pause --declaration <release.json> [options]
23
- durable-processing.mjs quiescence --declaration <release.json> [options]
24
- durable-processing.mjs resume --declaration <release.json> [options]
25
- durable-processing.mjs down --declaration <release.json> [options]
26
- durable-processing.mjs up --declaration <release.json> [options]
27
- durable-processing.mjs unwind-pre-shift --declaration <release.json> [options]
28
-
29
- The declaration must decide durableProcessing.present as true or false. A true
30
- declaration names an ES module exporting DURABLE_PROCESSING_ADAPTER. The adapter
31
- contains typed data and an execute(request) function; declarations contain no shell
32
- commands, credentials, timestamps, or platform-specific snippets. The adapter is a
33
- dependency-free strict-UTF-8 .mjs file no larger than 256 KiB. Execution receives
34
- only the declared config and isolated snapshot-backed readTextFile/writeTextFile operations;
35
- validated final write intents commit in the host only after receipt validation. The host stages
36
- same-directory final and original files, validates every target identity immediately before the
37
- first deterministic replacement, and restores replaced targets in reverse if a later operation
38
- fails. Rollback failure is reported as ambiguous with affected targets. This recoverable
39
- transaction cannot provide cross-file kernel atomicity or guaranteed recovery from process crash
40
- or power loss during the replacement window.`
41
-
42
- const isObject = (value) => value != null && typeof value === 'object' && !Array.isArray(value)
43
- const nonEmptyString = (value) => typeof value === 'string' && value.trim() !== ''
44
- const safeGeneration = (value) => Number.isSafeInteger(value) && value >= 0
45
- const isoTimestamp = (value) => nonEmptyString(value) && Number.isFinite(Date.parse(value))
46
- const exactArray = (actual, expected) => Array.isArray(actual)
47
- && actual.length === expected.length
48
- && actual.every((value, index) => value === expected[index])
49
-
50
- function written(state, fields = {}) {
51
- return {
52
- schema: RECEIPT_SCHEMA,
53
- state,
54
- ...fields,
55
- writer: CLI_WRITER,
56
- writtenAt: new Date().toISOString(),
57
- }
58
- }
59
-
60
- function refused(...failed) {
61
- return written('refused', { failed: [...new Set(failed)] })
62
- }
63
-
64
- function readJson(file, failedId) {
65
- try {
66
- const value = JSON.parse(fs.readFileSync(file, 'utf8'))
67
- return { value }
68
- } catch (error) {
69
- return { error: refused(failedId), cause: error }
70
- }
71
- }
72
-
73
- function validateAdapter(adapter) {
74
- const failed = []
75
- if (!isObject(adapter) || adapter.schemaVersion !== ADAPTER_SCHEMA_VERSION) {
76
- failed.push('e:invalid-adapter-schema-version')
77
- }
78
- if (!nonEmptyString(adapter?.writerId)) failed.push('e:invalid-writer-id')
79
- if (!exactArray(adapter?.pauseOrder, PAUSE_ORDER)) failed.push('e:invalid-pause-order')
80
- if (!exactArray(adapter?.resumeOrder, RESUME_ORDER)) failed.push('e:resume-not-exact-reverse')
81
- const quiescence = adapter?.quiescence
82
- if (
83
- !isObject(quiescence)
84
- || !nonEmptyString(quiescence.component)
85
- || !nonEmptyString(quiescence.blockerField)
86
- || typeof quiescence.readyValue !== 'number'
87
- || !Number.isSafeInteger(quiescence.timeoutMs)
88
- || quiescence.timeoutMs <= 0
89
- ) failed.push('e:invalid-quiescence')
90
- if (adapter?.evidenceSchema !== RECEIPT_SCHEMA) failed.push('e:invalid-evidence-schema')
91
- if (typeof adapter?.execute !== 'function') failed.push('e:adapter-not-executable')
92
- return failed
93
- }
94
-
95
- export async function loadDeclaration(file, { onFilesystemBoundary } = {}) {
96
- const read = readJson(file, 'e:declaration-unreadable')
97
- if (read.error) return read
98
- const declaration = read.value
99
- if (!isObject(declaration) || declaration.schemaVersion !== 1) {
100
- return { error: refused('e:invalid-declaration-schema') }
101
- }
102
- if (!nonEmptyString(declaration.repositoryRoot)) {
103
- return { error: refused('e:repository-root-undeclared') }
104
- }
105
- const durable = declaration.durableProcessing
106
- if (!isObject(durable) || typeof durable.present !== 'boolean') {
107
- return { error: refused('e:durable-processing-undecided') }
108
- }
109
- if (durable.config != null && !isObject(durable.config)) {
110
- return { error: refused('e:adapter-config-invalid') }
111
- }
112
- if (durable.present === false) return { declaration, durable }
113
- if (!nonEmptyString(durable.adapter)) return { error: refused('e:adapter-undeclared') }
114
-
115
- const declarationDir = path.dirname(path.resolve(file))
116
- const repositoryRoot = path.isAbsolute(declaration.repositoryRoot)
117
- ? path.resolve(declaration.repositoryRoot)
118
- : path.resolve(declarationDir, declaration.repositoryRoot)
119
- let loaded
120
- try {
121
- loaded = await loadCheckedAdapter({
122
- repositoryRoot,
123
- adapterPath: durable.adapter,
124
- onFilesystemBoundary,
125
- })
126
- } catch (error) {
127
- const code = error instanceof CheckedAdapterError ? error.code : 'e:adapter-unloadable'
128
- return { error: refused(code) }
129
- }
130
- const adapter = loaded.module[ADAPTER_EXPORT]
131
- const failed = validateAdapter(adapter)
132
- if (failed.length) return { error: refused(...failed) }
133
- return { declaration, durable, adapter, adapterPath: loaded.canonicalPath }
134
- }
135
-
136
- export async function preflight(file) {
137
- const loaded = await loadDeclaration(file)
138
- if (loaded.error) return loaded.error
139
- if (loaded.durable.present === false) return written('ready', { present: false })
140
- const { adapter, adapterPath } = loaded
141
- return written('ready', {
142
- present: true,
143
- adapter: adapterPath,
144
- adapterSchemaVersion: adapter.schemaVersion,
145
- pauseOrder: [...adapter.pauseOrder],
146
- resumeOrder: [...adapter.resumeOrder],
147
- quiescence: { ...adapter.quiescence },
148
- evidenceSchema: adapter.evidenceSchema,
149
- adapterWriter: { id: adapter.writerId },
150
- })
151
- }
152
-
153
- export function normalizePlatformOutput(output) {
154
- if (!isObject(output)) return { error: 'e:platform-output-malformed' }
155
- const hasPayload = Object.hasOwn(output, 'payload')
156
- const hasText = Object.hasOwn(output, 'text')
157
- if (hasPayload === hasText) return { error: 'e:platform-output-ambiguous' }
158
- if (hasPayload) {
159
- if (!isObject(output.payload)) return { error: 'e:platform-output-malformed' }
160
- return { receipt: output.payload }
161
- }
162
- if (!nonEmptyString(output.text)) return { error: 'e:platform-output-malformed' }
163
- try {
164
- const receipt = JSON.parse(output.text)
165
- return isObject(receipt) ? { receipt } : { error: 'e:platform-output-malformed' }
166
- } catch {
167
- return { error: 'e:platform-output-malformed' }
168
- }
169
- }
170
-
171
- export function validateReceipt(receipt, request, writerId) {
172
- const required = [
173
- 'schema', 'state', 'operation', 'component', 'requestId', 'writer', 'writtenAt',
174
- 'generation', 'fence', 'observed',
175
- ]
176
- if (!isObject(receipt) || required.some((field) => receipt[field] == null)) {
177
- return ['e:receipt-partial']
178
- }
179
- const failed = []
180
- if (receipt.schema !== RECEIPT_SCHEMA) failed.push('e:receipt-schema-mismatch')
181
- if (!['observed', 'applied', 'refused'].includes(receipt.state)) failed.push('e:receipt-state-invalid')
182
- if (receipt.operation !== request.operation) failed.push('e:receipt-operation-mismatch')
183
- if (receipt.component !== request.component) failed.push('e:receipt-component-mismatch')
184
- if (receipt.requestId !== request.requestId) failed.push('e:receipt-request-mismatch')
185
- if (!isObject(receipt.writer) || receipt.writer.id !== writerId) failed.push('e:receipt-writer-mismatch')
186
- if (!isoTimestamp(receipt.writtenAt)) failed.push('e:receipt-timestamp-invalid')
187
- if (!safeGeneration(receipt.generation)) failed.push('e:receipt-generation-invalid')
188
- if (!nonEmptyString(receipt.fence)) failed.push('e:receipt-fence-invalid')
189
- if (!isObject(receipt.observed)) failed.push('e:receipt-observation-invalid')
190
- if (receipt.state === 'refused' && (
191
- !Array.isArray(receipt.failed) || receipt.failed.length === 0 || receipt.failed.some((id) => !nonEmptyString(id))
192
- )) failed.push('e:receipt-partial')
193
- if (receipt.state !== 'refused') {
194
- if (['pause', 'resume'].includes(request.operation) && receipt.state !== 'applied') {
195
- failed.push('e:receipt-effect-unproven')
196
- }
197
- if (request.operation === 'pause' && receipt.observed?.paused !== true) failed.push('e:receipt-effect-unproven')
198
- if (request.operation === 'resume' && receipt.observed?.resumed !== true) failed.push('e:receipt-effect-unproven')
199
- if (['status', 'quiescence'].includes(request.operation) && receipt.state !== 'observed') {
200
- failed.push('e:receipt-observation-unproven')
201
- }
202
- if (request.operation === 'quiescence'
203
- && (!Number.isSafeInteger(receipt.observed?.blockers) || receipt.observed.blockers < 0)) {
204
- failed.push('e:receipt-observation-incomplete')
205
- }
206
- if (request.operation === 'status' && (
207
- typeof receipt.observed?.admissionPaused !== 'boolean'
208
- || typeof receipt.observed?.schedulingPaused !== 'boolean'
209
- || !Number.isSafeInteger(receipt.observed?.blockers)
210
- || receipt.observed.blockers < 0
211
- || !isObject(receipt.observed?.traffic)
212
- )) failed.push('e:receipt-observation-incomplete')
213
- }
214
- return failed
215
- }
216
-
217
- export async function executeOperation(file, operation, options) {
218
- const loaded = await loadDeclaration(file)
219
- if (loaded.error) return loaded.error
220
- if (loaded.durable.present === false) return refused('e:durable-processing-not-present')
221
- const request = Object.freeze({
222
- schemaVersion: 1,
223
- operation,
224
- component: options.component,
225
- requestId: options.requestId ?? crypto.randomUUID(),
226
- expectedGeneration: options.generation,
227
- expectedFence: options.fence,
228
- servingRevision: options.servingRevision,
229
- })
230
- if (!nonEmptyString(request.component) || !nonEmptyString(request.requestId)) {
231
- return refused('e:request-invalid')
232
- }
233
- let isolated
234
- try {
235
- isolated = await loaded.adapter.execute(
236
- request,
237
- Object.freeze({ ...(loaded.durable.config ?? {}) }),
238
- )
239
- } catch (error) {
240
- const code = typeof error?.code === 'string' && error.code.startsWith('e:adapter-')
241
- ? error.code
242
- : 'e:adapter-execution-failed'
243
- return refused(code)
244
- }
245
- if (!isObject(isolated) || !Object.hasOwn(isolated, 'output')
246
- || typeof isolated.commit !== 'function') {
247
- return refused('e:adapter-execution-failed')
248
- }
249
- const normalized = normalizePlatformOutput(isolated.output)
250
- if (normalized.error) return refused(normalized.error)
251
- const failed = validateReceipt(normalized.receipt, request, loaded.adapter.writerId)
252
- if (failed.length) return refused(...failed)
253
- try {
254
- isolated.commit()
255
- } catch (error) {
256
- const code = typeof error?.code === 'string' && error.code.startsWith('e:adapter-')
257
- ? error.code
258
- : 'e:adapter-file-changed'
259
- if (error?.ambiguousEffects === true || error?.committedEffects === true) {
260
- return written('refused', {
261
- failed: [code],
262
- effects: error.ambiguousEffects === true ? 'ambiguous' : 'committed',
263
- affectedTargets: Array.isArray(error.affectedTargets) ? [...error.affectedTargets] : [],
264
- })
265
- }
266
- return refused(code)
267
- }
268
- return normalized.receipt
269
- }
270
-
271
- function sequenceReceipt(state, fields) {
272
- return written(state, fields)
273
- }
274
-
275
- function isSuccess(receipt) {
276
- return receipt.state === 'observed' || receipt.state === 'applied'
277
- }
278
-
279
- function identityFrom(receipt) {
280
- return { generation: receipt.generation, fence: receipt.fence }
281
- }
282
-
283
- async function status(file) {
284
- return executeOperation(file, 'status', { component: 'durable-state' })
285
- }
286
-
287
- export async function down(file, options) {
288
- const release = readJson(options.releaseState, 'e:release-state-unreadable')
289
- if (release.error) return release.error
290
- if (release.value?.skill !== 'resolve-release' || release.value?.step !== 'expose') {
291
- return refused('e:expose-step-not-recorded')
292
- }
293
- const initial = await status(file)
294
- if (!isSuccess(initial)) return initial
295
- const receipts = []
296
- let identity = identityFrom(initial)
297
- for (const component of PAUSE_ORDER) {
298
- const receipt = await executeOperation(file, 'pause', { component, ...identity })
299
- receipts.push(receipt)
300
- if (!isSuccess(receipt)) return sequenceReceipt('blocked', {
301
- failed: receipt.failed,
302
- pausedAt: receipts.find((item) => isSuccess(item))?.writtenAt,
303
- pauseOrder: [...PAUSE_ORDER],
304
- receipts,
305
- proof: receipt,
306
- })
307
- identity = identityFrom(receipt)
308
- }
309
- const loaded = await loadDeclaration(file)
310
- if (loaded.error) return loaded.error
311
- const deadline = Date.now() + loaded.adapter.quiescence.timeoutMs
312
- let proof
313
- do {
314
- proof = await executeOperation(file, 'quiescence', {
315
- component: 'durable-state',
316
- ...identity,
317
- })
318
- receipts.push(proof)
319
- if (!isSuccess(proof)) return sequenceReceipt('blocked', {
320
- failed: proof.failed,
321
- pausedAt: receipts[0]?.writtenAt,
322
- pauseOrder: [...PAUSE_ORDER],
323
- receipts,
324
- })
325
- if (proof.observed.blockers === 0) break
326
- if (Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 25))
327
- } while (Date.now() < deadline)
328
- if (proof.observed.blockers !== 0) {
329
- return sequenceReceipt('blocked', {
330
- failed: ['e:quiescence-timeout'],
331
- pausedAt: receipts[0]?.writtenAt,
332
- pauseOrder: [...PAUSE_ORDER],
333
- receipts,
334
- proof,
335
- })
336
- }
337
- return sequenceReceipt('ready-to-shift', {
338
- pausedAt: receipts[0]?.writtenAt,
339
- pauseOrder: [...PAUSE_ORDER],
340
- receipts,
341
- proof,
342
- shiftFence: identityFrom(proof),
343
- })
344
- }
345
-
346
- function trafficMatches(traffic, expected) {
347
- return isObject(traffic)
348
- && traffic.incumbentRevision === expected.incumbentRevision
349
- && traffic.incumbentPercent === expected.incumbentPercent
350
- && traffic.candidateRevision === expected.candidateRevision
351
- && traffic.candidatePercent === expected.candidatePercent
352
- }
353
-
354
- async function resumeSequence(file, options, initialStatus) {
355
- const receipts = [initialStatus]
356
- let identity = identityFrom(initialStatus)
357
- if (
358
- !safeGeneration(options.generation)
359
- || !nonEmptyString(options.fence)
360
- || options.generation !== identity.generation
361
- || options.fence !== identity.fence
362
- ) {
363
- return sequenceReceipt('ambiguous', {
364
- failed: ['e:serving-generation-mismatch'],
365
- receipts,
366
- })
367
- }
368
- for (const component of RESUME_ORDER) {
369
- const receipt = await executeOperation(file, 'resume', {
370
- component,
371
- ...identity,
372
- servingRevision: options.servingRevision,
373
- })
374
- receipts.push(receipt)
375
- if (!isSuccess(receipt)) {
376
- return sequenceReceipt('ambiguous', { failed: receipt.failed, receipts })
377
- }
378
- identity = identityFrom(receipt)
379
- }
380
- const confirmation = await status(file)
381
- receipts.push(confirmation)
382
- if (
383
- !isSuccess(confirmation)
384
- || confirmation.observed.schedulingPaused !== false
385
- || confirmation.observed.admissionPaused !== false
386
- ) {
387
- return sequenceReceipt('ambiguous', {
388
- failed: ['e:processing-restoration-ambiguous'],
389
- receipts,
390
- })
391
- }
392
- return sequenceReceipt('restored', {
393
- resumedAt: receipts.find((receipt) => receipt.operation === 'resume')?.writtenAt,
394
- resumeOrder: [...RESUME_ORDER],
395
- receipts,
396
- servingRevisionAtResume: options.servingRevision,
397
- restorationFence: identityFrom(confirmation),
398
- })
399
- }
400
-
401
- export async function up(file, options) {
402
- const observed = await status(file)
403
- if (!isSuccess(observed)) return observed
404
- const traffic = observed.observed.traffic
405
- const identitiesDeclared = nonEmptyString(options.incumbentRevision)
406
- && nonEmptyString(options.candidateRevision)
407
- && options.incumbentRevision !== options.candidateRevision
408
- const namedCandidateServes = options.servingRevision === options.candidateRevision
409
- && trafficMatches(traffic, {
410
- incumbentRevision: options.incumbentRevision,
411
- incumbentPercent: 0,
412
- candidateRevision: options.candidateRevision,
413
- candidatePercent: 100,
414
- })
415
- const namedIncumbentServes = options.servingRevision === options.incumbentRevision
416
- && trafficMatches(traffic, {
417
- incumbentRevision: options.incumbentRevision,
418
- incumbentPercent: 100,
419
- candidateRevision: options.candidateRevision,
420
- candidatePercent: 0,
421
- })
422
- if (!identitiesDeclared || (!namedCandidateServes && !namedIncumbentServes)) {
423
- return sequenceReceipt('ambiguous', {
424
- failed: ['e:serving-revision-unproven'],
425
- trafficProof: traffic,
426
- receipts: [observed],
427
- })
428
- }
429
- const resumed = await resumeSequence(file, options, observed)
430
- return resumed.state === 'restored'
431
- ? {
432
- ...resumed,
433
- trafficProof: traffic,
434
- incumbentRevision: options.incumbentRevision,
435
- candidateRevision: options.candidateRevision,
436
- }
437
- : resumed
438
- }
439
-
440
- export async function unwindPreShift(file, options) {
441
- const observed = await status(file)
442
- if (!isSuccess(observed)) return observed
443
- const trafficProof = observed.observed.traffic
444
- const expected = {
445
- incumbentRevision: options.incumbentRevision,
446
- incumbentPercent: 100,
447
- candidateRevision: options.candidateRevision,
448
- candidatePercent: 0,
449
- }
450
- if (!trafficMatches(trafficProof, expected)) {
451
- return sequenceReceipt('ambiguous', {
452
- failed: ['e:traffic-split-ambiguous'],
453
- trafficProof,
454
- receipts: [observed],
455
- })
456
- }
457
- const resumed = await resumeSequence(file, {
458
- ...options,
459
- servingRevision: options.incumbentRevision,
460
- }, observed)
461
- if (resumed.state !== 'restored') return resumed
462
- return { ...resumed, state: 'unwound', trafficProof }
463
- }
464
-
465
- function parseArgs(argv) {
466
- const positional = []
467
- const options = {}
468
- const unknown = []
469
- for (let index = 0; index < argv.length; index++) {
470
- const value = argv[index]
471
- if (value === '--help' || value === '-h') options.help = true
472
- else if (value === '--declaration') options.declaration = argv[++index]
473
- else if (value === '--component') options.component = argv[++index]
474
- else if (value === '--generation') options.generation = Number(argv[++index])
475
- else if (value === '--fence') options.fence = argv[++index]
476
- else if (value === '--serving-revision') options.servingRevision = argv[++index]
477
- else if (value === '--request-id') options.requestId = argv[++index]
478
- else if (value === '--release-state') options.releaseState = argv[++index]
479
- else if (value === '--incumbent-revision') options.incumbentRevision = argv[++index]
480
- else if (value === '--candidate-revision') options.candidateRevision = argv[++index]
481
- else if (value.startsWith('--')) unknown.push(value)
482
- else positional.push(value)
483
- }
484
- return { positional, options, unknown }
485
- }
486
-
487
- export async function main(argv) {
488
- const { positional, options, unknown } = parseArgs(argv)
489
- if (options.help || positional[0] === 'help') {
490
- console.log(USAGE)
491
- return 0
492
- }
493
- if (unknown.length) {
494
- console.log(JSON.stringify(refused('e:unknown-option')))
495
- return 2
496
- }
497
- if (!nonEmptyString(options.declaration)) {
498
- console.log(JSON.stringify(refused('e:declaration-required')))
499
- return 2
500
- }
501
- const command = positional[0]
502
- const operations = ['status', 'pause', 'quiescence', 'resume']
503
- const sequences = ['down', 'up', 'unwind-pre-shift']
504
- if (command !== 'preflight' && !operations.includes(command) && !sequences.includes(command)) {
505
- console.log(JSON.stringify(refused('e:unknown-command')))
506
- return 2
507
- }
508
- let receipt
509
- if (command === 'preflight') receipt = await preflight(options.declaration)
510
- else if (operations.includes(command)) receipt = await executeOperation(options.declaration, command, options)
511
- else if (command === 'down') receipt = await down(options.declaration, options)
512
- else if (command === 'up') receipt = await up(options.declaration, options)
513
- else receipt = await unwindPreShift(options.declaration, options)
514
- console.log(JSON.stringify(receipt))
515
- return ['ready', 'observed', 'applied', 'ready-to-shift', 'restored', 'unwound'].includes(receipt.state)
516
- ? 0
517
- : 1
518
- }
519
-
520
- if (isMainModule(import.meta.url)) {
521
- main(process.argv.slice(2)).then((code) => { process.exitCode = code })
522
- }