feat(mcp): подготовить сервер и доступ к приёмке
This commit is contained in:
commit
bf3d50e1ff
3835 files changed
+537122
No files matched your search
@@ -0,0 +1,197 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { randomUUID } from "node:crypto"
|
||||
import { basename, dirname, resolve } from "node:path"
|
||||
import { execFile } from "node:child_process"
|
||||
import { writeFile } from "node:fs/promises"
|
||||
|
||||
assert.equal(process.env.MMAPP_OWN_DATABASE_SCHEMA_CONTROL, "true")
|
||||
const databaseUrl = new URL(process.env.DATABASE_URL ?? "")
|
||||
assert.equal(databaseUrl.hostname, "127.0.0.1"); assert.equal(databaseUrl.pathname, "/postgres")
|
||||
assert(process.env.MCP_STATE_DIR && basename(dirname(resolve(process.env.MCP_STATE_DIR))).startsWith("mm-mcp-test-"))
|
||||
const { Pool } = await import("pg")
|
||||
const { pool, closePool } = await import("./index.js")
|
||||
const { applySqlMigrations } = await import("./migrate.js")
|
||||
const { acquireDatabaseSchemaControl } = await import("./database-schema-control.js")
|
||||
const { initialDatabaseSchemaControlState, DatabaseSchemaControlError } = await import("./database-schema-control-model.js")
|
||||
const { runPrivateDatabaseDump } = await import("../services/database-dump-connection.js")
|
||||
const { restorePartitionedDatabase } = await import("../services/system-database-partition-restore.js")
|
||||
type ControlPool = Parameters<typeof acquireDatabaseSchemaControl>[0]
|
||||
const names: string[] = [], cases: string[] = [], handles: Awaited<ReturnType<typeof acquireDatabaseSchemaControl>>[] = []
|
||||
const connections: InstanceType<typeof Pool>[] = []
|
||||
async function database() {
|
||||
const name = "mm_control_" + randomUUID().replaceAll("-", "")
|
||||
assert(/^mm_control_[a-f0-9]{32}$/.test(name))
|
||||
await pool.query('CREATE DATABASE "' + name + '" TEMPLATE template0')
|
||||
const url = new URL(databaseUrl); url.pathname = "/" + name
|
||||
const connection = new Pool({ connectionString: url.toString(), max: 6 })
|
||||
names.push(name); connections.push(connection); return connection
|
||||
}
|
||||
async function seed(connection: InstanceType<typeof Pool>) {
|
||||
await connection.query("CREATE SCHEMA mmapp_control; CREATE TABLE mmapp_control.database_schema_state(id smallint PRIMARY KEY CHECK(id=1),revision bigint NOT NULL CHECK(revision>=0),state jsonb NOT NULL); CREATE TABLE mmapp_control.database_schema_intents(id uuid PRIMARY KEY,intent jsonb NOT NULL,terminal jsonb)")
|
||||
await connection.query("INSERT INTO mmapp_control.database_schema_state VALUES(1,0,$1::jsonb)", [JSON.stringify(initialDatabaseSchemaControlState())])
|
||||
}
|
||||
async function acquire(connection: ControlPool) { const owner = await acquireDatabaseSchemaControl(connection); handles.push(owner); return owner }
|
||||
const command = () => ({ type: "begin" as const, id: randomUUID(), kind: "migration" as const, manifestSha256: "a".repeat(64), artifactSha256: "b".repeat(64) })
|
||||
const terminal = (id: string, outcome: "observed-success" | "observed-failure" | "interrupted-unknown" = "observed-success") => ({ type: "terminal" as const, id, outcome, observationSha256: "c".repeat(64) })
|
||||
const row = async (connection: InstanceType<typeof Pool>) => (await connection.query("SELECT revision::text,state FROM mmapp_control.database_schema_state WHERE id=1")).rows[0]
|
||||
const history = async (connection: InstanceType<typeof Pool>, id: string) => (await connection.query("SELECT intent,terminal FROM mmapp_control.database_schema_intents WHERE id=$1", [id])).rows[0]
|
||||
function lostCommit(connection: InstanceType<typeof Pool>, number: number): ControlPool {
|
||||
return { async connect() {
|
||||
const client = await connection.connect(); let commits = 0
|
||||
return { release(destroy?: boolean) { client.release(destroy) }, async query(sql: string, values?: unknown[]) {
|
||||
const result = await client.query(sql, values)
|
||||
if (sql === "COMMIT" && ++commits === number) throw Error("owned fixture lost actual COMMIT acknowledgement")
|
||||
return { rows: result.rows, command: result.command }
|
||||
} }
|
||||
} }
|
||||
}
|
||||
function abortedCommit(connection: InstanceType<typeof Pool>, number: number): ControlPool {
|
||||
return { async connect() {
|
||||
const client = await connection.connect(); let commits = 0
|
||||
return { release(destroy?: boolean) { client.release(destroy) }, async query(sql: string, values?: unknown[]) {
|
||||
if (sql === "COMMIT" && ++commits === number) {
|
||||
// A real aborted PostgreSQL transaction acknowledges COMMIT as ROLLBACK.
|
||||
await assert.rejects(client.query("SELECT 1/0"))
|
||||
}
|
||||
const result = await client.query(sql, values)
|
||||
return { rows: result.rows, command: result.command }
|
||||
} }
|
||||
} }
|
||||
}
|
||||
async function commandIo(program: string, args: string[]): Promise<Buffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(program, args, { encoding: "buffer", timeout: 30000, maxBuffer: 32 * 1048576, windowsHide: true }, (error, stdout) => error ? reject(Error("Owned PostgreSQL archive command failed: " + program)) : resolve(stdout))
|
||||
})
|
||||
}
|
||||
try {
|
||||
// The runner's separate terminal restore fixture uses this owned database.
|
||||
// Control protocol scenarios below each use their own independent database.
|
||||
await applySqlMigrations(pool)
|
||||
const missing = await database()
|
||||
await assert.rejects(acquire(missing), DatabaseSchemaControlError)
|
||||
assert.equal((await missing.query("SELECT count(*)::int AS n FROM pg_namespace WHERE nspname='mmapp_control'")).rows[0].n, 0)
|
||||
cases.push("missing external control refuses without startup DDL or enrollment")
|
||||
|
||||
const regular = await database(); await seed(regular)
|
||||
let owner = await acquire(regular)
|
||||
const initial = await owner.read(); assert.equal(initial.state.epoch, 1); assert.equal(initial.revision, "1")
|
||||
assert.equal((await owner.admission()).ready, false)
|
||||
await assert.rejects(acquire(regular), DatabaseSchemaControlError)
|
||||
assert.deepEqual(await owner.read(), initial)
|
||||
cases.push("one checked-out session owns exclusive lease; another owner changes no epoch or revision")
|
||||
const begin = command(), intended = await owner.transition(begin)
|
||||
assert.equal(intended.revision, "2"); assert.equal(intended.state.phase, "INTENT")
|
||||
assert.deepEqual((await history(regular, begin.id)).intent, intended.state.intent)
|
||||
assert.equal((await owner.admission()).reason, "UNRESOLVED_INTENT")
|
||||
cases.push("intent singleton and unique immutable history publish atomically before application dispatch")
|
||||
await owner.close(); owner = await acquire(regular)
|
||||
assert.equal((await owner.read()).state.intent?.id, begin.id)
|
||||
await assert.rejects(owner.transition(terminal(begin.id)), DatabaseSchemaControlError)
|
||||
assert.equal((await history(regular, begin.id)).terminal, null)
|
||||
cases.push("restart preserves old intent and cannot claim an unobserved successful outcome")
|
||||
owner = await acquire(regular)
|
||||
const interrupted = await owner.transition(terminal(begin.id, "interrupted-unknown"))
|
||||
assert.equal(interrupted.state.terminal?.outcome, "interrupted-unknown")
|
||||
assert.equal((await owner.admission()).ready, false)
|
||||
assert.deepEqual((await history(regular, begin.id)).terminal, interrupted.state.terminal)
|
||||
cases.push("interrupted unknown can be recorded once but never enables consumers")
|
||||
const second = command(); await owner.transition(second); await owner.transition(terminal(second.id))
|
||||
const prior = await row(regular)
|
||||
await assert.rejects(owner.transition(begin), DatabaseSchemaControlError)
|
||||
assert.deepEqual(await row(regular), prior)
|
||||
assert.equal((await regular.query("SELECT count(*)::int AS n FROM mmapp_control.database_schema_intents")).rows[0].n, 2)
|
||||
cases.push("reuse of an older completed operation is refused by actual unique history with no singleton advance")
|
||||
|
||||
for (const stage of ["claim", "intent", "terminal"] as const) {
|
||||
const connection = await database(); await seed(connection)
|
||||
const wrapped = lostCommit(connection, stage === "claim" ? 1 : stage === "intent" ? 2 : 3)
|
||||
const intent = command()
|
||||
if (stage === "claim") await assert.rejects(acquire(wrapped), (error: unknown) => error instanceof DatabaseSchemaControlError && error.outcomeUnknown)
|
||||
else {
|
||||
const handle = await acquire(wrapped)
|
||||
if (stage === "terminal") await handle.transition(intent)
|
||||
await assert.rejects(handle.transition(stage === "intent" ? intent : terminal(intent.id)), (error: unknown) => error instanceof DatabaseSchemaControlError && error.outcomeUnknown)
|
||||
await assert.rejects(handle.read(), DatabaseSchemaControlError)
|
||||
}
|
||||
const fresh = await acquire(connection), observed = await fresh.read()
|
||||
assert.equal(observed.state.epoch, 2); assert.equal(observed.state.phase, "QUARANTINED")
|
||||
assert.equal((await fresh.admission()).ready, false)
|
||||
if (stage === "intent") { assert.equal(observed.state.intent?.id, intent.id); assert.equal((await history(connection, intent.id)).terminal, null) }
|
||||
if (stage === "terminal") { assert.equal(observed.state.intent, null); assert.equal(observed.state.terminal?.intent.id, intent.id); assert.equal(observed.state.terminal?.outcome, "observed-success") }
|
||||
await fresh.close()
|
||||
cases.push("actual server COMMIT with lost " + stage + " ACK destroys owner; restart preserves quarantine and durable observation")
|
||||
}
|
||||
for (const stage of ["claim", "intent", "terminal"] as const) {
|
||||
const connection = await database(); await seed(connection)
|
||||
const wrapped = abortedCommit(connection, stage === "claim" ? 1 : stage === "intent" ? 2 : 3)
|
||||
const intent = command()
|
||||
if (stage === "claim") await assert.rejects(acquire(wrapped), (error: unknown) => error instanceof DatabaseSchemaControlError && error.outcomeUnknown)
|
||||
else {
|
||||
const handle = await acquire(wrapped)
|
||||
if (stage === "terminal") await handle.transition(intent)
|
||||
await assert.rejects(handle.transition(stage === "intent" ? intent : terminal(intent.id)), (error: unknown) => error instanceof DatabaseSchemaControlError && error.outcomeUnknown)
|
||||
await assert.rejects(handle.read(), DatabaseSchemaControlError)
|
||||
}
|
||||
// Readback diagnoses the rolled-back publication without replaying it.
|
||||
const saved = await row(connection)
|
||||
assert.equal(saved.revision, stage === "claim" ? "0" : stage === "intent" ? "1" : "2")
|
||||
assert.equal((await history(connection, intent.id))?.terminal ?? null, null)
|
||||
assert.equal(saved.state.intent?.id ?? null, stage === "terminal" ? intent.id : null)
|
||||
const fresh = await acquire(connection)
|
||||
assert.equal((await fresh.admission()).ready, false)
|
||||
if (stage === "terminal") await assert.rejects(fresh.transition(terminal(intent.id)), DatabaseSchemaControlError)
|
||||
await fresh.close()
|
||||
cases.push("actual aborted " + stage + " transaction returns ROLLBACK command; owner discarded without successful publication or replay")
|
||||
}
|
||||
const altered = await database(); await seed(altered)
|
||||
const stale = await acquire(altered)
|
||||
await altered.query("UPDATE mmapp_control.database_schema_state SET revision=revision+1")
|
||||
await assert.rejects(stale.read(), DatabaseSchemaControlError)
|
||||
cases.push("external revision drift invalidates a live owner before admission")
|
||||
const corrupted = await database(); await seed(corrupted)
|
||||
const corruptOwner = await acquire(corrupted), corruptIntent = command(); await corruptOwner.transition(corruptIntent)
|
||||
await corrupted.query("UPDATE mmapp_control.database_schema_intents SET intent=jsonb_set(intent,'{artifactSha256}',to_jsonb($1::text)) WHERE id=$2", ["d".repeat(64), corruptIntent.id])
|
||||
await assert.rejects(corruptOwner.read(), DatabaseSchemaControlError)
|
||||
await assert.rejects(acquire(corrupted), DatabaseSchemaControlError)
|
||||
cases.push("immutable history mismatch invalidates both current owner and new owner claim")
|
||||
const reset = await database(); await seed(reset)
|
||||
const resetOwner = await acquire(reset), resetIntent = command(); await resetOwner.transition(resetIntent)
|
||||
// This database was created by this test in the owned disposable cluster.
|
||||
await reset.query("CREATE TABLE public.owned_fixture(id integer); DROP SCHEMA public CASCADE; CREATE SCHEMA public")
|
||||
assert.equal((await resetOwner.read()).state.intent?.id, resetIntent.id)
|
||||
assert.equal((await history(reset, resetIntent.id)).terminal, null)
|
||||
cases.push("control epoch and unresolved history survive replacement of the public application namespace")
|
||||
const archiveDb = await database(); await seed(archiveDb)
|
||||
await archiveDb.query("CREATE TABLE public.servers(id integer PRIMARY KEY); CREATE TABLE public.schema_migrations(id text PRIMARY KEY); INSERT INTO public.servers VALUES(1)")
|
||||
const archiveOwner = await acquire(archiveDb), archiveIntent = command(); await archiveOwner.transition(archiveIntent)
|
||||
const originalState = await row(archiveDb), archiveUrl = new URL(databaseUrl); archiveUrl.pathname = "/" + names.at(-1)!
|
||||
const safeFile = resolve(process.env.MCP_STATE_DIR!, "control-excluded.dump"), unsafeFile = resolve(process.env.MCP_STATE_DIR!, "control-full.dump")
|
||||
const actualPrivateDump = await runPrivateDatabaseDump(archiveUrl.toString(), { authorize: async () => {} })
|
||||
await writeFile(safeFile, actualPrivateDump, { flag: "wx", mode: 0o600 })
|
||||
const toc = (await commandIo("pg_restore", ["--list", safeFile])).toString("utf8")
|
||||
assert(!toc.includes("mmapp_control"))
|
||||
await archiveDb.query("UPDATE public.servers SET id=2")
|
||||
let replacements = 0
|
||||
await restorePartitionedDatabase(safeFile, archiveUrl.toString(), {
|
||||
command: commandIo, prepareSql: file => writeFile(file, "", { flag: "wx", mode: 0o600 }), beforeTargetReplacement: () => { replacements++ },
|
||||
})
|
||||
assert.equal(replacements, 1)
|
||||
assert.deepEqual((await archiveDb.query("SELECT id FROM public.servers")).rows, [{ id: 1 }])
|
||||
assert.deepEqual(await row(archiveDb), originalState)
|
||||
assert.equal((await archiveOwner.read()).state.intent?.id, archiveIntent.id)
|
||||
cases.push("actual private pg_dump excludes control; actual application restore changes public data while preserving live owner epoch and intent")
|
||||
await commandIo("pg_dump", ["-Fc", "--file", unsafeFile, archiveUrl.toString()])
|
||||
let archiveCommands = 0, preparations = 0
|
||||
await assert.rejects(restorePartitionedDatabase(unsafeFile, archiveUrl.toString(), {
|
||||
command: async (program, args) => { archiveCommands++; return commandIo(program, args) },
|
||||
prepareSql: async () => { preparations++ }, beforeTargetReplacement: () => { replacements++ },
|
||||
}))
|
||||
assert.equal(archiveCommands, 1); assert.equal(preparations, 0); assert.equal(replacements, 1)
|
||||
assert.deepEqual(await row(archiveDb), originalState)
|
||||
cases.push("actual full archive containing control refuses immediately after TOC before SQL decode or target replacement")
|
||||
console.log("Owned database schema control SQL:", JSON.stringify({ cases: cases.length, names: cases, databases: names.length, actualSql: true, lostActualCommitAcks: 3, startupWired: false, readyAuthorized: false, productionDatabaseTouched: false }))
|
||||
} finally {
|
||||
for (const handle of handles) await handle.close().catch(() => {})
|
||||
for (const connection of connections) await connection.end()
|
||||
await closePool()
|
||||
}
|
||||
Reference in new issue
Block a user