@ouro.bot/cli 0.1.0-alpha.816 → 0.1.0-alpha.817

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 (49) hide show
  1. package/assets/sanctuary-host-launcher.sh +16 -0
  2. package/changelog.json +10 -0
  3. package/deploy/unraid/Dockerfile +6 -0
  4. package/deploy/unraid/README.txt +280 -60
  5. package/deploy/unraid/docker-man-template-transaction.mjs +192 -7
  6. package/deploy/unraid/sanctuary-acceptance-adapter.sh +1 -1
  7. package/deploy/unraid/sanctuary-acceptance-contract.json +7 -7
  8. package/deploy/unraid/sanctuary-authority-installation.json +36 -0
  9. package/deploy/unraid/sanctuary-authority-service.sh +30 -0
  10. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +62 -9
  11. package/deploy/unraid/sanctuary-unit16-run.sh +15 -15
  12. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
  13. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +2 -2
  14. package/deploy/unraid/sanctuary.xml +2 -1
  15. package/dist/heart/core.js +2 -1
  16. package/dist/heart/daemon/container-spec-auditor-main.js +8 -7
  17. package/dist/heart/daemon/container-spec-auditor.js +16 -9
  18. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +95 -91
  19. package/dist/heart/daemon/sanctuary-acceptance-harness.js +95 -169
  20. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +4 -5
  21. package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
  22. package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
  23. package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
  24. package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
  25. package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +780 -0
  26. package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
  27. package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
  28. package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
  29. package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
  30. package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
  31. package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
  32. package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
  33. package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
  34. package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
  35. package/dist/heart/daemon/sanctuary-telegram-authority-service.js +615 -0
  36. package/dist/heart/daemon/sense-manager.js +20 -10
  37. package/dist/repertoire/tools-sanctuary-host.js +202 -0
  38. package/dist/repertoire/tools.js +15 -6
  39. package/dist/senses/root-host-approval-port.js +345 -0
  40. package/dist/senses/root-host-approval-runtime.js +393 -0
  41. package/dist/senses/sanctuary-authority-resident.js +102 -0
  42. package/dist/senses/telegram-admission.js +7 -0
  43. package/dist/senses/telegram-attachments.js +5 -1
  44. package/dist/senses/telegram-authority-transport.js +231 -0
  45. package/dist/senses/telegram-client.js +31 -8
  46. package/dist/senses/telegram-entry.js +1 -1
  47. package/dist/senses/telegram.js +168 -28
  48. package/npm-shrinkwrap.json +2 -2
  49. package/package.json +1 -1
@@ -132,7 +132,6 @@ IMAGE_FACT=$PRIVATE_ROOT/image-digest
132
132
  CONTAINER_FACT=$PRIVATE_ROOT/container-digest
133
133
  PROCESS_BINDING_FACT=$PRIVATE_ROOT/process-binding-digest
134
134
  HEALTH_FACT=$PRIVATE_ROOT/postboot-health.json
135
- POLLER_FACT=$PRIVATE_ROOT/telegram-poller-count.json
136
135
  CONTAINER_INSPECT_FACT=$PRIVATE_ROOT/container-inspect.json
137
136
 
