feat(mcp): подготовить сервер и доступ к приёмке

This commit is contained in:
Denozordec committed 2026-11-03 03:12:03 +07:00
commit bf3d50e1ff
3835 files changed
+537122

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
import { createHash, createPublicKey, verify } from "node:crypto"
import { constants } from "node:fs"
import { lstat, open, realpath } from "node:fs/promises"
import path from "node:path"
import { z } from "zod"
import { assertDatabaseSourceObservationCurrent, type DatabaseSourceObservation, type DatabaseSourceObservationOptions } from "./database-source-observation.js"
import type { ProbeStorageSql } from "../services/server-probe-journal-maintenance.js"
// Public verification only. No private key, signing, enrollment, SQL mutation,
// configured pool, provisioning or automatic current-head advancement here.
export class DatabaseSourceBindingError extends Error { readonly code = "DATABASE_SOURCE_BINDING_REFUSED" }
const fail = (): never => { throw new DatabaseSourceBindingError("Доверенная подпись и текущая привязка источника БД не подтверждены") }
const sha = z.string().regex(/^[a-f0-9]{64}$/)
const uuid = z.string().uuid()
const text = z.string().min(1).max(200).regex(/^[^\x00-\x1f\x7f]+$/)
const source = z.object({ systemIdentifier: z.string().regex(/^[1-9][0-9]{0,19}$/).refine(value => BigInt(value) <= 18446744073709551615n),
databaseOid: z.number().int().positive().max(4294967295), databaseName: text, sessionUser: text, currentUser: text,
inRecovery: z.literal(false), serverVersionNum: z.number().int().min(180000).max(189999) }).strict()
export const sourceBindingBodySchema = z.object({ format: z.literal("database-source-binding-v1"), installationId: uuid,
sourceIdentityId: uuid, generation: uuid, ordinal: z.number().int().positive().max(Number.MAX_SAFE_INTEGER), source }).strict()
export const sourceRegistryHeadSchema = z.object({ format: z.literal("database-source-registry-head-v1"), installationId: uuid,
sourceIdentityId: uuid, generation: uuid, ordinal: z.number().int().positive().max(Number.MAX_SAFE_INTEGER), bindingSha256: sha,
previousHeadSha256: sha.nullable() }).strict().refine(value => (value.ordinal === 1) === (value.previousHeadSha256 === null))
const deployment = z.object({ registryDirectory: z.string().min(1).max(4096), installationId: uuid, publicKeySpkiSha256: sha, currentHeadSha256: sha }).strict()
const envelope = z.object({ body: z.unknown(), signature: z.string().regex(/^[A-Za-z0-9_-]{86}$/) }).strict()
export type DatabaseSourceBindingBody = z.infer<typeof sourceBindingBodySchema>
export type DatabaseSourceRegistryHead = z.infer<typeof sourceRegistryHeadSchema>
export type DatabaseSourceDeploymentPin = z.infer<typeof deployment>
export const DATABASE_SOURCE_SIGNATURE_DOMAINS = Object.freeze({ binding: "mmapp:database-source-binding:ed25519:v1\n", head: "mmapp:database-source-head:ed25519:v1\n" })
export function sourceBindingCanonical(value: unknown): string {
return JSON.stringify(value, (_key, item: unknown) => item && typeof item === "object" && !Array.isArray(item)
? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) : item)
}
export const sourceBindingSha256 = (bytes: Uint8Array | string) => createHash("sha256").update(bytes).digest("hex")
export const sourceBindingBytes = (value: unknown) => Buffer.from(sourceBindingCanonical(value) + "\n", "utf8")
function parseExact(bytes: Buffer) {
const value: unknown = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes))
// Exact canonical bytes also reject duplicate JSON keys, alternate numeric
// spellings, trailing content and ambiguous/noncanonical Unicode encodings.
if (!sourceBindingBytes(value).equals(bytes)) fail()
return envelope.parse(value)
}
async function boundedFile(filename: string, maximum = 8192): Promise<Buffer> {
const before = await lstat(filename)
if (!before.isFile() || before.isSymbolicLink() || before.size < 1 || before.size > maximum) fail()
const handle = await open(filename, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0))
try {
const stat = await handle.stat()
if (!stat.isFile() || stat.dev !== before.dev || stat.ino !== before.ino || stat.size !== before.size) fail()
const bytes = Buffer.alloc(stat.size + 1); let length = 0
while (length < bytes.length) { const got = await handle.read(bytes, length, bytes.length - length, length); if (!got.bytesRead) break; length += got.bytesRead }
const after = await handle.stat()
if (length !== stat.size || after.size !== stat.size || after.mtimeMs !== stat.mtimeMs || after.ctimeMs !== stat.ctimeMs) fail()
return bytes.subarray(0, length)
} finally { await handle.close() }
}
function fence(options: DatabaseSourceObservationOptions) {
if (!Number.isSafeInteger(options.deadlineAt) || options.deadlineAt <= Date.now() || options.signal?.aborted) fail()
}
/** Input is privileged deployment configuration, never request JSON. Actual
* protection/ACL and current-process invalidation are separate deployment gates.
* This deliberately grants signature/source evidence only, NOT generation/READY. */
export async function readDatabaseSourceBindingRegistry(raw: DatabaseSourceDeploymentPin) {
try {
const cfg = deployment.parse(raw), directory = path.resolve(cfg.registryDirectory)
if (!path.isAbsolute(cfg.registryDirectory) || directory !== await realpath(directory)) fail()
const stat = await lstat(directory); if (!stat.isDirectory() || stat.isSymbolicLink()) fail()
const publicBytes = await boundedFile(path.join(directory, "operator-public.pem"), 4096)
if (publicBytes.toString("ascii").includes("PRIVATE KEY")) fail()
const publicKey = createPublicKey(publicBytes)
if (publicKey.type !== "public" || publicKey.asymmetricKeyType !== "ed25519") fail()
const spki = publicKey.export({ type: "spki", format: "der" })
if (sourceBindingSha256(spki) !== cfg.publicKeySpkiSha256) fail()
const headBytes = await boundedFile(path.join(directory, "head-" + cfg.currentHeadSha256 + ".json"))
if (sourceBindingSha256(headBytes) !== cfg.currentHeadSha256) fail()
const signedHead = parseExact(headBytes), head = sourceRegistryHeadSchema.parse(signedHead.body)
const check = (domain: string, body: unknown, signature: string) => verify(null, Buffer.from(domain + sourceBindingCanonical(body)), publicKey, Buffer.from(signature, "base64url"))
if (head.installationId !== cfg.installationId || !check(DATABASE_SOURCE_SIGNATURE_DOMAINS.head, head, signedHead.signature)) fail()
const bindingBytes = await boundedFile(path.join(directory, "binding-" + head.bindingSha256 + ".json"))
if (sourceBindingSha256(bindingBytes) !== head.bindingSha256) fail()
const signedBinding = parseExact(bindingBytes), body = sourceBindingBodySchema.parse(signedBinding.body)
if (!check(DATABASE_SOURCE_SIGNATURE_DOMAINS.binding, body, signedBinding.signature) || body.installationId !== head.installationId ||
body.sourceIdentityId !== head.sourceIdentityId || body.generation !== head.generation || body.ordinal !== head.ordinal) fail()
return Object.freeze({ body: Object.freeze({ ...body, source: Object.freeze({ ...body.source }) }),
head: Object.freeze(head), publicKeySpkiSha256: cfg.publicKeySpkiSha256, headSha256: cfg.currentHeadSha256,
platformProtectionVerified: false as const, generationAuthenticated: false as const, readyAuthorized: false as const })
} catch { return fail() }
}
/** Genuine same-facade observation is mandatory. Serialized source facts never
* become a capability. Registry pin comes only from future privileged assembly. */
export function createDatabaseSourceBindingVerifier(raw: DatabaseSourceDeploymentPin) {
const cfg = Object.freeze(deployment.parse(raw))
return Object.freeze({ async verify(sql: ProbeStorageSql, observation: DatabaseSourceObservation, options: DatabaseSourceObservationOptions) {
try {
fence(options); await assertDatabaseSourceObservationCurrent(sql, observation, options)
const before = await readDatabaseSourceBindingRegistry(cfg); fence(options)
if (sourceBindingCanonical(before.body.source) !== sourceBindingCanonical(observation.source)) fail()
await assertDatabaseSourceObservationCurrent(sql, observation, options); fence(options)
const after = await readDatabaseSourceBindingRegistry(cfg); fence(options)
if (sourceBindingCanonical(before) !== sourceBindingCanonical(after)) fail()
await assertDatabaseSourceObservationCurrent(sql, observation, options); fence(options)
return Object.freeze({ format: "database-source-binding-evidence-v1" as const,
installationId: before.body.installationId, sourceIdentityId: before.body.sourceIdentityId, generation: before.body.generation,
ordinal: before.body.ordinal, headSha256: before.headSha256, bindingSha256: before.head.bindingSha256,
operatorSignatureVerified: true as const, currentHeadPinVerified: true as const, genuineSourceFactsMatched: true as const,
platformProtectionVerified: false as const, generationAuthenticated: false as const, allWriterDrainAuthenticated: false as const, readyAuthorized: false as const })
} catch { return fail() }
} })
}