@guiho/runx 0.2.7 → 0.4.0

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 (86) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/DOCS.md +127 -128
  3. package/README.md +56 -75
  4. package/devops/build-binaries.ts +44 -62
  5. package/devops/devops.xdocs.md +8 -5
  6. package/devops/install.ps1 +182 -152
  7. package/devops/install.sh +197 -262
  8. package/devops/installers.spec.ts +31 -0
  9. package/devops/verify-release-assets.ts +17 -0
  10. package/docs/docs.xdocs.md +3 -1
  11. package/docs/plans/plans.xdocs.md +7 -0
  12. package/docs/plans/rfc-0034-cli-compliance-migration.md +586 -0
  13. package/docs/plans/upgrade-reliability-implementation.md +95 -0
  14. package/docs/reviews/implementation/implementation.xdocs.md +2 -0
  15. package/docs/reviews/implementation/rfc-0034-cli-compliance-migration-review.md +59 -0
  16. package/docs/reviews/plans/plans.xdocs.md +6 -0
  17. package/docs/reviews/plans/rfc-0034-cli-compliance-migration-review.md +80 -0
  18. package/docs/reviews/plans/upgrade-reliability-implementation-review.md +65 -0
  19. package/docs/superpowers/specs/2026-07-15-upgrade-reliability-design.md +662 -0
  20. package/docs/superpowers/specs/specs.xdocs.md +24 -0
  21. package/docs/superpowers/superpowers.xdocs.md +21 -0
  22. package/docs/todo/rfc-0034-cli-compliance-migration-implementation.md +58 -0
  23. package/docs/todo/rfc-0034-cli-compliance-migration.md +151 -0
  24. package/docs/todo/todo.xdocs.md +6 -2
  25. package/docs/validation/rfc-0034-cli-compliance-migration.md +67 -0
  26. package/docs/validation/upgrade-reliability.md +124 -0
  27. package/docs/validation/validation.xdocs.md +5 -0
  28. package/library/agents.d.ts +28 -4
  29. package/library/agents.d.ts.map +1 -1
  30. package/library/agents.js +143 -41
  31. package/library/cli.d.ts.map +1 -1
  32. package/library/cli.js +296 -334
  33. package/library/configuration.d.ts +57 -0
  34. package/library/configuration.d.ts.map +1 -0
  35. package/library/configuration.js +111 -0
  36. package/library/embedded-resources.d.ts +6 -1
  37. package/library/embedded-resources.d.ts.map +1 -1
  38. package/library/embedded-resources.js +10 -4
  39. package/library/executor.d.ts +5 -1
  40. package/library/executor.d.ts.map +1 -1
  41. package/library/executor.js +10 -10
  42. package/library/help.d.ts +8 -4
  43. package/library/help.d.ts.map +1 -1
  44. package/library/help.js +70 -46
  45. package/library/init.d.ts +8 -27
  46. package/library/init.d.ts.map +1 -1
  47. package/library/init.js +37 -159
  48. package/library/manifest.d.ts +4 -43
  49. package/library/manifest.d.ts.map +1 -1
  50. package/library/manifest.js +4 -125
  51. package/library/path-utils.d.ts +13 -0
  52. package/library/path-utils.d.ts.map +1 -0
  53. package/library/path-utils.js +82 -0
  54. package/library/recovery.d.ts +7 -0
  55. package/library/recovery.d.ts.map +1 -0
  56. package/library/recovery.js +23 -0
  57. package/library/release-catalog.d.ts +32 -0
  58. package/library/release-catalog.d.ts.map +1 -0
  59. package/library/release-catalog.js +124 -0
  60. package/library/self-management.d.ts +24 -4
  61. package/library/self-management.d.ts.map +1 -1
  62. package/library/self-management.js +279 -99
  63. package/library/storage.d.ts +13 -0
  64. package/library/storage.d.ts.map +1 -0
  65. package/library/storage.js +38 -0
  66. package/library/types.d.ts +11 -16
  67. package/library/types.d.ts.map +1 -1
  68. package/library/types.js +3 -0
  69. package/library/update-cache.d.ts +21 -0
  70. package/library/update-cache.d.ts.map +1 -0
  71. package/library/update-cache.js +68 -0
  72. package/library/upgrade-reporting.d.ts +11 -0
  73. package/library/upgrade-reporting.d.ts.map +1 -0
  74. package/library/upgrade-reporting.js +67 -0
  75. package/library/upgrade-types.d.ts +72 -0
  76. package/library/upgrade-types.d.ts.map +1 -0
  77. package/library/upgrade-types.js +3 -0
  78. package/package.json +6 -3
  79. package/scripts/runx-bin.mjs +49 -0
  80. package/scripts/runx-bin.spec.ts +62 -0
  81. package/scripts/scripts.xdocs.md +3 -3
  82. package/skills/guiho-s-runx/SKILL.md +64 -59
  83. package/skills/guiho-s-runx/guiho-s-runx.xdocs.md +7 -4
  84. package/skills/skills.xdocs.md +1 -1
  85. package/docs/todo/implement-runx-alpha.md +0 -36
  86. package/scripts/runx-bin.ts +0 -24
