import assert from "node:assert/strict" import { attestRoutingFixture } from "../../test/mcp-routing-fixture.js" import { fixtureGrantExpiry } from "../../test/mcp-fixture-expiry.js" import { verifyMcpGrantExpiryBounds } from "../../test/mcp-grant-expiry-bounds.js" import { createHash, createHmac, randomUUID } from "node:crypto" import { performance } from "node:perf_hooks" import { fileURLToPath } from "node:url" import { createServer } from "node:http" import { setTimeout as delay } from "node:timers/promises" import { pool, closePool } from "../../db/index.js" import { applySqlMigrations } from "../../db/migrate.js" import { mcpConnectionCreateSchema } from "@mmapp/contracts/mcp" import { createMcpConnection, authenticateMcpToken, updateMcpSettings, rotateConnectionToken, setConnectionStatus, revalidateMcpOwner, effectiveMcpConnection } from "./store.js" import { invokeMcpTool, approvePlan, applyPlan, recoverMcpOperations } from "./runtime.js" import { decryptSecret, encryptSecret, invalidateMcpAccess, tokenDigest } from "./security.js" import { addRoutingDependencies } from "./routing-dependencies.js" import { acceptOspfRouteProofMcp } from "./ospf-route-proof-acceptance.js" import { acceptWireguardInterfaceDeletion } from "./wireguard-interface-delete-acceptance.js" import { acceptWireguardPeerUpdate } from "./wireguard-peer-update-acceptance.js" import { acceptWireguardPeerDeletion } from "./wireguard-peer-delete-acceptance.js" import { acceptGreRevisionRestore } from "../../test/mcp-gre-revision.js" import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client" import { StdioClientTransport } from "@modelcontextprotocol/client/stdio" import type { AuthUser } from "../../lib/permissions.js" import { managedComment } from "../../managed-markers.js" import { compileManagedFilterRules } from "../../services/filter-rules-write-model.js" // One fixed authority deadline for the long integration suite; never renewed during the run. const fixtureAuthExpiresAt = Date.now() + 4 * 60 * 60 * 1000 const actor: AuthUser = { id: "integration-owner", email: "test@example.invalid", name: "Test", apps: ["mm"], permissions: ["mm:settings:admin", "mm:certificates:write", "mm:network:admin", "mm:bgp:admin", "mm:servers:admin", "mm:filters:admin"], expiresAt: fixtureAuthExpiresAt } let settings = { "rp-filter": "no", "tcp-syncookies": "no", "ipv4-multipath-hash-policy": "l3" }, writes = 0 let reconcileReadHook: (() => Promise) | undefined let reconcileSettingsReads = 0 const emptyEvoCatalog = { modules: { items: [] }, domains: { items: [] }, ip_ranges: { items: [] }, asns: { items: [] }, communities: { items: [] } } let evoCatalogPayload: unknown = emptyEvoCatalog, evoCatalogCalls = 0, evoCatalogAuthorization: string | undefined, evoCatalogRedirect = false let evoProbePayload: unknown = { items: [] }, evoProbeCalls = 0, evoProbeAuthorization: string | undefined, evoProbeRedirect = false let bridge = { ".id": "*fixture", name: "fixture-bridge", comment: "baseline", disabled: "true" } let healthState = true, failHealthAfterWrite = false, healthUnavailable = false, loseHealthResponse = false let healthBfdRemote = "192.0.2.2%fixture-ospf" let fixtureRouteTable: string | undefined let fixtureRoutingRules: Record[] = [] let interfaceFixtures = false, ignoreVrfPlacement = false, vrfWrites = 0 let fixtureVrfs: Record[] = [] // Preparation-only native catalogs for the business alias assertions. The // genuine owned component separately proves actual alias-only execution. let nativeAliasFixtures = false const nativeAliasCatalogs: Record = { "/rest/ip/vrf": [{ ".id": "*abc", name: "managed-vrf", interfaces: "fixture-gre", disabled: "false", comment: "MikrotikManager: integration" }, { ".id": "*0", name: "main", interfaces: "all", disabled: "false", builtin: "true" }], "/rest/interface": [{ ".id": "*b1", name: "fixture-gre", type: "gre-tunnel", vrf: "managed-vrf" }, { ".id": "*b2", name: "fixture-bridge", type: "bridge", vrf: "main" }], "/rest/interface/gre": [{ ".id": "*a1", name: "fixture-gre", disabled: "false", "local-address": "192.0.2.1", "remote-address": "198.51.100.2" }], "/rest/interface/bridge": [{ ".id": "*a2", name: "fixture-bridge", disabled: "false" }], "/rest/ip/address": [{ ".id": "*a3", address: "192.0.2.1/30", interface: "fixture-bridge", vrf: "main", dynamic: "false", disabled: "false" }], "/rest/ip/route": [{ ".id": "*a4", "dst-address": "192.0.2.0/30", "routing-table": "main", gateway: "fixture-bridge", "immediate-gw": "fixture-bridge", dynamic: "true", connect: "true" }], "/rest/routing/table": [{ ".id": "*0", name: "main", fib: "true", dynamic: "true" }, { ".id": "*abc", name: "managed-vrf", fib: "", dynamic: "true" }], "/rest/ip/settings": {}, "/rest/ipv6/settings": {}, } let ospfFixtures = false let ospfAnalysisFixtures = false, ospfAnalysisDispatches = 0, ospfAnalysisNativeRequests = 0 let ospfAfterCostPatch: (() => void) | undefined let ospfAnalysisExtraNeighbors: Record[] = [] const ospfAnalysisAddresses = [{ ".id": "*la", interface: "fixture-gre", address: "192.0.2.1/30", disabled: "false", invalid: "false" }] let ospfCostWriteFault = "none" let ospfPassiveWrites = 0, ospfIgnoreUnset = false let ospfPreviewPauseConnection:string|undefined let countCatalogCalls = false, catalogCalls = 0, ospfTemplateFlagReads = 0 let ospfPauseOnTemplatesConnection: string | undefined let ospfTemplateListsBeforePause = 0 let greCatalogFixtures = false let recursiveCatalogFixtures = false, recursivePropertyFailure = false let recursiveCatalogRows: Record[] = [] let recursiveMutations = false, recursiveMutationCount = 0, recursiveIgnorePayload = false let recursivePauseConnection: string | undefined let deviceStateFixtures = false, containerStateFailure: "none" | "envs" | "unsupported" = "none", certificateClockKnown = true let reverseContainerRows = false let certificateStateMalformed = false let filterCatalogFixtures = false, filterCatalogMalformed = false let legacyRouteContextFixture = false const legacyRouteContextRows: Record[]> = { "/routing/table": [{ ".id": "*main", name: "main", fib: "true", dynamic: "true" }], "/ip/vrf": [{ ".id": "*vrf", name: "main", interfaces: "all", disabled: "false" }], "/routing/bgp/connection": [{ ".id": "*peer", name: "legacy-peer", instance: "legacy", vrf: "main", "routing-table": "main", afi: "ip", "input.filter": "bgp-in", "output.filter-chain": "", templates: "", disabled: "false" }], "/routing/bgp/instance": [{ ".id": "*instance", name: "legacy", vrf: "main", "routing-table": "main", disabled: "false" }], "/routing/bgp/template": [], "/routing/filter/rule": [{ ".id": "*filter", chain: "bgp-in", rule: compileManagedFilterRules([{ community: "65000:10", gateway: "192.0.2.1", outIface: "", action: "route", description: "" }]), comment: managedComment("test-router"), disabled: "false" }], "/ip/route": [{ ".id": "*1", "dst-address": "192.0.2.1/32", "routing-table": "main", comment: managedComment("recursive"), disabled: "false" }], "/routing/ospf/instance": [], "/routing/rip/instance": [], "/routing/isis/instance": [], "/routing/filter/select-rule": [], "/routing/bgp/vpn": [], "/routing/rule": [], "/ip/firewall/mangle": [], "/ipv6/firewall/mangle": [], } let greMutationFixtures = false, greMutationIgnored = false, greMutationLost = false, greMutationCommands = 0, greMutationNextId = 100 let greMutationRevoke: string | undefined let greRevisionAfterCommand: (()=>Promise) | undefined let greRevisionReads=0 let greNativeCounterTick = false let greNativeRouteReads = 0, greNativeLateDynamicAt = 0 let greMutationCollections: Record[]> = {} let greMutationDependencies: Record[]> = {} let preflightFixtures: Record | undefined, preflightUnavailable = "" let containerMutationFixtures = false, containerDeletes = 0, containerCommands = 0, ignoreContainerDelete = false, replaceContainerAfterCommand = false let mutableContainers: Record[] = [] const containerParent: { ".id": string; name: string; type: string; disabled?: string } = { ".id": "*veth", name: "fixture-veth", type: "veth", disabled: "false" } let ecmpFixtures = false, ecmpWrites = 0, loseEcmpGateway = false let fixtureEcmpRoutes: Record[] = [] let wanFixtures = false, wanAddressFailure = false let probeInterfaceFixtures = false, probeNativeCalls = 0, probeReadFailure = false let liveFixtures = false, liveNativeCalls = 0, liveIncomplete = false let liveRevokeConnection: string | undefined, liveChangeSource = false let firewallStateFixtures = false, firewallStateCalls = 0, firewallKnownFlags = false, firewallFailedChunk = "", firewallDuplicate = false let fwRestoreFixtures = false, fwRestoreCommands = 0, fwRestoreNextId = 1, fwRestoreIgnored = false, fwRestoreLost = false let fwRestoreRevoke: string | undefined let fwRestoreCollections: Record[]> = {} let addressFixtures = false, addressCommands = 0, addressIgnored = false, addressExpiry = 0 let addressRows: Record[] = [] let firewallWriteFixtures = false, firewallWriteCommands = 0, firewallWriteIgnored = false let firewallWriteRows: Record[] = [] let firewallMoveFixtures = false, firewallMoveCommands = 0, firewallIgnoreMove = false let firewallMoveRows = [1, 2, 3].map(id => ({ ".id": "*" + id, chain: "fixture-move", action: "accept", disabled: "true", dynamic: "false", log: "false", comment: "rule " + id, packets: "0", "connection-mark": "keep-exactly" })) let vpnFixtures = false, vpnFailurePath = "", vpnAmbiguous = false, vpnUnknownSession = false, vpnDuplicate = false const vpnCalls: string[] = [], vpnPrivate = "never-return-wg-private-fixture", vpnPsk = "never-return-ipsec-psk-fixture" const wanDefault = { ".id": "*wan", "dst-address": "0.0.0.0/0", "routing-table": "main", active: "true", distance: "1", gateway: "192.0.2.1", "immediate-gw": "192.0.2.1%ether1" } let wanRoutes: Record[] = [wanDefault] const fixtureOspf: Record<"instance" | "area" | "interface-template", Record[]> = { instance: [{ ".id": "*ospf", name: "fixture-ospf", vrf: "main", "router-id": "192.0.2.1", version: "2", disabled: "false" }], area: [{ ".id": "*area", name: "fixture-area", instance: "fixture-ospf", disabled: "false", "area-id": "0.0.0.0" }], "interface-template": [{ ".id": "*template", area: "fixture-area", interfaces: "fixture-gre", cost: "30", disabled: "false", "use-bfd": "true" }], } let bgpFixtures = false let bgpSessionOverride: unknown = undefined const fixtureBgp = { instance: [{ ".id": "*bgp", name: "fixture-bgp", vrf: "main", "routing-table": "main", as: "64512", "router-id": "192.0.2.1", disabled: "false" }], connection: [{ ".id": "*peer", name: "fixture-peer", instance: "fixture-bgp", "remote.address": "192.0.2.2", "local.address": "192.0.2.1", "remote.as": "64513", "local.role": "ebgp", disabled: "false" }], session: [{ ".id": "*session", name: "fixture-peer-1", instance: "fixture-bgp", established: "true", "remote.address": "192.0.2.2", "remote.as": "64513", "local.as": "64512", "local.afi": "ip,ipv6", "remote.afi": "ip,ipv6" }], } const router = createServer((req, res) => { if (nativeAliasFixtures) { res.setHeader("content-type", "application/json") if (req.method !== "GET") { vrfWrites++; res.statusCode = 405; res.end("{}"); return } res.end(JSON.stringify(nativeAliasCatalogs[req.url ?? ""] ?? [])); return } if (ospfAnalysisFixtures) ospfAnalysisNativeRequests++ if (legacyRouteContextFixture) { const path = (req.url ?? "").replace(/^\/rest/, ""), collection = path.endsWith("/get") ? path.slice(0, -4) : path if (collection in legacyRouteContextRows) { res.setHeader("content-type", "application/json") if (req.method === "GET" && path === collection) { res.end(JSON.stringify(legacyRouteContextRows[collection])); return } if (req.method === "POST" && path.endsWith("/get")) { let raw = ""; req.on("data", chunk => { raw += chunk }); req.on("end", () => { const args = JSON.parse(raw), row = legacyRouteContextRows[collection].find(item => item[".id"] === args[".id"]), value = row?.[args["value-name"]] res.end(JSON.stringify(value === undefined ? [] : { ret: value })) }); return } res.statusCode = 405; res.end("{}"); return } } if (req.url === "/v1/modules?limit=1") { evoProbeCalls++; evoProbeAuthorization = req.headers.authorization if (evoProbeRedirect) { res.writeHead(302, { Location: "/probe-redirect-target" }); res.end(); return } res.setHeader("content-type", "application/json"); res.end(JSON.stringify(evoProbePayload)); return } if (req.url === "/v1/router-lists/catalog") { evoCatalogCalls++; evoCatalogAuthorization = req.headers.authorization if (evoCatalogRedirect) { res.writeHead(302, { Location: "/catalog-redirect-target" }); res.end(); return } res.setHeader("content-type", "application/json"); res.end(JSON.stringify(evoCatalogPayload)); return } if (countCatalogCalls) catalogCalls++ if (vpnFixtures && req.method === "GET") { const path = req.url ?? "" vpnCalls.push(path); res.setHeader("content-type", "application/json") if (path === vpnFailurePath) { res.statusCode = 503; res.end("{}"); return } const wg = [{ ".id": "*wg1", name: "wg1", "private-key": vpnPrivate, comment: vpnPrivate, disabled: "false", running: "true", "listen-port": "13231", mtu: "1420" }, { ".id": "*wg2", name: "wg2" }] const identity = { ".id": "*I1", peer: "ike", "auth-method": "pre-shared-key", "remote-id": "key-id:alice", secret: vpnPsk, comment: "MikrotikManager:ipsec user=" + vpnPsk } const collections: Record = { "/rest/interface/gre": vpnDuplicate ? [{ ".id": "*G", name: "gre" }, { ".id": "*G", name: "gre" }] : [{ ".id": "*G", name: "gre", disabled: "false", running: "true", "ipsec-secret": vpnPsk, comment: vpnPsk, mtu: "1400", keepalive: "10s,10" }, { ".id": "*U", name: "unknown" }], "/rest/interface/wireguard": vpnDuplicate ? [wg[0], wg[0]] : wg, "/rest/interface/wireguard/peers": [1, 2, 3, 4].map(id => ({ ".id": "*P" + id, interface: id === 4 ? "wg2" : "*wg1", "public-key": "public-" + id, "preshared-key": vpnPsk, name: vpnPsk, rx: "0", "persistent-keepalive": "0", disabled: "false", "allowed-address": "192.0.2.0/24,198.51.100.0/24,fd00::/64" })), "/rest/ip/address": [{ ".id": "*a", interface: "*wg1", address: "192.0.2.1/24", disabled: "false", dynamic: "false", invalid: "false" }], "/rest/ipv6/address": [1, 2, 3].map(id => ({ ".id": "*a" + id, interface: id === 1 ? "*G" : "gre", address: "fd00::" + id + "/64", disabled: "false", dynamic: "false", invalid: "false" })), "/rest/interface/vxlan": [{ ".id": "*vx1", name: "vx1", vni: "100", port: "4789", mtu: "1420", l2mtu: "65535", arp: "enabled", learning: "true", disabled: "false", running: "true" }], "/rest/interface/vxlan/vteps": [1, 2, 3].map(id => ({ ".id": "*v" + id, interface: "*vx1", "remote-ip": "198.51.100." + id })), "/rest/ip/ipsec/peer": [{ ".id": "*ike", name: "ike", "exchange-mode": "ike2", passive: "true", certificate: "ipsec-server" }], "/rest/ip/ipsec/identity": vpnAmbiguous ? [identity, { ...identity, ".id": "*I2", comment: "MikrotikManager:ipsec user=second" }] : [identity], "/rest/ip/ipsec/active-peers": [{ ".id": "*active", id: "alice", ...(vpnUnknownSession ? {} : { state: "established" }), "remote-address": "192.0.2.2" }], "/rest/certificate": [{ ".id": "*CA", name: "ipsec-ca", "key-usage": "key-cert-sign,crl-sign", authority: "true", trusted: "false", "private-key": "true" }, { ".id": "*CRT", name: "ipsec-server", "key-usage": "tls-server", "common-name": "vpn.example.invalid", ca: "ipsec-ca", "invalid-after": "oct/31/2026 12:00:00", trusted: "false", "private-key": "true" }], } res.end(JSON.stringify(collections[path] ?? [])); return } if (fwRestoreFixtures && /^\/rest\/(?:ip|ipv6)\/firewall\//.test(req.url ?? "")) { res.setHeader("content-type", "application/json") const path = decodeURIComponent(req.url ?? ""), getPath = path.endsWith("/get"), base = getPath ? path.slice(0, -4) : path.replace(/\/\*[A-Za-z0-9]+$/, ""), id = path.slice(base.length + 1) const rows = fwRestoreCollections[base] ?? (fwRestoreCollections[base] = []) if (req.method === "GET") { res.end(JSON.stringify(rows)); return } let body = ""; req.on("data", chunk => { body += chunk }); req.on("end", async () => { const data = body ? JSON.parse(body) : {} if (getPath) { res.end(JSON.stringify({ ret: rows.find(row => row[".id"] === data[".id"])?.[data["value-name"]] ?? "false" })); return } fwRestoreCommands++ const fields = Object.fromEntries(Object.entries(data).map(([key, value]) => [key, ["disabled", "log"].includes(key) ? value === "yes" ? "true" : "false" : value])) as Record if (req.method === "PUT" && !fwRestoreIgnored) rows.push({ ".id": "*NEW" + fwRestoreNextId++, dynamic: "false", disabled: "false", ...(base.endsWith("address-list") ? {} : { log: "false" }), ...fields }) if (req.method === "DELETE") fwRestoreCollections[base] = rows.filter(row => row[".id"] !== id) if (req.method === "PATCH") Object.assign(rows.find(row => row[".id"] === id)!, fields) if (fwRestoreRevoke) { const connectionId = fwRestoreRevoke; fwRestoreRevoke = undefined; await setConnectionStatus(connectionId, "paused", actor) } if (fwRestoreLost) { fwRestoreLost = false; res.destroy(); return } res.end("{}") }); return } if (addressFixtures && /^\/rest\/(?:ip|ipv6)\/firewall\//.test(req.url ?? "")) { res.setHeader("content-type", "application/json"); const path = decodeURIComponent(req.url ?? ""), base = "/rest/ip/firewall/address-list" if (req.method === "GET") { res.end(JSON.stringify(path === base ? addressRows.map(row => row[".id"] === "*T" ? { ...row, timeout: Math.max(1, Math.floor((addressExpiry - Date.now()) / 1000)) + "s" } : row) : [])); return } let body = ""; req.on("data", chunk => { body += chunk }); req.on("end", () => { const data = body ? JSON.parse(body) : {} if (path === base + "/get") { res.end(JSON.stringify({ ret: "false" })); return } addressCommands++ if (!addressIgnored) { const fields = Object.fromEntries(Object.entries(data).map(([key, value]) => [key, key === "disabled" ? value === "yes" ? "true" : "false" : key === "address" ? String(value).replace(/\/32$/, "") : value])) as Record if (req.method === "PUT") { const id = fields.timeout ? "*T" : "*C"; addressRows.push({ ".id": id, dynamic: fields.timeout ? "true" : "false", disabled: "false", ...fields }); if (fields.timeout) addressExpiry = Date.now() + 60000 } if (req.method === "PATCH") Object.assign(addressRows.find(row => row[".id"] === path.slice(base.length + 1))!, fields) if (req.method === "DELETE") addressRows = addressRows.filter(row => row[".id"] !== path.slice(base.length + 1)) } res.end("{}") }); return } if (firewallWriteFixtures && /^\/rest\/(?:ip|ipv6)\/firewall\//.test(req.url ?? "")) { res.setHeader("content-type", "application/json") const path = decodeURIComponent(req.url ?? ""), base = "/rest/ip/firewall/filter" if (req.method === "GET") { res.end(JSON.stringify(path === base ? firewallWriteRows : [])); return } let body = ""; req.on("data", chunk => { body += chunk }); req.on("end", () => { const data = body ? JSON.parse(body) : {} if (path === base + "/get") { res.end(JSON.stringify({ ret: "false" })); return } firewallWriteCommands++ if (!firewallWriteIgnored) { const fields = Object.fromEntries(Object.entries(data).map(([k, v]) => [k, ["disabled", "log"].includes(k) ? v === "yes" ? "true" : "false" : v])) as Record if (req.method === "PUT") firewallWriteRows.push({ ".id": "*C", dynamic: "false", log: "false", disabled: "false", ...fields }) if (req.method === "PATCH") Object.assign(firewallWriteRows.find(row => row[".id"] === path.slice(base.length + 1))!, fields) if (req.method === "DELETE") firewallWriteRows = firewallWriteRows.filter(row => row[".id"] !== path.slice(base.length + 1)) } res.end("{}") }); return } if (firewallMoveFixtures && /^\/rest\/(?:ip|ipv6)\/firewall\//.test(req.url ?? "")) { res.setHeader("content-type", "application/json") if (req.url === "/rest/ip/firewall/filter/move") { let body = ""; req.on("data", chunk => { body += chunk }); req.on("end", () => { const input = JSON.parse(body); firewallMoveCommands++ if (!firewallIgnoreMove) { const row = firewallMoveRows.find(row => row[".id"] === input.numbers)! firewallMoveRows = firewallMoveRows.filter(item => item !== row) const index = input.destination ? firewallMoveRows.findIndex(row => row[".id"] === input.destination) : firewallMoveRows.length firewallMoveRows.splice(index, 0, row) } res.end("{}") }); return } if (req.url === "/rest/ip/firewall/filter") { for (const row of firewallMoveRows) row.packets = String(Number(row.packets) + 1) res.end(JSON.stringify(firewallMoveRows)); return } res.end("[]"); return } if (firewallStateFixtures && /^\/rest\/(?:ip|ipv6)\/firewall\/(?:filter|nat|mangle|raw|address-list)$/.test(req.url ?? "")) { firewallStateCalls++; res.setHeader("content-type", "application/json") if (req.url === firewallFailedChunk) { res.statusCode = 503; res.end("{}"); return } const flags = firewallKnownFlags ? { disabled: "false", dynamic: "false", log: "false" } : {} const row = req.url!.endsWith("address-list") ? { ".id": "*list", list: "fixture-list", address: "192.0.2.1", ...flags } : { ".id": "*rule", chain: "forward", action: "accept", packets: "0", "src-address-list": "fixture-list", "dst-address": "192.0.2.0/24", "in-interface": "ether1", "out-interface": "ether2", password: "never-return-firewall-key", ...flags } res.end(JSON.stringify(firewallDuplicate && req.url === "/rest/ip/firewall/filter" ? [row, row] : [row])); return } if (liveFixtures && ["/rest/interface", "/rest/interface/monitor-traffic"].includes(req.url ?? "")) { liveNativeCalls++; res.setHeader("content-type", "application/json") if (req.url === "/rest/interface") { res.end(JSON.stringify(["ether1", "ether2", "lo"].map(name => ({ name, running: "true", disabled: "false" })))); return } let body = "" req.on("data", chunk => { body += chunk }) req.on("end", () => { const payload = JSON.parse(body), names = String(payload.interface).split(",") assert.equal(payload.once, "") const rows = names.slice(0, liveIncomplete ? 1 : undefined).map(name => ({ name, "rx-bits-per-second": "1000000", "tx-bits-per-second": "0", password: "never-return-live-key" })) const update = liveRevokeConnection ? pool.query("UPDATE mcp_connections SET policy=jsonb_set(policy,'{status}','\"paused\"'::jsonb) WHERE id=$1", [liveRevokeConnection]) : liveChangeSource ? pool.query("UPDATE servers SET updated_at=now() WHERE id=1") : Promise.resolve() void update.then(() => res.end(JSON.stringify(rows))).catch(() => { res.statusCode = 500; res.end("{}") }) }); return } if (probeInterfaceFixtures && ["/rest/interface", "/rest/interface/get", "/rest/ip/address"].includes(req.url ?? "")) { probeNativeCalls++; res.setHeader("content-type", "application/json") if (probeReadFailure) { res.statusCode = 503; res.end("{}"); return } if (req.url === "/rest/interface/get") { req.resume(); req.on("end", () => res.end(JSON.stringify({ ret: "false" }))); return } const rows = req.url === "/rest/interface" ? [{ ".id": "*p1", name: "ether1", running: "true", disabled: "false" }, { ".id": "*p2", name: "gre-probe", running: "true" }, { ".id": "*p3", name: "unknown-probe", disabled: "false" }] : [{ ".id": "*a1", interface: "ether1", address: "192.0.2.10/24", disabled: "false", invalid: "false", dynamic: "false", "private-key": "never-return-probe-key" }] res.end(JSON.stringify(rows)); return } if (ospfAnalysisFixtures && req.url === "/rest/ip/address") { res.setHeader("content-type", "application/json"); res.end(JSON.stringify(ospfAnalysisAddresses)); return } if (ospfFixtures && req.url?.startsWith("/rest/routing/ospf/interface-template") && ["PUT", "PATCH", "DELETE"].includes(req.method ?? "") || ospfFixtures && req.url === "/rest/routing/ospf/interface-template/unset") { ospfPassiveWrites++ let body = ""; req.on("data", chunk => { body += chunk }); req.on("end", () => { res.setHeader("content-type", "application/json") const fields = body ? JSON.parse(body) : {} const id = req.url?.endsWith("/unset") ? fields[".id"] : decodeURIComponent(req.url!.split("/").at(-1)!) const row = fixtureOspf["interface-template"].find(row => row[".id"] === id) if (req.method === "PUT") { const added = { ...fields, ".id": "*abc123", ...(Object.hasOwn(fields, "passive") ? { passive: "" } : {}) } fixtureOspf["interface-template"].push(added); res.end(JSON.stringify(added)); return } if (!row) { res.statusCode = 404; res.end("{}"); return } if (req.method === "DELETE") fixtureOspf["interface-template"] = fixtureOspf["interface-template"].filter(item => item !== row) else if (req.url?.endsWith("/unset")) { if (!ospfIgnoreUnset && fields["value-name"] === "passive") delete row.passive } else Object.assign(row, fields, Object.hasOwn(fields, "passive") ? { passive: "" } : {}) res.end("{}") }); return } if ((ospfFixtures || ospfAnalysisFixtures) && ["/rest/routing/ospf/interface-template/get", "/rest/routing/ospf/interface-template/print"].includes(req.url ?? "")) { ospfTemplateFlagReads++ let body = ""; req.on("data", chunk => { body += chunk }); req.on("end", () => { res.setHeader("content-type", "application/json") const input = JSON.parse(body) if (req.url?.endsWith("/print")) { const query = JSON.stringify(input[".query"]) if (![JSON.stringify(["passive"]), JSON.stringify(["passive", "#!"])].includes(query)) { res.statusCode = 400; res.end("{}"); return } const absent = query === JSON.stringify(["passive", "#!"]) res.end(JSON.stringify(fixtureOspf["interface-template"].filter(row => ("passive" in row) !== absent).map(row => ({ ".id": row[".id"] })))); return } const row = fixtureOspf["interface-template"].find(row => row[".id"] === input[".id"]) as Record | undefined if (!row || !["passive", "disabled", "use-bfd"].includes(input["value-name"])) { res.statusCode = 404; res.end("{}"); return } if (input["value-name"] === "use-bfd" && row.interfaces === "fixture-loop") { res.end("[]"); return } const key = input["value-name"], value = key === "use-bfd" && ospfAnalysisFixtures ? "false" : key === "passive" && row[key] === "" ? "true" : row[key] ?? "false" if (key === "passive" && row[key] === undefined) { res.end("[]"); return } res.end(JSON.stringify({ ret: value })) }); return } if (ospfAnalysisFixtures && req.url?.startsWith("/rest/routing/ospf/")) { if (req.method !== "GET") ospfAnalysisDispatches++ res.setHeader("content-type", "application/json") const key = req.url.slice("/rest/routing/ospf/".length) if (req.method === "PATCH" && key.startsWith("interface-template/")) { let body = ""; req.on("data", chunk => { body += chunk }); req.on("end", () => { const id = decodeURIComponent(key.slice("interface-template/".length)), target = fixtureOspf["interface-template"].find(row => row[".id"] === id) if (!target) { res.statusCode = 404; res.end("{}"); return } if (ospfCostWriteFault !== "ignored") target.cost = JSON.parse(body).cost if (ospfCostWriteFault === "changed-field") Object.assign(target, { opaque: "UNEXPECTED_OSPF_CHANGE" }) if (ospfAfterCostPatch) { const work = ospfAfterCostPatch; ospfAfterCostPatch = undefined; work() } if (ospfCostWriteFault === "lost-response") { req.socket.destroy(); return } res.end("{}") }); return } const rows = key === "neighbor" ? [{ ".id": "*analysis-neighbor", interface: "fixture-gre", instance: "fixture-ospf", "router-id": "192.0.2.2", address: "192.0.2.2", state: "Full" }, ...ospfAnalysisExtraNeighbors] : key === "interface-template" ? fixtureOspf["interface-template"].map(row => ({ ...row, "use-bfd": row.interfaces === "fixture-loop" ? undefined : "false" })) : fixtureOspf[key as keyof typeof fixtureOspf] ?? [] if (ospfPauseOnTemplatesConnection && key === "interface-template" && ospfTemplateListsBeforePause-- === 0) { const connection = ospfPauseOnTemplatesConnection; ospfPauseOnTemplatesConnection = undefined void pool.query("UPDATE mcp_connections SET policy=jsonb_set(policy,'{status}',to_jsonb('paused'::text)) WHERE id=$1", [connection]).then(() => res.end(JSON.stringify(rows)), () => { res.statusCode = 503; res.end("{}") }) return } if(ospfPreviewPauseConnection&&key==="neighbor"){ const connection=ospfPreviewPauseConnection;ospfPreviewPauseConnection=undefined void pool.query("UPDATE mcp_connections SET policy=jsonb_set(policy,'{status}',to_jsonb('paused'::text)) WHERE id=$1",[connection]).then(()=>res.end(JSON.stringify(rows)),()=>{res.statusCode=503;res.end("{}")}) return } res.end(JSON.stringify(rows)); return } if (wanFixtures && ["/rest/ip/dhcp-client", "/rest/ip/address", "/rest/ip/route"].includes(req.url ?? "")) { res.setHeader("content-type", "application/json") if (wanAddressFailure && req.url === "/rest/ip/address") { res.statusCode = 503; res.end("{}"); return } const rows = req.url === "/rest/ip/route" ? wanRoutes : req.url === "/rest/ip/address" ? [{ address: "192.0.2.10/24", interface: "ether1" }] : [{ address: "192.0.2.10/24", interface: "ether1", gateway: "192.0.2.1", status: "bound", password: "never-return-wan-key" }] res.end(JSON.stringify(rows)); return } if (containerMutationFixtures && req.url === "/rest/interface") { res.setHeader("content-type", "application/json"); res.end(JSON.stringify([containerParent])); return } if (containerMutationFixtures && req.url?.startsWith("/rest/container")) { res.setHeader("content-type", "application/json") if (req.method === "DELETE") { containerDeletes++ if (!ignoreContainerDelete) mutableContainers = mutableContainers.filter(row => req.url !== "/rest/container/" + encodeURIComponent(String(row[".id"]))) res.end("{}"); return } if (req.method === "POST") { let raw = ""; req.on("data", chunk => { raw += chunk }); req.on("end", () => { const input = JSON.parse(raw), row = mutableContainers.find(row => row[".id"] === input[".id"]) if (!row) { res.statusCode = 404; res.end("{}"); return } containerCommands++; row.status = req.url?.endsWith("/start") ? "running" : "stopped" if (replaceContainerAfterCommand) row.name = "replacement-after-command" res.end("{}") }); return } res.end(JSON.stringify(mutableContainers)); return } if (preflightFixtures && req.url && Object.hasOwn(preflightFixtures, req.url)) { res.setHeader("content-type", "application/json") if (req.url === preflightUnavailable) { res.statusCode = 503; res.end("{}"); return } res.end(JSON.stringify(preflightFixtures[req.url])); return } if (greMutationFixtures && req.url?.startsWith("/rest/")) { greRevisionReads++ res.setHeader("content-type","application/json") const path = decodeURIComponent(req.url.slice(5)), bases = ["/interface/gre","/ip/address","/ipv6/address"] const base = bases.find(base=>path===base||path.startsWith(base+"/")) if (req.method === "GET") { if(path==="/ip/route"){ greNativeRouteReads++ if(greNativeLateDynamicAt&&greNativeRouteReads>=greNativeLateDynamicAt)greMutationDependencies[path]=[{".id":"*LATEDYNAMIC",dynamic:"true",gateway:"192.0.2.2%native-gre"}] } if (base) { res.end(JSON.stringify((greMutationCollections[base]??[]).map(row=>Object.fromEntries(Object.entries(row).filter(([key,value])=>(key!=="comment"||value!=="")&&(base!=="/interface/gre"||key!=="dynamic")))))); return } if (path==="/interface") { res.end(JSON.stringify(greMutationDependencies[path]??[{ ".id":"*E",name:"ether1",type:"ether" },...(greMutationCollections["/interface/gre"]??[]).map(row=>({".id":row[".id"],name:row.name,type:"gre",...(row.dynamic===undefined?{}:{dynamic:row.dynamic})}))]));return } if(greNativeCounterTick&&path.includes("/firewall/"))for(const row of greMutationDependencies[path]??[]){row.packets=String(Number(row.packets??"0")+1);row.bytes=String(Number(row.bytes??"0")+100)} res.end(JSON.stringify(greMutationDependencies[path]??[])); return } let body = ""; req.on("data",chunk=>body+=chunk); req.on("end",async()=>{ const data = body?JSON.parse(body):{} if(path==="/interface/get"){ const row=greMutationDependencies[path]?.find(row=>row[".id"]===data[".id"]) if(row&&data["value-name"]==="dynamic"){res.end(JSON.stringify(row.ret==="empty-array"?[]:{ret:row.ret}));return} res.statusCode=400;res.end(JSON.stringify({error:400,detail:"Property not available"}));return } if(path==="/interface/print"){ assert.deepEqual(data[".query"],[".id="+greMutationDependencies["/interface/get"]?.[0]?.[".id"],data[".query"]?.includes("-dynamic")?"-dynamic":"dynamic","#&"]) const key=data[".query"]?.includes("-dynamic")?"/interface/static-print":"/interface/dynamic-print" res.end(JSON.stringify(greMutationDependencies[key]??[]));return } if (path.endsWith("/get") && base) { const row = (greMutationCollections[base]??[]).find(row=>row[".id"]===data[".id"]) res.end(JSON.stringify({ret:row?.[data["value-name"]]??(["comment","ipsec-secret","from-pool"].includes(data["value-name"])?"":"false")})); return } greMutationCommands++ if(base==="/interface/gre"&&data["ipsec-secret"]===""&&["yes","true"].includes(data["allow-fast-path"])){ res.statusCode=400;res.end(JSON.stringify({error:400,detail:"failure: cannot enable fastpath together with ipsec yet"}));return } if (!greMutationIgnored && base) { const fields = Object.fromEntries(Object.entries(data).map(([key,value])=>[key,value==="yes"?"true":value==="no"?"false":String(value)])) if (req.method==="PUT") { const row = base==="/interface/gre"?{".id":"*G"+(greMutationNextId++),"local-address":"0.0.0.0",mtu:"1476",keepalive:"10s,10",dscp:"inherit",disabled:"false",dynamic:"false",running:"false","clamp-tcp-mss":"true","allow-fast-path":"true","ipsec-secret":"",comment:"",...fields}:{".id":"*A"+(greMutationNextId++),disabled:"false",dynamic:"false",invalid:"false",advertise:"false","no-dad":"false","eui-64":"false","from-pool":"",comment:"",...fields} ;(greMutationCollections[base]??=[]).push(row) } else if (req.method==="PATCH") Object.assign(greMutationCollections[base].find(row=>row[".id"]===path.slice(base.length+1))!,fields) else if (req.method==="DELETE") greMutationCollections[base]=greMutationCollections[base].filter(row=>row[".id"]!==path.slice(base.length+1)) } if (greMutationRevoke) { const id=greMutationRevoke;greMutationRevoke=undefined;await pool.query("UPDATE mcp_connections SET policy=jsonb_set(policy,'{status}','\"paused\"'::jsonb) WHERE id=$1",[id]) } await greRevisionAfterCommand?.() if (greMutationLost) { greMutationLost=false;req.socket.destroy();return } res.end("{}") });return } if (filterCatalogFixtures && ["/rest/interface/gre", "/rest/routing/filter/rule"].includes(req.url ?? "")) { res.setHeader("content-type","application/json") if (req.url === "/rest/interface/gre") { res.end(JSON.stringify([{ ".id":"*G",name:"filter-gre","local-address":"192.0.2.1","remote-address":"192.0.2.2","ipsec-secret":"NEVER_FILTER_PSK" }])); return } res.end(JSON.stringify(filterCatalogMalformed ? { rules: [] } : [ {".id":"*F1",chain:"bgp-in",rule:"if (bgp-communities includes 64512:1) { set gw 192.0.2.2; accept; }",disabled:"true","private-key":"NEVER_FILTER_NATIVE_KEY"}, {".id":"*F2",chain:"bgp-in",rule:"jump other;"}, {".id":"*F3",chain:"other",rule:"reject;",disabled:"false"}, ])); return } if (deviceStateFixtures && req.url?.startsWith("/rest/container")) { res.setHeader("content-type", "application/json") if (req.url === "/rest/container") { if (containerStateFailure === "unsupported") { res.statusCode = 404; res.end("{}"); return } res.end(JSON.stringify((reverseContainerRows ? [2, 1] : [1, 2]).map(index => ({ ".id": `*c${index}`, name: `fixture-container-${index}`, "remote-image": "test/image:latest", envlists: "fixture-env", cmd: "never-return-this-command", running: index === 1 ? "true" : undefined })))); return } if (req.url === "/rest/container/envs") { if (containerStateFailure === "envs") { res.statusCode = 503; res.end("{}"); return } res.end(JSON.stringify([{ list: "fixture-env", key: "UNCLASSIFIED_VALUE", value: "never-return-this-env" }])); return } res.end("[]"); return } if (deviceStateFixtures && req.url === "/rest/system/clock") { res.setHeader("content-type", "application/json"); res.end(JSON.stringify({ "gmt-offset": certificateClockKnown ? "+07:00" : "unknown" })); return } if (deviceStateFixtures && req.url === "/rest/certificate") { res.setHeader("content-type", "application/json") if (healthUnavailable) { res.statusCode = 503; res.end("{}"); return } if (certificateStateMalformed) { res.end(JSON.stringify({ certificates: [] })); return } res.end(JSON.stringify([{ ".id": "*cert1", name: "fixture-certificate", flags: "T", "invalid-before": "2025-01-01 00:00:00", "invalid-after": "2035-01-01 00:00:00", "private-key": "never-return-certificate-key" }, { ".id": "*cert2", name: "unknown-certificate" }])); return } if (recursiveCatalogFixtures && req.url?.startsWith("/rest/ip/route")) { res.setHeader("content-type", "application/json") if (healthUnavailable) { res.statusCode = 503; res.end("{}"); return } if (recursiveMutations && req.method === "PUT") { let raw = ""; req.on("data", chunk => { raw += chunk }); req.on("end", async () => { const fields = JSON.parse(raw), row = { ...fields, ".id": `*created-${++recursiveMutationCount}`, static: "true" } if (recursiveIgnorePayload) row.distance = "254" recursiveCatalogRows.push(row) if (recursivePauseConnection) { await pool.query("UPDATE mcp_connections SET policy=jsonb_set(policy,'{status}',to_jsonb($2::text)) WHERE id=$1", [recursivePauseConnection, "paused"]) recursivePauseConnection = undefined } res.end(JSON.stringify(row)) }); return } if (recursiveMutations && req.method === "DELETE") { recursiveMutationCount++ recursiveCatalogRows = recursiveCatalogRows.filter(row => req.url !== "/rest/ip/route/" + encodeURIComponent(String(row[".id"]))) res.end("{}"); return } if (req.url.endsWith("/get")) { let raw = ""; req.on("data", chunk => { raw += chunk }); req.on("end", () => { if (recursivePropertyFailure) { res.statusCode = 503; res.end("{}"); return } const input = JSON.parse(raw); res.end(input["value-name"] === "disabled" ? JSON.stringify({ ret: "false" }) : "[]") }); return } res.end(JSON.stringify(recursiveCatalogRows)); return } if (greCatalogFixtures && req.url === "/rest/interface/gre") { res.setHeader("content-type", "application/json") if (healthUnavailable) { res.statusCode = 503; res.end("{}"); return } res.end(JSON.stringify([1, 2].map(id => ({ ".id": `*gre${id}`, name: `catalog-gre-${id}`, running: "true", disabled: "false", "ipsec-secret": "never-return-gre-key" })))); return } if (bgpFixtures && req.url?.startsWith("/rest/routing/bgp/")) { const key = req.url.slice("/rest/routing/bgp/".length) as keyof typeof fixtureBgp if (key in fixtureBgp) { res.setHeader("content-type", "application/json"); if (key === "session" && healthUnavailable) { res.statusCode = 503; res.end("{}"); return }; res.end(JSON.stringify(key === "session" && bgpSessionOverride !== undefined ? bgpSessionOverride : fixtureBgp[key])); return } } if (ecmpFixtures && req.url?.startsWith("/rest/ip/route")) { res.setHeader("content-type", "application/json") if (req.method === "PATCH") { let raw = ""; req.on("data", chunk => { raw += chunk }); req.on("end", () => { const row = fixtureEcmpRoutes.find(row => req.url!.endsWith(encodeURIComponent(row[".id"])))! Object.assign(row, JSON.parse(raw)); ecmpWrites++ const best = Math.min(...fixtureEcmpRoutes.map(row => Number(row.distance))) for (const route of fixtureEcmpRoutes) route.active = String(Number(route.distance) === best) if (loseEcmpGateway) fixtureEcmpRoutes[0]["immediate-gw"] = "" res.end(JSON.stringify(row)) }); return } res.end(JSON.stringify(fixtureEcmpRoutes)); return } if (ospfFixtures && req.url?.startsWith("/rest/routing/ospf/")) { const key = req.url.slice("/rest/routing/ospf/".length) as keyof typeof fixtureOspf if (key in fixtureOspf) { res.setHeader("content-type", "application/json"); res.end(JSON.stringify(fixtureOspf[key])); return } } if (req.url === "/rest/routing/bfd/session") { res.setHeader("content-type", "application/json") if (healthUnavailable) { res.statusCode = 503; res.end("{}"); return } res.end(JSON.stringify([{ ".id": "*bfd", state: healthState ? "up" : "down", "remote-address": healthBfdRemote, "local-address": "192.0.2.1%fixture-ospf" }])); return } if (interfaceFixtures && req.url === "/rest/interface/gre") { res.setHeader("content-type", "application/json"); res.end(JSON.stringify([{ ".id": "*gre", name: "fixture-gre", disabled: "false" }])); return } if (interfaceFixtures && req.url === "/rest/interface") { res.setHeader("content-type", "application/json"); res.end(JSON.stringify([{ ".id": "*gre", name: "fixture-gre", type: "gre" }, { ".id": "*fixture", name: "fixture-bridge", type: "bridge" }])); return } if (interfaceFixtures && req.url?.startsWith("/rest/ip/vrf")) { res.setHeader("content-type", "application/json") if (req.method === "PUT") { let raw = ""; req.on("data", chunk => { raw += chunk }); req.on("end", () => { const { "place-before": before, ...fields } = JSON.parse(raw) const row = { ".id": "*CREATEDVRF", ...fields }; vrfWrites++ const index = ignoreVrfPlacement ? -1 : fixtureVrfs.findIndex(item => item[".id"] === before) if (index >= 0) fixtureVrfs.splice(index, 0, row); else fixtureVrfs.push(row) res.end(JSON.stringify(row)) }); return } if (req.method === "DELETE") { fixtureVrfs = fixtureVrfs.filter(row => !req.url!.endsWith(encodeURIComponent(row[".id"]))); vrfWrites++; res.end("{}"); return } res.end(JSON.stringify(fixtureVrfs)); return } if (req.url === "/rest/routing/table" || req.url === "/rest/routing/rule") { res.setHeader("content-type", "application/json") res.end(JSON.stringify(req.url.endsWith("/rule") ? fixtureRoutingRules : [{ ".id": "*main", name: "main", fib: "true" }, { ".id": "*table", name: "fixture-table", fib: "" }])); return } if (req.url === "/rest/routing/ospf/neighbor" || req.url === "/rest/ip/route") { res.setHeader("content-type", "application/json") if (healthUnavailable) { res.statusCode = 503; res.end(JSON.stringify({ error: "fixture unavailable" })); return } res.end(JSON.stringify(req.url.endsWith("/neighbor") ? [{ ".id": "*neighbor", interface: "fixture-ospf", "router-id": "192.0.2.2", state: healthState ? "Full" : "Down" }] : [{ ".id": "*healthroute", "dst-address": "192.0.2.0/24", "routing-table": fixtureRouteTable, active: healthState ? "true" : "false" }])); return } if (req.url === "/rest/interface/bridge" || req.url === "/rest/interface/bridge/*fixture") { res.setHeader("content-type", "application/json") if (req.method === "PATCH") { let raw = ""; req.on("data", chunk => { raw += chunk }); req.on("end", () => { bridge = { ...bridge, ...JSON.parse(raw) }; res.end(JSON.stringify(bridge)) }); return } res.end(JSON.stringify([bridge])); return } if (req.url === "/rest/system/license") { res.setHeader("content-type", "application/json"); res.end(JSON.stringify({ "system-id": "fixture-primary" })); return } if (req.url !== "/rest/ip/settings") { res.setHeader("content-type", "application/json"); res.end("[]"); return } if (req.method === "GET") { reconcileSettingsReads++ if (reconcileReadHook) { const hook = reconcileReadHook; reconcileReadHook = undefined void hook().then(() => { res.setHeader("content-type", "application/json"); res.end(JSON.stringify(settings)) }, () => { res.statusCode = 503; res.end("{}"); }) return } } if (req.method === "PATCH") { let body = ""; req.on("data", value => { body += value }); req.on("end", () => { settings = { ...settings, ...JSON.parse(body) }; writes++ if (failHealthAfterWrite) healthState = false if (loseHealthResponse) healthUnavailable = true res.setHeader("content-type", "application/json"); res.end(JSON.stringify(settings)) }); return } res.setHeader("content-type", "application/json"); res.end(JSON.stringify(settings)) }) await new Promise(resolve => router.listen(0, "127.0.0.1", resolve)) const port = (router.address() as { port: number }).port let app: Awaited> | undefined try { const { configurationRows, resourceById } = await import("./resources.js") const tableSpec = resourceById("routing_table") assert.equal(configurationRows(tableSpec, [{ ".id": "*1", name: "isolated", fib: "" }])[0].fib, "false") assert.equal(configurationRows(tableSpec, [{ ".id": "*1", name: "main", fib: "true" }])[0].fib, "true") assert.equal(configurationRows(tableSpec, [{ ".id": "*1", name: "unknown" }])[0].fib, undefined) assert.equal(configurationRows(resourceById("bridge"), [{ disabled: "" }])[0].disabled, "") assert.deepEqual(configurationRows(resourceById("ipv6_address"), [{ ".id": "*auto", address: "fe80::1/64", dynamic: "true" }, { ".id": "*explicit", address: "fe80::2/64", dynamic: "false" }, { ".id": "*global", address: "fd00::1/64", dynamic: "true" }]).map(row => row[".id"]), ["*explicit", "*global"]) const orderedVrfs = [{ ".id": "*a", name: "customer", interfaces: "ether2" }, { ".id": "*b", name: "main", interfaces: "all" }] assert.deepEqual(configurationRows(resourceById("vrf"), [{ ".id": "*a", name: "customer", interfaces: ["ether3", "ether2"] }]), configurationRows(resourceById("vrf"), [{ ".id": "*a", name: "customer", interfaces: "ether2,ether3" }])) assert.notDeepEqual(configurationRows(resourceById("vrf"), orderedVrfs), configurationRows(resourceById("vrf"), [...orderedVrfs].reverse())) await applySqlMigrations(pool) await applySqlMigrations(pool) console.log("✓ All migrations apply and are repeatable on PostgreSQL 18") await (await import("../../test/partition-timezones.js")).verifyPartitionTimezones(pool) // refreshScheduler starts enabled jobs immediately. Keep external GeoIP // downloads disabled in this owned fixture before any telemetry refresh. await pool.query("INSERT INTO geoip_settings(id,enabled) VALUES(1,false) ON CONFLICT(id) DO UPDATE SET enabled=false") await pool.query("INSERT INTO servers(id,name,host,port,use_ssl) VALUES(1,'test-router','127.0.0.1',$1,false),(2,'outside-scope','127.0.0.1',$1,false)", [port]) await updateMcpSettings({ enabled: true }, actor) const { registerManagementChannel, verifyManagementChannel } = await import("./management.js") let alternateIdentity = "fixture-primary" const alternate = createServer((_req, res) => { res.setHeader("content-type", "application/json"); res.end(JSON.stringify({ "system-id": alternateIdentity })) }) await new Promise(resolve => alternate.listen(0, "127.0.0.2", resolve)) try { const management = { serverId: 1, host: "127.0.0.2", port: (alternate.address() as { port: number }).port, useSsl: false, verifySsl: true, username: "fixture", password: "fixture", attestation: "Изолированный тестовый канал с отдельным адресом loopback для проверки границы допуска", expiresAt: new Date(Date.now() + 1800000).toISOString() } await assert.rejects(registerManagementChannel({ ...management, host: "127.0.0.1" }, actor)) alternateIdentity = "different-device"; await assert.rejects(registerManagementChannel(management, actor)) alternateIdentity = "fixture-primary" const channel = await registerManagementChannel(management, actor) assert.equal((await verifyManagementChannel(1, channel.id)).channelId, channel.id) await pool.query("UPDATE mcp_management_channels SET revoked_at=now() WHERE id=$1", [channel.id]) await assert.rejects(verifyManagementChannel(1, channel.id)) console.log("✓ Independent channel rejects primary aliases and different devices; identity verification and revocation work") } finally { await new Promise(resolve => alternate.close(() => resolve())) } const input = mcpConnectionCreateSchema.parse({ name: "Integration", mode: "approval", expiresAt: new Date(fixtureAuthExpiresAt).toISOString(), permissions: ["mm:network:write", "mm:servers:read"], scope: { serverIds: [1], operationIds: ["ros.ip_settings.list", "ros.ip_settings.update", "servers.list", "operations.recover", "operations.reconcile"], allowGlobal: false } }) const issued = await createMcpConnection(input, actor) const principal = await authenticateMcpToken(issued.token) const credentials = await pool.query("SELECT digest FROM mcp_credentials WHERE connection_id=$1", [issued.connection.id]) assert.equal(credentials.rows[0].digest, tokenDigest(issued.token)); assert.notEqual(credentials.rows[0].digest, issued.token) const read = await invokeMcpTool(principal, "mm_ip_settings_list", { serverId: 1 }) as { items: unknown[] } assert.equal(read.items.length, 1) await assert.rejects(invokeMcpTool(principal, "mm_ip_settings_list", { serverId: 2 })) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_requests WHERE status='denied'")).rows[0].n, 1) console.log("✓ Opaque credentials, explicit server scope, denied-call audit") const wanAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped WAN runtime", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:servers:read"], scope: { serverIds: [1], operationIds: ["servers.wan-runtime.read"], allowGlobal: false } }), actor) const wanPrincipal = await authenticateMcpToken(wanAccess.token) await pool.query("UPDATE servers SET wan_uplinks=$1::jsonb WHERE id=1", [JSON.stringify([{ id: "wan", iface: "ether1", name: "Fixture WAN", isp: "Fixture ISP", ip: "192.0.2.10" }])]) wanFixtures = true; countCatalogCalls = true; catalogCalls = 0 try { type WanState = { defaultRouteStatus: string; defaultInterface: string | null; forwardingVerified: boolean; uplinks: { leasedIp: string | null; defaultKnown: boolean }[] } const state = await invokeMcpTool(wanPrincipal, "mm_server_wan_runtime_get", { serverId: 1 }) as WanState assert.equal(state.defaultRouteStatus, "confirmed"); assert.equal(state.defaultInterface, "ether1") assert.equal(state.uplinks[0].leasedIp, "192.0.2.10"); assert.equal(state.forwardingVerified, false) assert.equal(catalogCalls, 3); assert.ok(!JSON.stringify(state).includes("never-return-wan-key")) await assert.rejects(invokeMcpTool(wanPrincipal, "mm_server_wan_runtime_get", { serverId: 2 })); assert.equal(catalogCalls, 3) await pool.query("UPDATE servers SET enabled=false WHERE id=1") await assert.rejects(invokeMcpTool(wanPrincipal, "mm_server_wan_runtime_get", { serverId: 1 })); assert.equal(catalogCalls, 3) await pool.query("UPDATE servers SET enabled=true WHERE id=1") wanRoutes = [wanDefault, { ...wanDefault, ".id": "*wan2", gateway: "198.51.100.1", "immediate-gw": "198.51.100.1%ether2" }] const ambiguous = await invokeMcpTool(wanPrincipal, "mm_server_wan_runtime_get", { serverId: 1 }) as WanState assert.equal(ambiguous.defaultRouteStatus, "ambiguous"); assert.equal(ambiguous.defaultInterface, null); assert.equal(ambiguous.uplinks[0].defaultKnown, false) wanAddressFailure = true await assert.rejects(invokeMcpTool(wanPrincipal, "mm_server_wan_runtime_get", { serverId: 1 })) } finally { wanFixtures = false; wanAddressFailure = false; countCatalogCalls = false await pool.query("UPDATE servers SET enabled=true,wan_uplinks='[]'::jsonb WHERE id=1") } console.log("✓ WAN runtime: only selected enabled server, native default/lease, explicit ECMP ambiguity, no credential echo or forwarding claim, failed source is not fabricated empty") const historySections = ["filters", "recursive-routes", "firewall", "wireguard", "ipsec", "gre"] const historyAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped configuration history", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read", "mm:filters:read"], scope: { serverIds: [1], operationIds: historySections.map(section => `config.${section}.revisions.read`), allowGlobal: false } }), actor) const historyPrincipal = await authenticateMcpToken(historyAccess.token) const historyIds: string[] = [] for (const section of historySections) { for (const sid of [1, 2]) { const id = randomUUID(); historyIds.push(id) await pool.query("INSERT INTO config_revisions(id,server_id,section,source,fingerprint,payload,note) VALUES($1,$2,$3,'observed',$4,$5::jsonb,$6)", [id, sid, section, id, JSON.stringify([{ privateKey: "never-return-this-payload", secret: "fixture" }]), `fixture-${sid}-${section}`]) } const tool = `mm_${section.replaceAll("-", "_")}_revisions_get` const result = await invokeMcpTool(historyPrincipal, tool, { serverId: 1 }) as { revisions: { serverId: string; section: string; itemCount: number }[]; retentionLimit: number } assert.equal(result.revisions.length, 1); assert.equal(result.retentionLimit, 50) assert.equal(result.revisions[0].serverId, "1"); assert.equal(result.revisions[0].section, section); assert.equal(result.revisions[0].itemCount, 1) assert.ok(!JSON.stringify(result).includes("never-return-this-payload")) await assert.rejects(invokeMcpTool(historyPrincipal, tool, { serverId: 2 })) await assert.rejects(invokeMcpTool(historyPrincipal, tool, { serverId: 1, section: "wireguard" })) } const historyDenied = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "History permission boundary", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read"], scope: { serverIds: [1], operationIds: ["config.filters.revisions.read"], allowGlobal: false } }), actor) await assert.rejects(invokeMcpTool(await authenticateMcpToken(historyDenied.token), "mm_filters_revisions_get", { serverId: 1 })) for (let index = 0; index < 51; index++) { const id = randomUUID(); historyIds.push(id) await pool.query("INSERT INTO config_revisions(id,server_id,section,source,fingerprint,payload) VALUES($1,1,'gre','observed',$1,'[]'::jsonb)", [id]) } const boundedHistory = await invokeMcpTool(historyPrincipal, "mm_gre_revisions_get", { serverId: 1 }) as { revisions: unknown[]; incomplete: boolean } assert.equal(boundedHistory.revisions.length, 50); assert.equal(boundedHistory.incomplete, true) await pool.query("DELETE FROM config_revisions WHERE id=ANY($1::text[])", [historyIds]) console.log("✓ Six configuration histories share REST metadata service, bound results, reject hidden servers and never expose saved secret payloads") const greCatalogAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped GRE catalog", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:filters:read"], scope: { serverIds: [1], operationIds: ["routing.gre.catalog.read"], allowGlobal: false } }), actor) const greCatalogPrincipal = await authenticateMcpToken(greCatalogAccess.token) greCatalogFixtures = true; countCatalogCalls = true; catalogCalls = 0 const greCatalog = await invokeMcpTool(greCatalogPrincipal, "mm_gre_catalog_get", { limit: 1 }) as { serverIds: number[]; tunnels: { serverId: string }[]; total: number; nextOffset: number; failures: unknown[] } assert.deepEqual(greCatalog.serverIds, [1]); assert.equal(greCatalog.total, 2); assert.equal(greCatalog.tunnels.length, 1); assert.equal(greCatalog.nextOffset, 1); assert.equal(greCatalog.tunnels[0].serverId, "1"); assert.equal(greCatalog.failures.length, 0); assert.equal(catalogCalls, 2) assert.ok(!JSON.stringify(greCatalog).includes("never-return-gre-key")) await assert.rejects(invokeMcpTool(greCatalogPrincipal, "mm_gre_catalog_get", { serverId: 2 })); assert.equal(catalogCalls, 2) const greNext = await invokeMcpTool(greCatalogPrincipal, "mm_gre_catalog_get", { offset: 1, limit: 1 }) as { tunnels: { name: string }[]; nextOffset: null } assert.equal(greNext.tunnels[0].name, "catalog-gre-2"); assert.equal(greNext.nextOffset, null) healthUnavailable = true const failedGre = await invokeMcpTool(greCatalogPrincipal, "mm_gre_catalog_get", {}) as { failures: { serverId: string }[]; incomplete: boolean } assert.equal(failedGre.failures.length, 1); assert.equal(failedGre.failures[0].serverId, "1"); assert.equal(failedGre.incomplete, true) greCatalogFixtures = false; countCatalogCalls = false; healthUnavailable = false console.log("✓ GRE routing catalog shares REST polling/parser, scopes before polling, paginates, strips IPsec keys and preserves device failures") await pool.query("INSERT INTO servers(id,name,host,port,use_ssl) VALUES(43,'filter-catalog-owner','127.0.0.1',$1,false),(44,'NEVER_FILTER_FOREIGN','192.0.2.44',1,false)",[port]) await pool.query("INSERT INTO filter_rules(server_id,community,description) VALUES(43,'64512:99','own cached hint'),(44,'64512:100','NEVER_FILTER_CACHED')") const filterAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({name:"Scoped BGP filters",mode:"observer",expiresAt:new Date(Date.now()+1800000).toISOString(),permissions:["mm:filters:read"],scope:{serverIds:[43],operationIds:["routing.filters.catalog.read"],allowGlobal:false}}),actor) const filterPrincipal = await authenticateMcpToken(filterAccess.token) filterCatalogFixtures = true; countCatalogCalls = true; catalogCalls = 0 const { default: FilterFastify } = await import("fastify"), {validatorCompiler: filterValidator,serializerCompiler: filterSerializer} = await import("@fastify/type-provider-zod") const filterHttp = FilterFastify(); filterHttp.setValidatorCompiler(filterValidator); filterHttp.setSerializerCompiler(filterSerializer); await filterHttp.register((await import("../../routes/filters.js")).default,{prefix:"/api"}) type FilterPage = {rulesets:{serverId:string;live:boolean;stale:boolean;source:string;error:string|null;rules:{items:{community:string}[];total:number};nativeRules:{items:{rosId:string;enabled:boolean|null}[];total:number}|null}[];incomplete:boolean;writesPerformed:boolean} try { const beforeCache = (await pool.query("SELECT * FROM filter_rules WHERE server_id=43")).rows const fresh = await invokeMcpTool(filterPrincipal,"mm_bgp_filter_rules_get",{limit:1}) as FilterPage assert.equal(catalogCalls,2); assert.equal(fresh.rulesets[0].serverId,"43"); assert.equal(fresh.rulesets[0].live,true); assert.equal(fresh.rulesets[0].rules.items[0].community,"64512:1") assert.equal(fresh.rulesets[0].nativeRules!.total,2); assert.equal(fresh.rulesets[0].nativeRules!.items[0].enabled,false); assert.equal(fresh.writesPerformed,false); assert.equal(fresh.incomplete,true) assert.ok(!JSON.stringify(fresh).includes("NEVER_FILTER")); assert.deepEqual((await pool.query("SELECT * FROM filter_rules WHERE server_id=43")).rows,beforeCache) assert.equal((await pool.query("SELECT count(*)::int AS n FROM config_revisions WHERE server_id=43")).rows[0].n,0) const second = await invokeMcpTool(filterPrincipal,"mm_bgp_filter_rules_get",{offset:1,limit:1}) as FilterPage assert.equal(second.rulesets[0].nativeRules!.items[0].rosId,"*F2"); assert.equal(second.rulesets[0].nativeRules!.items[0].enabled,null) const noPoll = catalogCalls await assert.rejects(invokeMcpTool(filterPrincipal,"mm_bgp_filter_rules_get",{serverId:44})); assert.equal(catalogCalls,noPoll) const cache = await invokeMcpTool(filterPrincipal,"mm_bgp_filter_rules_get",{source:"cache"}) as FilterPage assert.equal(catalogCalls,noPoll); assert.equal(cache.rulesets[0].rules.items[0].community,"64512:99"); assert.equal(cache.rulesets[0].nativeRules,null) assert.ok(!JSON.stringify(cache).includes("NEVER_FILTER")) const rest = await filterHttp.inject({method:"GET",url:"/api/filters/rules?serverId=43"}); assert.equal(rest.statusCode,200,rest.body); assert.equal(rest.json().live,true) assert.deepEqual(rest.json().rulesets[0].rules.map((row:{community:string})=>row.community),fresh.rulesets[0].rules.items.map(row=>row.community)) assert.equal((await pool.query("SELECT count(*)::int AS n FROM config_revisions WHERE server_id=43")).rows[0].n,1) filterCatalogMalformed = true const stale = await invokeMcpTool(filterPrincipal,"mm_bgp_filter_rules_get",{}) as FilterPage assert.equal(stale.rulesets[0].live,false); assert.equal(stale.rulesets[0].stale,true); assert.equal(stale.rulesets[0].source,"cache"); assert.equal(stale.rulesets[0].nativeRules,null); assert.ok(stale.rulesets[0].error) assert.equal(stale.rulesets[0].rules.items[0].community,"64512:1"); assert.equal(stale.incomplete,true) const failedRest = await filterHttp.inject({method:"GET",url:"/api/filters/rules?serverId=43"}); assert.equal(failedRest.json().stale,true) await pool.query("UPDATE servers SET enabled=false WHERE id=43") const beforeDisabled = catalogCalls const disabled = await invokeMcpTool(filterPrincipal,"mm_bgp_filter_rules_get",{source:"cache"}) as FilterPage assert.equal(disabled.rulesets[0].rules.items[0].community,"64512:1"); assert.equal(catalogCalls,beforeDisabled) assert.equal((await filterHttp.inject({method:"GET",url:"/api/filters/rules?serverId=43"})).json().stale,true); assert.equal(catalogCalls,beforeDisabled) for(const id of ["","bad","0","1abc"]) assert.equal((await filterHttp.inject({method:"GET",url:"/api/filters/rules?serverId="+id})).statusCode,400) assert.equal(catalogCalls,beforeDisabled) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_requests WHERE connection_id=$1 AND result::text LIKE '%NEVER_FILTER%'",[filterAccess.connection.id])).rows[0].n,0) } finally { filterCatalogFixtures = false; filterCatalogMalformed = false; countCatalogCalls = false; await filterHttp.close(); await pool.query("DELETE FROM servers WHERE id IN(43,44)") } console.log("✓ BGP filters: shared REST/MCP live/cache selection, strict native identity and relative order, unknown/disabled rows, no observer cache/revision writes, scoped fallback, hidden cache/keys, disabled no-poll and malformed GET rejection") const recursiveAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped recursive catalogs", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read"], scope: { serverIds: [1], operationIds: ["routing.recursive.catalog.read", "routing.recursive.gateways.catalog.read", "routing.recursive.routes.read", "routing.recursive.gateways.read"], allowGlobal: false } }), actor) const recursivePrincipal = await authenticateMcpToken(recursiveAccess.token) const recursiveFixture = { ".id": "*rec1", static: "true", "dst-address": "192.0.2.0/24", gateway: "192.0.2.1%fixture-gre", distance: "0", scope: "0", "target-scope": "0", "routing-table": "main", comment: "MikrotikManager:recursive fixture" } recursiveCatalogRows = [recursiveFixture, { ...recursiveFixture, ".id": "*rec2", gateway: "192.0.2.2%fixture-gre", disabled: "true", "check-gateway": "ping", active: "false" }] recursiveCatalogFixtures = true; countCatalogCalls = true; catalogCalls = 0 await pool.query("INSERT INTO recursive_routes(server_id,sort_order,dst_address,gateway,distance,scope,target_scope,routing_table,check_gateway,country,comment,disabled) VALUES(1,0,'192.0.2.0/24','192.0.2.1%fixture-gre',0,0,0,'main','none','Fixture country','fixture',false)") const recursiveBeforeObservation = (await pool.query("SELECT * FROM recursive_routes WHERE server_id=1 ORDER BY id")).rows const recursiveRevisionsBefore = (await pool.query("SELECT * FROM config_revisions WHERE server_id=1 AND section='recursive-routes' ORDER BY id")).rows const recursiveCatalog = await invokeMcpTool(recursivePrincipal, "mm_recursive_routes_catalog_get", { serverId: 1, limit: 1 }) as { routes: { distance: number; scope: number; disabled: boolean; checkGateway: string; country: string }[]; total: number; nextOffset: number; live: boolean; stale: boolean } assert.equal(recursiveCatalog.total, 2); assert.equal(recursiveCatalog.nextOffset, 1); assert.equal(recursiveCatalog.routes.length, 1) assert.equal(recursiveCatalog.live, true); assert.equal(recursiveCatalog.stale, false) assert.deepEqual(recursiveCatalog.routes[0], { id: "*rec1", dstAddress: "192.0.2.0/24", gateway: "192.0.2.1%fixture-gre", distance: 0, scope: 0, targetScope: 0, routingTable: "main", checkGateway: "none", country: "Fixture country", comment: "fixture", disabled: false }) assert.equal(catalogCalls, 3) await assert.rejects(invokeMcpTool(recursivePrincipal, "mm_recursive_routes_catalog_get", { serverId: 2 })); assert.equal(catalogCalls, 3) const recursiveBusinessRead = await invokeMcpTool(recursivePrincipal, "mm_recursive_routes_get", { serverId: 1, limit: 1 }) as { routes: { id: string }[]; total: number; live: boolean; forwardingVerified: boolean } assert.equal(recursiveBusinessRead.routes[0].id, String(recursiveBeforeObservation[0].id)); assert.equal(recursiveBusinessRead.total, 2); assert.equal(recursiveBusinessRead.live, true); assert.equal(recursiveBusinessRead.forwardingVerified, false) recursivePropertyFailure = true await assert.rejects(invokeMcpTool(recursivePrincipal, "mm_recursive_routes_catalog_get", { serverId: 1 })) recursivePropertyFailure = false recursiveCatalogRows = [{ ...recursiveFixture, blackhole: "true" }] await assert.rejects(invokeMcpTool(recursivePrincipal, "mm_recursive_routes_catalog_get", { serverId: 1 })) const recursiveFallback = await invokeMcpTool(recursivePrincipal, "mm_recursive_routes_get", { serverId: 1 }) as { routes: { id: string }[]; live: boolean; stale: boolean; incomplete: boolean } assert.equal(recursiveFallback.live, false); assert.equal(recursiveFallback.stale, true); assert.equal(recursiveFallback.incomplete, true); assert.equal(recursiveFallback.routes[0].id, String(recursiveBeforeObservation[0].id)) assert.deepEqual((await pool.query("SELECT * FROM config_revisions WHERE server_id=1 AND section='recursive-routes' ORDER BY id")).rows, recursiveRevisionsBefore) assert.deepEqual((await pool.query("SELECT * FROM recursive_routes WHERE server_id=1 ORDER BY id")).rows, recursiveBeforeObservation) recursiveCatalogRows = [{ ".id": "*gw1", gateway: "192.0.2.1%fixture-gre" }, { ".id": "*gw2", gateway: "192.0.2.2%fixture-gre", active: "false" }, { ".id": "*gw3", gateway: "192.0.2.3%fixture-gre", active: "true" }] const gatewayCatalog = await invokeMcpTool(recursivePrincipal, "mm_recursive_gateways_catalog_get", { serverId: 1, limit: 2 }) as { gateways: { status: string }[]; total: number; nextOffset: number } assert.deepEqual(gatewayCatalog.gateways.map(row => row.status), ["unknown", "inactive"]); assert.equal(gatewayCatalog.total, 3); assert.equal(gatewayCatalog.nextOffset, 2) const gatewayLast = await invokeMcpTool(recursivePrincipal, "mm_recursive_gateways_catalog_get", { serverId: 1, offset: 2, limit: 2 }) as { gateways: { status: string }[]; nextOffset: null } assert.equal(gatewayLast.gateways[0].status, "active"); assert.equal(gatewayLast.nextOffset, null) const legacyGateways = await invokeMcpTool(recursivePrincipal, "mm_recursive_gateways_get", { serverId: 1 }) as { gateways: { status: string }[]; forwardingVerified: boolean } assert.deepEqual(legacyGateways.gateways.map(row => row.status), ["unknown", "down", "up"]); assert.equal(legacyGateways.forwardingVerified, false) const beforeDeniedGateway = catalogCalls await assert.rejects(invokeMcpTool(recursivePrincipal, "mm_recursive_gateways_catalog_get", { serverId: 2 })); assert.equal(catalogCalls, beforeDeniedGateway) for (const name of ["mm_recursive_routes_get", "mm_recursive_gateways_get"]) await assert.rejects(invokeMcpTool(recursivePrincipal, name, { serverId: 2 })) assert.equal(catalogCalls, beforeDeniedGateway) healthUnavailable = true await assert.rejects(invokeMcpTool(recursivePrincipal, "mm_recursive_gateways_catalog_get", { serverId: 1 })) healthUnavailable = false; recursiveCatalogRows = [] assert.equal((await invokeMcpTool(recursivePrincipal, "mm_recursive_routes_catalog_get", { serverId: 1 }) as { total: number }).total, 0) assert.deepEqual((await pool.query("SELECT * FROM recursive_routes WHERE server_id=1 ORDER BY id")).rows, recursiveBeforeObservation) recursiveCatalogFixtures = false; countCatalogCalls = false const { storeRecursiveRoutes } = await import("../../services/recursive-route-store.js") const { readCachedRecursiveRoutes, applyRecursiveToServer } = await import("../../routes/recursive-routes.js") const cachedRecursive = await readCachedRecursiveRoutes(1), recursiveId = cachedRecursive[0].id await storeRecursiveRoutes(1, cachedRecursive, "observed") assert.deepEqual((await pool.query("SELECT * FROM recursive_routes WHERE server_id=1 ORDER BY id")).rows, recursiveBeforeObservation) const binding = (await pool.query("INSERT INTO filter_rules(server_id,community,gateway_tunnel_id) VALUES(1,'65000:997',$1) RETURNING id", ["rec:" + recursiveId])).rows[0].id try { const changed = [{ ...cachedRecursive[0], id: "*RECREATED", gateway: "192.0.2.99%fixture-gre", distance: 7, country: "" }] assert.equal((await storeRecursiveRoutes(1, changed, "observed"))[0].id, recursiveId) assert.equal((await readCachedRecursiveRoutes(1))[0].country, "Fixture country") for (const rows of [[], [{ ...cachedRecursive[0], dstAddress: "198.51.100.0/24" }], [{ ...cachedRecursive[0], routingTable: "other" }]]) { const beforeWrites = writes // Invalid metadata is rejected before constructing the RouterOS client. await assert.rejects(applyRecursiveToServer({ id: 1 } as Parameters[0], rows, "apply"), /BGP/) assert.equal(writes, beforeWrites) } const beforeForeign = (await pool.query("SELECT * FROM recursive_routes WHERE server_id=1")).rows await assert.rejects(storeRecursiveRoutes(2, [{ ...cachedRecursive[0] }], "apply"), /сервере/) assert.deepEqual((await pool.query("SELECT * FROM recursive_routes WHERE server_id=1")).rows, beforeForeign) assert.equal((await pool.query("SELECT gateway_tunnel_id FROM filter_rules WHERE id=$1", [binding])).rows[0].gateway_tunnel_id, "rec:" + recursiveId) await storeRecursiveRoutes(1, [{ ...cachedRecursive[0], country: "" }], "apply") assert.equal((await readCachedRecursiveRoutes(1))[0].country, "") } finally { await pool.query("DELETE FROM filter_rules WHERE id=$1", [binding]) await pool.query("DELETE FROM recursive_routes WHERE server_id=1") } await pool.query("DELETE FROM config_revisions WHERE server_id=1 AND section='recursive-routes'") console.log("✓ Recursive shared readers: observer has no cache/revision writes, scoped stale fallback and unknown gateway state; SQL stable IDs, no-op timestamps, country ownership, rec bindings and pre-native referenced-prefix rejection") const { fingerprintPayload: revisionFingerprint } = await import("../../services/config-revisions.js") const recursiveRevisionIds: string[] = [] const addRecursiveRevision = async (payload: unknown, sid = 1, section = "recursive-routes") => { const id = randomUUID(); recursiveRevisionIds.push(id) await pool.query("INSERT INTO config_revisions(id,server_id,section,source,fingerprint,payload) VALUES($1,$2,$3,'apply',$4,$5::jsonb)", [id, sid, section, revisionFingerprint(payload), JSON.stringify(payload)]) return id } const restorePayload = [{ id: "historical-id", dstAddress: "192.0.2.0/24", gateway: "192.0.2.2", distance: 0, scope: 0, targetScope: 0, routingTable: "main", checkGateway: "none", country: "fixture-country", comment: "restore-fixture", disabled: false }] const restoreRevisionId = await addRecursiveRevision(restorePayload) const recursiveRestoreAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Recursive revision restore", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], limits: { callsPerMinute: 120, concurrentReads: 4, mutationsPerHour: 50, serversPerPlan: 2, objectsPerPlan: 100 }, scope: { serverIds: [1], operationIds: ["routing.recursive.revision.restore"], allowGlobal: false } }), actor) const recursiveRestorePrincipal = await authenticateMcpToken(recursiveRestoreAccess.token) const restoreRevisionArgs = { serverId: 1, revisionId: restoreRevisionId } const foreignRecursive = { ".id": "*foreign-recursive", "dst-address": "0.0.0.0/0", gateway: "192.0.2.254", distance: "1", static: "true", comment: "Keep foreign default" } recursiveCatalogRows = [{ ".id": "*old-recursive", "dst-address": "192.0.2.0/24", gateway: "192.0.2.1", distance: "1", scope: "30", "target-scope": "10", "routing-table": "main", static: "true", comment: "MikrotikManager:recursive old" }, { ...foreignRecursive }] recursiveCatalogFixtures = true; recursiveMutations = true; countCatalogCalls = true; catalogCalls = 0 for (const invalidRevision of [await addRecursiveRevision(restorePayload, 2), await addRecursiveRevision(restorePayload, 1, "gre"), await addRecursiveRevision([{}]), await addRecursiveRevision(Array.from({ length: 51 }, (_, index) => ({ ...restorePayload[0], id: String(index) })))]) { await assert.rejects(invokeMcpTool(recursiveRestorePrincipal, "mm_recursive_revision_restore_plan", { ...restoreRevisionArgs, revisionId: invalidRevision })) } assert.equal(catalogCalls, 0) await pool.query("UPDATE config_revisions SET fingerprint='corrupt' WHERE id=$1", [restoreRevisionId]) await assert.rejects(invokeMcpTool(recursiveRestorePrincipal, "mm_recursive_revision_restore_plan", restoreRevisionArgs)); assert.equal(catalogCalls, 0) await pool.query("UPDATE config_revisions SET fingerprint=$2 WHERE id=$1", [restoreRevisionId, revisionFingerprint(restorePayload)]) await assert.rejects(invokeMcpTool(recursiveRestorePrincipal, "mm_recursive_revision_restore_plan", { ...restoreRevisionArgs, serverId: 2 })); assert.equal(catalogCalls, 0) const tableRestoreProtection = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,'integration')", [tableRestoreProtection, JSON.stringify({ kind: "routing_table", id: "*main", serverId: 1, protection: "deny" })]) await assert.rejects(invokeMcpTool(recursiveRestorePrincipal, "mm_recursive_revision_restore_plan", restoreRevisionArgs)); assert.equal(recursiveMutationCount, 0) await pool.query("DELETE FROM mcp_protections WHERE id=$1", [tableRestoreProtection]) const lowBudgetRestore = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Recursive batch budget", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], limits: { callsPerMinute: 120, concurrentReads: 4, mutationsPerHour: 2, serversPerPlan: 2, objectsPerPlan: 100 }, scope: { serverIds: [1], operationIds: ["routing.recursive.revision.restore"], allowGlobal: false } }), actor) const lowBudgetPrincipal = await authenticateMcpToken(lowBudgetRestore.token) await assert.rejects(invokeMcpTool(lowBudgetPrincipal, "mm_recursive_revision_restore_plan", restoreRevisionArgs), error => error instanceof Error && "code" in error && error.code === "BUDGET_EXCEEDED") assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_plans WHERE connection_id=$1", [lowBudgetRestore.connection.id])).rows[0].n, 0) const exactBudgetRestore = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Recursive exact budget", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], limits: { mutationsPerHour: 3 }, scope: { serverIds: [1], operationIds: ["routing.recursive.revision.restore"], allowGlobal: false } }), actor) const exactBudgetPrincipal = await authenticateMcpToken(exactBudgetRestore.token) const lowBudgetPlan = await invokeMcpTool(exactBudgetPrincipal, "mm_recursive_revision_restore_plan", restoreRevisionArgs) as { planId: string; changes: { mutationCost: number } } assert.equal(lowBudgetPlan.changes.mutationCost, 3) await approvePlan(lowBudgetPlan.planId, actor, true) // Another accepted operation consumed part of the budget after preview. await pool.query("INSERT INTO mcp_budget_buckets(connection_id,kind,bucket,used) VALUES($1,'mutations',date_trunc('hour',now()),1)", [exactBudgetRestore.connection.id]) await assert.rejects(applyPlan(exactBudgetPrincipal, lowBudgetPlan.planId, "batch-budget-denied"), error => error instanceof Error && "code" in error && error.code === "BUDGET_EXCEEDED") assert.equal(recursiveMutationCount, 0) await pool.query("INSERT INTO recursive_routes(server_id,sort_order,dst_address,gateway,distance,scope,target_scope,routing_table,check_gateway,country,comment,disabled) VALUES(1,0,'192.0.2.0/24','192.0.2.1',1,30,10,'main','none','','old',false)") const boundRecursiveId = (await pool.query("SELECT id FROM recursive_routes WHERE server_id=1 LIMIT 1")).rows[0].id const bindRecursiveFilter = async () => { const result = await pool.query("INSERT INTO filter_rules(server_id,community,gateway_tunnel_id) VALUES(1,'65000:777',$1) RETURNING id", ["rec:" + boundRecursiveId]) return result.rows[0].id } const boundFilterId = await bindRecursiveFilter() await assert.rejects(invokeMcpTool(recursiveRestorePrincipal, "mm_recursive_revision_restore_plan", restoreRevisionArgs)) await pool.query("DELETE FROM filter_rules WHERE id=$1", [boundFilterId]) const lateBindingPlan = await invokeMcpTool(recursiveRestorePrincipal, "mm_recursive_revision_restore_plan", restoreRevisionArgs) as { planId: string } await approvePlan(lateBindingPlan.planId, actor, true) const lateFilterId = await bindRecursiveFilter() const waitRecursiveOperation = async (id: string) => { const deadline = Date.now() + 30000 while (Date.now() < deadline) { const state = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [id])).rows[0].state if (!["running", "pending"].includes(state)) return state await delay(30) } const state = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [id])).rows[0]?.state throw Error(`MCP fixture operation did not finish within 30 seconds (state=${state})`) } assert.equal(await waitRecursiveOperation((await applyPlan(recursiveRestorePrincipal, lateBindingPlan.planId, randomUUID())).id), "rejected") assert.equal(recursiveMutationCount, 0) await pool.query("DELETE FROM filter_rules WHERE id=$1", [lateFilterId]) const staleRevisionPlan = await invokeMcpTool(recursiveRestorePrincipal, "mm_recursive_revision_restore_plan", restoreRevisionArgs) as { planId: string } await approvePlan(staleRevisionPlan.planId, actor, true) const changedPayload = [{ ...restorePayload[0], distance: 20 }] await pool.query("UPDATE config_revisions SET payload=$2::jsonb,fingerprint=$3 WHERE id=$1", [restoreRevisionId, JSON.stringify(changedPayload), revisionFingerprint(changedPayload)]) assert.equal(await waitRecursiveOperation((await applyPlan(recursiveRestorePrincipal, staleRevisionPlan.planId, randomUUID())).id), "rejected") assert.equal(recursiveMutationCount, 0) await pool.query("UPDATE config_revisions SET payload=$2::jsonb,fingerprint=$3 WHERE id=$1", [restoreRevisionId, JSON.stringify(restorePayload), revisionFingerprint(restorePayload)]) const restoreRevisionPlan = await invokeMcpTool(recursiveRestorePrincipal, "mm_recursive_revision_restore_plan", restoreRevisionArgs) as { planId: string } await approvePlan(restoreRevisionPlan.planId, actor, true) const budgetBeforeRestore = (await pool.query("SELECT used FROM mcp_budget_buckets WHERE connection_id=$1 AND kind='mutations' AND bucket=date_trunc('hour',now())", [recursiveRestoreAccess.connection.id])).rows[0]?.used ?? 0 const restoredRevisionOperation = await applyPlan(recursiveRestorePrincipal, restoreRevisionPlan.planId, "restore-recursive-once") assert.equal(await waitRecursiveOperation(restoredRevisionOperation.id), "applied"); assert.equal(recursiveMutationCount, 2) assert.deepEqual(recursiveCatalogRows.find(row => row[".id"] === "*foreign-recursive"), foreignRecursive) assert.equal((await pool.query("SELECT distance,country FROM recursive_routes WHERE server_id=1")).rows[0].distance, 0) assert.equal((await pool.query("SELECT distance,country FROM recursive_routes WHERE server_id=1")).rows[0].country, "fixture-country") assert.equal((await pool.query("SELECT used FROM mcp_budget_buckets WHERE connection_id=$1 AND kind='mutations' AND bucket=date_trunc('hour',now())", [recursiveRestoreAccess.connection.id])).rows[0].used - budgetBeforeRestore, 3) await applyPlan(recursiveRestorePrincipal, restoreRevisionPlan.planId, "restore-recursive-once"); assert.equal(recursiveMutationCount, 2) recursiveIgnorePayload = true const mismatchedRestore = await invokeMcpTool(recursiveRestorePrincipal, "mm_recursive_revision_restore_plan", restoreRevisionArgs) as { planId: string } await approvePlan(mismatchedRestore.planId, actor, true) assert.equal(await waitRecursiveOperation((await applyPlan(recursiveRestorePrincipal, mismatchedRestore.planId, randomUUID())).id), "partial") recursiveIgnorePayload = false await pool.query("UPDATE recursive_routes SET country='fixture-before-restore' WHERE server_id=1") await pool.query("CREATE FUNCTION mcp_fixture_wrong_recursive_cache() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.country := 'unexpected-cache-value'; RETURN NEW; END $$") await pool.query("CREATE TRIGGER mcp_fixture_wrong_recursive_cache BEFORE INSERT OR UPDATE ON recursive_routes FOR EACH ROW EXECUTE FUNCTION mcp_fixture_wrong_recursive_cache()") try { const badCacheRestore = await invokeMcpTool(recursiveRestorePrincipal, "mm_recursive_revision_restore_plan", restoreRevisionArgs) as { planId: string } await approvePlan(badCacheRestore.planId, actor, true) assert.equal(await waitRecursiveOperation((await applyPlan(recursiveRestorePrincipal, badCacheRestore.planId, randomUUID())).id), "partial") assert.equal((await pool.query("SELECT country FROM recursive_routes WHERE server_id=1")).rows[0].country, "unexpected-cache-value") } finally { await pool.query("DROP TRIGGER mcp_fixture_wrong_recursive_cache ON recursive_routes") await pool.query("DROP FUNCTION mcp_fixture_wrong_recursive_cache()") } const revokedBatch = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Revoke inside recursive batch", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], scope: { serverIds: [1], operationIds: ["routing.recursive.revision.restore"], allowGlobal: false } }), actor) const revokedBatchPrincipal = await authenticateMcpToken(revokedBatch.token) const revokeInsidePlan = await invokeMcpTool(revokedBatchPrincipal, "mm_recursive_revision_restore_plan", restoreRevisionArgs) as { planId: string } await approvePlan(revokeInsidePlan.planId, actor, true) const beforeRevokedBatch = recursiveMutationCount recursivePauseConnection = revokedBatch.connection.id const revokedBatchOperation = await applyPlan(revokedBatchPrincipal, revokeInsidePlan.planId, randomUUID()) assert.equal(await waitRecursiveOperation(revokedBatchOperation.id), "unknown") assert.equal(recursiveMutationCount - beforeRevokedBatch, 1, "Revocation must stop DELETE inside the same service step after the first acknowledged PUT") assert.equal(recursiveCatalogRows.filter(row => String(row.comment).startsWith("MikrotikManager:recursive")).length, 2) await assert.rejects(applyPlan(revokedBatchPrincipal, revokeInsidePlan.planId, randomUUID())) assert.equal(recursiveMutationCount - beforeRevokedBatch, 1) const emptyRecursiveRevision = await addRecursiveRevision([]) const clearRecursivePlan = await invokeMcpTool(recursiveRestorePrincipal, "mm_recursive_revision_restore_plan", { ...restoreRevisionArgs, revisionId: emptyRecursiveRevision }) as { planId: string } await approvePlan(clearRecursivePlan.planId, actor, true) assert.equal(await waitRecursiveOperation((await applyPlan(recursiveRestorePrincipal, clearRecursivePlan.planId, randomUUID())).id), "applied") assert.deepEqual(recursiveCatalogRows, [foreignRecursive]); assert.equal((await pool.query("SELECT count(*)::int AS n FROM recursive_routes WHERE server_id=1")).rows[0].n, 0) const recursiveWriteAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Recursive full-set editor", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], limits: { callsPerMinute: 120, concurrentReads: 4, mutationsPerHour: 50, serversPerPlan: 2, objectsPerPlan: 100 }, scope: { serverIds: [1], operationIds: ["routing.recursive.routes.replace"], allowGlobal: false } }), actor) const recursiveWritePrincipal = await authenticateMcpToken(recursiveWriteAccess.token) const directRecursive = { ...restorePayload[0], id: "", country: "direct-country", disabled: true } for (const invalid of [{ serverId: 2, routes: [] }, { serverId: 1 }, { serverId: 1, routes: [{ ...directRecursive, distance: -1 }] }, { serverId: 1, routes: Array.from({ length: 51 }, () => directRecursive) }]) await assert.rejects(invokeMcpTool(recursiveWritePrincipal, "mm_recursive_routes_replace_plan", invalid)) const directCreate = await invokeMcpTool(recursiveWritePrincipal, "mm_recursive_routes_replace_plan", { serverId: 1, routes: [directRecursive] }) as { planId: string; changes: { mutationCost: number } } assert.equal(directCreate.changes.mutationCost, 2) await assert.rejects(applyPlan(recursiveWritePrincipal, directCreate.planId, randomUUID())) await approvePlan(directCreate.planId, actor, true) const directCreated = await applyPlan(recursiveWritePrincipal, directCreate.planId, "recursive-direct-once") assert.equal(await waitRecursiveOperation(directCreated.id), "applied") const directCreateCount = recursiveMutationCount await applyPlan(recursiveWritePrincipal, directCreate.planId, "recursive-direct-once"); assert.equal(recursiveMutationCount, directCreateCount) const directCached = (await readCachedRecursiveRoutes(1))[0] const directBinding = (await pool.query("INSERT INTO filter_rules(server_id,community,gateway_tunnel_id) VALUES(1,'65000:998',$1) RETURNING id", ["rec:" + directCached.id])).rows[0].id try { for (const routes of [[], [{ ...directCached, dstAddress: "198.51.100.0/24" }]]) await assert.rejects(invokeMcpTool(recursiveWritePrincipal, "mm_recursive_routes_replace_plan", { serverId: 1, routes }), /BGP/) const edited = { ...directCached, gateway: "192.0.2.3", distance: 2, country: "" } const editPlan = await invokeMcpTool(recursiveWritePrincipal, "mm_recursive_routes_replace_plan", { serverId: 1, routes: [edited] }) as { planId: string; changes: { mutationCost: number } } assert.equal(editPlan.changes.mutationCost, 3) await approvePlan(editPlan.planId, actor, true) assert.equal(await waitRecursiveOperation((await applyPlan(recursiveWritePrincipal, editPlan.planId, randomUUID())).id), "applied") assert.equal((await readCachedRecursiveRoutes(1))[0].id, directCached.id); assert.equal((await readCachedRecursiveRoutes(1))[0].country, "") assert.equal((await pool.query("SELECT gateway_tunnel_id FROM filter_rules WHERE id=$1", [directBinding])).rows[0].gateway_tunnel_id, "rec:" + directCached.id) const staleMetadataPlan = await invokeMcpTool(recursiveWritePrincipal, "mm_recursive_routes_replace_plan", { serverId: 1, routes: [edited] }) as { planId: string } await approvePlan(staleMetadataPlan.planId, actor, true) await pool.query("UPDATE recursive_routes SET country='late-metadata' WHERE id=$1", [directCached.id]) const beforeStale = recursiveMutationCount assert.equal(await waitRecursiveOperation((await applyPlan(recursiveWritePrincipal, staleMetadataPlan.planId, randomUUID())).id), "rejected") assert.equal(recursiveMutationCount, beforeStale) } finally { await pool.query("DELETE FROM filter_rules WHERE id=$1", [directBinding]) } const directClear = await invokeMcpTool(recursiveWritePrincipal, "mm_recursive_routes_replace_plan", { serverId: 1, routes: [], source: "copy" }) as { planId: string } await approvePlan(directClear.planId, actor, true) assert.equal(await waitRecursiveOperation((await applyPlan(recursiveWritePrincipal, directClear.planId, randomUUID())).id), "applied") assert.deepEqual(recursiveCatalogRows, [foreignRecursive]); assert.deepEqual(await readCachedRecursiveRoutes(1), []) console.log("✓ Recursive full-set editor: explicit human approval, complete batch cost, empty/create/edit/copy, stable BGP binding, country clear, sealed metadata drift, foreign route retention and no replay") recursiveCatalogFixtures = false; recursiveMutations = false; countCatalogCalls = false await pool.query("DELETE FROM config_revisions WHERE server_id=1 AND section='recursive-routes' OR id=ANY($1::text[])", [recursiveRevisionIds]) console.log("✓ Recursive revision restore seals scoped history/full owned set, blocks dependent BGP filters and drift, reserves all batch writes, preserves foreign routes, verifies native/cache/revision and supports explicit empty without replay") const backupJobActor: AuthUser = { ...actor, id: "backup-job-owner", permissions: ["mm:settings:admin", "mm:backups:read"] } const backupJobAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped backup progress", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:backups:read"], scope: { serverIds: [1], operationIds: ["backups.jobs.read"], allowGlobal: false } }), backupJobActor) const backupJobPrincipal = await authenticateMcpToken(backupJobAccess.token) const { createBackupsRoutes } = await import("../../routes/backups.js"), { databaseBackgroundWork } = await import("../../services/database-background-work.js"), { readBackupJob } = await import("../../services/backup-job-store.js") let releaseBackupJob!: () => void const waitBackupJob = new Promise(resolve => { releaseBackupJob = resolve }) const backupJobsHttp = FilterFastify(); backupJobsHttp.setValidatorCompiler(filterValidator); backupJobsHttp.setSerializerCompiler(filterSerializer) await backupJobsHttp.register(createBackupsRoutes({ listServers: async () => [{ id: 1 }, { id: 2 }], appendEvent: async () => {}, create: async id => { await waitBackupJob if (id === "2") throw Error("NEVER_BACKUP_JOB_FAILURE") return { id: randomUUID(), serverId: 1, serverName: "NEVER_BACKUP_JOB_SERVER", filename: "NEVER_BACKUP_JOB_FILE", sizeBytes: 0, createdAt: new Date().toISOString(), kind: "manual", storage: "both", s3Key: "NEVER_BACKUP_JOB_KEY", uploadError: "NEVER_BACKUP_JOB_UPLOAD", notes: "NEVER_BACKUP_JOB_NOTE" } } }), { prefix: "/api" }) let singleBackupJobId = "" const backupWritesBefore = writes try { const started = await backupJobsHttp.inject({ method: "POST", url: "/api/backups/create", payload: { serverIds: [1], notes: "NEVER_BACKUP_JOB_NOTES" } }) assert.equal(started.statusCode, 202, started.body); singleBackupJobId = started.json().jobId const pendingJob = await invokeMcpTool(backupJobPrincipal, "mm_backup_job_get", { jobId: singleBackupJobId }) as { completed: number; terminal: boolean; ephemeral: boolean } assert.equal(pendingJob.completed, 0); assert.equal(pendingJob.terminal, false); assert.equal(pendingJob.ephemeral, true) releaseBackupJob(); await databaseBackgroundWork.drain() const restJob = (await backupJobsHttp.inject({ method: "GET", url: "/api/backups/jobs/" + singleBackupJobId })).json() const finishedJob = await invokeMcpTool(backupJobPrincipal, "mm_backup_job_get", { jobId: singleBackupJobId, limit: 1 }) as { status: string; completed: number; hasUploadErrors: boolean; created: { items: { sizeBytes: number }[] } } assert.equal(finishedJob.status, restJob.status); assert.equal(finishedJob.completed, restJob.completed); assert.equal(finishedJob.hasUploadErrors, true); assert.equal(finishedJob.created.items[0].sizeBytes, 0) assert.equal(JSON.stringify(finishedJob).includes("NEVER_BACKUP_JOB"), false) const emptyPage = await invokeMcpTool(backupJobPrincipal, "mm_backup_job_get", { jobId: singleBackupJobId, offset: 1, limit: 1 }) as { created: { items: unknown[]; total: number } } assert.deepEqual(emptyPage.created.items, []); assert.equal(emptyPage.created.total, 1) const mixed = await backupJobsHttp.inject({ method: "POST", url: "/api/backups/create", payload: { serverIds: [1, 2] } }) assert.equal(mixed.statusCode, 202); await databaseBackgroundWork.drain() await assert.rejects(invokeMcpTool(backupJobPrincipal, "mm_backup_job_get", { jobId: mixed.json().jobId }), /не найдена/) await assert.rejects(invokeMcpTool(backupJobPrincipal, "mm_backup_job_get", { jobId: "missing-job" }), /не найдена/) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_requests WHERE connection_id=$1 AND result::text LIKE '%NEVER_BACKUP_JOB%'", [backupJobAccess.connection.id])).rows[0].n, 0) assert.equal(writes, backupWritesBefore) } finally { releaseBackupJob(); await databaseBackgroundWork.drain(); await backupJobsHttp.close() } assert.equal(readBackupJob(singleBackupJobId), undefined) console.log("✓ Backup jobs share the actual REST queue: scoped whole-job visibility, queued/running/done progress, zero/upload status, bounded pages, no note/path/error leakage in response/audit, no RouterOS requests and app-close invalidation") const storageActor: AuthUser={...actor,id:"backup-storage-owner",permissions:["mm:settings:admin","mm:backups:admin"]} const storageServerIds=(await pool.query("SELECT id FROM servers ORDER BY id")).rows.map(row=>Number(row.id)) const storagePolicy={name:"Backup storage settings",mode:"approval",expiresAt:new Date(Date.now()+1800000).toISOString(),permissions:["mm:backups:write"],limits:{callsPerMinute:120,concurrentReads:4,mutationsPerHour:50,serversPerPlan:10,objectsPerPlan:100},scope:{serverIds:storageServerIds,operationIds:["backups.storage.update"],allowGlobal:true}} const storageAccess=await createMcpConnection(mcpConnectionCreateSchema.parse(storagePolicy),storageActor),storagePrincipal=await authenticateMcpToken(storageAccess.token) const oldStorageRows=(await pool.query("SELECT * FROM backup_storage_settings WHERE id=1")).rows const storageSecretIds:string[]=[],storageEntryId=randomUUID(),storageProtection=randomUUID() const {backupStorageSecretPurpose}=await import("./backup-storage-actions.js"),{withBackupStorageOperation}=await import("../../services/backup-storage-operation.js") const storageSettings={provider:"s3" as const,s3Endpoint:"https://s3.example.test",s3Region:"us-east-1",s3Bucket:"backups",s3Prefix:"mcp-fixture",s3AccessKeyId:"fixture-access",s3ForcePathStyle:true,keepLocalCopy:true} const storageSecretValue="NEVER_STORAGE_SECRET_INITIAL" const addStorageSecret=async(purpose=backupStorageSecretPurpose(storageSettings),owner=storageActor.id,serverId:number|null=null,value=storageSecretValue)=>{ const id=randomUUID();storageSecretIds.push(id) await pool.query("INSERT INTO mcp_secrets(id,purpose,server_id,ciphertext,created_by) VALUES($1,$2,$3,$4,$5)",[id,purpose,serverId,await encryptSecret(value),owner]) return id } const storageWritesBefore=writes let releaseStorage:()=>void=()=>{},storagePending:Promise|undefined try { const secretRef=await addStorageSecret(),settingsArgs={settings:storageSettings,secretRef} const partialAccess=await createMcpConnection(mcpConnectionCreateSchema.parse({...storagePolicy,name:"Partial storage scope",scope:{...storagePolicy.scope,serverIds:[1]}}),storageActor) await assert.rejects(invokeMcpTool(await authenticateMcpToken(partialAccess.token),"mm_backup_storage_update_plan",settingsArgs),/вне разрешённой области/) for(const invalid of [{},{settings:{s3SecretAccessKey:"NEVER_STORAGE_LITERAL"}},{settings:{s3Endpoint:"https://user:password@s3.example.test"}},{settings:storageSettings,secretRef:await addStorageSecret("wrong-purpose")},{settings:storageSettings,secretRef:await addStorageSecret(undefined,"other-owner")},{settings:storageSettings,secretRef:await addStorageSecret(undefined,storageActor.id,1)}])await assert.rejects(invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",invalid)) const first=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",settingsArgs) as {planId:string} assert.equal(JSON.stringify(first).includes(storageSecretValue),false) await assert.rejects(applyPlan(storagePrincipal,first.planId,randomUUID())) assert.deepEqual((await pool.query("SELECT * FROM backup_storage_settings WHERE id=1")).rows,oldStorageRows) await approvePlan(first.planId,storageActor,true) const applied=await applyPlan(storagePrincipal,first.planId,"storage-config-once") assert.equal(await waitRecursiveOperation(applied.id),"applied") const saved=(await pool.query("SELECT * FROM backup_storage_settings WHERE id=1")).rows assert.equal(saved[0].s3_secret_access_key,storageSecretValue);assert.equal(saved[0].s3_endpoint,storageSettings.s3Endpoint);assert.equal(saved[0].last_test_at,null) assert.equal((await pool.query("SELECT result FROM mcp_operation_steps WHERE operation_id=$1",[applied.id])).rows[0].result.storageTestVerified,false) await applyPlan(storagePrincipal,first.planId,"storage-config-once") assert.deepEqual((await pool.query("SELECT * FROM backup_storage_settings WHERE id=1")).rows,saved) const newStorageEndpoint="https://other.example.test" await assert.rejects(invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{s3Endpoint:newStorageEndpoint}}),/новое назначение/) const moveRef=await addStorageSecret(backupStorageSecretPurpose({...storageSettings,s3Endpoint:newStorageEndpoint}),storageActor.id,null,"NEVER_STORAGE_MOVED") const movePlan=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{s3Endpoint:newStorageEndpoint},secretRef:moveRef}) as {planId:string} await approvePlan(movePlan.planId,storageActor,true) assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,movePlan.planId,randomUUID())).id),"applied") assert.equal((await pool.query("SELECT s3_endpoint,s3_secret_access_key FROM backup_storage_settings WHERE id=1")).rows[0].s3_endpoint,newStorageEndpoint) const moveBack=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{s3Endpoint:storageSettings.s3Endpoint},secretRef}) as {planId:string} await approvePlan(moveBack.planId,storageActor,true) assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,moveBack.planId,randomUUID())).id),"applied") const dormant=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{provider:"local",s3Bucket:""}}) as {planId:string} await approvePlan(dormant.planId,storageActor,true) assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,dormant.planId,randomUUID())).id),"applied") assert.equal((await pool.query("SELECT s3_bucket FROM backup_storage_settings WHERE id=1")).rows[0].s3_bucket,"") await assert.rejects(invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:storageSettings}),/новое назначение/) const enableAgain=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",settingsArgs) as {planId:string} await approvePlan(enableAgain.planId,storageActor,true) assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,enableAgain.planId,randomUUID())).id),"applied") const beforeArchive=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{keepLocalCopy:false}}) as {planId:string} await approvePlan(beforeArchive.planId,storageActor,true) await pool.query("INSERT INTO backup_entries(id,server_id,server_name,filename,size_bytes,kind,storage,s3_key,created_at) VALUES($1,1,'fixture',$2,0,'manual','s3','mcp-fixture/object.rsc',now())",[storageEntryId,storageEntryId+".rsc"]) assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,beforeArchive.planId,randomUUID())).id),"rejected") assert.equal((await pool.query("SELECT keep_local_copy FROM backup_storage_settings WHERE id=1")).rows[0].keep_local_copy,true) for(const settings of [{provider:"local"},{s3Endpoint:"https://other.example.test"},{s3Bucket:"other"},{s3Region:"other"},{s3ForcePathStyle:false}])await assert.rejects(invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings}),/архивы/) const prefixPlan=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{s3Prefix:"mcp-fixture-new",keepLocalCopy:false}}) as {planId:string} await approvePlan(prefixPlan.planId,storageActor,true) await pool.query("UPDATE backup_storage_settings SET last_test_at=now(),last_test_error='NEVER_STORAGE_OBSERVER_ERROR' WHERE id=1") assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,prefixPlan.planId,randomUUID())).id),"applied") const afterPrefix=(await pool.query("SELECT * FROM backup_storage_settings WHERE id=1")).rows[0] assert.equal(afterPrefix.keep_local_copy,false);assert.equal(afterPrefix.s3_prefix,"mcp-fixture-new");assert.equal(afterPrefix.s3_secret_access_key,storageSecretValue);assert.equal(afterPrefix.last_test_at,null);assert.equal(afterPrefix.last_test_error,null) const staleKey=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{keepLocalCopy:true}}) as {planId:string} await approvePlan(staleKey.planId,storageActor,true) await pool.query("UPDATE backup_storage_settings SET s3_secret_access_key='NEVER_STORAGE_EXTERNALLY_CHANGED' WHERE id=1") assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,staleKey.planId,randomUUID())).id),"rejected") await pool.query("UPDATE backup_storage_settings SET s3_secret_access_key=$1 WHERE id=1",[storageSecretValue]) const rotationRef=await addStorageSecret(undefined,storageActor.id,null,"NEVER_STORAGE_ROTATION") const rotationPlan=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{secretRef:rotationRef}) as {planId:string} await approvePlan(rotationPlan.planId,storageActor,true) await pool.query("DELETE FROM mcp_secrets WHERE id=$1",[rotationRef]) assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,rotationPlan.planId,randomUUID())).id),"rejected") const validRotationRef=await addStorageSecret(undefined,storageActor.id,null,"NEVER_STORAGE_ROTATION_VALID") const validRotation=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{secretRef:validRotationRef}) as {planId:string} await approvePlan(validRotation.planId,storageActor,true) assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,validRotation.planId,randomUUID())).id),"applied") assert.equal((await pool.query("SELECT s3_secret_access_key FROM backup_storage_settings WHERE id=1")).rows[0].s3_secret_access_key,"NEVER_STORAGE_ROTATION_VALID") const newServerPlan=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{keepLocalCopy:true}}) as {planId:string} await approvePlan(newServerPlan.planId,storageActor,true) await pool.query("INSERT INTO servers(id,name,host,username,password) VALUES(49,'storage-catalog-fixture','127.0.0.1','admin','fixture')") try{assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,newServerPlan.planId,randomUUID())).id),"rejected")}finally{await pool.query("DELETE FROM servers WHERE id=49")} storagePending=withBackupStorageOperation(()=>new Promise(resolve=>{releaseStorage=resolve})) await Promise.resolve() await assert.rejects(invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{keepLocalCopy:true}}),/занято/) releaseStorage();await storagePending const lateBusy=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{keepLocalCopy:true}}) as {planId:string} await approvePlan(lateBusy.planId,storageActor,true) storagePending=withBackupStorageOperation(()=>new Promise(resolve=>{releaseStorage=resolve})) await Promise.resolve() assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,lateBusy.planId,randomUUID())).id),"rejected") releaseStorage();await storagePending await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,'fixture')",[storageProtection,JSON.stringify({kind:"backup-storage",id:"1",protection:"deny"})]) await assert.rejects(invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{keepLocalCopy:true}})) await pool.query("DELETE FROM mcp_protections WHERE id=$1",[storageProtection]) await pool.query("CREATE FUNCTION mcp_fixture_ignore_storage() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.keep_local_copy := OLD.keep_local_copy; RETURN NEW; END $$") await pool.query("CREATE TRIGGER mcp_fixture_ignore_storage BEFORE UPDATE ON backup_storage_settings FOR EACH ROW EXECUTE FUNCTION mcp_fixture_ignore_storage()") try { const ignored=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{keepLocalCopy:true}}) as {planId:string} await approvePlan(ignored.planId,storageActor,true) assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,ignored.planId,randomUUID())).id),"partial") assert.equal((await pool.query("SELECT keep_local_copy FROM backup_storage_settings WHERE id=1")).rows[0].keep_local_copy,false) }finally{await pool.query("DROP TRIGGER mcp_fixture_ignore_storage ON backup_storage_settings");await pool.query("DROP FUNCTION mcp_fixture_ignore_storage()")} await pool.query("CREATE FUNCTION mcp_fixture_change_storage_bucket() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.s3_bucket := 'unexpected-bucket'; RETURN NEW; END $$") await pool.query("CREATE TRIGGER mcp_fixture_change_storage_bucket BEFORE UPDATE ON backup_storage_settings FOR EACH ROW EXECUTE FUNCTION mcp_fixture_change_storage_bucket()") try{ const changedUnspecified=await invokeMcpTool(storagePrincipal,"mm_backup_storage_update_plan",{settings:{keepLocalCopy:true}}) as {planId:string} await approvePlan(changedUnspecified.planId,storageActor,true) assert.equal(await waitRecursiveOperation((await applyPlan(storagePrincipal,changedUnspecified.planId,randomUUID())).id),"partial") }finally{await pool.query("DROP TRIGGER mcp_fixture_change_storage_bucket ON backup_storage_settings");await pool.query("DROP FUNCTION mcp_fixture_change_storage_bucket()")} assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_requests WHERE connection_id=$1 AND result::text LIKE '%NEVER_STORAGE%'",[storageAccess.connection.id])).rows[0].n,0) assert.equal(writes,storageWritesBefore) }finally{ releaseStorage();await storagePending await pool.query("DELETE FROM mcp_protections WHERE id=$1",[storageProtection]) await pool.query("DELETE FROM backup_entries WHERE id=$1",[storageEntryId]) await pool.query("DELETE FROM mcp_secrets WHERE id=ANY($1::uuid[])",[storageSecretIds]) await pool.query("DELETE FROM backup_storage_settings WHERE id=1") if(oldStorageRows[0])await pool.query("INSERT INTO backup_storage_settings SELECT * FROM jsonb_populate_record(NULL::backup_storage_settings,$1::jsonb)",[JSON.stringify(oldStorageRows[0])]) } console.log("✓ Backup storage shares REST mutation: full global scope, human approval, private credential/secret-ref proof, destination/owner binding, remote archives, late catalog/credential/busy drift, preserved key, test invalidation, protection, partial verification and no replay or network dispatch") const internetPathAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped Internet path history", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read"], scope: { serverIds: [1], operationIds: ["routing.internet-path.latest.read"], allowGlobal: false } }), actor) const internetPathPrincipal = await authenticateMcpToken(internetPathAccess.token) const internetPathHttp = FilterFastify(); internetPathHttp.setValidatorCompiler(filterValidator); internetPathHttp.setSerializerCompiler(filterSerializer) await internetPathHttp.register((await import("../../routes/internet-path.js")).default,{prefix:"/api"}) const internetPathIds: number[]=[] const pathSnapshotTime=new Date().toISOString() const {ensurePartitionFor:ensurePathPartition}=await import("../../db/partitions.js") for(const delta of [-3600000,0,3600000])await ensurePathPartition(pool,"internet_path_snapshots","week",new Date(Date.parse(pathSnapshotTime)+delta)) const pathPayload={collectionComplete:true,sampledAt:pathSnapshotTime,internetTarget:"1.1.1.1", servers:[{id:"1",name:"Visible home",type:"home-router",enabled:true,password:"NEVER_PATH_SECRET"},{id:"2",name:"NEVER_PATH_HIDDEN",enabled:"malformed"}], greTunnels:[{id:"gre1",serverId:"1",name:"fixture-gre",enabled:true,status:"up",ipsec:{secret:"NEVER_PATH_SECRET"},comment:"NEVER_PATH_SECRET"},{id:"NEVER_PATH_HIDDEN",serverId:"2",mtu:"bad"}], filtersRulesets:[{serverId:"1",rules:[{id:"r1",community:"65000:1",action:"route",gateway:"192.0.2.1",description:"NEVER_PATH_SECRET"},{id:"r2",community:"65000:2",action:"blackhole"}]},{serverId:"2",rules:"malformed"}], speedProbes:[{id:"p1",srcServerId:"1",dstServerId:"1",lastTxAvgMbps:0,lastError:"NEVER_PATH_SECRET"},{id:"NEVER_PATH_HIDDEN",srcServerId:"1",dstServerId:"2",lastTxAvgMbps:"bad"}], routeLookupByServerId:{"1":{gateway:null,routingMark:null},"2":"NEVER_PATH_HIDDEN"}, wanRuntimeByHomeId:{"1":{defaultGateway:null,defaultInterface:null,uplinks:[{id:"wan",iface:"ether1",isDefault:false,password:"NEVER_PATH_SECRET"} ]},"2":"malformed"}, } countCatalogCalls=true;catalogCalls=0 try { assert.equal((await pool.query("SELECT count(*)::int AS n FROM internet_path_snapshots")).rows[0].n,0) assert.equal((await invokeMcpTool(internetPathPrincipal,"mm_internet_path_latest_get",{}) as {snapshot:unknown}).snapshot,null) const id=(await pool.query("INSERT INTO internet_path_snapshots(sampled_at,payload_json) VALUES($1,$2::jsonb) RETURNING id",[pathSnapshotTime,JSON.stringify(pathPayload)])).rows[0].id internetPathIds.push(id) const observed=await invokeMcpTool(internetPathPrincipal,"mm_internet_path_latest_get",{limit:1}) as {snapshot:{servers:{items:{id:number}[]};greTunnels:{items:{mtu:null}[]};speedProbes:{items:{lastTxAvgMbps:number;lastRxAvgMbps:null}[];total:number};filtersRulesets:{items:{rules:{total:number;nextOffset:number}}[]};live:boolean;forwardingVerified:boolean;stale:boolean};incomplete:boolean} assert.equal(observed.snapshot.servers.items.length,1);assert.equal(observed.snapshot.servers.items[0].id,1) assert.equal(observed.snapshot.speedProbes.total,1);assert.equal(observed.snapshot.speedProbes.items[0].lastTxAvgMbps,0);assert.equal(observed.snapshot.speedProbes.items[0].lastRxAvgMbps,null) assert.equal(observed.snapshot.greTunnels.items[0].mtu,null);assert.equal(observed.snapshot.filtersRulesets.items[0].rules.total,2);assert.equal(observed.snapshot.filtersRulesets.items[0].rules.nextOffset,1) assert.equal(observed.snapshot.live,false);assert.equal(observed.snapshot.forwardingVerified,false);assert.equal(observed.snapshot.stale,false);assert.equal(observed.incomplete,true) assert.equal(JSON.stringify(observed).includes("NEVER_PATH"),false) const rest=await internetPathHttp.inject({method:"GET",url:"/api/internet-path/latest"}) assert.equal(rest.statusCode,200);assert.equal(rest.json().sampledAt,pathSnapshotTime);assert.equal(rest.json().snapshot.servers.length,2) await assert.rejects(invokeMcpTool(internetPathPrincipal,"mm_internet_path_latest_get",{serverId:2})) const updatePath=async(payload:unknown,time=pathSnapshotTime)=>pool.query("UPDATE internet_path_snapshots SET payload_json=$2::jsonb,sampled_at=$3 WHERE id=$1",[id,JSON.stringify(payload),time]) for(const bad of [{...pathPayload,greTunnels:[pathPayload.greTunnels[0],pathPayload.greTunnels[0]]},{...pathPayload,greTunnels:[{id:"unbound"}]},{...pathPayload,servers:{bad:true}},"legacy serialized payload",{...pathPayload,filtersRulesets:[{serverId:"1",rules:"bad"}]}]){ await updatePath(bad);await assert.rejects(invokeMcpTool(internetPathPrincipal,"mm_internet_path_latest_get",{}),/не подтверждён/) } const pastTime=new Date(Date.now()-3600000).toISOString() await updatePath({...pathPayload,sampledAt:pastTime},pastTime) assert.equal((await invokeMcpTool(internetPathPrincipal,"mm_internet_path_latest_get",{}) as {snapshot:{stale:boolean}}).snapshot.stale,true) const futureTime=new Date(Date.now()+3600000).toISOString() await updatePath({...pathPayload,sampledAt:futureTime},futureTime) const future=await invokeMcpTool(internetPathPrincipal,"mm_internet_path_latest_get",{}) as {snapshot:{clockSkew:boolean;stale:boolean}} assert.equal(future.snapshot.clockSkew,true);assert.equal(future.snapshot.stale,true) assert.equal(catalogCalls,0) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_requests WHERE connection_id=$1 AND result::text LIKE '%NEVER_PATH%'",[internetPathAccess.connection.id])).rows[0].n,0) } finally {countCatalogCalls=false;await internetPathHttp.close();await pool.query("DELETE FROM internet_path_snapshots WHERE id=ANY($1::bigint[])",[internetPathIds])} console.log("✓ Stored Internet path: shared latest REST source, SQL projection before parsing hidden/mixed records, nested pages, zero/unknown fields, omitted GRE keys/notes, stale/future/malformed source, scoped audit and no device requests; no current routing claim") const deviceStateAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped device states", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read", "mm:certificates:read"], scope: { serverIds: [1], operationIds: ["containers.state.read", "certificates.state.read"], allowGlobal: false } }), { ...actor, permissions: [...actor.permissions, "mm:certificates:admin"] }) const deviceStatePrincipal = await authenticateMcpToken(deviceStateAccess.token) deviceStateFixtures = true; countCatalogCalls = true; catalogCalls = 0 const containerState = await invokeMcpTool(deviceStatePrincipal, "mm_containers_state_get", { limit: 1 }) as { servers: { serverId: number; availability: string; containers: { status: string; envs: { value: string }[] }[]; total: number; nextOffset: number; failures: unknown[] }[] } assert.equal(containerState.servers.length, 1); assert.equal(containerState.servers[0].serverId, 1); assert.equal(containerState.servers[0].total, 2); assert.equal(containerState.servers[0].nextOffset, 1) assert.equal(containerState.servers[0].availability, "available"); assert.equal(containerState.servers[0].containers[0].status, "running"); assert.equal(containerState.servers[0].failures.length, 0) assert.equal(containerState.servers[0].containers[0].envs[0].value, "[redacted]") assert.ok(!JSON.stringify(containerState).includes("never-return")); assert.equal(catalogCalls, 3) await assert.rejects(invokeMcpTool(deviceStatePrincipal, "mm_containers_state_get", { serverId: 2 })); assert.equal(catalogCalls, 3) const containerNext = await invokeMcpTool(deviceStatePrincipal, "mm_containers_state_get", { offset: 1, limit: 1 }) as typeof containerState assert.equal(containerNext.servers[0].containers[0].status, "unknown") containerStateFailure = "envs" const partialContainers = await invokeMcpTool(deviceStatePrincipal, "mm_containers_state_get", {}) as { servers: { failures: { resource: string }[] }[]; incomplete: boolean } assert.equal(partialContainers.servers[0].failures[0].resource, "envs"); assert.equal(partialContainers.incomplete, true) containerStateFailure = "unsupported" const unsupportedContainers = await invokeMcpTool(deviceStatePrincipal, "mm_containers_state_get", {}) as { servers: { availability: string; containers: unknown[] }[]; incomplete: boolean } assert.equal(unsupportedContainers.servers[0].availability, "unsupported"); assert.equal(unsupportedContainers.servers[0].containers.length, 0); assert.equal(unsupportedContainers.incomplete, true) containerStateFailure = "none"; catalogCalls = 0 const certificateState = await invokeMcpTool(deviceStatePrincipal, "mm_certificates_state_get", { limit: 1 }) as { servers: { serverId: string; certificates: { status: string; validUntil: string | null }[]; total: number; nextOffset: number; error: string | null }[]; incomplete: boolean } assert.equal(certificateState.servers.length, 1); assert.equal(certificateState.servers[0].serverId, "1"); assert.equal(certificateState.servers[0].total, 2); assert.equal(certificateState.servers[0].nextOffset, 1) assert.equal(certificateState.servers[0].certificates[0].status, "valid"); assert.equal(certificateState.servers[0].certificates[0].validUntil, "2034-12-31T17:00:00.000Z"); assert.equal(certificateState.servers[0].error, null) assert.ok(!JSON.stringify(certificateState).includes("never-return")); assert.equal(catalogCalls, 3) await assert.rejects(invokeMcpTool(deviceStatePrincipal, "mm_certificates_state_get", { serverId: 2 })); assert.equal(catalogCalls, 3) certificateClockKnown = false const unknownCertificate = await invokeMcpTool(deviceStatePrincipal, "mm_certificates_state_get", {}) as typeof certificateState assert.equal(unknownCertificate.servers[0].certificates[0].status, "unknown"); assert.equal(unknownCertificate.servers[0].certificates[0].validUntil, null) certificateClockKnown = true; healthUnavailable = true const failedCertificates = await invokeMcpTool(deviceStatePrincipal, "mm_certificates_state_get", {}) as typeof certificateState assert.ok(failedCertificates.servers[0].error); assert.equal(failedCertificates.incomplete, true) healthUnavailable = false const beforeEmptyPage = catalogCalls const emptyDevicePage = await invokeMcpTool(deviceStatePrincipal, "mm_containers_state_get", { serverOffset: 1 }) as { servers: unknown[]; incomplete: boolean } assert.equal(emptyDevicePage.servers.length, 0); assert.equal(emptyDevicePage.incomplete, true); assert.equal(catalogCalls, beforeEmptyPage) await pool.query("UPDATE servers SET enabled=false WHERE id=1") assert.equal((await invokeMcpTool(deviceStatePrincipal, "mm_certificates_state_get", {}) as typeof certificateState).servers.length, 0) assert.equal(catalogCalls, beforeEmptyPage) await pool.query("UPDATE servers SET enabled=true WHERE id=1") assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_requests WHERE connection_id=$1 AND result::text LIKE '%never-return%'", [deviceStateAccess.connection.id])).rows[0].n, 0) const { default: ContainersFastify } = await import("fastify") const { validatorCompiler: containerValidator, serializerCompiler: containerSerializer } = await import("@fastify/type-provider-zod") const containersHttp = ContainersFastify(); containersHttp.setValidatorCompiler(containerValidator); containersHttp.setSerializerCompiler(containerSerializer) await containersHttp.register((await import("../../routes/containers.js")).default, { prefix: "/api" }) await containersHttp.register((await import("../../routes/certificates.js")).default, { prefix: "/api" }) const otherEnabled = (await pool.query("SELECT id FROM servers WHERE enabled AND id<>1")).rows.map(row => row.id) await pool.query("UPDATE servers SET enabled=false WHERE id=ANY($1::bigint[])", [otherEnabled]) try { const fullState = await invokeMcpTool(deviceStatePrincipal, "mm_containers_state_get", {}) as { servers: { containers: { id:string; rosId:string; status:string; startOnBoot:boolean|null; envs:{value:string}[] }[] }[] } for (const url of ["/api/containers", "/api/servers/1/containers"]) { const response = await containersHttp.inject({method:"GET",url}); assert.equal(response.statusCode,200,response.body) const restRows = response.json().containers as typeof fullState.servers[0]["containers"] assert.deepEqual(restRows.map(row=>({id:row.id,rosId:row.rosId,status:row.status,startOnBoot:row.startOnBoot})),fullState.servers[0].containers.map(row=>({id:row.id,rosId:row.rosId,status:row.status,startOnBoot:row.startOnBoot}))) assert.equal(restRows[0].envs[0].value,"never-return-this-env"); assert.equal(fullState.servers[0].containers[0].envs[0].value,"[redacted]") } reverseContainerRows = true assert.equal((await invokeMcpTool(deviceStatePrincipal,"mm_containers_state_get",{limit:1}) as typeof fullState).servers[0].containers[0].rosId,"*c1") const exhausted = await invokeMcpTool(deviceStatePrincipal,"mm_containers_state_get",{offset:100}) as {incomplete:boolean;servers:{containers:unknown[]}[]} assert.equal(exhausted.incomplete,true); assert.equal(exhausted.servers[0].containers.length,0) containerStateFailure = "envs" for (const url of ["/api/containers","/api/servers/1/containers"]) assert.equal((await containersHttp.inject({method:"GET",url})).statusCode,502) containerStateFailure = "unsupported" assert.deepEqual((await containersHttp.inject({method:"GET",url:"/api/containers"})).json(),{containers:[]}) assert.equal((await invokeMcpTool(deviceStatePrincipal,"mm_containers_state_get",{}) as {servers:{availability:string}[]}).servers[0].availability,"unsupported") for (const method of ["GET","POST"] as const) { const response = await containersHttp.inject({method,url:method==="GET"?"/api/certificates":"/api/certificates/refresh"}) assert.equal(response.statusCode,200,response.body); assert.equal(response.json().failures.length,0) const observed = await invokeMcpTool(deviceStatePrincipal,"mm_certificates_state_get",{}) as {servers:{certificates:{id:string;name:string;status:string;validUntil:string|null}[]}[]} assert.deepEqual(response.json().certificates.map((row:{id:string;name:string})=>({id:row.id,name:row.name})),observed.servers[0].certificates.map(row=>({id:row.id,name:row.name}))) assert.equal(observed.servers[0].certificates[0].validUntil,"2034-12-31T17:00:00.000Z"); assert.equal(observed.servers[0].certificates[1].status,"unknown") assert.ok(!JSON.stringify(response.json()).includes("never-return-certificate-key")) } const certificateEnd = await invokeMcpTool(deviceStatePrincipal,"mm_certificates_state_get",{offset:100}) as {incomplete:boolean;servers:{certificates:unknown[]}[]} assert.equal(certificateEnd.incomplete,true); assert.equal(certificateEnd.servers[0].certificates.length,0) const { listCertificatesFromServers: legacyCertificateCatalog } = await import("../../services/certificates-service.js") const noCertificatePolls = catalogCalls; assert.deepEqual(await legacyCertificateCatalog([]),{certificates:[],failures:[]}); assert.equal(catalogCalls,noCertificatePolls) certificateStateMalformed = true for (const method of ["GET","POST"] as const) { const response = await containersHttp.inject({method,url:method==="GET"?"/api/certificates":"/api/certificates/refresh"}) assert.equal(response.statusCode,200,response.body); assert.equal(response.json().certificates.length,0); assert.equal(response.json().failures.length,1) assert.equal(response.json().failures[0].error,"Некорректный ответ каталога сертификатов") } const invalidCertificates = await invokeMcpTool(deviceStatePrincipal,"mm_certificates_state_get",{}) as {incomplete:boolean;servers:{error:string}[]} assert.equal(invalidCertificates.incomplete,true); assert.equal(invalidCertificates.servers[0].error,"Некорректный ответ каталога сертификатов") } finally { containerStateFailure = "none"; reverseContainerRows = false; certificateStateMalformed = false await pool.query("UPDATE servers SET enabled=true WHERE id=ANY($1::bigint[])",[otherEnabled]); await containersHttp.close() } deviceStateFixtures = false; countCatalogCalls = false console.log("✓ Container/certificate states use shared collectors with pre-poll scope, paging, explicit unsupported/partial/unknown, timezone evidence and no env/command/key leakage in response or audit") const serverHistoryAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Server history and source address", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:servers:read"], scope: { serverIds: [1], operationIds: ["servers.snapshots.read", "servers.source-address.read"], allowGlobal: false } }), actor) const serverHistoryPrincipal = await authenticateMcpToken(serverHistoryAccess.token) await (await import("../../db/partitions.js")).ensurePartitionFor(pool, "server_snapshots", "week", new Date("2026-10-03T00:00:00Z")) await pool.query("INSERT INTO server_snapshots(server_id,polled_at,status,cpu_load,identity_name) VALUES(1,'2026-10-03T00:00:00Z','online',0,'visible-old'),(1,'2026-10-03T00:00:01Z','offline',NULL,'visible-new'),(2,'2026-10-03T00:00:02Z','online',99,'hidden-server')") const serverHistory = await invokeMcpTool(serverHistoryPrincipal, "mm_server_snapshots_get", { serverId: 1, limit: 1 }) as { serverId: number; snapshots: { status: string; identityName: string }[]; incomplete: boolean } assert.equal(serverHistory.serverId, 1); assert.equal(serverHistory.snapshots.length, 1); assert.equal(serverHistory.snapshots[0].status, "offline"); assert.equal(serverHistory.snapshots[0].identityName, "visible-new"); assert.equal(serverHistory.incomplete, true) assert.ok(!JSON.stringify(serverHistory).includes("hidden-server")) await assert.rejects(invokeMcpTool(serverHistoryPrincipal, "mm_server_snapshots_get", { serverId: 2 })) await assert.rejects(invokeMcpTool(serverHistoryPrincipal, "mm_server_snapshots_get", { serverId: 1, limit: 201 })) const sourceAddress = await invokeMcpTool(serverHistoryPrincipal, "mm_server_source_address_get", { serverId: 1 }) as { host: string; ipv4: string; interfaceAddressVerified: boolean } assert.equal(sourceAddress.host, "127.0.0.1"); assert.equal(sourceAddress.ipv4, "127.0.0.1"); assert.equal(sourceAddress.interfaceAddressVerified, false) await assert.rejects(invokeMcpTool(serverHistoryPrincipal, "mm_server_source_address_get", { serverId: 2 })) await assert.rejects(invokeMcpTool(serverHistoryPrincipal, "mm_server_source_address_get", { serverId: 1, host: "outside.example.invalid" })) await pool.query("DELETE FROM server_snapshots WHERE identity_name IN ('visible-old','visible-new','hidden-server')") console.log("✓ Server history uses the shared bounded SQL service; source resolution accepts only the registered host and does not attest interface ownership") countCatalogCalls = true const beforeTrafficHistoryCalls = catalogCalls await (await import("../../test/mcp-traffic-history.js")).verifyMcpTrafficHistory(actor) await (await import("../../test/mcp-user-traffic-history.js")).verifyMcpUserTrafficHistory(actor) await (await import("../../test/mcp-bgp-export.js")).verifyMcpBgpExport(actor) await (await import("../../test/mcp-bgp-filter-binding.js")).verifyMcpBgpFilterBinding(actor) await (await import("../../test/mcp-bgp-community-policy.js")).verifyMcpBgpCommunityPolicy(actor) await (await import("../../test/mcp-bgp-prefixes.js")).verifyMcpBgpPrefixes(actor) await (await import("../../test/mcp-filter-shared.js")).verifySharedFilterWrites() await (await import("../../test/mcp-filter-write.js")).verifyMcpFilterWrites(actor) await (await import("../../test/mcp-uptime-overview.js")).verifyMcpUptimeOverview(actor) await (await import("../../test/mcp-network-hosts.js")).verifyMcpNetworkHosts(actor) assert.equal(catalogCalls, beforeTrafficHistoryCalls) await (await import("../../test/mcp-sidebar-counts.js")).verifyMcpSidebarCounts(actor, () => catalogCalls) countCatalogCalls = false const { preflightPaths } = await import("./ecmp-preflight.js") await (await import("../../test/mcp-path-completion.js")).verifyMcpPathCompletion(actor) await (await import("../../test/mcp-log-artifacts.js")).verifyMcpLogArtifacts(actor) await (await import("../../test/mcp-alerts-outbox.js")).verifyMcpAlertsOutbox(actor) preflightFixtures = Object.fromEntries(Object.values(preflightPaths("ipv4")).map(path => ["/rest" + path, []])) preflightFixtures["/rest/ip/route"] = [1, 2].map(index => ({ ".id": `*p${index}`, "dst-address": "192.0.2.0/24", gateway: `192.0.2.${index}`, "immediate-gw": `192.0.2.${index}%same-gre`, "routing-table": "main", distance: "10", active: "true", disabled: "false" })) preflightFixtures["/rest/ip/settings"] = { "rp-filter": "strict", "ipv4-multipath-hash-policy": "l3" } preflightFixtures["/rest/ip/firewall/connection/tracking"] = { enabled: "auto" } preflightFixtures["/rest/ip/firewall/filter"] = [{ ".id": "*ft", action: "fasttrack-connection", disabled: "false" }] preflightFixtures["/rest/ip/firewall/nat"] = [{ ".id": "*nat", action: "masquerade", disabled: "false" }] const preflightAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Read-only ECMP preflight", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read"], scope: { serverIds: [1], operationIds: ["routing.ecmp.preflight.read"], allowGlobal: false } }), actor) const preflightPrincipal = await authenticateMcpToken(preflightAccess.token), preflightWritesBefore = writes countCatalogCalls = true; catalogCalls = 0 const preflightInput = { serverId: 1, family: "ipv4", routeIds: ["*p1", "*p2"] } const preflight = await invokeMcpTool(preflightPrincipal, "mm_ecmp_preflight_get", preflightInput) as { configurationObserved: boolean; findings: { code: string }[]; networkValidation: string } assert.equal(preflight.configurationObserved, true); assert.equal(preflight.networkValidation, "not-performed"); assert.equal(catalogCalls, 10) for (const code of ["SHARED_EGRESS_INTERFACE", "STRICT_RPF_REVIEW", "FASTTRACK_REVIEW", "NAT_AFFINITY_REVIEW"]) assert.ok(preflight.findings.some(item => item.code === code), code) await assert.rejects(invokeMcpTool(preflightPrincipal, "mm_ecmp_preflight_get", { ...preflightInput, serverId: 2 })); assert.equal(catalogCalls, 10) preflightUnavailable = "/rest/ip/firewall/nat" const unknownPreflight = await invokeMcpTool(preflightPrincipal, "mm_ecmp_preflight_get", preflightInput) as typeof preflight assert.equal(unknownPreflight.configurationObserved, false); assert.ok(unknownPreflight.findings.some(item => item.code === "OBSERVATION_UNAVAILABLE")); assert.equal(writes, preflightWritesBefore) preflightFixtures = undefined; preflightUnavailable = ""; countCatalogCalls = false console.log("✓ ECMP preflight is read-only, scopes before polling, detects shared egress/FastTrack/NAT/strict RPF and preserves unknown resources without claiming network acceptance") const removalAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Container removal", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], scope: { serverIds: [1], operationIds: ["containers.remove", "containers.restart", "containers.start"], allowGlobal: false } }), actor) const removalPrincipal = await authenticateMcpToken(removalAccess.token) const legacyRemovalAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Legacy container delete alias", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], scope: { serverIds: [1], operationIds: ["ros.container.delete"], allowGlobal: false } }), actor) const legacyRemovalPrincipal = await authenticateMcpToken(legacyRemovalAccess.token) const removalRow = { ".id": "*remove", name: "fixture-container", interface: "fixture-veth", status: "stopped", "start-on-boot": "false", cmd: "never-return-command-identity", comment: "fixture" } const containerArgs = { serverId: 1, id: "*remove" } const waitContainerOperation = async (id: string) => { for (let attempt = 0; attempt < 100; attempt++) { const state = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [id])).rows[0].state if (!["running", "pending"].includes(state)) return state await delay(30) } throw Error("Container operation timed out") } containerMutationFixtures = true for (const badStatus of ["running", "unknown", "starting", "error"]) { mutableContainers = [{ ...removalRow, status: badStatus }] await assert.rejects(invokeMcpTool(removalPrincipal, "mm_container_remove_plan", containerArgs)) await assert.rejects(invokeMcpTool(legacyRemovalPrincipal, "mm_container_delete_plan", containerArgs)) } assert.equal(containerDeletes, 0); assert.equal(containerCommands, 0) mutableContainers = [{ ...removalRow }] for (const kind of ["interface", "veth"]) { const protection = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,'integration')", [protection, JSON.stringify({ kind, id: "*veth", serverId: 1, protection: "deny" })]) await assert.rejects(invokeMcpTool(removalPrincipal, "mm_container_remove_plan", containerArgs)) await assert.rejects(invokeMcpTool(legacyRemovalPrincipal, "mm_container_delete_plan", containerArgs)) await assert.rejects(invokeMcpTool(removalPrincipal, "mm_container_start_plan", containerArgs)) await assert.rejects(invokeMcpTool(removalPrincipal, "mm_container_restart_plan", containerArgs)) await pool.query("DELETE FROM mcp_protections WHERE id=$1", [protection]) } for (const drift of ["configuration", "state", "parent"]) { mutableContainers = [{ ...removalRow }]; containerParent.name = "fixture-veth" const prepared = await invokeMcpTool(removalPrincipal, "mm_container_remove_plan", containerArgs) as { planId: string } assert.ok(!JSON.stringify(prepared).includes("never-return-command-identity")) await approvePlan(prepared.planId, actor, true) if (drift === "configuration") mutableContainers[0].name = "replacement-same-id" if (drift === "state") mutableContainers[0].status = "running" if (drift === "parent") containerParent.name = "renamed-veth" assert.equal(await waitContainerOperation((await applyPlan(removalPrincipal, prepared.planId, randomUUID())).id), "rejected") } assert.equal(containerDeletes, 0); containerParent.name = "fixture-veth" mutableContainers = [{ ...removalRow }] const restartPlan = await invokeMcpTool(removalPrincipal, "mm_container_restart_plan", containerArgs) as { planId: string } await approvePlan(restartPlan.planId, actor, true) assert.equal(await waitContainerOperation((await applyPlan(removalPrincipal, restartPlan.planId, randomUUID())).id), "applied") assert.equal(containerCommands, 1); assert.equal(mutableContainers[0].status, "running") const runningRestart = await invokeMcpTool(removalPrincipal, "mm_container_restart_plan", containerArgs) as { planId: string } await approvePlan(runningRestart.planId, actor, true) assert.equal(await waitContainerOperation((await applyPlan(removalPrincipal, runningRestart.planId, randomUUID())).id), "applied") assert.equal(containerCommands, 3); assert.equal(mutableContainers[0].status, "running") mutableContainers = [{ ...removalRow }] const replacedStart = await invokeMcpTool(removalPrincipal, "mm_container_start_plan", containerArgs) as { planId: string } await approvePlan(replacedStart.planId, actor, true); replaceContainerAfterCommand = true assert.equal(await waitContainerOperation((await applyPlan(removalPrincipal, replacedStart.planId, randomUUID())).id), "partial") replaceContainerAfterCommand = false mutableContainers = [{ ...removalRow }] const commandsBeforeRemoval = containerCommands const removePlan = await invokeMcpTool(legacyRemovalPrincipal, "mm_container_delete_plan", containerArgs) as { planId: string } await approvePlan(removePlan.planId, actor, true) const removed = await applyPlan(legacyRemovalPrincipal, removePlan.planId, "remove-container-once") assert.equal(await waitContainerOperation(removed.id), "applied"); assert.equal(containerDeletes, 1); assert.equal(mutableContainers.length, 0); assert.equal(containerCommands, commandsBeforeRemoval) await applyPlan(legacyRemovalPrincipal, removePlan.planId, "remove-container-once"); assert.equal(containerDeletes, 1) mutableContainers = [{ ...removalRow }]; ignoreContainerDelete = true const unconfirmedRemoval = await invokeMcpTool(removalPrincipal, "mm_container_remove_plan", containerArgs) as { planId: string } await approvePlan(unconfirmedRemoval.planId, actor, true) const unconfirmed = await applyPlan(removalPrincipal, unconfirmedRemoval.planId, "unconfirmed-container-removal") assert.equal(await waitContainerOperation(unconfirmed.id), "unknown"); assert.equal(containerDeletes, 2) await applyPlan(removalPrincipal, unconfirmedRemoval.planId, "unconfirmed-container-removal"); assert.equal(containerDeletes, 2) ignoreContainerDelete = false; delete containerParent.disabled mutableContainers = [{ ...removalRow }] const omittedParentFlag = await invokeMcpTool(removalPrincipal, "mm_container_remove_plan", containerArgs) as { planId: string } await approvePlan(omittedParentFlag.planId, actor, true) assert.equal(await waitContainerOperation((await applyPlan(removalPrincipal, omittedParentFlag.planId, randomUUID())).id), "applied") assert.equal(containerDeletes, 3) containerParent.disabled = "false"; containerMutationFixtures = false console.log("✓ Container removal shares REST stopped-only service; native alias cannot bypass state/parent protections, drift and ID reuse reject writes, command identity and failed delete verification never claim success or replay") const tableAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "routing-dependency-test", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read", "mm:network:write"], scope: { serverIds: [1], operationIds: ["ros.routing_table.delete", "ros.ipv4_route.create"], allowGlobal: false } }), actor) const tablePrincipal = await authenticateMcpToken(tableAccess.token) await assert.rejects(invokeMcpTool(tablePrincipal, "mm_ipv4_route_create_plan", { serverId: 1, fields: { "dst-address": "192.0.2.0/24", "routing-table": "missing" } }), /Таблица маршрутизации/) fixtureRouteTable = "fixture-table" await assert.rejects(invokeMcpTool(tablePrincipal, "mm_routing_table_delete_plan", { serverId: 1, id: "*table" }), /Таблица используется/) fixtureRouteTable = undefined try { for (const path of ["/rest/ip/route", "/rest/ipv6/route"]) for (const target of ["fixture-table", "*table"]) { preflightFixtures = { "/rest/ip/route": [], "/rest/ipv6/route": [], [path]: [{ ".id": "*LOOKUP", "routing-table": "main", gateway: "fixture-gre@" + target, dynamic: "false" }] } await assert.rejects(invokeMcpTool(tablePrincipal, "mm_routing_table_delete_plan", { serverId: 1, id: "*table" }), /Таблица используется/) } preflightFixtures = { "/rest/ip/route": [{ ".id": "*BADLOOKUP", "routing-table": "main", gateway: {} }], "/rest/ipv6/route": [] } await assert.rejects(invokeMcpTool(tablePrincipal, "mm_routing_table_delete_plan", { serverId: 1, id: "*table" }), (error: { code?: string }) => error.code === "STATE_UNAVAILABLE") } finally { preflightFixtures = undefined } fixtureRoutingRules = [{ ".id": "*rule", table: "fixture-table", action: "lookup" }] await assert.rejects(invokeMcpTool(tablePrincipal, "mm_routing_table_delete_plan", { serverId: 1, id: "*table" }), /Таблица используется/) fixtureRoutingRules = [] const tablePlan = await invokeMcpTool(tablePrincipal, "mm_routing_table_delete_plan", { serverId: 1, id: "*table" }) as { planId: string } await approvePlan(tablePlan.planId, actor, true) fixtureRoutingRules = [{ ".id": "*late-rule", table: "fixture-table", action: "lookup" }] const tableOperation = await applyPlan(tablePrincipal, tablePlan.planId, randomUUID()) for (let attempt = 0; attempt < 100; attempt++) { const state = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [tableOperation.id])).rows[0].state if (!["pending", "running"].includes(state)) { assert.equal(state, "rejected"); break } assert.ok(attempt < 99, "Table dependency operation did not finish") await delay(30) } assert.equal((await pool.query("SELECT error_code FROM mcp_operations WHERE id=$1", [tableOperation.id])).rows[0].error_code, "STATE_CONFLICT") fixtureRoutingRules = [] console.log("✓ Routing tables: missing table rejected, route/rule references protected, post-approval dependency changes reject before write") interfaceFixtures = true const initialVrfs: Record[] = [{ ".id": "*older", name: "older", interfaces: "ether9" }, { ".id": "*MAINVRF", name: "main", interfaces: "all", builtin: "true" }] fixtureVrfs = [...initialVrfs] const interfaceAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "overlay-dependency-test", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read", "mm:network:write"], scope: { serverIds: [1], operationIds: ["ros.vrf.create", "ros.vrf.update", "ros.vrf.delete", "ros.ipv4_address.create", "ros.gre.delete", "operations.recover"], allowGlobal: false } }), actor) const interfacePrincipal = await authenticateMcpToken(interfaceAccess.token) const awaitOperation = async (id: string, expected: string) => { for (let attempt = 0; attempt < 100; attempt++) { const state = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [id])).rows[0].state if (!["pending", "running"].includes(state)) { assert.equal(state, expected); return } await delay(30) } assert.fail("Interface fixture operation did not finish") } const addressArgs = { serverId: 1, fields: { address: "192.0.2.1/30", interface: "fixture-gre" } } const addressPlan = await invokeMcpTool(interfacePrincipal, "mm_ipv4_address_create_plan", addressArgs) as { planId: string } const addressEvidence = JSON.parse(await decryptSecret((await pool.query("SELECT evidence FROM mcp_plans WHERE id=$1", [addressPlan.planId])).rows[0].evidence.sealed)) assert.ok(addressEvidence.objects.some((row: { kind: string; id: string }) => row.kind === "gre" && row.id === "*gre")) const greProtection = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,'integration')", [greProtection, JSON.stringify({ kind: "gre", id: "*gre", serverId: 1, protection: "deny" })]) await assert.rejects(invokeMcpTool(interfacePrincipal, "mm_ipv4_address_create_plan", addressArgs)) await pool.query("DELETE FROM mcp_protections WHERE id=$1", [greProtection]) // This legacy fixture has symbolic IDs and no native companions. It cannot // grant production execution. Full CRUD/order/ACK/readback is covered by the // registered genuine VRF production component with native raw vectors. const vrfArgs = { serverId: 1, fields: { name: "owned-vrf", interfaces: "fixture-gre" } } const plansBeforeLegacyVrf = Number((await pool.query("SELECT count(*)::int AS n FROM mcp_plans WHERE connection_id=$1", [interfaceAccess.connection.id])).rows[0].n) for (const action of ["create", "update", "delete"]) await assert.rejects(invokeMcpTool(interfacePrincipal, `mm_vrf_${action}_plan`, { ...vrfArgs, ...(action === "create" ? {} : { id: "*CREATEDVRF" }) }), /productionAdmission:true/) assert.equal(Number((await pool.query("SELECT count(*)::int AS n FROM mcp_plans WHERE connection_id=$1", [interfaceAccess.connection.id])).rows[0].n), plansBeforeLegacyVrf) assert.equal(vrfWrites, 0) // Synthetic existing state is only a dependency fixture, not an ACK proof. fixtureVrfs.unshift({ ".id": "*CREATEDVRF", name: "owned-vrf", interfaces: "fixture-gre", disabled: "false" }) const ownedVrfId = String(fixtureVrfs[0][".id"]), beforeReferencedVrf = vrfWrites try { for (const path of ["/rest/ip/route", "/rest/ipv6/route"]) for (const target of ["owned-vrf", ownedVrfId]) { preflightFixtures = { "/rest/ip/route": [], "/rest/ipv6/route": [], [path]: [{ ".id": "*RESOLVER", "dst-address": path.includes("ipv6") ? "fd00::2/128" : "192.0.2.2/32", "routing-table": "main", gateway: "fixture-gre@" + target, dynamic: "false" }] } await assert.rejects(invokeMcpTool(interfacePrincipal, "mm_vrf_delete_plan", { serverId: 1, id: ownedVrfId, productionAdmission: true }), /VRF используется gateway/) await assert.rejects(invokeMcpTool(interfacePrincipal, "mm_vrf_update_plan", { serverId: 1, id: ownedVrfId, productionAdmission: true, fields: { name: "renamed-vrf" } }), /VRF используется gateway/) await assert.rejects(invokeMcpTool(interfacePrincipal, "mm_vrf_update_plan", { serverId: 1, id: ownedVrfId, productionAdmission: true, fields: { disabled: true } }), /VRF используется gateway/) } preflightFixtures = { "/rest/ip/route": [{ ".id": "*SELF", "dst-address": "192.0.2.0/30", "routing-table": "owned-vrf", gateway: "fixture-gre@owned-vrf", dynamic: "true" }], "/rest/ipv6/route": [] } // Preserve the dynamic self-route exception at its actual dependency // layer; this incomplete symbolic fixture is not raw admission evidence. await addRoutingDependencies(1, "vrf", "delete", fixtureVrfs[0], {}, [], []) preflightFixtures["/rest/ip/route"] = [{ ".id": "*OWNSTATIC", "dst-address": "192.0.2.0/30", "routing-table": "owned-vrf", gateway: "fixture-gre", dynamic: "false" }] await assert.rejects(invokeMcpTool(interfacePrincipal, "mm_vrf_delete_plan", { serverId: 1, id: ownedVrfId, productionAdmission: true }), /VRF используется настроенным маршрутом/) const lateGateway = { ".id": "*RESOLVER", "dst-address": "192.0.2.2/32", "routing-table": "main", gateway: "fixture-gre@owned-vrf-other", dynamic: "false" } preflightFixtures = { "/rest/ip/route": [lateGateway], "/rest/ipv6/route": [] } await addRoutingDependencies(1, "vrf", "delete", fixtureVrfs[0], {}, [], []) lateGateway.gateway = "fixture-gre@owned-vrf" await assert.rejects(addRoutingDependencies(1, "vrf", "delete", fixtureVrfs[0], {}, [], []), /VRF используется gateway/) assert.equal(vrfWrites, beforeReferencedVrf) } finally { preflightFixtures = undefined } await assert.rejects(invokeMcpTool(interfacePrincipal, "mm_gre_delete_plan", { serverId: 1, id: "*gre" }), /используется/) // Late post-approval gateway drift, ignored ordering, full CRUD and explicit // no-inverse/read-only reconciliation are tested by the genuine component. fixtureVrfs = [...initialVrfs, { ".id": "*MANAGEDVRF", name: "managed-vrf", interfaces: "fixture-gre", comment: "MikrotikManager: integration" }] const vrfAliasAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "VRF alias dependency", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read", "mm:network:write"], scope: { serverIds: [1], operationIds: ["routing.vrf.update"], allowGlobal: false } }), actor) const vrfAliasPrincipal = await authenticateMcpToken(vrfAliasAccess.token) nativeAliasFixtures = true try { const oldParentProtection = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,'integration')", [oldParentProtection, JSON.stringify({ kind: "gre", id: "*a1", serverId: 1, protection: "deny" })]) await assert.rejects(invokeMcpTool(vrfAliasPrincipal, "mm_vrf_plan", { serverId: 1, id: "*abc", interfaces: ["fixture-bridge"] }), { code: "PROTECTED_OBJECT" }) await pool.query("DELETE FROM mcp_protections WHERE id=$1", [oldParentProtection]) const connectedAliasRoutes = nativeAliasCatalogs["/rest/ip/route"] try { nativeAliasCatalogs["/rest/ip/route"] = [] await assert.rejects(invokeMcpTool(vrfAliasPrincipal, "mm_vrf_plan", { serverId: 1, id: "*abc", interfaces: ["fixture-bridge"] }), /dependency connected route identity/) } finally { nativeAliasCatalogs["/rest/ip/route"] = connectedAliasRoutes } const vrfAliasPlan = await invokeMcpTool(vrfAliasPrincipal, "mm_vrf_plan", { serverId: 1, id: "*abc", interfaces: ["fixture-bridge"] }) as { planId: string; vrfTicket: { ticketId: string } } assert.ok(vrfAliasPlan.vrfTicket.ticketId) const vrfAliasEvidence = JSON.parse(await decryptSecret((await pool.query("SELECT evidence FROM mcp_plans WHERE id=$1", [vrfAliasPlan.planId])).rows[0].evidence.sealed)) assert.ok(vrfAliasEvidence.objects.some((row: { kind: string; id: string }) => row.kind === "gre" && row.id === "*a1")) assert.ok(vrfAliasEvidence.objects.some((row: { kind: string; id: string }) => row.kind === "ipv4_address" && row.id === "*a3")) assert.ok(vrfAliasEvidence.objects.some((row: { kind: string; id: string }) => row.kind === "ipv4_route" && row.id === "*a4")) assert.equal(vrfAliasEvidence.snapshots.filter((row: { resource: string }) => row.resource === "vrf").length, 1) assert.equal(vrfWrites, 0) } finally { nativeAliasFixtures = false } ospfFixtures = true fixtureVrfs = [...initialVrfs] const ospfAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "OSPF dependency test", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read", "mm:network:write"], scope: { serverIds: [1], operationIds: ["ros.ospf_instance.create", "ros.ospf_instance.delete", "ros.ospf_area.delete", "ros.ospf_template.create", "ros.ospf_template.update", "ros.ospf_template.delete", "operations.recover", "ros.bfd_configuration.create"], allowGlobal: false } }), actor) const ospfPrincipal = await authenticateMcpToken(ospfAccess.token) const { readRosSnapshot: readPassiveSnapshot } = await import("./resources.js") const passiveFixture = fixtureOspf["interface-template"][0] as Record, previousPassive = passiveFixture.passive try { passiveFixture.passive = "" assert.equal((await readPassiveSnapshot(1, "ospf_template")).rows[0].passive, "true") assert.equal(passiveFixture.passive, "", "Effective reads must not rewrite the native fixture") delete passiveFixture.passive assert.equal((await readPassiveSnapshot(1, "ospf_template")).rows[0].passive, "false") } finally { if (previousPassive === undefined) delete passiveFixture.passive; else passiveFixture.passive = previousPassive } const passiveCall = async (tool: string, args: Record) => { const plan = await invokeMcpTool(ospfPrincipal, tool, args) as { planId: string } await approvePlan(plan.planId, actor, true) const key = randomUUID(), operation = await applyPlan(ospfPrincipal, plan.planId, key) return { ...plan, key, operation, state: await waitRecursiveOperation(operation.id) } } try { const created = await passiveCall("mm_ospf_template_create_plan", { serverId: 1, fields: { area: "fixture-area", interfaces: "fixture-bridge", cost: 30, passive: false } }) assert.equal(created.state, "applied") assert.equal((await readPassiveSnapshot(1, "ospf_template")).rows.find(row => row[".id"] === "*abc123")!.passive, "false") assert.equal((await passiveCall("mm_ospf_template_update_plan", { serverId: 1, id: "*abc123", fields: { passive: true, cost: 40 } })).state, "applied") const writesBefore = ospfPassiveWrites const changed = await passiveCall("mm_ospf_template_update_plan", { serverId: 1, id: "*abc123", fields: { passive: false, cost: 30 } }) assert.equal(changed.state, "applied"); assert.equal(ospfPassiveWrites, writesBefore + 2) const intended = (await pool.query("SELECT intended_state FROM mcp_operation_steps WHERE operation_id=$1 ORDER BY step_index", [changed.operation.id])).rows assert.deepEqual(intended.map(row => row.intended_state.step.fields), [{ cost: "30" }, { passive: "false" }]) assert.equal((await passiveCall("mm_operation_recovery_plan", { operationId: changed.operation.id })).state, "applied") let row = (await readPassiveSnapshot(1, "ospf_template")).rows.find(row => row[".id"] === "*abc123")! assert.equal(row.passive, "true"); assert.equal(row.cost, "40") ospfIgnoreUnset = true const beforeIgnored = ospfPassiveWrites const ignored = await passiveCall("mm_ospf_template_update_plan", { serverId: 1, id: "*abc123", fields: { passive: false, cost: 30 } }) assert.equal(ignored.state, "partial"); assert.equal(ospfPassiveWrites, beforeIgnored + 2) assert.equal((await applyPlan(ospfPrincipal, ignored.planId, ignored.key)).id, ignored.operation.id); assert.equal(ospfPassiveWrites, beforeIgnored + 2) row = (await readPassiveSnapshot(1, "ospf_template")).rows.find(row => row[".id"] === "*abc123")! assert.equal(row.passive, "true"); assert.equal(row.cost, "30") console.log("✓ OSPF passive switch: false create omits switch, true PATCH, explicit unset with separate write intent, combined fields/inverse, ignored unset is partial and never replayed") } finally { ospfIgnoreUnset = false fixtureOspf["interface-template"] = fixtureOspf["interface-template"].filter(row => row[".id"] !== "*abc123") } await assert.rejects(invokeMcpTool(ospfPrincipal, "mm_ospf_instance_delete_plan", { serverId: 1, id: "*ospf" }), /используется областью/) await assert.rejects(invokeMcpTool(ospfPrincipal, "mm_ospf_area_delete_plan", { serverId: 1, id: "*area" }), /используется шаблоном/) await assert.rejects(invokeMcpTool(ospfPrincipal, "mm_ospf_template_create_plan", { serverId: 1, fields: { area: "missing", interfaces: "fixture-gre", cost: 10 } }), /Не подтверждена зависимость/) await assert.rejects(invokeMcpTool(ospfPrincipal, "mm_bfd_configuration_create_plan", { serverId: 1, fields: { interfaces: "fixture-gre", addresses: "0.0.0.0/0", vrf: "main" } }), /один явный адрес/) const vrfProtection = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,'integration')", [vrfProtection, JSON.stringify({ kind: "vrf", id: "*MAINVRF", serverId: 1, protection: "deny" })]) await assert.rejects(invokeMcpTool(ospfPrincipal, "mm_ospf_instance_create_plan", { serverId: 1, fields: { name: "blocked-ospf", "router-id": "192.0.2.1", version: "3", vrf: "main" } })) await pool.query("DELETE FROM mcp_protections WHERE id=$1", [vrfProtection]) bgpFixtures = true const bgpAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "BGP dependency test", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:bgp:read", "mm:bgp:write", "mm:network:read"], scope: { serverIds: [1], operationIds: ["ros.bgp_instance.delete", "ros.bgp_instance.update", "ros.bgp_connection.create", "ros.vrf.delete"], allowGlobal: false } }), actor) const bgpPrincipal = await authenticateMcpToken(bgpAccess.token) await assert.rejects(invokeMcpTool(bgpPrincipal, "mm_bgp_instance_delete_plan", { serverId: 1, id: "*bgp" }), /используется подключением/) const peerFields = { name: "new-peer", instance: "missing", "remote.address": "192.0.2.2", "local.address": "192.0.2.1", "remote.as": 64513, "local.role": "ebgp" } await assert.rejects(invokeMcpTool(bgpPrincipal, "mm_bgp_connection_create_plan", { serverId: 1, fields: peerFields }), /Не подтверждена зависимость/) await assert.rejects(invokeMcpTool(bgpPrincipal, "mm_bgp_connection_create_plan", { serverId: 1, fields: { ...peerFields, instance: "fixture-bgp", "local.address": "fd00::1" } }), /одного семейства/) await assert.rejects(invokeMcpTool(bgpPrincipal, "mm_bgp_connection_create_plan", { serverId: 1, fields: { ...peerFields, instance: "fixture-bgp", "remote.address": "0.0.0.0/0" } })) const peerProtection = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,'integration')", [peerProtection, JSON.stringify({ kind: "bgp_connection", id: "*peer", serverId: 1, protection: "deny" })]) await assert.rejects(invokeMcpTool(bgpPrincipal, "mm_bgp_instance_update_plan", { serverId: 1, id: "*bgp", fields: { as: 64514 } })) await pool.query("DELETE FROM mcp_protections WHERE id=$1", [peerProtection]) const bgpCatalogAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped BGP catalog", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:bgp:read"], scope: { serverIds: [1], operationIds: ["routing.bgp.catalog.read"], allowGlobal: false } }), actor) const bgpCatalogPrincipal = await authenticateMcpToken(bgpCatalogAccess.token) fixtureBgp.session.push({ ...fixtureBgp.session[0], ".id": "*other-session", name: "other-session" }) countCatalogCalls = true; catalogCalls = 0 const bgpCatalog = await invokeMcpTool(bgpCatalogPrincipal, "mm_bgp_catalog_get", { limit: 1 }) as { serverIds: number[]; items: { serverId: number }[]; total: number; nextOffset: number; failures: unknown[] } assert.deepEqual(bgpCatalog.serverIds, [1]); assert.equal(bgpCatalog.total, 2); assert.equal(bgpCatalog.items.length, 1); assert.equal(bgpCatalog.nextOffset, 1); assert.equal(bgpCatalog.items[0].serverId, 1); assert.equal(bgpCatalog.failures.length, 0); assert.equal(catalogCalls, 1) await assert.rejects(invokeMcpTool(bgpCatalogPrincipal, "mm_bgp_catalog_get", { serverId: 2 })); assert.equal(catalogCalls, 1) healthUnavailable = true const failedBgp = await invokeMcpTool(bgpCatalogPrincipal, "mm_bgp_catalog_get", {}) as { failures: { serverId: number }[]; incomplete: boolean } assert.equal(failedBgp.failures.length, 1); assert.equal(failedBgp.failures[0].serverId, 1); assert.equal(failedBgp.incomplete, true) healthUnavailable = false; countCatalogCalls = false const bgpSessionAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "BGP session diagnostics", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:bgp:read"], scope: { serverIds: [1], operationIds: ["routing.bgp.sessions.read", "routing.bgp.sessions.raw.read"], allowGlobal: false } }), actor) const bgpSessionPrincipal = await authenticateMcpToken(bgpSessionAccess.token) bgpSessionOverride = fixtureBgp.session.map(row => ({ ...row, "prefix-count": "0", "tcp-md5-key": "never-return-bgp-key" })) countCatalogCalls = true; catalogCalls = 0 try { const parsed = await invokeMcpTool(bgpSessionPrincipal, "mm_bgp_sessions_get", { serverId: 1, limit: 1 }) as { sessions: { serverId: number; prefixesRx: number | null; prefixesTx: number | null; holdTime: number | null }[]; nextOffset: number; total: number } assert.equal(parsed.total, 2); assert.equal(parsed.nextOffset, 1); assert.equal(parsed.sessions[0].serverId, 1) assert.equal(parsed.sessions[0].prefixesRx, 0); assert.equal(parsed.sessions[0].prefixesTx, null); assert.equal(parsed.sessions[0].holdTime, null) const raw = await invokeMcpTool(bgpSessionPrincipal, "mm_bgp_sessions_raw_get", { serverId: 1, limit: 1 }) as { raw: Record[]; total: number; nextOffset: number } assert.equal(raw.total, 2); assert.equal(raw.nextOffset, 1); assert.equal(raw.raw[0]["tcp-md5-key"], "[redacted]") assert.ok(!JSON.stringify(raw).includes("never-return-bgp-key")); assert.equal(catalogCalls, 2) await assert.rejects(invokeMcpTool(bgpSessionPrincipal, "mm_bgp_sessions_raw_get", { serverId: 2 })); assert.equal(catalogCalls, 2) await assert.rejects(invokeMcpTool(bgpSessionPrincipal, "mm_bgp_sessions_get", { serverId: 2 })); assert.equal(catalogCalls, 2) await pool.query("UPDATE servers SET enabled=false WHERE id=1") await assert.rejects(invokeMcpTool(bgpSessionPrincipal, "mm_bgp_sessions_raw_get", { serverId: 1 })); assert.equal(catalogCalls, 2) await pool.query("UPDATE servers SET enabled=true WHERE id=1") for (const invalid of [[null], [fixtureBgp.session[0], fixtureBgp.session[0]]]) { bgpSessionOverride = invalid const result = await invokeMcpTool(bgpSessionPrincipal, "mm_bgp_sessions_get", {}) as { sessions: unknown[]; failures: unknown[]; incomplete: boolean } assert.deepEqual(result.sessions, []); assert.equal(result.failures.length, 1); assert.equal(result.incomplete, true) } bgpSessionOverride = [null] await assert.rejects(invokeMcpTool(bgpSessionPrincipal, "mm_bgp_sessions_raw_get", { serverId: 1 })) bgpSessionOverride = [] const emptySessions = await invokeMcpTool(bgpSessionPrincipal, "mm_bgp_sessions_get", {}) as { total: number; failures: unknown[]; incomplete: boolean } assert.equal(emptySessions.total, 0); assert.deepEqual(emptySessions.failures, []); assert.equal(emptySessions.incomplete, false) const diagnosticsAudit = (await pool.query("SELECT result FROM mcp_requests WHERE connection_id=$1", [bgpSessionAccess.connection.id])).rows assert.ok(!JSON.stringify(diagnosticsAudit).includes("never-return-bgp-key")) } finally { bgpSessionOverride = undefined; countCatalogCalls = false; fixtureBgp.session.pop() await pool.query("UPDATE servers SET enabled=true WHERE id=1") } console.log("✓ Shared BGP sessions/raw: scope before polling, bounded pages, known zero versus unknown, source/duplicate failure preserved, disabled raw refused and native keys absent from result/audit") bgpFixtures = false const catalogAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped OSPF catalog", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read"], scope: { serverIds: [1], operationIds: ["routing.ospf.catalog.read"], allowGlobal: false } }), actor) const catalogPrincipal = await authenticateMcpToken(catalogAccess.token) countCatalogCalls = true; catalogCalls = 0; ospfTemplateFlagReads = 0 fixtureOspf.instance.push({ ...fixtureOspf.instance[0], ".id": "*another", name: "another-instance" }) const ospfCatalog = await invokeMcpTool(catalogPrincipal, "mm_ospf_catalog_get", { limit: 1 }) as { serverIds: number[]; totalServers: number; failures: unknown[]; collections: { instances: { items: { serverId: number }[]; total: number; nextOffset: number | null } } } assert.deepEqual(ospfCatalog.serverIds, [1]); assert.equal(ospfCatalog.totalServers, 1); assert.equal(ospfCatalog.failures.length, 0) assert.equal(ospfCatalog.collections.instances.total, 2); assert.equal(ospfCatalog.collections.instances.items.length, 1); assert.equal(ospfCatalog.collections.instances.nextOffset, 1) assert.equal(ospfCatalog.collections.instances.items[0].serverId, 1); assert.equal(catalogCalls, 9, "Seven collections and two complementary passive query receipts expected") assert.equal(ospfTemplateFlagReads, 2) await assert.rejects(invokeMcpTool(catalogPrincipal, "mm_ospf_catalog_get", { serverId: 2 })) assert.equal(catalogCalls, 9); assert.equal(ospfTemplateFlagReads, 2) const stateAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped OSPF/BFD state", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read"], scope: { serverIds: [1], operationIds: ["routing.ospf.state.read", "routing.bfd.sessions.read"], allowGlobal: false } }), actor) const statePrincipal = await authenticateMcpToken(stateAccess.token) type StatePage = { totalServers: number; servers: { serverId: number; collections: Record[]; total: number; nextOffset: number | null }> | null }[]; failures: unknown[]; incomplete: boolean } const stateBefore = catalogCalls const ospfStatePage = await invokeMcpTool(statePrincipal, "mm_ospf_state_get", { limit: 1 }) as StatePage assert.equal(catalogCalls, stateBefore + 9); assert.equal(ospfTemplateFlagReads, 4); assert.equal(ospfStatePage.totalServers, 1); assert.deepEqual(ospfStatePage.failures, []) assert.equal(ospfStatePage.servers[0].serverId, 1); assert.equal(ospfStatePage.servers[0].collections!.instances.total, 2) assert.equal(ospfStatePage.servers[0].collections!.instances.nextOffset, 1); assert.equal(ospfStatePage.incomplete, true) assert.equal(ospfStatePage.servers[0].collections!.areas.items[0].areaId, "0.0.0.0") const bfd = await invokeMcpTool(statePrincipal, "mm_bfd_sessions_get", {}) as StatePage assert.equal(catalogCalls, stateBefore + 10); assert.equal(bfd.servers[0].collections!.sessions.items[0].packetsRx, null) assert.equal(bfd.servers[0].collections!.sessions.items[0].state, "Up") await assert.rejects(invokeMcpTool(statePrincipal, "mm_bfd_sessions_get", { serverId: 2 })); assert.equal(catalogCalls, stateBefore + 10); assert.equal(ospfTemplateFlagReads, 4) await pool.query("UPDATE servers SET enabled=false WHERE id=1") const disabledState = await invokeMcpTool(statePrincipal, "mm_ospf_state_get", { serverId: 1 }) as StatePage assert.equal(disabledState.totalServers, 0); assert.deepEqual(disabledState.servers, []); assert.equal(catalogCalls, stateBefore + 10); assert.equal(ospfTemplateFlagReads, 4) await pool.query("UPDATE servers SET enabled=true WHERE id=1") healthUnavailable = true const failedBfd = await invokeMcpTool(statePrincipal, "mm_bfd_sessions_get", {}) as StatePage assert.equal(failedBfd.servers[0].collections, null); assert.equal(failedBfd.failures.length, 1); assert.equal(failedBfd.incomplete, true) const failedState = await invokeMcpTool(statePrincipal, "mm_ospf_state_get", {}) as StatePage assert.equal(failedState.servers[0].collections, null); assert.equal(failedState.failures.length, 1) const failedCatalog = await invokeMcpTool(catalogPrincipal, "mm_ospf_catalog_get", { serverId: 1 }) as { failures: { serverId: number }[]; incomplete: boolean } assert.equal(failedCatalog.failures.length, 1); assert.equal(failedCatalog.failures[0].serverId, 1); assert.equal(failedCatalog.incomplete, true) healthUnavailable = false; countCatalogCalls = false; fixtureOspf.instance.pop() console.log("✓ Shared OSPF/BFD state adapters: strict observed fields and areas, one scoped poll, BFD-only query, per-device paging, source failure and no poll for hidden/disabled devices") await acceptOspfRouteProofMcp(actor) await acceptWireguardPeerDeletion(actor) await acceptWireguardPeerUpdate(actor) await acceptWireguardInterfaceDeletion(actor) await (await import("./wireguard-interface-create-acceptance.js")).acceptWireguardInterfaceCreation(actor) await (await import("./routing-community-context-acceptance.js")).acceptRouteCommunityContextMcp(actor) await (await import("./wireguard-peer-create-acceptance.js")).acceptWireguardPeerCreation(actor) await (await import("../../test/mcp-flow-analytics.js")).verifyMcpFlowAnalytics(actor) await (await import("../../test/mcp-flow-map.js")).verifyMcpFlowMap(actor) await (await import("../../test/mcp-flow-live.js")).verifyMcpFlowLive(actor) await (await import("../../test/mcp-flow-overlay.js")).verifyMcpFlowOverlay(actor) await (await import("../../test/mcp-acme-test.js")).verifyMcpAcmeTest(actor) await (await import("../../test/mcp-acme-settings.js")).verifyMcpAcmeSettings(actor) await (await import("../../test/mcp-geoip-update.js")).verifyMcpGeoipUpdate(actor) await (await import("../../test/mcp-telegram-settings.js")).verifyMcpTelegramSettings(actor) await (await import("../../test/mcp-telegram-test.js")).verifyMcpTelegramTest(actor) ospfFixtures = false interfaceFixtures = false; fixtureVrfs = []; ignoreVrfPlacement = false console.log("✓ GRE/VRF: parent protection, dependent delete rejection, ordered placement/read-back, inverse cleanup and reorder conflict") const fwAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped firewall state", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read"], scope: { serverIds: [1], operationIds: ["firewall.state.read"], allowGlobal: false } }), actor) const fwPrincipal = await authenticateMcpToken(fwAccess.token) type FirewallPage = { serverIds: number[]; collections: { rules: { items: { enabled: boolean | null; hits: number | null; log: boolean | null; matches: { srcAddress: string | null; inInterface: string; outInterface: string } }[]; total: number; nextOffset: number | null }; addressLists: { total: number } }; failures: unknown[]; incomplete: boolean; unknownRuleStates: number } const fwRevisions = async () => Number((await pool.query("SELECT count(*)::int AS n FROM config_revisions WHERE server_id=1 AND section='firewall'")).rows[0].n) const revisionsBeforeUnknown = await fwRevisions() firewallStateFixtures = true; firewallStateCalls = 0 try { const unknownFw = await invokeMcpTool(fwPrincipal, "mm_firewall_state_get", { limit: 1 }) as FirewallPage assert.equal(firewallStateCalls, 10); assert.deepEqual(unknownFw.serverIds, [1]); assert.deepEqual(unknownFw.failures, []) assert.equal(unknownFw.collections.rules.total, 8); assert.equal(unknownFw.collections.addressLists.total, 2) assert.equal(unknownFw.collections.rules.items[0].hits, 0); assert.equal(unknownFw.collections.rules.items[0].enabled, null) assert.equal(unknownFw.collections.rules.items[0].log, null); assert.equal(unknownFw.unknownRuleStates, 8) assert.equal(unknownFw.collections.rules.items[0].matches.srcAddress, null) assert.equal(unknownFw.collections.rules.items[0].matches.inInterface, "ether1"); assert.equal(unknownFw.collections.rules.items[0].matches.outInterface, "ether2") assert.equal(unknownFw.collections.rules.nextOffset, 1); assert.equal(unknownFw.incomplete, true) assert.equal(await fwRevisions(), revisionsBeforeUnknown, "Unknown flags cannot be persisted as a confirmed observed revision") assert.ok(!JSON.stringify(unknownFw).includes("never-return-firewall-key")) await assert.rejects(invokeMcpTool(fwPrincipal, "mm_firewall_state_get", { serverId: 2 })); assert.equal(firewallStateCalls, 10) await pool.query("UPDATE servers SET enabled=false WHERE id=1") assert.deepEqual((await invokeMcpTool(fwPrincipal, "mm_firewall_state_get", {}) as FirewallPage).serverIds, []); assert.equal(firewallStateCalls, 10) await pool.query("UPDATE servers SET enabled=true WHERE id=1") firewallKnownFlags = true const knownFw = await invokeMcpTool(fwPrincipal, "mm_firewall_state_get", { offset: 1, limit: 1 }) as FirewallPage assert.equal(knownFw.collections.rules.items[0].enabled, true); assert.equal(knownFw.collections.rules.items[0].log, false); assert.equal(knownFw.unknownRuleStates, 0) assert.equal(await fwRevisions(), revisionsBeforeUnknown + 1) firewallFailedChunk = "/rest/ipv6/firewall/nat" const partialFw = await invokeMcpTool(fwPrincipal, "mm_firewall_state_get", {}) as FirewallPage assert.equal(partialFw.failures.length, 1); assert.equal(partialFw.collections.rules.total, 7); assert.equal(partialFw.incomplete, true) assert.equal(await fwRevisions(), revisionsBeforeUnknown + 1) firewallFailedChunk = ""; firewallDuplicate = true const invalidFw = await invokeMcpTool(fwPrincipal, "mm_firewall_state_get", {}) as FirewallPage assert.equal(invalidFw.failures.length, 1); assert.equal(invalidFw.collections.rules.total, 7) assert.equal(await fwRevisions(), revisionsBeforeUnknown + 1) } finally { firewallStateFixtures = false; firewallKnownFlags = false; firewallFailedChunk = ""; firewallDuplicate = false await pool.query("UPDATE servers SET enabled=true WHERE id=1") } console.log("✓ Firewall shared catalog: exactly 10 scoped reads, legacy versus nullable flags/counters, preserved matches, paging, failed/duplicate slices, no secret reflection or speculative observed revision") const fwMoveAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Firewall move plans", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], scope: { serverIds: [1], operationIds: ["firewall.rules.move"] }, limits: { mutationsPerHour: 50 } }), actor) const fwMovePrincipal = await authenticateMcpToken(fwMoveAccess.token), fwMoveArgs = { serverId: 1, move: { family: "ip", table: "filter", rosId: "*1", destinationRosId: "*3" } } const waitFirewallMove = async (operationId: string) => { for (let attempt = 0; attempt < 200; attempt++) { const state = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [operationId])).rows[0].state as string if (!["pending", "running"].includes(state)) return state await delay(25) } throw new Error("Firewall move did not complete") } firewallMoveFixtures = true; firewallMoveCommands = 0 try { const { withConfigMutationLock: fwConfigLock } = await import("../../services/config-mutation-lock.js") const { moveFirewallRule: sharedFirewallMove } = await import("../../services/firewall-rule-move.js") const { server: fwServer } = await (await import("./resources.js")).routerClient(1) let releaseFwLease!: () => void, markFwLeaseReady!: () => void const fwLeaseReady = new Promise(resolve => { markFwLeaseReady = resolve }) const fwLeaseRelease = new Promise(resolve => { releaseFwLease = resolve }) const fwLease = fwConfigLock([1], async () => { markFwLeaseReady(); await fwLeaseRelease }) await fwLeaseReady try { await assert.rejects(sharedFirewallMove(fwServer, { family: "ip", table: "filter", rosId: "*1", destinationRosId: "*3" }), /изменяется другой операцией/); assert.equal(firewallMoveCommands, 0) } finally { releaseFwLease(); await fwLease } await assert.rejects(sharedFirewallMove({ ...fwServer, host: "changed.invalid" }, { family: "ip", table: "filter", rosId: "*1", destinationRosId: "*3" }), /Источник Firewall изменился/) assert.equal(firewallMoveCommands, 0) await assert.rejects(invokeMcpTool(fwMovePrincipal, "mm_firewall_rule_move_plan", { ...fwMoveArgs, serverId: 2 })) const protectedMove = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,$3)", [protectedMove, JSON.stringify({ kind: "ip_filter", id: "*2", serverId: 1, protection: "deny" }), actor.id]) await assert.rejects(invokeMcpTool(fwMovePrincipal, "mm_firewall_rule_move_plan", fwMoveArgs)) await pool.query("DELETE FROM mcp_protections WHERE id=$1", [protectedMove]) const staleMove = await invokeMcpTool(fwMovePrincipal, "mm_firewall_rule_move_plan", fwMoveArgs) as { planId: string } await approvePlan(staleMove.planId, actor, true) firewallMoveRows[0]["connection-mark"] = "externally-changed" assert.equal(await waitFirewallMove((await applyPlan(fwMovePrincipal, staleMove.planId, randomUUID())).id), "rejected"); assert.equal(firewallMoveCommands, 0) firewallMoveRows[0]["connection-mark"] = "keep-exactly" const movePlan = await invokeMcpTool(fwMovePrincipal, "mm_firewall_rule_move_plan", fwMoveArgs) as { planId: string } await approvePlan(movePlan.planId, actor, true) const moveKey = randomUUID(), moved = await applyPlan(fwMovePrincipal, movePlan.planId, moveKey) assert.equal(await waitFirewallMove(moved.id), "applied"); assert.equal(firewallMoveCommands, 1) assert.deepEqual(firewallMoveRows.map(row => row[".id"]), ["*2", "*1", "*3"]) assert.ok(firewallMoveRows.every(row => row["connection-mark"] === "keep-exactly")) assert.equal((await applyPlan(fwMovePrincipal, movePlan.planId, moveKey)).id, moved.id); assert.equal(firewallMoveCommands, 1) const movedStep = (await pool.query("SELECT result FROM mcp_operation_steps WHERE operation_id=$1", [moved.id])).rows[0].result assert.equal(movedStep.verified, true); assert.equal(movedStep.revisionRecorded, true) await assert.rejects(invokeMcpTool(fwMovePrincipal, "mm_firewall_rule_move_plan", fwMoveArgs), /уже находится/) const ignoredArgs = { ...fwMoveArgs, move: { ...fwMoveArgs.move, destinationRosId: "*2" } } const ignoredMove = await invokeMcpTool(fwMovePrincipal, "mm_firewall_rule_move_plan", ignoredArgs) as { planId: string } await approvePlan(ignoredMove.planId, actor, true); firewallIgnoreMove = true assert.equal(await waitFirewallMove((await applyPlan(fwMovePrincipal, ignoredMove.planId, randomUUID())).id), "partial"); assert.equal(firewallMoveCommands, 2) firewallIgnoreMove = false; firewallMoveRows[0].dynamic = "true" await assert.rejects(invokeMcpTool(fwMovePrincipal, "mm_firewall_rule_move_plan", ignoredArgs), /статического/); assert.equal(firewallMoveCommands, 2) } finally { firewallMoveFixtures = false; firewallIgnoreMove = false firewallMoveRows = [1, 2, 3].map(id => ({ ".id": "*" + id, chain: "fixture-move", action: "accept", disabled: "true", dynamic: "false", log: "false", comment: "rule " + id, packets: "0", "connection-mark": "keep-exactly" })) } console.log("✓ Firewall move uses shared REST service: exact ordered proof, opaque matcher drift, protected table rows, counter changes tolerated, native/read-back/revision, no-op/dynamic rejection, partial and no replay") const fwWriteAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Firewall form writes", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], scope: { serverIds: [1], operationIds: ["firewall.rules.create", "firewall.rules.update", "firewall.rules.toggle", "firewall.rules.delete"] }, limits: { mutationsPerHour: 50 } }), actor) const fwWritePrincipal = await authenticateMcpToken(fwWriteAccess.token) const fwFields = { chain: "fixture-form", action: "accept", disabled: true, comment: "created", tlsHost: "*.example.invalid", layer7Proto: "fixture" } const fwInput = (operation: string, extra: Record) => ({ serverId: 1, rule: { family: "ip", table: "filter", operation, ...extra } }) const fwFormPlan = async (operation: string, extra: Record) => invokeMcpTool(fwWritePrincipal, "mm_firewall_rule_form_" + operation + "_plan", fwInput(operation, extra)) as Promise<{ planId: string }> const fwFormApply = async (planId: string) => { await approvePlan(planId, actor, true); const key = randomUUID(), op = await applyPlan(fwWritePrincipal, planId, key); return { op, key, state: await waitFirewallMove(op.id) } } const fwFormProtection = randomUUID() firewallWriteFixtures = true; firewallWriteCommands = 0; firewallWriteRows = [{ ".id": "*A", chain: "fixture-form", action: "drop", disabled: "true", dynamic: "false", log: "false", "connection-mark": "opaque" }] try { await assert.rejects(invokeMcpTool(fwWritePrincipal, "mm_firewall_rule_form_create_plan", { ...fwInput("create", { fields: fwFields }), serverId: 2 })); assert.equal(firewallWriteCommands, 0) await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,$3)", [fwFormProtection, JSON.stringify({ kind: "ethernet", id: "*parent", serverId: 1, protection: "deny" }), actor.id]) await assert.rejects(fwFormPlan("create", { fields: fwFields })); assert.equal(firewallWriteCommands, 0) await pool.query("DELETE FROM mcp_protections WHERE id=$1", [fwFormProtection]) const { writeFirewallRule: writeSharedFirewall } = await import("../../services/firewall-rule-write.js"), { withConfigMutationLock: holdFormLock } = await import("../../services/config-mutation-lock.js") const formSource = (await (await import("./resources.js")).routerClient(1)).server await assert.rejects(writeSharedFirewall({ ...formSource, host: "changed.invalid" }, { operation: "create", family: "ip", table: "filter", fields: fwFields }), /Источник/); assert.equal(firewallWriteCommands, 0) let unlockForm!: () => void, readyForm!: () => void const lockFormReady = new Promise(resolve => { readyForm = resolve }), lockFormRelease = new Promise(resolve => { unlockForm = resolve }) const formLease = holdFormLock([1], async () => { readyForm(); await lockFormRelease }); await lockFormReady try { await assert.rejects(writeSharedFirewall(formSource, { operation: "create", family: "ip", table: "filter", fields: fwFields }), /другой операцией/); assert.equal(firewallWriteCommands, 0) } finally { unlockForm(); await formLease } const created = await fwFormApply((await fwFormPlan("create", { fields: fwFields })).planId) assert.equal(created.state, "applied"); assert.equal(firewallWriteCommands, 1); assert.equal(firewallWriteRows.length, 2) assert.equal(firewallWriteRows[1]["tls-host"], "*.example.invalid"); assert.equal(firewallWriteRows[1]["layer7-protocol"], "fixture") assert.equal((await applyPlan(fwWritePrincipal, (await pool.query("SELECT plan_id FROM mcp_operations WHERE id=$1", [created.op.id])).rows[0].plan_id, created.key)).id, created.op.id); assert.equal(firewallWriteCommands, 1) const stale = await fwFormPlan("toggle", { rosId: "*C", disabled: false }); firewallWriteRows[0]["connection-mark"] = "changed" assert.equal((await fwFormApply(stale.planId)).state, "rejected"); assert.equal(firewallWriteCommands, 1); firewallWriteRows[0]["connection-mark"] = "opaque" const updated = await fwFormApply((await fwFormPlan("update", { rosId: "*C", fields: { ...fwFields, comment: "edited", action: "drop" } })).planId) assert.equal(updated.state, "applied"); assert.equal(firewallWriteRows[1].comment, "edited"); assert.equal(firewallWriteRows[0]["connection-mark"], "opaque") const toggled = await fwFormApply((await fwFormPlan("toggle", { rosId: "*C", disabled: false })).planId) assert.equal(toggled.state, "applied"); assert.equal(firewallWriteRows[1].disabled, "false") const receipt = (await pool.query("SELECT result FROM mcp_operation_steps WHERE operation_id=$1", [toggled.op.id])).rows[0].result assert.equal(receipt.verified, true); assert.equal(receipt.revisionRecorded, true) firewallWriteIgnored = true const ignored = await fwFormApply((await fwFormPlan("toggle", { rosId: "*C", disabled: true })).planId) assert.equal(ignored.state, "partial"); firewallWriteIgnored = false assert.equal((await fwFormApply((await fwFormPlan("delete", { rosId: "*C" })).planId)).state, "applied"); assert.deepEqual(firewallWriteRows.map(row => row[".id"]), ["*A"]) firewallWriteRows[0].dynamic = "true"; await assert.rejects(fwFormPlan("delete", { rosId: "*A" }), /статическое/) firewallWriteRows[0].dynamic = "false" const { default: FastifyForm } = await import("fastify"), { validatorCompiler: formValidator, serializerCompiler: formSerializer } = await import("@fastify/type-provider-zod") const formHttp = FastifyForm(); formHttp.setValidatorCompiler(formValidator); formHttp.setSerializerCompiler(formSerializer) await formHttp.register((await import("../../routes/firewall.js")).default, { prefix: "/api" }) try { for (const rosId of ["*A,*B", "*A/../../", "not-native"]) { const invalid = await formHttp.inject({ method: "DELETE", url: "/api/firewall/rules", payload: { serverId: "1", family: "ip", table: "filter", rosId } }); assert.equal(invalid.statusCode, 400) } for (const [method, payload] of [["POST", { ...fwFields }], ["PUT", { ...fwFields, rosId: "*C", comment: "REST edit" }], ["PATCH", { rosId: "*C", disabled: false }], ["DELETE", { rosId: "*C" }]] as const) { const response = await formHttp.inject({ method, url: "/api/firewall/rules", payload: { serverId: "1", family: "ip", table: "filter", ...payload } }) assert.equal(response.statusCode, method === "POST" ? 201 : 200, response.body); assert.equal(response.json().ok, true); assert.equal(response.json().revisionRecorded, true) } assert.deepEqual(firewallWriteRows.map(row => row[".id"]), ["*A"]) } finally { await formHttp.close() } } finally { firewallWriteFixtures = false; firewallWriteIgnored = false; await pool.query("DELETE FROM mcp_protections WHERE id=$1", [fwFormProtection]) } console.log("✓ Firewall form REST service: create/edit/toggle/delete, TLS/L7, opaque matcher preservation, scope/protected parent, stale proof, read-back/revision, partial and idempotency") const addrAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Address-list forms", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], scope: { serverIds: [1], operationIds: ["firewall.addressLists.create", "firewall.addressLists.update", "firewall.addressLists.toggle", "firewall.addressLists.delete"] }, limits: { mutationsPerHour: 60 } }), actor) const addrPrincipal = await authenticateMcpToken(addrAccess.token), addrFields = { list: "fixture-list", address: "192.0.2.2/32", disabled: true, comment: "fixture" } const addrPlan = async (operation: string, extra: Record) => invokeMcpTool(addrPrincipal, "mm_firewall_address_list_" + operation + "_plan", { serverId: 1, address: { family: "ip", operation, ...extra } }) as Promise<{ planId: string }> const addrApply = async (planId: string) => { await approvePlan(planId, actor, true); const key = randomUUID(), op = await applyPlan(addrPrincipal, planId, key); return { op, key, state: await waitFirewallMove(op.id) } } const addressProtection = randomUUID() addressFixtures = true; addressCommands = 0; addressRows = [{ ".id": "*A", list: "unrelated", address: "198.51.100.1", disabled: "true", dynamic: "false", comment: "keep" }] try { await assert.rejects(invokeMcpTool(addrPrincipal, "mm_firewall_address_list_create_plan", { serverId: 2, address: { operation: "create", family: "ip", fields: addrFields } })); assert.equal(addressCommands, 0) await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,$3)", [addressProtection, JSON.stringify({ kind: "ip_filter", id: "*rule-using-list", serverId: 1, protection: "deny" }), actor.id]) await assert.rejects(addrPlan("create", { fields: addrFields })); assert.equal(addressCommands, 0); await pool.query("DELETE FROM mcp_protections WHERE id=$1", [addressProtection]) const createdPlan = await addrPlan("create", { fields: addrFields }), created = await addrApply(createdPlan.planId) assert.equal(created.state, "applied"); assert.equal(addressRows[1].address, "192.0.2.2"); assert.equal(addressCommands, 1) assert.equal((await applyPlan(addrPrincipal, createdPlan.planId, created.key)).id, created.op.id); assert.equal(addressCommands, 1) const stale = await addrPlan("toggle", { rosId: "*C", disabled: false }); addressRows[0].comment = "foreign drift" assert.equal((await addrApply(stale.planId)).state, "rejected"); assert.equal(addressCommands, 1); addressRows[0].comment = "keep" assert.equal((await addrApply((await addrPlan("update", { rosId: "*C", fields: { ...addrFields, comment: "edited" } })).planId)).state, "applied") assert.equal((await addrApply((await addrPlan("toggle", { rosId: "*C", disabled: false })).planId)).state, "applied") addressIgnored = true; assert.equal((await addrApply((await addrPlan("delete", { rosId: "*C" })).planId)).state, "partial"); addressIgnored = false assert.equal((await addrApply((await addrPlan("delete", { rosId: "*C" })).planId)).state, "applied") assert.equal((await addrApply((await addrPlan("create", { fields: { ...addrFields, timeout: "1m" } })).planId)).state, "applied") const timerPlan = await addrPlan("delete", { rosId: "*T" }), writesBeforeTimer = addressCommands addressExpiry += 60000 assert.equal((await addrApply(timerPlan.planId)).state, "rejected"); assert.equal(addressCommands, writesBeforeTimer) const natural = await addrPlan("delete", { rosId: "*T" }); await delay(1200) assert.equal((await addrApply(natural.planId)).state, "applied"); assert.deepEqual(addressRows.map(row => row[".id"]), ["*A"]) const { default: AddrFastify } = await import("fastify"), { validatorCompiler: addrValidator, serializerCompiler: addrSerializer } = await import("@fastify/type-provider-zod") const addrHttp = AddrFastify(); addrHttp.setValidatorCompiler(addrValidator); addrHttp.setSerializerCompiler(addrSerializer); await addrHttp.register((await import("../../routes/firewall.js")).default, { prefix: "/api" }) try { for (const [method, payload] of [["POST", { ...addrFields }], ["PUT", { ...addrFields, rosId: "*C", comment: "REST edited" }], ["PATCH", { rosId: "*C", disabled: false }], ["DELETE", { rosId: "*C" }]] as const) { const response = await addrHttp.inject({ method, url: "/api/firewall/address-lists", payload: { serverId: "1", family: "ip", ...payload } }); assert.equal(response.statusCode, method === "POST" ? 201 : 200, response.body); assert.equal(response.json().revisionRecorded, true) } } finally { await addrHttp.close() } } finally { addressFixtures = false; addressIgnored = false; await pool.query("DELETE FROM mcp_protections WHERE id=$1", [addressProtection]) } console.log("✓ Address-list shared REST/MCP forms: canonical hosts, CRUD, protected dependent rule, scope/stale/partial/idempotency, finite lifetime and countdown versus renewal drift") const fwRevisionAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Firewall revision restore", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], scope: { serverIds: [1], operationIds: ["firewall.revision.restore"] }, limits: { mutationsPerHour: 100 } }), actor) const fwRevisionPrincipal = await authenticateMcpToken(fwRevisionAccess.token), fwRevisionId = randomUUID() const { canonicalFirewallSnapshot: canonFw } = await import("../../services/entity-snapshots.js"), { fingerprintPayload: fwRevHash } = await import("../../services/config-revisions.js") const fwSnapshot = canonFw({ rules: [1, 2].map(() => ({ family: "ip" as const, table: "filter" as const, chain: "fixture-restore", action: "accept", disabled: true, native: { "connection-mark": "opaque" } })), addressLists: [{ family: "ip6", list: "fixture-restore", address: "2001:db8::1", disabled: true }] }) await pool.query("INSERT INTO config_revisions(id,server_id,section,source,fingerprint,payload) VALUES($1,1,'firewall','apply',$2,$3::jsonb)", [fwRevisionId, fwRevHash(fwSnapshot), JSON.stringify(fwSnapshot)]) const fwRevisionArgs = { serverId: 1, revisionId: fwRevisionId } const fwRevisionPlan = async () => invokeMcpTool(fwRevisionPrincipal, "mm_firewall_revision_restore_plan", fwRevisionArgs) as Promise<{ planId: string }> const fwRevisionApply = async (planId: string) => { await approvePlan(planId, actor, true); const key = randomUUID(), op = await applyPlan(fwRevisionPrincipal, planId, key); return { op, key, state: await waitFirewallMove(op.id) } } fwRestoreFixtures = true; fwRestoreCommands = 0; fwRestoreCollections = { "/rest/ip/firewall/filter": [{ ".id": "*D", chain: "dynamic", action: "accept", disabled: "false", dynamic: "true", log: "false" }, { ".id": "*OLD", chain: "fixture-restore", action: "drop", disabled: "true", dynamic: "false", log: "false", "connection-mark": "original" }] } try { await assert.rejects(invokeMcpTool(fwRevisionPrincipal, "mm_firewall_revision_restore_plan", { ...fwRevisionArgs, serverId: 2 })); assert.equal(fwRestoreCommands, 0) await pool.query("UPDATE config_revisions SET fingerprint='bad' WHERE id=$1", [fwRevisionId]); await assert.rejects(fwRevisionPlan()); assert.equal(fwRestoreCommands, 0) await pool.query("UPDATE config_revisions SET fingerprint=$2 WHERE id=$1", [fwRevisionId, fwRevHash(fwSnapshot)]) const stale = await fwRevisionPlan(); fwRestoreCollections["/rest/ip/firewall/filter"][1]["connection-mark"] = "drift" assert.equal((await fwRevisionApply(stale.planId)).state, "rejected"); assert.equal(fwRestoreCommands, 0); fwRestoreCollections["/rest/ip/firewall/filter"][1]["connection-mark"] = "original" const protection = randomUUID(); await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,$3)", [protection, JSON.stringify({ kind: "ethernet", id: "*protected-dependency", serverId: 1, protection: "deny" }), actor.id]) await assert.rejects(fwRevisionPlan()); await pool.query("DELETE FROM mcp_protections WHERE id=$1", [protection]); assert.equal(fwRestoreCommands, 0) const low = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Firewall restore budget", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], scope: { serverIds: [1], operationIds: ["firewall.revision.restore"] }, limits: { mutationsPerHour: 2 } }), actor) await assert.rejects(invokeMcpTool(await authenticateMcpToken(low.token), "mm_firewall_revision_restore_plan", fwRevisionArgs), error => error instanceof Error && "code" in error && error.code === "BUDGET_EXCEEDED"); assert.equal(fwRestoreCommands, 0) const exact = await fwRevisionPlan(), applied = await fwRevisionApply(exact.planId) assert.equal(applied.state, "applied"); assert.equal(fwRestoreCommands, 4) assert.deepEqual(fwRestoreCollections["/rest/ip/firewall/filter"].map(row => row["connection-mark"] ?? "dynamic"), ["dynamic", "opaque", "opaque"]) assert.equal((await applyPlan(fwRevisionPrincipal, exact.planId, applied.key)).id, applied.op.id); assert.equal(fwRestoreCommands, 4) const step = (await pool.query("SELECT result FROM mcp_operation_steps WHERE operation_id=$1", [applied.op.id])).rows[0].result assert.equal(step.revisionRecorded, true); assert.equal(step.completedCommands, 4) const enabledRevision = randomUUID(), enabledSnapshot = { ...fwSnapshot, rules: fwSnapshot.rules.map(row => ({ ...row, disabled: false })) } await pool.query("INSERT INTO config_revisions(id,server_id,section,source,fingerprint,payload) VALUES($1,1,'firewall','apply',$2,$3::jsonb)", [enabledRevision, fwRevHash(enabledSnapshot), JSON.stringify(enabledSnapshot)]) const activatePlan = await invokeMcpTool(fwRevisionPrincipal, "mm_firewall_revision_restore_plan", { ...fwRevisionArgs, revisionId: enabledRevision }) as { planId: string } const activated = await fwRevisionApply(activatePlan.planId); assert.equal(activated.state, "applied") assert.ok(fwRestoreCollections["/rest/ip/firewall/filter"].filter(row => row.dynamic === "false").every(row => row.disabled === "false")) const activationStep = (await pool.query("SELECT result FROM mcp_operation_steps WHERE operation_id=$1", [activated.op.id])).rows[0].result assert.equal(activationStep.commandCount, 8); assert.equal(activationStep.completedCommands, 8); assert.equal(activationStep.acknowledgedCommands, 8) const oldIds = fwRestoreCollections["/rest/ip/firewall/filter"].map(row => row[".id"]) fwRestoreIgnored = true; const beforeIgnored = fwRestoreCommands assert.equal((await fwRevisionApply((await fwRevisionPlan()).planId)).state, "partial"); assert.equal(fwRestoreCommands, beforeIgnored + 1); assert.deepEqual(fwRestoreCollections["/rest/ip/firewall/filter"].map(row => row[".id"]), oldIds); fwRestoreIgnored = false const { default: RestoreFastify } = await import("fastify"), { validatorCompiler: restoreValidator, serializerCompiler: restoreSerializer } = await import("@fastify/type-provider-zod") const restoreHttp = RestoreFastify(); restoreHttp.setValidatorCompiler(restoreValidator); restoreHttp.setSerializerCompiler(restoreSerializer); await restoreHttp.register((await import("../../routes/firewall.js")).default, { prefix: "/api" }) try { const response = await restoreHttp.inject({ method: "POST", url: "/api/firewall/revisions/" + fwRevisionId + "/restore", payload: { serverId: "1" } }); assert.equal(response.statusCode, 200, response.body); assert.equal(response.json().ok, true); assert.equal(response.json().rules.length, 3) } finally { await restoreHttp.close() } fwRestoreLost = true; const beforeLost = fwRestoreCommands assert.equal((await fwRevisionApply((await fwRevisionPlan()).planId)).state, "unknown"); assert.equal(fwRestoreCommands, beforeLost + 1) assert.ok(fwRestoreCollections["/rest/ip/firewall/filter"].some(row => row.dynamic === "false" && row.disabled === "true")) const beforeRevoke = fwRestoreCommands; fwRestoreRevoke = fwRevisionAccess.connection.id assert.equal((await fwRevisionApply((await fwRevisionPlan()).planId)).state, "unknown"); assert.equal(fwRestoreCommands, beforeRevoke + 1) } finally { fwRestoreFixtures = false; fwRestoreIgnored = false; fwRestoreLost = false; fwRestoreRevoke = undefined } console.log("✓ Firewall revision shared REST/MCP restore: strict scoped revision, full matcher/order/multiplicity, disabled staging, per-command read-back, protected/budget/stale, ignored/lost stop, revocation and no replay") const { ensurePartitionFor: flowPartition } = await import("../../db/partitions.js") const { getTrafficFlowSettingsRow: flowSettingsRow, invalidateTrafficFlowSettingsCache: invalidateFlowSettings } = await import("../../services/traffic-flow-settings.js") const { getServerCatalog: flowServerCatalog, invalidateFlowCatalogCache: invalidateFlowCatalog } = await import("../../services/traffic-flow-topology.js") const { readFlowCatalogWindow: flowWindow } = await import("../../services/traffic-flow-catalog-read.js") const { listStoredFlowRows: flowRows, ingestParsedFlowsForServerForTests: addPendingFlow, resetFlowRingsForTests: clearPendingFlows } = await import("../../services/traffic-flow-ingest.js") const { getFlowMonthly: flowMonthly } = await import("../../services/traffic-flow-analytics.js") const { disableRipeEnqueueForTests: disableFlowRipe, resetRipeCacheForTests: resetFlowRipe } = await import("../../services/traffic-flow-ripe.js") await flowSettingsRow() const savedFlow = (await pool.query("SELECT peers_json,host_private_key,top_n FROM traffic_flow_settings WHERE id=1")).rows[0] const flowOpIds = ["traffic.flow.talkers.read", "traffic.flow.exporters.read", "traffic.flow.clients.read", "traffic.flow.monthly.read"] const flowActor = { ...actor, id: "netflow-reports-owner", permissions: [...actor.permissions, "mm:traffic:read"] } const makeFlowAccess = async (ids: number[]) => createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped flow reports", mode: "observer", expiresAt: new Date(Date.now()+1800000).toISOString(), permissions: ["mm:traffic:read"], scope: { serverIds: ids, operationIds: flowOpIds, allowGlobal: false }, limits: { callsPerMinute: 120 } }), flowActor) const flowAccess = await makeFlowAccess([1]), flowPrincipal = await authenticateMcpToken(flowAccess.token) const bucket = new Date(Date.now()-60000), oldBucket = new Date(Date.now()-7200000), futureBucket = new Date(Date.now()+3600000) const flowMonth = new Date().toISOString().slice(0,7), flowDay = flowMonth+"-01", flowUsers = ["nf-a","nf-b","nf-peer","nf-mixed","nf-disabled","nf-split"] for (const time of [bucket,oldBucket,futureBucket]) await flowPartition(pool,"flow_buckets","day",time) await flowPartition(pool,"flow_daily_dims","month",new Date(flowDay+"T00:00:00Z")) await pool.query("INSERT INTO servers(id,name,host,enabled) VALUES(42,'disabled-flow-history','192.0.2.42',false)") clearPendingFlows() disableFlowRipe() try { await pool.query("UPDATE traffic_flow_settings SET top_n=20,host_private_key='NEVER_FLOW_HOST_KEY',peers_json=$1::jsonb WHERE id=1", [JSON.stringify([{serverId:1,publicKey:"public-one"},{serverId:2,publicKey:{bad:"NEVER_FLOW_PEER_KEY"},privateKey:"NEVER_FLOW_PEER_KEY"},{serverId:42,publicKey:"public-history"}])]); invalidateFlowSettings() for (const id of flowUsers) await pool.query("INSERT INTO app_users(id,login,name,active) VALUES($1,$1,$2,$3)",[id,id==="nf-mixed"?"NEVER_FLOW_HIDDEN_USER":id,id!=="nf-b"]) for (const [userId,serverId,interfaceName,peer] of [["nf-a",1,"visible-iface",""],["nf-b",1,"other-iface",""],["nf-peer",1,"wg-whole","peer-public"],["nf-mixed",1,"mixed-iface",""],["nf-mixed",2,"NEVER_FLOW_HIDDEN_IFACE",""],["nf-disabled",42,"offline-iface",""],["nf-split",1,"split-iface",""],["nf-split",42,"split-history"," "]] as const) await pool.query("INSERT INTO user_interface_bindings(id,user_id,server_id,interface_name,peer_public_key) VALUES($1,$2,$3,$4,$5)",[randomUUID(),userId,serverId,interfaceName,peer]) const putFlow = async (sid: number, iface: string, bytes: string | number, src: string, at=bucket) => pool.query("INSERT INTO flow_buckets(server_id,bucket_at,src,dst,bytes,packets,in_iface) VALUES($1,$2,$3,'8.8.8.8',$4,1,$5)",[sid,at.toISOString(),src,bytes,iface]) await putFlow(1,"visible-iface",1000,"198.18.242.1"); await putFlow(1,"2",500,"198.18.242.2"); await putFlow(1,"wg-whole",700,"198.18.242.3"); await putFlow(1,"mixed-iface",900,"198.18.242.4") await putFlow(1,"visible-iface",8000,"198.18.242.5",oldBucket); await putFlow(1,"visible-iface",9000,"198.18.242.6",futureBucket) await putFlow(2,"NEVER_FLOW_HIDDEN_IFACE","9007199254740993","198.18.242.7"); await putFlow(42,"offline-iface",300,"198.18.242.8") await pool.query("INSERT INTO flow_daily_dims(server_id,day,dim,key,bytes,packets) VALUES(1,$1,'country','NF_US',1000,10),(1,$1,'country','NF_DE',500,5),(1,$1,'service','NF_VISIBLE_SERVICE',500,5),(1,$1,'asn','64512',200,2),(2,$1,'country','NEVER_FLOW_HIDDEN_DIM',9007199254740993,1),(42,$1,'country','NF_GB',300,3)",[flowDay]) await pool.query("INSERT INTO flow_daily_dims(server_id,day,dim,key,bytes,packets) SELECT 2,$1,'country','NF_HIDDEN_'||n,1,1 FROM generate_series(1,10001) n",[flowDay]) addPendingFlow(2,[{src:"198.18.242.9",dst:"8.8.4.4",proto:6,srcPort:1,dstPort:443,bytes:7777,packets:1,inIface:"NEVER_FLOW_PENDING_IFACE",outIface:""}]) const { applyRingSnapshot: flowRingSnapshot } = await import("../../services/traffic-flow-engine.js") const ringQuality=(await import("../../services/traffic-flow-ingest-quality.js")).createFlowIngestQuality().snapshot() const visibleRing={key:"1\0__all__",inBps:[...Array(59).fill(0),1000000],outBps:Array(60).fill(0)} flowRingSnapshot([visibleRing],ringQuality) assert.throws(()=>flowRingSnapshot([visibleRing,{key:"2\0__all__",inBps:[NaN],outBps:[]}],ringQuality),/Invalid|oversized/) invalidateFlowCatalog(); await flowServerCatalog() countCatalogCalls = true; catalogCalls = 0 type ExporterPage = { exporters: { id: string; retainedRowsBytes: number; wireRateMbps: null; availability: string; collectionEnabled: boolean }[]; configurationMembershipVerified: boolean; coverageVerified: boolean; serverIds: number[] } const exporter = await invokeMcpTool(flowPrincipal,"mm_netflow_exporters_get",{minutes:5}) as ExporterPage assert.deepEqual(exporter.exporters.map(row=>row.id),["1"]); assert.equal(exporter.exporters[0].retainedRowsBytes,3100); assert.equal(exporter.configurationMembershipVerified,true) const ringResult = exporter.exporters[0] as unknown as {flowRing:{available:boolean;lastSlotIngressMbps:number;wireRateVerified:boolean}} assert.equal(ringResult.flowRing.available,true); assert.equal(ringResult.flowRing.lastSlotIngressMbps,1); assert.equal(ringResult.flowRing.wireRateVerified,false) assert.equal(exporter.exporters[0].wireRateMbps,null); assert.equal(exporter.exporters[0].availability,"unknown"); assert.equal(exporter.coverageVerified,false) assert.ok(!JSON.stringify(exporter).includes("NEVER_FLOW")); assert.ok(!JSON.stringify(exporter).includes("outside-scope")); assert.ok(!("lastExporterIp" in exporter)) await pool.query("UPDATE traffic_flow_settings SET peers_json=jsonb_set(peers_json,'{0,publicKey}','{}'::jsonb) WHERE id=1"); invalidateFlowSettings() const badPeer = await invokeMcpTool(flowPrincipal,"mm_netflow_exporters_get",{minutes:5}) as {configurationMembershipVerified:boolean;incomplete:boolean;failures:unknown[];exporters:{storedPeerRecord:boolean|null}[]} assert.equal(badPeer.configurationMembershipVerified,false); assert.equal(badPeer.incomplete,true); assert.equal(badPeer.failures.length,1); assert.equal(badPeer.exporters[0].storedPeerRecord,null) await pool.query("UPDATE traffic_flow_settings SET peers_json=jsonb_set(peers_json,'{0,publicKey}',$1::jsonb) WHERE id=1",[JSON.stringify("public-one")]); invalidateFlowSettings() const rawWindow = await flowWindow(5,[1]); assert.equal(rawWindow.rows.length,4); assert.ok(rawWindow.rows.every(row=>row.serverId===1&&row.bucketAt<=rawWindow.until)) await assert.rejects(flowRows(rawWindow.since,1,{maxRows:1,untilIso:rawWindow.until}),/предел/) type TalkerPage = { items: {serverId:string;src:string;bytes:number;bps:number}[]; total:number; totalConversations:number; retainedRowsBytes:number; nextOffset:number|null; incomplete:boolean; serverIds:number[] } const talkers = await invokeMcpTool(flowPrincipal,"mm_netflow_talkers_get",{minutes:5,limit:1}) as TalkerPage assert.equal(talkers.retainedRowsBytes,3100); assert.equal(talkers.totalConversations,4); assert.equal(talkers.total,4); assert.equal(talkers.nextOffset,1); assert.equal(talkers.incomplete,true) assert.equal(talkers.items[0].bytes,1000); assert.deepEqual(talkers.serverIds,[1]); assert.ok(!JSON.stringify(talkers).includes("NEVER_FLOW")); assert.ok(!("lastExporterIp" in talkers)) const secondTalker = await invokeMcpTool(flowPrincipal,"mm_netflow_talkers_get",{minutes:5,offset:1,limit:1}) as TalkerPage assert.equal(secondTalker.items[0].bytes,900) addPendingFlow(1,[{src:"198.18.242.10",dst:"8.8.4.4",proto:6,srcPort:1,dstPort:443,bytes:222,packets:1,inIface:"visible-iface",outIface:""}]) assert.equal((await invokeMcpTool(flowPrincipal,"mm_netflow_talkers_get",{minutes:5}) as TalkerPage).retainedRowsBytes,3322) clearPendingFlows() await pool.query("UPDATE traffic_flow_settings SET top_n=1 WHERE id=1"); invalidateFlowSettings() const limitedTalkers = await invokeMcpTool(flowPrincipal,"mm_netflow_talkers_get",{minutes:5}) as TalkerPage assert.equal(limitedTalkers.total,1); assert.equal(limitedTalkers.totalConversations,4); assert.equal(limitedTalkers.retainedRowsBytes,3100) await pool.query("UPDATE traffic_flow_settings SET top_n=20 WHERE id=1"); invalidateFlowSettings() type ClientPage = { clients: { id: string; ingressNamedRowsBytes: number|null; peerBindingsNotAttributed: number; ifIndexRowsNotAttributed: number; availability: string; completeSourceSet: boolean; accountEnabled: boolean }[]; total: number; nextOffset: number|null } const clients = await invokeMcpTool(flowPrincipal,"mm_netflow_clients_get",{minutes:5}) as ClientPage assert.deepEqual(clients.clients.map(row=>row.id),["nf-a","nf-b","nf-peer"]); assert.equal(clients.total,3) assert.equal(clients.clients[0].ingressNamedRowsBytes,1000); assert.equal(clients.clients[0].ifIndexRowsNotAttributed,1) assert.equal(clients.clients[1].accountEnabled,false); assert.equal(clients.clients[1].availability,"unknown") assert.equal(clients.clients[2].ingressNamedRowsBytes,null); assert.equal(clients.clients[2].peerBindingsNotAttributed,1) const clientPage = await invokeMcpTool(flowPrincipal,"mm_netflow_clients_get",{minutes:5,offset:1,limit:1}) as ClientPage assert.deepEqual(clientPage.clients.map(row=>row.id),["nf-b"]); assert.equal(clientPage.total,3); assert.equal(clientPage.nextOffset,2) type MonthPage = { bytes: number; collections: { countries: {items:{id:string;bytes:number}[];total:number;nextOffset:number|null} }; serverIds:number[] } const monthly = await invokeMcpTool(flowPrincipal,"mm_netflow_monthly_report_get",{month:flowMonth,limit:1}) as MonthPage assert.equal(monthly.bytes,1500); assert.equal(monthly.collections.countries.total,2); assert.equal(monthly.collections.countries.nextOffset,1); assert.equal(monthly.collections.countries.items[0].id,"NF_US") assert.equal((await invokeMcpTool(flowPrincipal,"mm_netflow_monthly_report_get",{month:flowMonth,offset:1,limit:1}) as MonthPage).collections.countries.items[0].id,"NF_DE") await assert.rejects(flowMonthly(flowMonth,[2],{maxKeys:10000}),/предел/) for(const tool of ["mm_netflow_talkers_get","mm_netflow_exporters_get","mm_netflow_clients_get","mm_netflow_monthly_report_get"]) await assert.rejects(invokeMcpTool(flowPrincipal,tool,{serverId:2,...(tool.includes("monthly")?{month:flowMonth}:{minutes:5})})) const disabled = await authenticateMcpToken((await makeFlowAccess([42])).token) const disabledExporter = await invokeMcpTool(disabled,"mm_netflow_exporters_get",{minutes:5}) as ExporterPage assert.equal(disabledExporter.exporters[0].retainedRowsBytes,300); assert.equal(disabledExporter.exporters[0].collectionEnabled,false); assert.equal(disabledExporter.exporters[0].availability,"unknown") assert.equal((await invokeMcpTool(disabled,"mm_netflow_monthly_report_get",{month:flowMonth}) as MonthPage).bytes,300) const broad = await makeFlowAccess([1,42]), broadPrincipal = await authenticateMcpToken(broad.token) const split = await invokeMcpTool(broadPrincipal,"mm_netflow_clients_get",{minutes:5,serverLimit:1}) as ClientPage assert.equal(split.clients.find(row=>row.id==="nf-split")?.completeSourceSet,false) const wide = await makeFlowAccess([1,2]), widePrincipal = await authenticateMcpToken(wide.token), grantId = randomUUID() await pool.query("INSERT INTO mcp_grants(id,connection_id,payload) VALUES($1,$2,$3::jsonb)",[grantId,wide.connection.id,JSON.stringify({id:grantId,credentialId:widePrincipal.credentialId,startsAt:new Date(Date.now()-1000).toISOString(),expiresAt:new Date(Date.now()+60000).toISOString(),scope:{allowGlobal:false,serverIds:[1],operationIds:flowOpIds}})]) assert.deepEqual((await invokeMcpTool(widePrincipal,"mm_netflow_clients_get",{minutes:5}) as ClientPage).clients.map(row=>row.id),["nf-a","nf-b","nf-peer"]) assert.equal((await invokeMcpTool(widePrincipal,"mm_netflow_monthly_report_get",{month:flowMonth}) as MonthPage).bytes,1500) assert.equal((await invokeMcpTool(widePrincipal,"mm_netflow_talkers_get",{minutes:5}) as TalkerPage).retainedRowsBytes,3100) assert.equal((await invokeMcpTool(disabled,"mm_netflow_talkers_get",{minutes:5}) as TalkerPage).retainedRowsBytes,300) assert.equal((await invokeMcpTool(flowPrincipal,"mm_netflow_talkers_get",{serverOffset:10000}) as TalkerPage).total,0) const empty = await authenticateMcpToken((await makeFlowAccess([])).token) for(const tool of ["mm_netflow_talkers_get","mm_netflow_exporters_get","mm_netflow_clients_get","mm_netflow_monthly_report_get"]) await assert.rejects(invokeMcpTool(empty,tool,tool.includes("monthly")?{month:flowMonth}:{}),/сервер/) assert.deepEqual((await invokeMcpTool(flowPrincipal,"mm_netflow_exporters_get",{serverOffset:10000}) as ExporterPage).exporters,[]) assert.equal((await invokeMcpTool(flowPrincipal,"mm_netflow_clients_get",{serverOffset:10000}) as ClientPage).total,0) assert.equal((await invokeMcpTool(flowPrincipal,"mm_netflow_monthly_report_get",{month:flowMonth,serverOffset:10000}) as MonthPage).bytes,0) await pool.query("UPDATE flow_daily_dims SET bytes=9007199254740993 WHERE server_id=1 AND key='NF_US'") await assert.rejects(invokeMcpTool(flowPrincipal,"mm_netflow_monthly_report_get",{month:flowMonth}),/счётчик/) await pool.query("UPDATE flow_daily_dims SET bytes=1000 WHERE server_id=1 AND key='NF_US'") assert.equal(catalogCalls,0,"Historical reports must not dispatch RouterOS requests") const stored = (await pool.query("SELECT result FROM mcp_requests WHERE connection_id=$1",[flowAccess.connection.id])).rows assert.ok(!JSON.stringify(stored).includes("NEVER_FLOW")) await pool.query("DELETE FROM flow_daily_dims WHERE server_id=2 AND (key='NEVER_FLOW_HIDDEN_DIM' OR key LIKE 'NF_HIDDEN_%')") await pool.query("UPDATE flow_buckets SET bytes=2000 WHERE server_id=2 AND src='198.18.242.7'"); clearPendingFlows() const { default: FlowFastify } = await import("fastify"), { validatorCompiler: flowValidator, serializerCompiler: flowSerializer } = await import("@fastify/type-provider-zod") const flowHttp = FlowFastify(); flowHttp.setValidatorCompiler(flowValidator); flowHttp.setSerializerCompiler(flowSerializer); await flowHttp.register((await import("../../routes/traffic-flow.js")).default,{prefix:"/api"}) try { const restTalkers = await flowHttp.inject({method:"GET",url:"/api/traffic/flow?range=5m"}) assert.equal(restTalkers.statusCode,200,restTalkers.body) const restVisible = restTalkers.json().talkers.filter((row:{serverId:string})=>row.serverId==="1") const mcpVisible = await invokeMcpTool(flowPrincipal,"mm_netflow_talkers_get",{minutes:5}) as TalkerPage assert.deepEqual(mcpVisible.items.map(row=>({bytes:row.bytes,bps:row.bps})),restVisible.map((row:{bytes:number;bps:number})=>({bytes:row.bytes,bps:row.bps}))) assert.ok(restVisible.every((row:{src:string})=>row.src!=="198.18.242.6"),"Future rows must not enter REST talkers") assert.equal((await flowHttp.inject({method:"GET",url:"/api/traffic/flows?range=5m"})).statusCode,200) for(const path of ["/api/traffic/flow/exporters?range=5m","/api/traffic/flow/clients?range=5m","/api/traffic/flow/monthly?month="+flowMonth+"&serverId=1"]) { const response = await flowHttp.inject({method:"GET",url:path}); assert.equal(response.statusCode,200,response.body); assert.equal(typeof response.json(),"object") } } finally { await flowHttp.close() } await pool.query("INSERT INTO flow_buckets(server_id,bucket_at,src,dst,bytes,packets,in_iface) SELECT 1,now()-interval '90 seconds'+n*interval '1 millisecond','198.18.242.11','8.8.8.8',1,1,'cap' FROM generate_series(1,50001) n") await assert.rejects(invokeMcpTool(flowPrincipal,"mm_netflow_talkers_get",{minutes:5}), (error:{code?:string})=>error.code==="STATE_UNAVAILABLE") await pool.query("DELETE FROM flow_buckets WHERE server_id=1 AND src='198.18.242.11'") const talkerRace = async (change:()=>Promise, principal=flowPrincipal, signal?:AbortSignal) => { const blocker = await pool.connect() let pending: Promise<{error:unknown}>|undefined try { await blocker.query("BEGIN"); await blocker.query("LOCK TABLE flow_buckets IN ACCESS EXCLUSIVE MODE") pending = invokeMcpTool(principal,"mm_netflow_talkers_get",{minutes:5},signal).then(()=>({error:null}),error=>({error})) let reached = false for(let n=0;n<100;n++) { reached = (await pool.query("SELECT EXISTS(SELECT 1 FROM pg_stat_activity WHERE datname=current_database() AND wait_event_type='Lock' AND query ILIKE '%FROM flow_buckets fb%') AS reached")).rows[0].reached if(reached) break await delay(20) } assert.equal(reached,true,"Talker race must block the actual scoped SQL read") await change(); await blocker.query("COMMIT") assert.ok((await pending).error,"Late cancellation/access/source drift must withhold all talker data") } finally { await blocker.query("ROLLBACK"); blocker.release(); await pending } } const originalFlowName = (await pool.query("SELECT name FROM servers WHERE id=1")).rows[0].name await talkerRace(()=>pool.query("UPDATE servers SET name='NEVER_FLOW_CHANGED_SOURCE' WHERE id=1")) await pool.query("UPDATE servers SET name=$1 WHERE id=1",[originalFlowName]) await talkerRace(async()=>{await pool.query("UPDATE traffic_flow_settings SET top_n=1 WHERE id=1");invalidateFlowSettings()}) await pool.query("UPDATE traffic_flow_settings SET top_n=20 WHERE id=1");invalidateFlowSettings() await talkerRace(()=>pool.query("UPDATE mcp_grants SET payload=jsonb_set(payload,'{scope,serverIds}','[]'::jsonb) WHERE id=$1",[grantId]),widePrincipal) const originalFlowPermissions=(await pool.query("SELECT owner_permissions FROM mcp_connections WHERE id=$1",[flowAccess.connection.id])).rows[0].owner_permissions await talkerRace(()=>pool.query("UPDATE mcp_connections SET owner_permissions='[]'::jsonb WHERE id=$1",[flowAccess.connection.id])) await pool.query("UPDATE mcp_connections SET owner_permissions=$2::jsonb WHERE id=$1",[flowAccess.connection.id,JSON.stringify(originalFlowPermissions)]) const talkerCancel=new AbortController() await talkerRace(async()=>talkerCancel.abort(),flowPrincipal,talkerCancel.signal) await talkerRace(()=>setConnectionStatus(flowAccess.connection.id,"paused",flowActor)) assert.equal(catalogCalls,0,"Talker history and races must never poll RouterOS") assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_plans WHERE connection_id=$1",[flowAccess.connection.id])).rows[0].n,0) assert.ok(!JSON.stringify((await pool.query("SELECT result FROM mcp_requests WHERE connection_id=$1",[flowAccess.connection.id])).rows).includes("NEVER_FLOW")) console.log("✓ NetFlow talkers: shared REST parity, scoped SQL/pending, topN/pages, 50001-row refusal, real blocked-read source/topN/grant/owner/cancel/pause races, no global fields or network") } finally { countCatalogCalls = false; clearPendingFlows(); resetFlowRipe() await pool.query("DELETE FROM app_users WHERE id=ANY($1::text[])",[flowUsers]) await pool.query("DELETE FROM flow_buckets WHERE src << '198.18.242.0/24'::inet AND server_id IN(1,2,42)") await pool.query("DELETE FROM flow_daily_dims WHERE server_id IN(1,2,42) AND (key LIKE 'NF_%' OR key='NEVER_FLOW_HIDDEN_DIM' OR key='64512')") await pool.query("DELETE FROM servers WHERE id=42") await pool.query("UPDATE traffic_flow_settings SET peers_json=$1::jsonb,host_private_key=$2,top_n=$3 WHERE id=1",[JSON.stringify(savedFlow.peers_json),savedFlow.host_private_key,savedFlow.top_n]); invalidateFlowSettings(); invalidateFlowCatalog() } console.log("✓ NetFlow reports: scoped SQL before aggregation/parsing, hidden/mixed binding exclusion and grant narrowing, bounded pages/sources, future/pending/foreign filtering, disabled history, safe counters and real REST parity; no RouterOS dispatch") await pool.query("INSERT INTO servers(id,name,host,port,use_ssl) VALUES(45,'gre-service-fixture','127.0.0.1',$1,false)",[port]) const greServiceOperations = ["network.gre.create","network.gre.update","network.gre.delete"] const greServiceAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({name:"GRE service plans",mode:"approval",expiresAt:new Date(Date.now()+1800000).toISOString(),permissions:["mm:network:write"],scope:{serverIds:[45],operationIds:greServiceOperations,allowGlobal:false},limits:{mutationsPerHour:200}}),actor) const greServicePrincipal = await authenticateMcpToken(greServiceAccess.token) const greNativeAccess=await createMcpConnection(mcpConnectionCreateSchema.parse({name:"GRE native aliases",mode:"approval",expiresAt:new Date(Date.now()+1800000).toISOString(),permissions:["mm:network:write"],scope:{serverIds:[45],operationIds:["ros.gre.update","ros.gre.delete"],allowGlobal:false},limits:{mutationsPerHour:100}}),actor) const greNativePrincipal=await authenticateMcpToken(greNativeAccess.token) const greNativePlan=async(operation:string,id:string,fields?:Record)=>invokeMcpTool(greNativePrincipal,"mm_gre_"+operation+"_plan",{serverId:45,id,...(fields?{fields}:{})})as Promise<{planId:string}> const greNativeApply=async(planId:string)=>{await approvePlan(planId,actor,true);const key=randomUUID(),op=await applyPlan(greNativePrincipal,planId,key);return{key,op,state:await waitFirewallMove(op.id)}} const greServicePlan = async(operation:string,args:Record)=>invokeMcpTool(greServicePrincipal,"mm_gre_tunnel_"+operation+"_plan",{serverId:45,...args}) as Promise<{planId:string}> const greServiceApply = async(planId:string)=>{await approvePlan(planId,actor,true);const key=randomUUID(),op=await applyPlan(greServicePrincipal,planId,key);return{key,op,state:await waitFirewallMove(op.id)}} const greServiceSecret = randomUUID(), greServiceProtection = randomUUID() await pool.query("INSERT INTO mcp_secrets(id,purpose,server_id,ciphertext,created_by) VALUES($1,'gre-ipsec:45:gre-business',45,$2,$3)",[greServiceSecret,await encryptSecret("NEVER_GRE_SERVICE_PSK"),actor.id]) greMutationFixtures=true;greMutationCommands=0;greMutationCollections={"/interface/gre":[],"/ip/address":[],"/ipv6/address":[]};greMutationDependencies={} const {default:GreServiceFastify}=await import("fastify"),{validatorCompiler:greServiceValidator,serializerCompiler:greServiceSerializer}=await import("@fastify/type-provider-zod") const greServiceHttp=GreServiceFastify();greServiceHttp.setValidatorCompiler(greServiceValidator);greServiceHttp.setSerializerCompiler(greServiceSerializer);await greServiceHttp.register((await import("../../routes/gre.js")).default,{prefix:"/api"}) try { const createFields={name:"gre-business",remoteAddress:"192.0.2.2",localInnerIp:"198.18.45.1/30",enabled:false} await assert.rejects(invokeMcpTool(greServicePrincipal,"mm_gre_tunnel_create_plan",{serverId:1,fields:createFields}));assert.equal(greMutationCommands,0) await assert.rejects(greServicePlan("create",{fields:{...createFields,ipsecSecret:"raw-not-permitted"}}));assert.equal(greMutationCommands,0) await assert.rejects(greServicePlan("create",{fields:{...createFields,name:"wrong-secret-target"},ipsecSecretRef:greServiceSecret}));assert.equal(greMutationCommands,0) const shortBudget=await createMcpConnection(mcpConnectionCreateSchema.parse({name:"GRE one-write budget",mode:"approval",expiresAt:new Date(Date.now()+1800000).toISOString(),permissions:["mm:network:write"],scope:{serverIds:[45],operationIds:greServiceOperations,allowGlobal:false},limits:{mutationsPerHour:1}}),actor) await assert.rejects(invokeMcpTool(await authenticateMcpToken(shortBudget.token),"mm_gre_tunnel_create_plan",{serverId:45,fields:createFields}));assert.equal(greMutationCommands,0) await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,$3)",[greServiceProtection,JSON.stringify({kind:"vlan",id:"*protected",serverId:45,protection:"deny"}),actor.id]) await assert.rejects(greServicePlan("create",{fields:createFields}));assert.equal(greMutationCommands,0) assert.equal((await greServiceHttp.inject({method:"POST",url:"/api/gre/tunnels",payload:{serverId:"45",...createFields}})).statusCode,409);assert.equal(greMutationCommands,0) await pool.query("DELETE FROM mcp_protections WHERE id=$1",[greServiceProtection]) const createdPlan=await greServicePlan("create",{fields:createFields,ipsecSecretRef:greServiceSecret}),created=await greServiceApply(createdPlan.planId) assert.equal(created.state,"applied");assert.equal(greMutationCommands,2);assert.equal(greMutationCollections["/interface/gre"][0].disabled,"true");assert.equal(greMutationCollections["/interface/gre"][0]["ipsec-secret"],"NEVER_GRE_SERVICE_PSK");assert.equal(greMutationCollections["/interface/gre"][0]["allow-fast-path"],"false") await applyPlan(greServicePrincipal,createdPlan.planId,created.key);assert.equal(greMutationCommands,2) const greId=greMutationCollections["/interface/gre"][0][".id"] greMutationCollections["/interface/gre"][0]["dont-fragment"]="no" const stale=await greServicePlan("update",{rosId:greId,fields:{mtu:1410}}) greMutationCollections["/interface/gre"][0]["dont-fragment"]="yes" assert.equal((await greServiceApply(stale.planId)).state,"rejected");assert.equal(greMutationCommands,2) greMutationCollections["/interface/gre"][0]["dont-fragment"]="no" greMutationCollections["/ip/address"].push({".id":"*SECOND",address:"198.18.99.1/32",interface:"gre-business",disabled:"false",dynamic:"false",invalid:"false",comment:"preserve-secondary"}) const beforeNativeAddressCheck=greMutationCommands await assert.rejects(greNativePlan("delete",greId),/адресом/) await assert.rejects(greNativePlan("update",greId,{name:"native-bound-rename"}),/адресом/) assert.equal(greMutationCommands,beforeNativeAddressCheck) const changed=await greServiceApply((await greServicePlan("update",{rosId:greId,fields:{mtu:1400,localInnerIp:"fd00:45::1/64",enabled:true}})).planId) assert.equal(changed.state,"applied");assert.equal(greMutationCollections["/interface/gre"][0].disabled,"false");assert.equal(greMutationCollections["/interface/gre"][0]["dont-fragment"],"no");assert.equal(greMutationCollections["/interface/gre"][0]["ipsec-secret"],"NEVER_GRE_SERVICE_PSK") assert.equal(greMutationCollections["/ip/address"].length,1);assert.equal(greMutationCollections["/ip/address"][0][".id"],"*SECOND");assert.equal(greMutationCollections["/ipv6/address"][0].address,"fd00:45::1/64");assert.equal(greMutationCollections["/ipv6/address"][0].advertise,"false") await assert.rejects(greServicePlan("update",{rosId:greId,name:"different",fields:{mtu:1420}})) greMutationDependencies["/ip/route"]=[{".id":"*USE",gateway:"192.0.2.2%gre-business",dynamic:"false"}] await assert.rejects(greServicePlan("delete",{rosId:greId}),/используется/);greMutationDependencies={} await pool.query("INSERT INTO app_users(id,login,name) VALUES('gre-business-user','gre-business-user','GRE binding fixture')") await pool.query("INSERT INTO user_interface_bindings(id,user_id,server_id,interface_name) VALUES($1,'gre-business-user',45,'gre-business')",[randomUUID()]) await assert.rejects(greServicePlan("delete",{rosId:greId}),/привязкой/);await pool.query("DELETE FROM app_users WHERE id='gre-business-user'") const removed=await greServiceApply((await greServicePlan("delete",{rosId:greId})).planId);assert.equal(removed.state,"applied");assert.equal(greMutationCollections["/interface/gre"].length,0);assert.equal(greMutationCollections["/ip/address"].length,0);assert.equal(greMutationCollections["/ipv6/address"].length,0) for(const [method,payload,status] of [["POST",{serverId:"45",name:"rest-gre",remoteAddress:"192.0.2.3",localInnerIp:"fd00:46::1/64",enabled:false},201],["PATCH",{serverId:"45",name:"rest-gre",mtu:1400},200],["DELETE",{serverId:"45",name:"rest-gre"},200]] as const){const response=await greServiceHttp.inject({method,url:"/api/gre/tunnels",payload});assert.equal(response.statusCode,status,response.body);assert.equal(response.json().mutation.verified,true)} assert.equal((await greServiceApply((await greServicePlan("create",{fields:{name:"native-gre",remoteAddress:"192.0.2.4",enabled:false,comment:"native note"}})).planId)).state,"applied") const nativeId=greMutationCollections["/interface/gre"][0][".id"],nativeBefore=greMutationCommands greMutationCollections["/interface/gre"][0]["dont-fragment"]="no" await pool.query("INSERT INTO app_users(id,login,name) VALUES('gre-native-user','gre-native-user','Native GRE binding fixture')") const nativeBindingId=randomUUID() await pool.query("INSERT INTO user_interface_bindings(id,user_id,server_id,interface_name) VALUES($1,'gre-native-user',45,'native-gre')",[nativeBindingId]) await assert.rejects(greNativePlan("delete",nativeId),/привязкой/);await assert.rejects(greNativePlan("update",nativeId,{name:"native-renamed"}),/привязкой/) const {executeRosStep:directGreStep}=await import("./resources.js") await assert.rejects(directGreStep({serverId:45,resource:"gre",action:"delete",id:nativeId,fields:{}}),/привязкой/) await assert.rejects(directGreStep({serverId:45,resource:"gre",action:"update",id:nativeId,fields:{name:"bypass-rename"}}),/привязкой/) assert.equal(greMutationCommands,nativeBefore) const scalarNative=await greNativeApply((await greNativePlan("update",nativeId,{mtu:1395})).planId);assert.equal(scalarNative.state,"applied") await pool.query("DELETE FROM user_interface_bindings WHERE id=$1",[nativeBindingId]) const lateNative=await greNativePlan("delete",nativeId),beforeLateNative=greMutationCommands await pool.query("INSERT INTO user_interface_bindings(id,user_id,server_id,interface_name) VALUES($1,'gre-native-user',45,'native-gre')",[nativeBindingId]) assert.equal((await greNativeApply(lateNative.planId)).state,"rejected");assert.equal(greMutationCommands,beforeLateNative) await pool.query("DELETE FROM app_users WHERE id='gre-native-user'") const dynamicNative=await greNativePlan("delete",nativeId),beforeDynamicNative=greMutationCommands // Apply reads the sealed current state and execution's initial state first; // introduce the learned route only in the final pre-dispatch read. greNativeLateDynamicAt=greNativeRouteReads+3 assert.equal((await greNativeApply(dynamicNative.planId)).state,"unknown");assert.equal(greMutationCommands,beforeDynamicNative) greNativeLateDynamicAt=0;greMutationDependencies["/ip/route"]=[] greMutationDependencies["/ip/firewall/nat"]=[{".id":"*FWUSE",chain:"srcnat",action:"masquerade","out-interface":"native-gre",packets:"1",bytes:"100"}] await assert.rejects(greNativePlan("delete",nativeId),/используется/);await assert.rejects(greNativePlan("update",nativeId,{name:"native-renamed"}),/используется/) await assert.rejects(directGreStep({serverId:45,resource:"gre",action:"delete",id:nativeId,fields:{}}),/используется/) greMutationDependencies["/ip/firewall/nat"]=[] await assert.rejects(greNativePlan("update",nativeId,{name:"ether1"}),/уже используется/) const nativeOpaque=await greNativePlan("update",nativeId,{mtu:1390}),beforeOpaque=greMutationCommands greMutationCollections["/interface/gre"][0]["dont-fragment"]="yes" assert.equal((await greNativeApply(nativeOpaque.planId)).state,"rejected");assert.equal(greMutationCommands,beforeOpaque) greMutationCollections["/interface/gre"][0]["dont-fragment"]="no" greMutationDependencies["/ip/firewall/filter"]=[{".id":"*COUNTER",chain:"forward",action:"accept","in-interface":"ether1",packets:"1",bytes:"100"}];greNativeCounterTick=true const renamed=await greNativeApply((await greNativePlan("update",nativeId,{name:"native-renamed",comment:""})).planId);assert.equal(renamed.state,"applied");assert.equal(greMutationCollections["/interface/gre"][0].name,"native-renamed");assert.equal(greMutationCollections["/interface/gre"][0].comment,"");assert.equal(greMutationCollections["/interface/gre"][0]["dont-fragment"],"no") const ignoredNative=await greNativePlan("update",nativeId,{mtu:1385}),beforeIgnoredNative=greMutationCommands;greMutationIgnored=true assert.equal((await greNativeApply(ignoredNative.planId)).state,"partial");assert.equal(greMutationCommands,beforeIgnoredNative+1);greMutationIgnored=false const lostNative=await greNativePlan("update",nativeId,{mtu:1380}),beforeLostNative=greMutationCommands;greMutationLost=true const nativeLostResult=await greNativeApply(lostNative.planId);assert.equal(nativeLostResult.state,"unknown");assert.equal(greMutationCommands,beforeLostNative+1) await applyPlan(greNativePrincipal,lostNative.planId,nativeLostResult.key);assert.equal(greMutationCommands,beforeLostNative+1) const nativeDeleteResult = await greNativeApply((await greNativePlan("delete",nativeId)).planId) assert.equal(nativeDeleteResult.state,"applied",JSON.stringify((await pool.query("SELECT state,error_code,result FROM mcp_operations WHERE id=$1",[nativeDeleteResult.op.id])).rows[0]));assert.equal(greMutationCollections["/interface/gre"].length,0) greNativeCounterTick=false;greMutationDependencies={} console.log("✓ Native GRE aliases and direct execution: sealed DB/firewall/opaque proof, user/address reference rejection, rename and omitted empty-comment read-back, late binding rejection, scalar update, counter tolerance, partial/lost and no replay") await acceptGreRevisionRestore(actor,{get:()=>greMutationCollections,set:value=>{greMutationCollections=value},writes:()=>greMutationCommands,reads:()=>greRevisionReads,ignored:value=>{greMutationIgnored=value},lost:value=>{greMutationLost=value},afterCommand:value=>{greRevisionAfterCommand=value},dependencies:value=>{greMutationDependencies=value}}) const ignored=await greServicePlan("create",{fields:{...createFields,name:"ignored-gre"}}),beforeIgnored=greMutationCommands;greMutationIgnored=true assert.equal((await greServiceApply(ignored.planId)).state,"partial");assert.equal(greMutationCommands,beforeIgnored+1);greMutationIgnored=false const lost=await greServicePlan("create",{fields:{...createFields,name:"lost-gre"}}),beforeLost=greMutationCommands;greMutationLost=true const lostResult=await greServiceApply(lost.planId);assert.equal(lostResult.state,"unknown");assert.equal(greMutationCommands,beforeLost+1);assert.equal(greMutationCollections["/interface/gre"][0].disabled,"true") await applyPlan(greServicePrincipal,lost.planId,lostResult.key);assert.equal(greMutationCommands,beforeLost+1) greMutationCollections={"/interface/gre":[],"/ip/address":[],"/ipv6/address":[]} const revoked=await greServicePlan("create",{fields:{...createFields,name:"revoked-gre"}}),beforeRevoke=greMutationCommands;greMutationRevoke=greServiceAccess.connection.id assert.equal((await greServiceApply(revoked.planId)).state,"unknown");assert.equal(greMutationCommands,beforeRevoke+1) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_requests WHERE connection_id=$1 AND result::text LIKE '%NEVER_GRE_SERVICE_PSK%'",[greServiceAccess.connection.id])).rows[0].n,0) } finally {greMutationFixtures=false;greMutationIgnored=false;greMutationLost=false;greMutationRevoke=undefined;greNativeCounterTick=false;greNativeLateDynamicAt=0;await greServiceHttp.close();await pool.query("DELETE FROM app_users WHERE id IN('gre-business-user','gre-native-user')");await pool.query("DELETE FROM mcp_protections WHERE id=$1",[greServiceProtection]);await pool.query("DELETE FROM mcp_secrets WHERE id=$1",[greServiceSecret]);await pool.query("DELETE FROM servers WHERE id=45")} console.log("✓ GRE shared REST/MCP business CRUD: scoped native identity/proof, staged disabled v4/v6, preserved secondary/opaque/PSK, secret purpose, server-wide protections, parent/user dependencies, budget, stale rejection, partial/lost/no replay and revoke between commands") const vpnAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Scoped VPN catalogs", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read"], scope: { serverIds: [1], operationIds: ["network.wireguard.state.read", "network.vxlan.state.read", "network.ipsec.state.read", "network.gre.state.read"], allowGlobal: false } }), actor) const vpnPrincipal = await authenticateMcpToken(vpnAccess.token) vpnFixtures = true; vpnCalls.length = 0 try { type WgPage = { interfaces: { enabled: boolean | null; status: string; address: string | null; addressStateVerified: boolean; peers: { rosId: string; transferRx: number | null; transferTx: number | null }[]; peersPage: { total: number; nextOffset: number | null } }[]; total: number; failures: unknown[]; incomplete: boolean; keyMaterialIncluded: boolean } const wgPage = await invokeMcpTool(vpnPrincipal, "mm_wireguard_state_get", { limit: 1, peerLimit: 1 }) as WgPage assert.equal(vpnCalls.length, 4); assert.deepEqual(wgPage.failures, []); assert.equal(wgPage.total, 2); assert.equal(wgPage.incomplete, true) assert.equal(wgPage.interfaces[0].peersPage.total, 3); assert.equal(wgPage.interfaces[0].peersPage.nextOffset, 1) assert.equal(wgPage.interfaces[0].peers[0].transferRx, 0); assert.equal(wgPage.interfaces[0].peers[0].transferTx, null) assert.equal(wgPage.interfaces[0].address, "192.0.2.1/24"); assert.equal(wgPage.interfaces[0].addressStateVerified, true) assert.equal(wgPage.keyMaterialIncluded, false); assert.ok(!JSON.stringify(wgPage).includes(vpnPrivate)); assert.ok(!JSON.stringify(wgPage).includes(vpnPsk)) const wgNext = await invokeMcpTool(vpnPrincipal, "mm_wireguard_state_get", { limit: 1, peerOffset: 1, peerLimit: 1 }) as WgPage assert.equal(wgNext.interfaces[0].peers[0].rosId, "*P2") const prefixes = await invokeMcpTool(vpnPrincipal, "mm_wireguard_state_get", { limit: 1, peerLimit: 1, allowedOffset: 1, allowedLimit: 1 }) as { interfaces: { peers: { allowedIps: string[]; allowedIpsPage: { total: number; nextOffset: number } }[] }[]; incomplete: boolean } assert.deepEqual(prefixes.interfaces[0].peers[0].allowedIps, ["198.51.100.0/24"]); assert.equal(prefixes.interfaces[0].peers[0].allowedIpsPage.total, 3); assert.equal(prefixes.interfaces[0].peers[0].allowedIpsPage.nextOffset, 2); assert.equal(prefixes.incomplete, true) const wgUnknown = await invokeMcpTool(vpnPrincipal, "mm_wireguard_state_get", { offset: 1 }) as WgPage assert.equal(wgUnknown.interfaces[0].enabled, null); assert.equal(wgUnknown.interfaces[0].status, "unknown") const beforeGre = vpnCalls.length type GrePage = { tunnels: { status: string; mtu: number | null; comment: string; localAddresses: { address: string; configurationVerified: boolean }[]; addressesPage: { total: number; nextOffset: number | null } }[]; failures: unknown[]; incomplete: boolean; configurationHistoryUpdated: boolean } const historyBeforeGre = Number((await pool.query("SELECT count(*) AS n FROM config_revisions WHERE server_id=1 AND section='gre'")).rows[0].n) const gre = await invokeMcpTool(vpnPrincipal, "mm_gre_state_get", { limit: 1, addressLimit: 1 }) as GrePage assert.equal(vpnCalls.length, beforeGre + 3); assert.deepEqual(gre.failures, []); assert.equal(gre.tunnels[0].status, "up"); assert.equal(gre.tunnels[0].mtu, 1400) assert.equal(gre.tunnels[0].addressesPage.total, 3); assert.equal(gre.tunnels[0].addressesPage.nextOffset, 1); assert.equal(gre.incomplete, true) assert.equal(gre.tunnels[0].localAddresses[0].configurationVerified, true); assert.equal(gre.configurationHistoryUpdated, false); assert.ok(!JSON.stringify(gre).includes(vpnPsk)) assert.equal(Number((await pool.query("SELECT count(*) AS n FROM config_revisions WHERE server_id=1 AND section='gre'")).rows[0].n), historyBeforeGre) const greNext = await invokeMcpTool(vpnPrincipal, "mm_gre_state_get", { limit: 1, addressOffset: 1, addressLimit: 1 }) as GrePage assert.equal(greNext.tunnels[0].localAddresses[0].address, "fd00::2/64") const greUnknown = await invokeMcpTool(vpnPrincipal, "mm_gre_state_get", { offset: 1 }) as GrePage assert.equal(greUnknown.tunnels[0].status, "unknown"); assert.equal(greUnknown.tunnels[0].mtu, null) const beforeVxlan = vpnCalls.length type VxPage = { tunnels: { remoteVteps: string[]; vtepsPage: { total: number; nextOffset: number | null } }[]; failures: unknown[]; incomplete: boolean } const vx = await invokeMcpTool(vpnPrincipal, "mm_vxlan_state_get", { vtepLimit: 2 }) as VxPage assert.equal(vpnCalls.length, beforeVxlan + 2); assert.deepEqual(vx.failures, []); assert.equal(vx.tunnels[0].vtepsPage.total, 3); assert.equal(vx.tunnels[0].vtepsPage.nextOffset, 2); assert.equal(vx.incomplete, true) const vxNext = await invokeMcpTool(vpnPrincipal, "mm_vxlan_state_get", { vtepOffset: 2, vtepLimit: 2 }) as VxPage assert.deepEqual(vxNext.tunnels[0].remoteVteps, ["198.51.100.3"]) const beforeIpsec = vpnCalls.length type IpsecPage = { clients: { disabled: boolean | null; online: boolean | null }[]; servers: { clientsOnline: number | null; clientsSessionUnknown: number; peers: { disabled: boolean | null; passive: boolean | null }[]; certs: { trusted: boolean | null; keyPairAvailable: boolean | null; expiryTimestampVerified: boolean }[]; certificatesPage: { total: number; nextOffset: number | null } }[]; failures: unknown[]; incomplete: boolean } const ike = await invokeMcpTool(vpnPrincipal, "mm_ipsec_state_get", { detailLimit: 1 }) as IpsecPage assert.equal(vpnCalls.length, beforeIpsec + 8); assert.deepEqual(ike.failures, []) assert.equal(ike.clients[0].disabled, null); assert.equal(ike.clients[0].online, true); assert.equal(ike.servers[0].clientsOnline, 1) assert.equal(ike.servers[0].peers[0].disabled, null); assert.equal(ike.servers[0].peers[0].passive, true) assert.equal(ike.servers[0].certificatesPage.total, 2); assert.equal(ike.servers[0].certificatesPage.nextOffset, 1) assert.equal(ike.servers[0].certs[0].trusted, false); assert.equal(ike.servers[0].certs[0].keyPairAvailable, true); assert.equal(ike.servers[0].certs[0].expiryTimestampVerified, false) assert.ok(!JSON.stringify(ike).includes(vpnPsk)) vpnAmbiguous = true const ambiguousIke = await invokeMcpTool(vpnPrincipal, "mm_ipsec_state_get", {}) as IpsecPage assert.ok(ambiguousIke.clients.every(row => row.online === null)); assert.equal(ambiguousIke.servers[0].clientsOnline, null); assert.equal(ambiguousIke.servers[0].clientsSessionUnknown, 2) vpnAmbiguous = false; vpnUnknownSession = true assert.equal((await invokeMcpTool(vpnPrincipal, "mm_ipsec_state_get", {}) as IpsecPage).clients[0].online, null); vpnUnknownSession = false const beforeDenied = vpnCalls.length for (const tool of ["mm_wireguard_state_get", "mm_vxlan_state_get", "mm_ipsec_state_get", "mm_gre_state_get"]) await assert.rejects(invokeMcpTool(vpnPrincipal, tool, { serverId: 2 })) assert.equal(vpnCalls.length, beforeDenied) await pool.query("UPDATE servers SET enabled=false WHERE id=1") for (const tool of ["mm_wireguard_state_get", "mm_vxlan_state_get", "mm_ipsec_state_get", "mm_gre_state_get"]) assert.equal((await invokeMcpTool(vpnPrincipal, tool, {}) as { totalServers: number }).totalServers, 0) assert.equal(vpnCalls.length, beforeDenied); await pool.query("UPDATE servers SET enabled=true WHERE id=1") for (const [tool, path] of [["mm_gre_state_get", "/rest/interface/gre"], ["mm_gre_state_get", "/rest/ip/address"], ["mm_gre_state_get", "/rest/ipv6/address"], ["mm_wireguard_state_get", "/rest/interface/wireguard"], ["mm_vxlan_state_get", "/rest/interface/vxlan/vteps"], ["mm_ipsec_state_get", "/rest/ip/ipsec/identity"]]) { vpnFailurePath = path const failed = await invokeMcpTool(vpnPrincipal, tool, {}) as { failures: unknown[]; incomplete: boolean } assert.equal(failed.failures.length, 1); assert.equal(failed.incomplete, true) } vpnFailurePath = ""; vpnDuplicate = true assert.equal((await invokeMcpTool(vpnPrincipal, "mm_wireguard_state_get", {}) as WgPage).failures.length, 1) assert.equal((await invokeMcpTool(vpnPrincipal, "mm_gre_state_get", {}) as GrePage).failures.length, 1) const results = (await pool.query("SELECT result FROM mcp_requests WHERE connection_id=$1", [vpnAccess.connection.id])).rows assert.ok(!JSON.stringify(results).includes(vpnPsk)); assert.ok(!JSON.stringify(results).includes(vpnPrivate)) } finally { vpnFixtures = false; vpnFailurePath = ""; vpnAmbiguous = false; vpnUnknownSession = false; vpnDuplicate = false await pool.query("UPDATE servers SET enabled=true WHERE id=1") } console.log("✓ VPN catalogs share REST polling/models: 3 GRE/4 WG/2 VXLAN/8 IPsec scoped GETs, nested paging, unknown flags/counters/sessions, keys and reflected credentials masked, failures/duplicates and disabled/hidden sources preserved") await (await import("../../test/mcp-routing-sources.js")).verifyRoutingSources(actor) let analysisPeerReads = 0 let analysisPeerInstances = [{ ".id": "*peer-ospf", name: "peer-ospf", "router-id": "192.0.2.2", disabled: "false", version: "2", vrf: "main" }] let analysisPeerNeighbors: Record[] = [{ ".id": "*pn", instance: "peer-ospf", interface: "peer", "router-id": "192.0.2.1", address: "192.0.2.1", state: "Full" }] let analysisPeerAddresses = [{ ".id": "*pa", interface: "peer", address: "192.0.2.2/30", disabled: "false", invalid: "false" }] const analysisPeer = createServer((req, res) => { analysisPeerReads++; if (ospfAnalysisFixtures) ospfAnalysisNativeRequests++; res.setHeader("content-type", "application/json"); res.end(JSON.stringify(req.url === "/rest/routing/ospf/neighbor" ? analysisPeerNeighbors : req.url === "/rest/ip/address" ? analysisPeerAddresses : analysisPeerInstances)) }) await new Promise(resolve => analysisPeer.listen(0, "127.0.0.1", resolve)) const analysisAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Observer OSPF analysis", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read"], scope: { serverIds: [1, 2], operationIds: ["routing.ospf.analyze"], allowGlobal: false } }), actor) const analysisPrincipal = await authenticateMcpToken(analysisAccess.token), observationId = randomUUID(), badObservationId = randomUUID() ospfAnalysisFixtures = true; ospfAnalysisDispatches = 0 await pool.query("UPDATE servers SET port=$1 WHERE id=2", [(analysisPeer.address() as { port: number }).port]) await pool.query("INSERT INTO mcp_routing_observations(id,src_server_id,dst_server_id,src_interface,dst_interface,family,routing_table,ping_ms,loss_pct,dl_mbps,ul_mbps,ping_at,bandwidth_at,context_verified,source) VALUES($1,1,2,'fixture-gre','peer','ipv4','main',12,0,100,200,now(),now(),true,'fixture')", [observationId]) await attestRoutingFixture(observationId) try { type OspfAnalysis = { ranking: { currentCost: number; proposedCost: number; observationId: string; peerId: number }[]; serverIds: number[]; changes: number; proposalOnly: boolean; executionAvailable: boolean } const applicationPreviewPolicy={name:"OSPF application preview",mode:"observer",expiresAt:new Date(Date.now()+1800000).toISOString(),permissions:["mm:network:read"],scope:{serverIds:[1,2],operationIds:["routing.ospf.application.preview"],allowGlobal:false}} const applicationPreviewAccess=await createMcpConnection(mcpConnectionCreateSchema.parse(applicationPreviewPolicy),actor),applicationPreviewPrincipal=await authenticateMcpToken(applicationPreviewAccess.token) const isolatedPreviewAccess=await createMcpConnection(mcpConnectionCreateSchema.parse({...applicationPreviewPolicy,name:"OSPF source-only preview",scope:{...applicationPreviewPolicy.scope,serverIds:[1]}}),actor),isolatedPreviewPrincipal=await authenticateMcpToken(isolatedPreviewAccess.token) const {createOspfRoutes}=await import("../../routes/ospf.js"),{ensurePartitionFor:ensurePreviewPartition}=await import("../../db/partitions.js") const previewHttp=FilterFastify();previewHttp.setValidatorCompiler(filterValidator);previewHttp.setSerializerCompiler(filterSerializer);await previewHttp.register(createOspfRoutes(),{prefix:"/api"}) const previewProbeId=randomUUID(),previewTrafficTimes=[0,1,2].map(delta=>new Date(Date.now()-10000+delta).toISOString()),templatesBeforePreview=structuredClone(fixtureOspf["interface-template"]) type ApplicationPreview={interfacesTotal:number;proposalOnly:boolean;executionAvailable:boolean;metricContextVerified:boolean;interfaces:{items:{currentCost:number;modeled:{pingMs:number;dlMbps:number;ulMbps:number;score:number;optimalCost:number};observed:{ping:{value:number|null;sourceId:string|null};dl:{value:number|null};ul:{value:number|null}}}[];total:number;nextOffset:number|null};changes:{total:number};allowedMetricServerIds:number[]} try{ await pool.query("INSERT INTO uptime_speed_probes(id,src_server_id,dst_server_id,src_interface,last_ping_rtt_ms,last_ping_at,last_tx_avg_mbps,last_rx_avg_mbps,last_error) VALUES($1,1,2,'fixture-gre',0,now(),0,0,'NEVER_OSPF_PREVIEW_ERROR')",[previewProbeId]) const beforePeer=analysisPeerReads,plansBeforePreview=(await pool.query("SELECT count(*)::int AS n FROM mcp_plans")).rows[0].n const isolated=await invokeMcpTool(isolatedPreviewPrincipal,"mm_ospf_application_preview",{serverId:1}) as ApplicationPreview assert.equal(analysisPeerReads,beforePeer);assert.equal(isolated.interfaces.items[0].observed.ping.value,null);assert.equal(isolated.interfaces.items[0].observed.ping.sourceId,null) assert.deepEqual(isolated.allowedMetricServerIds,[1]);assert.equal(JSON.stringify(isolated).includes(previewProbeId),false) await assert.rejects(invokeMcpTool(isolatedPreviewPrincipal,"mm_ospf_application_preview",{serverId:1,peerServerIds:[2]}));assert.equal(analysisPeerReads,beforePeer) const application=await invokeMcpTool(applicationPreviewPrincipal,"mm_ospf_application_preview",{serverId:1,limit:1}) as ApplicationPreview assert.ok(analysisPeerReads>beforePeer);assert.equal(application.interfaces.items[0].observed.ping.value,0);assert.equal(application.interfaces.items[0].observed.dl.value,0);assert.equal(application.interfaces.items[0].observed.ul.value,0) assert.equal(application.interfaces.items[0].modeled.pingMs,1);assert.equal(application.interfaces.items[0].modeled.dlMbps,1);assert.equal(application.metricContextVerified,false);assert.equal(application.proposalOnly,true);assert.equal(application.executionAvailable,false) const restPreview=await previewHttp.inject({method:"POST",url:"/api/servers/1/ospf/optimize/preview",payload:{pingWeight:60}}) assert.equal(restPreview.statusCode,200,restPreview.body) assert.deepEqual(application.interfaces.items[0].modeled,{pingMs:restPreview.json().interfaces[0].pingMs,dlMbps:restPreview.json().interfaces[0].dlMbps,ulMbps:restPreview.json().interfaces[0].ulMbps,score:restPreview.json().interfaces[0].score,optimalCost:restPreview.json().interfaces[0].optimalCost}) const {readLegacyOspfSealedSources}=await import("./ospf-legacy-source.js") const {MikrotikClient}=await import("../../services/mikrotik.js") const legacyServer=(await (await import("./resources.js")).routerClient(1)).server let legacySourceGates=0 const legacyOptions={serverIds:[1,2],authorize:async()=>{legacySourceGates++},signal:AbortSignal.timeout(30000)} const legacySealed=await readLegacyOspfSealedSources(legacyServer,60,legacyOptions) assert.deepEqual(legacySealed.plan.interfaces,restPreview.json().interfaces) assert.ok(legacySourceGates>10);assert.ok(legacySealed.sourceCount>5) assert.equal((await readLegacyOspfSealedSources(legacyServer,60,legacyOptions)).proof,legacySealed.proof) const legacyFirst=legacySealed.plan.interfaces.find(row=>row.currentCost!==row.optimalCost)! assert.ok(legacyFirst) const legacyNativeCost=fixtureOspf["interface-template"].find(row=>row[".id"]===legacyFirst.id)! const legacyOriginalCost=legacyNativeCost.cost try{ legacyNativeCost.cost=String(legacyFirst.optimalCost) const prefix=await readLegacyOspfSealedSources(legacyServer,60,legacyOptions,{baseline:legacySealed.plan,completedCount:1}) assert.equal(prefix.proof,legacySealed.proof) await assert.rejects(readLegacyOspfSealedSources(legacyServer,60,legacyOptions,{baseline:legacySealed.plan,completedCount:0}),/не подтверждены/) }finally{legacyNativeCost.cost=legacyOriginalCost} const legacyProbeMetric=(await pool.query("SELECT last_ping_rtt_ms FROM uptime_speed_probes WHERE id=$1",[previewProbeId])).rows[0].last_ping_rtt_ms try{ await pool.query("UPDATE uptime_speed_probes SET last_ping_rtt_ms=last_ping_rtt_ms+1 WHERE id=$1",[previewProbeId]) assert.notEqual((await readLegacyOspfSealedSources(legacyServer,60,legacyOptions)).proof,legacySealed.proof) }finally{await pool.query("UPDATE uptime_speed_probes SET last_ping_rtt_ms=$2 WHERE id=$1",[previewProbeId,legacyProbeMetric])} const legacyIntentBefore=(await pool.query("SELECT count(*)::int AS n FROM mcp_audit WHERE action='configuration.intent'")).rows[0].n const legacyDispatchBefore=ospfAnalysisDispatches let legacyDispatchGate=0 try{ await assert.rejects(MikrotikClient.fromServer(legacyServer).setOspfInterfaceTemplateCost(legacyFirst.id,legacyFirst.optimalCost,legacyFirst.currentCost,undefined,async()=>{ legacyDispatchGate++ assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_audit WHERE action='configuration.intent'")).rows[0].n,legacyIntentBefore+1) await pool.query("UPDATE uptime_speed_probes SET last_ping_rtt_ms=last_ping_rtt_ms+1 WHERE id=$1",[previewProbeId]) if((await readLegacyOspfSealedSources(legacyServer,60,legacyOptions)).proof!==legacySealed.proof)throw Error("NEVER_PUBLISH_LEGACY_SOURCE_DRIFT") }),error=>error instanceof Error&&"writeAttempted"in error&&error.writeAttempted===false&&!error.message.includes("NEVER_PUBLISH")) assert.equal(legacyDispatchGate,1);assert.equal(ospfAnalysisDispatches,legacyDispatchBefore) assert.equal(legacyNativeCost.cost,legacyOriginalCost) }finally{await pool.query("UPDATE uptime_speed_probes SET last_ping_rtt_ms=$2 WHERE id=$1",[previewProbeId,legacyProbeMetric])} console.log("✓ Legacy OSPF native dispatch gate: SQL drift after durable configuration.intent rejected before PATCH, attempted=false and no provider/source error leakage") const {executeSealedLegacyOspf,reconcileSealedLegacyOspf,LegacyOspfExecutionError}=await import("./ospf-legacy-execution.js") const legacyExecutionBaseline=structuredClone(fixtureOspf["interface-template"]),legacyExecutionDispatches=ospfAnalysisDispatches try{ fixtureOspf["interface-template"].push({...legacyExecutionBaseline[0],".id":"*secondary",interfaces:"fixture-other",cost:"40"}) const executionBaseline=structuredClone(fixtureOspf["interface-template"]) const readExecution=()=>readLegacyOspfSealedSources(legacyServer,60,{...legacyOptions,signal:AbortSignal.timeout(45000)}) const executionOptions={...legacyOptions,signal:AbortSignal.timeout(45000)} const sealed=await readExecution() assert.equal(sealed.plan.interfaces.length,2) assert.deepEqual(await reconcileSealedLegacyOspf(legacyServer,sealed,executionOptions),{matchesOriginalState:true,matchesDesiredState:false,confirmedPrefix:0}) const successBefore=ospfAnalysisDispatches const receipt=await executeSealedLegacyOspf(legacyServer,sealed,executionOptions) assert.equal(receipt.optimizedCount,2);assert.equal(ospfAnalysisDispatches,successBefore+2) assert.deepEqual(await reconcileSealedLegacyOspf(legacyServer,sealed,executionOptions),{matchesOriginalState:false,matchesDesiredState:true,confirmedPrefix:2}) assert.equal(ospfAnalysisDispatches,successBefore+2) fixtureOspf["interface-template"]=structuredClone(executionBaseline) await pool.query("UPDATE uptime_speed_probes SET last_ping_rtt_ms=last_ping_rtt_ms+1 WHERE id=$1",[previewProbeId]) const staleBefore=ospfAnalysisDispatches await assert.rejects(executeSealedLegacyOspf(legacyServer,sealed,executionOptions),error=>error instanceof LegacyOspfExecutionError&&error.state==="rejected") assert.equal(ospfAnalysisDispatches,staleBefore) await pool.query("UPDATE uptime_speed_probes SET last_ping_rtt_ms=$2 WHERE id=$1",[previewProbeId,legacyProbeMetric]) for(const fault of ["ignored","lost-response","changed-field"] as const){ fixtureOspf["interface-template"]=structuredClone(executionBaseline) ospfCostWriteFault=fault const before=ospfAnalysisDispatches await assert.rejects(executeSealedLegacyOspf(legacyServer,sealed,{...executionOptions,signal:AbortSignal.timeout(45000)}),error=>error instanceof LegacyOspfExecutionError&&error.state==="unknown"&&!error.message.includes("NEVER_PUBLISH")) assert.equal(ospfAnalysisDispatches,before+1,"An uncertain first write must stop the second and must not be retried") ospfCostWriteFault="none" const reconciled=await reconcileSealedLegacyOspf(legacyServer,sealed,{...executionOptions,signal:AbortSignal.timeout(45000)}) assert.equal(reconciled.matchesDesiredState,false) if(fault==="ignored")assert.equal(reconciled.matchesOriginalState,true) if(fault==="lost-response")assert.equal(reconciled.confirmedPrefix,1) if(fault==="changed-field")assert.equal(reconciled.confirmedPrefix,null) assert.equal(ospfAnalysisDispatches,before+1,"Reconciliation must not dispatch any mutation") } }finally{ fixtureOspf["interface-template"]=legacyExecutionBaseline;ospfAnalysisDispatches=legacyExecutionDispatches;ospfCostWriteFault="none" await pool.query("UPDATE uptime_speed_probes SET last_ping_rtt_ms=$2 WHERE id=$1",[previewProbeId,legacyProbeMetric]) } console.log("✓ Legacy OSPF sealed executor: two sequential cost PATCHes, full source checks before dispatch/after read-back, stale zero-write refusal, ignored/lost/collateral stop after one command, private errors and readonly prefix reconciliation; local SQL/HTTP only") const legacyBeforeDenied=analysisPeerReads await assert.rejects(readLegacyOspfSealedSources(legacyServer,60,{...legacyOptions,authorize:async()=>{throw Error("legacy access revoked")}}),/legacy access revoked/) await assert.rejects(readLegacyOspfSealedSources(legacyServer,60,{...legacyOptions,signal:AbortSignal.abort()})) await assert.rejects(readLegacyOspfSealedSources(legacyServer,60,{...legacyOptions,deadlineAt:Date.now()-1})) assert.equal(analysisPeerReads,legacyBeforeDenied) countCatalogCalls=true const legacyBeforeStaleSource=catalogCalls try{ await assert.rejects(readLegacyOspfSealedSources({...legacyServer,password:"stale-client-credential"},60,{...legacyOptions,signal:AbortSignal.timeout(45000)}),/не подтверждены/) assert.equal(catalogCalls,legacyBeforeStaleSource) }finally{countCatalogCalls=false} console.log("✓ Legacy OSPF sealed sources: actual SQL/native REST rank parity, stable private proof, confirmed cost prefix, wrong prefix refusal and pre-source access denial; no native mutation") assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_plans")).rows[0].n,plansBeforePreview) for(const time of previewTrafficTimes)await ensurePreviewPartition(pool,"traffic_samples","week",new Date(time)) await pool.query("UPDATE uptime_speed_probes SET last_tx_avg_mbps=NULL,last_rx_avg_mbps=NULL WHERE id=$1",[previewProbeId]) await pool.query("INSERT INTO traffic_samples(server_id,interface_name,peer_public_key,sampled_at,rx_bps,tx_bps,flags) VALUES(1,'fixture-gre','',$1,0,0,61),(1,'fixture-gre','NEVER_OSPF_PEER_KEY',$2,99000000,99000000,61)",[previewTrafficTimes[0],previewTrafficTimes[1]]) const measuredZero=await invokeMcpTool(applicationPreviewPrincipal,"mm_ospf_application_preview",{serverId:1}) as ApplicationPreview assert.equal(measuredZero.interfaces.items[0].observed.dl.value,0);assert.equal(measuredZero.interfaces.items[0].observed.ul.value,0) await pool.query("INSERT INTO traffic_samples(server_id,interface_name,sampled_at,rx_bps,tx_bps,flags) VALUES(1,'fixture-gre',$1,0,0,36)",[previewTrafficTimes[2]]) const unknownRate=await invokeMcpTool(applicationPreviewPrincipal,"mm_ospf_application_preview",{serverId:1}) as ApplicationPreview assert.equal(unknownRate.interfaces.items[0].observed.dl.value,null);assert.equal(unknownRate.interfaces.items[0].observed.ul.value,null);assert.equal(unknownRate.interfaces.items[0].modeled.dlMbps,10) await pool.query("UPDATE traffic_samples SET flags=0,rx_bps=40000000 WHERE server_id=1 AND interface_name='fixture-gre' AND sampled_at=$1",[previewTrafficTimes[2]]) assert.equal((await invokeMcpTool(applicationPreviewPrincipal,"mm_ospf_application_preview",{serverId:1}) as ApplicationPreview).interfaces.items[0].observed.dl.value,null) await pool.query("UPDATE uptime_speed_probes SET enabled=false WHERE id=$1",[previewProbeId]) assert.equal((await invokeMcpTool(applicationPreviewPrincipal,"mm_ospf_application_preview",{serverId:1}) as ApplicationPreview).interfaces.items[0].observed.ping.value,null) const paged=await invokeMcpTool(applicationPreviewPrincipal,"mm_ospf_application_preview",{serverId:1,offset:1,limit:1}) as ApplicationPreview assert.deepEqual(paged.interfaces.items,[]);assert.equal(paged.interfaces.total,1);assert.equal(paged.interfaces.nextOffset,null) fixtureOspf["interface-template"]=[] const noPeerReads=analysisPeerReads,emptyPreview=await invokeMcpTool(applicationPreviewPrincipal,"mm_ospf_application_preview",{serverId:1}) as ApplicationPreview assert.equal(emptyPreview.interfacesTotal,0);assert.equal(emptyPreview.changes.total,0);assert.equal(analysisPeerReads,noPeerReads) fixtureOspf["interface-template"]=[{...templatesBeforePreview[0],cost:"unknown"}] await assert.rejects(invokeMcpTool(applicationPreviewPrincipal,"mm_ospf_application_preview",{serverId:1}),/не подтверждён/) fixtureOspf["interface-template"]=templatesBeforePreview countCatalogCalls=true const beforeCanceled=catalogCalls await assert.rejects(invokeMcpTool(applicationPreviewPrincipal,"mm_ospf_application_preview",{serverId:1},AbortSignal.abort())) assert.equal(catalogCalls,beforeCanceled);countCatalogCalls=false const beforePause=analysisPeerReads ospfPreviewPauseConnection=applicationPreviewAccess.connection.id await assert.rejects(invokeMcpTool(applicationPreviewPrincipal,"mm_ospf_application_preview",{serverId:1})) assert.equal(analysisPeerReads,beforePause) await pool.query("UPDATE mcp_connections SET policy=jsonb_set(policy,'{status}',to_jsonb('active'::text)) WHERE id=$1",[applicationPreviewAccess.connection.id]) const disabledPeerReads=analysisPeerReads await pool.query("UPDATE servers SET enabled=false WHERE id=1") await assert.rejects(invokeMcpTool(applicationPreviewPrincipal,"mm_ospf_application_preview",{serverId:1})) assert.equal(analysisPeerReads,disabledPeerReads) assert.equal(ospfAnalysisDispatches,0) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_requests WHERE connection_id=ANY($1::uuid[]) AND result::text LIKE '%NEVER_OSPF%'",[[applicationPreviewAccess.connection.id,isolatedPreviewAccess.connection.id]])).rows[0].n,0) }finally{ countCatalogCalls=false;ospfPreviewPauseConnection=undefined await pool.query("UPDATE mcp_connections SET policy=jsonb_set(policy,'{status}',to_jsonb('active'::text)) WHERE id=$1",[applicationPreviewAccess.connection.id]) fixtureOspf["interface-template"]=templatesBeforePreview await pool.query("UPDATE servers SET enabled=true WHERE id=1") await pool.query("DELETE FROM uptime_speed_probes WHERE id=$1",[previewProbeId]) await pool.query("DELETE FROM traffic_samples WHERE server_id=1 AND interface_name='fixture-gre' AND sampled_at=ANY($1::timestamptz[])",[previewTrafficTimes]) await previewHttp.close() } console.log("✓ OSPF application preview shares REST ranking: scoped peer/native/SQL reads, hidden probe exclusion, modeled versus observed zero/unknown, aggregate continuity flags, no WG-peer substitution, pages, empty/malformed/disabled sources and no mutation/plan/audit leakage") countCatalogCalls=true try{await (await import("../../test/mcp-ospf-legacy.js")).acceptLegacyOspfPlans(actor,{ get:()=>fixtureOspf["interface-template"],set:rows=>{fixtureOspf["interface-template"]=rows as typeof fixtureOspf["interface-template"]}, reads:()=>catalogCalls,writes:()=>ospfAnalysisDispatches,resetWrites:value=>{ospfAnalysisDispatches=value},fault:value=>{ospfCostWriteFault=value}, })}finally{countCatalogCalls=false} assert.equal((await (await import("./routing-observations.js")).readRoutingObservations([1, 2]))[0].sourceVerified, false, "Disable/re-enable must invalidate previous source attestation") await attestRoutingFixture(observationId) const proposal = await invokeMcpTool(analysisPrincipal, "mm_ospf_optimizer_analyze", { serverId: 1 }) as OspfAnalysis assert.deepEqual(proposal.serverIds, [1, 2]); assert.equal(proposal.changes, 1) assert.equal(proposal.ranking[0].currentCost, 30); assert.equal(proposal.ranking[0].proposedCost, 10) assert.equal(proposal.ranking[0].observationId, observationId); assert.equal(proposal.ranking[0].peerId, 2) assert.equal(proposal.proposalOnly, true); assert.equal(proposal.executionAvailable, false) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_plans WHERE connection_id=$1", [analysisAccess.connection.id])).rows[0].n, 0) assert.equal(ospfAnalysisDispatches, 0) await assert.rejects(invokeMcpTool(analysisPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 })) const writer = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "OSPF analysis comparison", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], scope: { serverIds: [1, 2], operationIds: ["routing.ospf.optimize"], allowGlobal: false } }), actor) const writerPrincipal = await authenticateMcpToken(writer.token) const prepared = await invokeMcpTool(writerPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }) as { planId: string; changes: { before: { ranking: unknown[] } } } assert.deepEqual(prepared.changes.before.ranking, proposal.ranking) const observationEvidence = JSON.parse(await decryptSecret((await pool.query("SELECT evidence FROM mcp_plans WHERE id=$1", [prepared.planId])).rows[0].evidence.sealed)) const { verifyRoutingObservationChecks } = await import("./routing-observations.js") await verifyRoutingObservationChecks(observationEvidence.observationChecks) await assert.rejects(verifyRoutingObservationChecks(observationEvidence.observationChecks.map((check: Record) => ({ ...check, validUntil: new Date(Date.now() - 1).toISOString() })))) await approvePlan(prepared.planId, actor, true) await pool.query("UPDATE mcp_routing_observations SET created_at=now()-interval '1 minute' WHERE id=$1", [observationId]) await attestRoutingFixture(observationId) await pool.query("INSERT INTO mcp_routing_observations(id,src_server_id,dst_server_id,src_interface,dst_interface,family,routing_table,ping_ms,loss_pct,dl_mbps,ul_mbps,ping_at,bandwidth_at,context_verified,source) VALUES($1,1,2,'fixture-gre','peer','ipv4','main',12,50,100,200,now(),now(),true,'fixture')", [badObservationId]) await attestRoutingFixture(badObservationId) await assert.rejects(invokeMcpTool(analysisPrincipal, "mm_ospf_optimizer_analyze", { serverId: 1 }), "A newer bad result must block fallback to an older healthy observation") await assert.rejects(verifyRoutingObservationChecks(observationEvidence.observationChecks)) const staleOperation = await applyPlan(writerPrincipal, prepared.planId, randomUUID()) let staleState = "pending" for (let attempt = 0; attempt < 100; attempt++) { staleState = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [staleOperation.id])).rows[0].state if (!["pending", "running"].includes(staleState)) break await delay(20) } assert.equal(staleState, "rejected"); assert.equal(ospfAnalysisDispatches, 0) await pool.query("UPDATE mcp_routing_observations SET loss_pct=0,context_verified=false WHERE id=$1", [badObservationId]) await attestRoutingFixture(badObservationId) await assert.rejects(invokeMcpTool(analysisPrincipal, "mm_ospf_optimizer_analyze", { serverId: 1 })) await pool.query("DELETE FROM mcp_routing_observations WHERE id=$1", [badObservationId]) await pool.query("UPDATE mcp_routing_observations SET loss_pct=5 WHERE id=$1", [observationId]) await attestRoutingFixture(observationId) await assert.rejects(verifyRoutingObservationChecks(observationEvidence.observationChecks)) await assert.rejects(invokeMcpTool(analysisPrincipal, "mm_ospf_optimizer_analyze", { serverId: 1 })) const overridden = await invokeMcpTool(analysisPrincipal, "mm_ospf_optimizer_analyze", { serverId: 1, maxLossPct: 5 }) as OspfAnalysis assert.equal(overridden.ranking.length, 1) await pool.query("UPDATE mcp_routing_observations SET loss_pct=0,ping_at=now()-interval '10 minutes' WHERE id=$1", [observationId]) await attestRoutingFixture(observationId) await assert.rejects(invokeMcpTool(analysisPrincipal, "mm_ospf_optimizer_analyze", { serverId: 1 })) const limitedAnalysis = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "OSPF isolated analysis", mode: "observer", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read"], scope: { serverIds: [1], operationIds: ["routing.ospf.analyze"], allowGlobal: false } }), actor) const readsBefore = analysisPeerReads await assert.rejects(invokeMcpTool(await authenticateMcpToken(limitedAnalysis.token), "mm_ospf_optimizer_analyze", { serverId: 1 })) assert.equal(analysisPeerReads, readsBefore); assert.equal(ospfAnalysisDispatches, 0) await pool.query("UPDATE mcp_routing_observations SET loss_pct=0,ping_at=now(),bandwidth_at=now(),context_verified=true WHERE id=$1", [observationId]) await attestRoutingFixture(observationId) const costBaseline = structuredClone(fixtureOspf["interface-template"]) const waitCost = waitRecursiveOperation const rejectGenuinelyQueuedOldVersion = async (principal: Parameters[0], planId: string, oldVersion: number, description: string) => { const originalPoolQuery = pool.query, query = originalPoolQuery.bind(pool) let release!: () => void const barrier = new Promise(done => { release = done }) let paused: { id: string; plan_id: string; state: string } | undefined let operation: Awaited> | undefined, terminal: string | undefined const dispatches = ospfAnalysisDispatches // Delegate every original query with unchanged arguments/results. Hold // only the REAL matching running row, before executeOperationWithLease // can loadPlan. Admission and its private capabilities stay genuine. pool.query = ((...args: unknown[]) => { const actual = (query as (...values: unknown[]) => unknown)(...args) if (args[0] !== "SELECT * FROM mcp_operations WHERE id=$1 AND state='running'") return actual return (actual as Promise<{ rows: { id: string; plan_id: string; state: string }[] }>).then(async result => { const row = result.rows[0] if (!paused && row?.plan_id === planId) { assert.equal(row.state, "running"); paused = row await barrier } return result }) }) as typeof pool.query try { operation = await applyPlan(principal, planId, randomUUID()) const deadline = Date.now() + 10000 while (!paused) { assert(Date.now() < deadline, "The genuine runner did not reach the matching queued-version observation") await delay(10) } assert.equal(paused.id, operation.id); assert.equal(paused.plan_id, planId) await query("UPDATE mcp_plans SET operation_version=$2 WHERE id=$1", [planId, oldVersion]) } finally { // Always release the original returned row, restore the observer and // join the actual admitted operation before native fixture cleanup. release(); pool.query = originalPoolQuery if (operation) terminal = await waitCost(operation.id) } assert(operation) assert.equal(terminal, "rejected", description) assert.equal((await query("SELECT error_code FROM mcp_operations WHERE id=$1", [operation.id])).rows[0].error_code, "PLAN_EXPIRED") assert.equal(ospfAnalysisDispatches, dispatches) } let thirdOwnerRows: Record[] = [{ ".id": "*third", name: "third", "router-id": "192.0.2.103", disabled: "false", version: "2", vrf: "main" }], thirdOwnerReads = 0 let thirdOwnerDuringRead: (() => Promise) | undefined let thirdOwnerIds: Record[] = [] let thirdOwnerNeighbors: Record[] = [], thirdOwnerAddresses: Record[] = [] const thirdOwner = createServer(async (req, res) => { thirdOwnerReads++ if (ospfAnalysisFixtures) ospfAnalysisNativeRequests++ if (thirdOwnerDuringRead) { const work = thirdOwnerDuringRead; thirdOwnerDuringRead = undefined; await work() } res.setHeader("content-type", "application/json") if (req.url === "/rest/routing/id") { res.end(JSON.stringify(thirdOwnerIds)); return } if (req.url === "/rest/routing/ospf/neighbor") { res.end(JSON.stringify(thirdOwnerNeighbors)); return } if (req.url === "/rest/ip/address") { res.end(JSON.stringify(thirdOwnerAddresses)); return } if (req.url === "/rest/routing/ospf/instance/get") { let body = ""; for await (const chunk of req) body += chunk const key = JSON.parse(body)["value-name"] res.end(JSON.stringify({ ret: ({ version: "2", vrf: "remote-named-vrf", "router-id": "main" } as Record)[key] })); return } res.end(JSON.stringify(thirdOwnerRows)) }) await new Promise(resolve => thirdOwner.listen(0, "127.0.0.1", resolve)) await pool.query("INSERT INTO servers(id,name,host,port,use_ssl,password) VALUES(103,'unselected-ospf-owner','127.0.0.1',$1,false,'NEVER_OSPF_UNUSED_SECRET')", [(thirdOwner.address() as { port: number }).port]) const graphPolicy = { limits: { callsPerMinute: 120, mutationsPerHour: 100 }, name: "OSPF complete owners", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:write"], scope: { serverIds: [1, 2, 103], operationIds: ["routing.ospf.optimize", "operations.recover"], allowGlobal: false } } const graphAccess = await createMcpConnection(mcpConnectionCreateSchema.parse(graphPolicy), actor), graphPrincipal = await authenticateMcpToken(graphAccess.token) const graphPrimaryInstances = structuredClone(fixtureOspf.instance) const graphPeerInstances = structuredClone(analysisPeerInstances), graphPeerNeighbors = structuredClone(analysisPeerNeighbors), graphPeerAddresses = structuredClone(analysisPeerAddresses) try { const dispatchesBeforePassive = ospfAnalysisDispatches fixtureOspf["interface-template"][0].passive = "true" await assert.rejects(invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }), (error: { code?: string }) => error.code === "MEASUREMENT_UNKNOWN", "A passive template cannot become eligible through a stale Full neighbor") for (const passive of ["true", "false"]) { fixtureOspf["interface-template"] = [...structuredClone(costBaseline), { ...costBaseline[0], ".id": "*passive", interfaces: "fixture-loop", passive }] const mixedPassive = await invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }) as { planId: string } const mixedRow = (await pool.query("SELECT evidence FROM mcp_plans WHERE id=$1", [mixedPassive.planId])).rows[0] const mixedEvidence = JSON.parse(await decryptSecret(mixedRow.evidence.sealed)) as { before: { ranking: { interface: string }[] }; steps: { step: { id: string } }[] } assert.deepEqual(mixedEvidence.before.ranking.map(row => row.interface), [costBaseline[0].interfaces]) assert.deepEqual(mixedEvidence.steps.map(row => row.step.id), [costBaseline[0][".id"]]) } assert.equal(ospfAnalysisDispatches, dispatchesBeforePassive, "Passive or no-Full templates with unknown BFD cannot block or enter the selected GRE plan") fixtureOspf["interface-template"] = [{ ...costBaseline[0], interfaces: "fixture-loop" }] ospfAnalysisExtraNeighbors = [{ ".id": "*loop", instance: "fixture-ospf", interface: "fixture-loop", state: "Full", "router-id": "192.0.2.2", address: "192.0.2.2" }] try { await assert.rejects(invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }), (error: { code?: string; message?: string }) => error.code === "MEASUREMENT_UNKNOWN" && Boolean(error.message?.includes("use-bfd")), "Unknown BFD remains forbidden for a Full candidate") } finally { ospfAnalysisExtraNeighbors = [] } fixtureOspf["interface-template"] = structuredClone(costBaseline) for (const listsBeforePause of [0, 1]) { const readsBeforePause: number = ospfTemplateFlagReads ospfPauseOnTemplatesConnection = graphAccess.connection.id; ospfTemplateListsBeforePause = listsBeforePause try { await assert.rejects(invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 })) assert.equal(ospfTemplateFlagReads, readsBeforePause + listsBeforePause * 2, "Pause committed during either template list must stop before another flag receipt") } finally { ospfPauseOnTemplatesConnection = undefined; await setConnectionStatus(graphAccess.connection.id, "active", actor) } } const limitedGraph = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...graphPolicy, name: "OSPF owner budget", limits: { serversPerPlan: 2 } }), actor) const readsBeforeBudget = [thirdOwnerReads, analysisPeerReads] await assert.rejects(invokeMcpTool(await authenticateMcpToken(limitedGraph.token), "mm_ospf_optimizer_plan", { serverId: 1 })) assert.deepEqual([thirdOwnerReads, analysisPeerReads], readsBeforeBudget, "All owner dependencies must fit policy before native polling") await setConnectionStatus(limitedGraph.connection.id, "paused", actor) for (const fault of ["none", "duplicate", "empty", "credential", "disabled", "during-read", "other-family", "family-change", "elected-change", "elected-inactive", "elected-defaults", "unrelated-vrf", "isolated-vrf", "peer-vrf", "peer-down", "peer-address", "peer-interface", "counter-only", "post-peer-down", "template-order", "post-template-order"]) { fixtureOspf["interface-template"] = structuredClone(costBaseline) if (fault === "template-order" || fault === "post-template-order") fixtureOspf["interface-template"].push({ ...costBaseline[0], ".id": "*passive", interfaces: "fixture-loop", passive: "true" }) if (fault === "other-family" || fault === "family-change") Object.assign(thirdOwnerRows[0], { "router-id": "192.0.2.2", version: "3", vrf: "remote-named-vrf" }) if (fault.startsWith("elected-")) { thirdOwnerRows[0]["router-id"] = "main" thirdOwnerIds = [{ ".id": "*ref", name: "main", "dynamic-id": "192.0.2.103", disabled: "false", inactive: "false" }] if (fault === "elected-defaults") { delete thirdOwnerRows[0].version; delete thirdOwnerRows[0].vrf; delete thirdOwnerRows[0]["router-id"] } } if (fault === "unrelated-vrf") { fixtureOspf.instance.push({ ".id": "*other", name: "other-instance", vrf: "isolated", "router-id": "192.0.2.200", version: "2", disabled: "false" }) ospfAnalysisExtraNeighbors = [{ ".id": "*other-neighbor", interface: "other-iface", instance: "other-instance", "router-id": "192.0.2.1", address: "198.51.100.1", state: "Full" }] thirdOwnerRows[0]["router-id"] = "192.0.2.1" } if (fault === "isolated-vrf") { Object.assign(thirdOwnerRows[0], { "router-id": "192.0.2.2", vrf: "isolated-third-vrf" }) thirdOwnerNeighbors = [{ ".id": "*tn", instance: "third", interface: "third", "router-id": "192.0.2.1", address: "198.51.100.1", state: "Full" }] thirdOwnerAddresses = [{ ".id": "*ta", interface: "third", address: "198.51.100.3/24", disabled: "false", invalid: "false" }] } if (fault === "peer-vrf") analysisPeerInstances[0].vrf = "remote-different-vrf" const prepared = await invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }) as { planId: string; changes: { target: { serverIds: number[] }; ospfOwnerGraph: { sourceSecretProofs: unknown; instances: { serverId: number }[] } } } assert.deepEqual(prepared.changes.target.serverIds, [1, 2, 103]) assert.deepEqual(prepared.changes.ospfOwnerGraph.instances.map(row => row.serverId), [1, 2, 103]) assert.equal(prepared.changes.ospfOwnerGraph.sourceSecretProofs, "[redacted]") assert.ok(!JSON.stringify(prepared).includes("NEVER_OSPF_UNUSED_SECRET")) await approvePlan(prepared.planId, actor, true) if (fault === "duplicate") thirdOwnerRows[0]["router-id"] = "192.0.2.2" if (fault === "empty") thirdOwnerRows = [] if (fault === "credential") await pool.query("UPDATE servers SET password='changed-unused-owner' WHERE id=103") if (fault === "disabled") await pool.query("UPDATE servers SET enabled=false WHERE id=103") if (fault === "during-read") thirdOwnerDuringRead = () => pool.query("UPDATE servers SET password='changed-during-native-read' WHERE id=103") if (fault === "family-change") thirdOwnerRows[0].version = "2" if (fault === "elected-change") thirdOwnerIds[0]["dynamic-id"] = "192.0.2.2" if (fault === "elected-inactive") thirdOwnerIds[0].inactive = "true" if (fault === "peer-down") analysisPeerNeighbors[0].state = "Down" if (fault === "peer-address") analysisPeerAddresses[0].address = "192.0.2.99/30" if (fault === "peer-interface") analysisPeerNeighbors[0].interface = "another-interface" if (fault === "counter-only") Object.assign(analysisPeerNeighbors[0], { adjacency: "99h", "state-changes": "999" }) if (fault === "post-peer-down") ospfAfterCostPatch = () => { analysisPeerNeighbors[0].state = "Down" } if (fault === "template-order") fixtureOspf["interface-template"].reverse() if (fault === "post-template-order") ospfAfterCostPatch = () => { fixtureOspf["interface-template"].reverse() } const dispatches: number = ospfAnalysisDispatches, requestsBeforeApply = ospfAnalysisNativeRequests, key = randomUUID(), operation = await applyPlan(graphPrincipal, prepared.planId, key) const positive = ["none", "other-family", "elected-defaults", "unrelated-vrf", "isolated-vrf", "peer-vrf", "counter-only"].includes(fault) assert.equal(await waitCost(operation.id), positive ? "applied" : fault === "post-peer-down" ? "partial" : fault === "post-template-order" ? "unknown" : "rejected", fault) assert.equal(ospfAnalysisDispatches, dispatches + (positive || fault === "post-peer-down" || fault === "post-template-order" ? 1 : 0), fault) assert.equal((await applyPlan(graphPrincipal, prepared.planId, key)).id, operation.id) if (fault === "none") { const nativeRequests = ospfAnalysisNativeRequests - requestsBeforeApply assert.ok(nativeRequests <= 160, `One OSPF cost PATCH caused ${nativeRequests} native requests; full graph polling must not run inside each catalog callback`) console.log(`✓ OSPF dispatch read budget: ${nativeRequests} native requests across three owners, including one PATCH; no nested full graph polling`) await pool.query("UPDATE mcp_plans SET operation_version=8 WHERE id=$1", [prepared.planId]) try { await assert.rejects(invokeMcpTool(graphPrincipal, "mm_operation_recovery_plan", { operationId: operation.id }), (error: { code?: string }) => error.code === "PLAN_EXPIRED") } finally { await pool.query("UPDATE mcp_plans SET operation_version=9 WHERE id=$1", [prepared.planId]) } const oldInverse = await invokeMcpTool(graphPrincipal, "mm_operation_recovery_plan", { operationId: operation.id }) as { planId: string } await approvePlan(oldInverse.planId, actor, true) await pool.query("UPDATE mcp_plans SET operation_version=1 WHERE id=$1", [oldInverse.planId]) await assert.rejects(applyPlan(graphPrincipal, oldInverse.planId, randomUUID()), (error: { code?: string }) => error.code === "PLAN_EXPIRED") const queuedInverse = await invokeMcpTool(graphPrincipal, "mm_operation_recovery_plan", { operationId: operation.id }) as { planId: string } await approvePlan(queuedInverse.planId, actor, true) await rejectGenuinelyQueuedOldVersion(graphPrincipal, queuedInverse.planId, 1, "Queued old inverse revision cannot escape the runner version check") } if (fault === "post-peer-down" || fault === "post-template-order") { const verified = (await pool.query("SELECT count(*)::int AS n FROM mcp_operation_steps WHERE operation_id=$1 AND state='verified'", [operation.id])).rows[0].n assert.equal(verified, 0, "A peer or template-order change after ACK cannot leave a verified cost checkpoint") } if (fault === "none" || fault === "peer-vrf") { const inverse = await invokeMcpTool(graphPrincipal, "mm_operation_recovery_plan", { operationId: operation.id }) as { planId: string } const inverseEvidence = JSON.parse(await decryptSecret((await pool.query("SELECT evidence FROM mcp_plans WHERE id=$1", [inverse.planId])).rows[0].evidence.sealed)) assert.equal(inverseEvidence.steps[0].step.expectedCost, 10, "Inverse must compare the applied cost, not the original pre-write value") assert.equal(inverseEvidence.steps[0].step.fields.cost, costBaseline[0].cost) assert.deepEqual(inverseEvidence.ospfOwnerGraph.instances.map((row: { serverId: number }) => row.serverId), [1, 2, 103]) await approvePlan(inverse.planId, actor, true) if (fault === "peer-vrf") analysisPeerNeighbors[0].state = "Down" const beforeInverse: number = ospfAnalysisDispatches, inverseKey = randomUUID(), inverseOperation = await applyPlan(graphPrincipal, inverse.planId, inverseKey) assert.equal(await waitCost(inverseOperation.id), fault === "none" ? "applied" : "rejected") assert.equal(ospfAnalysisDispatches, beforeInverse + (fault === "none" ? 1 : 0)) assert.equal((await applyPlan(graphPrincipal, inverse.planId, inverseKey)).id, inverseOperation.id) if (fault === "none") assert.deepEqual(fixtureOspf["interface-template"], costBaseline) } thirdOwnerRows = [{ ".id": "*third", name: "third", "router-id": "192.0.2.103", disabled: "false", version: "2", vrf: "main" }] thirdOwnerIds = [] thirdOwnerNeighbors = []; thirdOwnerAddresses = [] analysisPeerInstances = structuredClone(graphPeerInstances); analysisPeerNeighbors = structuredClone(graphPeerNeighbors); analysisPeerAddresses = structuredClone(graphPeerAddresses) ospfAnalysisExtraNeighbors = []; fixtureOspf.instance = structuredClone(graphPrimaryInstances) await pool.query("UPDATE servers SET password='NEVER_OSPF_UNUSED_SECRET',enabled=true WHERE id=103") } const contextDispatches = ospfAnalysisDispatches Object.assign(thirdOwnerRows[0], { "router-id": "192.0.2.2", vrf: "cloned-context" }) thirdOwnerNeighbors = [{ ".id": "*tn", instance: "third", interface: "third", "router-id": "192.0.2.1", address: "192.0.2.1", state: "Full" }] thirdOwnerAddresses = [{ ".id": "*ta", interface: "third", address: "192.0.2.2/30", disabled: "false", invalid: "false" }] await assert.rejects(invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }), (error: { code?: string }) => error.code === "MEASUREMENT_UNKNOWN", "Two devices with identical reciprocal contexts remain ambiguous") thirdOwnerRows[0]["router-id"] = "192.0.2.103"; thirdOwnerNeighbors = []; thirdOwnerAddresses = [] analysisPeerInstances.push({ ...analysisPeerInstances[0], ".id": "*second", name: "second-vrf", vrf: "another-peer-vrf" }) analysisPeerNeighbors.push({ ...analysisPeerNeighbors[0], ".id": "*secondn", instance: "second-vrf", interface: "another-peer-interface" }) analysisPeerAddresses.push({ ...analysisPeerAddresses[0], ".id": "*seconda", interface: "another-peer-interface" }) await assert.rejects(invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }), (error: { code?: string }) => error.code === "MEASUREMENT_UNKNOWN", "Two matching VRFs on the same peer are not merged by device ID") analysisPeerInstances = structuredClone(graphPeerInstances); analysisPeerNeighbors = structuredClone(graphPeerNeighbors); analysisPeerAddresses = structuredClone(graphPeerAddresses) await pool.query("UPDATE mcp_routing_observations SET dst_interface='wrong-peer-interface' WHERE id=$1", [observationId]) await attestRoutingFixture(observationId) await assert.rejects(invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }), "Metric for another peer interface cannot be substituted") await pool.query("UPDATE mcp_routing_observations SET dst_interface='peer' WHERE id=$1", [observationId]); await attestRoutingFixture(observationId) assert.equal(ospfAnalysisDispatches, contextDispatches) const preparationReads = thirdOwnerReads thirdOwnerDuringRead = () => setConnectionStatus(graphAccess.connection.id, "paused", actor) await assert.rejects(invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 })) assert.equal(thirdOwnerReads, preparationReads + 1, "Pause during preparation stops before another native context read") await setConnectionStatus(graphAccess.connection.id, "active", actor) const cancelledReads = thirdOwnerReads, preparationCancel = new AbortController() thirdOwnerDuringRead = async () => preparationCancel.abort() await assert.rejects(invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }, preparationCancel.signal)) assert.equal(thirdOwnerReads, cancelledReads + 1, "Cancellation stops before another native context read") const changedReads = thirdOwnerReads thirdOwnerDuringRead = () => pool.query("UPDATE servers SET password='changed-during-preparation' WHERE id=103") await assert.rejects(invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 })) assert.equal(thirdOwnerReads, changedReads + 1, "Source drift during preparation stops before another native context read") await pool.query("UPDATE servers SET password='NEVER_OSPF_UNUSED_SECRET' WHERE id=103") fixtureOspf["interface-template"] = structuredClone(costBaseline) await pool.query("UPDATE servers SET enabled=false WHERE id=103") const disabledReads = [thirdOwnerReads, analysisPeerReads] await assert.rejects(invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 })) assert.deepEqual([thirdOwnerReads, analysisPeerReads], disabledReads, "Monitoring disabled cannot be guessed to mean an absent Router ID owner") await pool.query("UPDATE servers SET enabled=true WHERE id=103") fixtureOspf["interface-template"] = structuredClone(costBaseline) const graphLease = await pool.connect() try { await graphLease.query("SELECT pg_advisory_lock(hashtextextended('mm-config:103',0))") const prepared = await invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }) as { planId: string } await approvePlan(prepared.planId, actor, true) const dispatches = ospfAnalysisDispatches, key = randomUUID(), operation = await applyPlan(graphPrincipal, prepared.planId, key) assert.equal(await waitCost(operation.id), "rejected"); assert.equal(ospfAnalysisDispatches, dispatches, "Unselected owner participates in the actual multi-router lease") } finally { await graphLease.query("SELECT pg_advisory_unlock(hashtextextended('mm-config:103',0))"); graphLease.release() } fixtureOspf["interface-template"] = structuredClone(costBaseline) thirdOwnerRows = Array.from({ length: 10001 }, (_, n) => ({ ".id": "*third" + n, name: "third" + n, "router-id": "192.0.2.103", disabled: "false", version: "2", vrf: "main" })) await assert.rejects(invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 })) thirdOwnerRows = [{ ".id": "*third", name: "third", "router-id": "192.0.2.103", disabled: "false", version: "2", vrf: "main", opaque: "x".repeat(1048576) }] await assert.rejects(invokeMcpTool(graphPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 })) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_requests WHERE connection_id=$1 AND result::text LIKE '%NEVER_OSPF_UNUSED_SECRET%'", [graphAccess.connection.id])).rows[0].n, 0) console.log("✓ OSPF full owner graph: unselected third device sealed and locked, late duplicate/removal/credential/disable reject before PATCH, positive cost, scoped server budget before GET, private proofs and bounded graph") console.log("✓ OSPF VRF link context: reciprocal Full plus both address/interface bindings, different VRF names, isolated duplicate accepted, cloned contexts and wrong peer metric refused; late/post-write drift, counter tolerance, preparation pause/cancel/source fencing and no replay") } finally { ospfAfterCostPatch = undefined analysisPeerInstances = graphPeerInstances; analysisPeerNeighbors = graphPeerNeighbors; analysisPeerAddresses = graphPeerAddresses ospfAnalysisExtraNeighbors = []; fixtureOspf.instance = graphPrimaryInstances await setConnectionStatus(graphAccess.connection.id, "paused", actor) await pool.query("DELETE FROM servers WHERE id=103") await new Promise(resolve => thirdOwner.close(() => resolve())) fixtureOspf["interface-template"] = structuredClone(costBaseline) } try { const savedPassword = (await pool.query("SELECT password FROM servers WHERE id=1")).rows[0].password const credentialCandidate = await invokeMcpTool(writerPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }) as { planId: string } await approvePlan(credentialCandidate.planId, actor, true) await pool.query("UPDATE servers SET password='NEVER_OSPF_CHANGED_CREDENTIAL' WHERE id=1") const credentialDispatches = ospfAnalysisDispatches try { const op = await applyPlan(writerPrincipal, credentialCandidate.planId, randomUUID()) assert.equal(await waitCost(op.id), "rejected") assert.equal(ospfAnalysisDispatches, credentialDispatches, "Credential drift must stop before PATCH even if native configuration is identical") } finally { await pool.query("UPDATE servers SET password=$1 WHERE id=1", [savedPassword]) } await attestRoutingFixture(observationId) const oldVersionCandidate = await invokeMcpTool(writerPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }) as { planId: string } await approvePlan(oldVersionCandidate.planId, actor, true) await pool.query("UPDATE mcp_plans SET operation_version=6 WHERE id=$1", [oldVersionCandidate.planId]) await assert.rejects(applyPlan(writerPrincipal, oldVersionCandidate.planId, randomUUID()), (error: { code?: string }) => error.code === "PLAN_EXPIRED") assert.equal(ospfAnalysisDispatches, credentialDispatches) const queuedVersionCandidate = await invokeMcpTool(writerPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }) as { planId: string } await approvePlan(queuedVersionCandidate.planId, actor, true) await rejectGenuinelyQueuedOldVersion(writerPrincipal, queuedVersionCandidate.planId, 6, "Previously queued old revision must not escape the runner version check") assert.equal(ospfAnalysisDispatches, credentialDispatches) for (const fault of ["none", "ignored", "changed-field", "lost-response"]) { fixtureOspf["interface-template"] = structuredClone(costBaseline) Object.assign(fixtureOspf["interface-template"][0], { opaque: "NEVER_OSPF_OPAQUE" }) ospfCostWriteFault = fault const candidate = await invokeMcpTool(writerPrincipal, "mm_ospf_optimizer_plan", { serverId: 1 }) as { planId: string } await approvePlan(candidate.planId, actor, true) const key = randomUUID(), beforeDispatch: number = ospfAnalysisDispatches, op = await applyPlan(writerPrincipal, candidate.planId, key) assert.equal(await waitCost(op.id), fault === "none" ? "applied" : "unknown") assert.equal(ospfAnalysisDispatches, beforeDispatch + 1, "Cost must use exactly one PATCH and never replay it") assert.equal((await applyPlan(writerPrincipal, candidate.planId, key)).id, op.id) assert.equal(ospfAnalysisDispatches, beforeDispatch + 1) if (fault === "none") { assert.equal(fixtureOspf["interface-template"][0].cost, "10"); assert.equal((fixtureOspf["interface-template"][0] as Record).opaque, "NEVER_OSPF_OPAQUE") } } } finally { fixtureOspf["interface-template"] = costBaseline; ospfCostWriteFault = "none" } assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_requests WHERE connection_id=$1 AND result::text LIKE '%NEVER_OSPF_OPAQUE%'", [writer.connection.id])).rows[0].n, 0) console.log("✓ Shared OSPF cost writer over actual MCP/HTTP: sealed expected cost, opaque-field preservation, ignored/changed/lost result stop and idempotency without PATCH replay") } finally { ospfAnalysisFixtures = false await pool.query("UPDATE servers SET port=$1 WHERE id=2", [port]) await pool.query("DELETE FROM mcp_routing_observations WHERE id=ANY($1::uuid[])", [[observationId, badObservationId]]) await new Promise(resolve => analysisPeer.close(() => resolve())) } console.log("✓ Observer OSPF analysis shares the sealed planner ranking, dispatches no writes/plans, narrows peer scope and rejects stale/lossy observations by default") ecmpFixtures = true const ecmpBaseline = [0, 1].map(index => ({ ".id": `*ecmp${index}`, "dst-address": "192.0.2.0/24", "routing-table": "main", gateway: `192.0.2.${index + 2}`, "immediate-gw": `192.0.2.${index + 2}%fixture-gre`, distance: String((index + 1) * 10), active: index === 0 ? "true" : "false", comment: "MikrotikManager: integration", disabled: "false" })) fixtureEcmpRoutes = structuredClone(ecmpBaseline) const ecmpAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "ECMP transition test", mode: "approval", expiresAt: new Date(Date.now() + 1800000).toISOString(), permissions: ["mm:network:read", "mm:network:write"], scope: { serverIds: [1], operationIds: ["routing.ecmp.update", "routing.rpf.update", "ros.ip_settings.update", "operations.recover"], allowGlobal: false } }), actor) const ecmpPrincipal = await authenticateMcpToken(ecmpAccess.token) const ecmpArgs = { serverId: 1, family: "ipv4", routeIds: ["*ecmp0", "*ecmp1"], distance: 10 } const equalPlan = await invokeMcpTool(ecmpPrincipal, "mm_ecmp_plan", ecmpArgs) as { planId: string } await approvePlan(equalPlan.planId, actor, true) const equalOperation = await applyPlan(ecmpPrincipal, equalPlan.planId, randomUUID()) await awaitOperation(equalOperation.id, "applied"); assert.equal(ecmpWrites, 2) assert.ok(fixtureEcmpRoutes.every(row => row.active === "true" && row.distance === "10")) const unequalPlan = await invokeMcpTool(ecmpPrincipal, "mm_operation_recovery_plan", { operationId: equalOperation.id }) as { planId: string } await approvePlan(unequalPlan.planId, actor, true) await awaitOperation((await applyPlan(ecmpPrincipal, unequalPlan.planId, randomUUID())).id, "applied") assert.deepEqual(fixtureEcmpRoutes, ecmpBaseline) const staleEcmp = await invokeMcpTool(ecmpPrincipal, "mm_ecmp_plan", ecmpArgs) as { planId: string } await approvePlan(staleEcmp.planId, actor, true) fixtureEcmpRoutes[1]["immediate-gw"] = "" await awaitOperation((await applyPlan(ecmpPrincipal, staleEcmp.planId, randomUUID())).id, "rejected"); assert.equal(ecmpWrites, 4) fixtureEcmpRoutes = structuredClone(ecmpBaseline) const brokenEcmp = await invokeMcpTool(ecmpPrincipal, "mm_ecmp_plan", ecmpArgs) as { planId: string } await approvePlan(brokenEcmp.planId, actor, true) loseEcmpGateway = true const brokenOperation = await applyPlan(ecmpPrincipal, brokenEcmp.planId, randomUUID()) await awaitOperation(brokenOperation.id, "partial"); assert.equal(ecmpWrites, 6) await applyPlan(ecmpPrincipal, brokenEcmp.planId, randomUUID()); await delay(30); assert.equal(ecmpWrites, 6) loseEcmpGateway = false; fixtureEcmpRoutes = structuredClone(ecmpBaseline) const tableProtection = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,'integration')", [tableProtection, JSON.stringify({ kind: "routing_table", id: "*main", serverId: 1, protection: "deny" })]) await assert.rejects(invokeMcpTool(ecmpPrincipal, "mm_ecmp_plan", ecmpArgs)) await pool.query("DELETE FROM mcp_protections WHERE id=$1", [tableProtection]) const settingsProtection = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,'integration')", [settingsProtection, JSON.stringify({ kind: "ip_settings", id: "singleton", serverId: 1, protection: "deny" })]) await assert.rejects(invokeMcpTool(ecmpPrincipal, "mm_ecmp_plan", { ...ecmpArgs, hashPolicy: "l4" })) await assert.rejects(invokeMcpTool(ecmpPrincipal, "mm_rpf_plan", { serverId: 1, mode: "loose", acknowledgeAsymmetry: true })) await assert.rejects(invokeMcpTool(ecmpPrincipal, "mm_ip_settings_update_plan", { serverId: 1, fields: { "rp-filter": "loose" } })) await pool.query("DELETE FROM mcp_protections WHERE id=$1", [settingsProtection]) ecmpFixtures = false console.log("✓ ECMP: resolved inactive candidate, equal-active transition, inverse to unequal distances, lost resolution before/after write and table protection") const plan = await invokeMcpTool(principal, "mm_ip_settings_update_plan", { serverId: 1, fields: { "rp-filter": "loose" } }) as { planId: string; state: string } assert.equal(plan.state, "awaiting-approval") await assert.rejects(applyPlan(principal, plan.planId, "test-key")) await approvePlan(plan.planId, actor, true) const operation = await applyPlan(principal, plan.planId, "test-key") assert.equal((await applyPlan(principal, plan.planId, "test-key")).id, operation.id) let state = "pending" for (let index = 0; index < 100; index++) { const row = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [operation.id])).rows[0] state = row.state if (!["pending", "running"].includes(state)) break await delay(30) } assert.equal(state, "applied"); assert.equal(writes, 1); assert.equal(settings["rp-filter"], "loose") assert.equal((await pool.query("SELECT state FROM mcp_operation_steps WHERE operation_id=$1", [operation.id])).rows[0].state, "verified") console.log("✓ Human approval, write-ahead intent, single idempotent write and read-back verification") const stale = await invokeMcpTool(principal, "mm_ip_settings_update_plan", { serverId: 1, fields: { "rp-filter": "strict" } }) as { planId: string } await approvePlan(stale.planId, actor, true) settings["rp-filter"] = "no" const conflict = await applyPlan(principal, stale.planId, "conflict-key") for (let index = 0; index < 100; index++) { state = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [conflict.id])).rows[0].state if (!["pending", "running"].includes(state)) break await delay(30) } assert.equal(state, "rejected"); assert.equal(writes, 1) console.log("✓ Changed snapshots reject execution before contacting the write endpoint") const expiring = await invokeMcpTool(principal, "mm_ip_settings_update_plan", { serverId: 1, fields: { "rp-filter": "loose" } }) as { planId: string } const sealedEvidence = (await pool.query("SELECT evidence FROM mcp_plans WHERE id=$1", [expiring.planId])).rows[0].evidence const expiringEvidence = JSON.parse(await decryptSecret(sealedEvidence.sealed)) expiringEvidence.validUntil = new Date(Date.now() - 1000).toISOString() await pool.query("UPDATE mcp_plans SET evidence=$2::jsonb WHERE id=$1", [expiring.planId, JSON.stringify({ sealed: await encryptSecret(JSON.stringify(expiringEvidence)) })]) await approvePlan(expiring.planId, actor, true) const mutationBudgetBeforeExpiry = (await pool.query("SELECT kind,bucket,used FROM mcp_budget_buckets WHERE connection_id=$1 AND kind='mutations' ORDER BY bucket", [principal.connectionId])).rows await assert.rejects(applyPlan(principal, expiring.planId, "expired-measurement"), (error: { code?: string }) => error.code === "MEASUREMENT_UNKNOWN") assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_operations WHERE plan_id=$1", [expiring.planId])).rows[0].n, 0) assert.deepEqual((await pool.query("SELECT kind,bucket,used FROM mcp_budget_buckets WHERE connection_id=$1 AND kind='mutations' ORDER BY bucket", [principal.connectionId])).rows, mutationBudgetBeforeExpiry) assert.equal(writes, 1) console.log("✓ Expired measurement evidence refuses admission with its own reason before operation, mutation debit or native write") const { routerClient } = await import("./resources.js") const nativeClient = (await routerClient(1)).client, protectionId = randomUUID(), writesBeforeProtection = writes const { updateServer } = await import("../servers/service/servers-service.js") const nameBeforeGuard = (await pool.query("SELECT name FROM servers WHERE id=1")).rows[0].name await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,'integration')", [protectionId, JSON.stringify({ kind: "server", id: "1", serverId: 1, protection: "deny" })]) try { await assert.rejects(nativeClient.patch("/ip/settings", { "rp-filter": "strict" }), /защитой/) await assert.rejects(updateServer(1, { name: "must-not-change" })) assert.equal((await pool.query("SELECT name FROM servers WHERE id=1")).rows[0].name, nameBeforeGuard) assert.equal(writes, writesBeforeProtection) } finally { await pool.query("DELETE FROM mcp_protections WHERE id=$1", [protectionId]) } const auditCount = (await pool.query("SELECT count(*)::int AS n FROM mcp_audit WHERE action='configuration.intent'")).rows[0].n assert.ok(auditCount > 0) const originalQuery = pool.query.bind(pool) // Storage outage must be observed before any native network request is made. pool.query = ((...args: unknown[]) => { if (typeof args[0] === "string" && args[0].startsWith("INSERT INTO mcp_audit")) return Promise.reject(new Error("test audit outage")) return (originalQuery as (...args: unknown[]) => unknown)(...args) }) as typeof pool.query try { await assert.rejects(nativeClient.patch("/ip/settings", { "rp-filter": "strict" }), /audit outage/); await assert.rejects(updateServer(1, { name: "must-not-change" }), /audit outage/); assert.equal((await pool.query("SELECT name FROM servers WHERE id=1")).rows[0].name, nameBeforeGuard); assert.equal(writes, writesBeforeProtection) } finally { pool.query = originalQuery as typeof pool.query } console.log("✓ Legacy native writes cannot bypass server protection or unavailable write-ahead audit") const { withConfigMutationLock } = await import("../../services/config-mutation-lock.js") let wake!: () => void, late!: Promise, lateWrites = 0 const wait = new Promise(resolve => { wake = resolve }) await withConfigMutationLock([1], async () => { late = wait.then(() => withConfigMutationLock([1], async () => { lateWrites++ })) }) const competingLease = await pool.connect() try { await competingLease.query("SELECT pg_advisory_lock(hashtextextended('mm-config:1',0))") wake(); await assert.rejects(late, /другой операцией/); assert.equal(lateWrites, 0) } finally { await competingLease.query("SELECT pg_advisory_unlock(hashtextextended('mm-config:1',0))"); competingLease.release() } console.log("✓ Detached children cannot reuse a released configuration lease") const rotated = await rotateConnectionToken(issued.connection.id, actor) await assert.rejects(authenticateMcpToken(issued.token)) await authenticateMcpToken(rotated.token) const { buildApp } = await import("../../index.js") const reserve = createServer() await new Promise(resolve => reserve.listen(0, "127.0.0.1", resolve)) const appPort = (reserve.address() as { port: number }).port await new Promise(resolve => reserve.close(() => resolve())) const endpoint = `http://localhost:${appPort}/mcp` process.env.MCP_PUBLIC_URL = endpoint app = await buildApp({ startScheduler: false }) await app.listen({ port: appPort, host: "127.0.0.1" }) await (await import("../../test/mcp-backup-download.js")).verifyMcpBackupDownload(actor,app) await (await import("../../test/mcp-database-backup.js")).verifyMcpDatabaseBackup(actor,app) const fixtureScheduler = await import("../../services/scheduler-running.js") const fixtureSchedulerWasPaused = fixtureScheduler.isSchedulerJobsPaused() await fixtureScheduler.pauseSchedulerJobsAndWait() try { await (await import("./control-path-context-acceptance.js")).verifyMcpControlPathContext(actor) await (await import("./routing-community-scoped-acceptance.js")).acceptScopedRouteCommunitySwitch(actor) await (await import("../../test/mcp-terminal-diagnostics.js")).verifyMcpTerminalDiagnostics(actor, app) await (await import("../../test/mcp-events-batch.js")).verifyMcpEventsBatch(actor, app) await (await import("./wireguard-interface-update-acceptance.js")).acceptWireguardInterfaceUpdate(actor) await (await import("./wireguard-import-acceptance.js")).acceptWireguardImport(actor) await (await import("./wireguard-export-acceptance.js")).acceptWireguardExportMcp(actor, app) await (await import("./protection-scope-acceptance.js")).acceptProtectionRegistrationScope(actor) console.log("Routing controller CAS SQL:", await (await import("./routing-controller-cas-acceptance.js")).acceptRoutingControllerCas()) await (await import("../../test/mcp-server-connection.js")).verifyMcpServerConnection(actor) await (await import("./ipsec-peer-delete-acceptance.js")).acceptIpsecPeerDeletion(actor) await (await import("./ipsec-user-delete-acceptance.js")).acceptIpsecUserDeletion(actor) await (await import("./ipsec-certificate-delete-acceptance.js")).acceptIpsecCertificateDeletion(actor) await (await import("./ipsec-peer-update-acceptance.js")).acceptIpsecPeerUpdate(actor) await (await import("./management-transport-acceptance.js")).acceptManagementTransport(actor) await (await import("../../test/mcp-flow-purge.js")).verifyMcpFlowPurge(actor) await (await import("./routing-community-context-compatibility-acceptance.js")).acceptRouteCommunityContextCompatibilityMcp(actor) } finally { if (!fixtureSchedulerWasPaused) fixtureScheduler.resumeSchedulerJobs() } const client = new Client({ name: "integration", version: "1.0.0" }) try { await client.connect(new StreamableHTTPClientTransport(new URL(endpoint), { requestInit: { headers: { Authorization: `Bearer ${rotated.token}` } } })) const listed = await client.listTools() assert.ok(listed.tools.some(tool => tool.name === "mm_ip_settings_list")) assert.ok(!listed.tools.some(tool => tool.name === "mm_servers_delete_plan")) const result = await client.callTool({ name: "mm_ip_settings_list", arguments: { serverId: 1 } }) assert.ok(!result.isError) console.log("✓ Official SDK HTTP initialize, filtered tools/list and tools/call") } finally { await client.close() } { const access = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Logs follow maintenance", permissions: ["mm:servers:read"], scope: { serverIds: [1], operationIds: ["logs.follow"], allowGlobal: false }, }), actor) const follower = new Client({ name: "logs-follow-maintenance", version: "1.0.0" }) const { sharedDatabaseGate } = await import("../../services/shared-database-gate.js") const original = pool.query let arrived!: () => void, release!: () => void const reading = new Promise(resolve => { arrived = resolve }) const held = new Promise(resolve => { release = resolve }) try { await follower.connect(new StreamableHTTPClientTransport(new URL(endpoint), { requestInit: { headers: { Authorization: `Bearer ${access.token}` } } })) pool.query = (async (...args: Parameters) => { const text = typeof args[0] === "string" ? args[0] : args[0]?.text const result = await Reflect.apply(original, pool, args) if (typeof text === "string" && text.startsWith("SELECT GREATEST(COALESCE(max(id),0)")) { arrived() await held } return result }) as typeof original const following = follower.callTool({ name: "mm_logs_follow", arguments: { serverId: 1, waitSeconds: 30 } }) try { const observation = new AbortController() try { await Promise.race([reading, following.then(() => { throw new Error("Follow finished before its stream query") }), delay(5000, undefined, { signal: observation.signal }).then(() => { throw new Error("Follow stream query was not observed") })]) } finally { observation.abort() } let exclusiveEntered = false const maintenance = sharedDatabaseGate.exclusive(async () => { exclusiveEntered = true }, 5000) try { await delay(25) assert.equal(exclusiveEntered, false, "maintenance must wait for the pending database read") } finally { release() await maintenance } const result = await following assert.equal(result.isError, true) assert.match(JSON.stringify(result), /RESET_REQUIRED/) assert.doesNotMatch(JSON.stringify(result), /"cursor"/) assert.equal((await pool.query("SELECT count(*)::int AS count FROM mcp_read_leases WHERE connection_id=$1", [access.connection.id])).rows[0].count, 0) } finally { release(); await following.catch(() => undefined) } } finally { pool.query = original; await follower.close() } } const stdio = new Client({ name: "stdio-integration", version: "1.0.0" }) try { await stdio.connect(new StdioClientTransport({ command: process.execPath, args: ["--import", "tsx", fileURLToPath(new URL("../../scripts/mcp-stdio.ts", import.meta.url))], env: { MCP_URL: endpoint, MCP_TOKEN: rotated.token }, stderr: "pipe" })) assert.ok((await stdio.listTools()).tools.some(tool => tool.name === "mm_ip_settings_list")) assert.ok(!(await stdio.callTool({ name: "mm_ip_settings_list", arguments: { serverId: 1 } })).isError) console.log("✓ Official SDK stdio bridge initializes and calls the same authorized HTTP tools") } finally { await stdio.close() } const clientId = randomUUID(), redirectUri = "http://localhost:45678/callback", verifier = "v".repeat(64) await pool.query("INSERT INTO mcp_oauth_clients(id,name,redirect_uris) VALUES($1,'integration',$2::jsonb)", [clientId, JSON.stringify([redirectUri])]) const parameters = { response_type: "code", client_id: clientId, redirect_uri: redirectUri, resource: endpoint, scope: "mcp", state: "test-state", code_challenge_method: "S256", code_challenge: createHash("sha256").update(verifier).digest("base64url") } const authorization = await app.inject({ url: "/oauth/authorize?" + new URLSearchParams(parameters) }) assert.equal(authorization.statusCode, 302) const requestId = new URL(authorization.headers.location!).searchParams.get("oauthRequest")! const { SignJWT } = await import("jose"), { env } = await import("../../config.js") const jwt = await new SignJWT({ sub: actor.id, apps: ["mm"], permissions: actor.permissions }).setProtectedHeader({ alg: "HS256" }).setIssuer(env.authIssuer).setExpirationTime(Math.floor(fixtureAuthExpiresAt / 1000)).sign(new TextEncoder().encode(env.jwtSecret)) await (await import("../../test/mcp-host-files.js")).verifyMcpHostFiles(actor,app) const readinessAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Actual readiness handshake", scope: { ...input.scope, operationIds: ["servers.list"] } }), actor) const readinessHeaders = { authorization: "Bearer " + jwt } const ready = await app.inject({ method: "POST", url: `/api/mcp/connections/${readinessAccess.connection.id}/check`, headers: readinessHeaders, payload: {} }) assert.equal(ready.statusCode, 200, ready.body) assert.equal(ready.json().ok, true, ready.body); assert.equal(ready.json().checks.protocolHandshake, true) assert.equal(ready.json().protocolProbe.status, "passed"); assert.equal(ready.json().protocolProbe.clientCredentialsVerified, false) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_credentials WHERE connection_id=$1 AND client_id='mcp-readiness-probe'", [readinessAccess.connection.id])).rows[0].n, 0) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_grants WHERE connection_id=$1", [readinessAccess.connection.id])).rows[0].n, 0) const duplicateReady = await app.inject({ method: "POST", url: `/api/mcp/connections/${readinessAccess.connection.id}/check`, headers: readinessHeaders, payload: {} }) assert.equal(duplicateReady.statusCode, 429) await setConnectionStatus(readinessAccess.connection.id, "paused", actor) const pausedReady = await app.inject({ method: "POST", url: `/api/mcp/connections/${readinessAccess.connection.id}/check`, headers: readinessHeaders, payload: {} }) assert.equal(pausedReady.statusCode, 200); assert.equal(pausedReady.json().protocolProbe.status, "skipped") const emptyGrantAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Readiness has no operation authority" }), actor) const { withMcpProbeCredential } = await import("./readiness.js") let endedProbeToken = "" await assert.rejects(withMcpProbeCredential(emptyGrantAccess.connection.id, actor, async token => { endedProbeToken = token const probePrincipal = await authenticateMcpToken(token) const { effectiveMcpConnection } = await import("./store.js") assert.deepEqual((await effectiveMcpConnection(probePrincipal)).policy.scope.operationIds, []) await assert.rejects(invokeMcpTool(probePrincipal, "mm_ip_settings_list", { serverId: 1 })) throw new Error("Simulated interrupted handshake") })) await assert.rejects(authenticateMcpToken(endedProbeToken)) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_grants WHERE connection_id=$1", [emptyGrantAccess.connection.id])).rows[0].n, 0) const readinessAudit = (await pool.query("SELECT payload FROM mcp_audit WHERE connection_id=$1", [emptyGrantAccess.connection.id])).rows assert.ok(!JSON.stringify(readinessAudit).includes(endedProbeToken)) console.log("✓ Actual readiness initialize/tools-list over public HTTP: temporary empty grant, no tool authority, cleanup on success/error, cooldown and paused precheck skip") const deviceConnection = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Web interfaces", scope: { ...input.scope, operationIds: ["ros.bridge.list", "ros.bridge.create"] } }), actor) const deviceHeaders = { authorization: "Bearer " + jwt } const deviceCatalog = await app.inject({ url: "/api/mcp/devices/catalog?connectionId=" + deviceConnection.connection.id, headers: deviceHeaders }) assert.equal(deviceCatalog.statusCode, 200, deviceCatalog.body) assert.deepEqual(deviceCatalog.json().servers.map((server: { id: number }) => server.id), [1]) assert.equal(deviceCatalog.json().resources.find((resource: { id: string }) => resource.id === "bridge").create, true) assert.equal((await app.inject({ url: `/api/mcp/devices/2/bridge?connectionId=${deviceConnection.connection.id}`, headers: deviceHeaders })).statusCode, 403) const devicePlan = await app.inject({ method: "POST", url: "/api/mcp/devices/1/bridge/plan", headers: deviceHeaders, payload: { connectionId: deviceConnection.connection.id, action: "create", fields: { name: "own-web-test", disabled: true } } }) assert.equal(devicePlan.statusCode, 200, devicePlan.body); assert.equal(devicePlan.json().state, "awaiting-approval") const invalidDevice = await app.inject({ method: "POST", url: "/api/mcp/devices/1/bridge/plan", headers: deviceHeaders, payload: { connectionId: deviceConnection.connection.id, action: "create", fields: { name: "own-web-test", script: "forbidden" } } }) assert.equal(invalidDevice.statusCode, 400, invalidDevice.body) console.log("✓ Web interface catalog enforces server scope; typed creation requires approval and rejects arbitrary fields") const consent = await app.inject({ method: "POST", url: "/api/mcp/oauth/consent", headers: { authorization: "Bearer " + jwt }, payload: { requestId, connectionId: issued.connection.id, approve: true } }) assert.equal(consent.statusCode, 200, consent.body) const redirect = new URL(consent.json().redirectUrl) assert.equal(redirect.searchParams.get("state"), "test-state") const codePayload = { grant_type: "authorization_code", client_id: clientId, redirect_uri: redirectUri, resource: endpoint, code: redirect.searchParams.get("code")!, code_verifier: verifier } const tokenRequest = (payload: Record) => app!.inject({ method: "POST", url: "/oauth/token", headers: { "content-type": "application/x-www-form-urlencoded" }, payload: new URLSearchParams(payload).toString() }) assert.equal((await tokenRequest({ ...codePayload, code_verifier: "x".repeat(64) })).statusCode, 400) assert.equal((await tokenRequest({ ...codePayload, resource: "https://wrong.invalid/mcp" })).statusCode, 400) const exchanged = await tokenRequest(codePayload) assert.equal(exchanged.statusCode, 200, exchanged.body) await authenticateMcpToken(exchanged.json().access_token) assert.equal((await tokenRequest(codePayload)).statusCode, 400) const refreshPayload = { grant_type: "refresh_token", client_id: clientId, resource: endpoint, refresh_token: exchanged.json().refresh_token } const refreshed = await tokenRequest(refreshPayload) assert.equal(refreshed.statusCode, 200) assert.equal((await tokenRequest(refreshPayload)).statusCode, 400) await assert.rejects(authenticateMcpToken(refreshed.json().access_token)) console.log("✓ OAuth consent, PKCE, resource binding, one-use code, refresh rotation and reuse-family revocation") const { detectMcpDrift } = await import("./workers.js") await detectMcpDrift() assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_drift WHERE operation_id=$1", [operation.id])).rows[0].n, 1) await detectMcpDrift() assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_drift WHERE operation_id=$1", [operation.id])).rows[0].n, 1) console.log("✓ Drift detection records a single open incident without replaying writes") const currentPrincipal = await authenticateMcpToken(rotated.token) await assert.rejects(invokeMcpTool(currentPrincipal, "mm_operation_recovery_plan", { operationId: operation.id }), error => error instanceof Error && "code" in error && error.code === "STATE_CONFLICT") const beforeRecovery = settings["rp-filter"] const forward = await invokeMcpTool(currentPrincipal, "mm_ip_settings_update_plan", { serverId: 1, fields: { "rp-filter": "loose" } }) as { planId: string } await approvePlan(forward.planId, actor, true) const forwardOperation = await applyPlan(currentPrincipal, forward.planId, "recovery-forward") const waitOperation = async (id: string) => { // This observes asynchronous execution; it does not set an operation TTL. // Genuine settings/drain execution can exceed the former 100 x 30ms loop. const observationDeadline = performance.now() + 30000 while (performance.now() < observationDeadline) { const value = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [id])).rows[0].state if (!["pending", "running"].includes(value)) return value await delay(30) } return "timeout" } assert.equal(await waitOperation(forwardOperation.id), "applied") await pool.query("INSERT INTO servers(id,name,host,port,use_ssl,password) VALUES(107,'native-reconciliation','127.0.0.1',$1,false,'fixture')", [port]) const reconcileAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Native reconciliation fences", scope: { serverIds: [107], operationIds: ["ros.ip_settings.update", "operations.reconcile"], allowGlobal: false } }), actor) const reconcilePrincipal = await authenticateMcpToken(reconcileAccess.token) try { const reconcilePlan = await invokeMcpTool(reconcilePrincipal, "mm_ip_settings_update_plan", { serverId: 107, fields: { "rp-filter": "loose" } }) as { planId: string } await approvePlan(reconcilePlan.planId, actor, true) const reconcileOperation = await applyPlan(reconcilePrincipal, reconcilePlan.planId, randomUUID()) assert.equal(await waitOperation(reconcileOperation.id), "applied") const reconcileWrites = writes const nativeReconcile = await invokeMcpTool(reconcilePrincipal, "mm_operation_reconcile", { operationId: reconcileOperation.id }) as { state: string; automaticRetry: boolean; results: { matchesVerifiedCheckpoint: boolean; comparisonScope: string; sourceUnchangedDuringRead: boolean; originalSourceIdentityVerified: boolean }[] } assert.equal(nativeReconcile.state, "applied"); assert.equal(nativeReconcile.automaticRetry, false) assert.equal(nativeReconcile.results[0].matchesVerifiedCheckpoint, true) assert.equal(nativeReconcile.results[0].comparisonScope, "visible-resource-configuration") assert.equal(nativeReconcile.results[0].sourceUnchangedDuringRead, true) assert.equal(nativeReconcile.results[0].originalSourceIdentityVerified, false) const reconcileAbort = new AbortController(), beforeReconcileCancel = reconcileSettingsReads reconcileReadHook = async () => reconcileAbort.abort() await assert.rejects(invokeMcpTool(reconcilePrincipal, "mm_operation_reconcile", { operationId: reconcileOperation.id }, reconcileAbort.signal), (error: { code?: string }) => error.code === "READ_CANCELLED") assert.equal(reconcileSettingsReads, beforeReconcileCancel + 1) reconcileReadHook = () => setConnectionStatus(reconcileAccess.connection.id, "paused", actor) await assert.rejects(invokeMcpTool(reconcilePrincipal, "mm_operation_reconcile", { operationId: reconcileOperation.id })) await setConnectionStatus(reconcileAccess.connection.id, "active", actor) reconcileReadHook = async () => { await pool.query("UPDATE servers SET verify_ssl=NOT verify_ssl WHERE id=107") await pool.query("UPDATE servers SET verify_ssl=NOT verify_ssl WHERE id=107") } await assert.rejects(invokeMcpTool(reconcilePrincipal, "mm_operation_reconcile", { operationId: reconcileOperation.id }), (error: { code?: string }) => error.code === "STATE_CONFLICT") assert.equal(writes, reconcileWrites) assert.equal((await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [reconcileOperation.id])).rows[0].state, "applied") console.log("✓ Native reconciliation fences cancellation, paused access and endpoint ABA during HTTP without replay") } finally { reconcileReadHook = undefined await setConnectionStatus(reconcileAccess.connection.id, "paused", actor) await pool.query("DELETE FROM servers WHERE id=107") } const recovery = await invokeMcpTool(currentPrincipal, "mm_operation_recovery_plan", { operationId: forwardOperation.id }) as { planId: string; state: string } assert.equal(recovery.state, "awaiting-approval") await approvePlan(recovery.planId, actor, true) const restored = await applyPlan(currentPrincipal, recovery.planId, "recovery-once") assert.equal(await waitOperation(restored.id), "applied"); assert.equal(settings["rp-filter"], beforeRecovery) assert.equal((await applyPlan(currentPrincipal, recovery.planId, "recovery-once")).id, restored.id) console.log("✓ Encrypted checkpoints support approved inverse plans, reject external drift and preserve idempotency") const usersAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Users writer", permissions: ["mm:users:write"], scope: { serverIds: [1], operationIds: ["users.create", "users.update", "users.permissions.update"], allowGlobal: true } }), actor) const usersPrincipal = await authenticateMcpToken(usersAccess.token) const userData = { name: " MCP catalog fixture ", login: "mcp-" + randomUUID(), email: "fixture@example.invalid" } await assert.rejects(invokeMcpTool(usersPrincipal, "mm_users_create_plan", { user: { ...userData, role: "admin" } })) const userPlan = await invokeMcpTool(usersPrincipal, "mm_users_create_plan", { user: userData }) as { planId: string } await approvePlan(userPlan.planId, actor, true) assert.equal(await waitOperation((await applyPlan(usersPrincipal, userPlan.planId, "create-catalog-user")).id), "applied") const catalogUser = (await pool.query("SELECT id,role FROM app_users WHERE login=$1", [userData.login])).rows[0] assert.equal(catalogUser.role, "viewer") await assert.rejects(invokeMcpTool(usersPrincipal, "mm_users_permissions_update_plan", { id: catalogUser.id, permissions: { role: "admin" } })) const rolesAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Catalog roles", permissions: ["mm:settings:admin"], scope: { serverIds: [1], operationIds: ["users.permissions.update"], allowGlobal: true } }), actor) const rolesPrincipal = await authenticateMcpToken(rolesAccess.token) await assert.rejects(invokeMcpTool(rolesPrincipal, "mm_users_permissions_update_plan", { id: catalogUser.id, permissions: { servers: [{ serverId: "2", level: "read" }] } })) const rolesPlan = await invokeMcpTool(rolesPrincipal, "mm_users_permissions_update_plan", { id: catalogUser.id, permissions: { role: "operator", servers: [{ serverId: "1", level: "read" }] } }) as { planId: string } await approvePlan(rolesPlan.planId, actor, true) assert.equal(await waitOperation((await applyPlan(rolesPrincipal, rolesPlan.planId, "catalog-role-only")).id), "applied") assert.equal((await pool.query("SELECT role FROM app_users WHERE id=$1", [catalogUser.id])).rows[0].role, "operator") const nameOnly = await invokeMcpTool(usersPrincipal, "mm_users_update_plan", { id: catalogUser.id, user: { name: "Renamed only" } }) as { planId: string } await approvePlan(nameOnly.planId, actor, true) assert.equal(await waitOperation((await applyPlan(usersPrincipal, nameOnly.planId, "name-without-privileges")).id), "applied") const preservedUser = (await pool.query("SELECT role,servers_json FROM app_users WHERE id=$1", [catalogUser.id])).rows[0] assert.equal(preservedUser.role, "operator") assert.deepEqual(typeof preservedUser.servers_json === "string" ? JSON.parse(preservedUser.servers_json) : preservedUser.servers_json, [{ serverId: "1", level: "read" }]) for (const type of ["bridge", "vlan", "vxlan", "ipsec"]) await pool.query("INSERT INTO user_interface_bindings(id,user_id,server_id,interface_name,interface_type) VALUES($1,$2,1,$3,$4)", [randomUUID(), catalogUser.id, "fixture-" + type, type]) assert.equal((await pool.query("SELECT count(*)::int AS n FROM user_interface_bindings WHERE user_id=$1", [catalogUser.id])).rows[0].n, 4) console.log("✓ Catalog roles require a separate admin action; scoped permissions, normalized read-back and real interface types work") await verifyMcpGrantExpiryBounds(app, actor, deviceHeaders) const grantOperations = ["users.list", "users.get", "servers.list", "ros.ip_settings.update", "changes.read", "changes.cancel", "operations.read"] const grantAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Temporary scope acceptance", permissions: ["mm:users:read", "mm:network:write", "mm:servers:read"], scope: { allowGlobal: true, serverIds: [1, 2], operationIds: [...grantOperations, "interfaces.update"] } }), actor) const baseGrantPrincipal = await authenticateMcpToken(grantAccess.token) const outsidePlan = await invokeMcpTool(baseGrantPrincipal, "mm_ip_settings_update_plan", { serverId: 2, fields: { "rp-filter": settings["rp-filter"] } }) as { planId: string } await approvePlan(outsidePlan.planId, actor, true) const outsideOperation = await applyPlan(baseGrantPrincipal, outsidePlan.planId, "outside-temporary-grant") assert.equal(await waitOperation(outsideOperation.id), "applied") const cancelOutside = await invokeMcpTool(baseGrantPrincipal, "mm_ip_settings_update_plan", { serverId: 2, fields: { "rp-filter": settings["rp-filter"] } }) as { planId: string } const issuedGrant = await app.inject({ method: "POST", url: `/api/mcp/connections/${grantAccess.connection.id}/grants`, headers: deviceHeaders, payload: { id: randomUUID(), startsAt: new Date(Date.now() - 1000).toISOString(), expiresAt: new Date(Date.now() + 600000).toISOString(), scope: { allowGlobal: true, serverIds: [1], operationIds: grantOperations } } }) assert.equal(issuedGrant.statusCode, 200, issuedGrant.body) const grantPrincipal = await authenticateMcpToken(issuedGrant.json().token) const hiddenBinding = randomUUID() await pool.query("INSERT INTO user_interface_bindings(id,user_id,server_id,interface_name,interface_type) VALUES($1,$2,2,'hidden-binding','ether')", [hiddenBinding, catalogUser.id]) const grantUser = await invokeMcpTool(grantPrincipal, "mm_users_get", { id: catalogUser.id }) as { user: { incomplete: boolean; bindings: { serverId: number }[] } } assert.equal(grantUser.user.incomplete, true) assert.ok(grantUser.user.bindings.every(binding => binding.serverId === 1)) const grantList = await invokeMcpTool(grantPrincipal, "mm_users_list", {}) as { items: { bindings: { serverId: number }[] }[] } assert.ok(grantList.items.every(user => user.bindings.every(binding => binding.serverId === 1))) assert.deepEqual((await invokeMcpTool(grantPrincipal, "mm_servers_list", {}) as { items: { id: number }[] }).items.map(item => item.id), [1]) for (const [tool, body] of [["mm_change_get", { planId: outsidePlan.planId }], ["mm_change_cancel", { planId: cancelOutside.planId }], ["mm_operation_get", { operationId: outsideOperation.id }]] as const) await assert.rejects(invokeMcpTool(grantPrincipal, tool, body)) assert.equal((await pool.query("SELECT state FROM mcp_plans WHERE id=$1", [cancelOutside.planId])).rows[0].state, "awaiting-approval") const insidePlan = await invokeMcpTool(grantPrincipal, "mm_ip_settings_update_plan", { serverId: 1, fields: { "rp-filter": settings["rp-filter"] } }) as { planId: string } assert.equal((await invokeMcpTool(grantPrincipal, "mm_change_get", { planId: insidePlan.planId }) as { id: string }).id, insidePlan.planId) const grantClient = new Client({ name: "temporary-grant", version: "1.0.0" }) try { await grantClient.connect(new StreamableHTTPClientTransport(new URL(endpoint), { requestInit: { headers: { Authorization: "Bearer " + issuedGrant.json().token } } })) const tools = (await grantClient.listTools()).tools.map(tool => tool.name) assert.ok(tools.includes("mm_users_get")); assert.ok(!tools.includes("mm_interfaces_update_plan")) } finally { await grantClient.close() } await pool.query("DELETE FROM user_interface_bindings WHERE id=$1", [hiddenBinding]) console.log("✓ Temporary grants narrow catalogs and SDK discovery; hidden user bindings and out-of-scope plan/operation details or cancellation are denied") const healthAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Post-write health", scope: { ...input.scope, operationIds: ["ros.ip_settings.update"] } }), actor) const healthPrincipal = await authenticateMcpToken(healthAccess.token) const healthPlan = async (resource: string, id: string, field: string, identity?: Record) => { const plan = await invokeMcpTool(healthPrincipal, "mm_ip_settings_update_plan", { serverId: 1, fields: { "rp-filter": settings["rp-filter"] } }) as { planId: string } const evidence = JSON.parse(await decryptSecret((await pool.query("SELECT evidence FROM mcp_plans WHERE id=$1", [plan.planId])).rows[0].evidence.sealed)) evidence.healthChecks = [{ serverId: 1, resource, id, field, identity }] await pool.query("UPDATE mcp_plans SET evidence=$2::jsonb WHERE id=$1", [plan.planId, JSON.stringify({ sealed: await encryptSecret(JSON.stringify(evidence)) })]) await approvePlan(plan.planId, actor, true) return plan } try { for (const [resource, id, field] of [["ospf_neighbor", "*neighbor", "state"], ["ipv4_route", "*healthroute", "active"], ["bfd_session", "*bfd", "bfd-up"]]) { healthState = true; failHealthAfterWrite = true const plan = await healthPlan(resource, id, field), before: number = writes const operation = await applyPlan(healthPrincipal, plan.planId, randomUUID()) assert.equal(await waitOperation(operation.id), "partial"); assert.equal(writes, before + 1) assert.equal((await pool.query("SELECT error_code FROM mcp_operations WHERE id=$1", [operation.id])).rows[0].error_code, "VERIFICATION_FAILED") await applyPlan(healthPrincipal, plan.planId, "another-key"); await delay(30); assert.equal(writes, before + 1) } failHealthAfterWrite = false; healthState = false const preflight = await healthPlan("ospf_neighbor", "*neighbor", "state"), before = writes assert.equal(await waitOperation((await applyPlan(healthPrincipal, preflight.planId, randomUUID())).id), "rejected"); assert.equal(writes, before) healthState = true const reusedBfd = await healthPlan("bfd_session", "*bfd", "bfd-up", { "remote-address": healthBfdRemote }) healthBfdRemote = "192.0.2.99%foreign" assert.equal(await waitOperation((await applyPlan(healthPrincipal, reusedBfd.planId, randomUUID())).id), "rejected"); assert.equal(writes, before) healthBfdRemote = "192.0.2.2%fixture-ospf" healthState = true; loseHealthResponse = true const unavailable = await healthPlan("ospf_neighbor", "*neighbor", "state") assert.equal(await waitOperation((await applyPlan(healthPrincipal, unavailable.planId, randomUUID())).id), "unknown"); assert.equal(writes, before + 1) console.log("✓ Post-write routing health: lost Full/active is partial, unavailable evidence is unknown, failed preflight writes nothing, no replay") } finally { healthState = true; failHealthAfterWrite = false; healthUnavailable = false; loseHealthResponse = false; healthBfdRemote = "192.0.2.2%fixture-ospf" } const limited = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Budget", limits: { callsPerMinute: 2, concurrentReads: 100 } }), actor) const limitedPrincipal = await authenticateMcpToken(limited.token) const concurrent = await Promise.allSettled(Array.from({ length: 12 }, () => invokeMcpTool(limitedPrincipal, "mm_ip_settings_list", { serverId: 1 }))) assert.equal(concurrent.filter(value => value.status === "fulfilled").length, 2) console.log("✓ Concurrent calls cannot overspend the atomic call budget") const { reserveProtocolEnvelope, recordUnauthorizedProtocol } = await import("./protocol-limits.js") const envelopes = await Promise.all(Array.from({ length: 16 }, () => reserveProtocolEnvelope(limitedPrincipal))) assert.equal(envelopes.filter(value => value.allowed).length, 10) const rejectedBefore = (await pool.query("SELECT count(*)::int AS n FROM mcp_requests WHERE tool='protocol:unauthorized'")).rows[0].n for (let attempt = 0; attempt < 8; attempt++) await recordUnauthorizedProtocol("192.0.2.99", "UNAUTHORIZED") assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_requests WHERE tool='protocol:unauthorized'")).rows[0].n, rejectedBefore + 1) assert.equal((await pool.query("SELECT max(attempts)::int AS n FROM mcp_unauthorized_buckets WHERE reason='UNAUTHORIZED'")).rows[0].n, 8) console.log("✓ Protocol metadata is atomically bounded; unauthorized repeats preserve counters without storing tokens") const aliasConnection = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Interface alias acceptance", scope: { ...input.scope, operationIds: ["interfaces.update", "firewall.update"] } }), actor) const aliasPrincipal = await authenticateMcpToken(aliasConnection.token) await assert.rejects(invokeMcpTool(aliasPrincipal, "mm_interfaces_update_plan", { serverId: 1, resource: "ip_settings", id: "*fixture", fields: { comment: "out-of-allowlist" } })) await assert.rejects(invokeMcpTool(aliasPrincipal, "mm_interfaces_update_plan", { serverId: 1, resource: "bridge", id: "*fixture", fields: { script: "not-allowed" } })) const aliasPlan = await invokeMcpTool(aliasPrincipal, "mm_interfaces_update_plan", { serverId: 1, resource: "bridge", id: "*fixture", fields: { comment: "reviewed-alias" } }) as { planId: string } await approvePlan(aliasPlan.planId, actor, true) assert.equal(await waitOperation((await applyPlan(aliasPrincipal, aliasPlan.planId, "interface-alias")).id), "applied") assert.equal(bridge.comment, "reviewed-alias") console.log("✓ Named interface/firewall tools reuse typed native plans; resources and fields cannot escape their allowlists") const telemetryActor = { ...actor, permissions: [...actor.permissions, "mm:uptime:admin", "mm:traffic:admin"] } const telemetryOps = ["traffic.settings.update", "uptime.settings.update", "netflow.settings.update", "uptime.ping.probes.read", "uptime.ping.probes.replace", "uptime.speed.probes.read", "uptime.speed.probes.replace", "servers.api-ping.settings.update", "internet-path.settings.update"] // NetFlow reserves four writes, seven successful updates reserve one each, // and the stale Internet path plan reserves one before execution rejects it. // Keep the production default budget unchanged; this fixture needs 12. const telemetry = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Telemetry acceptance", limits: { ...input.limits, mutationsPerHour: 12 }, permissions: ["mm:uptime:write", "mm:traffic:write", "mm:servers:write", "mm:network:write"], scope: { allowGlobal: true, serverIds: [1, 2], operationIds: telemetryOps } }), telemetryActor) const telemetryPrincipal = await authenticateMcpToken(telemetry.token) const applyTelemetry = async (tool: string, value: Record, afterPrepare?: () => Promise) => { const plan = await invokeMcpTool(telemetryPrincipal, tool, value) as { planId: string } await afterPrepare?.() await approvePlan(plan.planId, telemetryActor, true) const operation = await applyPlan(telemetryPrincipal, plan.planId, randomUUID()) assert.equal(await waitOperation(operation.id), "applied", JSON.stringify((await pool.query("SELECT error_code,result FROM mcp_operations WHERE id=$1", [operation.id])).rows[0])) return operation } await applyTelemetry("mm_traffic_settings_update_plan", { settings: { enabled: false, intervalSec: 37 } }, async () => { await pool.query("UPDATE traffic_settings SET last_collected_at=now(),last_duration_ms=123,last_error='synthetic collector status',updated_at=now() WHERE id=1") }) assert.deepEqual((await pool.query("SELECT enabled,interval_sec FROM traffic_settings WHERE id=1")).rows[0], { enabled: false, interval_sec: 37 }) await applyTelemetry("mm_uptime_settings_update_plan", { settings: { resourcesEnabled: false, pingEnabled: false, speedEnabled: false, intervalSec: 317 } }, async () => { await pool.query("UPDATE uptime_settings SET last_collected_at=now(),last_duration_ms=123,last_error='synthetic collector status',updated_at=now() WHERE id=1") }) assert.deepEqual((await pool.query("SELECT resources_enabled,ping_enabled,speed_enabled,interval_sec FROM uptime_settings WHERE id=1")).rows[0], { resources_enabled: false, ping_enabled: false, speed_enabled: false, interval_sec: 317 }) await applyTelemetry("mm_netflow_settings_update_plan", { settings: { enabled: false, topN: 23, mapServiceMinSharePct: 7 } }) await applyTelemetry("mm_servers_api_ping_settings_update_plan", { settings: { enabled: false, intervalSec: 131 } }, async () => { await pool.query("UPDATE servers_api_ping_settings SET last_collected_at=now(),last_duration_ms=123,last_error='synthetic collector status',updated_at=now() WHERE id=1") }) assert.deepEqual((await pool.query("SELECT enabled,interval_sec FROM servers_api_ping_settings WHERE id=1")).rows[0], { enabled: false, interval_sec: 131 }) console.log("✓ Traffic, uptime and API Ping settings tolerate actual collector metadata changes between prepare/apply and persist requested configuration with ordinary scheduler activity") const stalePathPlan = await invokeMcpTool(telemetryPrincipal, "mm_internet_path_settings_update_plan", { settings: { enabled: false, intervalSec: 368 } }) as { planId: string } await approvePlan(stalePathPlan.planId, telemetryActor, true) await pool.query("UPDATE internet_path_settings SET interval_sec=interval_sec+1 WHERE id=1") const changedPathConfig = (await pool.query("SELECT enabled,interval_sec,retention_days FROM internet_path_settings WHERE id=1")).rows[0] const stalePathOperation = await applyPlan(telemetryPrincipal, stalePathPlan.planId, randomUUID()) assert.equal(await waitOperation(stalePathOperation.id), "rejected") assert.equal((await pool.query("SELECT error_code FROM mcp_operations WHERE id=$1", [stalePathOperation.id])).rows[0].error_code, "STATE_CONFLICT") assert.deepEqual((await pool.query("SELECT enabled,interval_sec,retention_days FROM internet_path_settings WHERE id=1")).rows[0], changedPathConfig) await applyTelemetry("mm_internet_path_settings_update_plan", { settings: { enabled: false, intervalSec: 367, retentionDays: 9 } }, async () => { await pool.query("UPDATE internet_path_settings SET last_collected_at=now(),last_duration_ms=123,last_error='synthetic collector status',updated_at=now() WHERE id=1") }) console.log("✓ Internet path plans tolerate collector status updates and reject changed configuration without writes") const pingProbe = { id: "mcp-ping-fixture", srcServerId: 1, srcInterface: "ether1", name: "MCP disabled Ping", target: "192.0.2.1", enabled: false, intervalSec: 1 } await applyTelemetry("mm_ping_probes_replace_plan", { probes: [pingProbe] }) assert.equal((await pool.query("SELECT interval_sec FROM uptime_probes WHERE id=$1", [pingProbe.id])).rows[0].interval_sec, 5) await assert.rejects(invokeMcpTool(telemetryPrincipal, "mm_ping_probes_replace_plan", { probes: [{ ...pingProbe, srcServerId: 999999 }] })) const probeProtection = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,reason,created_by) VALUES($1,$2::jsonb,'fixture',$3)", [probeProtection, JSON.stringify({ kind: "ping-probe", id: pingProbe.id, protection: "deny" }), actor.id]) const { replaceProbes } = await import("../../services/uptime-collector.js") await assert.rejects(replaceProbes([])) await assert.rejects(invokeMcpTool(telemetryPrincipal, "mm_ping_probes_replace_plan", { probes: [] })) await pool.query("DELETE FROM mcp_protections WHERE id=$1", [probeProtection]) const speedProbe = { id: "mcp-speed-fixture", srcServerId: 1, dstServerId: 2, srcInterface: "ether1", dstInterface: "ether1", protocol: "tcp", direction: "both", durationSec: 7, enabled: false } await applyTelemetry("mm_speed_probes_replace_plan", { probes: [speedProbe] }) await pool.query("UPDATE uptime_speed_probes SET last_tx_avg_mbps=100,last_rx_avg_mbps=200,last_run_at=now(),last_status='done' WHERE id=$1", [speedProbe.id]) await applyTelemetry("mm_speed_probes_replace_plan", { probes: [{ ...speedProbe, durationSec: 8 }] }) const reset = (await pool.query("SELECT last_tx_avg_mbps,last_rx_avg_mbps,last_run_at FROM uptime_speed_probes WHERE id=$1", [speedProbe.id])).rows[0] assert.deepEqual(reset, { last_tx_avg_mbps: null, last_rx_avg_mbps: null, last_run_at: null }) await assert.rejects(invokeMcpTool(telemetryPrincipal, "mm_speed_probes_replace_plan", { probes: [{ ...speedProbe, durationSec: 31 }] })) const limitedTelemetry = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Limited telemetry", permissions: ["mm:uptime:read"], scope: { allowGlobal: true, serverIds: [1], operationIds: ["uptime.ping.probes.read"] } }), telemetryActor) await assert.rejects(invokeMcpTool(await authenticateMcpToken(limitedTelemetry.token), "mm_ping_probes_get", {})) console.log("✓ Telemetry settings have actual read-back; probe catalogs enforce full scope, protected deletions, bounded bandwidth duration and measurement reset") const probeAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Probe service adapters", permissions: ["mm:uptime:read", "mm:uptime:write"], scope: { allowGlobal: false, serverIds: [1, 2], operationIds: ["uptime.speed.runs.read", "uptime.speed.interfaces.read", "uptime.ping.interfaces.read", "uptime.ping.visibility.update"] } }), telemetryActor) const probePrincipal = await authenticateMcpToken(probeAccess.token) const probeLimited = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Limited probe readers", mode: "observer", permissions: ["mm:uptime:read"], scope: { allowGlobal: false, serverIds: [1], operationIds: ["uptime.speed.runs.read", "uptime.speed.interfaces.read", "uptime.ping.interfaces.read"] } }), telemetryActor) const probeLimitedPrincipal = await authenticateMcpToken(probeLimited.token) await pool.query("INSERT INTO servers(id,name,host,enabled) VALUES(3,'Foreign history fixture','192.0.2.253',false)") await pool.query("INSERT INTO uptime_speed_test_runs(id,src_server_id,dst_server_id,rx_avg_mbps,ping_loss_pct,created_at) VALUES('speed-visible-z',1,2,0,0,'2026-10-10'),('speed-visible-a',2,1,0,0,'2026-10-10')") await pool.query("INSERT INTO uptime_speed_test_runs(id,src_server_id,dst_server_id,tx_avg_mbps,created_at) VALUES('speed-foreign-src',3,1,-1,'2026-10-17'),('speed-foreign-dst',1,3,-1,'2026-10-17')") await pool.query("INSERT INTO uptime_probes(id,src_server_id,name,target,enabled) VALUES('visibility-own',1,'Visibility fixture','192.0.2.1',false),('visibility-foreign',3,'Foreign visibility fixture','192.0.2.3',false)") probeInterfaceFixtures = true; probeNativeCalls = 0 try { type Runs = { runs: { id: string; txAvgMbps: number | null; rxAvgMbps: number | null; afterBtPing: { lossPct: number | null } }[]; nextOffset: number | null } const history = await invokeMcpTool(probePrincipal, "mm_speed_test_runs_get", { limit: 1 }) as Runs assert.equal(history.runs[0].id, "speed-visible-z"); assert.equal(history.runs[0].txAvgMbps, null); assert.equal(history.runs[0].rxAvgMbps, 0); assert.equal(history.runs[0].afterBtPing.lossPct, 0) assert.equal(history.nextOffset, 1) const nextHistory = await invokeMcpTool(probePrincipal, "mm_speed_test_runs_get", { limit: 1, offset: 1 }) as Runs assert.equal(nextHistory.runs[0].id, "speed-visible-a") assert.deepEqual((await invokeMcpTool(probeLimitedPrincipal, "mm_speed_test_runs_get", {}) as Runs).runs, []) const pingIfaces = await invokeMcpTool(probeLimitedPrincipal, "mm_ping_source_interfaces_get", { serverId: 1, limit: 1 }) as { interfaces: { name: string }[]; unknownInterfaces: { name: string }[]; total: number; incomplete: boolean; nextOffset: number } assert.equal(pingIfaces.interfaces[0].name, "ether1"); assert.equal(pingIfaces.total, 2); assert.equal(pingIfaces.nextOffset, 1) assert.equal(pingIfaces.unknownInterfaces[0].name, "unknown-probe"); assert.equal(pingIfaces.incomplete, true) const speedIfaces = await invokeMcpTool(probeLimitedPrincipal, "mm_speed_test_interfaces_get", { serverId: 1 }) as { interfaces: { addresses: string[]; addressStateVerified: boolean }[] } assert.deepEqual(speedIfaces.interfaces[0].addresses, ["192.0.2.10"]); assert.equal(speedIfaces.interfaces[0].addressStateVerified, true) assert.ok(!JSON.stringify(speedIfaces).includes("never-return-probe-key")) const nativeBefore = probeNativeCalls await assert.rejects(invokeMcpTool(probeLimitedPrincipal, "mm_speed_test_interfaces_get", { serverId: 2 })); assert.equal(probeNativeCalls, nativeBefore) await pool.query("UPDATE servers SET enabled=false WHERE id=1") await assert.rejects(invokeMcpTool(probeLimitedPrincipal, "mm_ping_source_interfaces_get", { serverId: 1 })); assert.equal(probeNativeCalls, nativeBefore) await pool.query("UPDATE servers SET enabled=true WHERE id=1") probeReadFailure = true await assert.rejects(invokeMcpTool(probeLimitedPrincipal, "mm_speed_test_interfaces_get", { serverId: 1 })) probeReadFailure = false const visibilityArgs = { serverId: 1, probeId: "visibility-own", showOnDashboard: false } await assert.rejects(invokeMcpTool(probePrincipal, "mm_ping_probe_visibility_plan", { ...visibilityArgs, probeId: "visibility-foreign" })) const protectedVisibility = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,created_by) VALUES($1,$2::jsonb,$3)", [protectedVisibility, JSON.stringify({ kind: "ping-probe", id: "visibility-own", protection: "deny" }), actor.id]) try { await assert.rejects(invokeMcpTool(probePrincipal, "mm_ping_probe_visibility_plan", visibilityArgs)) await assert.rejects((await import("../../services/uptime-collector.js")).updateProbeShowOnDashboard("visibility-own", false)) } finally { await pool.query("DELETE FROM mcp_protections WHERE id=$1", [protectedVisibility]) } const staleVisibility = await invokeMcpTool(probePrincipal, "mm_ping_probe_visibility_plan", visibilityArgs) as { planId: string } await approvePlan(staleVisibility.planId, telemetryActor, true) await pool.query("UPDATE uptime_probes SET target='192.0.2.2' WHERE id='visibility-own'") assert.equal(await waitOperation((await applyPlan(probePrincipal, staleVisibility.planId, randomUUID())).id), "rejected") const visibility = await invokeMcpTool(probePrincipal, "mm_ping_probe_visibility_plan", visibilityArgs) as { planId: string } await approvePlan(visibility.planId, telemetryActor, true) const beforeVisibilityCalls = probeNativeCalls assert.equal(await waitOperation((await applyPlan(probePrincipal, visibility.planId, randomUUID())).id), "applied"); assert.equal(probeNativeCalls, beforeVisibilityCalls) assert.deepEqual((await pool.query("SELECT show_on_dashboard,enabled,target FROM uptime_probes WHERE id='visibility-own'")).rows[0], { show_on_dashboard: false, enabled: false, target: "192.0.2.2" }) } finally { probeInterfaceFixtures = false; probeReadFailure = false await pool.query("DELETE FROM uptime_speed_test_runs WHERE id LIKE 'speed-visible-%' OR id LIKE 'speed-foreign-%'") await pool.query("DELETE FROM uptime_probes WHERE id IN ('visibility-own','visibility-foreign')") await pool.query("DELETE FROM servers WHERE id=3") await pool.query("UPDATE servers SET enabled=true WHERE id=1") } console.log("✓ Probe adapters: both history endpoints scoped in SQL before paging/parser, interface effective flags/unknowns/errors, protected/stale visibility plans with read-back and no network dispatch") const liveAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Bounded live traffic", mode: "observer", permissions: ["mm:traffic:read"], scope: { allowGlobal: false, serverIds: [1], operationIds: ["traffic.live.read"] } }), telemetryActor) const livePrincipal = await authenticateMcpToken(liveAccess.token) liveFixtures = true; liveNativeCalls = 0 try { const sample = await invokeMcpTool(livePrincipal, "mm_traffic_live_get", { serverId: 1 }) as { samples: { rxMbps: number; txMbps: number }[]; forwardingVerified: boolean } assert.equal(sample.samples[0].rxMbps, 2); assert.equal(sample.samples[0].txMbps, 0); assert.equal(sample.forwardingVerified, false) assert.equal(liveNativeCalls, 2); assert.ok(!JSON.stringify(sample).includes("never-return-live-key")) const separate = await invokeMcpTool(livePrincipal, "mm_traffic_live_get", { serverId: 1, interface: "ether1", samples: 2 }) as { samples: unknown[] } assert.equal(separate.samples.length, 2); assert.equal(liveNativeCalls, 4) const beforeDenied = liveNativeCalls await assert.rejects(invokeMcpTool(livePrincipal, "mm_traffic_live_get", { serverId: 2 })) await assert.rejects(invokeMcpTool(livePrincipal, "mm_traffic_live_get", { serverId: 1, interface: "ether1,ether2" })) await assert.rejects(invokeMcpTool(livePrincipal, "mm_traffic_live_get", { serverId: 1, samples: 4 })) const aborted = new AbortController(); aborted.abort() await assert.rejects(invokeMcpTool(livePrincipal, "mm_traffic_live_get", { serverId: 1 }, aborted.signal)) await pool.query("UPDATE servers SET enabled=false WHERE id=1") await assert.rejects(invokeMcpTool(livePrincipal, "mm_traffic_live_get", { serverId: 1 })); assert.equal(liveNativeCalls, beforeDenied) await pool.query("UPDATE servers SET enabled=true WHERE id=1") liveIncomplete = true await assert.rejects(invokeMcpTool(livePrincipal, "mm_traffic_live_get", { serverId: 1 })) liveIncomplete = false; liveChangeSource = true await assert.rejects(invokeMcpTool(livePrincipal, "mm_traffic_live_get", { serverId: 1, samples: 3 })) liveChangeSource = false; liveRevokeConnection = liveAccess.connection.id const beforeRevoke = liveNativeCalls await assert.rejects(invokeMcpTool(livePrincipal, "mm_traffic_live_get", { serverId: 1, samples: 3 })) assert.equal(liveNativeCalls, beforeRevoke + 2, "Revocation must stop before another native monitor request") } finally { liveFixtures = false; liveIncomplete = false; liveChangeSource = false; liveRevokeConnection = undefined await pool.query("UPDATE servers SET enabled=true WHERE id=1") } console.log("✓ Bounded live traffic: complete sums, single interface, exact zero, no secret payload, scope/disabled/cancellation, bounds, no partial sum and revocation/source drift before next dispatch") const geoipAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "GeoIP acceptance", permissions: ["mm:traffic:read", "mm:traffic:write"], scope: { allowGlobal: true, serverIds: [1, 2], operationIds: ["geoip.read", "geoip.settings.update"] } }), telemetryActor) const geoipPrincipal = await authenticateMcpToken(geoipAccess.token) const { getGeoipSettings, updateGeoipSettings, touchGeoipRunMeta } = await import("../../services/geoip-settings.js") const geoipStatus = await invokeMcpTool(geoipPrincipal, "mm_geoip_get", {}) as { running: boolean; settings: { enabled: boolean } } assert.equal(geoipStatus.running, false) await assert.rejects(invokeMcpTool(geoipPrincipal, "mm_geoip_settings_update_plan", { settings: {} })) await assert.rejects(invokeMcpTool(geoipPrincipal, "mm_geoip_settings_update_plan", { settings: { updateIntervalSec: 60 } })) await assert.rejects(invokeMcpTool(geoipPrincipal, "mm_geoip_settings_update_plan", { settings: { mirrorUrl: "https://example.invalid" } })) const geoipPlan = await invokeMcpTool(geoipPrincipal, "mm_geoip_settings_update_plan", { settings: { enabled: false, updateIntervalSec: 86400 } }) as { planId: string } await approvePlan(geoipPlan.planId, telemetryActor, true) // Collector metadata must not invalidate a reviewed configuration change. await touchGeoipRunMeta({ lastCheckAt: new Date().toISOString(), lastError: "fixture update observation" }) assert.equal(await waitOperation((await applyPlan(geoipPrincipal, geoipPlan.planId, randomUUID())).id), "applied") assert.equal((await getGeoipSettings()).enabled, false) assert.equal((await getGeoipSettings()).updateIntervalSec, 86400) assert.equal((await getGeoipSettings()).lastError, "fixture update observation") const staleGeoip = await invokeMcpTool(geoipPrincipal, "mm_geoip_settings_update_plan", { settings: { updateIntervalSec: 172800 } }) as { planId: string } await approvePlan(staleGeoip.planId, telemetryActor, true) await updateGeoipSettings({ updateIntervalSec: 259200 }) assert.equal(await waitOperation((await applyPlan(geoipPrincipal, staleGeoip.planId, randomUUID())).id), "rejected") assert.equal((await getGeoipSettings()).updateIntervalSec, 259200) const geoipProtection = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,reason,created_by) VALUES($1,$2::jsonb,'fixture',$3)", [geoipProtection, JSON.stringify({ kind: "geoip-settings", id: "1", protection: "deny" }), actor.id]) try { await assert.rejects(updateGeoipSettings({ enabled: true })) await assert.rejects(invokeMcpTool(geoipPrincipal, "mm_geoip_settings_update_plan", { settings: { enabled: true } })) assert.equal((await getGeoipSettings()).enabled, false) } finally { await pool.query("DELETE FROM mcp_protections WHERE id=$1", [geoipProtection]) } const partialGeoipAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "GeoIP partial scope", permissions: ["mm:traffic:read", "mm:traffic:write"], scope: { allowGlobal: true, serverIds: [1], operationIds: ["geoip.read", "geoip.settings.update"] } }), telemetryActor) const partialGeoipPrincipal = await authenticateMcpToken(partialGeoipAccess.token) await assert.rejects(invokeMcpTool(partialGeoipPrincipal, "mm_geoip_get", {})) await assert.rejects(invokeMcpTool(partialGeoipPrincipal, "mm_geoip_settings_update_plan", { settings: { enabled: true } })) await pool.query("CREATE FUNCTION mcp_test_ignore_geoip() RETURNS trigger LANGUAGE plpgsql AS 'BEGIN RETURN OLD; END'") await pool.query("CREATE TRIGGER mcp_test_ignore_geoip BEFORE UPDATE ON geoip_settings FOR EACH ROW EXECUTE FUNCTION mcp_test_ignore_geoip()") try { const ignored = await invokeMcpTool(geoipPrincipal, "mm_geoip_settings_update_plan", { settings: { updateIntervalSec: 345600 } }) as { planId: string } await approvePlan(ignored.planId, telemetryActor, true) assert.equal(await waitOperation((await applyPlan(geoipPrincipal, ignored.planId, randomUUID())).id), "partial") assert.equal((await getGeoipSettings()).updateIntervalSec, 259200) } finally { await pool.query("DROP TRIGGER mcp_test_ignore_geoip ON geoip_settings") await pool.query("DROP FUNCTION mcp_test_ignore_geoip()") } const geoipCollector = await import("../../services/geoip-update-collector.js") const savedGeoipDirectory = process.env.GEOIP_DIR process.env.GEOIP_DIR = (await import("node:path")).join(process.env.MCP_STATE_DIR!, "geoip-fixture") let mirrorRequests = 0 geoipCollector.setGeoipFetchForTests(async () => { mirrorRequests++; return new Response(null, { status: 503 }) }) try { const enableGeoip = await invokeMcpTool(geoipPrincipal, "mm_geoip_settings_update_plan", { settings: { enabled: true } }) as { planId: string; changes: { warnings: string[] } } assert.ok(enableGeoip.changes.warnings.some(warning => warning.includes("сразу"))) await approvePlan(enableGeoip.planId, telemetryActor, true) assert.equal(await waitOperation((await applyPlan(geoipPrincipal, enableGeoip.planId, randomUUID())).id), "applied") for (let attempt = 0; attempt < 100 && (mirrorRequests < 2 || geoipCollector.getGeoipUpdateState().running); attempt++) await delay(20) assert.equal(mirrorRequests, 2); assert.equal(geoipCollector.getGeoipUpdateState().running, false) const failedMirrorStatus = await invokeMcpTool(geoipPrincipal, "mm_geoip_get", {}) as { settings: { enabled: boolean; lastError: string } } assert.equal(failedMirrorStatus.settings.enabled, true) assert.match(failedMirrorStatus.settings.lastError, /HTTP 503/) assert.equal(mirrorRequests, 2, "Status reads must not restart the failed download") } finally { await updateGeoipSettings({ enabled: false }) await (await import("../../services/scheduler.js")).refreshScheduler() geoipCollector.resetGeoipUpdateForTests() if (savedGeoipDirectory === undefined) delete process.env.GEOIP_DIR else process.env.GEOIP_DIR = savedGeoipDirectory } console.log("✓ GeoIP uses shared guarded settings: strict input, stable metadata, stale-plan rejection, full scope, protected REST service and failed persistence read-back") const evoAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "EvoBGP settings acceptance", limits: { mutationsPerHour: 50 }, permissions: ["mm:settings:admin"], scope: { allowGlobal: true, serverIds: [1, 2], operationIds: ["evobgp.settings.read", "evobgp.settings.update", "evobgp.catalog.read"] } }), actor) const evoPrincipal = await authenticateMcpToken(evoAccess.token) const limitedEvo = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "EvoBGP partial scope", permissions: ["mm:settings:admin"], scope: { allowGlobal: true, serverIds: [1], operationIds: ["evobgp.settings.read", "evobgp.settings.update", "evobgp.catalog.read"] } }), actor) const limitedEvoPrincipal = await authenticateMcpToken(limitedEvo.token) await assert.rejects(invokeMcpTool(limitedEvoPrincipal, "mm_evobgp_settings_get", {})) await assert.rejects(invokeMcpTool(limitedEvoPrincipal, "mm_evobgp_settings_update_plan", { settings: { enabled: true } })) const { readEvoBgpSettings, writeEvoBgpSettings } = await import("../../services/evobgp-settings.js") await pool.query("DELETE FROM evobgp_settings") assert.deepEqual(await invokeMcpTool(evoPrincipal, "mm_evobgp_settings_get", {}), { baseUrl: "", enabled: false, secretConfigured: false }) assert.equal((await pool.query("SELECT count(*)::int AS n FROM evobgp_settings")).rows[0].n, 0) const evoRoot = "https://evo.example.invalid", evoKey = "fixture-evo-private-key" const issueEvoSecret = async (purpose = `evobgp-api:${evoRoot}`, owner = actor.id, serverId: number | null = null) => { const id = randomUUID() await pool.query("INSERT INTO mcp_secrets(id,purpose,server_id,ciphertext,created_by) VALUES($1,$2,$3,$4,$5)", [id, purpose, serverId, await encryptSecret("Bearer " + evoKey), owner]) return id } for (const ref of [await issueEvoSecret("evobgp-api:https://wrong.example.invalid"), await issueEvoSecret(undefined, "other-owner"), await issueEvoSecret(undefined, actor.id, 1)]) { await assert.rejects(invokeMcpTool(evoPrincipal, "mm_evobgp_settings_update_plan", { settings: { baseUrl: evoRoot }, apiKeyRef: ref })) } await assert.rejects(invokeMcpTool(evoPrincipal, "mm_evobgp_settings_update_plan", { settings: { apiKey: "raw-input-rejected" } })) await assert.rejects(invokeMcpTool(evoPrincipal, "mm_evobgp_settings_update_plan", { settings: {} })) const evoKeyRef = await issueEvoSecret() const applyEvo = async (args: Record, state = "applied") => { const plan = await invokeMcpTool(evoPrincipal, "mm_evobgp_settings_update_plan", args) as { planId: string; changes: unknown } assert.ok(!JSON.stringify(plan).includes(evoKey)) await approvePlan(plan.planId, actor, true) const operation = await applyPlan(evoPrincipal, plan.planId, randomUUID()) assert.equal(await waitOperation(operation.id), state) return operation } await applyEvo({ settings: { baseUrl: "EVO.example.invalid/", enabled: false }, apiKeyRef: evoKeyRef }) assert.deepEqual(await readEvoBgpSettings(), { baseUrl: evoRoot, enabled: false, apiKey: evoKey }) assert.deepEqual(await invokeMcpTool(evoPrincipal, "mm_evobgp_settings_get", {}), { baseUrl: evoRoot, enabled: false, secretConfigured: true }) await assert.rejects(invokeMcpTool(evoPrincipal, "mm_evobgp_settings_update_plan", { settings: { baseUrl: "https://different.example.invalid" } })) const staleEvo = await invokeMcpTool(evoPrincipal, "mm_evobgp_settings_update_plan", { settings: { enabled: true } }) as { planId: string } await approvePlan(staleEvo.planId, actor, true) await writeEvoBgpSettings({ apiKey: "fixture-replaced-private-key" }) assert.equal(await waitOperation((await applyPlan(evoPrincipal, staleEvo.planId, randomUUID())).id), "rejected") assert.equal((await readEvoBgpSettings()).enabled, false) const evoProtection = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,reason,created_by) VALUES($1,$2::jsonb,'fixture',$3)", [evoProtection, JSON.stringify({ kind: "evobgp-settings", id: "1", protection: "deny" }), actor.id]) try { await assert.rejects(writeEvoBgpSettings({ enabled: true })) await assert.rejects(invokeMcpTool(evoPrincipal, "mm_evobgp_settings_update_plan", { settings: { enabled: true } })) } finally { await pool.query("DELETE FROM mcp_protections WHERE id=$1", [evoProtection]) } await pool.query("CREATE FUNCTION mcp_test_ignore_evo() RETURNS trigger LANGUAGE plpgsql AS 'BEGIN RETURN OLD; END'") await pool.query("CREATE TRIGGER mcp_test_ignore_evo BEFORE UPDATE ON evobgp_settings FOR EACH ROW EXECUTE FUNCTION mcp_test_ignore_evo()") try { await applyEvo({ settings: {}, apiKeyRef: evoKeyRef }, "partial") } finally { await pool.query("DROP TRIGGER mcp_test_ignore_evo ON evobgp_settings") await pool.query("DROP FUNCTION mcp_test_ignore_evo()") } await applyEvo({ settings: { baseUrl: "https://different.example.invalid" }, apiKeyRef: null }) assert.equal((await readEvoBgpSettings()).apiKey, "") await applyEvo({ settings: { baseUrl: evoRoot }, apiKeyRef: evoKeyRef }) await applyEvo({ settings: { enabled: true } }) assert.deepEqual(await readEvoBgpSettings(), { baseUrl: evoRoot, enabled: true, apiKey: evoKey }) const evoLogs = await pool.query("SELECT to_jsonb(request) AS payload FROM mcp_requests AS request WHERE connection_id=$1 UNION ALL SELECT intended_state FROM mcp_operation_steps WHERE operation_id IN (SELECT id FROM mcp_operations WHERE connection_id=$1)", [evoAccess.connection.id]) assert.ok(!JSON.stringify(evoLogs.rows).includes(evoKey)) console.log("✓ EvoBGP settings: read is non-mutating, owner/destination-bound opaque keys, encrypted/redacted plans, key rotation invalidates plans, protections and failed key read-back") await writeEvoBgpSettings({ baseUrl: `http://127.0.0.1:${port}`, enabled: true }) await assert.rejects(invokeMcpTool(limitedEvoPrincipal, "mm_evobgp_catalog_get", {})); assert.equal(evoCatalogCalls, 0) await assert.rejects(invokeMcpTool(evoPrincipal, "mm_evobgp_catalog_get", { baseUrl: "https://untrusted.invalid" })); assert.equal(evoCatalogCalls, 0) const emptyCatalog = await invokeMcpTool(evoPrincipal, "mm_evobgp_catalog_get", {}) as { communities: unknown[]; incomplete: boolean } assert.deepEqual(emptyCatalog.communities, []); assert.equal(emptyCatalog.incomplete, false) assert.equal(evoCatalogAuthorization, "Bearer " + evoKey) const evoAssociations = (await pool.query("INSERT INTO filter_rules(server_id,community,gateway_tunnel_id) VALUES(1,' 64512:4242 ','fixture'),(1,'64512:4242','fixture'),(2,'64512:4242','fixture') RETURNING id")).rows.map(row => row.id) try { evoCatalogPayload = { ...emptyEvoCatalog, modules: { items: [{ id: "m", name: evoKey, type: "custom" }] }, domains: { items: [1, 2].map(id => ({ module_id: "m", entry: { id: "d" + id, fqdn: `example${id}.invalid`, community_id: "c" } })) }, asns: { items: [{ module_id: "m", entry: { id: "a", asn: 64512, community_id: "c" } }] }, communities: { items: [{ id: "c", community: "64512:4242" }, { id: "z", community: "64512:4243" }] }, } type EvoPage = { catalogVersion: string; domains: { id: string }[]; communities: { serverCount: number; prefixCount: number | null }[]; pages: { domains: { total: number; nextOffset: number | null } }; incomplete: boolean } const first = await invokeMcpTool(evoPrincipal, "mm_evobgp_catalog_get", { limit: 1 }) as EvoPage assert.equal(first.pages.domains.total, 2); assert.equal(first.pages.domains.nextOffset, 1) assert.equal(first.communities[0].serverCount, 2); assert.equal(first.communities[0].prefixCount, null) assert.equal(first.incomplete, true); assert.ok(!JSON.stringify(first).includes(evoKey)) const second = await invokeMcpTool(evoPrincipal, "mm_evobgp_catalog_get", { limit: 1, offset: 1 }) as EvoPage assert.equal(second.catalogVersion, first.catalogVersion); assert.equal(second.pages.domains.nextOffset, null) assert.notEqual(second.domains[0].id, first.domains[0].id); assert.equal(second.communities[0].prefixCount, 0) evoCatalogPayload = { modules: { items: [] } } await assert.rejects(invokeMcpTool(evoPrincipal, "mm_evobgp_catalog_get", {})) evoCatalogRedirect = true await assert.rejects(invokeMcpTool(evoPrincipal, "mm_evobgp_catalog_get", {})) evoCatalogRedirect = false await writeEvoBgpSettings({ enabled: false }) const callsBeforeDisabled = evoCatalogCalls await assert.rejects(invokeMcpTool(evoPrincipal, "mm_evobgp_catalog_get", {})); assert.equal(evoCatalogCalls, callsBeforeDisabled) const catalogRequests = (await pool.query("SELECT result FROM mcp_requests WHERE connection_id=$1 AND tool='mm_evobgp_catalog_get'", [evoAccess.connection.id])).rows assert.ok(!JSON.stringify(catalogRequests).includes(evoKey)) } finally { evoCatalogRedirect = false; evoCatalogPayload = emptyEvoCatalog await pool.query("DELETE FROM filter_rules WHERE id=ANY($1::bigint[])", [evoAssociations]) } console.log("✓ EvoBGP catalog: full scope before upstream/SQL, saved credentials only, shared formatting, nullable counts, paging/version, credential reflection masked, disabled/malformed/redirect rejected") const evoProbeAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "EvoBGP connection check", permissions: ["mm:settings:admin"], scope: { allowGlobal: true, serverIds: [1, 2], operationIds: ["evobgp.connection.check"] } }), actor) const evoProbePrincipal = await authenticateMcpToken(evoProbeAccess.token) const partialEvoProbeAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Partial EvoBGP connection check", permissions: ["mm:settings:admin"], scope: { allowGlobal: true, serverIds: [1], operationIds: ["evobgp.connection.check"] } }), actor) await assert.rejects(invokeMcpTool(await authenticateMcpToken(partialEvoProbeAccess.token), "mm_evobgp_connection_check", {})); assert.equal(evoProbeCalls, 0) const beforeProbe = await readEvoBgpSettings() evoProbePayload = { items: [{ name: evoKey }] } const probeReceipt = await invokeMcpTool(evoProbePrincipal, "mm_evobgp_connection_check", {}) as { ok: boolean; configurationSaved: boolean; routingVerified: boolean } assert.equal(probeReceipt.ok, true); assert.equal(probeReceipt.configurationSaved, false); assert.equal(probeReceipt.routingVerified, false) assert.equal(evoProbeAuthorization, "Bearer " + evoKey); assert.ok(!JSON.stringify(probeReceipt).includes(evoKey)) assert.deepEqual(await readEvoBgpSettings(), beforeProbe) await assert.rejects(invokeMcpTool(evoProbePrincipal, "mm_evobgp_connection_check", { baseUrl: "https://different.example.invalid" })); assert.equal(evoProbeCalls, 1) await assert.rejects(invokeMcpTool(evoProbePrincipal, "mm_evobgp_connection_check", { apiKey: "raw-key-rejected" })); assert.equal(evoProbeCalls, 1) const probeRoot = `http://127.0.0.1:${port}` const foreignProbeRef = await issueEvoSecret(`evobgp-api:${probeRoot}`, "other-owner") await assert.rejects(invokeMcpTool(evoProbePrincipal, "mm_evobgp_connection_check", { apiKeyRef: foreignProbeRef })); assert.equal(evoProbeCalls, 1) await assert.rejects(invokeMcpTool(evoProbePrincipal, "mm_evobgp_connection_check", { apiKeyRef: evoKeyRef })); assert.equal(evoProbeCalls, 1) const probeKeyRef = await issueEvoSecret(`evobgp-api:${probeRoot}`) await writeEvoBgpSettings({ baseUrl: evoRoot }) await invokeMcpTool(evoProbePrincipal, "mm_evobgp_connection_check", { baseUrl: probeRoot, apiKeyRef: probeKeyRef }) assert.equal((await readEvoBgpSettings()).baseUrl, evoRoot) evoProbePayload = { error: evoKey } await assert.rejects(invokeMcpTool(evoProbePrincipal, "mm_evobgp_connection_check", { baseUrl: probeRoot, apiKeyRef: probeKeyRef }), error => error instanceof Error && !error.message.includes(evoKey)) evoProbeRedirect = true await assert.rejects(invokeMcpTool(evoProbePrincipal, "mm_evobgp_connection_check", { baseUrl: probeRoot, apiKeyRef: probeKeyRef })) evoProbeRedirect = false; evoProbePayload = { items: [] } const probeRequests = (await pool.query("SELECT result,error_code FROM mcp_requests WHERE connection_id=$1", [evoProbeAccess.connection.id])).rows assert.ok(!JSON.stringify(probeRequests).includes(evoKey)) console.log("✓ EvoBGP connection check: fixed bounded GET, full scope before network, owner/destination-bound draft secret, no save/credential reflection, malformed and redirects rejected") const statsAccess = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Scoped statistics", permissions: ["mm:traffic:read"], scope: { allowGlobal: false, serverIds: [1], operationIds: ["statistics.read", "statistics.pivot"] } }), telemetryActor) const statsPrincipal = await authenticateMcpToken(statsAccess.token) const { ensurePartitionFor } = await import("../../db/partitions.js") await ensurePartitionFor(pool, "flow_hour_facts", "day", new Date("2026-10-03T00:00:00Z")) await pool.query("INSERT INTO flow_hour_facts(server_id,bucket_at,iface,country,service,asn,bytes,packets) VALUES(1,'2026-10-03T00:00:00Z','fixture-stats','RU','FixtureStats',0,12345,10),(2,'2026-10-03T00:00:00Z','fixture-stats','US','ForeignStats',0,98765,90)") const touched: number[] = [], { setRefreshIfacesForTests } = await import("../../services/traffic-flow-ifaces.js") setRefreshIfacesForTests(async id => { touched.push(id) }) try { const summary = await invokeMcpTool(statsPrincipal, "mm_statistics_get", { serverId: 1, from: "2026-10-03", to: "2026-10-03", iface: "fixture-stats", planes: "all" }) as { kpis: { bytes: number }; services: { label: string }[] } assert.equal(summary.kpis.bytes, 12345); assert.ok(!JSON.stringify(summary).includes("ForeignStats")) const pivot = await invokeMcpTool(statsPrincipal, "mm_statistics_pivot_get", { serverId: 1, from: "2026-10-03", to: "2026-10-03", iface: "fixture-stats", planes: "all", row: "country", col: "service" }) as { rows: { total: number }[] } assert.equal(pivot.rows.reduce((sum, row) => sum + row.total, 0), 12345) assert.ok(touched.length > 0 && touched.every(id => id === 1), JSON.stringify(touched)) await assert.rejects(invokeMcpTool(statsPrincipal, "mm_statistics_get", { serverId: 2, from: "2026-10-03", to: "2026-10-03" })) await assert.rejects(invokeMcpTool(statsPrincipal, "mm_statistics_get", { serverId: 1, from: "2026-01-01", to: "2026-10-03" })) } finally { setRefreshIfacesForTests(null) } console.log("✓ Scoped statistics/pivot filter SQL and native interface refresh to the selected server; long windows and foreign servers are denied") const savedSocket = process.env.FLOW_AGENT_SOCKET const socket = process.platform === "win32" ? "\\\\.\\pipe\\mcp-flow-" + randomUUID() : (await import("node:path")).join((await import("node:os")).tmpdir(), "mcp-flow-" + randomUUID() + ".sock") let hostApplies = 0, hostMatches = true const flowAgent = createServer((request, response) => { request.resume() request.on("end", () => { if (request.url === "/apply") hostApplies++ response.setHeader("content-type", "application/json") response.end(JSON.stringify({ interfaceUp: true, configMatches: hostMatches, publicKey: "public-key-fixture", listenPort: 51821, addresses: ["10.255.254.1/24"], peers: [], privateKey: "must-never-return" })) }) }) await new Promise((resolve, reject) => { flowAgent.once("error", reject); flowAgent.listen(socket, resolve) }) try { process.env.FLOW_AGENT_SOCKET = socket const hostConnection = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, name: "Host acceptance", permissions: ["mm:settings:admin"], scope: { allowGlobal: true, serverIds: [1, 2], operationIds: ["netflow.host.read", "netflow.host.reconcile"] } }), actor) const hostPrincipal = await authenticateMcpToken(hostConnection.token) assert.ok(!JSON.stringify(await invokeMcpTool(hostPrincipal, "mm_netflow_host_get", {})).includes("must-never-return")) const hostPlan = await invokeMcpTool(hostPrincipal, "mm_netflow_host_reconcile_plan", {}) as { planId: string } await approvePlan(hostPlan.planId, actor, true) assert.equal(await waitOperation((await applyPlan(hostPrincipal, hostPlan.planId, "flow-host-apply")).id), "applied") assert.equal(hostApplies, 1) const protectedHost = randomUUID() await pool.query("INSERT INTO mcp_protections(id,object_ref,reason,created_by) VALUES($1,$2::jsonb,'fixture',$3)", [protectedHost, JSON.stringify({ kind: "netflow-host", id: "1", protection: "deny" }), actor.id]) await assert.rejects(invokeMcpTool(hostPrincipal, "mm_netflow_host_reconcile_plan", {})) await assert.rejects((await import("../../services/traffic-flow-host-agent.js")).reconcileFlowHost()) assert.equal(hostApplies, 1) await pool.query("DELETE FROM mcp_protections WHERE id=$1", [protectedHost]) const failedHost = await invokeMcpTool(hostPrincipal, "mm_netflow_host_reconcile_plan", {}) as { planId: string } await approvePlan(failedHost.planId, actor, true) hostMatches = false assert.equal(await waitOperation((await applyPlan(hostPrincipal, failedHost.planId, "flow-host-unverified")).id), "partial") assert.equal(hostApplies, 2) console.log("✓ NetFlow host uses saved settings and real local-agent protocol; protected legacy apply is blocked and a mismatched read-back remains partial") } finally { if (savedSocket === undefined) delete process.env.FLOW_AGENT_SOCKET; else process.env.FLOW_AGENT_SOCKET = savedSocket await new Promise(resolve => flowAgent.close(() => resolve())) } legacyRouteContextFixture = true const profileConnection = await createMcpConnection(mcpConnectionCreateSchema.parse({ ...input, permissions: [...input.permissions, "mm:filters:write"], name: "Controller", scope: { ...input.scope, operationIds: ["routing.profiles.save", "routing.profiles.read", "routing.analyze", "routing.community.switch", "routing.path.verify"] } }), actor) const controllerPrincipal = await authenticateMcpToken(profileConnection.token) const profileInput = { name: "Persisted", serverId: 1, family: "ipv4", community: "65000:10", candidates: [{ id: "single", wanInterface: "wan", downstreamInterface: "down", jumpHostId: 1, exitNodeId: 1, gateway: "192.0.2.1", gatewayRef: { resource: "ipv4_route", id: "*1" } }] } const legacyContext = await invokeMcpTool(controllerPrincipal, "mm_route_optimizer_analyze", { profile: profileInput }) as { contextVerified: boolean; currentCandidateId: string | null; executionAvailable: boolean; dependencyProof: string; targetRuleProof: string; sourceProofs: unknown } assert.equal(legacyContext.contextVerified, true); assert.equal(legacyContext.currentCandidateId, "single"); assert.equal(legacyContext.executionAvailable, true, "legacy main profile needs no explicit table, probeVrf or bgpContext") await assert.rejects(invokeMcpTool(controllerPrincipal, "mm_route_optimizer_plan", { profile: profileInput, candidateId: "single" }), error => error instanceof Error && "code" in error && error.code === "MEASUREMENT_UNKNOWN") const { findMcpService, registerMcpService } = await import("./service-registry.js") for (const iface of ["wan", "down"]) await pool.query("INSERT INTO mcp_routing_observations(id,src_server_id,dst_server_id,src_interface,dst_interface,family,routing_table,ping_ms,loss_pct,dl_mbps,ul_mbps,ping_at,bandwidth_at,context_verified,source) VALUES($1,1,1,$2,'peer','ipv4','main',10,0,100,100,now()-interval '2 hours',now()-interval '2 hours',true,'fixture')", [randomUUID(), iface]) await assert.rejects(findMcpService("routing.community")!.current({ serverId: 1, profile: profileInput, candidateId: "single" }), error => error instanceof Error && "code" in error && error.code === "STATE_CONFLICT", "an unbound legacy service input cannot bypass the native context proof") await assert.rejects(findMcpService("routing.community")!.current({ serverId: 1, profile: profileInput, candidateId: "single", contextProof: legacyContext.dependencyProof, targetRuleProof: legacyContext.targetRuleProof, contextSources: legacyContext.sourceProofs }), error => error instanceof Error && "code" in error && error.code === "MEASUREMENT_UNKNOWN") console.log("✓ Community plans reject missing measurements and recheck expired SLA evidence before any write") const controllerOriginalAuthority = await effectiveMcpConnection(controllerPrincipal) const controllerFixtureExpiry = () => fixtureGrantExpiry(Date.now(), Date.parse(controllerPrincipal.credentialExpiresAt), Date.parse(controllerOriginalAuthority.policy.expiresAt), controllerOriginalAuthority.owner_valid_until.getTime()) const profilePlan = await invokeMcpTool(controllerPrincipal, "mm_route_profile_save_plan", { profile: profileInput, enabled: true, expiresAt: controllerFixtureExpiry() }) as { planId: string } await approvePlan(profilePlan.planId, actor, true) const saved = await applyPlan(controllerPrincipal, profilePlan.planId, "profile-save") for (let index = 0; index < 100; index++) { state = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [saved.id])).rows[0].state if (!["pending", "running"].includes(state)) break await delay(30) } assert.equal(state, "applied") const { tickRoutingProfiles } = await import("./routing-profiles.js") await tickRoutingProfiles() const persisted = (await pool.query("SELECT state,enabled FROM mcp_routing_profiles WHERE connection_id=$1", [profileConnection.connection.id])).rows[0] assert.equal(persisted.enabled, true); assert.ok(persisted.state.checkedAt); assert.equal(persisted.state.planId, undefined) await setConnectionStatus(profileConnection.connection.id, "paused", actor) await tickRoutingProfiles() assert.equal((await pool.query("SELECT enabled FROM mcp_routing_profiles WHERE connection_id=$1", [profileConnection.connection.id])).rows[0].enabled, false) console.log("✓ Persistent controller preserves unknown path and stops after access revocation") legacyRouteContextFixture = false await setConnectionStatus(profileConnection.connection.id, "active", actor) const webPayload = { connectionId: profileConnection.connection.id, profile: { ...profileInput, name: "From web" }, enabled: false, expiresAt: controllerFixtureExpiry() } const webHeaders = { authorization: "Bearer " + jwt } assert.equal((await app.inject({ method: "POST", url: "/api/mcp/routing-profiles/plan", payload: webPayload })).statusCode, 401) const webPlanResponse = await app.inject({ method: "POST", url: "/api/mcp/routing-profiles/plan", headers: webHeaders, payload: webPayload }) assert.equal(webPlanResponse.statusCode, 200, webPlanResponse.body) const webPlanId = webPlanResponse.json().planId const otherJwt = await new SignJWT({ sub: "other-owner", apps: ["mm"], permissions: actor.permissions }).setProtectedHeader({ alg: "HS256" }).setIssuer(env.authIssuer).setExpirationTime("1h").sign(new TextEncoder().encode(env.jwtSecret)) assert.equal((await app.inject({ method: "POST", url: `/api/mcp/plans/${webPlanId}/apply`, headers: { authorization: "Bearer " + otherJwt }, payload: {} })).statusCode, 403) assert.equal((await app.inject({ method: "POST", url: `/api/mcp/plans/${webPlanId}/apply`, headers: webHeaders, payload: {} })).statusCode, 409) assert.equal((await app.inject({ method: "POST", url: `/api/mcp/plans/${webPlanId}/decision`, headers: webHeaders, payload: { approve: true } })).statusCode, 200) const webOperation = await app.inject({ method: "POST", url: `/api/mcp/plans/${webPlanId}/apply`, headers: webHeaders, payload: {} }) assert.equal(webOperation.statusCode, 200, webOperation.body) for (let index = 0; index < 100; index++) { state = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [webOperation.json().id])).rows[0].state if (!["pending", "running"].includes(state)) break await delay(30) } assert.equal(state, "applied") console.log("✓ Human profile UI uses scoped plans, requires approval and rejects another owner's execution") const orphanPlan = randomUUID(), orphanOperation = randomUUID() await pool.query("INSERT INTO mcp_plans SELECT $1,connection_id,operation_id,operation_version,policy_version,generation,payload,evidence,'executing',expires_at,approved_by,approved_at,created_at FROM mcp_plans WHERE id=$2", [orphanPlan, stale.planId]) await pool.query("INSERT INTO mcp_operations(id,plan_id,connection_id,idempotency_key,state,created_at) VALUES($1,$2,$3,'orphan','running',now()-interval '3 minutes')", [orphanOperation, orphanPlan, issued.connection.id]) const lease = await pool.connect() try { await lease.query("SELECT pg_advisory_lock(hashtextextended($1,0))", ["mcp-operation:" + orphanOperation]) await recoverMcpOperations() assert.equal((await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [orphanOperation])).rows[0].state, "running") } finally { await lease.query("SELECT pg_advisory_unlock(hashtextextended($1,0))", ["mcp-operation:" + orphanOperation]); lease.release() } await recoverMcpOperations() assert.equal((await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [orphanOperation])).rows[0].state, "unknown") console.log("✓ Recovery respects live runner leases and marks orphaned writes unknown") const recoveryChannelServer = createServer((_req, res) => { res.setHeader("content-type", "application/json"); res.end(JSON.stringify({ "system-id": "fixture-primary" })) }) await new Promise(resolve => recoveryChannelServer.listen(0, "127.0.0.2", resolve)) const restoreService = findMcpService("routing.community.restore")!, savedService = { ...restoreService } try { const channel = await registerManagementChannel({ serverId: 1, host: "127.0.0.2", port: (recoveryChannelServer.address() as { port: number }).port, useSsl: false, verifySsl: true, username: "fixture", password: "fixture", attestation: "Изолированный независимый канал для проверки заранее разрешённого восстановления", expiresAt: fixtureGrantExpiry(Date.now(), actor.expiresAt!) }, actor) const { routeAiProfileSchema } = await import("@mmapp/contracts/mcp-routing") const recoveryProfile = routeAiProfileSchema.parse({ ...profileInput, candidates: profileInput.candidates.map(candidate => ({ ...candidate, controlProbe: { targetInterface: "down", expectedHops: [{ serverId: 1, interface: "peer" }] } })), autoRecovery: true }), grantId = randomUUID() const activePrincipal = await authenticateMcpToken(rotated.token) const recoveryOriginalAuthority = await effectiveMcpConnection(activePrincipal) const recoveryFixtureExpiry = fixtureGrantExpiry(Date.now(), Date.parse(activePrincipal.credentialExpiresAt), Date.parse(recoveryOriginalAuthority.policy.expiresAt), recoveryOriginalAuthority.owner_valid_until.getTime()) await pool.query("INSERT INTO mcp_routing_profiles(id,connection_id,config,enabled,grant_expires_at,credential_id,generation,policy_version) VALUES($1,$2,$3::jsonb,true,$6,$4,$5,1)", [grantId, issued.connection.id, JSON.stringify(recoveryProfile), activePrincipal.credentialId, await (await import("./security.js")).accessGeneration(), recoveryFixtureExpiry]) const before = settings["rp-filter"] restoreService.current = async () => ({ value: settings["rp-filter"] }) restoreService.execute = async () => nativeClient.patch("/ip/settings", { "rp-filter": before }) restoreService.verify = async () => settings["rp-filter"] === before registerMcpService("fixture.confirmed-write-failed-probe", { current: async () => ({ value: settings["rp-filter"] }), execute: async () => nativeClient.patch("/ip/settings", { "rp-filter": "loose" }), verify: async () => false }) const prepared = await invokeMcpTool(activePrincipal, "mm_ip_settings_update_plan", { serverId: 1, fields: { "rp-filter": "loose" } }) as { planId: string } const sealed = (await pool.query("SELECT evidence FROM mcp_plans WHERE id=$1", [prepared.planId])).rows[0].evidence const evidence = JSON.parse(await decryptSecret(sealed.sealed)) evidence.steps = [{ kind: "service", service: "fixture.confirmed-write-failed-probe", input: {} }]; evidence.before = { value: before } evidence.managementChannels = [await verifyManagementChannel(1, channel.id)] evidence.recovery = { authorityProfileId: grantId, validUntil: new Date(Date.now() + 120000).toISOString(), expected: { value: "loose" }, step: { kind: "service", service: "routing.community.restore", input: { profile: recoveryProfile } } } await pool.query("UPDATE mcp_plans SET evidence=$2::jsonb WHERE id=$1", [prepared.planId, JSON.stringify({ sealed: await encryptSecret(JSON.stringify(evidence)) })]) await approvePlan(prepared.planId, actor, true) const writesBeforeLegacyWriter = writes const attempted = await applyPlan(activePrincipal, prepared.planId, "reserved-recovery") // This deliberately unsupported legacy profile has no authentic baseline. // Replacing an unavailable restore service cannot enroll a generic writer. // Genuine positive inverse coverage lives in the scoped production suite. assert.ok(["rejected", "partial", "unknown"].includes(await waitOperation(attempted.id)), "Legacy writer must reach a terminal refusal, never remain pending or time out") assert.equal(writes, writesBeforeLegacyWriter); assert.equal(settings["rp-filter"], before) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_operation_checkpoints WHERE operation_id=$1 AND configuration_sealed IS NOT NULL", [attempted.id])).rows[0].n, 0) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_recovery_intents WHERE operation_id=$1 AND state='verified'", [attempted.id])).rows[0].n, 0) const beforeReplay = writes await applyPlan(activePrincipal, prepared.planId, "reserved-recovery"); await delay(50); assert.equal(writes, beforeReplay) await pool.query("UPDATE mcp_routing_profiles SET enabled=false WHERE id=$1", [grantId]) const blockedPlan = await invokeMcpTool(activePrincipal, "mm_ip_settings_update_plan", { serverId: 1, fields: { "rp-filter": "loose" } }) as { planId: string } const blockedSealed = (await pool.query("SELECT evidence FROM mcp_plans WHERE id=$1", [blockedPlan.planId])).rows[0].evidence const blockedEvidence = JSON.parse(await decryptSecret(blockedSealed.sealed)); blockedEvidence.managementChannels = evidence.managementChannels; blockedEvidence.recovery = evidence.recovery await pool.query("UPDATE mcp_plans SET evidence=$2::jsonb WHERE id=$1", [blockedPlan.planId, JSON.stringify({ sealed: await encryptSecret(JSON.stringify(blockedEvidence)) })]) await approvePlan(blockedPlan.planId, actor, true) const writesBeforeRevokedGrant = writes await assert.rejects(applyPlan(activePrincipal, blockedPlan.planId, "revoked-recovery")) assert.equal(writes, writesBeforeRevokedGrant); assert.equal(settings["rp-filter"], before) console.log("✓ Unsupported legacy autoRecovery cannot enroll a generic native writer or verified recovery; replay and revoked grant produce no writes") } finally { Object.assign(restoreService, savedService); await new Promise(resolve => recoveryChannelServer.close(() => resolve())) } await pool.query("UPDATE mcp_connections SET owner_valid_until=now()-interval '1 second' WHERE id=$1", [limited.connection.id]) await assert.rejects(authenticateMcpToken(limited.token)) await revalidateMcpOwner(limited.connection.id, { ...actor, permissions: ["mm:mcp:read", "mm:servers:read"] }) const narrowed = await authenticateMcpToken(limited.token) await assert.rejects(invokeMcpTool(narrowed, "mm_ip_settings_list", { serverId: 1 })) console.log("✓ Owner claims expire; revalidation cannot preserve removed owner permissions") const portalOwner: AuthUser = { ...actor, id: "portal-revoked-owner", issuedAt: Date.now() - 60000 } const portalConnection = await createMcpConnection(input, portalOwner) const event = { eventId: randomUUID(), userId: portalOwner.id, type: "permissions.changed" } const timestamp = String(Math.floor(Date.now() / 1000)), raw = JSON.stringify(event) process.env.MCP_PORTAL_WEBHOOK_SECRET = "integration-only-portal-secret-32-characters" const signature = "sha256=" + createHmac("sha256", process.env.MCP_PORTAL_WEBHOOK_SECRET).update(timestamp + "." + raw).digest("hex") const sendEvent = (body = raw, stamp = timestamp, sig = signature) => app!.inject({ method: "POST", url: "/_internal/mcp/portal/revocations", headers: { "content-type": "application/json", "x-mm-timestamp": stamp, "x-mm-signature": sig }, payload: body }) assert.equal((await sendEvent(raw + " ")).statusCode, 401) assert.equal((await sendEvent(raw, String(Number(timestamp) - 600))).statusCode, 401) assert.equal((await sendEvent()).statusCode, 200) assert.equal((await sendEvent()).json().duplicate, true) await assert.rejects(authenticateMcpToken(portalConnection.token)) await assert.rejects(createMcpConnection(input, portalOwner)) await assert.rejects(revalidateMcpOwner(portalConnection.connection.id, portalOwner)) const oldPortalJwt = app.jwt.sign({ sub: portalOwner.id, apps: ["mm"], permissions: actor.permissions, iss: "https://auth.shnt.top", iat: Number(timestamp) - 60, exp: Number(timestamp) + 1800 }) assert.equal((await app.inject({ method: "GET", url: "/api/mcp/connections", headers: { authorization: "Bearer " + oldPortalJwt } })).statusCode, 401) const freshOwner = { ...portalOwner, issuedAt: (Number(timestamp) + 1) * 1000 } const freshAccess = await createMcpConnection(input, freshOwner) await authenticateMcpToken(freshAccess.token) assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_audit WHERE action='owner.revoke' AND payload->>'eventId'=$1", [event.eventId])).rows[0].n, 1) delete process.env.MCP_PORTAL_WEBHOOK_SECRET console.log("✓ Signed portal revocation: tamper/stale rejection, one atomic event, revoked credentials and stale-JWT regrant prevention") await setConnectionStatus(issued.connection.id, "paused", actor) await assert.rejects(authenticateMcpToken(rotated.token)) await setConnectionStatus(issued.connection.id, "active", actor) await invalidateMcpAccess() await assert.rejects(authenticateMcpToken(rotated.token)) console.log("✓ Immediate pause and external-generation invalidation") await (await import("./native-conservation-acceptance.js")).acceptNativeConservation(actor) await (await import("./native-create-id-acceptance.js")).acceptNativeCreateId(actor) } finally { await (await import("../../services/scheduler.js")).stopSchedulerForDatabaseOperation() await (await import("../../services/traffic-flow-ingest.js")).stopTrafficFlowListenerForDatabaseOperation() await app?.close() await new Promise((resolve, reject) => router.close(error => error ? reject(error) : resolve())) await closePool() }