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

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 (50) hide show
  1. package/assets/sanctuary-host-launcher.sh +16 -0
  2. package/changelog.json +16 -0
  3. package/deploy/unraid/Dockerfile +6 -0
  4. package/deploy/unraid/README.txt +296 -62
  5. package/deploy/unraid/docker-man-template-transaction.mjs +192 -7
  6. package/deploy/unraid/sanctuary-acceptance-adapter.sh +1 -1
  7. package/deploy/unraid/sanctuary-acceptance-contract.json +7 -7
  8. package/deploy/unraid/sanctuary-authority-installation.json +36 -0
  9. package/deploy/unraid/sanctuary-authority-service.sh +30 -0
  10. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +62 -9
  11. package/deploy/unraid/sanctuary-unit16-run.sh +15 -15
  12. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
  13. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +2 -2
  14. package/deploy/unraid/sanctuary.xml +2 -1
  15. package/dist/heart/core.js +2 -1
  16. package/dist/heart/daemon/container-spec-auditor-main.js +8 -7
  17. package/dist/heart/daemon/container-spec-auditor.js +16 -9
  18. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +95 -91
  19. package/dist/heart/daemon/sanctuary-acceptance-harness.js +95 -169
  20. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +4 -5
  21. package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
  22. package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
  23. package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
  24. package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
  25. package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +780 -0
  26. package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
  27. package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
  28. package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
  29. package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
  30. package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
  31. package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
  32. package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
  33. package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
  34. package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
  35. package/dist/heart/daemon/sanctuary-telegram-authority-service.js +615 -0
  36. package/dist/heart/daemon/sense-manager.js +20 -10
  37. package/dist/repertoire/tools-sanctuary-host.js +202 -0
  38. package/dist/repertoire/tools.js +15 -6
  39. package/dist/senses/private-runtime.js +2 -2
  40. package/dist/senses/root-host-approval-port.js +345 -0
  41. package/dist/senses/root-host-approval-runtime.js +393 -0
  42. package/dist/senses/sanctuary-authority-resident.js +102 -0
  43. package/dist/senses/telegram-admission.js +7 -0
  44. package/dist/senses/telegram-attachments.js +5 -1
  45. package/dist/senses/telegram-authority-transport.js +231 -0
  46. package/dist/senses/telegram-client.js +31 -8
  47. package/dist/senses/telegram-entry.js +1 -1
  48. package/dist/senses/telegram.js +168 -28
  49. package/npm-shrinkwrap.json +2 -2
  50. package/package.json +1 -1
@@ -48,20 +48,19 @@ const path = __importStar(require("node:path"));
48
48
  const node_util_1 = require("node:util");
49
49
  const friends_1 = require("@ouro.bot/friends");
50
50
  const runtime_1 = require("../../nerves/runtime");
51
- const telegram_1 = require("../../senses/telegram");
52
- const runtime_credentials_1 = require("../runtime-credentials");
51
+ const sanctuary_authority_resident_1 = require("../../senses/sanctuary-authority-resident");
53
52
  const relationship_authorization_1 = require("../../repertoire/relationship-authorization");
54
53
  const tools_1 = require("../../repertoire/tools");
55
54
  const MAX_ADAPTER_OUTPUT = 1_048_576;
56
55
  const DEFAULT_ADAPTER_TIMEOUT_MS = 240_000;
57
- const DEFAULT_TELEGRAM_TIMEOUT_MS = 10_000;
56
+ const DEFAULT_TELEGRAM_TIMEOUT_MS = 65_000;
58
57
  const PACKAGED_PROVENANCE_ADAPTER = "/opt/ouro/deploy/unraid/sanctuary-acceptance-adapter.sh";
59
58
  const OPAQUE_DIGEST = /^[0-9a-f]{64}$/u;
