@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
@@ -0,0 +1,393 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FileRootHostPendingApprovalStore = void 0;
37
+ exports.classifyRootHostPendingApproval = classifyRootHostPendingApproval;
38
+ exports.createRootHostApprovalRuntime = createRootHostApprovalRuntime;
39
+ const path = __importStar(require("node:path"));
40
+ const node_fs_1 = require("node:fs");
41
+ const node_crypto_1 = require("node:crypto");
42
+ const node_util_1 = require("node:util");
43
+ const friends_1 = require("@ouro.bot/friends");
44
+ const core_1 = require("../heart/core");
45
+ const tool_approval_1 = require("../heart/tool-approval");
46
+ const context_1 = require("../mind/context");
47
+ const session_transaction_1 = require("../mind/session-transaction");
48
+ const runtime_1 = require("../nerves/runtime");
49
+ const mcp_manager_1 = require("../repertoire/mcp-manager");
50
+ const tool_arguments_1 = require("../repertoire/tool-arguments");
51
+ const tools_1 = require("../repertoire/tools");
52
+ const tools_sanctuary_host_1 = require("../repertoire/tools-sanctuary-host");
53
+ const shared_turn_1 = require("./shared-turn");
54
+ class RootHostPendingApprovalError extends Error {
55
+ }
56
+ function requireValid(condition) {
57
+ if (!condition)
58
+ throw new RootHostPendingApprovalError("Root host pending approval is invalid");
59
+ }
60
+ function exact(value, keys) {
61
+ requireValid(value !== null && typeof value === "object" && !Array.isArray(value));
62
+ requireValid((0, node_util_1.isDeepStrictEqual)(Object.keys(value).sort(), keys.sort()));
63
+ }
64
+ function validate(value) {
65
+ exact(value, ["schemaVersion", "approvalId", "registration", "binding", "checkpoint", "executionRequested", "continuationState", "materializedSessionRevision", "rootStatusDigest", "reconciliationRequired"]);
66
+ requireValid(value.schemaVersion === 1 && typeof value.approvalId === "string" && /^root-host-[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/u.test(value.approvalId));
67
+ requireValid(typeof value.executionRequested === "boolean" && typeof value.reconciliationRequired === "boolean"
68
+ && typeof value.continuationState === "string" && ["pending", "materialized", "attempted", "complete"].includes(value.continuationState));
69
+ requireValid(value.rootStatusDigest === null || (typeof value.rootStatusDigest === "string" && /^sha256:[a-f0-9]{64}$/u.test(value.rootStatusDigest)));
70
+ requireValid(value.materializedSessionRevision === null || (typeof value.materializedSessionRevision === "string" && /^[a-f0-9]{64}$/u.test(value.materializedSessionRevision)));
71
+ requireValid((value.continuationState !== "materialized" && value.continuationState !== "attempted") || value.materializedSessionRevision !== null);
72
+ requireValid(value.continuationState !== "pending" || value.materializedSessionRevision === null);
73
+ exact(value.registration, ["registration", "registrationId", "telegramMessageId", "expiresAt"]);
74
+ requireValid(typeof value.registration.registrationId === "string" && /^hostreg-[A-Za-z0-9_-]{43}$/u.test(value.registration.registrationId)
75
+ && Number.isSafeInteger(value.registration.telegramMessageId) && value.registration.telegramMessageId > 0
76
+ && typeof value.registration.expiresAt === "string" && Number.isFinite(Date.parse(value.registration.expiresAt)));
77
+ exact(value.registration.registration, ["schemaVersion", "domain", "keyId", "payload", "signature"]);
78
+ const artifact = value.registration.registration;
79
+ requireValid(artifact.schemaVersion === 1 && artifact.domain === "ouro.sanctuary.host-registration.v1"
80
+ && typeof artifact.keyId === "string" && artifact.keyId.length > 0 && typeof artifact.signature === "string" && artifact.signature.length > 0
81
+ && artifact.payload !== null && typeof artifact.payload === "object" && !Array.isArray(artifact.payload));
82
+ exact(value.binding, ["agentRoot", "friendId", "requestId", "sessionKey", "sessionPath", "sessionEventId", "profileId", "profileVersion", "keyId", "publicKeyDigest", "targetHost"]);
83
+ const binding = value.binding;
84
+ requireValid(binding.profileId === "sanctuary-owner" && Number.isSafeInteger(binding.profileVersion) && binding.profileVersion >= 9);
85
+ requireValid(Object.entries(binding).every(([key, item]) => key === "profileVersion" || (typeof item === "string" && item.length > 0 && item.length <= 4096)));
86
+ requireValid(/^sha256:[a-f0-9]{64}$/u.test(binding.publicKeyDigest));
87
+ validateCheckpoint(value.checkpoint, value.approvalId, binding.targetHost);
88
+ }
89
+ function validateCheckpoint(value, approvalId, targetHost) {
90
+ exact(value, ["approvalId", "checkpointDigest", "baseSessionRevision", "suspendedSessionRevision", "argumentDigest", "schemaDigest", "toolDigest", "policyDigest", "preCallDigest", "preCallMessages", "frozenAssistantMessage"]);
91
+ const c = value;
92
+ requireValid(c.approvalId === approvalId && Array.isArray(c.preCallMessages));
93
+ requireValid(c.preCallMessages.every((message) => message !== null && typeof message === "object"
94
+ && ["system", "developer", "user", "assistant", "tool", "function"].includes(message.role)));
95
+ requireValid(["checkpointDigest", "baseSessionRevision", "suspendedSessionRevision", "argumentDigest", "schemaDigest", "toolDigest", "policyDigest", "preCallDigest"]
96
+ .every((key) => typeof c[key] === "string" && /^[a-f0-9]{64}$/u.test(String(c[key]))));
97
+ requireValid(c.preCallDigest === (0, tool_arguments_1.digestJson)(c.preCallMessages) && c.checkpointDigest === (0, tool_approval_1.digestApprovalSuspensionCheckpointPayload)(c));
98
+ requireValid(c.suspendedSessionRevision === c.baseSessionRevision);
99
+ requireValid(c.frozenAssistantMessage !== null && typeof c.frozenAssistantMessage === "object" && !Array.isArray(c.frozenAssistantMessage));
100
+ const calls = c.frozenAssistantMessage.tool_calls;
101
+ requireValid(Array.isArray(calls) && calls.length === 1);
102
+ exact(calls[0], ["id", "type", "function"]);
103
+ requireValid(c.frozenAssistantMessage.role === "assistant" && calls[0].type === "function" && typeof calls[0].id === "string" && calls[0].id.length > 0);
104
+ exact(calls[0].function, ["name", "arguments"]);
105
+ requireValid(calls[0].function.name === "sanctuary_host_execute" && typeof calls[0].function.arguments === "string");
106
+ const args = JSON.parse(calls[0].function.arguments);
107
+ requireValid((0, tool_arguments_1.digestJson)(args) === c.argumentDigest);
108
+ (0, tools_sanctuary_host_1.validateRootHostToolArguments)(args, targetHost);
109
+ }
110
+ function checkDefinition(checkpoint, definition) {
111
+ const args = JSON.parse(checkpoint.frozenAssistantMessage.tool_calls[0].function.arguments);
112
+ const policy = definition.approvalPolicy?.(args);
113
+ requireValid(policy?.kind === "required");
114
+ requireValid(checkpoint.schemaDigest === (0, tool_arguments_1.digestJson)(definition.tool.function.parameters));
115
+ requireValid(checkpoint.toolDigest === (0, tool_approval_1.digestApprovalToolDefinition)(definition, checkpoint.schemaDigest, policy.policyId));
116
+ requireValid(checkpoint.policyDigest === (0, tool_arguments_1.digestJson)({ policyId: policy.policyId, actionClass: policy.actionClass, classification: "required" }));
117
+ return policy;
118
+ }
119
+ function classifyRootHostPendingApproval(value) {
120
+ validate(value);
121
+ if (value.continuationState !== "pending")
122
+ return value.continuationState;
123
+ if (value.reconciliationRequired)
124
+ return "reconciliation_required";
125
+ return value.executionRequested ? "executing" : "pending";
126
+ }
127
+ class FileRootHostPendingApprovalStore {
128
+ filePath;
129
+ constructor(agentRoot) { this.filePath = path.join(agentRoot, "state", "root-host-approvals", "v1", "pending.json"); }
130
+ list() {
131
+ return (0, session_transaction_1.withImmediateSessionTurnLease)(this.filePath, (lease) => {
132
+ const transaction = (0, session_transaction_1.readSessionTransaction)(this.filePath, lease);
133
+ if (!transaction.bytes) {
134
+ requireValid(!(0, node_fs_1.existsSync)(this.filePath));
135
+ return [];
136
+ }
137
+ exact(transaction.value, ["schemaVersion", "records"]);
138
+ requireValid(transaction.value.schemaVersion === 1 && Array.isArray(transaction.value.records));
139
+ const records = transaction.value.records;
140
+ records.forEach(validate);
141
+ requireValid(new Set(records.map((record) => record.approvalId)).size === records.length
142
+ && new Set(records.map((record) => record.registration.registrationId)).size === records.length
143
+ && new Set(records.map((record) => record.registration.telegramMessageId)).size === records.length);
144
+ return structuredClone(records);
145
+ });
146
+ }
147
+ put(record) {
148
+ validate(record);
149
+ (0, session_transaction_1.withImmediateSessionTurnLease)(this.filePath, (lease) => {
150
+ const revision = (0, session_transaction_1.readSessionTransaction)(this.filePath, lease).revision;
151
+ const records = this.list();
152
+ const previous = records.find((item) => item.approvalId === record.approvalId);
153
+ if (previous) {
154
+ requireValid((0, node_util_1.isDeepStrictEqual)(previous.binding, record.binding) && (0, node_util_1.isDeepStrictEqual)(previous.registration, record.registration)
155
+ && (0, node_util_1.isDeepStrictEqual)(previous.checkpoint, record.checkpoint));
156
+ const phases = ["pending", "materialized", "attempted", "complete"];
157
+ requireValid(phases.indexOf(record.continuationState) >= phases.indexOf(previous.continuationState)
158
+ && (!previous.executionRequested || record.executionRequested)
159
+ && (previous.materializedSessionRevision === null || previous.materializedSessionRevision === record.materializedSessionRevision));
160
+ Object.assign(previous, structuredClone(record));
161
+ }
162
+ else {
163
+ requireValid(!records.some((item) => item.registration.registrationId === record.registration.registrationId
164
+ || item.registration.telegramMessageId === record.registration.telegramMessageId));
165
+ records.push(structuredClone(record));
166
+ }
167
+ (0, session_transaction_1.writeSessionTransaction)(this.filePath, { schemaVersion: 1, records }, { lease, expectedRevision: revision });
168
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.root_host_pending_saved", message: "root host continuation checkpoint saved", meta: { approvalId: record.approvalId, state: classifyRootHostPendingApproval(record) } });
169
+ });
170
+ }
171
+ }
172
+ exports.FileRootHostPendingApprovalStore = FileRootHostPendingApprovalStore;
173
+ function terminalProjection(record, status) {
174
+ const call = record.checkpoint.frozenAssistantMessage.tool_calls[0];
175
+ const common = { approvalId: record.approvalId, toolCallId: call.id };
176
+ if (status.state === "denied" || status.state === "expired")
177
+ return { ...common, state: status.state, result: null };
178
+ if (status.state !== "executed")
179
+ return null;
180
+ requireValid(status.receipt && status.permit);
181
+ const state = status.receipt.payload.state;
182
+ requireValid(state === "verified" || state === "failed" || state === "ambiguous");
183
+ requireValid(status.receipt.payload.cleanup === "cgroup_empty");
184
+ return { ...common, state: state === "verified" ? "succeeded" : state === "failed" ? "failed" : "attempted_indeterminate", result: JSON.stringify(status.receipt) };
185
+ }
186
+ function correlation(record) {
187
+ const b = record.binding;
188
+ return {
189
+ registrationId: record.registration.registrationId, residentFriendId: b.friendId,
190
+ relationshipProfileId: b.profileId, relationshipProfileVersion: b.profileVersion, requestId: b.requestId,
191
+ sessionKey: b.sessionKey, sessionEventId: b.sessionEventId, residentApprovalId: record.approvalId, stewardPolicy: null,
192
+ };
193
+ }
194
+ function createRootHostApprovalRuntime(options) {
195
+ const store = new FileRootHostPendingApprovalStore(options.agentRoot);
196
+ const port = options.port;
197
+ const barrier = options.effectBarrier ?? (() => undefined);
198
+ const readStatus = async (record) => {
199
+ const status = await port.status(record.registration);
200
+ if (status.permit)
201
+ requireValid(Object.entries(correlation(record)).every(([key, value]) => (0, node_util_1.isDeepStrictEqual)(status.permit.payload[key], value)));
202
+ return status;
203
+ };
204
+ const current = async (record) => {
205
+ requireValid(record.binding.agentRoot === options.agentRoot);
206
+ if (!await port.refresh())
207
+ throw new Error("Root host authority is unavailable");
208
+ const context = await options.resolveContext(record.binding);
209
+ requireValid(context.rootHost?.port === port);
210
+ await (0, tools_sanctuary_host_1.authorizeRootHostContext)(context, record.binding);
211
+ const owner = { agentName: "sanctuary", agentRoot: options.agentRoot };
212
+ const runtime = await (options.dependencies?.getProviderRuntime ?? core_1.getProviderRuntime)("human", owner);
213
+ const mcp = await (options.dependencies?.getMcp ?? mcp_manager_1.getSharedMcpManager)(owner) ?? undefined;
214
+ if (!port.isHealthy() && !await port.refresh())
215
+ throw new Error("Root host authority is unavailable");
216
+ const select = () => (0, tools_1.selectToolsForChannel)((0, friends_1.getChannelCapabilities)("telegram"), context.context?.friend.toolPreferences, context.context, runtime.capabilities, mcp, runtime.model, context);
217
+ const toolContext = { ...context, toolSelection: select(), selectCurrentTools: select };
218
+ const call = record.checkpoint.frozenAssistantMessage.tool_calls[0];
219
+ requireValid(call.type === "function");
220
+ const args = JSON.parse(call.function.arguments);
221
+ const preflight = await (0, tools_1.preflightToolCall)("sanctuary_host_execute", args, toolContext);
222
+ if (preflight.kind === "rejected_before_handler" && preflight.error && !(preflight.error instanceof tools_sanctuary_host_1.RootHostAuthorizationError))
223
+ throw preflight.error;
224
+ requireValid(preflight.kind === "ready" && preflight.definition);
225
+ checkDefinition(record.checkpoint, preflight.definition);
226
+ const proposed = (0, tools_sanctuary_host_1.validateRootHostToolArguments)(args, record.binding.targetHost);
227
+ for (const [key, value] of Object.entries(proposed))
228
+ requireValid((0, node_util_1.isDeepStrictEqual)(record.registration.registration.payload[key], value));
229
+ return { toolContext, providerRuntimeOverride: runtime, mcpManager: mcp };
230
+ };
231
+ const coordinator = (context) => ({
232
+ async propose(request) {
233
+ barrier();
234
+ const live = request.liveToolContext;
235
+ const { liveToolContext: _live, ...proposal } = request;
236
+ request = structuredClone(proposal);
237
+ requireValid(live?.rootHost?.port === port && request.toolCall.type === "function" && request.toolCall.function.name === "sanctuary_host_execute");
238
+ requireValid(live.currentSession?.sessionPath === context.sessionPath && live.agentRoot === options.agentRoot);
239
+ const binding = await (0, tools_sanctuary_host_1.authorizeRootHostContext)(live);
240
+ const preflight = await (0, tools_1.preflightToolCall)("sanctuary_host_execute", request.arguments, live);
241
+ requireValid(preflight.kind === "ready" && preflight.definition);
242
+ requireValid((0, session_transaction_1.withImmediateSessionTurnLease)(context.sessionPath, (lease) => (0, session_transaction_1.readSessionTransaction)(context.sessionPath, lease).revision) === context.baseSessionRevision);
243
+ const args = (0, tools_sanctuary_host_1.validateRootHostToolArguments)(request.arguments, binding.targetHost);
244
+ const approvalId = `root-host-${(0, node_crypto_1.randomUUID)()}`;
245
+ const draft = {
246
+ approvalId, baseSessionRevision: context.baseSessionRevision, argumentDigest: (0, tool_arguments_1.digestJson)(request.arguments),
247
+ schemaDigest: request.schemaDigest, toolDigest: request.toolDigest, policyDigest: request.policyDigest,
248
+ preCallDigest: (0, tool_arguments_1.digestJson)(request.preCallMessages), preCallMessages: structuredClone(request.preCallMessages),
249
+ frozenAssistantMessage: structuredClone(request.frozenAssistantMessage),
250
+ };
251
+ const checkpoint = { ...draft, suspendedSessionRevision: context.baseSessionRevision, checkpointDigest: (0, tool_approval_1.digestApprovalSuspensionCheckpointPayload)(draft) };
252
+ validateCheckpoint(checkpoint, approvalId, binding.targetHost);
253
+ requireValid((0, node_util_1.isDeepStrictEqual)(request.frozenAssistantMessage.tool_calls[0], request.toolCall));
254
+ const policy = checkDefinition(checkpoint, preflight.definition);
255
+ requireValid(request.policyId === policy.policyId && request.actionClass === policy.actionClass);
256
+ const observation = live.rootHost.observation;
257
+ barrier();
258
+ const registration = await port.register({ ...args, ownerObservation: {
259
+ digest: observation.observationDigest, updateId: observation.updateId, userId: observation.userId, chatId: observation.chatId, messageId: observation.messageId,
260
+ } });
261
+ await (0, tools_sanctuary_host_1.authorizeRootHostContext)(live);
262
+ requireValid((0, session_transaction_1.withImmediateSessionTurnLease)(context.sessionPath, (lease) => (0, session_transaction_1.readSessionTransaction)(context.sessionPath, lease).revision) === context.baseSessionRevision);
263
+ barrier();
264
+ store.put({ schemaVersion: 1, approvalId, registration, binding, checkpoint, executionRequested: false, continuationState: "pending", materializedSessionRevision: null, rootStatusDigest: null, reconciliationRequired: false });
265
+ return { approvalId, checkpointDigest: checkpoint.checkpointDigest, suspendedSessionRevision: checkpoint.suspendedSessionRevision };
266
+ },
267
+ });
268
+ const reconcile = async (initial) => {
269
+ // Never choose a filesystem path from an unvalidated persisted binding.
270
+ requireValid(initial.binding.agentRoot === options.agentRoot
271
+ && initial.binding.sessionPath === (0, shared_turn_1.getSenseSessionPath)("sanctuary", initial.binding.friendId, "telegram", initial.binding.sessionKey, options.agentRoot));
272
+ await (0, session_transaction_1.withSessionTurnLease)(initial.binding.sessionPath, async (lease) => {
273
+ const record = store.list().find((item) => item.approvalId === initial.approvalId);
274
+ if (record.continuationState === "complete")
275
+ return;
276
+ barrier();
277
+ let status = await readStatus(record);
278
+ record.rootStatusDigest = status.statusDigest;
279
+ if (status.state === "approved" && !record.executionRequested) {
280
+ await current(record);
281
+ requireValid((0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision === record.checkpoint.suspendedSessionRevision);
282
+ record.executionRequested = true;
283
+ barrier();
284
+ store.put(record);
285
+ try {
286
+ await port.execute(correlation(record));
287
+ }
288
+ catch {
289
+ record.reconciliationRequired = true;
290
+ store.put(record);
291
+ return;
292
+ }
293
+ status = await readStatus(record);
294
+ record.rootStatusDigest = status.statusDigest;
295
+ }
296
+ record.reconciliationRequired = status.execution === "reconciliation_required" || (record.executionRequested && status.state === "approved");
297
+ barrier();
298
+ store.put(record);
299
+ const terminal = terminalProjection(record, status);
300
+ if (!terminal)
301
+ return;
302
+ const expectedRevision = record.materializedSessionRevision ?? record.checkpoint.suspendedSessionRevision;
303
+ if (record.continuationState === "attempted"
304
+ || (0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision !== expectedRevision) {
305
+ record.continuationState = "complete";
306
+ barrier();
307
+ store.put(record);
308
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.root_host_terminal_control", message: "root outcome retained without retrying an attempted or superseded continuation", meta: { approvalId: record.approvalId } });
309
+ return;
310
+ }
311
+ try {
312
+ await current(record);
313
+ requireValid((0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision === expectedRevision);
314
+ }
315
+ catch (error) {
316
+ if (!(error instanceof tools_sanctuary_host_1.RootHostAuthorizationError) && !(error instanceof RootHostPendingApprovalError))
317
+ throw error;
318
+ record.continuationState = "complete";
319
+ barrier();
320
+ store.put(record);
321
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.root_host_authority_control", message: "root terminal outcome retained without a continuation because current owner authority changed", meta: { approvalId: record.approvalId, state: terminal.state } });
322
+ return;
323
+ }
324
+ let authorized = false;
325
+ const continuationCoordinator = {
326
+ propose: (request) => (options.approvalCoordinatorFactory ?? coordinator)({
327
+ sessionPath: record.binding.sessionPath, baseSessionRevision: (0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision,
328
+ }).propose(request),
329
+ };
330
+ await (0, core_1.resumeApprovalContinuation)({
331
+ record: terminal, checkpoint: record.checkpoint, currentSessionRevision: (0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision,
332
+ sessionMessages: record.continuationState === "pending" ? record.checkpoint.preCallMessages : (0, context_1.loadSession)(record.binding.sessionPath).messages,
333
+ callbacks: {}, channel: "telegram",
334
+ claimContinuation: () => ({ claimed: true, interruptedAfterAttempt: false, record: { continuationState: record.continuationState === "materialized" ? "materialized" : "claimed" } }),
335
+ markContinuationMaterialized: () => { barrier(); record.materializedSessionRevision = (0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision; record.continuationState = "materialized"; store.put(record); },
336
+ markContinuationAttempted: () => { barrier(); record.continuationState = "attempted"; store.put(record); },
337
+ completeContinuation: () => { barrier(); record.continuationState = "complete"; store.put(record); },
338
+ runAgent: options.dependencies?.runProvider ?? core_1.runAgent,
339
+ revalidate: async () => {
340
+ try {
341
+ const live = await current(record);
342
+ requireValid((0, session_transaction_1.readSessionTransaction)(record.binding.sessionPath, lease).revision === record.materializedSessionRevision);
343
+ authorized = true;
344
+ return { ...live, approvalCoordinator: continuationCoordinator };
345
+ }
346
+ catch (error) {
347
+ if (!(error instanceof tools_sanctuary_host_1.RootHostAuthorizationError) && !(error instanceof RootHostPendingApprovalError))
348
+ throw error;
349
+ return null;
350
+ }
351
+ },
352
+ persist: (messages, result) => { barrier(); (0, context_1.saveSession)(record.binding.sessionPath, messages, result?.usage, undefined, lease); },
353
+ deliver: async (text) => {
354
+ barrier();
355
+ if (authorized)
356
+ await options.deliver(text, record.approvalId);
357
+ else
358
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.root_host_terminal_control", message: "root owns the terminal host approval card; no unauthorized resident message was sent", meta: { approvalId: record.approvalId, state: terminal.state } });
359
+ },
360
+ });
361
+ });
362
+ };
363
+ const recover = async (registrationId) => {
364
+ for (const record of store.list()) {
365
+ if (record.continuationState === "complete" || (registrationId && registrationId !== record.registration.registrationId))
366
+ continue;
367
+ try {
368
+ await reconcile(record);
369
+ }
370
+ catch (error) {
371
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.root_host_reconciliation_required", message: "root host continuation remains pending reconciliation", meta: { approvalId: record.approvalId, category: error instanceof Error ? error.name : "unknown" } });
372
+ }
373
+ }
374
+ };
375
+ return {
376
+ coordinator,
377
+ recover: () => recover(),
378
+ async handleUpdate(update) {
379
+ const callback = port.callbackForUpdate(update);
380
+ if (!callback.handled)
381
+ return false;
382
+ if (callback.registrationId) {
383
+ try {
384
+ await recover(callback.registrationId);
385
+ }
386
+ catch {
387
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.root_host_reconciliation_required", message: "claimed root callback retained despite unavailable local pending state", meta: { registrationId: callback.registrationId } });
388
+ }
389
+ }
390
+ return true;
391
+ },
392
+ };
393
+ }
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.openSanctuaryResidentAuthority = openSanctuaryResidentAuthority;
37
+ const node_crypto_1 = require("node:crypto");
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const sanctuary_telegram_authority_service_1 = require("../heart/daemon/sanctuary-telegram-authority-service");
41
+ const sanctuary_authority_codec_1 = require("../heart/daemon/sanctuary-authority-codec");
42
+ const runtime_1 = require("../nerves/runtime");
43
+ const telegram_authority_transport_1 = require("./telegram-authority-transport");
44
+ function openSanctuaryResidentAuthority(runtime, machineRuntime, options = {}) {
45
+ if ([runtime, machineRuntime].some((config) => Object.prototype.hasOwnProperty.call(config, "telegramBotToken"))) {
46
+ throw new Error("Sanctuary resident Telegram token residue must be removed by the root migration");
47
+ }
48
+ const configPath = options.configPath ?? "/run/ouro-authority/resident.json";
49
+ const expectedUid = options.expectedUid ?? 0;
50
+ const expectedGid = options.expectedGid ?? 10001;
51
+ const root = path.dirname(configPath);
52
+ const parent = fs.lstatSync(root);
53
+ if (!parent.isDirectory() || parent.uid !== expectedUid || parent.gid !== expectedGid || (parent.mode & 0o7777) !== 0o750 || fs.realpathSync(root) !== root) {
54
+ throw new Error("Sanctuary resident authority directory is unsafe");
55
+ }
56
+ const descriptor = fs.openSync(configPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
57
+ let value;
58
+ try {
59
+ const stat = fs.fstatSync(descriptor);
60
+ if (!stat.isFile() || stat.uid !== expectedUid || stat.gid !== expectedGid || (stat.mode & 0o7777) !== 0o640 || stat.nlink !== 1 || stat.size > 8192)
61
+ throw new Error("Sanctuary resident authority pins are unsafe");
62
+ value = JSON.parse(fs.readFileSync(descriptor, "utf8"));
63
+ }
64
+ finally {
65
+ fs.closeSync(descriptor);
66
+ }
67
+ if (!value || typeof value !== "object" || Array.isArray(value))
68
+ throw new Error("Sanctuary resident authority pins are invalid");
69
+ const config = value;
70
+ if (Object.keys(config).sort().join(",") !== "botId,keyId,ownerChatId,ownerUserId,publicKeyDigest,publicKeyPem,schemaVersion,targetHost"
71
+ || config.schemaVersion !== 1 || config.targetHost !== "sanctuary"
72
+ || !["botId", "ownerUserId", "ownerChatId"].every((key) => typeof config[key] === "string" && /^[1-9][0-9]*$/u.test(config[key]))
73
+ || config.ownerUserId !== config.ownerChatId || typeof config.keyId !== "string" || !/^[A-Za-z0-9_-]{1,128}$/u.test(config.keyId)
74
+ || typeof config.publicKeyPem !== "string" || typeof config.publicKeyDigest !== "string")
75
+ throw new Error("Sanctuary resident authority identity is invalid");
76
+ const publicKey = (0, node_crypto_1.createPublicKey)(config.publicKeyPem);
77
+ if (publicKey.asymmetricKeyType !== "ed25519" || config.publicKeyDigest !== `sha256:${(0, node_crypto_1.createHash)("sha256").update(publicKey.export({ format: "der", type: "spki" })).digest("hex")}`)
78
+ throw new Error("Sanctuary resident authority issuer pin changed");
79
+ const client = (options.createClient ?? ((socketPath) => new sanctuary_telegram_authority_service_1.SocketSanctuaryTelegramAuthorityClient(socketPath)))(path.join(root, "authority.sock"));
80
+ const credentials = { botId: config.botId, authorizedUserId: config.ownerUserId, authorizedChatId: config.ownerChatId };
81
+ const authorityTransport = (0, telegram_authority_transport_1.createSanctuaryTelegramAuthorityTransport)(client, {
82
+ expectedTargetHost: config.targetHost, expectedBotId: credentials.botId, expectedOwnerUserId: credentials.authorizedUserId,
83
+ expectedOwnerChatId: credentials.authorizedChatId, expectedKeyId: config.keyId, expectedPublicKeyDigest: config.publicKeyDigest, publicKey,
84
+ });
85
+ async function cursorSnapshot(at) {
86
+ const snapshot = (0, sanctuary_authority_codec_1.verifyAuthorityPayload)({
87
+ artifact: await client.request("telegram.cursor.snapshot", {}), expectedDomain: "ouro.sanctuary.telegram-cursor.v1", expectedKeyId: config.keyId, publicKey,
88
+ });
89
+ const now = at ?? Date.now();
90
+ if (!snapshot || Object.keys(snapshot).sort().join(",") !== "botId,cursor,keyId,observedAt,ownerChatId,ownerUserId,pendingUpdateIds,progressDigest,publicKeyDigest,targetHost"
91
+ || !["targetHost", "botId", "ownerUserId", "ownerChatId", "keyId", "publicKeyDigest"].every((key) => snapshot[key] === config[key])
92
+ || !Number.isSafeInteger(snapshot.cursor) || snapshot.cursor < 0
93
+ || !Array.isArray(snapshot.pendingUpdateIds) || !snapshot.pendingUpdateIds.every((id, index, ids) => Number.isSafeInteger(id) && id >= snapshot.cursor && (index === 0 || id > ids[index - 1]))
94
+ || typeof snapshot.progressDigest !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(snapshot.progressDigest)
95
+ || typeof snapshot.observedAt !== "string" || !Number.isFinite(Date.parse(snapshot.observedAt)) || new Date(snapshot.observedAt).toISOString() !== snapshot.observedAt
96
+ || Date.parse(snapshot.observedAt) > now + 1000 || now - Date.parse(snapshot.observedAt) > 30_000)
97
+ throw new Error("Sanctuary authority cursor snapshot is invalid or stale");
98
+ return snapshot;
99
+ }
100
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.sanctuary_authority_pins_loaded", message: "Sanctuary resident authority pins loaded", meta: { keyId: config.keyId } });
101
+ return { credentials, authorityTransport, cursorSnapshot };
102
+ }
@@ -60,7 +60,6 @@ function evidenceInputs(agentName, result) {
60
60
  source: "sanctuary-health",
61
61
  eventType: "health.observed",
62
62
  eventId: incident.id,
63
- observationRevision: incident.observationRevision ?? revision,
64
63
  transition: "recovered",
65
64
  summary: `recovered: ${incident.summary}`,
66
65
  evidence: [`recovered: ${incident.summary}`],
@@ -132,7 +132,10 @@ function sanctuaryMediaCatalogRequiredToolCalls(request, advertisedToolNames) {
132
132
  const mentionsMedia = /\b(?:film|films|movie|movies|shows|tv|jellyfin|watch|shelf|stock|catalog|library|lib)\b/u.test(normalized);
133
133
  const asksCatalog = /\b(?:have|got|stock|catalog|library|lib|shelf|jellyfin|favorite|favourite|recommend|suggest|pick|watch|add|see|show|list|browse|access)\b/u.test(normalized);
134
134
  const requestedTitleQuery = requestedTitle(normalized);
135
- if ((!mentionsMedia && !requestedTitleQuery) || !asksCatalog)
135
+ const operationalOnly = /\b(?:restart|reboot|start|stop|stopped|stopping|running|down|investigate|redeploy|deploy|crash|crashed)\b/u.test(normalized)
136
+ && !requestedTitleQuery
137
+ && !/\b(?:film|films|movie|movies|shows|tv|watch|shelf|stock|catalog|library|lib|have|got|favorite|favourite|recommend|suggest|pick|add|see|show|list|browse|access|find|search|count|contents)\b/u.test(normalized);
138
+ if (operationalOnly || (!mentionsMedia && !requestedTitleQuery) || !asksCatalog)
136
139
  return undefined;
137
140
  const kind = requestKind(normalized, requestedTitleQuery);
138
141
  const asksForAddition = /\b(?:add|missing from|must get)\b/u.test(normalized);
@@ -274,6 +274,8 @@ function createSanctuaryToolContext(agentName) {
274
274
  acceptanceApproval: sanctuary_acceptance_marker_1.readSanctuaryAcceptanceApproval,
275
275
  reserveRoutineAction: (input) => (0, steward_policy_1.consumeRoutineActionGrant)(agentRoot, input),
276
276
  transitionRoutineAction: (input) => (0, steward_policy_1.transitionRoutineActionReceipt)(agentRoot, input),
277
+ withRoutineActionAttempt: (reservation, validate, attempt) => (0, steward_policy_1.withRoutineActionAttempt)(agentRoot, reservation, validate, attempt),
278
+ withApprovalPolicyLease: (operation) => (0, steward_policy_1.withStewardPolicyLease)(agentRoot, operation),
277
279
  });
278
280
  return {
279
281
  agentRoot,
@@ -524,6 +524,7 @@ function createTelegramAdmissionController(options) {
524
524
  const compensateFriend = async (record) => {
525
525
  if (!record.friendId)
526
526
  return;
527
+ await options.revokeCommunication?.({ userId: record.userId, chatId: record.chatId });
527
528
  const revocation = await options.revokeFriend({
528
529
  provider: "telegram-user",
529
530
  botId: record.botId,
@@ -562,6 +563,12 @@ function createTelegramAdmissionController(options) {
562
563
  }
563
564
  try {
564
565
  if (record.status === "friend_bound") {
566
+ await options.registerCommunication?.({
567
+ id: record.id,
568
+ updateId: record.updateId,
569
+ userId: record.userId,
570
+ chatId: record.chatId,
571
+ });
565
572
  if (record.quarantinedText === null || !record.friendId)
566
573
  throw new Error("Telegram admission lost input before durable ingress");
567
574
  const committedBase = await options.commitApprovedIngress({