@ouro.bot/cli 0.1.0-alpha.839 → 0.1.0-alpha.840

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.840",
6
+ "changes": [
7
+ "Sanctuary upgrade rollback robustness, from the 837 rehearsal on the real host: gateway readiness gets a 15-minute budget (verifying every pinned package file with a cold Unraid page cache takes minutes, and a 120-second budget failed a real rollback after it had restored every record), and a rollback marks its journal so an upgrade can never resume forward from a half-rolled-back journal; only upgrade-rollback continues it.",
8
+ "Sanctuary: the cgroup keep child is used only while the installed package understands it. An older gateway (before 837) refuses an unknown child cgroup, so a rollback to it now removes the keep child and holds Unraid's reaper first, and the host keeper does the same. Found when a rollback from 837 to 830 left the 830 gateway exiting on the keep child; the Butler was recovered by hand in about 17 minutes."
9
+ ]
10
+ },
4
11
  {
5
12
  "version": "0.1.0-alpha.839",
6
13
  "changes": [
@@ -446,17 +446,23 @@ R=/mnt/user/appdata/ouro-authority
446
446
  GW=$R/package/dist/heart/daemon/sanctuary-telegram-authority-entry.js
447
447
  CFG=$R/active.json
448
448
  LOG=/var/log/ouro-gateway.log
449
+ # Only an authority from 0.1.0-alpha.837 on understands the keep child; an older gateway
450
+ # refuses to start beside an unknown cgroup (a rollback to 830 hit exactly that). So the
451
+ # keep child exists only while the installed package supports it; otherwise it is removed
452
+ # and the reaper stays paused, the pre-837 behaviour.
453
+ keep_supported() { grep -q ouro-keep "$R/package/dist/heart/daemon/sanctuary-authority-installation.js" 2>/dev/null; }
449
454
  reaper_policy() {
450
455
  P=$(cat /run/cgroup2-unraid.pid 2>/dev/null) || return 0
451
456
  [ -n "$P" ] || return 0
452
- if [ -d "$CG/ouro-keep" ]; then kill -CONT "$P" 2>/dev/null; return 0; fi
457
+ if keep_supported && [ -d "$CG/ouro-keep" ]; then kill -CONT "$P" 2>/dev/null; return 0; fi
453
458
  S=$(ps -o stat= -p "$P" 2>/dev/null | tr -d ' ')
454
459
  case "$S" in T*) : ;; *) kill -STOP "$P" 2>/dev/null ;; esac
455
460
  }
456
461
  ensure_cg() {
457
462
  [ -d "$CG" ] || { mkdir -m 700 "$CG" 2>/dev/null; chown 0:0 "$CG" 2>/dev/null
458
463
  for c in cpu memory pids; do echo "+$c" > "$CG/cgroup.subtree_control" 2>/dev/null; done; }
459
- [ -d "$CG/ouro-keep" ] || mkdir -m 700 "$CG/ouro-keep" 2>/dev/null
464
+ if keep_supported; then [ -d "$CG/ouro-keep" ] || mkdir -m 700 "$CG/ouro-keep" 2>/dev/null
465
+ else [ -d "$CG/ouro-keep" ] && rmdir "$CG/ouro-keep" 2>/dev/null; fi
460
466
  }
