@ouro.bot/cli 0.1.0-alpha.733 → 0.1.0-alpha.734
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.json +25 -0
- package/deploy/unraid/Dockerfile +27 -0
- package/deploy/unraid/README.txt +1368 -0
- package/deploy/unraid/audit-container-spec.sh +4 -0
- package/deploy/unraid/container-runtime.json +1 -0
- package/deploy/unraid/sanctuary-acceptance-adapter.sh +37 -0
- package/deploy/unraid/sanctuary-acceptance-contract.json +200 -0
- package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
- package/deploy/unraid/sanctuary-deployment-target.mjs +705 -0
- package/deploy/unraid/sanctuary-safe-rename.py +24 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +1777 -0
- package/deploy/unraid/sanctuary-unit16-run.sh +502 -0
- package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
- package/deploy/unraid/sanctuary.ouro/agent.json +24 -0
- package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
- package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
- package/deploy/unraid/sanctuary.ouro/provider-readiness.json +18 -0
- package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +3 -0
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +3 -0
- package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +3 -0
- package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +5 -0
- package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +7 -0
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +21 -0
- package/deploy/unraid/sanctuary.xml +25 -0
- package/dist/heart/approval-files.js +113 -0
- package/dist/heart/approval-store.js +168 -9
- package/dist/heart/auth/auth-flow.js +17 -1
- package/dist/heart/autonomy-budget.js +44 -1
- package/dist/heart/awaiting/await-scheduler.js +6 -2
- package/dist/heart/config.js +8 -0
- package/dist/heart/core.js +117 -132
- package/dist/heart/daemon/cli-exec.js +73 -1
- package/dist/heart/daemon/cli-help.js +5 -0
- package/dist/heart/daemon/cli-parse.js +6 -4
- package/dist/heart/daemon/container-credential-bootstrap.js +185 -0
- package/dist/heart/daemon/container-healthcheck.js +94 -0
- package/dist/heart/daemon/container-runtime.js +84 -0
- package/dist/heart/daemon/container-spec-auditor-main.js +156 -0
- package/dist/heart/daemon/container-spec-auditor.js +156 -0
- package/dist/heart/daemon/daemon-bootstrap-startup.js +41 -0
- package/dist/heart/daemon/daemon-entry.js +116 -28
- package/dist/heart/daemon/daemon-health.js +10 -0
- package/dist/heart/daemon/daemon.js +92 -5
- package/dist/heart/daemon/runtime-logging.js +6 -0
- package/dist/heart/daemon/runtime-mode.js +13 -2
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2365 -0
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +1624 -0
- package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +985 -0
- package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
- package/dist/heart/daemon/sanctuary-scheduler-origin.js +126 -0
- package/dist/heart/daemon/sense-manager.js +25 -6
- package/dist/heart/daemon/supercronic-supervisor.js +267 -0
- package/dist/heart/habits/habit-scheduler.js +7 -3
- package/dist/heart/identity.js +13 -3
- package/dist/heart/provider-credentials.js +9 -1
- package/dist/heart/provider-models.js +8 -0
- package/dist/heart/provider-ping.js +5 -2
- package/dist/heart/providers/openai-compatible.js +177 -0
- package/dist/heart/runtime-credentials.js +217 -8
- package/dist/heart/sense-truth.js +2 -0
- package/dist/heart/tool-approval.js +1 -0
- package/dist/heart/turn-context.js +8 -0
- package/dist/mind/prompt.js +9 -0
- package/dist/nerves/coverage/source-scanner.js +4 -4
- package/dist/nerves/index.js +75 -9
- package/dist/nerves/runtime.js +12 -0
- package/dist/repertoire/tool-arguments.js +39 -0
- package/dist/repertoire/tools-unraid.js +353 -0
- package/dist/repertoire/tools.js +19 -1
- package/dist/repertoire/unraid-client.js +155 -0
- package/dist/repertoire/unraid-restart.js +130 -0
- package/dist/senses/private-runtime-worker.js +3 -2
- package/dist/senses/sanctuary-grounding.js +118 -0
- package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
- package/dist/senses/sanctuary-health-acceptance-probe.js +772 -0
- package/dist/senses/sanctuary-health-runner.js +146 -0
- package/dist/senses/sanctuary-health.js +447 -0
- package/dist/senses/sanctuary-interactive-control.js +306 -0
- package/dist/senses/sanctuary-runtime.js +206 -0
- package/dist/senses/shared-turn.js +11 -3
- package/dist/senses/telegram-approval-runtime.js +526 -0
- package/dist/senses/telegram-audit-ledger.js +220 -0
- package/dist/senses/telegram-client.js +1471 -0
- package/dist/senses/telegram-entry.js +80 -0
- package/dist/senses/telegram.js +1232 -0
- package/npm-shrinkwrap.json +6558 -0
- package/package.json +4 -2
|
@@ -0,0 +1,985 @@
|
|
|
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.SANCTUARY_APPROVAL_TERMINAL_EDIT_TIMEOUT_MS = exports.SANCTUARY_APPROVAL_RECONCILIATION_JITTER_MS = exports.SANCTUARY_APPROVAL_TTL_MS = void 0;
|
|
37
|
+
exports.verifySanctuaryPostbootIntegrity = verifySanctuaryPostbootIntegrity;
|
|
38
|
+
exports.deriveSanctuaryScenarioAssertions = deriveSanctuaryScenarioAssertions;
|
|
39
|
+
exports.createSanctuaryScenarioCapture = createSanctuaryScenarioCapture;
|
|
40
|
+
exports.finalizeSanctuaryScenarioCapture = finalizeSanctuaryScenarioCapture;
|
|
41
|
+
const node_crypto_1 = require("node:crypto");
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
45
|
+
const identity_1 = require("../identity");
|
|
46
|
+
const sanctuary_acceptance_marker_1 = require("./sanctuary-acceptance-marker");
|
|
47
|
+
const sanctuary_acceptance_harness_1 = require("./sanctuary-acceptance-harness");
|
|
48
|
+
const sanctuary_grounding_1 = require("../../senses/sanctuary-grounding");
|
|
49
|
+
const telegram_client_1 = require("../../senses/telegram-client");
|
|
50
|
+
const SHA256 = /^[0-9a-f]{64}$/u;
|
|
51
|
+
exports.SANCTUARY_APPROVAL_TTL_MS = telegram_client_1.TELEGRAM_APPROVAL_TTL_MS;
|
|
52
|
+
exports.SANCTUARY_APPROVAL_RECONCILIATION_JITTER_MS = 1_000;
|
|
53
|
+
exports.SANCTUARY_APPROVAL_TERMINAL_EDIT_TIMEOUT_MS = telegram_client_1.TELEGRAM_APPROVAL_TERMINAL_EDIT_TIMEOUT_MS;
|
|
54
|
+
const CANONICAL_RESTART_TARGET = "calibre-web";
|
|
55
|
+
function uniqueDigests(rows) {
|
|
56
|
+
return rows.every((row) => SHA256.test(row.idDigest)) && new Set(rows.map((row) => row.idDigest)).size === rows.length;
|
|
57
|
+
}
|
|
58
|
+
function preservedPrefix(before, after) {
|
|
59
|
+
return before.length <= after.length && before.every((row, index) => row.recordDigest === after[index]?.recordDigest);
|
|
60
|
+
}
|
|
61
|
+
function verifySanctuaryPostbootIntegrity(before, after, scenarioHandleDigest, options = {}) {
|
|
62
|
+
if (before.schemaVersion !== "sanctuary-postboot-integrity-v2" || after.schemaVersion !== before.schemaVersion || !SHA256.test(scenarioHandleDigest)
|
|
63
|
+
|| before.activeScenarioHandleDigest !== null || after.activeScenarioHandleDigest !== scenarioHandleDigest)
|
|
64
|
+
return null;
|
|
65
|
+
if (!Number.isSafeInteger(before.telegramNextUpdateId) || !Number.isSafeInteger(after.telegramNextUpdateId)
|
|
66
|
+
|| before.telegramNextUpdateId < 0 || after.telegramNextUpdateId < before.telegramNextUpdateId)
|
|
67
|
+
return null;
|
|
68
|
+
if (options.preserveCursors !== false && (JSON.stringify(before.approvalCheckpoints) !== JSON.stringify(after.approvalCheckpoints)
|
|
69
|
+
|| before.approvalExecutionCount !== after.approvalExecutionCount || before.fingerprintDigest !== after.fingerprintDigest))
|
|
70
|
+
return null;
|
|
71
|
+
if (![before.approvalCheckpoints, after.approvalCheckpoints, before.sweeps, after.sweeps, before.deliveries, after.deliveries, before.audits, after.audits].every(uniqueDigests))
|
|
72
|
+
return null;
|
|
73
|
+
const rows = [...before.approvalCheckpoints, ...after.approvalCheckpoints, ...before.restartAttempts, ...after.restartAttempts,
|
|
74
|
+
...before.sweeps, ...after.sweeps, ...before.deliveries, ...after.deliveries, ...before.audits, ...after.audits];
|
|
75
|
+
if (!rows.every((row) => SHA256.test(row.recordDigest)))
|
|
76
|
+
return null;
|
|
77
|
+
if (JSON.stringify(before.restartAttempts) !== JSON.stringify(after.restartAttempts) || !preservedPrefix(before.sweeps, after.sweeps)
|
|
78
|
+
|| !preservedPrefix(before.deliveries, after.deliveries) || !preservedPrefix(before.audits, after.audits))
|
|
79
|
+
return null;
|
|
80
|
+
const executionCount = (snapshot) => new Set(snapshot.restartAttempts
|
|
81
|
+
.filter((row) => row.state !== "attempt_not_started").map((row) => row.idDigest)).size;
|
|
82
|
+
const validAttemptLifecycles = (snapshot) => {
|
|
83
|
+
const states = new Map();
|
|
84
|
+
for (const row of snapshot.restartAttempts)
|
|
85
|
+
states.set(row.idDigest, [...(states.get(row.idDigest) ?? []), row.state]);
|
|
86
|
+
return [...states.values()].every((sequence) => sequence.length === 3 && sequence[0] === "attempt_not_started"
|
|
87
|
+
&& sequence[1] === "attempting" && (sequence[2] === "succeeded" || sequence[2] === "attempted_or_indeterminate"));
|
|
88
|
+
};
|
|
89
|
+
if (!validAttemptLifecycles(before) || !validAttemptLifecycles(after))
|
|
90
|
+
return null;
|
|
91
|
+
if (before.approvalExecutionCount !== executionCount(before) || after.approvalExecutionCount !== executionCount(after)
|
|
92
|
+
|| after.approvalExecutionCount !== before.approvalExecutionCount)
|
|
93
|
+
return null;
|
|
94
|
+
const newSweeps = after.sweeps.slice(before.sweeps.length);
|
|
95
|
+
const newDeliveries = after.deliveries.slice(before.deliveries.length);
|
|
96
|
+
const newAudits = after.audits.slice(before.audits.length);
|
|
97
|
+
if (newSweeps.some((row) => row.scenarioHandleDigest !== scenarioHandleDigest))
|
|
98
|
+
return null;
|
|
99
|
+
const boundDeliveryIds = new Set(newSweeps.flatMap((row) => row.deliveryIdDigest ? [row.deliveryIdDigest] : []));
|
|
100
|
+
if (newDeliveries.some((row) => !boundDeliveryIds.has(row.idDigest)))
|
|
101
|
+
return null;
|
|
102
|
+
if (newAudits.some((row) => row.scenarioRelevant && row.scenarioHandleDigest !== scenarioHandleDigest))
|
|
103
|
+
return null;
|
|
104
|
+
return { preserved: true, sweepDeltaCount: newSweeps.length, deliveryDeltaCount: newDeliveries.length, auditDeltaCount: newAudits.length };
|
|
105
|
+
}
|
|
106
|
+
const HEALTH_SCENARIO_LABELS = new Set([
|
|
107
|
+
"unit-16f-cron-fingerprint",
|
|
108
|
+
"unit-16g-health-transition",
|
|
109
|
+
"unit-16h-daily-digest",
|
|
110
|
+
]);
|
|
111
|
+
const INTERACTIVE_DRIVER_LABELS = new Set([
|
|
112
|
+
"unit-16l-duplicate-callback",
|
|
113
|
+
"unit-16m-restart-continuation",
|
|
114
|
+
]);
|
|
115
|
+
const READ_ONLY_DENIAL_LABELS = new Set([
|
|
116
|
+
"unit-16e-1-stop-denial",
|
|
117
|
+
"unit-16e-2-restart-denial",
|
|
118
|
+
]);
|
|
119
|
+
function hash(value) {
|
|
120
|
+
return (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(value), "utf8").digest("hex");
|
|
121
|
+
}
|
|
122
|
+
const CONTAINMENT_READ_SCOPE = ["ARRAY", "DASHBOARD", "DISK", "DOCKER", "INFO", "LOGS", "NOTIFICATIONS", "SHARE", "VARS"]
|
|
123
|
+
.map((resource) => `${resource}:READ_ANY`).sort();
|
|
124
|
+
const CONTAINMENT_TELEGRAM_PROFILE = ["unraid_list_containers", "unraid_get_container_logs", "unraid_get_storage", "unraid_get_disks", "unraid_get_notifications", "unraid_get_system", "unraid_restart_container", "ponder", "settle", "speak"];
|
|
125
|
+
const CONTAINMENT_PRIVATE_PROFILE = ["send_message", "rest"];
|
|
126
|
+
const CONTAINMENT_EXCLUDED_TOOLS = ["shell", "read_file", "edit_file", "vault_get", "mcp_call", "exec", "credential_get"];
|
|
127
|
+
const CONTAINMENT_AUDIT_PATH = "/home/ouro/AgentBundles/sanctuary.ouro/state/acceptance/telegram-audit-chain.ndjson";
|
|
128
|
+
const CONTAINMENT_WRITE_POLICY = { kind: "required", policyId: "sanctuary.unraid.restart.v1", actionClass: "unraid.container.restart", requiresSoleCall: true };
|
|
129
|
+
const CONTAINMENT_TELEGRAM_SCHEMA_DIGEST = "3c66299a5f70ec82f8795cae47659284e6dbc691ef49002c2fb22edba76c59b6";
|
|
130
|
+
const CONTAINMENT_PRIVATE_SCHEMA_DIGEST = "61b137b2467acbcf22ca7443ee01e71ed970a62728c42aabffbdcb562f4a6a70";
|
|
131
|
+
function exactContainmentAudit(evidence) {
|
|
132
|
+
const digestFields = [evidence.keyInventoryDigest, evidence.telegramSchemaDigest, evidence.privateSchemaDigest, evidence.auditLedgerDigest];
|
|
133
|
+
return evidence.schemaVersion === "sanctuary-containment-audit-v1"
|
|
134
|
+
&& evidence.keyCount === 2
|
|
135
|
+
&& evidence.readScopeDigest === hash(CONTAINMENT_READ_SCOPE)
|
|
136
|
+
&& evidence.writeScopeDigest === hash([...CONTAINMENT_READ_SCOPE, "DOCKER:UPDATE_ANY"].sort())
|
|
137
|
+
&& evidence.keyRoleAssignmentCount === 0
|
|
138
|
+
&& evidence.telegramToolCount === CONTAINMENT_TELEGRAM_PROFILE.length
|
|
139
|
+
&& evidence.telegramProfileDigest === hash(CONTAINMENT_TELEGRAM_PROFILE)
|
|
140
|
+
&& evidence.telegramSchemaDigest === CONTAINMENT_TELEGRAM_SCHEMA_DIGEST
|
|
141
|
+
&& evidence.privateToolCount === CONTAINMENT_PRIVATE_PROFILE.length
|
|
142
|
+
&& evidence.privateProfileDigest === hash(CONTAINMENT_PRIVATE_PROFILE)
|
|
143
|
+
&& evidence.privateSchemaDigest === CONTAINMENT_PRIVATE_SCHEMA_DIGEST
|
|
144
|
+
&& evidence.resolvedHandlerCount === CONTAINMENT_TELEGRAM_PROFILE.length + CONTAINMENT_PRIVATE_PROFILE.length
|
|
145
|
+
&& evidence.excludedToolCount === CONTAINMENT_EXCLUDED_TOOLS.length
|
|
146
|
+
&& evidence.excludedSchemaIntersectionCount === 0
|
|
147
|
+
&& evidence.fabricatedHandlerInvocationCount === 0
|
|
148
|
+
&& evidence.excludedToolAttemptCount === CONTAINMENT_EXCLUDED_TOOLS.length
|
|
149
|
+
&& evidence.excludedToolRejectedCount === CONTAINMENT_EXCLUDED_TOOLS.length
|
|
150
|
+
&& evidence.excludedToolInvokedCount === 0 && evidence.excludedToolSideEffectCount === 0
|
|
151
|
+
&& evidence.globallyResolvableExcludedToolCount >= 1
|
|
152
|
+
&& evidence.auditPathDigest === (0, node_crypto_1.createHash)("sha256").update(CONTAINMENT_AUDIT_PATH).digest("hex")
|
|
153
|
+
&& evidence.auditRecordCount >= 2 && evidence.auditLifecyclePairCount >= 1
|
|
154
|
+
&& evidence.containerUser === "10001:10001" && evidence.liveProcessUser === "10001:10001" && evidence.mountCount === 2 && evidence.publishedPortCount === 0
|
|
155
|
+
&& evidence.networkMode === "host" && evidence.readOnlyRoot && evidence.mountsExact && evidence.securityExact && evidence.updaterDisabled
|
|
156
|
+
&& !evidence.writableKeyExposure && evidence.rawWriteMaterialFieldCount === 0 && evidence.typedWriteExecutorCount === 1
|
|
157
|
+
&& evidence.writeApprovalPolicyDigest === hash(CONTAINMENT_WRITE_POLICY) && !evidence.sensitiveMaterialObserved
|
|
158
|
+
&& digestFields.every((value) => SHA256.test(value));
|
|
159
|
+
}
|
|
160
|
+
function atomicPrivateJson(filePath, value) {
|
|
161
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
162
|
+
const temporary = `${filePath}.${process.pid}.tmp`;
|
|
163
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
164
|
+
fs.renameSync(temporary, filePath);
|
|
165
|
+
fs.chmodSync(filePath, 0o600);
|
|
166
|
+
}
|
|
167
|
+
function eventCount(facts, event) {
|
|
168
|
+
return facts.events.filter((entry) => entry.event === event).length;
|
|
169
|
+
}
|
|
170
|
+
function delta(after, before, event) {
|
|
171
|
+
return Math.max(0, eventCount(after, event) - eventCount(before, event));
|
|
172
|
+
}
|
|
173
|
+
function turnHasGroundedRead(after, turn, toolName) {
|
|
174
|
+
const resultDigests = new Set(after.events.flatMap((entry) => entry.event === "senses.sanctuary_read_receipt" && entry.meta.toolName === toolName && entry.meta.success === true && typeof entry.meta.resultDigest === "string" ? [entry.meta.resultDigest] : []));
|
|
175
|
+
return turn.toolResultDigests.some((digest) => resultDigests.has(digest));
|
|
176
|
+
}
|
|
177
|
+
function exactGroundedResponse(after, turn, toolName) {
|
|
178
|
+
const grounding = turn.toolGroundings?.length === 1 ? turn.toolGroundings[0] : undefined;
|
|
179
|
+
const live = after.liveGrounding;
|
|
180
|
+
const audit = after.events.filter((entry) => entry.event === "senses.sanctuary_read_receipt" && entry.meta.toolName === toolName && entry.meta.success === true
|
|
181
|
+
&& entry.meta.resultDigest === grounding?.resultDigest && entry.meta.groundingDigest === grounding?.groundingDigest
|
|
182
|
+
&& entry.meta.sourceIdentityDigest === grounding?.sourceIdentityDigest && entry.meta.observedAt === grounding?.observedAt);
|
|
183
|
+
const turnObservedAt = Date.parse(grounding?.observedAt ?? "");
|
|
184
|
+
const liveObservedAt = Date.parse(live?.observedAt ?? "");
|
|
185
|
+
const timeBound = Number.isFinite(turnObservedAt) && Number.isFinite(liveObservedAt)
|
|
186
|
+
&& turnObservedAt <= turn.completedAt && turn.completedAt - turnObservedAt <= 300_000
|
|
187
|
+
&& liveObservedAt >= turnObservedAt && liveObservedAt - turnObservedAt <= 300_000;
|
|
188
|
+
const liveFactsBound = toolName === "unraid_get_storage"
|
|
189
|
+
? live?.groundingDigest === (0, sanctuary_grounding_1.sanctuaryGroundingDigest)(live?.facts ?? {})
|
|
190
|
+
: grounding?.groundingDigest === live?.groundingDigest && JSON.stringify(grounding?.facts) === JSON.stringify(live?.facts);
|
|
191
|
+
return Boolean(grounding && live && grounding.toolName === toolName && live.toolName === toolName
|
|
192
|
+
&& grounding.groundingDigest === (0, sanctuary_grounding_1.sanctuaryGroundingDigest)(grounding.facts) && live.groundingDigest === (0, sanctuary_grounding_1.sanctuaryGroundingDigest)(live.facts)
|
|
193
|
+
&& grounding.sourceIdentityDigest === live.sourceIdentityDigest && SHA256.test(grounding.sourceIdentityDigest) && timeBound && liveFactsBound
|
|
194
|
+
&& audit.length === 1 && typeof turn.responseText === "string" && turn.responseText.length > 0
|
|
195
|
+
&& turn.responseUtf16Units === turn.responseText.length && turn.responseUtf16Units <= 1_200
|
|
196
|
+
&& (0, sanctuary_grounding_1.sanctuaryGroundedResponseAccurate)(toolName, grounding.facts, turn.responseText));
|
|
197
|
+
}
|
|
198
|
+
function recordsAdded(before, after, key) {
|
|
199
|
+
const remaining = new Map();
|
|
200
|
+
for (const value of before)
|
|
201
|
+
remaining.set(key(value), (remaining.get(key(value)) ?? 0) + 1);
|
|
202
|
+
return after.filter((value) => {
|
|
203
|
+
const digest = key(value);
|
|
204
|
+
const count = remaining.get(digest) ?? 0;
|
|
205
|
+
if (count === 0)
|
|
206
|
+
return true;
|
|
207
|
+
remaining.set(digest, count - 1);
|
|
208
|
+
return false;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function exactFreshTelegramLifecycle(before, after, expectedUpdateDigest) {
|
|
212
|
+
if (before.events.length > after.events.length || before.events.some((entry, index) => hash(entry) !== hash(after.events[index])))
|
|
213
|
+
return false;
|
|
214
|
+
const fresh = recordsAdded(before.events, after.events, (entry) => hash(entry));
|
|
215
|
+
const starts = fresh.filter((entry) => entry.event === "senses.telegram_turn_start");
|
|
216
|
+
const terminals = fresh.filter((entry) => entry.event === "senses.telegram_turn_end" || entry.event === "senses.telegram_turn_error");
|
|
217
|
+
if (starts.length !== 1 || terminals.length !== 1)
|
|
218
|
+
return false;
|
|
219
|
+
const start = starts[0];
|
|
220
|
+
const terminal = terminals[0];
|
|
221
|
+
const coordinateKeys = ["scenarioHandleDigest", "turnDigest", "updateDigest", "subject", "identityDigest", "sessionDigest", "argumentDigest"];
|
|
222
|
+
if (coordinateKeys.some((key) => start.meta[key] !== terminal.meta[key]))
|
|
223
|
+
return false;
|
|
224
|
+
if (!SHA256.test(String(start.meta.scenarioHandleDigest)) || !SHA256.test(String(start.meta.turnDigest))
|
|
225
|
+
|| !SHA256.test(String(start.meta.updateDigest)) || (expectedUpdateDigest !== undefined && start.meta.updateDigest !== expectedUpdateDigest)
|
|
226
|
+
|| typeof start.meta.subject !== "string" || !/^tg_[A-Za-z0-9_-]{43}$/u.test(start.meta.subject)
|
|
227
|
+
|| ![start.meta.identityDigest, start.meta.sessionDigest, start.meta.argumentDigest, start.meta.lifecycleMac, terminal.meta.lifecycleMac].every((value) => typeof value === "string" && SHA256.test(value)))
|
|
228
|
+
return false;
|
|
229
|
+
if (!Number.isSafeInteger(terminal.meta.deliveryCount) || Number(terminal.meta.deliveryCount) < 0)
|
|
230
|
+
return false;
|
|
231
|
+
if (!Number.isSafeInteger(start.meta.lifecycleAt) || !Number.isSafeInteger(terminal.meta.lifecycleAt)
|
|
232
|
+
|| Number(start.meta.lifecycleAt) < 0 || Number(start.meta.lifecycleAt) >= Number(terminal.meta.lifecycleAt))
|
|
233
|
+
return false;
|
|
234
|
+
return terminal.event === "senses.telegram_turn_end" && terminal.meta.outcome === "success" && terminal.meta.errorDigest === null;
|
|
235
|
+
}
|
|
236
|
+
function intendedApproval(before, after) {
|
|
237
|
+
const previous = new Set(before.approvals.map((record) => record.approvalId));
|
|
238
|
+
const candidates = after.approvals.filter((record) => !previous.has(record.approvalId) && record.createdAt >= before.capturedAt);
|
|
239
|
+
return candidates.length === 1 ? candidates[0] : null;
|
|
240
|
+
}
|
|
241
|
+
function intendedRestartApproval(approval) {
|
|
242
|
+
const argumentDigest = (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({ container: CANONICAL_RESTART_TARGET })).digest("hex");
|
|
243
|
+
return approval?.toolName === "unraid_restart_container"
|
|
244
|
+
&& approval.target === CANONICAL_RESTART_TARGET
|
|
245
|
+
&& approval.argumentDigest === argumentDigest;
|
|
246
|
+
}
|
|
247
|
+
function approvalEvidenceCoordinates(approval) {
|
|
248
|
+
return {
|
|
249
|
+
actionDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({ toolName: approval.toolName, argumentDigest: approval.argumentDigest })).digest("hex"),
|
|
250
|
+
targetDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({ container: CANONICAL_RESTART_TARGET })).digest("hex"),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function exactApprovalEvidence(before, after, approval) {
|
|
254
|
+
const fresh = recordsAdded(before.events, after.events, (entry) => hash(entry));
|
|
255
|
+
const prompt = fresh.filter((entry) => entry.event === "senses.telegram_approval_prompt_bound" && entry.meta.approvalId === approval.approvalId);
|
|
256
|
+
const terminals = fresh.filter((entry) => entry.event === "telegram.approval_prompt_terminalized" && entry.meta.approvalId === approval.approvalId);
|
|
257
|
+
const callbackCandidates = fresh.filter((entry) => ["telegram.callback_settled", "telegram.callback_recovery_settled"].includes(entry.event) && entry.meta.approvalId === approval.approvalId);
|
|
258
|
+
const callbacks = [...new Map(callbackCandidates.map((entry) => [hash({ event: entry.event, meta: entry.meta }), entry])).values()];
|
|
259
|
+
const continuations = fresh.filter((entry) => entry.event === "senses.telegram_approval_continuation_delivered" && entry.meta.approvalId === approval.approvalId);
|
|
260
|
+
const staleCallbacks = fresh.filter((entry) => entry.event === "telegram.approval_stale_callback_settled" && entry.meta.approvalId === approval.approvalId);
|
|
261
|
+
const expiryObservations = fresh.filter((entry) => entry.event === "telegram.approval_expiry_observed" && entry.meta.approvalId === approval.approvalId);
|
|
262
|
+
if (prompt.length !== 1 || terminals.length !== 1 || callbacks.length > 1 || continuations.length > 1 || staleCallbacks.length > 1)
|
|
263
|
+
return null;
|
|
264
|
+
if ((approval.state === "expired" && (expiryObservations.length < 1
|
|
265
|
+
|| new Set(expiryObservations.map((entry) => hash({ event: entry.event, meta: entry.meta }))).size !== 1))
|
|
266
|
+
|| (approval.state !== "expired" && expiryObservations.length !== 0))
|
|
267
|
+
return null;
|
|
268
|
+
const expected = approvalEvidenceCoordinates(approval);
|
|
269
|
+
const coordinatesMatch = (entry) => entry.meta.scenarioHandleDigest !== undefined
|
|
270
|
+
&& typeof entry.meta.scenarioHandleDigest === "string" && SHA256.test(entry.meta.scenarioHandleDigest)
|
|
271
|
+
&& entry.meta.actionDigest === expected.actionDigest && entry.meta.targetDigest === expected.targetDigest
|
|
272
|
+
&& entry.meta.checkpointDigest === approval.checkpointDigest
|
|
273
|
+
&& typeof approval.suspendedSessionRevision === "string"
|
|
274
|
+
&& entry.meta.suspendedSessionRevisionDigest === (0, node_crypto_1.createHash)("sha256").update(approval.suspendedSessionRevision, "utf8").digest("hex")
|
|
275
|
+
&& typeof entry.meta.messageIdDigest === "string" && SHA256.test(entry.meta.messageIdDigest)
|
|
276
|
+
&& typeof entry.meta.evidenceMac === "string" && SHA256.test(entry.meta.evidenceMac);
|
|
277
|
+
if (![...prompt, ...terminals, ...callbacks, ...continuations, ...staleCallbacks, ...expiryObservations].every(coordinatesMatch))
|
|
278
|
+
return null;
|
|
279
|
+
const promptScenarioHandleDigest = prompt[0].meta.scenarioHandleDigest;
|
|
280
|
+
const promptMessageIdDigest = prompt[0].meta.messageIdDigest;
|
|
281
|
+
if (![...terminals, ...callbacks, ...continuations, ...staleCallbacks, ...expiryObservations].every((entry) => entry.meta.scenarioHandleDigest === promptScenarioHandleDigest
|
|
282
|
+
&& entry.meta.messageIdDigest === promptMessageIdDigest))
|
|
283
|
+
return null;
|
|
284
|
+
const boundAt = Number(prompt[0].meta.boundAt);
|
|
285
|
+
const terminalEditStartedAt = Number(terminals[0].meta.terminalEditStartedAt);
|
|
286
|
+
const terminalizedAt = Number(terminals[0].meta.terminalizedAt);
|
|
287
|
+
const expiryObservation = expiryObservations[0] ?? null;
|
|
288
|
+
const expiryObservedAt = expiryObservation === null || expiryObservation.meta.expiryObservedAt === null ? null : Number(expiryObservation.meta.expiryObservedAt);
|
|
289
|
+
const expiryDeadlineAt = expiryObservation === null ? null : Number(expiryObservation.meta.expiryDeadlineAt);
|
|
290
|
+
if (!Number.isSafeInteger(boundAt) || boundAt < 0
|
|
291
|
+
|| !Number.isSafeInteger(terminalEditStartedAt) || terminalEditStartedAt < boundAt
|
|
292
|
+
|| !Number.isSafeInteger(terminalizedAt) || terminalizedAt < terminalEditStartedAt
|
|
293
|
+
|| terminalizedAt - terminalEditStartedAt > exports.SANCTUARY_APPROVAL_TERMINAL_EDIT_TIMEOUT_MS
|
|
294
|
+
|| terminals[0].meta.boundAt !== boundAt || terminals[0].meta.buttonsRemoved !== true
|
|
295
|
+
|| approval.expiresAt !== boundAt + exports.SANCTUARY_APPROVAL_TTL_MS)
|
|
296
|
+
return null;
|
|
297
|
+
if (approval.state === "expired") {
|
|
298
|
+
if (expiryObservedAt === null || !Number.isSafeInteger(expiryObservedAt) || expiryObservedAt < boundAt
|
|
299
|
+
|| expiryObservation?.meta.expiryObservationSchemaVersion !== "telegram-approval-expiry-observation-v1"
|
|
300
|
+
|| expiryObservation.meta.boundAt !== boundAt
|
|
301
|
+
|| terminals[0].meta.expiryObservedAt !== expiryObservedAt || terminals[0].meta.expiryDeadlineAt !== expiryDeadlineAt
|
|
302
|
+
|| terminalEditStartedAt < expiryObservedAt || terminalizedAt - expiryObservedAt > exports.SANCTUARY_APPROVAL_TERMINAL_EDIT_TIMEOUT_MS)
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
else if (expiryObservedAt !== null || terminals[0].meta.expiryObservedAt !== undefined || terminals[0].meta.expiryDeadlineAt !== undefined)
|
|
306
|
+
return null;
|
|
307
|
+
const callback = callbacks[0] ?? null;
|
|
308
|
+
if (callback) {
|
|
309
|
+
const callbackAt = Number(callback.meta.callbackAt);
|
|
310
|
+
if (!Number.isSafeInteger(callbackAt) || callbackAt < boundAt || callbackAt > terminalEditStartedAt
|
|
311
|
+
|| callback.meta.boundAt !== boundAt
|
|
312
|
+
|| (callback.event === "telegram.callback_settled" && (callback.meta.acknowledged !== true || callback.meta.acknowledgementState !== "acknowledged"))
|
|
313
|
+
|| (callback.event === "telegram.callback_recovery_settled" && (callback.meta.acknowledgementState !== "indeterminate_after_restart"
|
|
314
|
+
|| !Number.isSafeInteger(Number(callback.meta.recoveredAt)) || Number(callback.meta.recoveredAt) < terminalizedAt
|
|
315
|
+
|| !Number.isSafeInteger(callback.at) || callback.at < Number(callback.meta.recoveredAt)
|
|
316
|
+
|| typeof callback.meta.decisionAttemptDigest !== "string" || !SHA256.test(callback.meta.decisionAttemptDigest))))
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
const continuation = continuations[0] ?? null;
|
|
320
|
+
if (continuation) {
|
|
321
|
+
const deliveredAt = Number(continuation.meta.deliveredAt);
|
|
322
|
+
const callbackAt = callback === null ? null : Number(callback.meta.callbackAt);
|
|
323
|
+
if (!Number.isSafeInteger(deliveredAt) || deliveredAt < boundAt || deliveredAt > terminalEditStartedAt
|
|
324
|
+
|| callbackAt !== null && deliveredAt < callbackAt
|
|
325
|
+
|| continuation.meta.boundAt !== boundAt || typeof continuation.meta.resultDigest !== "string" || !SHA256.test(continuation.meta.resultDigest)
|
|
326
|
+
|| continuation.meta.resultDigest !== approval.resultDigest
|
|
327
|
+
|| typeof continuation.meta.deliveryDigest !== "string" || !SHA256.test(continuation.meta.deliveryDigest)
|
|
328
|
+
|| typeof continuation.meta.deliveryMessageIdDigest !== "string" || !SHA256.test(continuation.meta.deliveryMessageIdDigest))
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
const staleCallback = staleCallbacks[0] ?? null;
|
|
332
|
+
if (staleCallback) {
|
|
333
|
+
const staleAt = Number(staleCallback.meta.staleAt);
|
|
334
|
+
if (!Number.isSafeInteger(staleAt) || staleAt < terminalizedAt || staleCallback.meta.acknowledged !== true
|
|
335
|
+
|| staleCallback.meta.accepted !== false || staleCallback.meta.reason !== "stale_callback")
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
return { boundAt, callback, continuation, expiryObservedAt, expiryDeadlineAt, terminalizedAt, staleCallback };
|
|
339
|
+
}
|
|
340
|
+
function terminalizedWithinTtlJitter(evidence) {
|
|
341
|
+
const expiryObservedAt = evidence?.expiryObservedAt ?? null;
|
|
342
|
+
const expiryDeadlineAt = evidence?.expiryDeadlineAt ?? null;
|
|
343
|
+
if ([expiryObservedAt, expiryDeadlineAt].some((value) => value === null))
|
|
344
|
+
return false;
|
|
345
|
+
return expiryDeadlineAt === evidence.boundAt + exports.SANCTUARY_APPROVAL_TTL_MS
|
|
346
|
+
&& expiryObservedAt >= expiryDeadlineAt
|
|
347
|
+
&& expiryObservedAt <= expiryDeadlineAt + exports.SANCTUARY_APPROVAL_RECONCILIATION_JITTER_MS;
|
|
348
|
+
}
|
|
349
|
+
function interactiveReceiptBindsApproval(receipt, approval) {
|
|
350
|
+
const digestText = (value) => (0, node_crypto_1.createHash)("sha256").update(value, "utf8").digest("hex");
|
|
351
|
+
return receipt.approvalIdDigest === digestText(approval.approvalId)
|
|
352
|
+
&& receipt.checkpointDigest === approval.checkpointDigest
|
|
353
|
+
&& receipt.approvalEpochBefore === approval.approvalEpoch
|
|
354
|
+
&& typeof approval.suspendedSessionRevision === "string"
|
|
355
|
+
&& receipt.suspendedSessionRevisionDigest === digestText(approval.suspendedSessionRevision);
|
|
356
|
+
}
|
|
357
|
+
function healthProbeRestored(probe, currentCron) {
|
|
358
|
+
return probe.ownerImageDigestBefore === probe.ownerImageDigestAfter
|
|
359
|
+
&& probe.ownerContainerDigestBefore === probe.ownerContainerDigestAfter
|
|
360
|
+
&& (probe.schedulerReceipt !== null || probe.beforeStateDigest === probe.restoredStateDigest)
|
|
361
|
+
&& probe.cronFingerprintBefore === probe.cronFingerprintAfter
|
|
362
|
+
&& probe.cronRegisteredBefore && probe.cronRegisteredAfter && !probe.cronDegradedBefore && !probe.cronDegradedAfter
|
|
363
|
+
&& currentCron?.fingerprint === probe.cronFingerprintAfter && currentCron.registered === probe.cronRegisteredAfter
|
|
364
|
+
&& probe.workspaceAbsent && probe.socketAbsent && probe.snapshotAbsent && probe.realCheckEquivalent && probe.productionRestored;
|
|
365
|
+
}
|
|
366
|
+
function deriveSanctuaryScenarioAssertions(label, before, after, _now, scenarioHandleDigest) {
|
|
367
|
+
const approval = intendedApproval(before, after);
|
|
368
|
+
const newTurns = recordsAdded(before.telegramTurns, after.telegramTurns, (turn) => hash(turn));
|
|
369
|
+
const newAttempts = recordsAdded(before.restartAttempts, after.restartAttempts, (attempt) => hash(attempt));
|
|
370
|
+
const newApprovals = recordsAdded(before.approvals, after.approvals, (record) => record.approvalId);
|
|
371
|
+
const linkedAttempts = approval ? after.restartAttempts
|
|
372
|
+
.filter((attempt) => approval.toolName === "unraid_restart_container" && attempt.approvalId === approval.approvalId && attempt.argumentDigest === approval.argumentDigest && attempt.target === approval.target)
|
|
373
|
+
.sort((left, right) => left.observedAt - right.observedAt) : [];
|
|
374
|
+
const attemptIsLinked = (attempt) => {
|
|
375
|
+
if (!approval || !intendedRestartApproval(approval) || attempt.approvalId !== approval.approvalId
|
|
376
|
+
|| attempt.argumentDigest !== approval.argumentDigest || attempt.target !== CANONICAL_RESTART_TARGET
|
|
377
|
+
|| typeof attempt.targetId !== "string" || attempt.targetId.length < 1 || attempt.targetId.length > 128
|
|
378
|
+
|| approval.resultTargetId === null || attempt.targetId !== approval.resultTargetId)
|
|
379
|
+
return false;
|
|
380
|
+
const expectedActionDigest = (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({ operation: "restart", container: { id: attempt.targetId, name: CANONICAL_RESTART_TARGET } })).digest("hex");
|
|
381
|
+
return attempt.actionDigest === expectedActionDigest;
|
|
382
|
+
};
|
|
383
|
+
const completeAttemptLedgerLinked = newAttempts.every(attemptIsLinked) && linkedAttempts.length === newAttempts.length
|
|
384
|
+
&& new Set(linkedAttempts.map((attempt) => attempt.targetId)).size <= 1;
|
|
385
|
+
const mutationAttemptIds = new Set(linkedAttempts.filter((attempt) => attempt.state === "attempting").map((attempt) => attempt.attemptId));
|
|
386
|
+
const mutationCount = mutationAttemptIds.size;
|
|
387
|
+
const unsafeAttemptStates = new Set(["attempting", "succeeded", "attempted_or_indeterminate"]);
|
|
388
|
+
const scenarioMutationCount = new Set(after.restartAttempts.filter((attempt) => unsafeAttemptStates.has(attempt.state)).map((attempt) => attempt.attemptId)).size;
|
|
389
|
+
const restartSucceeded = linkedAttempts.some((attempt) => attempt.state === "succeeded" && mutationAttemptIds.has(attempt.attemptId)
|
|
390
|
+
&& (attempt.mutationAcknowledged || attempt.afterState === "running"));
|
|
391
|
+
const firstIndeterminate = linkedAttempts.find((attempt) => attempt.state === "attempted_or_indeterminate");
|
|
392
|
+
const attemptedIndeterminateRetryCount = firstIndeterminate
|
|
393
|
+
? new Set(linkedAttempts.filter((attempt) => attempt.state === "attempting" && attempt.observedAt > firstIndeterminate.observedAt && attempt.attemptId !== firstIndeterminate.attemptId).map((attempt) => attempt.attemptId)).size
|
|
394
|
+
: 0;
|
|
395
|
+
const deliveredTurns = newTurns.filter((turn) => turn.status === "success" && turn.deliveryCount > 0);
|
|
396
|
+
const telegramResponses = deliveredTurns.length;
|
|
397
|
+
const approvalTransitions = delta(after, before, "approval.acceptance_transition");
|
|
398
|
+
const postbootIntegrity = after.prebootIntegrity && after.postbootIntegrity
|
|
399
|
+
&& typeof scenarioHandleDigest === "string"
|
|
400
|
+
? verifySanctuaryPostbootIntegrity(after.prebootIntegrity, after.postbootIntegrity, scenarioHandleDigest) : null;
|
|
401
|
+
switch (label) {
|
|
402
|
+
case "unit-12c-1-opaque-identity":
|
|
403
|
+
case "unit-14b-3-opaque-identity-live":
|
|
404
|
+
if (!after.identity || (after.identity.inspectedRecordCount ?? 0) < 1 || (after.identity.opaqueSubjectCount ?? 0) < 1 || after.identity.mismatchCount !== 0 || after.identity.rawLeakCount !== 0
|
|
405
|
+
|| (label.includes("live") && (telegramResponses !== 1 || newTurns.length !== 1 || !after.identity.liveSubjectObserved
|
|
406
|
+
|| after.identity.canonicalSessionCount !== 1 || after.identity.canonicalFriendCount !== 1
|
|
407
|
+
|| !exactFreshTelegramLifecycle(before, after, newTurns[0]?.updateDigest))))
|
|
408
|
+
return null;
|
|
409
|
+
return { identityBound: after.identity.keyPresent, opaqueSubject: after.identity.subjectOpaque, rawIdentityAbsent: after.identity.rawIdentityAbsent };
|
|
410
|
+
case "unit-15c-1-no-callback-terminalization": {
|
|
411
|
+
if (!intendedRestartApproval(approval) || approval.state !== "expired" || approval.createdAt < before.capturedAt)
|
|
412
|
+
return null;
|
|
413
|
+
const evidence = exactApprovalEvidence(before, after, approval);
|
|
414
|
+
if (!terminalizedWithinTtlJitter(evidence) || !evidence || evidence.callback !== null || evidence.continuation !== null)
|
|
415
|
+
return null;
|
|
416
|
+
const elapsedMs = evidence.terminalizedAt - evidence.boundAt;
|
|
417
|
+
if (!approval.buttonsRemoved || !approval.terminalPrompt)
|
|
418
|
+
return null;
|
|
419
|
+
const baseline = before.sourceValues["no-callback-baseline"];
|
|
420
|
+
if (!baseline || typeof baseline !== "object" || Array.isArray(baseline))
|
|
421
|
+
return null;
|
|
422
|
+
const baselineRecord = baseline;
|
|
423
|
+
const inboundEventCount = after.events.filter((entry) => entry.event === "telegram.callback_settled" || entry.event === "telegram.callback_recovery_settled" || entry.event === "telegram.update_dropped").length;
|
|
424
|
+
const noInboundUpdate = baselineRecord.approvalId === approval.approvalId
|
|
425
|
+
&& baselineRecord.offsetDigest === hash(after.sourceValues["telegram-offset"])
|
|
426
|
+
&& baselineRecord.inboundEventCount === inboundEventCount
|
|
427
|
+
&& approval.callbackCount === 0;
|
|
428
|
+
if (!noInboundUpdate || scenarioMutationCount !== 0 || !completeAttemptLedgerLinked)
|
|
429
|
+
return null;
|
|
430
|
+
return { buttonsRemoved: approval.buttonsRemoved, elapsedMs, mutationCount: 0, noInboundUpdate, replayMutationCount: approval.replayMutationCount, terminalExpired: approval.state === "expired", ttlMs: exports.SANCTUARY_APPROVAL_TTL_MS };
|
|
431
|
+
}
|
|
432
|
+
case "unit-16a-pre-reboot-checkpoint":
|
|
433
|
+
if (!after.reboot || after.reboot.phase !== "preflight" || !SHA256.test(after.reboot.processBindingDigest) || after.reboot.requestCount !== 0 || !after.container?.running || !after.container.healthy || after.reboot.unrelatedHostOperations !== 0)
|
|
434
|
+
return null;
|
|
435
|
+
return { approvalDigest: hash(after.approvals), auditDigest: hash(after.events), containerDigest: hash(after.container), fingerprintDigest: hash(after.cron), offsetDigest: hash(after.sourceValues["telegram-offset"]), processBindingDigest: after.reboot.processBindingDigest, ready: Boolean(after.container?.running && after.container.healthy), unrelatedHostOperations: after.reboot.unrelatedHostOperations };
|
|
436
|
+
case "unit-16a-reboot-request":
|
|
437
|
+
if (!after.reboot || after.reboot.phase !== "requested" || !SHA256.test(after.reboot.processBindingDigest) || after.reboot.requestCount !== 1 || !after.reboot.checkpointPersisted)
|
|
438
|
+
return null;
|
|
439
|
+
return { exactlyOnce: true, processBindingDigest: after.reboot.processBindingDigest, requestCheckpointPersisted: true, requestDigest: after.reboot.requestDigest };
|
|
440
|
+
case "unit-16a-boot-recovery-milestones":
|
|
441
|
+
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)
|
|
442
|
+
return null;
|
|
443
|
+
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 };
|
|
444
|
+
case "unit-16b-runtime-vault-containment":
|
|
445
|
+
if (!after.container)
|
|
446
|
+
return null;
|
|
447
|
+
if (!after.container.autostartExact || !after.container.exactImage || after.container.manualAuthRequired
|
|
448
|
+
|| after.container.mountCount !== 2 || Number(after.container.user.split(":")[0]) !== 10001
|
|
449
|
+
|| after.container.publishedPortCount !== 0 || !after.container.readOnlyRoot
|
|
450
|
+
|| !after.container.updaterDisabled || !after.container.vaultUnlocked)
|
|
451
|
+
return null;
|
|
452
|
+
return { autostartExact: after.container.autostartExact, exactImage: after.container.exactImage, manualAuthRequired: after.container.manualAuthRequired, mountCount: after.container.mountCount, nonRootUid: Number(after.container.user.split(":")[0]), publishedPortCount: after.container.publishedPortCount, readOnlyRoot: after.container.readOnlyRoot, updaterDisabled: after.container.updaterDisabled, vaultUnlocked: after.container.vaultUnlocked };
|
|
453
|
+
case "unit-16c-provider-readiness":
|
|
454
|
+
return after.provider && after.provider.outwardReady && after.provider.innerReady && after.provider.geminiCandidateReady && after.provider.providersDistinct && !after.provider.silentFallback
|
|
455
|
+
&& after.provider.credentialRevisionsPresent === true && after.provider.requestSemanticsExact === true && after.provider.fallbackAttemptCount === 0
|
|
456
|
+
&& after.provider.modelsExact === true && after.provider.baseUrlsExact === true && after.provider.vaultCoordinatesExact === true && after.provider.credentialIdentitiesDistinct === true
|
|
457
|
+
? { outwardReady: true, innerReady: true, geminiCandidateReady: true, providersDistinct: true, silentFallback: false, modelsExact: true, baseUrlsExact: true, vaultCoordinatesExact: true, credentialIdentitiesDistinct: true } : null;
|
|
458
|
+
case "unit-16d-whats-up":
|
|
459
|
+
if (telegramResponses !== 1 || newTurns.length !== 1 || !exactFreshTelegramLifecycle(before, after, newTurns[0].updateDigest) || deliveredTurns[0].toolInvocationCount !== 1 || deliveredTurns[0].toolResultDigests.length !== 1 || !turnHasGroundedRead(after, deliveredTurns[0], "unraid_get_system") || !exactGroundedResponse(after, deliveredTurns[0], "unraid_get_system"))
|
|
460
|
+
return null;
|
|
461
|
+
return { accurate: true, authorized: true, grounded: true, liveFactsMatched: true, responseCount: telegramResponses, responseWithinLimit: true, telegramDelivered: true };
|
|
462
|
+
case "unit-16d-1-space":
|
|
463
|
+
if (telegramResponses !== 1 || newTurns.length !== 1 || !exactFreshTelegramLifecycle(before, after, newTurns[0].updateDigest) || deliveredTurns[0].toolInvocationCount !== 1 || deliveredTurns[0].toolResultDigests.length !== 1 || !turnHasGroundedRead(after, deliveredTurns[0], "unraid_get_storage") || !exactGroundedResponse(after, deliveredTurns[0], "unraid_get_storage"))
|
|
464
|
+
return null;
|
|
465
|
+
return { accurate: true, authorized: true, grounded: true, liveFactsMatched: true, mutationCount: scenarioMutationCount, responseCount: telegramResponses, responseWithinLimit: true, telegramDelivered: true };
|
|
466
|
+
case "unit-16d-2-unauthorized": {
|
|
467
|
+
if (before.events.length > after.events.length || before.events.some((entry, index) => hash(entry) !== hash(after.events[index])))
|
|
468
|
+
return null;
|
|
469
|
+
const freshEvents = recordsAdded(before.events, after.events, (entry) => hash(entry));
|
|
470
|
+
const freshDrops = freshEvents.filter((entry) => entry.event === "telegram.update_dropped");
|
|
471
|
+
if (freshDrops.length !== 1)
|
|
472
|
+
return null;
|
|
473
|
+
const drop = freshDrops[0];
|
|
474
|
+
const distinctAccount = drop.meta.distinctAccount === true
|
|
475
|
+
&& typeof drop.meta.scenarioHandleDigest === "string" && SHA256.test(drop.meta.scenarioHandleDigest)
|
|
476
|
+
&& typeof drop.meta.updateDigest === "string" && SHA256.test(drop.meta.updateDigest)
|
|
477
|
+
&& typeof drop.meta.senderIdentityDigest === "string" && SHA256.test(drop.meta.senderIdentityDigest)
|
|
478
|
+
&& typeof drop.meta.authorizedIdentityDigest === "string" && SHA256.test(drop.meta.authorizedIdentityDigest)
|
|
479
|
+
&& drop.meta.senderIdentityDigest !== drop.meta.authorizedIdentityDigest
|
|
480
|
+
&& drop.meta.senderDistinct === true
|
|
481
|
+
&& typeof drop.meta.nextOffsetDigest === "string" && SHA256.test(drop.meta.nextOffsetDigest)
|
|
482
|
+
&& typeof drop.meta.dropMac === "string" && SHA256.test(drop.meta.dropMac);
|
|
483
|
+
const providerInvocationCount = newTurns.reduce((sum, turn) => sum + turn.providerTurnCount, 0);
|
|
484
|
+
const toolInvocationCount = newTurns.reduce((sum, turn) => sum + turn.toolInvocationCount, 0);
|
|
485
|
+
const durableToolRecordCount = delta(after, before, "senses.sanctuary_read_receipt");
|
|
486
|
+
const workItemCount = newApprovals.length;
|
|
487
|
+
const sessionStateUnchanged = Boolean(before.identity && after.identity
|
|
488
|
+
&& typeof before.identity.sessionSurfaceDigest === "string" && SHA256.test(before.identity.sessionSurfaceDigest)
|
|
489
|
+
&& before.identity.sessionSurfaceDigest === after.identity.sessionSurfaceDigest
|
|
490
|
+
&& typeof before.identity.friendSurfaceDigest === "string" && SHA256.test(before.identity.friendSurfaceDigest)
|
|
491
|
+
&& before.identity.friendSurfaceDigest === after.identity.friendSurfaceDigest);
|
|
492
|
+
const forbiddenWorkEvent = freshEvents.some((entry) => entry.event === "senses.telegram_turn_start" || entry.event === "senses.telegram_turn_end"
|
|
493
|
+
|| entry.event === "senses.telegram_turn_error" || entry.event === "senses.sanctuary_read_receipt"
|
|
494
|
+
|| entry.event.startsWith("senses.telegram_approved_restart_") || entry.event === "senses.sanctuary_health_delivered");
|
|
495
|
+
const offsetAdvanced = Number.isSafeInteger(before.telegramNextUpdateId) && Number.isSafeInteger(after.telegramNextUpdateId)
|
|
496
|
+
&& Number(after.telegramNextUpdateId) === Number(before.telegramNextUpdateId) + 1;
|
|
497
|
+
const zeroWorkUnchanged = Boolean(before.zeroWork && after.zeroWork
|
|
498
|
+
&& before.zeroWork.providerToolDigest === after.zeroWork.providerToolDigest
|
|
499
|
+
&& before.zeroWork.outwardDigest === after.zeroWork.outwardDigest
|
|
500
|
+
&& before.zeroWork.approvalMutationDigest === after.zeroWork.approvalMutationDigest
|
|
501
|
+
&& before.zeroWork.sessionFriendDigest === after.zeroWork.sessionFriendDigest);
|
|
502
|
+
if (!after.containment || !exactContainmentAudit(after.containment) || !distinctAccount || !offsetAdvanced || !zeroWorkUnchanged || !sessionStateUnchanged || newTurns.length !== 0 || providerInvocationCount !== 0 || toolInvocationCount !== 0 || telegramResponses !== 0 || workItemCount !== 0 || approvalTransitions !== 0 || newAttempts.length !== 0 || scenarioMutationCount !== 0 || durableToolRecordCount !== 0 || forbiddenWorkEvent)
|
|
503
|
+
return null;
|
|
504
|
+
return { auditRejected: true, distinctAccount, mutationCount: 0, providerInvocationCount: 0, responseCount: 0, workItemCount: 0 };
|
|
505
|
+
}
|
|
506
|
+
case "unit-16e-containment-audit":
|
|
507
|
+
if (!after.containment || !exactContainmentAudit(after.containment) || scenarioMutationCount !== 0)
|
|
508
|
+
return null;
|
|
509
|
+
return {
|
|
510
|
+
schemaVersion: after.containment.schemaVersion,
|
|
511
|
+
keyCount: after.containment.keyCount, keyInventoryDigest: after.containment.keyInventoryDigest,
|
|
512
|
+
readScopeDigest: after.containment.readScopeDigest, writeScopeDigest: after.containment.writeScopeDigest,
|
|
513
|
+
keyRoleAssignmentCount: after.containment.keyRoleAssignmentCount,
|
|
514
|
+
telegramToolCount: after.containment.telegramToolCount, telegramProfileDigest: after.containment.telegramProfileDigest,
|
|
515
|
+
telegramSchemaDigest: after.containment.telegramSchemaDigest,
|
|
516
|
+
privateToolCount: after.containment.privateToolCount, privateProfileDigest: after.containment.privateProfileDigest,
|
|
517
|
+
privateSchemaDigest: after.containment.privateSchemaDigest, resolvedHandlerCount: after.containment.resolvedHandlerCount,
|
|
518
|
+
excludedToolCount: after.containment.excludedToolCount, excludedSchemaIntersectionCount: after.containment.excludedSchemaIntersectionCount,
|
|
519
|
+
fabricatedHandlerInvocationCount: after.containment.fabricatedHandlerInvocationCount,
|
|
520
|
+
excludedToolAttemptCount: after.containment.excludedToolAttemptCount, excludedToolRejectedCount: after.containment.excludedToolRejectedCount,
|
|
521
|
+
excludedToolInvokedCount: after.containment.excludedToolInvokedCount, excludedToolSideEffectCount: after.containment.excludedToolSideEffectCount,
|
|
522
|
+
globallyResolvableExcludedToolCount: after.containment.globallyResolvableExcludedToolCount,
|
|
523
|
+
auditPathDigest: after.containment.auditPathDigest, auditLedgerDigest: after.containment.auditLedgerDigest,
|
|
524
|
+
auditRecordCount: after.containment.auditRecordCount, auditLifecyclePairCount: after.containment.auditLifecyclePairCount,
|
|
525
|
+
containerUser: after.containment.containerUser, liveProcessUser: after.containment.liveProcessUser, mountCount: after.containment.mountCount,
|
|
526
|
+
publishedPortCount: after.containment.publishedPortCount, networkMode: after.containment.networkMode,
|
|
527
|
+
readOnlyRoot: after.containment.readOnlyRoot, mountsExact: after.containment.mountsExact,
|
|
528
|
+
securityExact: after.containment.securityExact, updaterDisabled: after.containment.updaterDisabled,
|
|
529
|
+
writableKeyExposure: after.containment.writableKeyExposure,
|
|
530
|
+
rawWriteMaterialFieldCount: after.containment.rawWriteMaterialFieldCount,
|
|
531
|
+
typedWriteExecutorCount: after.containment.typedWriteExecutorCount,
|
|
532
|
+
writeApprovalPolicyDigest: after.containment.writeApprovalPolicyDigest,
|
|
533
|
+
sensitiveMaterialObserved: after.containment.sensitiveMaterialObserved,
|
|
534
|
+
mutationCount: scenarioMutationCount,
|
|
535
|
+
};
|
|
536
|
+
case "unit-16e-1-stop-denial":
|
|
537
|
+
case "unit-16e-2-restart-denial": {
|
|
538
|
+
const receipt = after.denial;
|
|
539
|
+
const expectedOperation = label === "unit-16e-1-stop-denial" ? "stop" : "restart";
|
|
540
|
+
if (!receipt || receipt.label !== label || receipt.operation !== expectedOperation || receipt.scenarioHandleDigest.length !== 64
|
|
541
|
+
|| receipt.attemptCount !== 1 || ![200, 401, 403].includes(receipt.httpStatus)
|
|
542
|
+
|| (receipt.errorCode !== "FORBIDDEN" && receipt.errorCode !== "PERMISSION_DENIED")
|
|
543
|
+
|| receipt.targetDigest !== receipt.before.targetContainerIdDigest || receipt.targetDigest !== receipt.after.targetContainerIdDigest
|
|
544
|
+
|| JSON.stringify(receipt.before) !== JSON.stringify(receipt.after) || scenarioMutationCount !== 0)
|
|
545
|
+
return null;
|
|
546
|
+
return { attemptCount: receipt.attemptCount, cursorBoundaryCount: 7, denied: true, mutationCount: 0, restartCountUnchanged: true, resumed: true };
|
|
547
|
+
}
|
|
548
|
+
case "unit-16f-cron-fingerprint":
|
|
549
|
+
if (!after.container?.running || !after.container.healthy || !after.healthProbe || !healthProbeRestored(after.healthProbe, after.cron) || after.healthProbe.clockMode !== "ambient" || after.healthProbe.phases.length !== 1
|
|
550
|
+
|| !after.healthProbe.schedulerReceipt || after.healthProbe.schedulerReceipt.trigger !== "cron" || after.healthProbe.schedulerReceipt.sweepDelta !== 1
|
|
551
|
+
|| after.healthProbe.schedulerReceipt.deliveryDelta !== 0 || after.healthProbe.schedulerReceipt.nonReplay !== true
|
|
552
|
+
|| after.healthProbe.schedulerReceipt.sweep.recordDigest !== after.healthProbe.phases[0]?.sweepReceiptDigest
|
|
553
|
+
|| after.healthProbe.phases[0]?.name !== "cron-unchanged" || after.healthProbe.phases[0].trigger !== "cron" || after.healthProbe.phases[0].fixtureStatus !== null
|
|
554
|
+
|| after.healthProbe.phases[0].opened !== 0 || after.healthProbe.phases[0].recovered !== 0 || after.healthProbe.phases[0].digestDue
|
|
555
|
+
|| after.healthProbe.phases[0].deliveryKind !== null || after.healthProbe.phases[0].deliveryReceiptDigest !== null
|
|
556
|
+
|| after.healthProbe.privateTurnCount !== 0 || after.healthProbe.providerInvocationCount !== 0 || after.healthProbe.deliveryCount !== 0 || telegramResponses !== 0 || delta(after, before, "senses.telegram_turn_start") !== 0)
|
|
557
|
+
return null;
|
|
558
|
+
return { fingerprintUnchanged: true, messageCount: 0, providerInvocationCount: 0, receiptUnchanged: true, scheduleRegistered: true, sweepObserved: true };
|
|
559
|
+
case "unit-16g-health-transition": {
|
|
560
|
+
const probe = after.healthProbe;
|
|
561
|
+
const exactPhases = [
|
|
562
|
+
["live-baseline", null, 0, 0, null], ["live-repeat", null, 0, 0, null], ["fixture-fail", 503, 1, 0, "transition"],
|
|
563
|
+
["fixture-repeat", 503, 0, 0, null], ["fixture-recover", 200, 0, 1, "transition"], ["fixture-refail", 503, 1, 0, "transition"],
|
|
564
|
+
];
|
|
565
|
+
if (!after.container?.running || !after.container.healthy || !probe || !healthProbeRestored(probe, after.cron) || probe.clockMode !== "ambient" || probe.privateTurnCount !== 3
|
|
566
|
+
|| probe.providerInvocationCount < probe.privateTurnCount || probe.providerInvocationCount > 1_000 || probe.deliveryCount !== 3 || probe.phases.length !== exactPhases.length
|
|
567
|
+
|| !probe.phases.every((phase, index) => phase.ordinal === index + 1 && phase.name === exactPhases[index][0] && phase.trigger === "acceptance"
|
|
568
|
+
&& phase.fixtureStatus === exactPhases[index][1] && phase.opened === exactPhases[index][2] && phase.recovered === exactPhases[index][3]
|
|
569
|
+
&& phase.deliveryKind === exactPhases[index][4] && phase.digestDue === false && (phase.deliveryKind === null) === (phase.deliveryReceiptDigest === null)))
|
|
570
|
+
return null;
|
|
571
|
+
return { alertCount: 3, productionRestored: true, transitionObserved: true };
|
|
572
|
+
}
|
|
573
|
+
case "unit-16h-daily-digest": {
|
|
574
|
+
const probe = after.healthProbe;
|
|
575
|
+
if (!after.container?.running || !after.container.healthy || !probe || !healthProbeRestored(probe, after.cron) || probe.clockMode !== "local-daily-boundary" || probe.privateTurnCount !== 1
|
|
576
|
+
|| probe.providerInvocationCount < probe.privateTurnCount || probe.providerInvocationCount > 1_000 || probe.deliveryCount !== 1 || probe.phases.length !== 2
|
|
577
|
+
|| probe.phases[0]?.ordinal !== 1 || probe.phases[0].name !== "digest-first" || probe.phases[0].trigger !== "acceptance" || probe.phases[0].fixtureStatus !== 503
|
|
578
|
+
|| !probe.phases[0].digestDue || probe.phases[0].deliveryKind !== "digest" || probe.phases[0].deliveryReceiptDigest === null
|
|
579
|
+
|| probe.phases[1]?.ordinal !== 2 || probe.phases[1].name !== "digest-repeat" || probe.phases[1].trigger !== "acceptance" || probe.phases[1].fixtureStatus !== 503
|
|
580
|
+
|| probe.phases[1].digestDue || probe.phases[1].deliveryKind !== null || probe.phases[1].deliveryReceiptDigest !== null)
|
|
581
|
+
return null;
|
|
582
|
+
return { firedWithinMs: 0, messageCount: 1, productionRestored: true, scheduleObserved: true };
|
|
583
|
+
}
|
|
584
|
+
case "unit-16i-delayed-approval":
|
|
585
|
+
if (!exactFreshTelegramLifecycle(before, after) || !intendedRestartApproval(approval) || !completeAttemptLedgerLinked || approval.state !== "succeeded" || mutationCount !== 1 || scenarioMutationCount !== 1 || !restartSucceeded || approval.replayMutationCount !== 0 || !approval.continuationCompleted)
|
|
586
|
+
return null;
|
|
587
|
+
if (!approval.terminalPrompt)
|
|
588
|
+
return null;
|
|
589
|
+
{
|
|
590
|
+
const evidence = exactApprovalEvidence(before, after, approval);
|
|
591
|
+
const callbackAt = Number(evidence?.callback?.meta.callbackAt);
|
|
592
|
+
if (!evidence || !evidence.callback || !evidence.continuation || callbackAt - evidence.boundAt < 120_000
|
|
593
|
+
|| evidence.callback.event !== "telegram.callback_settled" || evidence.callback.meta.accepted !== true || evidence.callback.meta.reason !== "accepted")
|
|
594
|
+
return null;
|
|
595
|
+
return { elapsedMs: callbackAt - evidence.boundAt, mutationCount, promptTerminal: approval.terminalPrompt, replayMutationCount: approval.replayMutationCount, resumed: approval.continuationCompleted, state: approval.state };
|
|
596
|
+
}
|
|
597
|
+
case "unit-16j-denial": {
|
|
598
|
+
if (!exactFreshTelegramLifecycle(before, after) || !intendedRestartApproval(approval) || !completeAttemptLedgerLinked || approval.state !== "denied" || scenarioMutationCount !== 0 || approval.replayMutationCount !== 0 || !approval.continuationCompleted)
|
|
599
|
+
return null;
|
|
600
|
+
if (!approval.terminalPrompt)
|
|
601
|
+
return null;
|
|
602
|
+
const evidence = exactApprovalEvidence(before, after, approval);
|
|
603
|
+
if (!evidence?.callback || evidence.callback.event !== "telegram.callback_settled" || !evidence.continuation || evidence.callback.meta.accepted !== false || evidence.callback.meta.reason !== "decision_refused")
|
|
604
|
+
return null;
|
|
605
|
+
return { mutationCount, promptTerminal: approval.terminalPrompt, replayMutationCount: approval.replayMutationCount, resumed: approval.continuationCompleted, state: approval.state };
|
|
606
|
+
}
|
|
607
|
+
case "unit-16k-timeout-stale": {
|
|
608
|
+
if (!intendedRestartApproval(approval) || !completeAttemptLedgerLinked || approval.state !== "expired" || scenarioMutationCount !== 0 || approval.replayMutationCount !== 0)
|
|
609
|
+
return null;
|
|
610
|
+
if (!approval.buttonsRemoved || !approval.terminalPrompt || !approval.staleAcknowledged)
|
|
611
|
+
return null;
|
|
612
|
+
const evidence = exactApprovalEvidence(before, after, approval);
|
|
613
|
+
if (!terminalizedWithinTtlJitter(evidence) || !evidence || evidence.callback !== null || evidence.continuation !== null || evidence.staleCallback === null)
|
|
614
|
+
return null;
|
|
615
|
+
const driver = after.interactiveDriver;
|
|
616
|
+
if (!driver || driver.label !== "unit-16k-timeout-stale" || !interactiveReceiptBindsApproval(driver, approval)
|
|
617
|
+
|| driver.callbackAttempts !== 1 || driver.distinctQueryCount !== 1 || driver.settledCount !== 1
|
|
618
|
+
|| driver.claimCount !== 0 || driver.mutationCount !== 0 || !driver.staleAcknowledged || !driver.promptTerminal)
|
|
619
|
+
return null;
|
|
620
|
+
return { buttonsRemoved: approval.buttonsRemoved, mutationCount, promptTerminal: approval.terminalPrompt, staleAcknowledged: approval.staleAcknowledged, staleReplayMutationCount: approval.replayMutationCount, state: approval.state };
|
|
621
|
+
}
|
|
622
|
+
case "unit-16l-duplicate-callback": {
|
|
623
|
+
if (!intendedRestartApproval(approval) || !completeAttemptLedgerLinked || approval.callbackCount < 1 || approval.settledCount < 1 || approval.claimCount !== 1 || !approval.terminalPrompt || approval.replayMutationCount !== 0 || mutationCount !== 1 || scenarioMutationCount !== 1 || !restartSucceeded)
|
|
624
|
+
return null;
|
|
625
|
+
const evidence = exactApprovalEvidence(before, after, approval);
|
|
626
|
+
if (!evidence?.callback || evidence.callback.event !== "telegram.callback_settled" || !evidence.continuation || evidence.callback.meta.accepted !== true || evidence.callback.meta.reason !== "accepted")
|
|
627
|
+
return null;
|
|
628
|
+
const driver = after.interactiveDriver;
|
|
629
|
+
if (!driver || driver.label !== "unit-16l-duplicate-callback"
|
|
630
|
+
|| !interactiveReceiptBindsApproval(driver, approval) || driver.callbackAttempts !== 2 || driver.distinctQueryCount !== 2
|
|
631
|
+
|| !driver.barrierObserved || driver.settledCount !== 2 || driver.claimCount !== 1 || driver.mutationCount !== 1
|
|
632
|
+
|| driver.staleReplayAttempts !== 1 || !driver.staleReplaySettled || driver.staleReplayMutationCount !== 0
|
|
633
|
+
|| !driver.promptTerminal || driver.writeCredentialObserved)
|
|
634
|
+
return null;
|
|
635
|
+
return { callbackCount: driver.callbackAttempts, claimCount: driver.claimCount, mutationCount, promptTerminal: approval.terminalPrompt, replayMutationCount: approval.replayMutationCount, settledCount: driver.settledCount, staleReplaySettled: true, writeCredentialAbsent: true };
|
|
636
|
+
}
|
|
637
|
+
case "unit-16m-restart-continuation":
|
|
638
|
+
if (!intendedRestartApproval(approval) || !completeAttemptLedgerLinked || approval.state !== "succeeded" || mutationCount !== 1 || scenarioMutationCount !== 1 || !restartSucceeded || attemptedIndeterminateRetryCount !== 0)
|
|
639
|
+
return null;
|
|
640
|
+
{
|
|
641
|
+
const evidence = exactApprovalEvidence(before, after, approval);
|
|
642
|
+
if (!evidence?.continuation || !evidence.callback || evidence.callback.event !== "telegram.callback_recovery_settled"
|
|
643
|
+
|| evidence.callback.meta.acknowledgementState !== "indeterminate_after_restart"
|
|
644
|
+
|| evidence.callback.meta.accepted !== true || evidence.callback.meta.reason !== "accepted"
|
|
645
|
+
|| typeof evidence.callback.meta.decisionAttemptDigest !== "string" || !SHA256.test(evidence.callback.meta.decisionAttemptDigest))
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
if (!after.interactiveDriver || after.interactiveDriver.label !== "unit-16m-restart-continuation"
|
|
649
|
+
|| !interactiveReceiptBindsApproval(after.interactiveDriver, approval)
|
|
650
|
+
|| !after.interactiveDriver.pendingRestored || after.interactiveDriver.pendingDigestBefore !== after.interactiveDriver.pendingDigestAfter || after.interactiveDriver.callbackAttempts !== 1 || after.interactiveDriver.mutationCount !== 1
|
|
651
|
+
|| after.interactiveDriver.approvalEpochBefore !== after.interactiveDriver.approvalEpochAfterRestart
|
|
652
|
+
|| after.interactiveDriver.continuationEpochAfter <= after.interactiveDriver.approvalEpochAfterRestart
|
|
653
|
+
|| approval.continuationEpoch !== after.interactiveDriver.continuationEpochAfter || approval.continuationState !== "completed"
|
|
654
|
+
|| after.interactiveDriver.restartCountAfter !== after.interactiveDriver.restartCountBefore + 1
|
|
655
|
+
|| !after.interactiveDriver.indeterminateRecoveryObserved || !after.interactiveDriver.attemptedRecoveryReopened
|
|
656
|
+
|| !SHA256.test(after.interactiveDriver.attemptedRecordDigest) || !SHA256.test(after.interactiveDriver.recoveredRecordDigest)
|
|
657
|
+
|| after.interactiveDriver.attemptedRecordDigest === after.interactiveDriver.recoveredRecordDigest
|
|
658
|
+
|| after.interactiveDriver.indeterminateRetryCount !== 0)
|
|
659
|
+
return null;
|
|
660
|
+
return {
|
|
661
|
+
attemptedIndeterminateRetryCount, mutationCount, preAttemptResumed: after.interactiveDriver.pendingRestored,
|
|
662
|
+
butlerRestartObserved: true, checkpointEpochPreserved: true, continuationEpochAdvanced: true,
|
|
663
|
+
restartObserved: restartSucceeded && after.events.filter((entry) => entry.event === "senses.telegram_approved_restart_end" && entry.meta.approvalId === approval.approvalId).length - before.events.filter((entry) => entry.event === "senses.telegram_approved_restart_end" && entry.meta.approvalId === approval.approvalId).length === 1,
|
|
664
|
+
state: approval.state,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function createSanctuaryScenarioCapture(deps) {
|
|
669
|
+
const receiptRoot = deps.receiptRoot ?? path.join(deps.agentRoot, "state", "acceptance", "receipts");
|
|
670
|
+
const receiptPath = (checkpointDigest) => path.join(receiptRoot, `${checkpointDigest}.json`);
|
|
671
|
+
return async (input) => {
|
|
672
|
+
if (input.phase === "begin") {
|
|
673
|
+
const startedAt = new Date(deps.now()).toISOString();
|
|
674
|
+
const scenarioHandleDigest = (0, node_crypto_1.createHash)("sha256").update((0, node_crypto_1.randomBytes)(32)).digest("hex");
|
|
675
|
+
const checkpointDigest = hash({ label: input.label, gate: input.externalGate, sources: input.sources, startedAt, scenarioHandleDigest });
|
|
676
|
+
const healthScenario = HEALTH_SCENARIO_LABELS.has(input.label);
|
|
677
|
+
const capturedBefore = await deps.readFacts(input.label, scenarioHandleDigest, healthScenario ? { skipContainerSnapshot: true } : undefined);
|
|
678
|
+
const before = { ...capturedBefore, sourceValues: Object.fromEntries(Object.entries(capturedBefore.sourceValues).map(([source, value]) => [source, hash(value)])) };
|
|
679
|
+
const receipt = { schemaVersion: "sanctuary-acceptance-receipt-v1", label: input.label, gate: input.externalGate, sources: [...input.sources], checkpointDigest, scenarioHandleDigest, startedAt, before };
|
|
680
|
+
(0, sanctuary_acceptance_marker_1.writeSanctuaryAcceptanceMarker)("sanctuary", { schemaVersion: "sanctuary-acceptance-marker-v1", label: input.label, scenarioHandleDigest, startedAt }, deps.agentRoot);
|
|
681
|
+
atomicPrivateJson(receiptPath(checkpointDigest), receipt);
|
|
682
|
+
(0, sanctuary_acceptance_marker_1.publishSanctuaryAcceptanceGateStatus)({ label: input.label, gate: input.externalGate, phase: "waiting", startedAt }, deps.gateStatusPath);
|
|
683
|
+
if (healthScenario) {
|
|
684
|
+
if (!deps.healthDriver)
|
|
685
|
+
throw new Error("health scenario driver is unavailable");
|
|
686
|
+
await deps.healthDriver.begin(input.label, scenarioHandleDigest);
|
|
687
|
+
}
|
|
688
|
+
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_acceptance_scenario_begin", message: "Sanctuary live acceptance scenario began", meta: { label: input.label, gate: input.externalGate, scenarioHandleDigest } });
|
|
689
|
+
return { state: "waiting", checkpointDigest };
|
|
690
|
+
}
|
|
691
|
+
if (!input.checkpointDigest || !SHA256.test(input.checkpointDigest))
|
|
692
|
+
throw new Error("scenario checkpoint digest is invalid");
|
|
693
|
+
const filePath = receiptPath(input.checkpointDigest);
|
|
694
|
+
const receipt = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
695
|
+
if (receipt.checkpointDigest !== input.checkpointDigest || receipt.label !== input.label || receipt.gate !== input.externalGate || JSON.stringify(receipt.sources) !== JSON.stringify(input.sources))
|
|
696
|
+
throw new Error("scenario checkpoint binding mismatch");
|
|
697
|
+
let factOptions;
|
|
698
|
+
if (HEALTH_SCENARIO_LABELS.has(input.label)) {
|
|
699
|
+
if (!deps.healthDriver)
|
|
700
|
+
throw new Error("health scenario driver is unavailable");
|
|
701
|
+
const probe = await deps.healthDriver.poll(input.label, receipt.scenarioHandleDigest);
|
|
702
|
+
if (probe.state === "waiting")
|
|
703
|
+
return { state: "waiting", checkpointDigest: receipt.checkpointDigest };
|
|
704
|
+
factOptions = { containerSnapshot: probe.containerSnapshot };
|
|
705
|
+
}
|
|
706
|
+
if (INTERACTIVE_DRIVER_LABELS.has(input.label)) {
|
|
707
|
+
if (!deps.interactiveDriver)
|
|
708
|
+
throw new Error("interactive scenario driver is unavailable");
|
|
709
|
+
const driven = await deps.interactiveDriver.poll(input.label, receipt.scenarioHandleDigest);
|
|
710
|
+
if (driven.state === "waiting")
|
|
711
|
+
return { state: "waiting", checkpointDigest: receipt.checkpointDigest };
|
|
712
|
+
}
|
|
713
|
+
if (READ_ONLY_DENIAL_LABELS.has(input.label)) {
|
|
714
|
+
if (!deps.denialDriver)
|
|
715
|
+
throw new Error("read-only denial scenario driver is unavailable");
|
|
716
|
+
const driven = await deps.denialDriver.poll(input.label, receipt.scenarioHandleDigest);
|
|
717
|
+
if (driven.state === "waiting")
|
|
718
|
+
return { state: "waiting", checkpointDigest: receipt.checkpointDigest };
|
|
719
|
+
}
|
|
720
|
+
const after = await deps.readFacts(input.label, receipt.scenarioHandleDigest, factOptions);
|
|
721
|
+
if (input.label === "unit-15c-1-no-callback-terminalization" && !receipt.noCallbackBaseline) {
|
|
722
|
+
const activeApproval = after.approvals.find((approval) => approval.state === "proposed" || approval.state === "claimed");
|
|
723
|
+
if (activeApproval) {
|
|
724
|
+
const baseline = {
|
|
725
|
+
approvalId: activeApproval.approvalId,
|
|
726
|
+
offsetDigest: hash(after.sourceValues["telegram-offset"]),
|
|
727
|
+
inboundEventCount: after.events.filter((entry) => entry.event === "telegram.callback_settled" || entry.event === "telegram.callback_recovery_settled" || entry.event === "telegram.update_dropped").length,
|
|
728
|
+
};
|
|
729
|
+
receipt.noCallbackBaseline = baseline;
|
|
730
|
+
receipt.before.sourceValues["no-callback-baseline"] = baseline;
|
|
731
|
+
atomicPrivateJson(filePath, receipt);
|
|
732
|
+
return { state: "waiting", checkpointDigest: receipt.checkpointDigest };
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
const candidate = deriveSanctuaryScenarioAssertions(input.label, receipt.before, after, deps.now(), receipt.scenarioHandleDigest);
|
|
736
|
+
if (!candidate)
|
|
737
|
+
return { state: "waiting", checkpointDigest: receipt.checkpointDigest };
|
|
738
|
+
const assertions = (0, sanctuary_acceptance_harness_1.validateSanctuaryUnit16EvidenceAssertions)(input.label, candidate);
|
|
739
|
+
const sourceDigests = Object.fromEntries(input.sources.map((source) => [source, hash(after.sourceValues[source])]));
|
|
740
|
+
if (HEALTH_SCENARIO_LABELS.has(input.label))
|
|
741
|
+
await deps.healthDriver.recover(input.label, receipt.scenarioHandleDigest);
|
|
742
|
+
if (INTERACTIVE_DRIVER_LABELS.has(input.label) && await deps.interactiveDriver.complete(input.label, receipt.scenarioHandleDigest) !== "complete") {
|
|
743
|
+
throw new Error("interactive scenario requires inspect-before-retry");
|
|
744
|
+
}
|
|
745
|
+
if (INTERACTIVE_DRIVER_LABELS.has(input.label))
|
|
746
|
+
await deps.interactiveDriver.cleanup(input.label, receipt.scenarioHandleDigest);
|
|
747
|
+
if (READ_ONLY_DENIAL_LABELS.has(input.label))
|
|
748
|
+
await deps.denialDriver.complete(input.label, receipt.scenarioHandleDigest);
|
|
749
|
+
(0, sanctuary_acceptance_marker_1.publishSanctuaryAcceptanceGateStatus)({ label: input.label, gate: input.externalGate, phase: "complete", startedAt: receipt.startedAt }, deps.gateStatusPath);
|
|
750
|
+
(0, sanctuary_acceptance_marker_1.clearSanctuaryAcceptanceMarker)("sanctuary", receipt.scenarioHandleDigest, deps.agentRoot);
|
|
751
|
+
fs.unlinkSync(filePath);
|
|
752
|
+
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_acceptance_scenario_end", message: "Sanctuary live acceptance scenario completed", meta: { label: input.label, scenarioHandleDigest: receipt.scenarioHandleDigest } });
|
|
753
|
+
return { state: "complete", checkpointDigest: receipt.checkpointDigest, sourceDigests, assertions };
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function boundedReceiptEntries(receiptRoot) {
|
|
757
|
+
const rootHandle = fs.openSync(receiptRoot, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
758
|
+
try {
|
|
759
|
+
const rootMetadata = fs.fstatSync(rootHandle);
|
|
760
|
+
const pathMetadata = fs.lstatSync(receiptRoot);
|
|
761
|
+
if (!rootMetadata.isDirectory() || !pathMetadata.isDirectory() || rootMetadata.dev !== pathMetadata.dev || rootMetadata.ino !== pathMetadata.ino) {
|
|
762
|
+
throw new Error("acceptance receipt root changed during cleanup");
|
|
763
|
+
}
|
|
764
|
+
const directory = fs.opendirSync(receiptRoot);
|
|
765
|
+
const entries = [];
|
|
766
|
+
try {
|
|
767
|
+
for (;;) {
|
|
768
|
+
const entry = directory.readSync();
|
|
769
|
+
if (!entry)
|
|
770
|
+
break;
|
|
771
|
+
entries.push(entry);
|
|
772
|
+
if (entries.length === 33)
|
|
773
|
+
throw new Error("acceptance receipt cleanup exceeds its bound");
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
finally {
|
|
777
|
+
directory.closeSync();
|
|
778
|
+
}
|
|
779
|
+
const finalMetadata = fs.lstatSync(receiptRoot);
|
|
780
|
+
if (!finalMetadata.isDirectory() || rootMetadata.dev !== finalMetadata.dev || rootMetadata.ino !== finalMetadata.ino) {
|
|
781
|
+
throw new Error("acceptance receipt root changed during cleanup");
|
|
782
|
+
}
|
|
783
|
+
return entries;
|
|
784
|
+
}
|
|
785
|
+
finally {
|
|
786
|
+
fs.closeSync(rootHandle);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
function prepareSafeQuarantineRoot(sourceParent, sourceParentHandle) {
|
|
790
|
+
const quarantineRoot = path.join(sourceParent, "quarantine");
|
|
791
|
+
const rejectedPath = path.join(sourceParent, `.quarantine-rejected-${(0, node_crypto_1.randomUUID)()}`);
|
|
792
|
+
const boundQuarantineRoot = (0, sanctuary_acceptance_marker_1.boundDirectoryEntryPath)(sourceParentHandle, sourceParent, path.basename(quarantineRoot));
|
|
793
|
+
const boundRejectedPath = (0, sanctuary_acceptance_marker_1.boundDirectoryEntryPath)(sourceParentHandle, sourceParent, path.basename(rejectedPath));
|
|
794
|
+
let quarantineExists = false;
|
|
795
|
+
let rejectedMetadata = null;
|
|
796
|
+
try {
|
|
797
|
+
const existing = fs.lstatSync(boundQuarantineRoot);
|
|
798
|
+
if (existing.isDirectory())
|
|
799
|
+
quarantineExists = true;
|
|
800
|
+
else {
|
|
801
|
+
(0, sanctuary_acceptance_marker_1.secureRenameBoundInodeSync)(sourceParentHandle, path.basename(quarantineRoot), sourceParentHandle, path.basename(rejectedPath), existing);
|
|
802
|
+
const rejected = fs.lstatSync(boundRejectedPath);
|
|
803
|
+
if (rejected.dev !== existing.dev || rejected.ino !== existing.ino)
|
|
804
|
+
throw new Error("acceptance quarantine rejection changed during move");
|
|
805
|
+
rejectedMetadata = rejected;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
catch (error) {
|
|
809
|
+
if (error.code !== "ENOENT")
|
|
810
|
+
throw error;
|
|
811
|
+
}
|
|
812
|
+
if (!quarantineExists)
|
|
813
|
+
fs.mkdirSync(boundQuarantineRoot, { mode: 0o700 });
|
|
814
|
+
const quarantineHandle = fs.openSync(boundQuarantineRoot, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
815
|
+
try {
|
|
816
|
+
const metadata = fs.fstatSync(quarantineHandle);
|
|
817
|
+
const pathMetadata = fs.lstatSync(boundQuarantineRoot);
|
|
818
|
+
if (!metadata.isDirectory() || !pathMetadata.isDirectory() || metadata.dev !== pathMetadata.dev || metadata.ino !== pathMetadata.ino) {
|
|
819
|
+
throw new Error("acceptance quarantine root changed");
|
|
820
|
+
}
|
|
821
|
+
fs.fchmodSync(quarantineHandle, 0o700);
|
|
822
|
+
if (rejectedMetadata) {
|
|
823
|
+
(0, sanctuary_acceptance_marker_1.secureRenameBoundInodeSync)(sourceParentHandle, path.basename(rejectedPath), quarantineHandle, path.basename(rejectedPath).slice(1), rejectedMetadata);
|
|
824
|
+
}
|
|
825
|
+
fs.fsyncSync(quarantineHandle);
|
|
826
|
+
fs.fsyncSync(sourceParentHandle);
|
|
827
|
+
return { quarantineRoot, quarantineHandle };
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
fs.closeSync(quarantineHandle);
|
|
831
|
+
throw error;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
function durableQuarantineReceiptRoot(receiptRoot) {
|
|
835
|
+
const sourceParent = path.dirname(receiptRoot);
|
|
836
|
+
const sourceParentHandle = fs.openSync(sourceParent, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
837
|
+
let rootHandle = null;
|
|
838
|
+
let quarantineHandle = null;
|
|
839
|
+
let quarantineRoot = "";
|
|
840
|
+
try {
|
|
841
|
+
const sourceParentMetadata = fs.fstatSync(sourceParentHandle);
|
|
842
|
+
const sourceParentPathMetadata = fs.lstatSync(sourceParent);
|
|
843
|
+
if (!sourceParentMetadata.isDirectory() || !sourceParentPathMetadata.isDirectory()
|
|
844
|
+
|| sourceParentMetadata.dev !== sourceParentPathMetadata.dev || sourceParentMetadata.ino !== sourceParentPathMetadata.ino)
|
|
845
|
+
throw new Error("acceptance receipt parent changed during quarantine");
|
|
846
|
+
const prepared = prepareSafeQuarantineRoot(sourceParent, sourceParentHandle);
|
|
847
|
+
quarantineRoot = prepared.quarantineRoot;
|
|
848
|
+
quarantineHandle = prepared.quarantineHandle;
|
|
849
|
+
const boundReceiptRoot = (0, sanctuary_acceptance_marker_1.boundDirectoryEntryPath)(sourceParentHandle, sourceParent, path.basename(receiptRoot));
|
|
850
|
+
const rootPathMetadata = fs.lstatSync(boundReceiptRoot);
|
|
851
|
+
if (rootPathMetadata.isDirectory() || rootPathMetadata.isFile()) {
|
|
852
|
+
rootHandle = fs.openSync(boundReceiptRoot, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
853
|
+
}
|
|
854
|
+
if (rootHandle !== null) {
|
|
855
|
+
const rootMetadata = fs.fstatSync(rootHandle);
|
|
856
|
+
const reboundMetadata = fs.lstatSync(boundReceiptRoot);
|
|
857
|
+
if (rootMetadata.dev !== reboundMetadata.dev || rootMetadata.ino !== reboundMetadata.ino)
|
|
858
|
+
throw new Error("acceptance receipt root changed during cleanup");
|
|
859
|
+
}
|
|
860
|
+
const finalRootPathMetadata = fs.lstatSync(boundReceiptRoot);
|
|
861
|
+
if (finalRootPathMetadata.dev !== rootPathMetadata.dev || finalRootPathMetadata.ino !== rootPathMetadata.ino)
|
|
862
|
+
throw new Error("acceptance receipt root changed before quarantine move");
|
|
863
|
+
const quarantinePath = path.join(quarantineRoot, `receipts-${(0, node_crypto_1.randomUUID)()}`);
|
|
864
|
+
(0, sanctuary_acceptance_marker_1.secureRenameBoundInodeSync)(sourceParentHandle, path.basename(receiptRoot), quarantineHandle, path.basename(quarantinePath), rootPathMetadata);
|
|
865
|
+
const movedMetadata = fs.lstatSync((0, sanctuary_acceptance_marker_1.boundDirectoryEntryPath)(quarantineHandle, quarantineRoot, path.basename(quarantinePath)));
|
|
866
|
+
if (movedMetadata.dev !== rootPathMetadata.dev || movedMetadata.ino !== rootPathMetadata.ino) {
|
|
867
|
+
throw new Error("acceptance receipt quarantine changed during move");
|
|
868
|
+
}
|
|
869
|
+
if (rootHandle !== null)
|
|
870
|
+
fs.fsyncSync(rootHandle);
|
|
871
|
+
fs.fsyncSync(sourceParentHandle);
|
|
872
|
+
fs.fsyncSync(quarantineHandle);
|
|
873
|
+
fs.mkdirSync(boundReceiptRoot, { mode: 0o700 });
|
|
874
|
+
const replacementHandle = fs.openSync(boundReceiptRoot, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
875
|
+
try {
|
|
876
|
+
fs.fsyncSync(replacementHandle);
|
|
877
|
+
}
|
|
878
|
+
finally {
|
|
879
|
+
fs.closeSync(replacementHandle);
|
|
880
|
+
}
|
|
881
|
+
fs.fsyncSync(sourceParentHandle);
|
|
882
|
+
}
|
|
883
|
+
finally {
|
|
884
|
+
fs.closeSync(sourceParentHandle);
|
|
885
|
+
if (quarantineHandle !== null)
|
|
886
|
+
fs.closeSync(quarantineHandle);
|
|
887
|
+
if (rootHandle !== null)
|
|
888
|
+
fs.closeSync(rootHandle);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function finalizeSanctuaryScenarioCapture(gateStatusPath, configuredReceiptRoot, configuredAgentRoot) {
|
|
892
|
+
const receiptRoot = configuredReceiptRoot ?? path.join(configuredAgentRoot ?? (0, identity_1.getAgentRoot)("sanctuary"), "state", "acceptance", "receipts");
|
|
893
|
+
const errors = [];
|
|
894
|
+
let marker = null;
|
|
895
|
+
let markerReadable = true;
|
|
896
|
+
let normalCleanupFailed = false;
|
|
897
|
+
try {
|
|
898
|
+
marker = (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)("sanctuary", configuredAgentRoot);
|
|
899
|
+
}
|
|
900
|
+
catch (error) {
|
|
901
|
+
markerReadable = false;
|
|
902
|
+
errors.push(error);
|
|
903
|
+
}
|
|
904
|
+
let activeReceipt = null;
|
|
905
|
+
let receiptSetCanonical = true;
|
|
906
|
+
try {
|
|
907
|
+
const entries = boundedReceiptEntries(receiptRoot);
|
|
908
|
+
if (entries.length !== (marker ? 1 : 0))
|
|
909
|
+
throw new Error(marker ? "active acceptance receipt is absent or ambiguous" : "acceptance receipt exists without an active marker");
|
|
910
|
+
if (marker) {
|
|
911
|
+
const entry = entries[0];
|
|
912
|
+
if (!entry.isFile() || !entry.name.endsWith(".json") || !SHA256.test(entry.name.slice(0, -5)))
|
|
913
|
+
throw new Error("acceptance receipt cleanup found a noncanonical entry");
|
|
914
|
+
const filePath = path.join(receiptRoot, entry.name);
|
|
915
|
+
const handle = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
916
|
+
try {
|
|
917
|
+
const metadata = fs.fstatSync(handle);
|
|
918
|
+
if (!metadata.isFile() || metadata.size > 4 * 1024 * 1024 || (metadata.mode & 0o777) !== 0o600)
|
|
919
|
+
throw new Error("acceptance receipt cleanup found an invalid file");
|
|
920
|
+
const receipt = JSON.parse(fs.readFileSync(handle, "utf8"));
|
|
921
|
+
const pathMetadata = fs.lstatSync(filePath);
|
|
922
|
+
if (!pathMetadata.isFile() || pathMetadata.dev !== metadata.dev || pathMetadata.ino !== metadata.ino)
|
|
923
|
+
throw new Error("acceptance receipt changed during cleanup");
|
|
924
|
+
if (receipt.checkpointDigest !== entry.name.slice(0, -5) || receipt.scenarioHandleDigest !== marker.scenarioHandleDigest
|
|
925
|
+
|| receipt.label !== marker.label || receipt.startedAt !== marker.startedAt)
|
|
926
|
+
throw new Error("active acceptance receipt is absent or ambiguous");
|
|
927
|
+
activeReceipt = filePath;
|
|
928
|
+
}
|
|
929
|
+
finally {
|
|
930
|
+
fs.closeSync(handle);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
catch (error) {
|
|
935
|
+
if (error.code !== "ENOENT") {
|
|
936
|
+
receiptSetCanonical = false;
|
|
937
|
+
errors.push(error);
|
|
938
|
+
}
|
|
939
|
+
else if (marker) {
|
|
940
|
+
receiptSetCanonical = false;
|
|
941
|
+
errors.push(new Error("active acceptance receipt is absent or ambiguous"));
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (markerReadable && marker && receiptSetCanonical && activeReceipt) {
|
|
945
|
+
try {
|
|
946
|
+
fs.unlinkSync(activeReceipt);
|
|
947
|
+
}
|
|
948
|
+
catch (error) {
|
|
949
|
+
normalCleanupFailed = true;
|
|
950
|
+
errors.push(error);
|
|
951
|
+
}
|
|
952
|
+
if (!normalCleanupFailed) {
|
|
953
|
+
try {
|
|
954
|
+
(0, sanctuary_acceptance_marker_1.clearSanctuaryAcceptanceMarker)("sanctuary", marker.scenarioHandleDigest, configuredAgentRoot);
|
|
955
|
+
}
|
|
956
|
+
catch (error) {
|
|
957
|
+
normalCleanupFailed = true;
|
|
958
|
+
errors.push(error);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (!markerReadable || !receiptSetCanonical || (marker && activeReceipt === null) || normalCleanupFailed) {
|
|
963
|
+
try {
|
|
964
|
+
durableQuarantineReceiptRoot(receiptRoot);
|
|
965
|
+
}
|
|
966
|
+
catch (error) {
|
|
967
|
+
if (error.code !== "ENOENT")
|
|
968
|
+
errors.push(error);
|
|
969
|
+
}
|
|
970
|
+
try {
|
|
971
|
+
(0, sanctuary_acceptance_marker_1.quarantineSanctuaryAcceptanceMarker)("sanctuary", configuredAgentRoot);
|
|
972
|
+
}
|
|
973
|
+
catch (error) {
|
|
974
|
+
errors.push(error);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
try {
|
|
978
|
+
(0, sanctuary_acceptance_marker_1.clearSanctuaryAcceptanceGateStatus)(gateStatusPath);
|
|
979
|
+
}
|
|
980
|
+
catch (error) {
|
|
981
|
+
errors.push(error);
|
|
982
|
+
}
|
|
983
|
+
if (errors.length > 0)
|
|
984
|
+
throw new AggregateError(errors, "Sanctuary scenario finalization failed");
|
|
985
|
+
}
|