55 lines
6.2 KiB
TypeScript
55 lines
6.2 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import { pool } from "../../db/index.js"
|
|
import { generateWireGuardKeyPair } from "../../services/wg-keys.js"
|
|
import { readWireguardInterfaceDeleteState } from "../../services/wireguard-interface-delete.js"
|
|
import { readRosSnapshot, routerClient } from "./resources.js"
|
|
import { invokeMcpTool } from "./runtime.js"
|
|
import type { McpPrincipal } from "./store.js"
|
|
|
|
/** Explicit cascade on an owned disabled interface, two disabled peers and disabled v4/v6 addresses. */
|
|
export async function acceptChrWireguardInterface(marker: string, principal: McpPrincipal, execute: (tool: string, args: Record<string, unknown>) => Promise<string>, checks: Record<string, unknown>[], checkpoint: () => Promise<void>) {
|
|
assert.match(marker, /^mcp-test-[a-f0-9]{8}$/)
|
|
const serverId = 1, name = marker + "-wg-cascade", keys = [generateWireGuardKeyPair().publicKey, generateWireGuardKeyPair().publicKey]
|
|
const { server } = await routerClient(serverId), privateBaseline = await readWireguardInterfaceDeleteState(server, "*0")
|
|
const resources = ["wireguard", "wireguard_peer", "ipv4_address", "ipv6_address"]
|
|
const baselines = await Promise.all(resources.map(resource => readRosSnapshot(serverId, resource)))
|
|
assert.ok(baselines.every(snapshot => !snapshot.rows.some(row => row.name === name || row.comment === marker)))
|
|
checks.push({ phase: "wg-interface-preflight", baselineFingerprints: baselines.map(({ serverId, resource, fingerprint }) => ({ serverId, resource, fingerprint })), privateProofBaseline: privateBaseline.proof }); await checkpoint()
|
|
let cascadeStarted = false
|
|
try {
|
|
await execute("mm_wireguard_create_plan", { serverId, fields: { name, disabled: true, comment: marker } })
|
|
const own = (await readRosSnapshot(serverId, "wireguard")).rows.filter(row => row.name === name && row.comment === marker)
|
|
assert.equal(own.length, 1); assert.equal(own[0].disabled, "true")
|
|
const id = String(own[0][".id"])
|
|
for (const [index, publicKey] of keys.entries()) await execute("mm_wireguard_peer_create_plan", { serverId, fields: { interface: name, "public-key": publicKey, "allowed-address": index ? "2001:db8:243::2/128" : "198.19.243.2/32", disabled: true, comment: marker } })
|
|
await execute("mm_ipv4_address_create_plan", { serverId, fields: { address: "198.19.243.1/30", interface: name, disabled: true, comment: marker } })
|
|
await execute("mm_ipv6_address_create_plan", { serverId, fields: { address: "2001:db8:243::1/64", interface: name, disabled: true, advertise: false, comment: marker } })
|
|
await assert.rejects(invokeMcpTool(principal, "mm_wireguard_delete_plan", { serverId, id }), (error: { code?: string }) => error.code === "DEPENDENCY_CONFLICT")
|
|
checks.push({ phase: "wg-interface-owned-fixture", interfaceId: id, interfaceDisabled: true, peersDisabled: true, addressesDisabled: true, peers: 2, staticAddresses: 2, nativeAliasCascadeRefused: true }); await checkpoint()
|
|
cascadeStarted = true
|
|
const operationId = await execute("mm_wireguard_interface_remove_plan", { serverId, interfaceId: id })
|
|
const receipt = (await pool.query("SELECT result FROM mcp_operation_steps WHERE operation_id=$1 AND step_index=0", [operationId])).rows[0].result
|
|
assert.equal(receipt.verified, true); assert.equal(receipt.commandCount, 5); assert.equal(receipt.completedCommands, 5)
|
|
const reconciliation = await invokeMcpTool(principal, "mm_operation_reconcile", { operationId }) as { automaticRetry: boolean; results: { matchesDesiredState: boolean; matchesExpectedPrefixState: boolean; verifiedPrefixCommands: number }[] }
|
|
assert.equal(reconciliation.automaticRetry, false); assert.equal(reconciliation.results[0].matchesDesiredState, true); assert.equal(reconciliation.results[0].matchesExpectedPrefixState, true); assert.equal(reconciliation.results[0].verifiedPrefixCommands, 5)
|
|
const intents = (await pool.query("SELECT count(*)::int AS n FROM mcp_audit WHERE connection_id=$1 AND action='configuration.intent' AND payload->>'method'='DELETE'", [principal.connectionId])).rows[0].n
|
|
assert.equal(intents, 5)
|
|
checks.push({ phase: "wg-interface-cascade-verified", operationId, commandCount: receipt.commandCount, completedCommands: receipt.completedCommands, nativeDeleteIntents: intents, perCommandReadBack: true, readOnlyReconciliation: true, revisionRecorded: receipt.revisionRecorded, forwardingVerified: false }); await checkpoint()
|
|
} finally {
|
|
const target = (await readRosSnapshot(serverId, "wireguard")).rows.filter(row => row.name === name && row.comment === marker)
|
|
const trace = checks.filter(row => row.phase === "mcp-operation" && row.tool === "mm_wireguard_interface_remove_plan").at(-1)
|
|
if (target.length && cascadeStarted && trace?.state !== "rejected" && !["prepare", "approval"].includes(String(trace?.stage))) {
|
|
checks.push({ phase: "wg-interface-cleanup-unconfirmed", marker, ownedDisabledFixtureRemaining: true, uncertainCascadeNotReplayed: true }); await checkpoint()
|
|
throw new Error("Owned WireGuard cascade requires read-only reconciliation; native commands will not be repeated")
|
|
}
|
|
if (target.length) {
|
|
for (const row of (await readRosSnapshot(serverId, "wireguard_peer")).rows.filter(row => row.interface === name && row.comment === marker && keys.includes(String(row["public-key"])))) await execute("mm_wireguard_peer_delete_plan", { serverId, id: row[".id"] })
|
|
for (const [resource, address] of [["ipv4_address", "198.19.243.1/30"], ["ipv6_address", "2001:db8:243::1/64"]]) for (const row of (await readRosSnapshot(serverId, resource)).rows.filter(row => row.interface === name && row.comment === marker && row.address === address)) await execute(`mm_${resource}_delete_plan`, { serverId, id: row[".id"] })
|
|
for (const row of target) await execute("mm_wireguard_delete_plan", { serverId, id: row[".id"] })
|
|
}
|
|
for (const baseline of baselines) assert.equal((await readRosSnapshot(serverId, baseline.resource)).fingerprint, baseline.fingerprint)
|
|
assert.equal((await readWireguardInterfaceDeleteState(server, "*0")).proof, privateBaseline.proof)
|
|
checks.push({ phase: "wg-interface-baseline-restored", fingerprints: baselines.length, privateProofRestored: true, staticConfigurationRestored: true, forwardingVerified: false }); await checkpoint()
|
|
}
|
|
}
|