@@ -0,0 +1,662 @@
1
+ ---
2
+ name: RunX Upgrade Reliability Design
3
+ purpose: Define the implementation-ready reliability contract for RunX self-upgrade, version listing, and recovery installation.
4
+ description: Specifies release discovery, visible progress, transactional replacement, rollback, recovery commands, installer hardening, tests, ownership, and release gates for GitHub issues 12 and 13.
5
+ created: 2026-07-15
6
+ flags:
7
+ - accepted
8
+ tags:
9
+ - architecture
10
+ - cli
11
+ - releases
12
+ - reliability
13
+ keywords:
14
+ - runx upgrade
15
+ - runx upgrade list
16
+ - issue 12
17
+ - issue 13
18
+ - windows self-upgrade
19
+ - recovery install command
20
+ - transactional replacement
21
+ owner: runx-superpowers-specs
22
+ ---
23
+
24
+ # RunX Upgrade Reliability Design
25
+
26
+ ## Status and Scope
27
+
28
+ This is the approved design for `CGuiho/runx` issues #12 and #13. It replaces
29
+ the assumption that a scheduled Windows move is a successful upgrade. It also
30
+ extends the existing verified swap into a complete operator experience:
31
+
32
+ - an upgrade plan and visible progress before long operations;
33
+ - synchronous, verified replacement with rollback;
34
+ - an exact-version recovery installation command after every attempt;
35
+ - a separate process-stop command for replacement conflicts;
36
+ - a complete, semantically sorted catalog of all published releases; and
37
+ - hardened direct installers that verify what they install.
38
+
39
+ The work remains inside the Bun/TypeScript native CLI and its existing
40
+ PowerShell and POSIX installers. Citty continues to own command routing and
41
+ ordinary argument parsing. No Node.js runtime dependency is introduced.
42
+
43
+ ## Problem Statement
44
+
45
+ ### The 0.2.5 bootstrap trap
46
+
47
+ RunX 0.2.5 downloads an update to `runx.exe.new`, launches a detached
48
+ `cmd.exe` process that tries to move the new file directly over the running
49
+ `runx.exe`, discards the helper's output, and returns `scheduled: true` without
50
+ verifying the canonical path. Windows can reject the direct overwrite while
51
+ the original image is mapped. The detached helper then fails silently and the
52
+ next invocation still reports 0.2.5.
53
+
54
+ The updater that is currently running controls the replacement procedure.
55
+ Therefore a corrected 0.2.6 asset cannot retroactively fix the 0.2.5 updater
56
+ that is trying to install it. Users on 0.2.5 need the exact-version recovery
57
+ installer as the bootstrap bridge into the first reliable release.
58
+
59
+ ### What 0.2.6 already proves
60
+
61
+ RunX 0.2.6 implements the correct Windows primitive: rename the mapped current
62
+ executable to a backup, place the new executable at the canonical path, execute
63
+ that canonical path with `--version`, roll back on failure, and defer only
64
+ deletion of the mapped backup. This design preserves that mechanism.
65
+
66
+ The remaining defects are broader than the swap itself:
67
+
68
+ - text output is produced only after the awaited upgrade operation returns;
69
+ - release lookup failures and missing assets can look like a no-op;
70
+ - download validation and temporary-file naming are insufficient;
71
+ - asset selection does not use the installers' baseline-first policy;
72
+ - `upgrade list` reads only one page of 20 releases and lacks metadata;
73
+ - the current comparator does not implement SemVer prerelease precedence;
74
+ - JSON output lacks phases, structured failures, and recovery instructions;
75
+ - direct installers do not verify the installed version or roll back; and
76
+ - generic CLI error handling cannot guarantee recovery output after failure.
77
+
78
+ ## Goals
79
+
80
+ 1. A successful `runx upgrade` means the canonical executable already runs as
81
+ the selected target version when the command returns.
82
+ 2. Human users see the plan before download and a message before every
83
+ potentially slow or disruptive phase.
84
+ 3. Every result - upgraded, already current, dry run, or failed - ends with an
85
+ exact-version recovery install command and a separate process-stop command.
86
+ 4. `runx upgrade list` returns every published RunX release, newest SemVer
87
+ first, with clear channel and compatibility metadata.
88
+ 5. Text and JSON expose equivalent facts without corrupting JSON with streamed
89
+ text.
90
+ 6. Direct installers apply the same target, platform, architecture, variant,
91
+ verification, and failure semantics as in-process upgrade.
92
+ 7. Automated tests make Windows replacement, rollback, output ordering,
93
+ catalog completeness, and installer verification release blockers.
94
+
95
+ ## Non-Goals
96
+
97
+ - RunX will not add a background update daemon or automatically upgrade on
98
+ startup.
99
+ - RunX will not automatically kill other RunX processes. It prints an explicit
100
+ process-stop command for the user to choose.
101
+ - npm, Homebrew, winget, Scoop, and other package-manager integrations are not
102
+ added by this work.
103
+ - This work does not add a new release-signing or checksum publication system.
104
+ It validates platform binary format and verifies the downloaded executable's
105
+ reported version. A future signing design can strengthen provenance.
106
+ - Scheduled replacement is not retained as a fallback success state. Only
107
+ cleanup of a renamed old executable may be deferred.
108
+
109
+ ## Chosen Architecture
110
+
111
+ The implementation uses five bounded units.
112
+
113
+ ### 1. Release catalog
114
+
115
+ The release catalog owns GitHub pagination, tag normalization, SemVer parsing,
116
+ channel labeling, publication metadata, and compatible-asset selection. It
117
+ returns normalized entries and never silently converts transport or schema
118
+ failure into "already current."
119
+
120
+ ### 2. Upgrade planner
121
+
122
+ The planner combines the catalog, installed version, platform, architecture,
123
+ CPU variant policy, and canonical executable path into an immutable plan. The
124
+ in-process upgrader always uses the baseline-first x64 policy; direct installers
125
+ may retain their explicit variant override. No download begins before the plan
126
+ is available and rendered.
127
+
128
+ ```text
129
+ UpgradePlan
130
+ currentVersion
131
+ targetVersion
132
+ os
133
+ arch
134
+ assetName
135
+ assetUrl
136
+ executablePath
137
+ recovery
138
+ ```
139
+
140
+ The default target is the highest valid stable SemVer release. Prereleases are
141
+ always visible in `upgrade list` but are not selected by default. A future
142
+ explicit prerelease selector can reuse the catalog without changing listing
143
+ semantics.
144
+
145
+ ### 3. Transactional installer
146
+
147
+ The transactional installer owns download, validation, backup, canonical
148
+ replacement, target verification, rollback, and cleanup. It emits typed phase
149
+ events but does not render terminal output.
150
+
151
+ ### 4. Upgrade reporter
152
+
153
+ The reporter converts the same plan, events, result, error, and recovery data
154
+ into either streaming human text or one buffered JSON document. This keeps
155
+ execution independent from presentation and prevents text/JSON drift.
156
+
157
+ ### 5. Recovery contract
158
+
159
+ The recovery contract generates the exact pinned installer and process-stop
160
+ commands for the detected platform. The CLI and installer contract tests use
161
+ the same version and asset-selection rules. The PowerShell and shell scripts
162
+ remain platform-native implementations, with cross-language contract tests to
163
+ prevent drift.
164
+
165
+ ## Release Catalog Contract
166
+
167
+ ### Retrieval
168
+
169
+ - Request `GET /repos/CGuiho/runx/releases?per_page=100&page=N`.
170
+ - Continue until the GitHub `Link` header has no `rel="next"` entry. A short
171
+ page may be used as a secondary termination signal, never as a substitute
172
+ when a next link exists.
173
+ - Include every published, non-draft release, including prereleases.
174
+ - Preserve the original tag and normalize project tags such as
175
+ `@guiho/runx@0.3.0` and `v0.3.0` to `0.3.0` for comparison.
176
+ - Report non-2xx responses, rate limits, malformed payloads, and pagination
177
+ failures explicitly.
178
+
179
+ ### Ordering and channels
180
+
181
+ - Valid SemVer releases sort by SemVer precedence, descending.
182
+ - A stable release outranks its prereleases.
183
+ - `alpha`, `beta`, and `rc` identifiers are displayed exactly as those
184
+ channels. Another textual prerelease identifier is displayed as itself.
185
+ - A numeric-only prerelease identifier is displayed as `prerelease`.
186
+ - Published tags that are not valid SemVer remain visible after valid SemVer
187
+ entries, ordered by publication time descending, and use channel `other`.
188
+ - `latest stable` means the greatest valid non-prerelease SemVer, independent
189
+ of GitHub's presentation order.
190
+
191
+ ### Catalog entry
192
+
193
+ Each text row and JSON entry exposes:
194
+
195
+ - original tag and normalized version;
196
+ - channel and prerelease state;
197
+ - publication timestamp;
198
+ - whether it is the installed version;
199
+ - whether it is the latest stable release; and
200
+ - whether the release has a compatible asset for the current OS,
201
+ architecture, and selected x64 variant policy.
202
+
203
+ Text output uses the existing aligned-table style:
204
+
205
+ ```text
206
+ AVAILABLE RUNX VERSIONS
207
+
208
+ VERSION CHANNEL PUBLISHED CURRENT LATEST ASSET
209
+ 0.3.0-alpha.2 alpha 2026-07-15 yes
210
+ 0.2.7 stable 2026-07-15 yes yes
211
+ 0.2.6 stable 2026-07-14 yes yes
212
+ ```
213
+
214
+ The actual catalog is not capped at the example's three rows.
215
+
216
+ JSON uses a complete command-specific wrapper rather than returning a bare
217
+ array:
218
+
219
+ ```json
220
+ {
221
+ "schemaVersion": 1,
222
+ "command": "runx upgrade list",
223
+ "currentVersion": "0.2.6",
224
+ "latestStableVersion": "0.2.7",
225
+ "releases": [
226
+ {
227
+ "tag": "@guiho/runx@0.2.7",
228
+ "version": "0.2.7",
229
+ "channel": "stable",
230
+ "prerelease": false,
231
+ "publishedAt": "2026-07-15T00:00:00Z",
232
+ "current": false,
233
+ "latestStable": true,
234
+ "compatibleAsset": {
235
+ "name": "runx-windows-x64-baseline.exe",
236
+ "url": "https://github.com/CGuiho/runx/releases/download/%40guiho%2Frunx%400.2.7/runx-windows-x64-baseline.exe"
237
+ }
238
+ }
239
+ ]
240
+ }
241
+ ```
242
+
243
+ `releases` contains every normalized published release from all retrieved
244
+ pages, not only compatible or stable releases. `currentVersion` is always the
245
+ installed version. `latestStableVersion` is the greatest valid stable SemVer,
246
+ or null when no published stable SemVer exists.
247
+
248
+ ### Asset selection
249
+
250
+ The CLI and installers use one ordered candidate policy:
251
+
252
+ - x64 baseline preference: `baseline`, default, `modern`;
253
+ - x64 default preference: default, `baseline`, `modern`;
254
+ - x64 modern preference: `modern`, default, `baseline`;
255
+ - ARM64: the single ARM64 asset for the detected OS.
256
+
257
+ The selected asset name and exact GitHub download URL are part of the plan. If
258
+ the latest stable release lacks a compatible asset, upgrade fails explicitly;
259
+ it does not silently skip to an older release. The recovery command remains
260
+ pinned to that target and the installer reports the missing asset clearly.
261
+
262
+ ## Upgrade Event and Human Output Contract
263
+
264
+ ### Event sequence
265
+
266
+ The execution layer emits ordered events with a monotonically increasing
267
+ sequence number. Every event uses one shared phase name and one shared status:
268
+
269
+ - phase: `plan`, `download`, `validate`, `replace`, `verify`, `cache`, or
270
+ `cleanup`;
271
+ - status: `started`, `succeeded`, `skipped`, or `failed`.
272
+
273
+ A normal mutation emits `started` before a phase and `succeeded` after it.
274
+ Failure emits `failed` for that same phase; there is no separate `failed`
275
+ phase. An already-current or dry-run outcome completes `plan` and marks
276
+ mutation phases `skipped`. RunX has no upgrade cache yet, so `cache` is emitted
277
+ as `skipped` after `verify`; retaining the common phase keeps the JSON contract
278
+ aligned with the other GUIHO CLIs without inventing cache behavior.
279
+
280
+ In text mode, the human message derived from each `started` event is written
281
+ and flushed before the implementation awaits that operation. In particular,
282
+ `Downloading...` is visible before the binary body is awaited,
283
+ `Validating...` appears before binary validation, `Replacing...` appears only
284
+ after a complete validated download, and `Verifying...` appears before
285
+ launching the canonical executable. `cleanup: skipped` is valid only when the
286
+ verified upgrade has handed deletion of a locked old image to the detached
287
+ cleanup helper; it never means replacement was scheduled.
288
+
289
+ ### Successful text output
290
+
291
+ ```text
292
+ ------------------------------------------------------------
293
+ Upgrading the CLI
294
+ ------------------------------------------------------------
295
+ current : 0.2.6
296
+ target : 0.2.7
297
+ os : windows
298
+ arch : x64
299
+ binary : runx-windows-x64-baseline.exe
300
+ path : C:/Users/crist/.local/bin/runx.exe
301
+ url : https://github.com/CGuiho/runx/releases/download/%40guiho%2Frunx%400.2.7/runx-windows-x64-baseline.exe
302
+ ------------------------------------------------------------
303
+ Downloading...
304
+ Validating...
305
+ Replacing...
306
+ Verifying...
307
+ Upgrade complete: 0.2.6 -> 0.2.7
308
+
309
+ If the new version is not active, install RunX 0.2.7 directly:
310
+ powershell.exe -NoProfile -ExecutionPolicy Bypass -Command '& ([scriptblock]::Create((Invoke-RestMethod "https://raw.githubusercontent.com/CGuiho/runx/main/devops/install.ps1"))) -Version "0.2.7"'
311
+
312
+ If RunX is still running and blocks installation, stop it first:
313
+ powershell.exe -NoProfile -Command "Get-Process runx -ErrorAction SilentlyContinue | Stop-Process -Force"
314
+ ```
315
+
316
+ The same recovery block follows `Already up to date`, `Dry run complete`, and
317
+ failure results. For an already-current result, the command is pinned to the
318
+ current version. For a release-discovery failure where no target can be
319
+ truthfully resolved, recovery is pinned to the installed version and described
320
+ as a repair installation.
321
+
322
+ Failures name the phase and specific cause, exit nonzero, and never print
323
+ `Upgrade complete`. Recovery instructions are not hidden behind `--verbose`.
324
+ Plan and phase text, non-failure results, and recovery instructions go to
325
+ stdout. The concise failure line goes to stderr, while recovery remains on
326
+ stdout. Diagnostic stacks remain a verbose-only stderr concern.
327
+
328
+ ## JSON Contract
329
+
330
+ `--format=json` emits exactly one valid JSON document. It does not stream human
331
+ phase lines. The reporter buffers events and produces this shared envelope:
332
+
333
+ ```json
334
+ {
335
+ "schemaVersion": 1,
336
+ "command": "runx upgrade",
337
+ "outcome": "upgraded",
338
+ "plan": {
339
+ "currentVersion": "0.2.6",
340
+ "targetVersion": "0.2.7",
341
+ "os": "windows",
342
+ "arch": "x64",
343
+ "assetName": "runx-windows-x64-baseline.exe",
344
+ "assetUrl": "https://github.com/CGuiho/runx/releases/download/%40guiho%2Frunx%400.2.7/runx-windows-x64-baseline.exe",
345
+ "executablePath": "C:/Users/crist/.local/bin/runx.exe"
346
+ },
347
+ "events": [
348
+ { "sequence": 1, "phase": "plan", "status": "started" },
349
+ { "sequence": 2, "phase": "plan", "status": "succeeded" },
350
+ { "sequence": 3, "phase": "download", "status": "started" },
351
+ { "sequence": 4, "phase": "download", "status": "succeeded" },
352
+ { "sequence": 5, "phase": "validate", "status": "started" },
353
+ { "sequence": 6, "phase": "validate", "status": "succeeded" },
354
+ { "sequence": 7, "phase": "replace", "status": "started" },
355
+ { "sequence": 8, "phase": "replace", "status": "succeeded" },
356
+ { "sequence": 9, "phase": "verify", "status": "started" },
357
+ { "sequence": 10, "phase": "verify", "status": "succeeded" },
358
+ { "sequence": 11, "phase": "cache", "status": "skipped" },
359
+ { "sequence": 12, "phase": "cleanup", "status": "started" },
360
+ { "sequence": 13, "phase": "cleanup", "status": "skipped" }
361
+ ],
362
+ "result": {
363
+ "installedVersion": "0.2.7",
364
+ "cleanupDeferred": true
365
+ },
366
+ "recovery": {
367
+ "targetVersion": "0.2.7",
368
+ "targetSource": "resolved",
369
+ "installCommand": "powershell.exe -NoProfile -ExecutionPolicy Bypass -Command '& ([scriptblock]::Create((Invoke-RestMethod \"https://raw.githubusercontent.com/CGuiho/runx/main/devops/install.ps1\"))) -Version \"0.2.7\"'",
370
+ "stopProcessCommand": "powershell.exe -NoProfile -Command \"Get-Process runx -ErrorAction SilentlyContinue | Stop-Process -Force\""
371
+ },
372
+ "error": null
373
+ }
374
+ ```
375
+
376
+ `outcome` is one of `upgraded`, `up-to-date`, `dry-run`, `rolled-back`, or
377
+ `failed`. A successful rollback uses `rolled-back`, retains the primary error,
378
+ and reports the restored installed version in `result`. `failed` covers a
379
+ failure before replacement, a failure for which rollback is unnecessary, or a
380
+ rollback failure. Failed and rolled-back JSON results exit nonzero.
381
+
382
+ The error is null on non-failure outcomes. Otherwise it contains `code`,
383
+ `phase`, and `message`. When release discovery fails before a plan exists,
384
+ `plan` is null and recovery uses `targetSource: "fallback-current"` with the
385
+ installed version. All other resolved plans use `targetSource: "resolved"`.
386
+ Every envelope includes `result`; it is null when no verified installed-version
387
+ result exists. The top-level CLI handler does not append a second generic text
388
+ error after the JSON document.
389
+
390
+ A discovery-failure recovery block is visibly labeled in text:
391
+
392
+ ```text
393
+ Repair reinstall (target lookup failed; pinned to installed RunX 0.2.6):
394
+ powershell.exe -NoProfile -ExecutionPolicy Bypass -Command '& ([scriptblock]::Create((Invoke-RestMethod "https://raw.githubusercontent.com/CGuiho/runx/main/devops/install.ps1"))) -Version "0.2.6"'
395
+ ```
396
+
397
+ Stable error codes are:
398
+
399
+ - `release_lookup_failed`
400
+ - `release_payload_invalid`
401
+ - `no_compatible_asset`
402
+ - `download_failed`
403
+ - `download_invalid`
404
+ - `backup_failed`
405
+ - `replace_failed`
406
+ - `verification_failed`
407
+ - `rollback_failed`
408
+ - `installer_verification_failed`
409
+
410
+ ## Transactional Replacement Contract
411
+
412
+ The state machine is:
413
+
414
+ ```text
415
+ discovered -> planned -> downloaded -> validated -> backed_up
416
+ -> installed -> verified -> complete
417
+ ```
418
+
419
+ The downloaded file and backup use unique names in the executable's directory,
420
+ for example `runx.exe.new.<pid>.<nonce>` and `runx.exe.old.<pid>.<nonce>`. The
421
+ same directory preserves rename semantics and avoids cross-volume moves.
422
+
423
+ Before modifying the canonical path, the installer requires:
424
+
425
+ - a successful HTTP response;
426
+ - a non-empty body;
427
+ - an expected Windows PE, Linux ELF, or macOS Mach-O binary signature;
428
+ - a filename compatible with the plan; and
429
+ - executable permissions on Unix platforms.
430
+
431
+ Replacement then proceeds:
432
+
433
+ 1. Rename the canonical executable to the unique backup path.
434
+ 2. Rename the validated download to the canonical path.
435
+ 3. Launch the canonical path with `--version`.
436
+ 4. Require exit code zero and output exactly equal to the target version.
437
+ 5. Mark the upgrade complete only after verification.
438
+
439
+ If any step after backup fails, remove a failed canonical replacement when
440
+ present and rename the backup to the canonical path. A rollback failure reports
441
+ both the primary and rollback errors and identifies every affected path.
442
+ Temporary downloads are removed on every terminal path.
443
+
444
+ On Windows, the mapped old executable may prevent immediate backup deletion.
445
+ A detached retry helper may remove that backup after the current process exits.
446
+ This is cleanup only: the target is already installed and verified at the
447
+ canonical path. No result field or message describes the upgrade itself as
448
+ scheduled.
449
+
450
+ ## Recovery Command Contract
451
+
452
+ Recovery instructions are generated before mutation and retained through every
453
+ error. They are always printed after the final text result and always included
454
+ in JSON.
455
+
456
+ Windows uses the supported PowerShell installer with an explicit version. The
457
+ command invokes `powershell.exe` directly so it is usable from the user's Git
458
+ Bash session as well as a PowerShell terminal. Its separate stop command is:
459
+
460
+ ```text
461
+ powershell.exe -NoProfile -Command "Get-Process runx -ErrorAction SilentlyContinue | Stop-Process -Force"
462
+ ```
463
+
464
+ Linux and macOS use the supported shell installer with an explicit version:
465
+
466
+ ```text
467
+ curl -fsSL https://raw.githubusercontent.com/CGuiho/runx/main/devops/install.sh | sh -s -- --version '0.2.7'
468
+ ```
469
+
470
+ Their separate stop command is:
471
+
472
+ ```text
473
+ pkill -x runx
474
+ ```
475
+
476
+ The real output substitutes the fully resolved target, including complete
477
+ prerelease identifiers such as `0.3.0-alpha.2`. It never uses `latest` in a
478
+ recovery command. Shell quoting is generated and tested rather than assembled
479
+ from unescaped user input.
480
+
481
+ The recovery object records where its target came from. `resolved` means
482
+ release discovery selected the target. `fallback-current` means discovery
483
+ failed before a target could be selected, so the visibly labeled repair
484
+ reinstall is pinned to the installed version instead of inventing a target.
485
+
486
+ The process-stop command is advisory and never executed automatically. If the
487
+ installer detects an active-image conflict, it preserves the old installation,
488
+ exits nonzero, and tells the user to run the stop command before retrying the
489
+ same pinned install command.
490
+
491
+ ## Direct Installer Hardening
492
+
493
+ `devops/install.ps1` already accepts `-Version`; `devops/install.sh` already
494
+ accepts `--version`. Both must now:
495
+
496
+ 1. preserve full stable or prerelease versions when resolving the release tag;
497
+ 2. use the same ordered asset candidates as the in-process planner;
498
+ 3. print the target, OS, architecture, asset, destination, and exact URL before
499
+ download;
500
+ 4. distinguish an unavailable candidate from a download, validation,
501
+ replacement, permission, or verification failure;
502
+ 5. download and validate before modifying the destination;
503
+ 6. install transactionally with backup and rollback;
504
+ 7. execute the canonical destination with `--version` and require the requested
505
+ version; and
506
+ 8. exit nonzero without claiming installation when any requirement fails.
507
+
508
+ Installer URL, tag, and candidate behavior is covered by shared fixtures and
509
+ cross-language contract tests. The scripts remain independently executable;
510
+ the native CLI does not invoke a remote script during normal self-upgrade.
511
+
512
+ ## Error Handling Rules
513
+
514
+ - Network, GitHub API, malformed release, and missing-asset errors are never
515
+ converted to an up-to-date result.
516
+ - An equal installed and target version is the only normal up-to-date result.
517
+ - A newer target without a compatible asset is an explicit release-integrity
518
+ failure.
519
+ - The first failed mutation phase determines the primary error code.
520
+ - Rollback is mandatory after the canonical executable has been moved.
521
+ - A successful rollback still yields a failed upgrade and nonzero exit.
522
+ - A rollback failure includes both errors and retains recovery commands.
523
+ - Human output remains concise; `--verbose` adds technical cause chains without
524
+ changing exit status or recovery visibility.
525
+ - Text and JSON are derived from the same structured outcome.
526
+
527
+ ## File Ownership
528
+
529
+ | Path | Responsibility |
530
+ | --- | --- |
531
+ | `source/releases.ts` | GitHub pagination, release normalization, SemVer/channel ordering, and compatible-asset selection. |
532
+ | `source/recovery.ts` | Exact-version install and process-stop command generation with platform-safe quoting. |
533
+ | `source/self-management.ts` | Upgrade planning orchestration, download validation, transactional replacement, verification, rollback, and cleanup. |
534
+ | `source/upgrade-output.ts` | Human event streaming and buffered JSON outcome rendering. |
535
+ | `source/types.ts` | Catalog, plan, event, outcome, structured error, and recovery contracts. |
536
+ | `source/render.ts` | Aligned `upgrade list` table rendering alongside existing command tables. |
537
+ | `source/cli.ts` | Citty routing and upgrade-specific exit handling without a second token parser. |
538
+ | `source/releases.spec.ts` | Pagination, SemVer, channel, metadata, and asset-selection tests. |
539
+ | `source/self-management.spec.ts` | Platform transaction, validation, verification, rollback, and cleanup tests. |
540
+ | `source/upgrade-output.spec.ts` | Human ordering, failure recovery, and single-document JSON tests. |
541
+ | `source/guiho-runx.spec.ts` | Command integration for upgrade, check, list, dry-run, and JSON routing. |
542
+ | `devops/install.ps1` | Exact-version transactional Windows installation and verification. |
543
+ | `devops/install.sh` | Exact-version transactional Linux/macOS installation and verification. |
544
+ | `.github/workflows/ci.yml` | Linux and Windows release-blocking coverage, including installer contracts. |
545
+ | `DOCS.md`, `README.md`, `CHANGELOG.md` | Public command, recovery, and release behavior once implementation is complete. |
546
+ | `source/source.xdocs.md`, `devops/devops.xdocs.md`, and affected docs descriptors | Structured ownership for changed and new files. |
547
+
548
+ The implementation plan may keep a proposed new source file inside an existing
549
+ module if review proves the boundary too small to justify a file. It may not
550
+ merge release retrieval, terminal rendering, and filesystem mutation back into
551
+ one untestable operation.
552
+
553
+ ## Test Strategy
554
+
555
+ ### Release catalog
556
+
557
+ - multiple GitHub pages, including more than 100 releases;
558
+ - `Link`-header continuation and mid-pagination failure;
559
+ - stable, `rc`, `beta`, `alpha`, arbitrary, and numeric prerelease channels;
560
+ - correct SemVer precedence and invalid-tag fallback ordering;
561
+ - current/latest markers and publication timestamps;
562
+ - Windows, Linux, macOS, x64, ARM64, and CPU-variant asset selection; and
563
+ - missing compatible assets without fallback to an older release.
564
+
565
+ ### Output and CLI
566
+
567
+ - plan text is captured before the binary fetch starts;
568
+ - every phase is emitted and flushed in exact order;
569
+ - already-current and dry-run paths omit mutation phases;
570
+ - every success and failure path ends with pinned recovery commands;
571
+ - release-discovery failure recovers to the installed version;
572
+ - stable and prerelease command quoting is exact;
573
+ - JSON is one parseable document with equivalent facts;
574
+ - failed JSON has a nonzero exit and no trailing generic text; and
575
+ - Citty help, `-v`, `--version`, and manifest-free behavior remain unchanged.
576
+
577
+ ### Transactional replacement
578
+
579
+ - running Windows executable renamed and replaced before success;
580
+ - canonical path reports the target version before the promise resolves;
581
+ - invalid download rejected before the canonical path changes;
582
+ - stale files do not collide because names are unique;
583
+ - permission, backup, rename, and process-launch failures;
584
+ - version mismatch and nonzero verification exit;
585
+ - successful rollback after every post-backup failure;
586
+ - rollback failure reports both causes and paths;
587
+ - temporary download cleanup on every terminal path;
588
+ - immediate or deferred backup cleanup without scheduled-upgrade output; and
589
+ - equivalent verified transaction behavior on Linux and macOS.
590
+
591
+ ### Direct installers
592
+
593
+ - explicit stable and prerelease versions;
594
+ - candidate order for every OS, architecture, and x64 variant;
595
+ - missing asset versus network and replacement errors;
596
+ - temporary-directory installation with no user PATH pollution in tests;
597
+ - exact canonical version verification;
598
+ - rollback after invalid or mismatched binary; and
599
+ - PowerShell and POSIX recovery commands executed against controlled release
600
+ fixtures.
601
+
602
+ ## Validation and Release Gates
603
+
604
+ Implementation is not complete until all of these pass:
605
+
606
+ 1. `bun run typecheck`
607
+ 2. `bun test`
608
+ 3. `bun run build`
609
+ 4. `bun run binary`
610
+ 5. `bun run binaries`
611
+ 6. Windows CI, including live executable replacement and PowerShell installer
612
+ contract tests
613
+ 7. Linux CI, including POSIX installer and catalog tests
614
+ 8. strict XDocs metadata and whole-tree doctor validation
615
+ 9. native text and JSON smoke tests outside a manifest directory
616
+ 10. `git diff --check` and a reviewed task-only diff
617
+
618
+ Release preparation then follows the repository's Mirror workflow:
619
+
620
+ - update public docs and the configured changelog;
621
+ - run `mirror version plan patch` and review the exact next version;
622
+ - never hand-edit Mirror-managed version fields;
623
+ - apply and publish only with explicit user release authorization; and
624
+ - wait for the protected version-tag workflow to publish all native assets and
625
+ npm successfully.
626
+
627
+ ### Bootstrap and issue-closure gates
628
+
629
+ The public acceptance run must test both paths:
630
+
631
+ 1. From 0.2.5 on Windows, use the printed or documented exact-version recovery
632
+ installer to enter the new reliable release. Do not claim 0.2.5 can repair
633
+ its own updater.
634
+ 2. From a reliable installed release, run `runx upgrade` to the newly published
635
+ patch and confirm the visible plan, phase order, synchronous replacement,
636
+ recovery block, and immediate `runx --version` result.
637
+
638
+ Issue #12 closes only after the published native asset passes the real Windows
639
+ upgrade and complete-catalog acceptance checks. Issue #13 closes only after the
640
+ printed stable and prerelease recovery commands are copied into fresh test
641
+ shells, install the pinned versions, and verify those exact versions. Neither
642
+ issue closes on source tests alone or before the release assets exist.
643
+
644
+ ## Rejected Alternatives
645
+
646
+ ### Keep scheduled replacement and improve the message
647
+
648
+ Rejected because a schedule is not proof of replacement. It recreates the
649
+ 0.2.5 failure mode and cannot satisfy immediate version verification.
650
+
651
+ ### Invoke the remote installer script from `runx upgrade`
652
+
653
+ Rejected because normal native self-upgrade would gain a shell dependency,
654
+ duplicate download behavior, weaken JSON/event ownership, and execute a second
655
+ remote program. The scripts remain explicit recovery and direct-install tools.
656
+
657
+ ### Patch only terminal output
658
+
659
+ Rejected because progress text cannot repair silent release lookup, incomplete
660
+ pagination, incorrect SemVer ordering, installer verification, or rollback
661
+ gaps. Output must describe a reliable structured operation rather than conceal
662
+ an unreliable one.
@@ -0,0 +1,24 @@
1
+ ---
2
+ subject: runx-superpowers-specs
3
+ description: Approved implementation-ready design specifications for RunX features and reliability work.
4
+ parent: runx-superpowers
5
+ children: []
6
+ files: {}
7
+ documents:
8
+ 2026-07-15-upgrade-reliability-design.md: Defines reliable self-upgrade, release catalog, recovery, installer, validation, and release contracts for issues 12 and 13.
9
+ tags:
10
+ - documentation
11
+ - specifications
12
+ - architecture
13
+ keywords:
14
+ - runx upgrade
15
+ - self-upgrade
16
+ - release catalog
17
+ - recovery commands
18
+ flags: []
19
+ status: stable
20
+ ---
21
+
22
+ Specifications in this directory are approved inputs to implementation
23
+ planning. They state observable contracts and acceptance gates, not completed
24
+ implementation evidence.