@ouro.bot/cli 0.1.0-alpha.810 → 0.1.0-alpha.812
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 +18 -0
- package/deploy/unraid/README.txt +3 -6
- package/deploy/unraid/sanctuary-acceptance-contract.json +1 -1
- package/deploy/unraid/sanctuary-deployment-target.mjs +1 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +8 -7
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +1 -1
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +2 -2
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/config.js +32 -2
- package/dist/heart/core.js +167 -115
- package/dist/heart/daemon/daemon.js +19 -9
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +88 -50
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +53 -15
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +6 -26
- package/dist/heart/frontend-approval-runtime.js +87 -13
- package/dist/heart/identity.js +17 -5
- package/dist/heart/session-events.js +8 -1
- package/dist/heart/tool-approval.js +64 -31
- package/dist/mind/pending.js +4 -4
- package/dist/repertoire/mcp-manager.js +365 -381
- package/dist/repertoire/mcp-tools.js +66 -25
- package/dist/repertoire/plugin-mcp.js +3 -3
- package/dist/repertoire/shell-sessions.js +8 -7
- package/dist/repertoire/tool-arguments.js +30 -8
- package/dist/repertoire/tools-session.js +30 -5
- package/dist/repertoire/tools-shell.js +30 -14
- package/dist/repertoire/tools-voice.js +6 -6
- package/dist/repertoire/tools.js +231 -205
- package/dist/senses/bluebubbles/index.js +4 -2
- package/dist/senses/cli.js +6 -5
- package/dist/senses/private-runtime.js +9 -3
- package/dist/senses/shared-turn.js +12 -9
- package/dist/senses/teams.js +15 -12
- package/dist/senses/telegram-approval-runtime.js +129 -20
- package/dist/senses/telegram-client.js +19 -2
- package/dist/senses/telegram.js +54 -15
- package/dist/senses/voice/twilio-phone.js +123 -173
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -65,6 +65,7 @@ const pending_1 = require("../../mind/pending");
|
|
|
65
65
|
const agent_service_1 = require("./agent-service");
|
|
66
66
|
const friends_1 = require("@ouro.bot/friends");
|
|
67
67
|
const mcp_manager_1 = require("../../repertoire/mcp-manager");
|
|
68
|
+
const mcp_tools_1 = require("../../repertoire/mcp-tools");
|
|
68
69
|
const mailbox_http_1 = require("../mailbox/mailbox-http");
|
|
69
70
|
const mailbox_types_1 = require("../mailbox/mailbox-types");
|
|
70
71
|
const mailbox_read_1 = require("../mailbox/mailbox-read");
|
|
@@ -1161,7 +1162,6 @@ class OuroDaemon {
|
|
|
1161
1162
|
}
|
|
1162
1163
|
this.frontendSessionService.close?.();
|
|
1163
1164
|
(0, update_checker_1.stopUpdateChecker)();
|
|
1164
|
-
(0, mcp_manager_1.shutdownSharedMcpManager)();
|
|
1165
1165
|
this.scheduler.stop?.();
|
|
1166
1166
|
this.healthMonitor.stopPeriodicChecks?.();
|
|
1167
1167
|
if (this.senseAutostartTimer) {
|
|
@@ -1172,7 +1172,10 @@ class OuroDaemon {
|
|
|
1172
1172
|
clearInterval(this.externalEventReconcileTimer);
|
|
1173
1173
|
this.externalEventReconcileTimer = null;
|
|
1174
1174
|
}
|
|
1175
|
-
const workerStopTasks = [
|
|
1175
|
+
const workerStopTasks = [
|
|
1176
|
+
Promise.resolve().then(() => this.processManager.stopAll()),
|
|
1177
|
+
Promise.resolve().then(() => (0, mcp_manager_1.shutdownSharedMcpManager)()),
|
|
1178
|
+
];
|
|
1176
1179
|
if (this.senseManager) {
|
|
1177
1180
|
workerStopTasks.push(Promise.resolve().then(() => this.senseManager.stopAll()));
|
|
1178
1181
|
}
|
|
@@ -2446,28 +2449,35 @@ class OuroDaemon {
|
|
|
2446
2449
|
}
|
|
2447
2450
|
case "mcp.list": {
|
|
2448
2451
|
return (0, turn_execution_lease_1.withTurnExecutionLease)(async () => {
|
|
2449
|
-
|
|
2450
|
-
const
|
|
2452
|
+
const agentName = command.agent ?? "default";
|
|
2453
|
+
const owner = { agentName, agentRoot: (0, identity_1.getAgentRoot)(agentName) };
|
|
2454
|
+
(0, identity_1.setAgentName)(agentName);
|
|
2455
|
+
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)(owner);
|
|
2451
2456
|
if (!mcpManager) {
|
|
2452
2457
|
return { ok: true, data: [], message: "no MCP servers configured" };
|
|
2453
2458
|
}
|
|
2454
|
-
return { ok: true, data: mcpManager.
|
|
2459
|
+
return { ok: true, data: mcpManager.entries.map(({ server, tools, pluginId }) => ({ server, tools, pluginId })) };
|
|
2455
2460
|
});
|
|
2456
2461
|
}
|
|
2457
2462
|
case "mcp.call": {
|
|
2458
2463
|
return (0, turn_execution_lease_1.withTurnExecutionLease)(async () => {
|
|
2459
|
-
|
|
2460
|
-
const
|
|
2464
|
+
const agentName = command.agent ?? "default";
|
|
2465
|
+
const owner = { agentName, agentRoot: (0, identity_1.getAgentRoot)(agentName) };
|
|
2466
|
+
(0, identity_1.setAgentName)(agentName);
|
|
2467
|
+
const mcpCallManager = await (0, mcp_manager_1.getSharedMcpManager)(owner);
|
|
2461
2468
|
if (!mcpCallManager) {
|
|
2462
2469
|
return { ok: false, error: "no MCP servers configured" };
|
|
2463
2470
|
}
|
|
2464
2471
|
try {
|
|
2465
2472
|
const parsedArgs = command.args ? JSON.parse(command.args) : {};
|
|
2466
|
-
const
|
|
2473
|
+
const matches = (0, mcp_tools_1.mcpToolsAsDefinitions)(mcpCallManager).filter((definition) => definition.mcpBinding?.server === command.server && definition.mcpBinding.rawName === command.tool);
|
|
2474
|
+
const binding = matches.length === 1 ? matches[0].mcpBinding : undefined;
|
|
2475
|
+
if (!binding)
|
|
2476
|
+
return { ok: false, error: "MCP tool is unavailable or ambiguous" };
|
|
2477
|
+
const result = await mcpCallManager.manager.callTool(binding, parsedArgs, owner);
|
|
2467
2478
|
return { ok: true, data: result };
|
|
2468
2479
|
}
|
|
2469
2480
|
catch (error) {
|
|
2470
|
-
/* v8 ignore next -- defensive: callTool errors are always Error instances @preserve */
|
|
2471
2481
|
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
2472
2482
|
}
|
|
2473
2483
|
});
|
|
@@ -60,6 +60,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
60
60
|
const node_fs_1 = require("node:fs");
|
|
61
61
|
const node_net_1 = require("node:net");
|
|
62
62
|
const path = __importStar(require("node:path"));
|
|
63
|
+
const node_util_1 = require("node:util");
|
|
63
64
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
64
65
|
const friends_1 = require("@ouro.bot/friends");
|
|
65
66
|
const runtime_1 = require("../../nerves/runtime");
|
|
@@ -258,6 +259,7 @@ function createSanctuaryAcceptanceAdapterDependencies(secretFd = 3, options = {}
|
|
|
258
259
|
createTelegramApi: telegram_client_1.createTelegramBotApi,
|
|
259
260
|
readLiveGrounding: readIndependentSanctuaryGrounding,
|
|
260
261
|
runProductionBoundaryProbe: runSanctuaryProductionBoundaryProbe,
|
|
262
|
+
providerRuntime: core_1.getProviderRuntime,
|
|
261
263
|
};
|
|
262
264
|
const healthDriver = createSanctuaryHealthAcceptanceScenarioDriver(dependencies.hostRequest);
|
|
263
265
|
const scenarioAgentRoot = options.scenarioCapture?.agentRoot ?? (0, identity_1.getAgentRoot)(TARGET_ID);
|
|
@@ -937,32 +939,57 @@ function canonicalDockerIdFromUnraidPrefixedId(value) {
|
|
|
937
939
|
throw new Error("Unraid Docker PrefixedID is invalid");
|
|
938
940
|
return value.slice(65);
|
|
939
941
|
}
|
|
940
|
-
|
|
941
|
-
const
|
|
942
|
+
function containmentToolContext(agentRoot, profileId) {
|
|
943
|
+
const profile = (0, relationship_authorization_1.loadRelationshipCapabilityRegistry)(agentRoot).profiles[profileId];
|
|
944
|
+
if (!profile)
|
|
945
|
+
throw new Error("Sanctuary containment profile is missing");
|
|
946
|
+
return {
|
|
947
|
+
signin: async () => undefined, agentName: TARGET_ID, agentRoot,
|
|
948
|
+
relationshipAuthorization: {
|
|
949
|
+
profileId, authorizedContextScopes: profile.contextScopes, advertisedToolNames: profile.toolNames,
|
|
950
|
+
// This request-bound subject audits the profile contract; it is not a persisted Friend or grant.
|
|
951
|
+
authorizeTool: async (name) => (0, relationship_authorization_1.authorizeRelationshipAccess)({
|
|
952
|
+
relationship: { friendId: "sanctuary-containment-probe", trustLevel: profileId === "sanctuary-household" ? "friend" : "family", admissionState: "active", initiativePolicy: "reactive_only", capabilityProfileId: profileId },
|
|
953
|
+
profiles: Object.values((0, relationship_authorization_1.loadRelationshipCapabilityRegistry)(agentRoot).profiles),
|
|
954
|
+
request: { kind: "tool", name, requestId: "sanctuary-containment-probe", returnTargetFriendId: "sanctuary-containment-probe" },
|
|
955
|
+
activeRequestId: "sanctuary-containment-probe", requestPhase: "inbound",
|
|
956
|
+
}),
|
|
957
|
+
},
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
async function runSanctuaryProductionBoundaryProbe(input) {
|
|
961
|
+
const excludedNames = ["vault_get", "mcp_call", "exec", "credential_get", ...(input.profileId === "sanctuary-owner" ? [] : tools_1.SANCTUARY_OWNER_ADDITIONS)];
|
|
962
|
+
const inner = input.profileId === "sanctuary-event";
|
|
963
|
+
const terminal = inner ? "rest" : "settle";
|
|
942
964
|
const turns = [
|
|
943
965
|
{ content: "", toolCalls: excludedNames.map((name, index) => ({ id: `sanctuary-excluded-${index}`, name, arguments: "{}" })), outputItems: [] },
|
|
944
966
|
{ content: "", toolCalls: [{ id: "sanctuary-valid-system", name: "unraid_get_system", arguments: "{}" }], outputItems: [] },
|
|
945
|
-
{ content: "", toolCalls: [{ id: "sanctuary-boundary-
|
|
967
|
+
{ content: "", toolCalls: [{ id: "sanctuary-boundary-terminal", name: terminal, arguments: JSON.stringify(inner ? { note: "boundary complete" } : { answer: "boundary complete", intent: "complete" }) }], outputItems: [] },
|
|
946
968
|
];
|
|
947
969
|
let turn = 0;
|
|
948
970
|
const controlOutputs = [];
|
|
949
971
|
const providerRuntime = {
|
|
950
|
-
|
|
972
|
+
...input.providerRuntime, client: null,
|
|
951
973
|
streamTurn: async () => turns[turn++] ?? (() => { throw new Error("production boundary probe exceeded its turn budget"); })(),
|
|
952
974
|
appendToolOutput: (callId, output) => { if (callId === "sanctuary-valid-system")
|
|
953
975
|
controlOutputs.push(output); }, resetTurnState: () => undefined, ping: async () => undefined, classifyError: () => "unknown",
|
|
954
976
|
};
|
|
955
977
|
const receipts = [];
|
|
956
978
|
const sanctuary = (0, sanctuary_runtime_1.createSanctuaryToolContext)(TARGET_ID).sanctuary;
|
|
979
|
+
const context = containmentToolContext(input.agentRoot, input.profileId);
|
|
980
|
+
const relationship = context.relationshipAuthorization;
|
|
981
|
+
const poisonedContext = { ...context, sanctuary, relationshipAuthorization: {
|
|
982
|
+
...relationship, advertisedToolNames: [...relationship.advertisedToolNames, ...excludedNames, "mcp__containment_poison"],
|
|
983
|
+
} };
|
|
957
984
|
const observed = await (0, sanctuary_runtime_1.runWithSanctuaryToolReceiptCollection)(() => (0, core_1.runAgent)([{ role: "user", content: "Run the bounded production tool authorization probe." }], {
|
|
958
985
|
onModelStart: () => undefined, onModelStreamStart: () => undefined, onTextChunk: () => undefined, onReasoningChunk: () => undefined,
|
|
959
986
|
onToolStart: () => undefined, onToolEnd: () => undefined, onError: () => undefined, onClearText: () => undefined,
|
|
960
|
-
}, "telegram", undefined, {
|
|
961
|
-
|
|
962
|
-
toolContext:
|
|
987
|
+
}, inner ? "inner" : "telegram", undefined, {
|
|
988
|
+
skipKeptNotes: true, providerRuntimeOverride: providerRuntime, toolBoundaryObserver: (receipt) => receipts.push(receipt),
|
|
989
|
+
toolContext: poisonedContext,
|
|
963
990
|
}));
|
|
964
991
|
const controlReceipts = receipts.filter((receipt) => receipt.name === "unraid_get_system");
|
|
965
|
-
if (observed.result.outcome !== "settled" || controlReceipts.length !== 1
|
|
992
|
+
if (observed.result.outcome !== (inner ? "rested" : "settled") || controlReceipts.length !== 1
|
|
966
993
|
|| controlReceipts[0].reason !== "dispatched" || !controlReceipts[0].invoked || controlReceipts[0].sideEffect
|
|
967
994
|
|| observed.toolResultDigests.length !== 1 || controlOutputs.length !== 1
|
|
968
995
|
|| (0, node_crypto_1.createHash)("sha256").update(controlOutputs[0]).digest("hex") !== observed.toolResultDigests[0]) {
|
|
@@ -973,7 +1000,7 @@ async function runSanctuaryProductionBoundaryProbe(telegramSchemas) {
|
|
|
973
1000
|
if (controlResult.ok !== true || typeof controlData.sourceIdentityDigest !== "string" || !SHA256.test(controlData.sourceIdentityDigest)) {
|
|
974
1001
|
throw new Error("production boundary valid control result is invalid");
|
|
975
1002
|
}
|
|
976
|
-
return receipts.filter((receipt) => receipt.name !==
|
|
1003
|
+
return receipts.filter((receipt) => receipt.name !== terminal);
|
|
977
1004
|
}
|
|
978
1005
|
function parseInteractiveDriverReceipt(raw, label, scenarioHandleDigest) {
|
|
979
1006
|
if (raw === null)
|
|
@@ -1505,26 +1532,52 @@ async function readDefaultSanctuaryScenarioFacts(label, scenarioHandleDigest, de
|
|
|
1505
1532
|
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
1506
1533
|
const readRecord = inventory.find((record) => record.name === "Butler RO");
|
|
1507
1534
|
const writeRecord = inventory.find((record) => record.name === "Butler RW");
|
|
1535
|
+
const rawProfiles = (0, node_fs_1.readFileSync)(path.join(agentRoot, "tool-profiles.json"), "utf8");
|
|
1536
|
+
const packagedProfiles = (0, node_fs_1.readFileSync)(path.resolve(__dirname, "../../../deploy/unraid/sanctuary.ouro/tool-profiles.json"), "utf8");
|
|
1537
|
+
if (!(0, node_util_1.isDeepStrictEqual)(JSON.parse(rawProfiles), JSON.parse(packagedProfiles)))
|
|
1538
|
+
throw new Error("Sanctuary containment profiles do not match the verified package");
|
|
1508
1539
|
const relationshipRegistry = (0, relationship_authorization_1.loadRelationshipCapabilityRegistry)(agentRoot);
|
|
1509
|
-
const
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
const
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1540
|
+
const digest = (value) => (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(value)).digest("hex");
|
|
1541
|
+
const boundaryFor = async (profileId) => {
|
|
1542
|
+
const profile = relationshipRegistry.profiles[profileId];
|
|
1543
|
+
const inner = profileId === "sanctuary-event";
|
|
1544
|
+
const runtime = await dependency(deps.providerRuntime, "current provider runtime")(inner ? "agent" : "human", { agentName: TARGET_ID, agentRoot });
|
|
1545
|
+
const context = containmentToolContext(agentRoot, profileId);
|
|
1546
|
+
const select = (toolContext) => (0, tools_1.selectToolsForChannel)((0, friends_1.getChannelCapabilities)(inner ? "inner" : "telegram"), undefined, undefined, runtime.capabilities, undefined, undefined, toolContext);
|
|
1547
|
+
const selection = select(context);
|
|
1548
|
+
const schemas = (0, tools_1.toolSelectionSchemas)(selection);
|
|
1549
|
+
const schemaToolNames = schemas.map((tool) => tool.function.name);
|
|
1550
|
+
const expectedNames = profile.toolNames.filter((name) => (inner || name !== "rest") && (name !== "set_reasoning_effort" || runtime.capabilities.has("reasoning-effort")));
|
|
1551
|
+
const excludedNames = ["vault_get", "mcp_call", "exec", "credential_get", ...(profileId === "sanctuary-owner" ? [] : tools_1.SANCTUARY_OWNER_ADDITIONS)];
|
|
1552
|
+
const poisoned = select({ ...context, relationshipAuthorization: {
|
|
1553
|
+
...context.relationshipAuthorization, advertisedToolNames: [...profile.toolNames, ...excludedNames, "mcp__containment_poison"],
|
|
1554
|
+
} });
|
|
1555
|
+
const productionBoundaryReceipts = await dependency(deps.runProductionBoundaryProbe, "production tool boundary probe")({ agentRoot, profileId, providerRuntime: runtime });
|
|
1556
|
+
const excludedAttempts = productionBoundaryReceipts.filter((receipt) => receipt.name !== "unraid_get_system");
|
|
1557
|
+
return {
|
|
1558
|
+
profileId, profileVersion: profile.version, profileDigest: digest(profile), profileToolNames: profile.toolNames,
|
|
1559
|
+
providerCapabilities: [...runtime.capabilities], schemaDigest: digest(schemas), schemaToolNames,
|
|
1560
|
+
ordinaryDefinitionCount: selection.ordinary.length, engineSchemaCount: selection.engine.length,
|
|
1561
|
+
profileExact: true, schemasExact: (0, node_util_1.isDeepStrictEqual)([...schemaToolNames].sort(), [...expectedNames].sort()),
|
|
1562
|
+
handlersExact: selection.ordinary.every((definition) => typeof definition.handler === "function") && selection.engine.every((schema) => [tools_1.ponderTool, tools_1.settleTool, tools_1.speakTool, tools_1.restTool].includes(schema)),
|
|
1563
|
+
poisonedSchemaIntersectionCount: (0, tools_1.toolSelectionSchemas)(poisoned).filter((schema) => !schemaToolNames.includes(schema.function.name)).length,
|
|
1564
|
+
excludedToolNames: excludedAttempts.map((receipt) => receipt.name),
|
|
1565
|
+
excludedSchemaIntersectionCount: schemaToolNames.filter((name) => excludedNames.includes(name)).length,
|
|
1566
|
+
fabricatedHandlerInvocationCount: excludedAttempts.filter((receipt) => receipt.invoked).length,
|
|
1567
|
+
excludedToolAttemptCount: excludedAttempts.length,
|
|
1568
|
+
excludedToolRejectedCount: excludedAttempts.filter((receipt) => receipt.reason === "profile_excluded").length,
|
|
1569
|
+
excludedToolInvokedCount: excludedAttempts.filter((receipt) => receipt.invoked).length,
|
|
1570
|
+
excludedToolSideEffectCount: excludedAttempts.filter((receipt) => receipt.sideEffect).length,
|
|
1571
|
+
globallyResolvableExcludedToolCount: excludedAttempts.filter((receipt) => receipt.globallyResolvable).length,
|
|
1572
|
+
};
|
|
1573
|
+
};
|
|
1574
|
+
const profileBoundaries = {
|
|
1575
|
+
"sanctuary-owner": await boundaryFor("sanctuary-owner"),
|
|
1576
|
+
"sanctuary-household": await boundaryFor("sanctuary-household"),
|
|
1577
|
+
"sanctuary-event": await boundaryFor("sanctuary-event"),
|
|
1578
|
+
};
|
|
1579
|
+
if ((0, node_fs_1.readFileSync)(path.join(agentRoot, "tool-profiles.json"), "utf8") !== rawProfiles)
|
|
1580
|
+
throw new Error("Sanctuary containment profiles changed during the audit");
|
|
1528
1581
|
const restartDefinition = (0, tools_1.resolveToolDefinition)("unraid_restart_container");
|
|
1529
1582
|
const writeApprovalPolicy = restartDefinition.approvalPolicy({ container: "calibre-web" });
|
|
1530
1583
|
const writeApprovalPolicyExact = writeApprovalPolicy.kind === "required"
|
|
@@ -1543,30 +1596,16 @@ async function readDefaultSanctuaryScenarioFacts(label, scenarioHandleDigest, de
|
|
|
1543
1596
|
}
|
|
1544
1597
|
const rawWriteMaterialFieldCount = rawInventory.reduce((count, record) => count
|
|
1545
1598
|
+ Object.keys(record).filter((field) => /^(?:key|credential|secret|token)$/iu.test(field)).length, 0);
|
|
1599
|
+
if (container && typeof container.readOnlyRoot !== "boolean")
|
|
1600
|
+
throw new Error("containment root-mode observation must be boolean");
|
|
1546
1601
|
containment = {
|
|
1547
|
-
schemaVersion: "sanctuary-containment-audit-
|
|
1602
|
+
schemaVersion: "sanctuary-containment-audit-v2",
|
|
1548
1603
|
keyCount: inventory.length,
|
|
1549
1604
|
keyInventoryDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(redactedInventory)).digest("hex"),
|
|
1550
1605
|
readScopeDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(readRecord ? flattenedPermissions(readRecord) : [])).digest("hex"),
|
|
1551
1606
|
writeScopeDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(writeRecord ? flattenedPermissions(writeRecord) : [])).digest("hex"),
|
|
1552
1607
|
keyRoleAssignmentCount: inventory.reduce((count, record) => count + record.roles.length, 0),
|
|
1553
|
-
|
|
1554
|
-
telegramProfileDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(telegramNames)).digest("hex"),
|
|
1555
|
-
telegramSchemaDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(telegramSchemas)).digest("hex"),
|
|
1556
|
-
privateToolCount: privateNames.length,
|
|
1557
|
-
privateProfileDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(privateNames)).digest("hex"),
|
|
1558
|
-
privateSchemaDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(privateSchemas)).digest("hex"),
|
|
1559
|
-
resolvedHandlerCount,
|
|
1560
|
-
relationshipProfilesExact,
|
|
1561
|
-
handlersExact,
|
|
1562
|
-
excludedToolCount: excludedNames.length,
|
|
1563
|
-
excludedSchemaIntersectionCount: excludedSchemaIntersection.length,
|
|
1564
|
-
fabricatedHandlerInvocationCount: excludedSchemaIntersection.filter(handlerResolves).length,
|
|
1565
|
-
excludedToolAttemptCount: excludedAttempts.length,
|
|
1566
|
-
excludedToolRejectedCount: excludedAttempts.filter((attempt) => attempt.reason === "profile_excluded").length,
|
|
1567
|
-
excludedToolInvokedCount: excludedAttempts.filter((attempt) => attempt.invoked).length,
|
|
1568
|
-
excludedToolSideEffectCount: excludedAttempts.filter((attempt) => attempt.sideEffect).length,
|
|
1569
|
-
globallyResolvableExcludedToolCount: excludedAttempts.filter((attempt) => attempt.globallyResolvable).length,
|
|
1608
|
+
profileBoundaries,
|
|
1570
1609
|
auditPathDigest: (0, node_crypto_1.createHash)("sha256").update(TELEGRAM_AUDIT).digest("hex"),
|
|
1571
1610
|
auditLedgerDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(auditLedgerEntries)).digest("hex"),
|
|
1572
1611
|
auditRecordCount: auditLedgerEntries.length,
|
|
@@ -1582,7 +1621,7 @@ async function readDefaultSanctuaryScenarioFacts(label, scenarioHandleDigest, de
|
|
|
1582
1621
|
updaterDisabled: container?.updaterDisabled === true,
|
|
1583
1622
|
writableKeyExposure: container?.writableKeyExposure !== false,
|
|
1584
1623
|
rawWriteMaterialFieldCount,
|
|
1585
|
-
typedWriteExecutorCount:
|
|
1624
|
+
typedWriteExecutorCount: relationshipRegistry.profiles["sanctuary-owner"].toolNames.filter((name) => name === "unraid_restart_container" && writeApprovalPolicy.kind === "required").length,
|
|
1586
1625
|
writeApprovalPolicyDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(writeApprovalPolicy)).digest("hex"),
|
|
1587
1626
|
writeApprovalPolicyExact,
|
|
1588
1627
|
sensitiveMaterialObserved: auditContainsSensitiveMaterial(auditRaw ?? "", deps.telegramCredentials?.()) || rawWriteMaterialFieldCount > 0 || container?.writableKeyExposure === true,
|
|
@@ -1652,9 +1691,8 @@ async function readDefaultSanctuaryScenarioFacts(label, scenarioHandleDigest, de
|
|
|
1652
1691
|
digest: health && digestFiredWithinMs !== null ? { scheduleObserved: Boolean(cronRaw && canonicalSanctuaryHealthCronRegistered(cronRaw)), messageCount: scenarioDeliveries.filter((receipt) => receipt.kind === "digest" || receipt.kind === "transition_and_digest").length, firedWithinMs: digestFiredWithinMs, productionRestored: container?.running === true && container.health === "healthy" } : undefined,
|
|
1653
1692
|
reboot,
|
|
1654
1693
|
containment: containment ?? {
|
|
1655
|
-
schemaVersion: "sanctuary-containment-audit-
|
|
1656
|
-
|
|
1657
|
-
excludedToolCount: 0, excludedSchemaIntersectionCount: 0, fabricatedHandlerInvocationCount: 0, excludedToolAttemptCount: 0, excludedToolRejectedCount: 0, excludedToolInvokedCount: 0, excludedToolSideEffectCount: 0, globallyResolvableExcludedToolCount: 0, auditPathDigest: "", auditLedgerDigest: "", auditRecordCount: 0, auditLifecyclePairCount: 0,
|
|
1694
|
+
schemaVersion: "sanctuary-containment-audit-v2", keyCount: 0, keyInventoryDigest: "", readScopeDigest: "", writeScopeDigest: "", keyRoleAssignmentCount: 0,
|
|
1695
|
+
profileBoundaries: null, auditPathDigest: "", auditLedgerDigest: "", auditRecordCount: 0, auditLifecyclePairCount: 0,
|
|
1658
1696
|
containerUser: "", liveProcessUser: "", mountCount: 0, publishedPortCount: 0, networkMode: "", readOnlyRoot: false, mountsExact: false, securityExact: false, updaterDisabled: false,
|
|
1659
1697
|
writableKeyExposure: container?.writableKeyExposure === true, rawWriteMaterialFieldCount: 0, typedWriteExecutorCount: 0, writeApprovalPolicyDigest: "", writeApprovalPolicyExact: false,
|
|
1660
1698
|
sensitiveMaterialObserved: auditContainsSensitiveMaterial(auditRaw ?? "") || container?.writableKeyExposure === true,
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.SANCTUARY_SCENARIO_SOURCES = exports.SANCTUARY_SCENARIO_GATES = exports.SANCTUARY_UNIT_16_EVIDENCE_LABELS = void 0;
|
|
37
|
+
exports.exactSanctuaryContainmentProfileBoundaries = exactSanctuaryContainmentProfileBoundaries;
|
|
37
38
|
exports.resolveSanctuaryAdapterTimeoutMs = resolveSanctuaryAdapterTimeoutMs;
|
|
38
39
|
exports.createSanctuaryAcceptanceHarnessDependencies = createSanctuaryAcceptanceHarnessDependencies;
|
|
39
40
|
exports.sanctuaryScenarioTimeoutBudget = sanctuaryScenarioTimeoutBudget;
|
|
@@ -44,14 +45,57 @@ const node_child_process_1 = require("node:child_process");
|
|
|
44
45
|
const node_fs_1 = require("node:fs");
|
|
45
46
|
const node_fs_2 = require("node:fs");
|
|
46
47
|
const path = __importStar(require("node:path"));
|
|
48
|
+
const node_util_1 = require("node:util");
|
|
49
|
+
const friends_1 = require("@ouro.bot/friends");
|
|
47
50
|
const runtime_1 = require("../../nerves/runtime");
|
|
48
51
|
const telegram_1 = require("../../senses/telegram");
|
|
49
52
|
const runtime_credentials_1 = require("../runtime-credentials");
|
|
53
|
+
const relationship_authorization_1 = require("../../repertoire/relationship-authorization");
|
|
54
|
+
const tools_1 = require("../../repertoire/tools");
|
|
50
55
|
const MAX_ADAPTER_OUTPUT = 1_048_576;
|
|
51
56
|
const DEFAULT_ADAPTER_TIMEOUT_MS = 240_000;
|
|
52
57
|
const DEFAULT_TELEGRAM_TIMEOUT_MS = 10_000;
|
|
53
58
|
const PACKAGED_PROVENANCE_ADAPTER = "/opt/ouro/deploy/unraid/sanctuary-acceptance-adapter.sh";
|
|
54
59
|
const OPAQUE_DIGEST = /^[0-9a-f]{64}$/u;
|
|
60
|
+
function exactSanctuaryContainmentProfileBoundaries(value) {
|
|
61
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
62
|
+
return false;
|
|
63
|
+
const boundaries = value;
|
|
64
|
+
const versions = { "sanctuary-owner": 8, "sanctuary-household": 5, "sanctuary-event": 4 };
|
|
65
|
+
if (!(0, node_util_1.isDeepStrictEqual)(Object.keys(boundaries).sort(), Object.keys(versions).sort()))
|
|
66
|
+
return false;
|
|
67
|
+
const packageRoot = path.resolve(__dirname, "../../../deploy/unraid/sanctuary.ouro");
|
|
68
|
+
const registry = (0, relationship_authorization_1.loadRelationshipCapabilityRegistry)(packageRoot);
|
|
69
|
+
const digest = (input) => (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(input)).digest("hex");
|
|
70
|
+
return Object.entries(versions).every(([id, version]) => {
|
|
71
|
+
const raw = boundaries[id];
|
|
72
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
73
|
+
return false;
|
|
74
|
+
const boundary = raw;
|
|
75
|
+
const capabilities = boundary.providerCapabilities;
|
|
76
|
+
if (!Array.isArray(capabilities) || !capabilities.every((entry) => entry === "reasoning-effort" || entry === "phase-annotation")
|
|
77
|
+
|| new Set(capabilities).size !== capabilities.length)
|
|
78
|
+
return false;
|
|
79
|
+
const profile = registry.profiles[id];
|
|
80
|
+
if (profile.version !== version)
|
|
81
|
+
return false;
|
|
82
|
+
const selection = (0, tools_1.selectToolsForChannel)((0, friends_1.getChannelCapabilities)(id === "sanctuary-event" ? "inner" : "telegram"), undefined, undefined, new Set(capabilities), undefined, undefined, {
|
|
83
|
+
agentName: "sanctuary", relationshipAuthorization: { profileId: id, advertisedToolNames: profile.toolNames },
|
|
84
|
+
});
|
|
85
|
+
const schemas = (0, tools_1.toolSelectionSchemas)(selection);
|
|
86
|
+
const excludedToolNames = ["vault_get", "mcp_call", "exec", "credential_get", ...(id === "sanctuary-owner" ? [] : tools_1.SANCTUARY_OWNER_ADDITIONS)];
|
|
87
|
+
const globallyResolvableExcludedToolCount = excludedToolNames.filter((name) => (0, tools_1.resolveToolDefinition)(name)).length;
|
|
88
|
+
return globallyResolvableExcludedToolCount > 0 && (0, node_util_1.isDeepStrictEqual)(boundary, {
|
|
89
|
+
profileId: id, profileVersion: version, profileDigest: digest(profile), profileToolNames: profile.toolNames,
|
|
90
|
+
providerCapabilities: capabilities, schemaDigest: digest(schemas), schemaToolNames: schemas.map((tool) => tool.function.name),
|
|
91
|
+
ordinaryDefinitionCount: selection.ordinary.length, engineSchemaCount: selection.engine.length,
|
|
92
|
+
profileExact: true, schemasExact: true, handlersExact: true, poisonedSchemaIntersectionCount: 0,
|
|
93
|
+
excludedToolNames, excludedSchemaIntersectionCount: 0, fabricatedHandlerInvocationCount: 0,
|
|
94
|
+
excludedToolAttemptCount: excludedToolNames.length, excludedToolRejectedCount: excludedToolNames.length,
|
|
95
|
+
excludedToolInvokedCount: 0, excludedToolSideEffectCount: 0, globallyResolvableExcludedToolCount,
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
55
99
|
function resolveSanctuaryAdapterTimeoutMs(configured, remaining) {
|
|
56
100
|
const maximum = configured ?? DEFAULT_ADAPTER_TIMEOUT_MS;
|
|
57
101
|
return Math.max(1, Math.min(maximum, remaining ?? maximum));
|
|
@@ -494,15 +538,14 @@ function validateSanctuaryUnit16EvidenceAssertions(label, raw) {
|
|
|
494
538
|
case "unit-16e-containment-audit":
|
|
495
539
|
exact([
|
|
496
540
|
"schemaVersion", "keyCount", "keyInventoryDigest", "readScopeDigest", "writeScopeDigest", "keyRoleAssignmentCount",
|
|
497
|
-
"
|
|
498
|
-
"excludedToolCount", "excludedSchemaIntersectionCount", "fabricatedHandlerInvocationCount", "excludedToolAttemptCount", "excludedToolRejectedCount", "excludedToolInvokedCount", "excludedToolSideEffectCount", "globallyResolvableExcludedToolCount",
|
|
541
|
+
"profileBoundaries",
|
|
499
542
|
"auditPathDigest", "auditLedgerDigest", "auditRecordCount", "auditLifecyclePairCount",
|
|
500
543
|
"containerUser", "liveProcessUser", "mountCount", "publishedPortCount", "networkMode", "readOnlyRoot", "mountsExact", "securityExact", "updaterDisabled", "writableKeyExposure",
|
|
501
544
|
"rawWriteMaterialFieldCount", "typedWriteExecutorCount", "writeApprovalPolicyDigest", "writeApprovalPolicyExact", "sensitiveMaterialObserved", "mutationCount",
|
|
502
545
|
]);
|
|
503
|
-
if (text(value.schemaVersion, `${label} schemaVersion`) !== "sanctuary-containment-audit-
|
|
546
|
+
if (text(value.schemaVersion, `${label} schemaVersion`) !== "sanctuary-containment-audit-v2")
|
|
504
547
|
throw new Error(`${label} schemaVersion is invalid`);
|
|
505
|
-
for (const key of ["keyInventoryDigest", "readScopeDigest", "writeScopeDigest", "
|
|
548
|
+
for (const key of ["keyInventoryDigest", "readScopeDigest", "writeScopeDigest", "auditPathDigest", "auditLedgerDigest", "writeApprovalPolicyDigest"])
|
|
506
549
|
opaqueDigest(value[key], `${label} ${key}`);
|
|
507
550
|
for (const [key, expected] of Object.entries({
|
|
508
551
|
readScopeDigest: "9914469afdcb574937d1020a03faa82e3c02d767169d3eccae4b81863dafa06e",
|
|
@@ -513,24 +556,19 @@ function validateSanctuaryUnit16EvidenceAssertions(label, raw) {
|
|
|
513
556
|
throw new Error(`${label} ${key} does not match the canonical contract`);
|
|
514
557
|
requiredInteger(value, "keyCount", 2, label);
|
|
515
558
|
requiredInteger(value, "keyRoleAssignmentCount", 0, label);
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
throw new Error(`${label} handler count does not match the live relationship profiles`);
|
|
520
|
-
requiredInteger(value, "excludedToolCount", 7, label);
|
|
521
|
-
requiredInteger(value, "excludedToolAttemptCount", 7, label);
|
|
522
|
-
requiredInteger(value, "excludedToolRejectedCount", 7, label);
|
|
523
|
-
integer(value.globallyResolvableExcludedToolCount, `${label} globallyResolvableExcludedToolCount`, 1);
|
|
524
|
-
allZero(["excludedSchemaIntersectionCount", "fabricatedHandlerInvocationCount", "excludedToolInvokedCount", "excludedToolSideEffectCount", "publishedPortCount", "rawWriteMaterialFieldCount", "mutationCount"]);
|
|
559
|
+
if (!exactSanctuaryContainmentProfileBoundaries(value.profileBoundaries))
|
|
560
|
+
throw new Error(`${label} profileBoundaries do not match the canonical contract`);
|
|
561
|
+
allZero(["publishedPortCount", "rawWriteMaterialFieldCount", "mutationCount"]);
|
|
525
562
|
requiredFalse(value, "sensitiveMaterialObserved", label);
|
|
526
563
|
requiredFalse(value, "writableKeyExposure", label);
|
|
564
|
+
requiredFalse(value, "readOnlyRoot", label);
|
|
527
565
|
integer(value.auditRecordCount, `${label} auditRecordCount`, 2);
|
|
528
566
|
integer(value.auditLifecyclePairCount, `${label} auditLifecyclePairCount`, 1);
|
|
529
567
|
if (text(value.containerUser, `${label} containerUser`) !== "10001:10001" || text(value.liveProcessUser, `${label} liveProcessUser`) !== "10001:10001" || text(value.networkMode, `${label} networkMode`) !== "host")
|
|
530
568
|
throw new Error(`${label} container identity or network is invalid`);
|
|
531
|
-
requiredInteger(value, "mountCount",
|
|
569
|
+
requiredInteger(value, "mountCount", 3, label);
|
|
532
570
|
requiredInteger(value, "typedWriteExecutorCount", 1, label);
|
|
533
|
-
allTrue(["
|
|
571
|
+
allTrue(["writeApprovalPolicyExact", "mountsExact", "securityExact", "updaterDisabled"]);
|
|
534
572
|
break;
|
|
535
573
|
case "unit-16e-1-stop-denial":
|
|
536
574
|
case "unit-16e-2-restart-denial":
|
|
@@ -121,30 +121,19 @@ function hash(value) {
|
|
|
121
121
|
}
|
|
122
122
|
const CONTAINMENT_READ_SCOPE = ["ARRAY", "DASHBOARD", "DISK", "DOCKER", "INFO", "LOGS", "NOTIFICATIONS", "SHARE", "VARS"]
|
|
123
123
|
.map((resource) => `${resource}:READ_ANY`).sort();
|
|
124
|
-
const CONTAINMENT_EXCLUDED_TOOLS = ["shell", "read_file", "edit_file", "vault_get", "mcp_call", "exec", "credential_get"];
|
|
125
124
|
const CONTAINMENT_AUDIT_PATH = "/home/ouro/AgentBundles/sanctuary.ouro/state/acceptance/telegram-audit-chain.ndjson";
|
|
126
125
|
function exactContainmentAudit(evidence) {
|
|
127
|
-
const digestFields = [evidence.keyInventoryDigest, evidence.
|
|
128
|
-
return evidence.schemaVersion === "sanctuary-containment-audit-
|
|
126
|
+
const digestFields = [evidence.keyInventoryDigest, evidence.auditLedgerDigest, evidence.writeApprovalPolicyDigest];
|
|
127
|
+
return evidence.schemaVersion === "sanctuary-containment-audit-v2"
|
|
129
128
|
&& evidence.keyCount === 2
|
|
130
129
|
&& evidence.readScopeDigest === hash(CONTAINMENT_READ_SCOPE)
|
|
131
130
|
&& evidence.writeScopeDigest === hash([...CONTAINMENT_READ_SCOPE, "DOCKER:UPDATE_ANY"].sort())
|
|
132
131
|
&& evidence.keyRoleAssignmentCount === 0
|
|
133
|
-
&&
|
|
134
|
-
&& Number.isSafeInteger(evidence.privateToolCount) && evidence.privateToolCount > 0
|
|
135
|
-
&& evidence.relationshipProfilesExact && evidence.handlersExact
|
|
136
|
-
&& evidence.resolvedHandlerCount === evidence.telegramToolCount + evidence.privateToolCount
|
|
137
|
-
&& evidence.excludedToolCount === CONTAINMENT_EXCLUDED_TOOLS.length
|
|
138
|
-
&& evidence.excludedSchemaIntersectionCount === 0
|
|
139
|
-
&& evidence.fabricatedHandlerInvocationCount === 0
|
|
140
|
-
&& evidence.excludedToolAttemptCount === CONTAINMENT_EXCLUDED_TOOLS.length
|
|
141
|
-
&& evidence.excludedToolRejectedCount === CONTAINMENT_EXCLUDED_TOOLS.length
|
|
142
|
-
&& evidence.excludedToolInvokedCount === 0 && evidence.excludedToolSideEffectCount === 0
|
|
143
|
-
&& evidence.globallyResolvableExcludedToolCount >= 1
|
|
132
|
+
&& (0, sanctuary_acceptance_harness_1.exactSanctuaryContainmentProfileBoundaries)(evidence.profileBoundaries)
|
|
144
133
|
&& evidence.auditPathDigest === (0, node_crypto_1.createHash)("sha256").update(CONTAINMENT_AUDIT_PATH).digest("hex")
|
|
145
134
|
&& evidence.auditRecordCount >= 2 && evidence.auditLifecyclePairCount >= 1
|
|
146
|
-
&& evidence.containerUser === "10001:10001" && evidence.liveProcessUser === "10001:10001" && evidence.mountCount ===
|
|
147
|
-
&& evidence.networkMode === "host" && evidence.readOnlyRoot && evidence.mountsExact && evidence.securityExact && evidence.updaterDisabled
|
|
135
|
+
&& evidence.containerUser === "10001:10001" && evidence.liveProcessUser === "10001:10001" && evidence.mountCount === 3 && evidence.publishedPortCount === 0
|
|
136
|
+
&& evidence.networkMode === "host" && evidence.readOnlyRoot === false && evidence.mountsExact && evidence.securityExact && evidence.updaterDisabled
|
|
148
137
|
&& !evidence.writableKeyExposure && evidence.rawWriteMaterialFieldCount === 0 && evidence.typedWriteExecutorCount === 1
|
|
149
138
|
&& evidence.writeApprovalPolicyExact && !evidence.sensitiveMaterialObserved
|
|
150
139
|
&& digestFields.every((value) => SHA256.test(value));
|
|
@@ -499,16 +488,7 @@ function deriveSanctuaryScenarioAssertions(label, before, after, _now, scenarioH
|
|
|
499
488
|
keyCount: after.containment.keyCount, keyInventoryDigest: after.containment.keyInventoryDigest,
|
|
500
489
|
readScopeDigest: after.containment.readScopeDigest, writeScopeDigest: after.containment.writeScopeDigest,
|
|
501
490
|
keyRoleAssignmentCount: after.containment.keyRoleAssignmentCount,
|
|
502
|
-
|
|
503
|
-
telegramSchemaDigest: after.containment.telegramSchemaDigest,
|
|
504
|
-
privateToolCount: after.containment.privateToolCount, privateProfileDigest: after.containment.privateProfileDigest,
|
|
505
|
-
privateSchemaDigest: after.containment.privateSchemaDigest, resolvedHandlerCount: after.containment.resolvedHandlerCount,
|
|
506
|
-
relationshipProfilesExact: after.containment.relationshipProfilesExact, handlersExact: after.containment.handlersExact,
|
|
507
|
-
excludedToolCount: after.containment.excludedToolCount, excludedSchemaIntersectionCount: after.containment.excludedSchemaIntersectionCount,
|
|
508
|
-
fabricatedHandlerInvocationCount: after.containment.fabricatedHandlerInvocationCount,
|
|
509
|
-
excludedToolAttemptCount: after.containment.excludedToolAttemptCount, excludedToolRejectedCount: after.containment.excludedToolRejectedCount,
|
|
510
|
-
excludedToolInvokedCount: after.containment.excludedToolInvokedCount, excludedToolSideEffectCount: after.containment.excludedToolSideEffectCount,
|
|
511
|
-
globallyResolvableExcludedToolCount: after.containment.globallyResolvableExcludedToolCount,
|
|
491
|
+
profileBoundaries: after.containment.profileBoundaries,
|
|
512
492
|
auditPathDigest: after.containment.auditPathDigest, auditLedgerDigest: after.containment.auditLedgerDigest,
|
|
513
493
|
auditRecordCount: after.containment.auditRecordCount, auditLifecyclePairCount: after.containment.auditLifecyclePairCount,
|
|
514
494
|
containerUser: after.containment.containerUser, liveProcessUser: after.containment.liveProcessUser, mountCount: after.containment.mountCount,
|