@ouro.bot/cli 0.1.0-alpha.815 → 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 (63) hide show
  1. package/assets/sanctuary-host-launcher.sh +16 -0
  2. package/changelog.json +18 -0
  3. package/deploy/unraid/Dockerfile +6 -0
  4. package/deploy/unraid/README.txt +305 -365
  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/approval-store.js +11 -1
  16. package/dist/heart/core.js +2 -1
  17. package/dist/heart/daemon/container-spec-auditor-main.js +9 -8
  18. package/dist/heart/daemon/container-spec-auditor.js +17 -24
  19. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +95 -91
  20. package/dist/heart/daemon/sanctuary-acceptance-harness.js +95 -169
  21. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +4 -5
  22. package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
  23. package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
  24. package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
  25. package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
  26. package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +780 -0
  27. package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
  28. package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
  29. package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
  30. package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
  31. package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
  32. package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
  33. package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
  34. package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
  35. package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
  36. package/dist/heart/daemon/sanctuary-telegram-authority-service.js +615 -0
  37. package/dist/heart/daemon/sense-manager.js +20 -10
  38. package/dist/heart/external-events/router.js +31 -15
  39. package/dist/heart/steward-policy.js +374 -58
  40. package/dist/heart/tool-approval.js +8 -1
  41. package/dist/repertoire/relationship-authorization.js +128 -0
  42. package/dist/repertoire/tools-base.js +9 -13
  43. package/dist/repertoire/tools-sanctuary-host.js +202 -0
  44. package/dist/repertoire/tools-steward-policy.js +34 -10
  45. package/dist/repertoire/tools-unraid.js +79 -32
  46. package/dist/repertoire/tools.js +37 -25
  47. package/dist/repertoire/unraid-restart.js +179 -52
  48. package/dist/senses/private-runtime.js +27 -12
  49. package/dist/senses/root-host-approval-port.js +345 -0
  50. package/dist/senses/root-host-approval-runtime.js +393 -0
  51. package/dist/senses/sanctuary-authority-resident.js +102 -0
  52. package/dist/senses/sanctuary-health-runner.js +0 -1
  53. package/dist/senses/sanctuary-media-catalog-contract.js +4 -1
  54. package/dist/senses/sanctuary-runtime.js +2 -0
  55. package/dist/senses/telegram-admission.js +7 -0
  56. package/dist/senses/telegram-approval-runtime.js +130 -12
  57. package/dist/senses/telegram-attachments.js +5 -1
  58. package/dist/senses/telegram-authority-transport.js +231 -0
  59. package/dist/senses/telegram-client.js +31 -8
  60. package/dist/senses/telegram-entry.js +1 -1
  61. package/dist/senses/telegram.js +174 -28
  62. package/npm-shrinkwrap.json +2 -2
  63. package/package.json +1 -1
@@ -161,6 +161,9 @@ function runtimeConfigUnavailableDetail(agent, runtimeConfig) {
161
161
  return `missing vault ${itemName} (${agent})`;
162
162
  return `vault ${itemName} unavailable (${compactRuntimeConfigError(agent, runtimeConfig.error)})`;
163
163
  }
