@1aboveio/skills 0.19.3 → 0.20.2

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