138
137
  start_broker() {
@@ -280,13 +279,6 @@ stop_exact_production_container() {
280
279
  test "$(/usr/bin/timeout -s KILL 20 /usr/bin/docker inspect --format '{{.State.Pid}}' "$EXPECTED_CONTAINER_ID")" = 0 || return 1
281
280
  }
282
281
 
283
- quiesce_production_telegram_poller() {
284
- stop_exact_production_container
285
- printf '%s\n' '{"activePollers":0,"productionContainerStopped":true}' >"$POLLER_FACT"
286
- chmod 0444 "$POLLER_FACT"
287
- chown 0:0 "$POLLER_FACT"
288
- }
289
-
290
282
  materialize_config() {
291
283
  OUTPUT=$1
292
284
  SNAPSHOT_PHASE=${2:-}
@@ -297,8 +289,9 @@ materialize_config() {
297
289
  EXTRA_MOUNT=$PRIVATE_ROOT/container-inventory.json
298
290
  fi
299
291
  if test -n "$EXTRA_MOUNT"; then
300
- /usr/bin/timeout -s KILL 30 /usr/bin/docker run --rm --pull=never --network none \
292
+ /usr/bin/timeout -s KILL 90 /usr/bin/docker run --rm --pull=never --network none \
301
293
  --user 10001:10001 --read-only \
294
+ --mount "type=bind,src=/run/ouro-authority,dst=/run/ouro-authority,readonly" \
302
295
  --mount "type=bind,src=$EVIDENCE_ROOT,dst=/evidence,readonly" \
303
296
  --mount "type=bind,src=$EXTRA_MOUNT,dst=/run/ouro-acceptance/closed-inventory.json,readonly" \
304
297
  --mount "type=bind,src=$RUNTIME_ROOT,dst=/home/ouro/.ouro-cli,readonly" \
@@ -306,16 +299,18 @@ materialize_config() {
306
299
  --entrypoint /opt/ouro/deploy/unraid/sanctuary-acceptance-adapter.sh \
307
300
  "$IMAGE_ID" materialize-config "$COMMAND" >"$OUTPUT"
308
301
  elif test -n "$SNAPSHOT_PHASE"; then
309
- /usr/bin/timeout -s KILL 30 /usr/bin/docker run --rm --pull=never --network none \
302
+ /usr/bin/timeout -s KILL 90 /usr/bin/docker run --rm --pull=never --network none \
310
303
  --user 10001:10001 --read-only \
304
+ --mount "type=bind,src=/run/ouro-authority,dst=/run/ouro-authority,readonly" \
311
305
  --mount "type=bind,src=$EVIDENCE_ROOT,dst=/evidence,readonly" \
312
306
  --mount "type=bind,src=$RUNTIME_ROOT,dst=/home/ouro/.ouro-cli,readonly" \
313
307
  --mount "type=bind,src=$BUNDLE_ROOT,dst=/home/ouro/AgentBundles/sanctuary.ouro,readonly" \
314
308
  --entrypoint /opt/ouro/deploy/unraid/sanctuary-acceptance-adapter.sh \
315
309
  "$IMAGE_ID" materialize-config "$COMMAND" "$SNAPSHOT_PHASE" >"$OUTPUT"
316
310
  else
317
- /usr/bin/timeout -s KILL 30 /usr/bin/docker run --rm --pull=never --network none \
311
+ /usr/bin/timeout -s KILL 90 /usr/bin/docker run --rm --pull=never --network none \
318
312
  --user 10001:10001 --read-only \
313
+ --mount "type=bind,src=/run/ouro-authority,dst=/run/ouro-authority,readonly" \
319
314
  --mount "type=bind,src=$EVIDENCE_ROOT,dst=/evidence,readonly" \
320
315
  --mount "type=bind,src=$RUNTIME_ROOT,dst=/home/ouro/.ouro-cli,readonly" \
321
316
  --mount "type=bind,src=$BUNDLE_ROOT,dst=/home/ouro/AgentBundles/sanctuary.ouro,readonly" \
@@ -362,11 +357,11 @@ materialize_config "$EXPECTED_CONFIG" "$SNAPSHOT_PHASE"
362
357
  cmp -s "$EXPECTED_CONFIG" "$CONFIG_PATH" || exit 1
363
358
  case "$COMMAND" in callback-inject) test -r /proc/self/fd/3 || exit 2 ;; esac
364
359
  prepare_live_facts
365
- if test "$COMMAND" = telegram-bootstrap; then quiesce_production_telegram_poller; fi
360
+ if test "$COMMAND" = telegram-bootstrap; then stop_exact_production_container; fi
366
361
  if test "$COMMAND" = unraid-key-rotate; then stop_exact_production_container; fi
367
362
 
368
363
  case "$COMMAND" in
369
- telegram-bootstrap) TIME_LIMIT=900; NETWORK=host; INPUT=no; BUNDLE_MODE=readonly; BROKER=no ;;
364
+ telegram-bootstrap) TIME_LIMIT=900; NETWORK=none; INPUT=no; BUNDLE_MODE=readonly; BROKER=no ;;
370
365
  callback-inject) TIME_LIMIT=120; NETWORK=host; INPUT=yes; BUNDLE_MODE=rw; BROKER=no ;;