60
59
  function exactSanctuaryContainmentProfileBoundaries(value) {
61
60
  if (!value || typeof value !== "object" || Array.isArray(value))
62
61
  return false;
63
62
  const boundaries = value;
64
- const versions = { "sanctuary-owner": 8, "sanctuary-household": 5, "sanctuary-event": 4 };
63
+ const versions = { "sanctuary-owner": 9, "sanctuary-household": 5, "sanctuary-event": 4 };
65
64
  if (!(0, node_util_1.isDeepStrictEqual)(Object.keys(boundaries).sort(), Object.keys(versions).sort()))
66
65
  return false;
67
66
  const packageRoot = path.resolve(__dirname, "../../../deploy/unraid/sanctuary.ouro");
@@ -73,7 +72,7 @@ function exactSanctuaryContainmentProfileBoundaries(value) {
73
72
  return false;
74
73
  const boundary = raw;
75
74
  const capabilities = boundary.providerCapabilities;
76
- if (!Array.isArray(capabilities) || !capabilities.every((entry) => entry === "reasoning-effort" || entry === "phase-annotation")
75
+ if (!Array.isArray(capabilities) || !capabilities.every((entry) => entry === "reasoning-effort" || entry === "phase-annotation" || entry === "approval-continuation")
77
76
  || new Set(capabilities).size !== capabilities.length)
78
77
  return false;
79
78
  const profile = registry.profiles[id];
@@ -104,10 +103,8 @@ function createSanctuaryAcceptanceHarnessDependencies(secretFd = 3, options = {}
104
103
  const adapterTimeoutMs = options.adapterTimeoutMs ?? DEFAULT_ADAPTER_TIMEOUT_MS;
105
104
  const telegramTimeoutMs = options.telegramTimeoutMs ?? DEFAULT_TELEGRAM_TIMEOUT_MS;
106
105
  return {
106
+ gateway: () => (0, sanctuary_authority_resident_1.openSanctuaryResidentAuthority)({}, {}),
107
107
  readSecret: () => (0, node_fs_1.readFileSync)(secretFd, "utf8"),
108
- refreshRuntime: runtime_credentials_1.refreshRuntimeCredentialConfig,
109
- mergeRuntime: runtime_credentials_1.mergeRuntimeCredentialConfig,
110
- telegramCredentials: telegram_1.loadTelegramSenseCredentials,
111
108
  runAdapter: async (executable, payload, remainingMs) => {
112
109
  requireAbsoluteExecutable(executable);
113
110
  const result = (0, node_child_process_1.spawnSync)(executable, [], {
@@ -134,32 +131,12 @@ function createSanctuaryAcceptanceHarnessDependencies(secretFd = 3, options = {}
134
131
  }
135
132
  },
136
133
  realpath: node_fs_1.realpathSync,
137
- fetch,
138
134
  now: Date.now,
139
135
  randomBytes: node_crypto_1.randomBytes,
140
136
  sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
141
137
  telegramTimeoutMs,
142
138
  };
143
139
  }
144
- async function canonicalTelegramBootstrapToken(deps) {
145
- let refreshed;
146
- try {
147
- refreshed = await deps.refreshRuntime("sanctuary");
148
- }
149
- catch {
150
- throw new Error("Telegram runtime credentials are unavailable; actor: human-required; unlock or repair vault runtime/config");
151
- }
152
- if (!refreshed.ok)
153
- throw new Error("Telegram runtime credentials are unavailable; actor: human-required; unlock or repair vault runtime/config");
154
- try {
155
- const token = deps.telegramCredentials("sanctuary").botToken.trim();
156
- (0, telegram_1.telegramBotIdFromToken)(token);
157
- return token;
158
- }
159
- catch {
160
- throw new Error("Telegram runtime credentials are invalid; actor: human-required; repair vault runtime/config");
161
- }
162
- }
163
140
  function object(value, label) {
164
141
  if (!value || typeof value !== "object" || Array.isArray(value))
165
142
  throw new Error(`${label} must be an object`);
@@ -357,39 +334,6 @@ function safeErrorCategory(error) {
357
334
  function failedCheckpoint(root, filePath, base, error) {
358
335
  replaceCheckpoint(root, filePath, { ...base, phase: "failed", errorCategory: safeErrorCategory(error) });
359
336
  }
360
- async function telegramRequest(deps, token, method, body, requestTimeoutMs = deps.telegramTimeoutMs ?? DEFAULT_TELEGRAM_TIMEOUT_MS) {
361
- const controller = new AbortController();
362
- const timeout = setTimeout(() => controller.abort(), requestTimeoutMs);
363
- let response;
364
- try {
365
- response = await deps.fetch(`https://api.telegram.org/bot${token}/${method}`, {
366
- ...(body ? {
367
- method: "POST",
368
- headers: { "content-type": "application/json" },
369
- body: JSON.stringify(body),
370
- } : {}),
371
- signal: controller.signal,
372
- });
373
- }
374
- catch (error) {
375
- if (controller.signal.aborted)
376
- throw new Error("Telegram request timed out");
377
- throw error;
378
- }
379
- finally {
380
- clearTimeout(timeout);
381
- }
382
- let envelope;
383
- try {
384
- envelope = object(await response.json(), "Telegram response");
385
- }
386
- catch {
387
- throw new Error("Telegram returned invalid JSON");
388
- }
389
- if (!response.ok || envelope.ok !== true)
390
- throw new Error("Telegram request failed");
391
- return envelope.result;
392
- }
393
337
  function telegramBootstrapRequestError(method, error) {
394
338
  const outcome = error instanceof Error && /timed out/iu.test(error.message) ? "timed out" : "failed";
395
339
  return new Error(`Telegram ${method} ${outcome}; actor: agent-runnable; retry Telegram bootstrap`);
@@ -401,7 +345,7 @@ exports.SANCTUARY_UNIT_16_EVIDENCE_LABELS = [
401
345
  "unit-16a-pre-reboot-checkpoint",
402
346
  "unit-16a-reboot-request",
403
347
  "unit-16a-boot-recovery-milestones",
404
- "unit-16b-runtime-vault-containment",
348
+ "unit-16b-runtime-vault-readiness",
405
349
  "unit-16c-provider-readiness",
406
350
  "unit-16d-whats-up",
407
351
  "unit-16d-1-space",
@@ -470,6 +414,8 @@ function requiredInteger(value, key, expected, label) {
470
414
  throw new Error(`${label} ${key} must equal ${expected}`);
471
415
  }
472
416
  function validateSanctuaryUnit16EvidenceAssertions(label, raw) {
417
+ if (!exports.SANCTUARY_UNIT_16_EVIDENCE_LABELS.includes(label))
418
+ throw new Error("Sanctuary evidence label is retired or unsupported");
473
419
  const value = object(raw, `${label} assertions`);
474
420
  const exact = (keys) => exactObjectKeys(value, keys, `${label} assertions`);
475
421
  const allTrue = (keys) => keys.forEach((key) => requiredTrue(value, key, label));
@@ -506,13 +452,10 @@ function validateSanctuaryUnit16EvidenceAssertions(label, raw) {
506
452
  allTrue(["arrayReady", "bootIdentityChanged", "butlerReady", "dockerReady", "hostReady", "postbootIntegrityPreserved", "sshReady", "tailscaleReady"]);
507
453
  opaqueDigest(value.processBindingDigest, `${label} processBindingDigest`);
508
454
  break;
509
- case "unit-16b-runtime-vault-containment":
510
- exact(["autostartExact", "exactImage", "manualAuthRequired", "mountCount", "mountsExact", "nonRootUid", "publishedPortCount", "readOnlyRoot", "updaterDisabled", "vaultUnlocked"]);
511
- allTrue(["autostartExact", "exactImage", "mountsExact", "readOnlyRoot", "updaterDisabled", "vaultUnlocked"]);
455
+ case "unit-16b-runtime-vault-readiness":
456
+ exact(["autostartExact", "exactImage", "manualAuthRequired", "updaterDisabled", "vaultUnlocked"]);
457
+ allTrue(["autostartExact", "exactImage", "updaterDisabled", "vaultUnlocked"]);
512
458
  requiredFalse(value, "manualAuthRequired", label);
513
- requiredInteger(value, "mountCount", 4, label);
514
- requiredInteger(value, "nonRootUid", 10001, label);
515
- requiredInteger(value, "publishedPortCount", 0, label);
516
459
  break;
517
460
  case "unit-16c-provider-readiness":
518
461
  exact(["innerReady", "laneSelectionExact", "outwardReady", "silentFallback", "singleCredentialExact", "vaultCoordinatesExact"]);
@@ -566,7 +509,7 @@ function validateSanctuaryUnit16EvidenceAssertions(label, raw) {
566
509
  integer(value.auditLifecyclePairCount, `${label} auditLifecyclePairCount`, 1);
567
510
  if (text(value.containerUser, `${label} containerUser`) !== "10001:10001" || text(value.liveProcessUser, `${label} liveProcessUser`) !== "10001:10001" || text(value.networkMode, `${label} networkMode`) !== "host")
568
511
  throw new Error(`${label} container identity or network is invalid`);
569
- requiredInteger(value, "mountCount", 3, label);
512
+ requiredInteger(value, "mountCount", 4, label);
570
513
  requiredInteger(value, "typedWriteExecutorCount", 1, label);
571
514
  allTrue(["writeApprovalPolicyExact", "mountsExact", "securityExact", "updaterDisabled"]);
572
515
  break;
@@ -703,7 +646,7 @@ exports.SANCTUARY_SCENARIO_GATES = {
703
646
  "unit-16a-pre-reboot-checkpoint": "none",
704
647
  "unit-16a-reboot-request": "none",
705
648
  "unit-16a-boot-recovery-milestones": "none",
706
- "unit-16b-runtime-vault-containment": "none",
649
+ "unit-16b-runtime-vault-readiness": "none",
707
650
  "unit-16c-provider-readiness": "none",
708
651
  "unit-16d-whats-up": "authorized-telegram-message",
709
652
  "unit-16d-1-space": "authorized-telegram-message",
@@ -727,7 +670,7 @@ exports.SANCTUARY_SCENARIO_SOURCES = {
727
670
  "unit-16a-pre-reboot-checkpoint": ["telegram-audit", "telegram-offset", "approval-journal", "container-inspect", "cron-runtime", "reboot-checkpoint"],
728
671
  "unit-16a-reboot-request": ["reboot-checkpoint"],
729
672
  "unit-16a-boot-recovery-milestones": ["reboot-checkpoint", "container-inspect"],
730
- "unit-16b-runtime-vault-containment": ["container-inspect"],
673
+ "unit-16b-runtime-vault-readiness": ["container-inspect"],
731
674
  "unit-16c-provider-readiness": ["provider-live-check"],
732
675
  "unit-16d-whats-up": ["telegram-audit", "telegram-offset", "telegram-turn-receipts", "live-grounding-read"],
733
676
  "unit-16d-1-space": ["telegram-audit", "telegram-offset", "telegram-turn-receipts", "restart-attempt-ledger", "container-inspect", "live-grounding-read"],
@@ -945,110 +888,94 @@ async function telegramBootstrap(config, deps) {
945
888
  const pollTimeoutSeconds = integer(config.pollTimeoutSeconds, "pollTimeoutSeconds", 1);
946
889
  if (pollTimeoutSeconds > 50)
947
890
  throw new Error("Telegram poll timeout exceeds 50 seconds");
948
- const token = deps.readSecret().trim();
949
- let getMe;
950
- try {
951
- getMe = await telegramRequest(deps, token, "getMe");
952
- }
953
- catch (error) {
954
- throw telegramBootstrapRequestError("getMe", error);
955
- }
956
- const bot = object(getMe, "Telegram getMe result");
957
- if (String(bot.id) !== expectedBotId || bot.username !== expectedUsername) {
958
- throw new Error("Telegram bot identity mismatch; actor: human-required; repair vault runtime/config");
891
+ const gateway = deps.gateway?.();
892
+ if (!gateway)
893
+ throw new Error("Telegram root gateway is unavailable");
894
+ async function bounded(operation, timeoutMs = deps.telegramTimeoutMs ?? DEFAULT_TELEGRAM_TIMEOUT_MS) {
895
+ let timer;
896
+ try {
897
+ return await Promise.race([
898
+ operation(),
899
+ new Promise((_resolve, reject) => {
900
+ timer = setTimeout(() => reject(new Error("Telegram gateway request timed out")), timeoutMs);
901
+ }),
902
+ ]);
903
+ }
904
+ finally {
905
+ clearTimeout(timer);
906
+ }
959
907
  }
960
- const nonce = deps.randomBytes(16).toString("hex");
961
- const base = {
962
- schemaVersion: 1,
963
- operation: "telegram-bootstrap",
964
- phase: "preflight",
965
- botIdentityDigest: digest({ id: expectedBotId, username: expectedUsername }),
966
- startedAt: deps.now(),
967
- };
968
- initializeCheckpoint(root, evidencePath, base);
969
908
  try {
970
- const quiesced = object(await deps.runAdapter(pollerAdapter, {
971
- operation: "quiesce_telegram_poller",
972
- expectedState: "stopped",
973
- }), "Telegram poller precondition");
974
- exactObjectKeys(quiesced, ["activePollers", "quiesced"], "Telegram poller precondition");
975
- if (quiesced.quiesced !== true || quiesced.activePollers !== 0)
976
- throw new Error("Telegram competing poller is not quiescent");
977
- writeAtomicPrivateText(root, noncePath, nonce);
978
- const deadline = deps.now() + deadlineMs;
979
- let nextOffset = currentOffset;
980
- let match;
981
- while (deps.now() < deadline && !match) {
982
- let updates;
983
- try {
984
- updates = await telegramRequest(deps, token, "getUpdates", {
985
- offset: nextOffset,
986
- timeout: pollTimeoutSeconds,
987
- allowed_updates: ["message"],
988
- }, (pollTimeoutSeconds + 5) * 1_000);
989
- }
990
- catch (error) {
991
- throw telegramBootstrapRequestError("getUpdates", error);
992
- }
993
- if (!Array.isArray(updates))
994
- throw new Error("Telegram getUpdates result must be an array");
995
- const parsed = updates.map((entry) => object(entry, "Telegram update"));
996
- const updateIds = parsed.map((entry) => integer(entry.update_id, "Telegram update id"));
997
- if (updateIds.length > 0)
998
- nextOffset = Math.max(nextOffset, ...updateIds.map((id) => id + 1));
999
- const matches = parsed.filter((entry) => {
1000
- const message = entry.message && typeof entry.message === "object" && !Array.isArray(entry.message) ? entry.message : null;
1001
- const chat = message?.chat && typeof message.chat === "object" && !Array.isArray(message.chat) ? message.chat : null;
1002
- return message?.text === nonce
1003
- && chat?.type === "private"
1004
- && typeof message?.from === "object"
1005
- && message.from !== null
1006
- && !Array.isArray(message.from)
1007
- && !Object.keys(message).some((key) => key.startsWith("forward_"))
1008
- && Number.isSafeInteger(message.date)
1009
- && message.date >= Math.floor(base.startedAt / 1000);
1010
- });
1011
- if (matches.length > 1)
1012
- throw new Error("Telegram nonce update is ambiguous");
1013
- match = matches[0];
909
+ if (!await bounded(async () => gateway.authorityTransport.hostApproval?.refresh()))
910
+ throw new Error("Telegram root gateway health is unavailable");
911
+ const initialCursor = await bounded(() => gateway.cursorSnapshot());
912
+ if (initialCursor.cursor !== currentOffset)
913
+ throw new Error("Telegram gateway cursor changed before bootstrap");
914
+ let getMe;
915
+ try {
916
+ getMe = await bounded(() => gateway.authorityTransport.api.request("getMe", {}));
1014
917
  }
1015
- if (!match)
1016
- throw new Error("Telegram nonce confirmation timed out");
1017
- const message = object(match.message, "Telegram nonce message");
1018
- const from = object(message.from, "Telegram nonce sender");
1019
- const chat = object(message.chat, "Telegram nonce chat");
1020
- const userId = String(integer(from.id, "Telegram user id", 1));
1021
- const chatId = String(integer(chat.id, "Telegram chat id", 1));
1022
- const nextUpdateId = nextOffset;
1023
- const confirmed = {
1024
- ...base,
1025
- phase: "nonce_confirmed",
1026
- updateDigest: digest(match),
1027
- coordinateDigest: digest({ userId, chatId }),
1028
- offsetDigest: digest(nextUpdateId),
918
+ catch (error) {
919
+ throw telegramBootstrapRequestError("getMe", error);
920
+ }
921
+ const bot = object(getMe, "Telegram getMe result");
922
+ if (String(bot.id) !== expectedBotId || gateway.credentials.botId !== expectedBotId || bot.username !== expectedUsername) {
923
+ throw new Error("Telegram bot identity mismatch; repair root gateway identity");
924
+ }
925
+ const nonce = deps.randomBytes(16).toString("hex");
926
+ const base = {
927
+ schemaVersion: 1, operation: "telegram-bootstrap", phase: "preflight",
928
+ botIdentityDigest: digest({ id: expectedBotId, username: expectedUsername }), startedAt: deps.now(),
1029
929
  };
1030
- replaceCheckpoint(root, evidencePath, confirmed);
1031
- let stored;
930
+ initializeCheckpoint(root, evidencePath, base);
1032
931
  try {
1033
- stored = await deps.mergeRuntime("sanctuary", {
1034
- telegramAuthorizedUserId: userId,
1035
- telegramAuthorizedChatId: chatId,
1036
- });
1037
- }
1038
- catch {
1039
- throw new Error("Telegram bootstrap vault update failed; actor: agent-runnable; retry Telegram bootstrap");
932
+ const quiesced = object(await deps.runAdapter(pollerAdapter, { operation: "quiesce_telegram_poller", expectedState: "stopped" }), "Telegram poller precondition");
933
+ exactObjectKeys(quiesced, ["activePollers", "quiesced"], "Telegram poller precondition");
934
+ if (quiesced.quiesced !== true || quiesced.activePollers !== 1)
935
+ throw new Error("Telegram root poller or stopped resident is not proven");
936
+ writeAtomicPrivateText(root, noncePath, nonce);
937
+ const deadline = deps.now() + deadlineMs;
938
+ let match;
939
+ while (deps.now() < deadline && !match) {
940
+ let updates;
941
+ try {
942
+ updates = await bounded(() => gateway.authorityTransport.api.request("getUpdates", { offset: currentOffset, timeout: pollTimeoutSeconds, allowed_updates: ["message", "callback_query"] }), Math.min(deps.telegramTimeoutMs ?? DEFAULT_TELEGRAM_TIMEOUT_MS, deadline - deps.now()));
943
+ }
944
+ catch (error) {
945
+ throw telegramBootstrapRequestError("getUpdates", error);
946
+ }
947
+ if (!Array.isArray(updates) || updates.length > 1)
948
+ throw new Error("Telegram gateway delivery is ambiguous");
949
+ if (updates.length === 0)
950
+ continue;
951
+ const candidate = updates[0];
952
+ const message = candidate.message;
953
+ const observation = gateway.authorityTransport.metadataForUpdate(candidate);
954
+ if (!message || message.text !== nonce || message.chat.type !== "private" || !message.from
955
+ || Object.keys(message).some((key) => key.startsWith("forward_")) || !Number.isSafeInteger(message.date) || message.date < Math.floor(Number(base.startedAt) / 1000)
956
+ || !observation?.ownerEligible || observation.userId !== gateway.credentials.authorizedUserId || observation.chatId !== gateway.credentials.authorizedChatId) {
957
+ throw new Error("Telegram gateway has a pending non-bootstrap owner update; resume resident dispatch without discarding it");
958
+ }
959
+ match = candidate;
960
+ }
961
+ if (!match)
962
+ throw new Error("Telegram nonce confirmation timed out");
963
+ const confirmed = { ...base, phase: "nonce_confirmed", updateDigest: digest(match), coordinateDigest: digest({ userId: gateway.credentials.authorizedUserId, chatId: gateway.credentials.authorizedChatId }) };
964
+ replaceCheckpoint(root, evidencePath, confirmed);
965
+ await bounded(() => gateway.authorityTransport.settleTransport(match, "completed"));
966
+ const settled = await bounded(() => gateway.cursorSnapshot());
967
+ if (settled.cursor <= match.update_id)
968
+ throw new Error("Telegram gateway settlement readback did not advance");
969
+ atomicPrivateJson(root, offsetPath, { nextUpdateId: settled.cursor, progressDigest: settled.progressDigest });
970
+ replaceCheckpoint(root, evidencePath, { ...confirmed, phase: "complete", offsetDigest: settled.progressDigest.slice("sha256:".length), completedAt: deps.now() });
1040
971
  }
1041
- if (!stored.ok || stored.config.telegramBotToken !== token
1042
- || stored.config.telegramAuthorizedUserId !== userId || stored.config.telegramAuthorizedChatId !== chatId) {
1043
- throw new Error("Telegram bootstrap vault readback failed; actor: agent-runnable; retry Telegram bootstrap");
972
+ catch (error) {
973
+ failedCheckpoint(root, evidencePath, base, error);
974
+ throw error;
1044
975
  }
1045
- replaceCheckpoint(root, evidencePath, { ...confirmed, phase: "vault_committed" });
1046
- atomicPrivateJson(root, offsetPath, { nextUpdateId });
1047
- replaceCheckpoint(root, evidencePath, { ...confirmed, phase: "complete", completedAt: deps.now() });
1048
976
  }
1049
- catch (error) {
1050
- failedCheckpoint(root, evidencePath, base, error);
1051
- throw error;
977
+ finally {
978
+ gateway.authorityTransport.api.stop();
1052
979
  }
1053
980
  }
1054
981
  function atomicPrivateJson(root, filePath, value) {
@@ -1862,8 +1789,7 @@ async function executeSanctuaryAcceptanceHarness(command, rawConfig, deps = crea
1862
1789
  const config = object(rawConfig, "acceptance config");
1863
1790
  switch (command) {
1864
1791
  case "telegram-bootstrap": {
1865
- const token = await canonicalTelegramBootstrapToken(deps);
1866
- await telegramBootstrap(config, { ...deps, readSecret: () => token });
1792
+ await telegramBootstrap(config, deps);
1867
1793
  break;
1868
1794
  }
1869
1795
  case "cursor-snapshot":
@@ -132,7 +132,7 @@ function exactContainmentAudit(evidence) {
132
132
  && (0, sanctuary_acceptance_harness_1.exactSanctuaryContainmentProfileBoundaries)(evidence.profileBoundaries)
133
133
  && evidence.auditPathDigest === (0, node_crypto_1.createHash)("sha256").update(CONTAINMENT_AUDIT_PATH).digest("hex")
134
134
  && evidence.auditRecordCount >= 2 && evidence.auditLifecyclePairCount >= 1
135
- && evidence.containerUser === "10001:10001" && evidence.liveProcessUser === "10001:10001" && evidence.mountCount === 3 && evidence.publishedPortCount === 0
135
+ && evidence.containerUser === "10001:10001" && evidence.liveProcessUser === "10001:10001" && evidence.mountCount === 4 && evidence.publishedPortCount === 0
136
136
  && evidence.networkMode === "host" && evidence.readOnlyRoot === false && evidence.mountsExact && evidence.securityExact && evidence.updaterDisabled
137
137
  && !evidence.writableKeyExposure && evidence.rawWriteMaterialFieldCount === 0 && evidence.typedWriteExecutorCount === 1
138
138
  && evidence.writeApprovalPolicyExact && !evidence.sensitiveMaterialObserved
@@ -422,15 +422,13 @@ function deriveSanctuaryScenarioAssertions(label, before, after, _now, scenarioH
422
422
  if (!after.reboot || after.reboot.phase !== "complete" || !SHA256.test(after.reboot.processBindingDigest) || !after.reboot.bootIdentityChanged || !after.reboot.arrayReady || !after.reboot.butlerReady || !after.reboot.dockerReady || !after.reboot.hostReady || !after.reboot.sshReady || !after.reboot.tailscaleReady || !postbootIntegrity)
423
423
  return null;
424
424
  return { arrayReady: after.reboot.arrayReady, bootIdentityChanged: true, butlerReady: after.reboot.butlerReady, dockerReady: after.reboot.dockerReady, hostReady: after.reboot.hostReady, postbootIntegrityPreserved: true, processBindingDigest: after.reboot.processBindingDigest, sshReady: after.reboot.sshReady, tailscaleReady: after.reboot.tailscaleReady };
425
- case "unit-16b-runtime-vault-containment":
425
+ case "unit-16b-runtime-vault-readiness":
426
426
  if (!after.container)
427
427
  return null;
428
428
  if (!after.container.autostartExact || !after.container.exactImage || !after.container.mountsExact || after.container.manualAuthRequired
429
- || after.container.mountCount !== 4 || Number(after.container.user.split(":")[0]) !== 10001
430
- || after.container.publishedPortCount !== 0 || !after.container.readOnlyRoot
431
429
  || !after.container.updaterDisabled || !after.container.vaultUnlocked)
432
430
  return null;
433
- return { autostartExact: after.container.autostartExact, exactImage: after.container.exactImage, manualAuthRequired: after.container.manualAuthRequired, mountCount: after.container.mountCount, mountsExact: after.container.mountsExact, nonRootUid: Number(after.container.user.split(":")[0]), publishedPortCount: after.container.publishedPortCount, readOnlyRoot: after.container.readOnlyRoot, updaterDisabled: after.container.updaterDisabled, vaultUnlocked: after.container.vaultUnlocked };
431
+ return { autostartExact: after.container.autostartExact, exactImage: after.container.exactImage, manualAuthRequired: after.container.manualAuthRequired, updaterDisabled: after.container.updaterDisabled, vaultUnlocked: after.container.vaultUnlocked };
434
432
  case "unit-16c-provider-readiness":
435
433
  return after.provider && after.provider.outwardReady && after.provider.innerReady && !after.provider.silentFallback
436
434
  && after.provider.credentialRevisionsPresent === true && after.provider.requestSemanticsExact === true && after.provider.fallbackAttemptCount === 0
@@ -635,6 +633,7 @@ function deriveSanctuaryScenarioAssertions(label, before, after, _now, scenarioH
635
633
  state: approval.state,
636
634
  };
637
635
  }
636
+ return null;
638
637
  }
639
638
  function createSanctuaryScenarioCapture(deps) {
640
639
  const receiptRoot = deps.receiptRoot ?? path.join(deps.agentRoot, "state", "acceptance", "receipts");
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.canonicalAuthorityJson = canonicalAuthorityJson;
4
+ exports.authorityArtifactDigest = authorityArtifactDigest;
5
+ exports.signAuthorityPayload = signAuthorityPayload;
6
+ exports.verifyAuthorityPayload = verifyAuthorityPayload;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const runtime_1 = require("../../nerves/runtime");
9
+ const ARTIFACT_KEYS = ["domain", "keyId", "payload", "schemaVersion", "signature"];
10
+ const BASE64URL_SIGNATURE = /^[A-Za-z0-9_-]{86}$/u;
11
+ function invalidCanonicalJson() {
12
+ throw new Error("canonical authority JSON value is invalid");
13
+ }
14
+ function canonicalAuthorityJson(value) {
15
+ if (value === null || typeof value === "boolean" || typeof value === "string")
16
+ return JSON.stringify(value);
17
+ if (typeof value === "number") {
18
+ if (!Number.isSafeInteger(value) || Object.is(value, -0))
19
+ return invalidCanonicalJson();
20
+ return JSON.stringify(value);
21
+ }
22
+ if (Array.isArray(value)) {
23
+ const entries = [];
24
+ for (let index = 0; index < value.length; index += 1) {
25
+ if (!(index in value))
26
+ return invalidCanonicalJson();
27
+ entries.push(canonicalAuthorityJson(value[index]));
28
+ }
29
+ return `[${entries.join(",")}]`;
30
+ }
31
+ if (typeof value !== "object" || Object.getPrototypeOf(value) !== Object.prototype)
32
+ return invalidCanonicalJson();
33
+ const record = value;
34
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalAuthorityJson(record[key])}`).join(",")}}`;
35
+ }
36
+ function signingBytes(domain, payload) {
37
+ if (!domain)
38
+ throw new Error("authority domain is invalid");
39
+ return Buffer.from(`ouro.sanctuary.authority.v1\0${domain}\0${canonicalAuthorityJson(payload)}`, "utf8");
40
+ }
41
+ function authorityArtifactDigest(domain, payload) {
42
+ return `sha256:${(0, node_crypto_1.createHash)("sha256").update(signingBytes(domain, payload)).digest("hex")}`;
43
+ }
44
+ function signAuthorityPayload(input) {
45
+ if (!input.keyId)
46
+ throw new Error("authority key id is invalid");
47
+ return {
48
+ schemaVersion: 1,
49
+ domain: input.domain,
50
+ keyId: input.keyId,
51
+ payload: input.payload,
52
+ signature: (0, node_crypto_1.sign)(null, signingBytes(input.domain, input.payload), input.privateKey).toString("base64url"),
53
+ };
54
+ }
55
+ function authorityArtifact(value) {
56
+ if (!value || typeof value !== "object" || Array.isArray(value))
57
+ throw new Error("authority artifact shape is invalid");
58
+ const record = value;
59
+ if (JSON.stringify(Object.keys(record).sort()) !== JSON.stringify([...ARTIFACT_KEYS]))
60
+ throw new Error("authority artifact shape is invalid");
61
+ if (record.schemaVersion !== 1)
62
+ throw new Error("authority artifact schema is invalid");
63
+ if (typeof record.domain !== "string" || !record.domain)
64
+ throw new Error("authority artifact domain is invalid");
65
+ if (typeof record.keyId !== "string" || !record.keyId)
66
+ throw new Error("authority artifact key id is invalid");
67
+ if (typeof record.signature !== "string" || !BASE64URL_SIGNATURE.test(record.signature)
68
+ || Buffer.from(record.signature, "base64url").length !== 64
69
+ || Buffer.from(record.signature, "base64url").toString("base64url") !== record.signature) {
70
+ throw new Error("authority artifact signature is invalid");
71
+ }
72
+ canonicalAuthorityJson(record.payload);
73
+ return record;
74
+ }
75
+ function verifyAuthorityPayload(input) {
76
+ const artifact = authorityArtifact(input.artifact);
77
+ if (artifact.domain !== input.expectedDomain)
78
+ throw new Error("authority artifact domain changed");
79
+ if (artifact.keyId !== input.expectedKeyId)
80
+ throw new Error("authority artifact key id changed");
81
+ if (!(0, node_crypto_1.verify)(null, signingBytes(artifact.domain, artifact.payload), input.publicKey, Buffer.from(artifact.signature, "base64url"))) {
82
+ throw new Error("authority artifact signature is invalid");
83
+ }
84
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_authority_signature_verified", message: "Sanctuary authority signature verified" });
85
+ return artifact.payload;
86
+ }