461
467
  ensure_sock() {
462
468
  # Docker autostarts the resident before us and auto-creates its missing bind
@@ -480,10 +486,11 @@ start_gw() {
480
486
  }
481
487
  # An in-place upgrade pauses us with a flag; a stale flag (> 30 min) is ignored.
482
488
  maintenance() { [ -n "$(find /run/ouro-authority-maintenance -mmin -30 2>/dev/null)" ]; }
483
- ensure_cg; reaper_policy; ensure_sock
489
+ reaper_policy; ensure_cg; ensure_sock
484
490
  last_res=0
485
491
  while true; do
486
492
  if maintenance; then sleep 15; continue; fi
493
+ reaper_policy
487
494
  ensure_cg
488
495
  reaper_policy
489
496
  ensure_sock
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.839",
2
+ "runtimeVersion": "0.1.0-alpha.840",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0"?>
2
2
  <Container version="2">
3
3
  <Name>ouro-butler</Name>
4
- <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.839</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.840</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -59,6 +59,10 @@ const CGROUP = "/sys/fs/cgroup/ouro-authority";
59
59
  const BOOT = "/boot/config/custom/ouro-authority/start.sh";
60
60
  const BOOT_LINE = `/bin/sh ${BOOT} --boot & # ouro-authority`;
61
61
  const DIGEST = /^sha256:[a-f0-9]{64}$/u;
62
+ // The gateway verifies every pinned package file before it reports ready. With a cold
63
+ // page cache on Unraid's array that takes minutes, so readiness gets a generous budget
64
+ // (a 120 s budget failed a real rollback on 2026-09-24).
65
+ const GATEWAY_READY_TIMEOUT_MS = 900_000;
62
66
  const TEMPLATE_JOURNAL = "/boot/config/custom/ouro-butler/docker-man-template-transaction.json";
63
67
  // In-place upgrade of an installed authority: same epoch (token, issuer, cursor,
64
68
  // gateway state), new reviewed package and resident image. Every root record it
@@ -298,6 +302,15 @@ class SanctuaryAuthorityRootLifecycle {
298
302
  * made (still empty) cgroup before the child exists, so retry that race a few times. */
299
303
  #cgroup() {
300
304
  const keep = this.#p(`${CGROUP}/${sanctuary_authority_installation_1.SANCTUARY_CGROUP_KEEP}`);
305
+ if (!this.#keepSupported()) {
306
+ // A package from before the keep child refuses an unknown child cgroup and relies on
307
+ // the host keeper holding Unraid's reaper paused (a rollback to 830 hit exactly that).
308
+ this.#freezeUnraidReaper();
309
+ if (fs.existsSync(keep))
310
+ fs.rmdirSync(keep);
311
+ this.#directory(CGROUP);
312
+ return;
313
+ }
301
314
  for (let attempt = 1; !fs.existsSync(keep); attempt += 1) {
302
315
  try {
303
316
  this.#directory(CGROUP);
@@ -310,6 +323,23 @@ class SanctuaryAuthorityRootLifecycle {
310
323
  }
311
324
  this.#directory(CGROUP);
312
325
  }
326
+ /** Whether the installed package's gateway understands the keep child (0.1.0-alpha.837+). */
327
+ #keepSupported() {
328
+ const installation = this.#p(`${ROOT}/package/dist/heart/daemon/sanctuary-authority-installation.js`);
329
+ return fs.existsSync(installation) && fs.readFileSync(installation, "utf8").includes(sanctuary_authority_installation_1.SANCTUARY_CGROUP_KEEP);
330
+ }
331
+ #freezeUnraidReaper() {
332
+ const pidFile = this.#p("/run/cgroup2-unraid.pid");
333
+ if (!fs.existsSync(pidFile))
334
+ return;
335
+ const pid = Number(fs.readFileSync(pidFile, "utf8").trim());
336
+ if (!Number.isSafeInteger(pid) || pid <= 0)
337
+ return;
338
+ try {
339
+ process.kill(pid, "SIGSTOP");
340
+ }
341
+ catch { /* not running: nothing to hold */ }
342
+ }
313
343
  #runtimeDirectories() {
314
344
  for (const directory of [this.#epochRoot(), STAGING])
315
345
  this.#directory(directory);
@@ -506,10 +536,10 @@ class SanctuaryAuthorityRootLifecycle {
506
536
  if (failed)
507
537
  throw new Error("Sanctuary gateway launch failed");
508
538
  return this.#ready();
509
- });
539
+ }, GATEWAY_READY_TIMEOUT_MS);
510
540
  }
511
541
  else
512
- await this.#wait(() => this.#ready());
542
+ await this.#wait(() => this.#ready(), GATEWAY_READY_TIMEOUT_MS);
513
543
  }
514
544
  #target() {
515
545
  const target = this.#containers().find((container) => container.Name === "/ouro-butler");
@@ -682,6 +712,10 @@ class SanctuaryAuthorityRootLifecycle {
682
712
  if (fs.existsSync(this.#p(TEMPLATE_JOURNAL)))
683
713
  throw new Error("Finish the pending installation before upgrading");
684
714
  let journal = fs.existsSync(this.#p(UPGRADE)) ? this.#upgradeJournal() : this.#beginUpgrade(input);
715
+ // A rollback that stopped partway has already restored some records; resuming
716
+ // forward from its step list would act on a predecessor that the list calls switched.
717
+ if (journal.rollingBack)
718
+ throw new Error("A Sanctuary upgrade rollback is pending; run upgrade-rollback to finish it");
685
719
  if (journal.to.imageId !== input.targetImageId || journal.to.imageReference !== input.imageReference)
686
720
  throw new Error("A different Sanctuary upgrade is pending; roll it back first");
687
721
  const target = () => new _a({ targetImageId: journal.to.imageId, rollbackImageId: journal.from.imageId }, this.#options);
@@ -714,6 +748,10 @@ class SanctuaryAuthorityRootLifecycle {
714
748
  if (!fs.existsSync(this.#p(UPGRADE)))
715
749
  return false;
716
750
  let journal = this.#upgradeJournal();
751
+ if (!journal.rollingBack) {
752
+ journal = { ...journal, rollingBack: true };
753
+ this.#write(UPGRADE, JSON.stringify(journal));
754
+ }
717
755
  await this.#halt();
718
756
  if (journal.completed.includes("switch")) {
719
757
  // A migration interrupted before the journal recorded it may or may not be
@@ -755,6 +793,7 @@ class SanctuaryAuthorityRootLifecycle {
755
793
  const sides = [journal?.from, journal?.to];
756
794
  if (!journal || journal.schemaVersion !== 1 || journal.epochId !== this.#request.epochId || !Array.isArray(journal.completed)
757
795
  || !journal.completed.every((step) => exports.SANCTUARY_UPGRADE_STEPS.includes(step))
796
+ || (journal.rollingBack !== undefined && journal.rollingBack !== true)
758
797
  || !sides.every((side) => side && DIGEST.test(side.imageId) && DIGEST.test(side.packageDigest) && IMAGE_REFERENCE.test(side.imageReference))
759
798
  || !DIGEST.test(journal.from.rollbackImageId))
760
799
  throw new Error("Sanctuary upgrade journal is invalid");
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.839",
3
+ "version": "0.1.0-alpha.840",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@ouro.bot/cli",
9
- "version": "0.1.0-alpha.839",
9
+ "version": "0.1.0-alpha.840",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.78.0",
12
12
  "@azure/identity": "^4.13.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.839",
3
+ "version": "0.1.0-alpha.840",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },