@ouro.bot/cli 0.1.0-alpha.771 → 0.1.0-alpha.773

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.
@@ -1,14 +1,37 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PUBLIC_DAEMON_STARTUP_FAILURE_REASON = void 0;
4
+ exports.createProviderReadinessPreparationFailure = createProviderReadinessPreparationFailure;
3
5
  exports.failFastContainerCredentialBootstrapStartup = failFastContainerCredentialBootstrapStartup;
4
6
  exports.startDaemonAfterContainerCredentialBootstrap = startDaemonAfterContainerCredentialBootstrap;
5
7
  const runtime_1 = require("../../nerves/runtime");
6
8
  const daemon_tombstone_1 = require("./daemon-tombstone");
7
9
  const REDACTED_BOOTSTRAP_STARTUP_ERROR = "container credential bootstrap rejected; recoverable claim retained for reconciliation";
10
+ const REDACTED_DAEMON_PREPARATION_ERROR = "provider runtime preparation failed before startup; run `ouro doctor` for diagnosis";
11
+ exports.PUBLIC_DAEMON_STARTUP_FAILURE_REASON = "startupFailurePublic";
12
+ class DaemonPreparationFailure extends Error {
13
+ name = "DaemonPreparationFailure";
14
+ }
15
+ function createProviderReadinessPreparationFailure(issues) {
16
+ const lines = ["Provider checks need attention"];
17
+ for (const issue of issues) {
18
+ lines.push(issue.summary);
19
+ lines.push(...issue.actions.map((action) => ` ${action.actor}: ${action.command}`));
20
+ }
21
+ return new DaemonPreparationFailure(lines.join("\n"));
22
+ }
8
23
  function failFastContainerCredentialBootstrapStartup(input) {
9
- const error = new Error(REDACTED_BOOTSTRAP_STARTUP_ERROR);
24
+ failFastDaemonStartup({
25
+ exit: input.exit,
26
+ errorMessage: REDACTED_BOOTSTRAP_STARTUP_ERROR,
27
+ eventMessage: "daemon entrypoint failed before server startup",
28
+ });
29
+ }
30
+ function failFastDaemonStartup(input) {
31
+ const errorMessage = input.errorMessage;
32
+ const error = new Error(errorMessage);
10
33
  try {
11
- (0, daemon_tombstone_1.writeDaemonTombstone)("startupFailure", error);
34
+ (0, daemon_tombstone_1.writeDaemonTombstone)(exports.PUBLIC_DAEMON_STARTUP_FAILURE_REASON, error);
12
35
  }
13
36
  catch {
14
37
  // Exit remains mandatory even if best-effort tombstone reporting fails.
@@ -18,8 +41,8 @@ function failFastContainerCredentialBootstrapStartup(input) {
18
41
  level: "error",
19
42
  component: "daemon",
20
43
  event: "daemon.entry_error",
21
- message: "daemon entrypoint failed before server startup",
22
- meta: { error: REDACTED_BOOTSTRAP_STARTUP_ERROR },
44
+ message: input.eventMessage,
45
+ meta: { error: errorMessage },
23
46
  });
24
47
  }
25
48
  catch {
@@ -36,6 +59,19 @@ async function startDaemonAfterContainerCredentialBootstrap(input) {
36
59
  failFastContainerCredentialBootstrapStartup({ exit: input.exit });
37
60
  return false;
38
61
  }
62
+ try {
63
+ await input.prepareDaemon?.();
64
+ }
65
+ catch (error) {
66
+ input.markStartupFailure();
67
+ const controlledMessage = error instanceof DaemonPreparationFailure ? error.message : null;
68
+ failFastDaemonStartup({
69
+ exit: input.exit,
70
+ errorMessage: controlledMessage ?? REDACTED_DAEMON_PREPARATION_ERROR,
71
+ eventMessage: controlledMessage ?? REDACTED_DAEMON_PREPARATION_ERROR,
72
+ });
73
+ return false;
74
+ }
39
75
  await input.startDaemon();
40
76
  return true;
41
77
  }
@@ -74,7 +74,6 @@ const runtime_credentials_1 = require("../runtime-credentials");
74
74
  const machine_identity_1 = require("../machine-identity");
75
75
  const container_credential_bootstrap_1 = require("./container-credential-bootstrap");
76
76
  const daemon_bootstrap_startup_1 = require("./daemon-bootstrap-startup");
77
- const auth_flow_1 = require("../auth/auth-flow");
78
77
  const sanctuary_health_runner_1 = require("../../senses/sanctuary-health-runner");
79
78
  const sanctuary_acceptance_marker_1 = require("./sanctuary-acceptance-marker");
80
79
  const sanctuary_scheduler_liveness_1 = require("./sanctuary-scheduler-liveness");
@@ -588,77 +587,40 @@ function writeStopCommandHealthState() {
588
587
  // Health writes are best-effort during shutdown.
589
588
  }
590
589
  }
591
- function providerPreloadTargets(agent) {
592
- try {
593
- const { config } = (0, auth_flow_1.readAgentConfigForAgent)(agent, (0, identity_1.getAgentBundlesRoot)());
594
- return [...new Set([config.humanFacing.provider, config.agentFacing.provider])];
595
- }
596
- catch (error) {
597
- (0, runtime_1.emitNervesEvent)({
598
- level: "warn",
599
- component: "daemon",
600
- event: "daemon.provider_preload_skipped",
601
- message: "skipping provider credential preload because agent config could not be read",
602
- meta: {
603
- agent,
604
- error: error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error config-read failures @preserve */ String(error),
605
- },
606
- });
607
- return [];
608
- }
609
- }
610
- function providerPreloadMissingTargets(result, providers) {
611
- return providers.filter((provider) => !result.pool.providers[provider]);
612
- }
613
- async function preloadProviderCredentialPools() {
614
- await Promise.all(managedAgents.map(async (agent) => {
615
- const providers = providerPreloadTargets(agent);
616
- if (providers.length === 0)
617
- return;
618
- const result = await (0, provider_credentials_1.refreshProviderCredentialPool)(agent, { preserveCachedOnFailure: true, providers });
619
- if (result.ok) {
620
- const missingProviders = providerPreloadMissingTargets(result, providers);
621
- if (missingProviders.length > 0) {
622
- (0, runtime_1.emitNervesEvent)({
623
- level: "warn",
624
- component: "daemon",
625
- event: "daemon.provider_preload_unavailable",
626
- message: "provider credential preload returned an incomplete selected provider cache",
627
- meta: {
628
- agent,
629
- reason: "missing",
630
- error: `missing selected providers: ${missingProviders.join(", ")}`,
631
- },
632
- });
633
- return;
634
- }
635
- const records = Object.values(result.pool.providers).filter((record) => !!record);
636
- (0, provider_credentials_1.cacheProviderCredentialRecords)(agent, records, new Date(result.pool.updatedAt));
637
- return;
590
+ async function prepareProviderRuntime() {
591
+ const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
592
+ const readiness = await Promise.all(managedAgents.map(async (agent) => {
593
+ try {
594
+ const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot);
595
+ if (result.ok)
596
+ return null;
597
+ (0, runtime_1.emitNervesEvent)({
598
+ level: "warn",
599
+ component: "daemon",
600
+ event: "daemon.provider_readiness_unavailable",
601
+ message: "fresh provider readiness was unavailable before daemon startup",
602
+ meta: { agent },
603
+ });
604
+ return result.issue ?? {
605
+ summary: `${agent}: provider runtime unavailable`,
606
+ actions: [{ actor: "agent-runnable", command: "ouro doctor" }],
607
+ };
608
+ }
609
+ catch {
610
+ (0, runtime_1.emitNervesEvent)({
611
+ level: "warn",
612
+ component: "daemon",
613
+ event: "daemon.provider_readiness_unavailable",
614
+ message: "fresh provider readiness check failed before daemon startup",
615
+ meta: { agent },
616
+ });
617
+ throw new Error("provider runtime preparation failed");
638
618
  }
639
- (0, runtime_1.emitNervesEvent)({
640
- level: "warn",
641
- component: "daemon",
642
- event: "daemon.provider_preload_unavailable",
643
- message: "provider credential preload could not refresh selected provider cache",
644
- meta: {
645
- agent,
646
- reason: result.reason,
647
- error: result.error,
648
- },
649
- });
650
619
  }));
651
- }
652
- function startProviderCredentialPoolPreload() {
653
- return preloadProviderCredentialPools().catch((error) => {
654
- (0, runtime_1.emitNervesEvent)({
655
- level: "error",
656
- component: "daemon",
657
- event: "daemon.provider_preload_error",
658
- message: "provider credential preload failed after daemon startup",
659
- meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error provider preload failures @preserve */ String(error) },
660
- });
661
- });
620
+ const failures = readiness.filter((entry) => entry !== null);
621
+ if (failures.length > 0) {
622
+ throw (0, daemon_bootstrap_startup_1.createProviderReadinessPreparationFailure)(failures);
623
+ }
662
624
  }
663
625
  function scheduleStartupSentinelAfterProviderPreload(agent, preload) {
664
626
  void preload.then(async () => {
@@ -693,6 +655,7 @@ function scheduleStartupSentinelAfterProviderPreload(agent, preload) {
693
655
  /* v8 ignore start -- habit wiring: lambdas delegate to processManager/fs; tested via HabitScheduler unit tests @preserve */
694
656
  void (0, daemon_bootstrap_startup_1.startDaemonAfterContainerCredentialBootstrap)({
695
657
  loadBootstrap: () => (0, container_credential_bootstrap_1.loadContainerCredentialBootstrap)(managedAgents),
658
+ prepareDaemon: prepareProviderRuntime,
696
659
  startDaemon: () => daemon.start(),
697
660
  markStartupFailure: () => { _tombstoneWritten = true; },
698
661
  exit: (code) => process.exit(code),
@@ -700,7 +663,7 @@ void (0, daemon_bootstrap_startup_1.startDaemonAfterContainerCredentialBootstrap
700
663
  if (!started)
701
664
  return;
702
665
  supercronicSupervisor?.start();
703
- const providerPreload = startProviderCredentialPoolPreload();
666
+ const providerPreload = Promise.resolve();
704
667
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
705
668
  const ouroPath = supercronicSupervisor
706
669
  ? "/usr/local/bin/node /opt/ouro/dist/heart/daemon/ouro-entry.js"
@@ -618,6 +618,7 @@ class OuroDaemon {
618
618
  senseAutostartTimer = null;
619
619
  externalEventReconcileTimer = null;
620
620
  externalEventReconcileRunning = false;
621
+ constructedAtMs = Date.now();
621
622
  mailboxServerFactory;
622
623
  privateRuntimePolicyDeps;
623
624
  onStopCommandComplete;
@@ -1503,7 +1504,7 @@ class OuroDaemon {
1503
1504
  }
1504
1505
  return { type: "message", privateTurnDecision: decision, ...(externalEvent ? { externalEvent } : {}) };
1505
1506
  }
1506
- queueExternalEventForPrivateRuntime(record) {
1507
+ queueExternalEventForPrivateRuntime(record, quiet = false) {
1507
1508
  const pendingDir = path.join(this.bundlesRoot, `${record.agent}.ouro`, "state", "pending", pending_1.PRIVATE_RUNTIME_PENDING.friendId, pending_1.PRIVATE_RUNTIME_PENDING.channel, pending_1.PRIVATE_RUNTIME_PENDING.key);
1508
1509
  const originKey = `${record.source}:${record.eventId}`;
1509
1510
  const parsedReceivedAt = Date.parse(record.receivedAt);
@@ -1523,18 +1524,20 @@ class OuroDaemon {
1523
1524
  mode: "relay",
1524
1525
  packetId: `external-event:${record.agent}:${record.source}:${record.eventId}:generation:${record.generation}:attempt:${record.attemptCount}`,
1525
1526
  });
1526
- (0, runtime_1.emitNervesEvent)({
1527
- component: "daemon",
1528
- event: "daemon.external_event_private_runtime_queued",
1529
- message: "queued external event for private-runtime attention",
1530
- meta: {
1531
- agent: record.agent,
1532
- source: record.source,
1533
- eventType: record.eventType,
1534
- eventId: record.eventId,
1535
- pendingDir,
1536
- },
1537
- });
1527
+ if (!quiet) {
1528
+ (0, runtime_1.emitNervesEvent)({
1529
+ component: "daemon",
1530
+ event: "daemon.external_event_private_runtime_queued",
1531
+ message: "queued external event for private-runtime attention",
1532
+ meta: {
1533
+ agent: record.agent,
1534
+ source: record.source,
1535
+ eventType: record.eventType,
1536
+ eventId: record.eventId,
1537
+ pendingDir,
1538
+ },
1539
+ });
1540
+ }
1538
1541
  }
1539
1542
  externalEventRootPath() {
1540
1543
  return this.externalEventRoot ?? (0, router_1.getExternalEventRoot)();
@@ -1575,8 +1578,19 @@ class OuroDaemon {
1575
1578
  source: primary.source,
1576
1579
  eventType: primary.eventType,
1577
1580
  eventId: primary.eventId,
1578
- }, receipt.id, primary.generation, primary.attemptCount), () => { for (const record of claimed)
1579
- this.queueExternalEventForPrivateRuntime(record); }, lease);
1581
+ }, receipt.id, primary.generation, primary.attemptCount), () => {
1582
+ const batched = claimed.length > 1;
1583
+ for (const record of claimed)
1584
+ this.queueExternalEventForPrivateRuntime(record, batched);
1585
+ if (batched) {
1586
+ (0, runtime_1.emitNervesEvent)({
1587
+ component: "daemon",
1588
+ event: "daemon.external_event_private_runtime_batch_queued",
1589
+ message: "queued an external-event batch for private-runtime attention",
1590
+ meta: { agent: primary.agent, source: primary.source, eventCount: claimed.length },
1591
+ });
1592
+ }
1593
+ }, lease);
1580
1594
  failureClass = wake.denialCode === "managed_runtime_unavailable"
1581
1595
  ? "managed_runtime_unavailable"
1582
1596
  : wake.data?.decision?.denialCode === "provider_lane_unavailable"
@@ -1616,6 +1630,9 @@ class OuroDaemon {
1616
1630
  }
1617
1631
  const statuses = (0, router_1.listExternalEventStatus)(this.externalEventRootPath());
1618
1632
  const dueRecords = [];
1633
+ const providerEvidenceByAgent = new Map();
1634
+ const runtimeEvidenceByAgent = new Map();
1635
+ const recoveredByClass = new Map();
1619
1636
  for (const status of statuses) {
1620
1637
  if (status.corrupt)
1621
1638
  continue;
@@ -1629,15 +1646,35 @@ class OuroDaemon {
1629
1646
  let evidence = null;
1630
1647
  try {
1631
1648
  if (failure.class === "provider_lane_unavailable") {
1632
- const binding = (0, provider_binding_resolver_1.resolveEffectiveProviderBinding)({
1633
- agentName: record.agent,
1634
- agentRoot: path.join(this.bundlesRoot, `${record.agent}.ouro`),
1635
- lane: "inner",
1636
- });
1637
- if (binding.ok && binding.binding.credential.status === "present"
1638
- && binding.binding.readiness.status === "ready" && binding.binding.readiness.checkedAt
1639
- && Date.parse(binding.binding.readiness.checkedAt) > Date.parse(failure.failedAt)) {
1640
- evidence = { class: failure.class, observedAt: binding.binding.readiness.checkedAt };
1649
+ if (!providerEvidenceByAgent.has(record.agent)) {
1650
+ providerEvidenceByAgent.set(record.agent, null);
1651
+ const binding = (0, provider_binding_resolver_1.resolveEffectiveProviderBinding)({
1652
+ agentName: record.agent,
1653
+ agentRoot: path.join(this.bundlesRoot, `${record.agent}.ouro`),
1654
+ lane: "inner",
1655
+ });
1656
+ providerEvidenceByAgent.set(record.agent, binding.ok && binding.binding.credential.status === "present"
1657
+ && binding.binding.readiness.status === "ready" && binding.binding.readiness.checkedAt
1658
+ ? { observedAt: binding.binding.readiness.checkedAt }
1659
+ : null);
1660
+ }
1661
+ const current = providerEvidenceByAgent.get(record.agent);
1662
+ const readinessIsCurrentBoot = current && Date.parse(current.observedAt) >= this.constructedAtMs;
1663
+ if (current && ((0, router_1.isExactLegacyProviderRecoveryFailure)(record)
1664
+ ? readinessIsCurrentBoot
1665
+ : Date.parse(current.observedAt) > Date.parse(failure.failedAt))) {
1666
+ evidence = { class: failure.class, observedAt: current.observedAt };
1667
+ }
1668
+ }
1669
+ else if (failure.class === "execution_lease_expired") {
1670
+ if (!runtimeEvidenceByAgent.has(record.agent)) {
1671
+ const snapshot = this.processManager.listAgentSnapshots().find((candidate) => candidate.name === record.agent && candidate.channel === "private-runtime"
1672
+ && candidate.status === "running" && candidate.pid !== null && candidate.startedAt !== null);
1673
+ runtimeEvidenceByAgent.set(record.agent, snapshot?.startedAt ? { observedAt: snapshot.startedAt } : null);
1674
+ }
1675
+ const current = runtimeEvidenceByAgent.get(record.agent);
1676
+ if (current && Date.parse(current.observedAt) > Date.parse(failure.failedAt)) {
1677
+ evidence = { class: failure.class, observedAt: current.observedAt };
1641
1678
  }
1642
1679
  }
1643
1680
  else if (this.hasManagedPrivateRuntime(record.agent) && Date.parse(now) > Date.parse(failure.failedAt)) {
@@ -1645,8 +1682,9 @@ class OuroDaemon {
1645
1682
  }
1646
1683
  if (!evidence)
1647
1684
  continue;
1648
- const revival = (0, router_1.reviveExternalEventAfterRecovery)(record.recordPath, { expectedVersion: record.version, expectedGeneration: record.generation, evidence, now: () => now });
1685
+ const revival = (0, router_1.reviveExternalEventAfterRecovery)(record.recordPath, { expectedVersion: record.version, expectedGeneration: record.generation, evidence, now: () => now, quiet: true });
1649
1686
  record = revival.record;
1687
+ recoveredByClass.set(failure.class, (recoveredByClass.get(failure.class) ?? 0) + 1);
1650
1688
  }
1651
1689
  catch (error) {
1652
1690
  (0, runtime_1.emitNervesEvent)({
@@ -1668,6 +1706,14 @@ class OuroDaemon {
1668
1706
  if (due)
1669
1707
  dueRecords.push(record);
1670
1708
  }
1709
+ if (recoveredByClass.size > 0) {
1710
+ (0, runtime_1.emitNervesEvent)({
1711
+ component: "daemon",
1712
+ event: "daemon.external_event_recovery_batch",
1713
+ message: "requeued external events after cached infrastructure recovery evidence",
1714
+ meta: { recovered: Object.fromEntries(recoveredByClass) },
1715
+ });
1716
+ }
1671
1717
  const batches = new Map();
1672
1718
  for (const record of dueRecords) {
1673
1719
  const key = `${record.agent}\0${record.source}`;
@@ -32,6 +32,9 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.proveAttemptedRecoveryWithoutRetry = void 0;
37
40
  exports.evaluateSanctuaryProviderReadinessContract = evaluateSanctuaryProviderReadinessContract;
@@ -45,6 +48,8 @@ exports.canonicalDockerIdFromUnraidPrefixedId = canonicalDockerIdFromUnraidPrefi
45
48
  exports.runSanctuaryProductionBoundaryProbe = runSanctuaryProductionBoundaryProbe;
46
49
  exports.createSanctuaryReadOnlyDenialScenarioDriver = createSanctuaryReadOnlyDenialScenarioDriver;
47
50
  exports.readDefaultSanctuaryScenarioFacts = readDefaultSanctuaryScenarioFacts;
51
+ exports.callbackPlaybackSnapshot = callbackPlaybackSnapshot;
52
+ exports.recordCallbackPlayback = recordCallbackPlayback;
48
53
  exports.executeSanctuaryAcceptanceAdapter = executeSanctuaryAcceptanceAdapter;
49
54
  exports.executeSanctuaryAcceptanceVaultProbe = executeSanctuaryAcceptanceVaultProbe;
50
55
  exports.executeSanctuaryAcceptanceRevokedProbe = executeSanctuaryAcceptanceRevokedProbe;
@@ -55,6 +60,7 @@ const node_crypto_1 = require("node:crypto");
55
60
  const node_fs_1 = require("node:fs");
56
61
  const node_net_1 = require("node:net");
57
62
  const path = __importStar(require("node:path"));
63
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
58
64
  const friends_1 = require("@ouro.bot/friends");
59
65
  const runtime_1 = require("../../nerves/runtime");
60
66
  const telegram_approval_runtime_1 = require("../../senses/telegram-approval-runtime");
@@ -99,6 +105,7 @@ const NETWORK_TIMEOUT_MS = 10_000;
99
105
  const KEY_DIRECTORY = "/boot/config/plugins/dynamix.my.servers/keys";
100
106
  const SELECTED_KEY_RECORD = "/run/ouro-acceptance/unraid-key.json";
101
107
  const TELEGRAM_OFFSET = "/home/ouro/AgentBundles/sanctuary.ouro/state/senses/telegram/offset.json";
108
+ const CALLBACK_PLAYBACK_JOURNAL = "state/approvals/sanctuary-callback-playback.sqlite";
102
109
  const TELEGRAM_IDENTITY_KEY = "/home/ouro/AgentBundles/sanctuary.ouro/state/senses/telegram/identity.key";
103
110
  const TELEGRAM_AUDIT = `/home/ouro/AgentBundles/sanctuary.ouro/${telegram_audit_ledger_1.TELEGRAM_ACCEPTANCE_AUDIT_RELATIVE_PATH}`;
104
111
  const TELEGRAM_AUDIT_HEAD = `/home/ouro/AgentBundles/sanctuary.ouro/${telegram_audit_ledger_1.TELEGRAM_ACCEPTANCE_AUDIT_HEAD_RELATIVE_PATH}`;
@@ -244,6 +251,7 @@ function createSanctuaryAcceptanceAdapterDependencies(secretFd = 3, options = {}
244
251
  refreshMachine: runtime_credentials_1.refreshMachineRuntimeCredentialConfig,
245
252
  mergeMachine: runtime_credentials_1.mergeMachineRuntimeCredentialConfig,
246
253
  callbackProbe: executeSanctuaryAcceptanceCallbackProbe,
254
+ callbackPlaybackSnapshot: (coordinateDigest) => callbackPlaybackSnapshot((0, identity_1.getAgentRoot)(TARGET_ID), coordinateDigest),
247
255
  interactiveRuntime: executeSanctuaryInteractiveRuntimeOperation,
248
256
  hostRequest: options.hostRequest ?? ((payload) => defaultHostRequest(payload, hostBrokerSocket, adapterTimeoutMs)),
249
257
  telegramCredentials: () => (0, telegram_1.loadTelegramSenseCredentials)(TARGET_ID),
@@ -1912,6 +1920,94 @@ function callbackUpdate(value) {
1912
1920
  object(update.callback_query, "callback update callback_query");
1913
1921
  return update;
1914
1922
  }
1923
+ function withCallbackPlaybackJournal(agentRoot, operation) {
1924
+ const databasePath = path.join(agentRoot, CALLBACK_PLAYBACK_JOURNAL);
1925
+ (0, node_fs_1.mkdirSync)(path.dirname(databasePath), { recursive: true, mode: 0o700 });
1926
+ const database = new better_sqlite3_1.default(databasePath);
1927
+ try {
1928
+ (0, node_fs_1.chmodSync)(databasePath, 0o600);
1929
+ database.pragma("journal_mode = DELETE");
1930
+ database.pragma("synchronous = FULL");
1931
+ database.exec(`
1932
+ CREATE TABLE IF NOT EXISTS callback_playback (
1933
+ coordinate_digest TEXT PRIMARY KEY NOT NULL CHECK(length(coordinate_digest) = 64),
1934
+ playback_count INTEGER NOT NULL CHECK(playback_count > 0)
1935
+ )
1936
+ `);
1937
+ return operation(database);
1938
+ }
1939
+ finally {
1940
+ database.close();
1941
+ }
1942
+ }
1943
+ function callbackPlaybackSnapshot(agentRoot, coordinateDigest) {
1944
+ if (!SHA256.test(coordinateDigest))
1945
+ throw new Error("callback coordinate digest is invalid");
1946
+ return withCallbackPlaybackJournal(agentRoot, (database) => {
1947
+ const rows = database.prepare("SELECT coordinate_digest, playback_count FROM callback_playback ORDER BY coordinate_digest")
1948
+ .all();
1949
+ if (rows.some((row) => !SHA256.test(row.coordinate_digest) || !Number.isSafeInteger(row.playback_count) || row.playback_count <= 0)) {
1950
+ throw new Error("callback playback journal is invalid");
1951
+ }
1952
+ const playbackCount = rows.find((row) => row.coordinate_digest === coordinateDigest)?.playback_count ?? 0;
1953
+ return { playbackCount, coordinateDigest, journalDigest: sha256(JSON.stringify(rows)) };
1954
+ });
1955
+ }
1956
+ function recordCallbackPlayback(agentRoot, coordinateDigest) {
1957
+ if (!SHA256.test(coordinateDigest))
1958
+ throw new Error("callback coordinate digest is invalid");
1959
+ withCallbackPlaybackJournal(agentRoot, (database) => {
1960
+ database.prepare(`
1961
+ INSERT INTO callback_playback (coordinate_digest, playback_count) VALUES (?, 1)
1962
+ ON CONFLICT(coordinate_digest) DO UPDATE SET playback_count = playback_count + 1
1963
+ `).run(coordinateDigest);
1964
+ });
1965
+ }
1966
+ function callbackCoordinate(update) {
1967
+ const updateId = update.update_id;
1968
+ if (!Number.isSafeInteger(updateId) || Number(updateId) < 0)
1969
+ throw new Error("callback update_id is invalid");
1970
+ const callback = object(update.callback_query, "callback update callback_query");
1971
+ const from = object(callback.from, "callback update sender");
1972
+ const message = object(callback.message, "callback update message");
1973
+ const chat = object(message.chat, "callback update chat");
1974
+ const callbackId = (value, label) => {
1975
+ if (!Number.isSafeInteger(value) || Number(value) <= 0)
1976
+ throw new Error(`${label} is invalid`);
1977
+ return String(value);
1978
+ };
1979
+ const coordinate = {
1980
+ updateId: Number(updateId),
1981
+ queryId: text(callback.id, "callback query id"),
1982
+ callbackData: text(callback.data, "callback data"),
1983
+ userId: callbackId(from.id, "callback user id"),
1984
+ chatId: callbackId(chat.id, "callback chat id"),
1985
+ messageId: callbackId(message.message_id, "callback message id"),
1986
+ };
1987
+ return {
1988
+ updateId: coordinate.updateId,
1989
+ digest: sha256(`ouroboros.sanctuary.callback-coordinate.v1\0${JSON.stringify(coordinate)}`),
1990
+ };
1991
+ }
1992
+ function callbackPlaybackPreflight(payload, deps) {
1993
+ exactKeys(payload, ["operation", "update"], "callback playback preflight request");
1994
+ const coordinate = callbackCoordinate(callbackUpdate(payload.update));
1995
+ const offset = object(JSON.parse(fixedFile(deps, TELEGRAM_OFFSET)), "Telegram offset");
1996
+ exactKeys(offset, ["nextUpdateId"], "Telegram offset");
1997
+ if (!Number.isSafeInteger(offset.nextUpdateId) || Number(offset.nextUpdateId) < 0)
1998
+ throw new Error("Telegram offset is invalid");
1999
+ const snapshot = object(dependency(deps.callbackPlaybackSnapshot, "callback playback journal")(coordinate.digest), "callback playback journal snapshot");
2000
+ exactKeys(snapshot, ["coordinateDigest", "journalDigest", "playbackCount"], "callback playback journal snapshot");
2001
+ if (snapshot.coordinateDigest !== coordinate.digest || typeof snapshot.journalDigest !== "string" || !SHA256.test(snapshot.journalDigest)
2002
+ || !Number.isSafeInteger(snapshot.playbackCount) || Number(snapshot.playbackCount) < 0) {
2003
+ throw new Error("callback playback journal snapshot is invalid");
2004
+ }
2005
+ return {
2006
+ playbackCount: Math.max(Number(snapshot.playbackCount), Number(offset.nextUpdateId) > coordinate.updateId ? 1 : 0),
2007
+ coordinateDigest: coordinate.digest,
2008
+ journalDigest: snapshot.journalDigest,
2009
+ };
2010
+ }
1915
2011
  async function concurrentCallbackProbe(payload, deps) {
1916
2012
  const update = callbackUpdate(payload.update);
1917
2013
  if (!Number.isSafeInteger(payload.concurrency) || payload.concurrency < 2 || payload.concurrency > 16) {
@@ -2026,6 +2122,12 @@ async function storeKey(payload, deps) {
2026
2122
  if (text(handles[id], "vault-backed Unraid credential") !== text(stored[field], "vault-backed Unraid credential")) {
2027
2123
  throw new Error("Unraid key handle does not bind to the active vault field");
2028
2124
  }
2125
+ const acknowledged = object(await dependency(deps.hostRequest, "Sanctuary host broker")({
2126
+ operation: "acknowledge_key_storage", targetServerId: TARGET_SERVER_ID, keyId: id,
2127
+ }), "Unraid key storage acknowledgement");
2128
+ if (acknowledged.acknowledged !== true || acknowledged.id !== id) {
2129
+ throw new Error("Unraid key storage acknowledgement failed");
2130
+ }
2029
2131
  return { stored: true, keyId: id };
2030
2132
  }
2031
2133
  async function probeKey(payload, deps) {
@@ -2232,6 +2334,9 @@ async function executeSanctuaryAcceptanceAdapter(rawPayload, deps = createSanctu
2232
2334
  case "snapshot":
2233
2335
  result = cursorSnapshot(payload, deps);
2234
2336
  break;
2337
+ case "callback_playback_preflight":
2338
+ result = callbackPlaybackPreflight(payload, deps);
2339
+ break;
2235
2340
  case "inject_callbacks_concurrently":
2236
2341
  result = await concurrentCallbackProbe(payload, deps);
2237
2342
  break;
@@ -2418,7 +2523,7 @@ async function executeSanctuaryInteractiveRuntimeOperation(rawPayload, supplied)
2418
2523
  };
2419
2524
  return (0, sanctuary_interactive_control_1.executeSanctuaryInteractiveEngine)(rawPayload, deps);
2420
2525
  }
2421
- async function executeSanctuaryAcceptanceCallbackProbe(rawUpdate, _replay, deps = {
2526
+ async function executeSanctuaryAcceptanceCallbackProbe(rawUpdate, replay, deps = {
2422
2527
  refresh: runtime_credentials_1.refreshRuntimeCredentialConfig,
2423
2528
  credentials: telegram_1.loadTelegramSenseCredentials,
2424
2529
  identityKey: telegram_1.readOrCreateTelegramIdentityKey,
@@ -2426,6 +2531,7 @@ async function executeSanctuaryAcceptanceCallbackProbe(rawUpdate, _replay, deps
2426
2531
  createRuntime: telegram_approval_runtime_1.createTelegramApprovalRuntime,
2427
2532
  toolContext: sanctuary_runtime_1.createSanctuaryToolContext,
2428
2533
  effects: createAcceptanceProbeEffects,
2534
+ recordCallbackPlayback: (coordinateDigest) => recordCallbackPlayback((0, identity_1.getAgentRoot)(TARGET_ID), coordinateDigest),
2429
2535
  }) {
2430
2536
  const update = callbackUpdate(rawUpdate);
2431
2537
  const refreshed = await deps.refresh(TARGET_ID);
@@ -2453,7 +2559,11 @@ async function executeSanctuaryAcceptanceCallbackProbe(rawUpdate, _replay, deps
2453
2559
  effects: effectBoundary?.port ?? unavailableEffects,
2454
2560
  });
2455
2561
  try {
2562
+ deps.recordCallbackPlayback(callbackCoordinate(update).digest);
2456
2563
  const result = await runtime.transport.handleUpdate(update);
2564
+ if (replay && (result.handled !== true || result.accepted !== false || result.reason !== "stale_callback")) {
2565
+ throw new Error("Telegram callback replay did not settle as stale");
2566
+ }
2457
2567
  return {
2458
2568
  settled: result.handled,
2459
2569
  claimed: result.reason === "accepted" || result.reason === "decision_refused",