143 lines
9.9 KiB
TypeScript
143 lines
9.9 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import { randomUUID } from "node:crypto"
|
|
import { mkdtemp, readFile, rm } from "node:fs/promises"
|
|
import { tmpdir } from "node:os"
|
|
import { basename, dirname, join, resolve } from "node:path"
|
|
import { test, type TestContext } from "node:test"
|
|
import ts from "typescript"
|
|
import { z } from "zod"
|
|
import { scopedTargetRuleMatches } from "./routing-community-scoped-model.js"
|
|
import * as security from "./security.js"
|
|
import type * as Baseline from "./routing-controller-baseline.js"
|
|
|
|
// Run the actual baseline implementation and durable HMAC on a temporary key.
|
|
// Only the SQL/RouterOS/authority adapters supply synthetic snapshots.
|
|
async function fixture(t: TestContext) {
|
|
const root = resolve(tmpdir()), prefix = "mcp-controller-baseline-test-"
|
|
const state = await mkdtemp(join(root, prefix)), previous = process.env.MCP_STATE_DIR
|
|
process.env.MCP_STATE_DIR = state
|
|
t.after(async () => {
|
|
if (previous === undefined) delete process.env.MCP_STATE_DIR
|
|
else process.env.MCP_STATE_DIR = previous
|
|
assert.equal(dirname(resolve(state)), root)
|
|
assert(basename(state).startsWith(prefix))
|
|
await rm(state, { recursive: true })
|
|
})
|
|
const { fingerprint, privateSecretFingerprint } = security
|
|
const now = Date.now(), profileId = randomUUID(), planId = randomUUID(), operationId = randomUUID(), connectionId = randomUUID()
|
|
const grant = new Date(now + 120000).toISOString()
|
|
const principal = { connectionId, credentialId: randomUUID(), credentialExpiresAt: grant }
|
|
const profile = { serverId: 1, table: "tenant", probeVrf: "tenant", candidates: ["a", "b"].map(id => ({ id, jumpHostId: 1, exitNodeId: 1 })) }
|
|
const permissions = ["mm:network:read", "mm:network:write", "mm:filters:write"]
|
|
const connection = { id: connectionId, owner_id: "owner", generation: await security.accessGeneration(),
|
|
owner_valid_until: new Date(grant), owner_claims_issued_at: new Date(now), owner_permissions: permissions,
|
|
policy: { status: "active", mode: "operator", expiresAt: grant, permissions,
|
|
scope: { serverIds: [1], operationIds: ["routing.analyze", "routing.community.switch", "routing.path.verify"] } } }
|
|
const context = { principal, connection } as Parameters<typeof Baseline.prepareRoutingControllerBaseline>[4]
|
|
const originalRule = 'if (bgp-communities includes 65000:10) {\n set gw 192.0.2.1;\n set gw-interface "marker-gre-a";\n accept;\n}'
|
|
const desiredRule = originalRule.replace("192.0.2.1", "192.0.2.9").replace("marker-gre-a", "marker-gre-b")
|
|
const bareRule = desiredRule.replace('"marker-gre-b"', "marker-gre-b")
|
|
const signTarget = await security.privateFingerprinter("baseline-test-native-target")
|
|
const stableTarget = { ".id": "*1", chain: "input", comment: "managed" }
|
|
let observed = { ...stableTarget, rule: originalRule }, candidate = "a", failAfterRead = false, driftSourceAtFence = false
|
|
const targetProof = (rule: string) => signTarget(fingerprint({ ...observed, rule }))
|
|
const desiredTargetProof = targetProof(desiredRule), dependencyProof = "d".repeat(64), checkpointProof = "c".repeat(64)
|
|
const sourceProofs = [{ serverId: 1, proof: "a".repeat(64) }]
|
|
const receiptId = randomUUID(), requestSeal = "request-proof", networkSeal = "network-proof"
|
|
const input = { proofRevision: 3, profile, principal, ownerId: connection.owner_id, nativeId: "*1", originalCandidateId: "a",
|
|
originalTargetProof: targetProof(originalRule), desiredTargetProof, dependencyProof, contextSources: sourceProofs, candidateId: "b",
|
|
persistentProfileAuthority: { packet: { profileId, profileVersion: 1 } }, receiptId }
|
|
const step = { kind: "service", service: "routing.community.scoped", input }
|
|
const configuration = { checkpointProof, observedAt: new Date(now-100).toISOString(), packet: { revision: 2, operationId, stepIndex: 0,
|
|
nativeId: "*1", originalCandidateId: "a", originalTargetProof: input.originalTargetProof, desiredCandidateId: "b", desiredTargetProof,
|
|
dependencyProof, contextSources: sourceProofs, desiredRule } }
|
|
const nativeVerificationSeal = await privateSecretFingerprint(fingerprint({ request: requestSeal, dependency: dependencyProof, desired: desiredTargetProof,
|
|
candidate: "b", configuration: checkpointProof, network: networkSeal }), "route-ai-scoped-verified-v3")
|
|
const row = { id: operationId, plan_id: planId, state: "applied", step_state: "verified", operation_id: "routing.community.switch",
|
|
operation_version: 7, approved_by: "owner", created_at: new Date(now-200), intended_state: security.redact(step),
|
|
evidence: { sealed: await security.encryptSecret(JSON.stringify({ steps: [step], target: { scope: "servers", serverIds: [1] } })) },
|
|
result: { nativeVerificationSeal, configurationCheckpointProof: checkpointProof, networkCheckpointProof: networkSeal, receiptId,
|
|
proofRevision: 3, nativeWrites: 1, configurationVerified: true, postProbeStatus: "passed", controlProbeVerified: true } }
|
|
const providers: Record<string, unknown> = {
|
|
"./routing-main-transition.js": {},
|
|
"../../db/index.js": { pool: { query: async (_sql: string, ids: string[]) => {
|
|
assert.deepEqual(ids, [operationId, planId, connectionId])
|
|
return { rows: [failAfterRead ? { ...row, step_state: "unknown" } : row] }
|
|
} } },
|
|
"./registry.js": { findMcpOperation: () => ({ version: 7 }) },
|
|
"./routing-community-scoped-request.js": { assertScopedCommunityRequest: async () => requestSeal },
|
|
"./routing-profile-authority.js": { assertPersistentScopedProfileAuthority: async () => {},
|
|
routingProfileAuthorityBindingSchema: { parse: (value: unknown) => value } },
|
|
"./routing-community-scoped-checkpoint.js": { loadScopedConfigurationPacket: async () => configuration,
|
|
loadScopedNetworkCheckpoint: async () => ({ status: "passed", networkSeal }) },
|
|
"./routing-original-effects.js": {},
|
|
zod: { z }, "@mmapp/contracts/mcp-routing": { routeAiProfileSchema: { parse: (value: unknown) => value } },
|
|
"../../lib/permissions.js": { hasPermission: (set: string[], permission: string) => set.includes(permission) },
|
|
"./store.js": { effectiveMcpConnection: async () => connection },
|
|
"./security.js": security,
|
|
"./routing-community-context.js": { readRouteCommunityContext: async (_profile: unknown, options: { authorize(): Promise<void> }) => {
|
|
await options.authorize()
|
|
const target = { nativeId: observed[".id"], rawRow: observed, desiredTargetProof: targetProof }
|
|
return { before: { nativeRuleId: target.nativeId, scopedExecutionAvailable: true, currentCandidateId: candidate,
|
|
targetRuleProof: targetProof(observed.rule), dependencyProof, sourceProofs },
|
|
ownedTarget: { ...target, matchesRuleProof: (body: string, proof: unknown) => scopedTargetRuleMatches(target, body, proof) },
|
|
fence: async () => { await options.authorize(); if (driftSourceAtFence) failAfterRead = true } }
|
|
} },
|
|
}
|
|
const source = await readFile(new URL("./routing-controller-baseline.ts", import.meta.url), "utf8")
|
|
const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText
|
|
const baseline = {} as typeof Baseline
|
|
new Function("exports", "require", compiled)(baseline, (name: string) => { assert(providers[name], name); return providers[name] })
|
|
const original = await baseline.prepareRoutingControllerBaseline(profileId, 1, profile, grant, context)
|
|
const links = { planId, operationId, checkpointProof }
|
|
return { original, links, desiredTargetProof, targetProof, originalRule, desiredRule, bareRule, baseline,
|
|
advance: () => baseline.advanceRoutingControllerBaseline(original, profileId, 1, profile, grant, context, planId, operationId),
|
|
assertCurrent: (value: unknown) => baseline.assertRoutingControllerBaseline(value, profileId, 1, profile, grant, context),
|
|
driftAfterNativeRead: () => { driftSourceAtFence = true },
|
|
observe: (rule: string, metadata: Partial<typeof stableTarget> = {}, selected = "b") => { candidate = selected; observed = { ...stableTarget, ...metadata, rule } } }
|
|
}
|
|
|
|
test("controller signs an exact legacy-shaped next baseline when native target proof matches", async t => {
|
|
const f = await fixture(t)
|
|
await f.assertCurrent(f.original)
|
|
f.observe(f.desiredRule)
|
|
const next = await f.advance()
|
|
assert.equal(next.packet.targetProof, f.desiredTargetProof)
|
|
assert.deepEqual(next.packet.transition, f.links)
|
|
assert.equal(next.packet.previousProof, f.original.proof)
|
|
assert.deepEqual(await f.assertCurrent(next), next)
|
|
})
|
|
|
|
test("controller authenticates the source but enrolls the exact bare RouterOS readback", async t => {
|
|
const f = await fixture(t)
|
|
f.observe(f.bareRule)
|
|
const next = await f.advance()
|
|
assert.equal(next.packet.targetProof, f.targetProof(f.bareRule))
|
|
assert.notEqual(next.packet.targetProof, f.desiredTargetProof)
|
|
assert.deepEqual(next.packet.transition, { ...f.links, sourceDesiredTargetProof: f.desiredTargetProof })
|
|
assert.deepEqual(await f.assertCurrent(next), next)
|
|
const tampered = structuredClone(next)
|
|
tampered.packet.transition!.sourceDesiredTargetProof = "e".repeat(64)
|
|
await assert.rejects(f.baseline.routingControllerBaselinePrincipal(tampered), { code: "STATE_CONFLICT" })
|
|
f.observe(f.desiredRule)
|
|
await assert.rejects(f.assertCurrent(next), { code: "STATE_CONFLICT" })
|
|
f.observe(f.originalRule, {}, "a")
|
|
await assert.rejects(f.assertCurrent(next), { code: "STATE_CONFLICT" })
|
|
await f.assertCurrent(f.original)
|
|
})
|
|
|
|
test("controller refuses changed selected rule and drift in other native row fields", async t => {
|
|
const f = await fixture(t)
|
|
f.observe(f.bareRule.replace("marker-gre-b", "other"))
|
|
await assert.rejects(f.advance(), { code: "STATE_CONFLICT" })
|
|
f.observe(f.bareRule, { comment: "changed" })
|
|
await assert.rejects(f.advance(), { code: "STATE_CONFLICT" })
|
|
})
|
|
|
|
test("controller refuses baseline publication if the source operation changes after readback", async t => {
|
|
const f = await fixture(t)
|
|
f.observe(f.bareRule)
|
|
f.driftAfterNativeRead()
|
|
await assert.rejects(f.advance(), { code: "STATE_CONFLICT" })
|
|
})
|