@ouro.bot/cli 0.1.0-alpha.878 → 0.1.0-alpha.879

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,12 @@
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.879",
6
+ "changes": [
7
+ "The upgrade lifecycle puts state/a2a and the escalation grant back under root after its recursive ownership restore, so escalation holders are honoured on a shipped release; the replay gate seeds outbox directories as the Butler and asks psyche-via-pr a concrete question"
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.878",
6
12
  "changes": [
@@ -374,7 +374,8 @@ export const CASES = [
374
374
  },
375
375
  {
376
376
  id: "psyche-via-pr",
377
- words: "can you change your personality?",
377
+ // A concrete ask. Given only "can you change your personality?" the Butler describes the pull-request path and offers to file it once told what to change (alpha.878 session bf112be9), which is reasonable, so he never calls a filing tool.
378
+ words: "change your personality: drop the jokes and be formal and flat with me from now on",
378
379
  sender: "principal",
379
380
  delegated: false,
380
381
  readback: ({ trace, reply, said, before, after }) => {
@@ -707,6 +708,17 @@ function sh(file, args, options = {}) {
707
708
  return execFileSync(file, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], ...options })
708
709
  }
709
710
 
711
+ /**
712
+ * Creates `dir` and every missing directory between `root` and it, each handed to the Butler's user. The gate runs as root:
713
+ * a plain recursive mkdir would leave state/outbox itself root-owned and closed (0700), and the Butler could then neither
714
+ * list nor write any outbox beneath it (the alpha.878 outbox-isolation and peer-await-delivers failures).
715
+ */
716
+ export function mkdirOwned(root, dir, owner, { mkdir = mkdirSync, chown = chownSync, exists = existsSync } = {}) {
717
+ const missing = []
718
+ for (let current = dir; current !== root && current.startsWith(root + path.sep) && !exists(current); current = path.dirname(current)) missing.unshift(current)
719
+ for (const made of missing) { mkdir(made, { mode: 0o700 }); chown(made, owner.uid, owner.gid) }
720
+ }
721
+
710
722
  export function makeHost({ bundle = DEFAULT_BUNDLE, cardUrl, log = console.error, exec = sh } = {}) {
711
723
  const state = path.join(bundle, "state")
712
724
  const replayDir = path.join(state, "replay")
@@ -772,8 +784,8 @@ export function makeHost({ bundle = DEFAULT_BUNDLE, cardUrl, log = console.error
772
784
  async seedOutbox(friendId, body) {
773
785
  if (!replayPeers.includes(friendId)) throw new Error(`refusing to seed the outbox of a non-replay friend: ${friendId}`)
774
786
  const dir = path.join(state, "outbox", friendId)
775
- mkdirSync(dir, { recursive: true, mode: 0o700 })
776
787
  const owner = statSync(state)
788
+ mkdirOwned(state, dir, owner)
777
789
  const id = `${String(Date.now()).padStart(13, "0")}-${randomUUID().replace(/-/g, "").slice(0, 6)}`
778
790
  writeAtomic(path.join(dir, `${id}.json`), JSON.stringify({ id, kind: "isolation_canary", createdAt: new Date().toISOString(), body }), 0o600)
779
791
  chownSync(dir, owner.uid, owner.gid); chownSync(path.join(dir, `${id}.json`), owner.uid, owner.gid)
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.878",
2
+ "runtimeVersion": "0.1.0-alpha.879",
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.878</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.879</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -986,6 +986,7 @@ class SanctuaryAuthorityRootLifecycle {
986
986
  });
987
987
  (0, node_child_process_1.execFileSync)("/bin/chown", ["-R", "10001:10001", this.#p(BUNDLE)], { stdio: "ignore" });
988
988
  this.#restoreReplayRoot();
989
+ this.#restoreEscalationGrantRoot();
989
990
  }
990
991
  /**
991
992
  * The recursive restore above hands the whole bundle to the resident, including state/replay. That directory is the
@@ -1009,6 +1010,38 @@ class SanctuaryAuthorityRootLifecycle {
1009
1010
  fs.chmodSync(directory, 0o755);
1010
1011
  fs.rmSync(path.join(directory, "window.json"), { force: true, recursive: true });
1011
1012
  }
1013
+ /**
1014
+ * The same recursive restore hands state/a2a and the escalation grant file to the resident. The Butler honours a grant
1015
+ * only while the file and its directory are root-owned and closed to everyone else (src/a2a/trusted-files.ts), so
1016
+ * after every restore they go back under root, and a prompt-injected model running as the resident cannot mint one.
1017
+ * The subdirectories the resident writes (tasks, pins, seen) are created first and stay with it, so it never needs to
1018
+ * create anything inside the root-owned directory. Missing this is what made every report_failure and outbox case of
1019
+ * the alpha.878 replay gate fail: no peer held the grant, so no peer was an escalation holder.
1020
+ */
1021
+ #restoreEscalationGrantRoot() {
1022
+ const directory = this.#p(`${BUNDLE}/state/a2a`);
1023
+ let stat;
1024
+ try {
1025
+ stat = fs.lstatSync(directory);
1026
+ }
1027
+ catch {
1028
+ return;
1029
+ }
1030
+ if (!stat.isDirectory())
1031
+ return;
1032
+ for (const sub of ["tasks", "pins", "seen"]) {
1033
+ fs.mkdirSync(path.join(directory, sub), { recursive: true });
1034
+ (0, node_child_process_1.execFileSync)("/bin/chown", ["-R", "10001:10001", path.join(directory, sub)], { stdio: "ignore" });
1035
+ }
1036
+ // Ownership first, before anything else can fail: nothing the Butler left in the directory may stop it going back under root.
1037
+ (0, node_child_process_1.execFileSync)("/bin/chown", ["-h", "0:0", directory], { stdio: "ignore" });
1038
+ fs.chmodSync(directory, 0o755);
1039
+ const grants = path.join(directory, "escalation-grants.json");
1040
+ if (fs.existsSync(grants)) {
1041
+ (0, node_child_process_1.execFileSync)("/bin/chown", ["-h", "0:0", grants], { stdio: "ignore" });
1042
+ fs.chmodSync(grants, 0o644);
1043
+ }
1044
+ }
1012
1045
  /** Start the gateway, then the resident, and prove both. */
1013
1046
  async #startUpgraded() {
1014
1047
  this.#runtimeDirectories();
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.878",
3
+ "version": "0.1.0-alpha.879",
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.878",
9
+ "version": "0.1.0-alpha.879",
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.878",
3
+ "version": "0.1.0-alpha.879",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },