@ouro.bot/cli 0.1.0-alpha.767 → 0.1.0-alpha.769

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,22 @@
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.769",
6
+ "changes": [
7
+ "Keep stale Sanctuary Care evidence out of current context and require provenance-qualified same-turn Docker verification before reporting or mutation.",
8
+ "Recognize natural whole-Sanctuary current-status requests and require all seven current reads, including notifications, before a terminal reply.",
9
+ "Revive provider-lane and managed-runtime external-event dead letters exactly once per generation after typed, newer recovery evidence.",
10
+ "Let verified fresh Docker alerts be reported while preserving unrelated Care display and policy metadata during partial incident refreshes and constraining durable status values."
11
+ ]
12
+ },
13
+ {
14
+ "version": "0.1.0-alpha.768",
15
+ "changes": [
16
+ "Fix Sanctuary acceptance cursor snapshots for authenticated genesis and concurrent audit writes.",
17
+ "Cover fail-closed cursor schema, authority, and offset validation."
18
+ ]
19
+ },
4
20
  {
5
21
  "version": "0.1.0-alpha.767",
6
22
  "changes": [
@@ -102,7 +102,7 @@
102
102
  "fixed": {
103
103
  "allowedRoot": "/evidence",
104
104
  "evidencePath": "/evidence/cursor-snapshot.json",
105
- "adapters": [{ "schema": "telegram-cursor-v1", "executable": "/opt/ouro/deploy/unraid/sanctuary-acceptance-adapter.sh" }]
105
+ "adapters": [{ "schema": "telegram-cursor-v1", "executable": "/opt/ouro/deploy/unraid/sanctuary-acceptance-adapter.sh", "allowGenesis": false }]
106
106
  }
107
107
  },
108
108
  "cursor-delta": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.767",
2
+ "runtimeVersion": "0.1.0-alpha.769",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-08-30T00:00:00.000Z"
5
5
  }
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0"?>
2
2
  <Container version="2">
3
3
  <Name>Mendelow Cloud Butler</Name>
4
- <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.767</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.769</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
package/dist/arc/cares.js CHANGED
@@ -33,6 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CARE_INCIDENT_RECOVERY_REVIEW_RISK = void 0;
37
+ exports.isManagedDockerCareIncidentBinding = isManagedDockerCareIncidentBinding;
38
+ exports.projectCareEvidence = projectCareEvidence;
36
39
  exports.createCare = createCare;
37
40
  exports.readCares = readCares;
38
41
  exports.readActiveCares = readActiveCares;
@@ -48,6 +51,32 @@ const session_events_1 = require("../heart/session-events");
48
51
  const runtime_1 = require("../nerves/runtime");
49
52
  const session_transaction_1 = require("../mind/session-transaction");
50
53
  const json_store_1 = require("./json-store");
54
+ exports.CARE_INCIDENT_RECOVERY_REVIEW_RISK = "The verified incident recovered, but this Care still has unresolved context that needs review.";
55
+ function isManagedDockerCareIncidentBinding(binding) {
56
+ return binding.source === "sanctuary-health::Docker_critical_image_disk_utilization"
57
+ && /^docker-image-disk-(?:100|[1-9]?[0-9])pct-[0-9]{8}T[0-9]{4}Z$/u.test(binding.incidentKey)
58
+ && /^[a-f0-9]{64}$/u.test(binding.classifiedRevision)
59
+ && binding.correlationKey === undefined
60
+ && binding.resolvedAt === undefined;
61
+ }
62
+ function projectCareEvidence(care, now = Date.now()) {
63
+ if (care.kind !== "system" || (care.status !== "active" && care.status !== "watching") || care.nextCheckAt === null)
64
+ return care;
65
+ const staleAt = Date.parse(care.nextCheckAt);
66
+ if (Number.isFinite(staleAt) && staleAt > now)
67
+ return care;
68
+ return {
69
+ id: care.id,
70
+ kind: care.kind,
71
+ status: care.status,
72
+ salience: care.salience,
73
+ steward: care.steward,
74
+ evidenceStatus: "stale",
75
+ recheckRequired: true,
76
+ staleAt: care.nextCheckAt,
77
+ lastAssessedAt: care.updatedAt,
78
+ };
79
+ }
51
80
  function caresDir(agentRoot) {
52
81
  return path.join(agentRoot, "arc", "cares");
53
82
  }
