@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.
- package/assets/sanctuary-host-launcher.sh +16 -0
- package/changelog.json +16 -0
- package/deploy/unraid/Dockerfile +6 -0
- package/deploy/unraid/README.txt +296 -62
- package/deploy/unraid/docker-man-template-transaction.mjs +192 -7
- package/deploy/unraid/sanctuary-acceptance-adapter.sh +1 -1
- package/deploy/unraid/sanctuary-acceptance-contract.json +7 -7
- package/deploy/unraid/sanctuary-authority-installation.json +36 -0
- package/deploy/unraid/sanctuary-authority-service.sh +30 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +62 -9
- package/deploy/unraid/sanctuary-unit16-run.sh +15 -15
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +2 -2
- package/deploy/unraid/sanctuary.xml +2 -1
- package/dist/heart/core.js +2 -1
- package/dist/heart/daemon/container-spec-auditor-main.js +8 -7
- package/dist/heart/daemon/container-spec-auditor.js +16 -9
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +95 -91
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +95 -169
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +4 -5
- package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
- package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
- package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
- package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
- package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +780 -0
- package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
- package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
- package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
- package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
- package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
- package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
- package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-service.js +615 -0
- package/dist/heart/daemon/sense-manager.js +20 -10
- package/dist/repertoire/tools-sanctuary-host.js +202 -0
- package/dist/repertoire/tools.js +15 -6
- package/dist/senses/private-runtime.js +2 -2
- package/dist/senses/root-host-approval-port.js +345 -0
- package/dist/senses/root-host-approval-runtime.js +393 -0
- package/dist/senses/sanctuary-authority-resident.js +102 -0
- package/dist/senses/telegram-admission.js +7 -0
- package/dist/senses/telegram-attachments.js +5 -1
- package/dist/senses/telegram-authority-transport.js +231 -0
- package/dist/senses/telegram-client.js +31 -8
- package/dist/senses/telegram-entry.js +1 -1
- package/dist/senses/telegram.js +168 -28
- package/npm-shrinkwrap.json +2 -2
- 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
|
|
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 =
|
|
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":
|
|
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-
|
|
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-
|
|
510
|
-
exact(["autostartExact", "exactImage", "manualAuthRequired", "
|
|
511
|
-
allTrue(["autostartExact", "exactImage", "
|
|
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",
|
|
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-
|
|
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-
|
|
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
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
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
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
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
|
-
|
|
1016
|
-
throw
|
|
1017
|
-
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
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
|
-
|
|
1031
|
-
let stored;
|
|
930
|
+
initializeCheckpoint(root, evidencePath, base);
|
|
1032
931
|
try {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
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
|
-
|
|
1042
|
-
|
|
1043
|
-
throw
|
|
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
|
-
|
|
1050
|
-
|
|
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
|
-
|
|
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 ===
|
|
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-
|
|
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,
|
|
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
|
+
}
|