164
+ function needsMachineRuntimeConfig(agent, sense) {
165
+ return sense === "bluebubbles" || sense === "voice" || sense === "a2a" || (agent === "sanctuary" && sense === "telegram");
166
+ }
164
167
  function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntimeConfig = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent), workbenchHasStaleBundleEntry = false) {
165
168
  const base = {
166
169
  cli: { configured: true, detail: "local interactive terminal" },
@@ -183,7 +186,14 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntim
183
186
  const integrations = payload.integrations;
184
187
  const voice = machinePayload.voice;
185
188
  const a2a = machinePayload.a2a;
186
- if (senses.telegram.enabled) {
189
+ if (senses.telegram.enabled && agent === "sanctuary") {
190
+ const ready = runtimeConfig.ok && machineRuntimeConfig.ok
191
+ && !Object.hasOwn(payload, "telegramBotToken") && !Object.hasOwn(machinePayload, "telegramBotToken");
192
+ base.telegram = ready
193
+ ? { configured: true, detail: "root authority transport; gateway validated at process startup" }
194
+ : { configured: false, detail: "root authority migration requires available tokenless credential inventories" };
195
+ }
196
+ else if (senses.telegram.enabled) {
187
197
  const missing = ["telegramBotToken", "telegramAuthorizedUserId", "telegramAuthorizedChatId"]
188
198
  .filter((field) => !textField(payload, field));
189
199
  base.telegram = missing.length === 0
@@ -355,7 +365,9 @@ function senseRepairHint(agent, sense) {
355
365
  return `Agent-runnable: run 'ouro connect a2a --agent ${agent}', then restart with 'ouro up'.`;
356
366
  }
357
367
  if (sense === "telegram") {
358
- return `Agent-runnable: store Telegram bot/user/chat coordinates with 'ouro connect telegram --agent ${agent}', then restart with 'ouro up'.`;
368
+ return agent === "sanctuary"
369
+ ? "Agent-runnable: complete or recover the root authority migration; do not restore a resident Telegram token."
370
+ : `Agent-runnable: store Telegram bot/user/chat coordinates with 'ouro connect telegram --agent ${agent}', then restart with 'ouro up'.`;
359
371
  }
360
372
  /* v8 ignore next -- Workbench is deliberately not daemon-managed, so getSenseInventory never asks the daemon manager for a repair hint @preserve */
361
373
  if (sense === "workbench") {
@@ -655,10 +667,10 @@ class DaemonSenseManager {
655
667
  this.configRefreshRetryTimers.delete(name);
656
668
  this.configRefreshRetryAttempts.delete(name);
657
669
  }
658
- shouldRetryConfigRefresh(sense, runtimeConfig, machineRuntimeConfig) {
670
+ shouldRetryConfigRefresh(agent, sense, runtimeConfig, machineRuntimeConfig) {
659
671
  if (!runtimeConfig.ok && runtimeConfig.reason === "unavailable")
660
672
  return true;
661
- if ((sense === "bluebubbles" || sense === "voice" || sense === "a2a") &&
673
+ if (needsMachineRuntimeConfig(agent, sense) &&
662
674
  !machineRuntimeConfig.ok &&
663
675
  machineRuntimeConfig.reason === "unavailable")
664
676
  return true;
@@ -668,7 +680,7 @@ class DaemonSenseManager {
668
680
  let retryAfterMs = null;
669
681
  try {
670
682
  const refreshed = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(parsed.agent, { preserveCachedOnFailure: true });
671
- const machineRefreshed = parsed.sense === "bluebubbles" || parsed.sense === "voice" || parsed.sense === "a2a"
683
+ const machineRefreshed = needsMachineRuntimeConfig(parsed.agent, parsed.sense)
672
684
  ? await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(parsed.agent, currentMachineId(), { preserveCachedOnFailure: true })
673
685
  : (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(parsed.agent);
674
686
  const context = this.contexts.get(parsed.agent);
@@ -677,7 +689,7 @@ class DaemonSenseManager {
677
689
  return;
678
690
  context.facts = senseFactsFromRuntimeConfig(parsed.agent, context.senses, refreshed, machineRefreshed, context.workbenchHasStaleBundleEntry);
679
691
  if (!context.facts[parsed.sense].configured) {
680
- if (this.shouldRetryConfigRefresh(parsed.sense, refreshed, machineRefreshed)) {
692
+ if (this.shouldRetryConfigRefresh(parsed.agent, parsed.sense, refreshed, machineRefreshed)) {
681
693
  retryAfterMs = this.nextConfigRetryDelayMs(name);
682
694
  }
683
695
  else {
@@ -723,13 +735,11 @@ class DaemonSenseManager {
723
735
  /* v8 ignore next -- periodic refresh work only exists when a managed background sense is enabled @preserve */
724
736
  if (enabledManagedSenses.length === 0)
725
737
  return;
726
- /* v8 ignore start -- periodic freshness refresh uses the same runtime readers covered by startup integration tests @preserve */
727
738
  const runtimeConfig = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(agent, { preserveCachedOnFailure: true });
728
- const needsMachineConfig = enabledManagedSenses.some((sense) => sense === "bluebubbles" || sense === "voice" || sense === "a2a");
739
+ const needsMachineConfig = enabledManagedSenses.some((sense) => needsMachineRuntimeConfig(agent, sense));
729
740
  const machineRuntimeConfig = needsMachineConfig
730
741
  ? await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agent, currentMachineId(), { preserveCachedOnFailure: true })
731
742
  : (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent);
732
- /* v8 ignore stop */
733
743
  context.facts = senseFactsFromRuntimeConfig(agent, context.senses, runtimeConfig, machineRuntimeConfig, context.workbenchHasStaleBundleEntry);
734
744
  });
735
745
  await Promise.all(refreshes);
@@ -835,7 +845,7 @@ class DaemonSenseManager {
835
845
  return null;
836
846
  const managedName = `${parsed.agent}:${parsed.sense}`;
837
847
  const runtimeConfig = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(parsed.agent, { preserveCachedOnFailure: true });
838
- const needsMachineConfig = parsed.sense === "bluebubbles" || parsed.sense === "voice" || parsed.sense === "a2a";
848
+ const needsMachineConfig = needsMachineRuntimeConfig(parsed.agent, parsed.sense);
839
849
  const machineRuntimeConfig = needsMachineConfig
840
850
  ? await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(parsed.agent, currentMachineId(), { preserveCachedOnFailure: true })
841
851
  : (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(parsed.agent);
@@ -290,10 +290,7 @@ function withRecordLock(recordPath, operation) {
290
290
  }
291
291
  };
292
292
  acquire();
293
- try {
294
- return operation();
295
- }
296
- finally {
293
+ const release = () => {
297
294
  try {
298
295
  /* v8 ignore else -- concurrency fence: a replaced owner must not release its successor's lock @preserve */
299
296
  if (fs.readFileSync(ownerPath, "utf8") === owner) {
@@ -304,6 +301,17 @@ function withRecordLock(recordPath, operation) {
304
301
  catch {
305
302
  // A stale owner must never remove a successor's lock.
306
303
  }
304
+ };
305
+ try {
306
+ const result = operation();
307
+ if (result instanceof Promise)
308
+ return result.finally(release);
309
+ release();
310
+ return result;
311
+ }
312
+ catch (error) {
313
+ release();
314
+ throw error;
307
315
  }
308
316
  }
309
317
  function isRecord(value) {
@@ -1174,18 +1182,26 @@ function claimExternalEvent(recordPath, input) {
1174
1182
  }, now);
1175
1183
  });
1176
1184
  }
1177
- function renewExternalEventClaim(recordPath, input) {
1185
+ function renewClaimLocked(recordPath, input, requireCurrent) {
1186
+ const record = readExternalEventRecord(recordPath);
1187
+ if (record.executionState !== "running" || record.claimOwner !== input.owner)
1188
+ throw new Error("External event claim owner mismatch");
1189
+ if (record.generation !== input.expectedGeneration)
1190
+ throw new Error("External event generation mismatch");
1191
+ const leaseMs = input.leaseMs ?? 30_000;
1192
+ if (!Number.isSafeInteger(leaseMs) || leaseMs <= 0)
1193
+ throw new Error("External event claim renewal is invalid");
1194
+ const now = input.now?.() ?? new Date().toISOString();
1195
+ if (requireCurrent && !(Date.parse(record.claimExpiresAt ?? "") > Date.parse(now)))
1196
+ throw new Error("External event claim expired");
1197
+ return commitMutation(recordPath, { ...record, claimExpiresAt: new Date(Date.parse(now) + leaseMs).toISOString() }, now);
1198
+ }
1199
+ function renewExternalEventClaim(recordPath, input, operation) {
1178
1200
  return withRecordLock(recordPath, () => {
1179
- const record = readExternalEventRecord(recordPath);
1180
- if (record.executionState !== "running" || record.claimOwner !== input.owner)
1181
- throw new Error("External event claim owner mismatch");
1182
- if (record.generation !== input.expectedGeneration)
1183
- throw new Error("External event generation mismatch");
1184
- const leaseMs = input.leaseMs ?? 30_000;
1185
- if (!Number.isSafeInteger(leaseMs) || leaseMs <= 0)
1186
- throw new Error("External event claim renewal is invalid");
1187
- const now = input.now?.() ?? new Date().toISOString();
1188
- return commitMutation(recordPath, { ...record, claimExpiresAt: new Date(Date.parse(now) + leaseMs).toISOString() }, now);
1201
+ if (!operation)
1202
+ return renewClaimLocked(recordPath, input, false);
1203
+ const signal = AbortSignal.timeout(25_000);
1204
+ return operation(renewClaimLocked(recordPath, input, true), signal);
1189
1205
  });
1190
1206
  }
1191
1207
  function assertClaim(record, input) {