371
366
  unraid-key-rotate) TIME_LIMIT=600; NETWORK=host; INPUT=no; BUNDLE_MODE=readonly; BROKER=yes ;;
372
367
  evidence-snapshot) TIME_LIMIT=5700; NETWORK=host; INPUT=no; BUNDLE_MODE=readonly; BROKER=yes ;;
@@ -426,11 +421,12 @@ run_harness() {
426
421
  if test "$COMMAND" = telegram-bootstrap; then
427
422
  /usr/bin/timeout -s KILL "$TIME_LIMIT" /usr/bin/docker run --rm --pull=never --network "$NETWORK" \
428
423
  --user 10001:10001 --read-only --cap-drop ALL --security-opt no-new-privileges \
424
+ --mount "type=bind,src=/run/ouro-authority,dst=/run/ouro-authority,readonly" \
429
425
  --mount "type=bind,src=$CONFIG_PATH,dst=/run/ouro-acceptance/config.json,readonly" \
430
426
  --mount "type=bind,src=$EVIDENCE_ROOT,dst=/evidence" \
431
- --mount "type=bind,src=$RUNTIME_ROOT,dst=/home/ouro/.ouro-cli" \
427
+ --mount "type=bind,src=$RUNTIME_ROOT,dst=/home/ouro/.ouro-cli,readonly" \
432
428
  --mount "type=bind,src=$BUNDLE_ROOT,dst=/home/ouro/AgentBundles/sanctuary.ouro$BUNDLE_SUFFIX" \
433
- --mount "type=bind,src=$POLLER_FACT,dst=/run/ouro-acceptance/telegram-poller-count.json,readonly" \
429
+ --mount "type=bind,src=$SOCKET_ROOT,dst=/run/ouro-host-acceptance,readonly" \
434
430
  --mount "type=bind,src=$IMAGE_FACT,dst=/run/ouro-acceptance/image-digest,readonly" \
435
431
  --mount "type=bind,src=$CONTAINER_FACT,dst=/run/ouro-acceptance/container-digest,readonly" \
436
432
  --mount "type=bind,src=$PROCESS_BINDING_FACT,dst=/run/ouro-acceptance/process-binding-digest,readonly" \
@@ -442,6 +438,7 @@ run_harness() {
442
438
  elif test "$COMMAND" = callback-inject; then
443
439
  /usr/bin/timeout -s KILL "$TIME_LIMIT" /usr/bin/docker run --rm -i --pull=never --network "$NETWORK" \
444
440
  --user 10001:10001 --read-only --cap-drop ALL --security-opt no-new-privileges \
441
+ --mount "type=bind,src=/run/ouro-authority,dst=/run/ouro-authority,readonly" \
445
442
  --mount "type=bind,src=$CONFIG_PATH,dst=/run/ouro-acceptance/config.json,readonly" \
446
443
  --mount "type=bind,src=$EVIDENCE_ROOT,dst=/evidence" \
447
444
  --mount "type=bind,src=$RUNTIME_ROOT,dst=/home/ouro/.ouro-cli,readonly" \
@@ -457,6 +454,7 @@ run_harness() {
457
454
  elif test "$COMMAND" = evidence-snapshot; then
458
455
  /usr/bin/timeout -s KILL "$TIME_LIMIT" /usr/bin/docker run --rm --pull=never --network "$NETWORK" \
459
456
  --user 10001:10001 --read-only --cap-drop ALL --security-opt no-new-privileges \
457
+ --mount "type=bind,src=/run/ouro-authority,dst=/run/ouro-authority,readonly" \
460
458
  --mount "type=bind,src=$CONFIG_PATH,dst=/run/ouro-acceptance/config.json,readonly" \
461
459
  --mount "type=bind,src=$EVIDENCE_ROOT,dst=/evidence" \
462
460
  --mount "type=bind,src=$RUNTIME_ROOT,dst=/home/ouro/.ouro-cli,readonly" \
@@ -475,6 +473,7 @@ run_harness() {
475
473
  if test "$COMMAND" = reboot-request || test "$COMMAND" = reboot-resume; then ACCEPTANCE_MOUNT="--mount type=bind,src=$ACCEPTANCE_PIN_ROOT,dst=/home/ouro/AgentBundles/sanctuary.ouro/state/acceptance"; else ACCEPTANCE_MOUNT=; fi
476
474
  /usr/bin/timeout -s KILL "$TIME_LIMIT" /usr/bin/docker run --rm --pull=never --network "$NETWORK" \
477
475
  --user 10001:10001 --read-only --cap-drop ALL --security-opt no-new-privileges \
476
+ --mount "type=bind,src=/run/ouro-authority,dst=/run/ouro-authority,readonly" \
478
477
  --mount "type=bind,src=$CONFIG_PATH,dst=/run/ouro-acceptance/config.json,readonly" \
479
478
  --mount "type=bind,src=$EVIDENCE_ROOT,dst=/evidence" \
480
479
  --mount "type=bind,src=$RUNTIME_ROOT,dst=/home/ouro/.ouro-cli,readonly" \
@@ -492,6 +491,7 @@ run_harness() {
492
491
  else
493
492
  /usr/bin/timeout -s KILL "$TIME_LIMIT" /usr/bin/docker run --rm --pull=never --network "$NETWORK" \
494
493
  --user 10001:10001 --read-only --cap-drop ALL --security-opt no-new-privileges \
494
+ --mount "type=bind,src=/run/ouro-authority,dst=/run/ouro-authority,readonly" \
495
495
  --mount "type=bind,src=$CONFIG_PATH,dst=/run/ouro-acceptance/config.json,readonly" \
496
496
  --mount "type=bind,src=$EVIDENCE_ROOT,dst=/evidence" \
497
497
  --mount "type=bind,src=$RUNTIME_ROOT,dst=/home/ouro/.ouro-cli,readonly" \
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.816",
2
+ "runtimeVersion": "0.1.0-alpha.817",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -2,9 +2,9 @@
2
2
  "version": 2,
3
3
  "profiles": {
4
4
  "sanctuary-owner": {
5
- "version": 8,
5
+ "version": 9,
6
6
  "contextScopes": ["household.status", "household.policy", "household.private"],
7
- "toolNames": ["external_event_disposition", "query_active_work", "save_friend_note", "telegram_contact_manage", "query_cares", "care_manage", "await_condition", "resolve_await", "cancel_await", "list_recent_attachments", "materialize_attachment", "describe_image", "unraid_list_containers", "unraid_get_container_logs", "unraid_get_storage", "sanctuary_get_media_optimization", "sanctuary_search_media_catalog", "unraid_get_disks", "unraid_get_notifications", "unraid_get_system", "sanctuary_get_install_state", "unraid_check_services", "sanctuary_get_download_queue", "sanctuary_resume_download_queue", "unraid_restart_container", "steward_policy_manage", "send_message", "ponder", "rest", "settle", "speak", "shell", "shell_status", "shell_tail", "read_file", "write_file", "edit_file", "glob", "grep", "web_search", "search_facts", "consult_diary", "consult_notes", "get_friend_note", "session_summary", "query_session", "set_reasoning_effort", "restart_runtime", "revive_sense"],
7
+ "toolNames": ["external_event_disposition", "query_active_work", "save_friend_note", "telegram_contact_manage", "query_cares", "care_manage", "await_condition", "resolve_await", "cancel_await", "list_recent_attachments", "materialize_attachment", "describe_image", "unraid_list_containers", "unraid_get_container_logs", "unraid_get_storage", "sanctuary_get_media_optimization", "sanctuary_search_media_catalog", "unraid_get_disks", "unraid_get_notifications", "unraid_get_system", "sanctuary_get_install_state", "unraid_check_services", "sanctuary_get_download_queue", "sanctuary_resume_download_queue", "unraid_restart_container", "steward_policy_manage", "send_message", "ponder", "rest", "settle", "speak", "shell", "shell_status", "shell_tail", "read_file", "write_file", "edit_file", "glob", "grep", "web_search", "search_facts", "consult_diary", "consult_notes", "get_friend_note", "session_summary", "query_session", "set_reasoning_effort", "restart_runtime", "revive_sense", "sanctuary_host_execute"],
8
8
  "effectScopes": ["telegram.proactive", "telegram.request_return", "telegram.owner_event"]
9
9
  },
10
10
  "sanctuary-household": {
@@ -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.816</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.817</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -24,4 +24,5 @@
24
24
  <Config Name="Runtime state" Target="/home/ouro/.ouro-cli" Default="/mnt/user/appdata/ouro-butler/runtime/.ouro-cli" Mode="rw" Description="Machine-scoped runtime" Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/ouro-butler/runtime/.ouro-cli</Config>
25
25
  <Config Name="Agent bundle" Target="/home/ouro/AgentBundles/sanctuary.ouro" Default="/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro" Mode="rw" Description="Sanctuary bundle" Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro</Config>
26
26
  <Config Name="Privileged event spool" Target="/run/ouro-events" Default="/boot/config/custom/ouro-events/spool" Mode="ro" Description="Root-owned detector envelopes, read-only inside the Butler" Type="Path" Display="always" Required="true" Mask="false">/boot/config/custom/ouro-events/spool</Config>
27
+ <Config Name="Root authority socket" Target="/run/ouro-authority" Default="/run/ouro-authority" Mode="ro" Description="Root-owned gateway socket and public issuer pins only" Type="Path" Display="always" Required="true" Mask="false">/run/ouro-authority</Config>
27
28
  </Container>
@@ -176,7 +176,8 @@ async function getProviderRuntime(facing = "human", owner) {
176
176
  console.error(`\n[fatal] ${msg}\n`);
177
177
  throw new Error(msg);
178
178
  }
179
- return runtime;
179
+ // The shared tool loop supplies approval continuation to every provider adapter.
180
+ return { ...runtime, capabilities: new Set([...runtime.capabilities, "approval-continuation"]) };
180
181
  }
181
182
  /**
182
183
  * Clear cached provider construction inputs so the next access re-reads them
@@ -71,15 +71,15 @@ function runContainerSpecAuditorCli(args, deps = {}) {
71
71
  const readFile = deps.readFile ?? ((filePath) => fs.readFileSync(filePath));
72
72
  const readText = (filePath) => decodeUtf8(readFile(filePath));
73
73
  const write = deps.write ?? ((text) => process.stdout.write(text));
74
- const staged = parseModeArguments(args, ["--template", "--runtime-policy", "--expected-image"]);
75
- const persistent = parseModeArguments(args, ["--persistent-template", "--runtime-policy", "--expected-image-reference"]);
76
- const effective = parseModeArguments(args, ["--inspect", "--image-inspect", "--expected-image", "--expected-image-reference", "--expected-icon"]);
74
+ const staged = parseModeArguments(args, ["--template", "--runtime-policy", "--expected-image", "--mount-contract"]);
75
+ const persistent = parseModeArguments(args, ["--persistent-template", "--runtime-policy", "--expected-image-reference", "--mount-contract"]);
76
+ const effective = parseModeArguments(args, ["--inspect", "--image-inspect", "--expected-image", "--expected-image-reference", "--expected-icon", "--mount-contract"]);
77
77
  const sourceCandidate = parseModeArguments(args, ["--inspect", "--image-inspect", "--expected-image", "--mount-contract"]);
78
78
  const sourceContract = sourceCandidate?.["--mount-contract"];
79
79
  const sourceEffective = sourceContract === "legacy-alpha742" ? sourceCandidate : null;
80
80
  const selectedEffective = effective ?? sourceEffective;
81
81
  if (!staged && !persistent && !selectedEffective) {
82
- write(JSON.stringify({ ok: false, error: "usage: staged --template <path> --runtime-policy <path> --expected-image <id>; persistent --persistent-template <path> --runtime-policy <path> --expected-image-reference <tag>; effective --inspect <path> --image-inspect <path> --expected-image <id> --expected-image-reference <tag> --expected-icon <url>; source compatibility effective --inspect <path> --image-inspect <path> --expected-image <id> --mount-contract legacy-alpha742" }) + "\n");
82
+ write(JSON.stringify({ ok: false, error: "usage: all modes require --mount-contract canonical-pre-gateway|canonical-gateway; staged --template <path> --runtime-policy <path> --expected-image <id>; persistent --persistent-template <path> --runtime-policy <path> --expected-image-reference <tag>; effective --inspect <path> --image-inspect <path> --expected-image <id> --expected-image-reference <tag> --expected-icon <url>; source compatibility effective --inspect <path> --image-inspect <path> --expected-image <id> --mount-contract legacy-alpha742" }) + "\n");
83
83
  (0, runtime_1.emitNervesEvent)({
84
84
  level: "error",
85
85
  component: "daemon",
@@ -129,7 +129,7 @@ function runContainerSpecAuditorCli(args, deps = {}) {
129
129
  expectedEnvironment,
130
130
  expectedImageReference: selectedEffective["--expected-image-reference"],
131
131
  expectedIcon: selectedEffective["--expected-icon"],
132
- mountContract: sourceContract === "legacy-alpha742" ? sourceContract : "canonical",
132
+ mountContract: selectedEffective["--mount-contract"],
133
133
  });
134
134
  if (imageInspect.Id !== selectedEffective["--expected-image"]) {
135
135
  result.ok = false;
@@ -139,6 +139,7 @@ function runContainerSpecAuditorCli(args, deps = {}) {
139
139
  return result.ok ? 0 : 1;
140
140
  }
141
141
  const templateArguments = persistent ?? staged;
142
+ const mountContract = templateArguments["--mount-contract"];
142
143
  const templatePath = persistent ? templateArguments["--persistent-template"] : templateArguments["--template"];
143
144
  let templateXml;
144
145
  let runtimePolicyText;
@@ -158,8 +159,8 @@ function runContainerSpecAuditorCli(args, deps = {}) {
158
159
  return 2;
159
160
  }
160
161
  const result = persistent
161
- ? (0, container_spec_auditor_1.auditSanctuaryPersistentTemplate)({ templateXml, runtimePolicyText, expectedImageReference: persistent["--expected-image-reference"] })
162
- : (0, container_spec_auditor_1.auditSanctuaryStagedFiles)({ templateXml, runtimePolicyText, expectedImage: staged["--expected-image"] });
162
+ ? (0, container_spec_auditor_1.auditSanctuaryPersistentTemplate)({ templateXml, runtimePolicyText, expectedImageReference: persistent["--expected-image-reference"], mountContract })
163
+ : (0, container_spec_auditor_1.auditSanctuaryStagedFiles)({ templateXml, runtimePolicyText, expectedImage: staged["--expected-image"], mountContract });
163
164
  write(JSON.stringify(result) + "\n");
164
165
  return result.ok ? 0 : 1;
165
166
  }
@@ -9,12 +9,16 @@ const EXPECTED_BINDS = [
9
9
  "/mnt/user/appdata/ouro-butler/runtime/.ouro-cli:/home/ouro/.ouro-cli:rw",
10
10
  "/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro:/home/ouro/AgentBundles/sanctuary.ouro:rw",
11
11
  "/boot/config/custom/ouro-events/spool:/run/ouro-events:ro",
12
+ "/run/ouro-authority:/run/ouro-authority:ro",
12
13
  ];
13
14
  const EXPECTED_MOUNTS = [
14
15
  ["/mnt/user/appdata/ouro-butler/runtime/.ouro-cli", "/home/ouro/.ouro-cli", true, "rprivate"],
15
16
  ["/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro", "/home/ouro/AgentBundles/sanctuary.ouro", true, "rprivate"],
16
17
  ["/boot/config/custom/ouro-events/spool", "/run/ouro-events", false, "rprivate"],
18
+ ["/run/ouro-authority", "/run/ouro-authority", false, "rprivate"],
17
19
  ];
20
+ const PRE_GATEWAY_BINDS = EXPECTED_BINDS.slice(0, 3);
21
+ const PRE_GATEWAY_MOUNTS = EXPECTED_MOUNTS.slice(0, 3);
18
22
  const LEGACY_ALPHA742_IMAGE = "sha256:681449ad47a2621705cd339b481e6339236b31dc65e195b1cf5025d0f2191d7d";
19
23
  const LEGACY_ALPHA742_MOUNTS = EXPECTED_MOUNTS.slice(0, 2);
20
24
  const EXPECTED_EXTRA_PARAMS = "--restart=unless-stopped --user=10001:10001";
@@ -51,14 +55,14 @@ function auditSanctuaryContainerSpec(value, options) {
51
55
  else {
52
56
  if (!EXACT_IMAGE.test(options.expectedImage))
53
57
  violations.push("expected image must be an exact local Docker image ID");
54
- const mountContract = options.mountContract ?? "canonical";
55
- if (mountContract !== "canonical" && mountContract !== "legacy-alpha742")
58
+ const mountContract = options.mountContract;
59
+ if (mountContract !== "canonical-pre-gateway" && mountContract !== "canonical-gateway" && mountContract !== "legacy-alpha742")
56
60
  violations.push("unsupported mount contract");
57
61
  if (mountContract === "legacy-alpha742" && options.expectedImage !== LEGACY_ALPHA742_IMAGE)
58
62
  violations.push("legacy mount exception requires the pinned alpha.742 image ID");
59
- const expectedMounts = mountContract === "legacy-alpha742" ? LEGACY_ALPHA742_MOUNTS : EXPECTED_MOUNTS;
60
- const expectedArgs = mountContract === "canonical" ? PACKAGE_DAEMON_ARGS : LEGACY_DAEMON_ARGS;
61
- const expectedEntrypoint = mountContract === "canonical" ? PACKAGE_ENTRYPOINT : LEGACY_ENTRYPOINT;
63
+ const expectedMounts = mountContract === "legacy-alpha742" ? LEGACY_ALPHA742_MOUNTS : mountContract === "canonical-pre-gateway" ? PRE_GATEWAY_MOUNTS : EXPECTED_MOUNTS;
64
+ const expectedArgs = mountContract !== "legacy-alpha742" ? PACKAGE_DAEMON_ARGS : LEGACY_DAEMON_ARGS;
65
+ const expectedEntrypoint = mountContract !== "legacy-alpha742" ? PACKAGE_ENTRYPOINT : LEGACY_ENTRYPOINT;
62
66
  if (root.Image !== options.expectedImage)
63
67
  violations.push("image does not match the reviewed exact local Docker image ID");
64
68
  if (root.Path !== "node")
@@ -103,9 +107,9 @@ function auditSanctuaryContainerSpec(value, options) {
103
107
  violations.push("container must not publish all exposed ports");
104
108
  if (!isEmptyRecord(network?.Ports))
105
109
  violations.push("effective network ports must be empty");
106
- if (mountContract === "canonical" && root.Name !== `/${EXPECTED_NAME}`)
110
+ if (mountContract !== "legacy-alpha742" && root.Name !== `/${EXPECTED_NAME}`)
107
111
  violations.push("container name must be /ouro-butler");
108
- if (mountContract === "canonical") {
112
+ if (mountContract !== "legacy-alpha742") {
109
113
  if (!options.expectedImageReference || !VERSION_REFERENCE.test(options.expectedImageReference))
110
114
  violations.push("expected image reference must be the canonical package-version tag");
111
115
  if (config.Image !== options.expectedImageReference)
@@ -162,6 +166,9 @@ function singleTextChild(document, name) {
162
166
  }
163
167
  function auditTemplate(input, expectedRepository, repositoryIsValid, repositoryViolation) {
164
168
  const violations = [];
169
+ if (input.mountContract !== "canonical-pre-gateway" && input.mountContract !== "canonical-gateway")
170
+ violations.push("unsupported template mount contract");
171
+ const expectedBinds = input.mountContract === "canonical-pre-gateway" ? PRE_GATEWAY_BINDS : EXPECTED_BINDS;
165
172
  const template = parseDockerManTemplateXml(input.templateXml);
166
173
  if (!template)
167
174
  violations.push("canonical DockerMan XML structure is invalid");
@@ -181,8 +188,8 @@ function auditTemplate(input, expectedRepository, repositoryIsValid, repositoryV
181
188
  const { Type: type, Target: target, Mode: mode } = entry.attributes;
182
189
  return entry.form === "text" && type === "Path" && target && mode ? `${entry.text}:${target}:${mode}` : "invalid";
183
190
  });
184
- if (configEntries.length !== EXPECTED_BINDS.length
185
- || JSON.stringify([...pathConfigs].sort()) !== JSON.stringify([...EXPECTED_BINDS].sort())) {
191
+ if (configEntries.length !== expectedBinds.length
192
+ || JSON.stringify([...pathConfigs].sort()) !== JSON.stringify([...expectedBinds].sort())) {
186
193
  violations.push("template Config entries must equal the canonical path binds");
187
194
  }
188
195
  const postArgs = directChildren(template, "PostArgs");