@@ -208,11 +237,21 @@ function bindCareIncident(agentRoot, id, binding, options) {
208
237
  function upsertCareForIncident(agentRoot, input) {
209
238
  return withCareMutationLock(agentRoot, () => {
210
239
  const incident = canonicalIncidentBinding(input.incident);
211
- const existing = (0, json_store_1.readJsonDir)(caresDir(agentRoot)).find((care) => care.incidentBindings?.some((binding) => binding.source === incident.source && binding.incidentKey === incident.incidentKey));
240
+ const existing = (0, json_store_1.readJsonDir)(caresDir(agentRoot)).find((care) => (!input.id || care.id === input.id) && care.incidentBindings?.some((binding) => binding.source === incident.source && binding.incidentKey === incident.incidentKey));
212
241
  if (existing) {
213
242
  const index = existing.incidentBindings.findIndex((binding) => binding.source === incident.source && binding.incidentKey === incident.incidentKey);
214
- const unchanged = JSON.stringify(existing.incidentBindings[index]) === JSON.stringify(incident)
215
- && existing.currentRisk === input.currentRisk && existing.nextCheckAt === input.nextCheckAt;
243
+ const requestedRisk = input.currentRisk === undefined ? existing.currentRisk : input.currentRisk === null ? null : (0, session_events_1.capStructuredRecordString)(input.currentRisk);
244
+ const requestedNextCheckAt = input.nextCheckAt === undefined ? existing.nextCheckAt : input.nextCheckAt;
245
+ const incidentStateUnchanged = JSON.stringify(existing.incidentBindings[index]) === JSON.stringify(incident)
246
+ && existing.currentRisk === requestedRisk
247
+ && existing.nextCheckAt === requestedNextCheckAt;
248
+ const displayUnchanged = !input.id || ((input.label === undefined || existing.label === (0, session_events_1.capStructuredRecordString)(input.label))
249
+ && (input.why === undefined || existing.why === (0, session_events_1.capStructuredRecordString)(input.why))
250
+ && (input.kind === undefined || existing.kind === input.kind)
251
+ && (input.status === undefined || existing.status === input.status)
252
+ && (input.salience === undefined || existing.salience === input.salience)
253
+ && (input.steward === undefined || existing.steward === input.steward));
254
+ const unchanged = incidentStateUnchanged && displayUnchanged;
216
255
  if (unchanged)
217
256
  return existing;
218
257
  if (!input.expectedUpdatedAt || input.expectedUpdatedAt !== existing.updatedAt)
@@ -221,15 +260,29 @@ function upsertCareForIncident(agentRoot, input) {
221
260
  incidentBindings[index] = incident;
222
261
  const updated = {
223
262
  ...existing,
224
- currentRisk: input.currentRisk === null ? null : (0, session_events_1.capStructuredRecordString)(input.currentRisk),
225
- nextCheckAt: input.nextCheckAt,
263
+ ...(input.id && input.label !== undefined ? { label: (0, session_events_1.capStructuredRecordString)(input.label) } : {}),
264
+ ...(input.id && input.why !== undefined ? { why: (0, session_events_1.capStructuredRecordString)(input.why) } : {}),
265
+ ...(input.id && input.kind !== undefined ? { kind: input.kind } : {}),
266
+ ...(input.id && input.status !== undefined ? { status: input.status } : {}),
267
+ ...(input.id && input.salience !== undefined ? { salience: input.salience } : {}),
268
+ ...(input.id && input.steward !== undefined ? { steward: input.steward } : {}),
269
+ currentRisk: requestedRisk,
270
+ nextCheckAt: requestedNextCheckAt,
226
271
  incidentBindings,
227
272
  updatedAt: nextUpdatedAt(existing.updatedAt),
228
273
  };
229
274
  writeCareFile(agentRoot, updated);
230
275
  return updated;
231
276
  }
232
- return createCareUnlocked(agentRoot, { ...input, incidentBindings: [incident] });
277
+ if (input.id)
278
+ throw new Error("Care incident upsert target not found");
279
+ const { id: _id, incident: _incident, expectedUpdatedAt: _expectedUpdatedAt, ...careInput } = input;
280
+ return createCareUnlocked(agentRoot, {
281
+ label: careInput.label ?? "untitled", why: careInput.why ?? "", kind: careInput.kind ?? "system", status: careInput.status ?? "active",
282
+ salience: careInput.salience ?? "medium", steward: careInput.steward ?? "mine", currentRisk: careInput.currentRisk ?? null,
283
+ nextCheckAt: careInput.nextCheckAt ?? null, relatedFriendIds: careInput.relatedFriendIds, relatedAgentIds: careInput.relatedAgentIds,
284
+ relatedObligationIds: careInput.relatedObligationIds, relatedEpisodeIds: careInput.relatedEpisodeIds, incidentBindings: [incident],
285
+ });
233
286
  });
234
287
  }
235
288
  function resolveCareIncident(agentRoot, id, input) {
@@ -241,11 +294,40 @@ function resolveCareIncident(agentRoot, id, input) {
241
294
  const index = bindings.findIndex((binding) => binding.source === input.source && binding.incidentKey === input.incidentKey);
242
295
  if (index < 0)
243
296
  throw new Error("Care incident binding not found");
244
- if (bindings[index].resolvedAt)
297
+ const display = input.display;
298
+ const requestedDisplay = display ? {
299
+ label: display.label === undefined ? care.label : (0, session_events_1.capStructuredRecordString)(display.label),
300
+ why: display.why === undefined ? care.why : (0, session_events_1.capStructuredRecordString)(display.why),
301
+ currentRisk: display.currentRisk === undefined ? care.currentRisk : display.currentRisk === null ? null : (0, session_events_1.capStructuredRecordString)(display.currentRisk),
302
+ nextCheckAt: display.nextCheckAt === undefined ? care.nextCheckAt : display.nextCheckAt,
303
+ } : undefined;
304
+ const displayUnchanged = !requestedDisplay || JSON.stringify({
305
+ label: care.label,
306
+ why: care.why,
307
+ currentRisk: care.currentRisk,
308
+ nextCheckAt: care.nextCheckAt,
309
+ }) === JSON.stringify(requestedDisplay);
310
+ if (bindings[index].resolvedAt && displayUnchanged)
245
311
  return care;
246
312
  const now = nextUpdatedAt(care.updatedAt);
247
- bindings[index] = { ...bindings[index], resolvedAt: now };
248
- const updated = { ...care, incidentBindings: bindings, updatedAt: now };
313
+ if (!bindings[index].resolvedAt)
314
+ bindings[index] = { ...bindings[index], resolvedAt: now };
315
+ const noUnresolvedBindings = bindings.every((binding) => binding.resolvedAt);
316
+ const unresolvedBindingsBeforeResolution = care.incidentBindings.filter((binding) => !binding.resolvedAt);
317
+ const soleBindingOwnsRisk = care.kind === "system" && unresolvedBindingsBeforeResolution.length === 1 && isManagedDockerCareIncidentBinding(unresolvedBindingsBeforeResolution[0]);
318
+ const unresolvedContextRemains = !noUnresolvedBindings || (care.currentRisk !== null && !soleBindingOwnsRisk);
319
+ const canonicalDisplay = requestedDisplay && requestedDisplay.currentRisk === null && unresolvedContextRemains
320
+ ? { ...requestedDisplay, currentRisk: exports.CARE_INCIDENT_RECOVERY_REVIEW_RISK, nextCheckAt: requestedDisplay.nextCheckAt ?? new Date(Date.parse(now) + 15 * 60_000).toISOString() }
321
+ : requestedDisplay;
322
+ const resultingRisk = canonicalDisplay ? canonicalDisplay.currentRisk : care.currentRisk;
323
+ const shouldResolveCare = noUnresolvedBindings && (care.currentRisk === null || soleBindingOwnsRisk) && resultingRisk === null;
324
+ const updated = {
325
+ ...care,
326
+ ...canonicalDisplay,
327
+ incidentBindings: bindings,
328
+ ...(shouldResolveCare ? { status: "resolved", resolvedAt: care.resolvedAt ?? now } : {}),
329
+ updatedAt: now,
330
+ };
249
331
  writeCareFile(agentRoot, updated);
250
332
  (0, runtime_1.emitNervesEvent)({
251
333
  component: "heart",
@@ -569,10 +569,10 @@ function toolResultIndicatesFailure(content) {
569
569
  || normalized.startsWith("blocked:")
570
570
  || normalized.startsWith("rejected:");
571
571
  }
572
- function requiredToolResultSucceeded(name, content, validate) {
572
+ function requiredToolResultSucceeded(name, content, args, validate) {
573
573
  if (toolResultIndicatesFailure(content))
574
574
  return false;
575
- return validate?.(name, content) ?? true;
575
+ return validate?.(name, content, args) ?? true;
576
576
  }
577
577
  function effectFingerprint(name, rawArguments) {
578
578
  let args;
@@ -1252,6 +1252,11 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1252
1252
  })()
1253
1253
  : ordinaryActiveTools;
1254
1254
  const candidateToolNames = new Set(candidateActiveTools.map((tool) => tool.function.name));
1255
+ const unadvertisedRequiredTool = requiredToolCallNames.find((name) => !candidateToolNames.has(name));
1256
+ if (unadvertisedRequiredTool) {
1257
+ (0, runtime_1.emitNervesEvent)({ level: "error", component: "engine", event: "engine.required_tool_unadvertised", message: "required tool is not advertised for the active channel", meta: { toolName: unadvertisedRequiredTool, channel: String(channel) } });
1258
+ throw new Error(`required tool is not advertised for this channel: ${unadvertisedRequiredTool}`);
1259
+ }
1255
1260
  const forcedHistoricalToolNames = new Set(unresolvedHistoricalEffects
1256
1261
  .map((effect) => effect.name)
1257
1262
  .filter((name) => candidateToolNames.has(name)));
@@ -1942,7 +1947,14 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1942
1947
  done = true;
1943
1948
  continue;
1944
1949
  }
1945
- const approvalCalls = await Promise.all(validCalls.map(async (entry) => {
1950
+ const requiredDispatchRejections = new Map();
1951
+ for (const entry of validCalls) {
1952
+ const requiredArgs = entry.validated.arguments;
1953
+ const rejection = options?.requiredToolCalls?.validateToolCallBeforeDispatch?.(entry.call.name, requiredArgs);
1954
+ if (rejection)
1955
+ requiredDispatchRejections.set(entry.call.id, { name: entry.call.name, args: requiredArgs, message: rejection });
1956
+ }
1957
+ const approvalCalls = await Promise.all(validCalls.filter((entry) => !requiredDispatchRejections.has(entry.call.id)).map(async (entry) => {
1946
1958
  const classification = await (0, tools_1.classifyApprovalForInvocation)(entry.call.name, entry.validated.arguments, augmentedToolContext);
1947
1959
  return {
1948
1960
  ...entry,
@@ -2039,6 +2051,28 @@ async function runAgent(messages, callbacks, channel, signal, options) {
2039
2051
  for (const tc of result.toolCalls) {
2040
2052
  if (signal?.aborted)
2041
2053
  break;
2054
+ const requiredDispatchRejection = requiredDispatchRejections.get(tc.id);
2055
+ if (requiredDispatchRejection) {
2056
+ callbacks.onToolStart(tc.name, requiredDispatchRejection.args);
2057
+ callbacks.onToolEnd(tc.name, (0, tools_1.summarizeArgs)(tc.name, requiredDispatchRejection.args), false);
2058
+ pushGenerated({ role: "tool", tool_call_id: tc.id, content: requiredDispatchRejection.message });
2059
+ providerRuntime.appendToolOutput(tc.id, requiredDispatchRejection.message);
2060
+ options?.toolBoundaryObserver?.({
2061
+ name: tc.name,
2062
+ reason: "dependency_rejected",
2063
+ globallyResolvable: typeof (0, tools_1.resolveToolDefinition)(tc.name)?.handler === "function",
2064
+ invoked: false,
2065
+ sideEffect: false,
2066
+ });
2067
+ (0, runtime_1.emitNervesEvent)({
2068
+ level: "warn",
2069
+ component: "engine",
2070
+ event: "engine.required_tool_dispatch_rejected",
2071
+ message: "required tool dependency rejected before approval and handler dispatch",
2072
+ meta: { toolName: tc.name },
2073
+ });
2074
+ continue;
2075
+ }
2042
2076
  // Reject sole-call tools when mixed with other tool calls
2043
2077
  const terminalProjection = (0, tools_1.resolveToolDefinition)(tc.name)?.terminalProjection;
2044
2078
  const soleCallRejection = SOLE_CALL_REJECTION[tc.name]
@@ -2321,9 +2355,20 @@ async function runAgent(messages, callbacks, channel, signal, options) {
2321
2355
  success = false;
2322
2356
  augmentedToolContext?.habitSession?.recordError?.(toolResult);
2323
2357
  }
2324
- if (success && requiredToolCallNames.includes(tc.name) && options?.requiredToolCalls?.requireSuccessfulResults
2325
- && requiredToolResultSucceeded(tc.name, toolResult, options.requiredToolCalls.validateRequiredToolResult))
2358
+ const validatedRequiredResult = success && requiredToolCallNames.includes(tc.name) && options?.requiredToolCalls?.requireSuccessfulResults
2359
+ ? requiredToolResultSucceeded(tc.name, toolResult, args, options.requiredToolCalls.validateRequiredToolResult)
2360
+ : false;
2361
+ if (validatedRequiredResult) {
2326
2362
  dispatchedRequiredToolCalls.add(tc.name);
2363
+ for (const requiredName of options?.requiredToolCalls?.requiredToolCallsAfterResult?.(tc.name, args, toolResult) ?? []) {
2364
+ if (!candidateToolNames.has(requiredName)) {
2365
+ (0, runtime_1.emitNervesEvent)({ level: "error", component: "engine", event: "engine.required_tool_unadvertised", message: "dependent required tool is not advertised for the active channel", meta: { toolName: requiredName, channel: String(channel) } });
2366
+ throw new Error(`dependent required tool is not advertised for this channel: ${requiredName}`);
2367
+ }
2368
+ if (!requiredToolCallNames.includes(requiredName))
2369
+ requiredToolCallNames.push(requiredName);
2370
+ }
2371
+ }
2327
2372
  if (success && currentEffectFingerprint && !toolResultIndicatesFailure(toolResult)) {
2328
2373
  unresolvedHistoricalEffects = unresolvedHistoricalEffects.filter((effect) => effect.fingerprint !== currentEffectFingerprint);
2329
2374
  }
@@ -70,6 +70,7 @@ const mailbox_types_1 = require("../mailbox/mailbox-types");
70
70
  const mailbox_read_1 = require("../mailbox/mailbox-read");
71
71
  const mailbox_view_1 = require("../mailbox/mailbox-view");
72
72
  const provider_visibility_1 = require("../provider-visibility");
73
+ const provider_binding_resolver_1 = require("../provider-binding-resolver");
73
74
  const private_runtime_1 = require("../private-runtime");
74
75
  const socket_client_1 = require("./socket-client");
75
76
  const flight_recorder_1 = require("../../arc/flight-recorder");
@@ -1555,6 +1556,7 @@ class OuroDaemon {
1555
1556
  }
1556
1557
  async dispatchExternalEvents(records) {
1557
1558
  const claimed = [];
1559
+ let failureClass;
1558
1560
  try {
1559
1561
  for (const record of records) {
1560
1562
  const owner = `external-event:${record.agent}:${record.source}:${record.eventId}:generation:${record.generation}:attempt:${record.attemptCount + 1}`;
@@ -1575,6 +1577,11 @@ class OuroDaemon {
1575
1577
  eventId: primary.eventId,
1576
1578
  }, receipt.id, primary.generation, primary.attemptCount), () => { for (const record of claimed)
1577
1579
  this.queueExternalEventForPrivateRuntime(record); }, lease);
1580
+ failureClass = wake.denialCode === "managed_runtime_unavailable"
1581
+ ? "managed_runtime_unavailable"
1582
+ : wake.data?.decision?.denialCode === "provider_lane_unavailable"
1583
+ ? "provider_lane_unavailable"
1584
+ : undefined;
1578
1585
  const denied = !wake.ok || wake.data?.decision?.executable === false;
1579
1586
  if (denied)
1580
1587
  throw new Error(wake.error ?? wake.message ?? /* v8 ignore next -- daemon wake responses always carry an error or message @preserve */ "external-event private turn was denied");
@@ -1587,6 +1594,7 @@ class OuroDaemon {
1587
1594
  (0, router_1.failExternalEventAttempt)(latest.recordPath, {
1588
1595
  owner: record.claimOwner, expectedVersion: latest.version, expectedGeneration: latest.generation,
1589
1596
  error: error instanceof Error ? error.message : String(error),
1597
+ ...(failureClass ? { failureClass } : {}),
1590
1598
  });
1591
1599
  }
1592
1600
  }
@@ -1614,6 +1622,43 @@ class OuroDaemon {
1614
1622
  let record = (0, router_1.readExternalEventRecord)(status.recordPath);
1615
1623
  if (record.dispatchEnabled === false)
1616
1624
  continue;
1625
+ if (record.executionState === "dead_letter") {
1626
+ const failure = (0, router_1.externalEventRecoveryFailure)(record);
1627
+ if (!failure || record.recoveryGrant?.generation === record.generation)
1628
+ continue;
1629
+ let evidence = null;
1630
+ try {
1631
+ 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 };
1641
+ }
1642
+ }
1643
+ else if (this.hasManagedPrivateRuntime(record.agent) && Date.parse(now) > Date.parse(failure.failedAt)) {
1644
+ evidence = { class: failure.class, observedAt: now };
1645
+ }
1646
+ if (!evidence)
1647
+ continue;
1648
+ const revival = (0, router_1.reviveExternalEventAfterRecovery)(record.recordPath, { expectedVersion: record.version, expectedGeneration: record.generation, evidence, now: () => now });
1649
+ record = revival.record;
1650
+ }
1651
+ catch (error) {
1652
+ (0, runtime_1.emitNervesEvent)({
1653
+ level: "warn",
1654
+ component: "daemon",
1655
+ event: "daemon.external_event_recovery_error",
1656
+ message: "external event dead-letter recovery check failed",
1657
+ meta: { agent: record.agent, source: record.source, eventId: record.eventId, generation: record.generation, error: error instanceof Error ? error.message : String(error) },
1658
+ });
1659
+ continue;
1660
+ }
1661
+ }
1617
1662
  if (record.executionState === "running" && record.claimExpiresAt && Date.parse(record.claimExpiresAt) <= Date.parse(now)) {
1618
1663
  record = (0, router_1.reconcileExternalEvent)(record.recordPath);
1619
1664
  }
@@ -1657,6 +1702,7 @@ class OuroDaemon {
1657
1702
  return {
1658
1703
  ok: false,
1659
1704
  error: `No managed agent '${command.agent}' is registered with daemon-managed private runtime.`,
1705
+ denialCode: "managed_runtime_unavailable",
1660
1706
  };
1661
1707
  }
1662
1708
  const decision = await (0, private_runtime_1.requestPrivateTurnDecision)(this.buildPrivateRuntimeWakeRequest(command), {
@@ -1667,6 +1713,7 @@ class OuroDaemon {
1667
1713
  return {
1668
1714
  ok: true,
1669
1715
  message: `private-runtime wake denied for ${command.agent}: ${decision.deniedReason}`,
1716
+ ...(decision.denialCode ? { denialCode: decision.denialCode } : {}),
1670
1717
  data: { decision },
1671
1718
  };
1672
1719
  }
@@ -99,6 +99,7 @@ const NETWORK_TIMEOUT_MS = 10_000;
99
99
  const KEY_DIRECTORY = "/boot/config/plugins/dynamix.my.servers/keys";
100
100
  const SELECTED_KEY_RECORD = "/run/ouro-acceptance/unraid-key.json";
101
101
  const TELEGRAM_OFFSET = "/home/ouro/AgentBundles/sanctuary.ouro/state/senses/telegram/offset.json";
102
+ const TELEGRAM_IDENTITY_KEY = "/home/ouro/AgentBundles/sanctuary.ouro/state/senses/telegram/identity.key";
102
103
  const TELEGRAM_AUDIT = `/home/ouro/AgentBundles/sanctuary.ouro/${telegram_audit_ledger_1.TELEGRAM_ACCEPTANCE_AUDIT_RELATIVE_PATH}`;
103
104
  const TELEGRAM_AUDIT_HEAD = `/home/ouro/AgentBundles/sanctuary.ouro/${telegram_audit_ledger_1.TELEGRAM_ACCEPTANCE_AUDIT_HEAD_RELATIVE_PATH}`;
104
105
  const IMAGE_DIGEST_FILE = "/run/ouro-acceptance/image-digest";
@@ -1848,16 +1849,52 @@ async function storeTelegramBootstrap(payload, deps) {
1848
1849
  }
1849
1850
  return { stored: true };
1850
1851
  }
1851
- function cursorSnapshot(deps) {
1852
+ function cursorSnapshot(payload, deps) {
1853
+ exactKeys(payload, ["allowGenesis", "operation", "schema"], "Telegram cursor snapshot request");
1854
+ if (payload.schema !== "telegram-cursor-v1" || typeof payload.allowGenesis !== "boolean")
1855
+ throw new Error("Telegram cursor snapshot request is invalid");
1852
1856
  const offsetRaw = fixedFile(deps, TELEGRAM_OFFSET);
1853
- const auditRaw = fixedFile(deps, TELEGRAM_AUDIT);
1854
- const auditHeadRaw = fixedFile(deps, TELEGRAM_AUDIT_HEAD);
1857
+ const identityKey = fixedFile(deps, TELEGRAM_IDENTITY_KEY).trim();
1858
+ if (!/^[A-Za-z0-9_-]{43}$/u.test(identityKey) || Buffer.from(identityKey, "base64url").length !== 32) {
1859
+ throw new Error("Telegram identity key is invalid");
1860
+ }
1855
1861
  const offset = object(JSON.parse(offsetRaw), "Telegram offset");
1856
1862
  if (!Number.isSafeInteger(offset.nextUpdateId) || offset.nextUpdateId < 0)
1857
1863
  throw new Error("Telegram offset is invalid");
1864
+ let auditCursorDigest;
1865
+ let verificationFailure;
1866
+ let observedAuditState = false;
1867
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1868
+ const headBefore = optionalFixedFile(deps, TELEGRAM_AUDIT_HEAD);
1869
+ const auditRaw = optionalFixedFile(deps, TELEGRAM_AUDIT);
1870
+ const headAfter = optionalFixedFile(deps, TELEGRAM_AUDIT_HEAD);
1871
+ if (headBefore === null && auditRaw === null && headAfter === null) {
1872
+ if (payload.allowGenesis !== true)
1873
+ throw new Error("Telegram audit genesis is not authorized for this snapshot");
1874
+ if (!observedAuditState) {
1875
+ auditCursorDigest = sha256("ouroboros.telegram.acceptance.cursor.v1\0genesis");
1876
+ break;
1877
+ }
1878
+ continue;
1879
+ }
1880
+ observedAuditState = true;
1881
+ if (headBefore !== null && auditRaw !== null && headAfter !== null && headBefore === headAfter) {
1882
+ try {
1883
+ (0, telegram_audit_ledger_1.verifyTelegramAuditLedger)({ ledgerRaw: auditRaw, headRaw: headAfter, identityKey });
1884
+ auditCursorDigest = sha256(`ouroboros.telegram.acceptance.cursor.v1\0present\0${auditRaw}\0${headAfter}`);
1885
+ break;
1886
+ }
1887
+ catch (error) {
1888
+ verificationFailure = error;
1889
+ }
1890
+ }
1891
+ }
1892
+ if (auditCursorDigest === undefined) {
1893
+ throw new Error(`Telegram acceptance audit state could not be captured consistently${verificationFailure ? `: ${verificationFailure.message}` : ""}`, verificationFailure ? { cause: verificationFailure } : undefined);
1894
+ }
1858
1895
  return {
1859
1896
  offsetDigest: sha256(JSON.stringify({ nextUpdateId: offset.nextUpdateId })),
1860
- auditCursorDigest: sha256(`${auditRaw}\0${auditHeadRaw}`),
1897
+ auditCursorDigest,
1861
1898
  };
1862
1899
  }
1863
1900
  function telegramPollerQuiescence(payload, deps) {
@@ -2065,7 +2102,7 @@ function provenance(payload, deps) {
2065
2102
  const containerDigest = fixedFile(deps, CONTAINER_DIGEST_FILE).trim();
2066
2103
  if (!SHA256.test(imageDigest) || !SHA256.test(containerDigest))
2067
2104
  throw new Error("live provenance digest is invalid");
2068
- const cursor = cursorSnapshot(deps);
2105
+ const cursor = cursorSnapshot({ operation: "snapshot", schema: "telegram-cursor-v1", allowGenesis: false }, deps);
2069
2106
  return { imageDigest, containerDigest, cursorDigest: sha256(`${cursor.offsetDigest}\0${cursor.auditCursorDigest}`) };
2070
2107
  }
2071
2108
  function evidenceSnapshot(payload, deps) {
@@ -2078,7 +2115,11 @@ function evidenceSnapshot(payload, deps) {
2078
2115
  const imageDigest = fixedFile(deps, IMAGE_DIGEST_FILE).trim();
2079
2116
  if (!SHA256.test(imageDigest))
2080
2117
  throw new Error("container image digest is invalid");
2081
- return { healthy: health.healthy, containerImageDigest: imageDigest, telegramOffsetDigest: cursorSnapshot(deps).offsetDigest };
2118
+ return {
2119
+ healthy: health.healthy,
2120
+ containerImageDigest: imageDigest,
2121
+ telegramOffsetDigest: cursorSnapshot({ operation: "snapshot", schema: "telegram-cursor-v1", allowGenesis: false }, deps).offsetDigest,
2122
+ };
2082
2123
  }
2083
2124
  function bootId(deps) {
2084
2125
  const value = fixedFile(deps, BOOT_ID_FILE).trim();
@@ -2147,6 +2188,7 @@ function materializeConfig(payload, deps) {
2147
2188
  if (phase !== "before" && phase !== "after")
2148
2189
  throw new Error("cursor snapshot phase is invalid");
2149
2190
  config.evidencePath = `/evidence/cursor-${phase}.json`;
2191
+ config.adapters = config.adapters.map((adapter) => ({ ...adapter, allowGenesis: phase === "before" }));
2150
2192
  }
2151
2193
  else if (command === "unraid-key-rotate") {
2152
2194
  const closed = object(JSON.parse(fixedFile(deps, CLOSED_INVENTORY_FILE)), "closed Unraid inventory");
@@ -2188,7 +2230,7 @@ async function executeSanctuaryAcceptanceAdapter(rawPayload, deps = createSanctu
2188
2230
  result = telegramPollerQuiescence(payload, deps);
2189
2231
  break;
2190
2232
  case "snapshot":
2191
- result = cursorSnapshot(deps);
2233
+ result = cursorSnapshot(payload, deps);
2192
2234
  break;
2193
2235
  case "inject_callbacks_concurrently":
2194
2236
  result = await concurrentCallbackProbe(payload, deps);
@@ -1011,7 +1011,10 @@ async function cursorSnapshot(config, deps) {
1011
1011
  throw new Error("snapshot adapter schemas must be unique");
1012
1012
  schemas.add(schema);
1013
1013
  const executable = adapter(spec.executable, "snapshot adapter executable");
1014
- const payload = await deps.runAdapter(executable, { operation: "snapshot", schema });
1014
+ const allowGenesis = spec.allowGenesis === undefined ? false : boolean(spec.allowGenesis, "snapshot adapter allowGenesis");
1015
+ if (allowGenesis && evidencePath !== path.join(root, "cursor-before.json"))
1016
+ throw new Error("snapshot adapter genesis authority is only valid for cursor-before evidence");
1017
+ const payload = await deps.runAdapter(executable, { operation: "snapshot", schema, allowGenesis });
1015
1018
  const selected = fixedEvidenceValues(schema, payload);
1016
1019
  for (const [name, value] of Object.entries(selected))
1017
1020
  values[`${schema}.${name}`] = value;