fix(mcp): обновить снимок приёмки маршрутизации и базы данных
This commit is contained in:
1 parent
bf3d50e1ff
commit
6a286a1f66
79 files changed
+10015
-109
No files matched your search
@@ -0,0 +1,84 @@
|
||||
/** Explicit operator/lifecycle read component. No startup, grants or READY. */
|
||||
import assert from 'node:assert/strict'
|
||||
import {lstat} from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import {protectedDirectory,readProtected} from './operator-files.mjs'
|
||||
import {createDatabaseSourceBindingVerifier,readDatabaseSourceBindingRegistry,
|
||||
type DatabaseSourceDeploymentPin} from '../../src/db/database-source-binding.js'
|
||||
import type {DatabaseSourceObservation,DatabaseSourceObservationOptions} from '../../src/db/database-source-observation.js'
|
||||
import type {ProbeStorageSql} from '../../src/services/server-probe-journal-maintenance.js'
|
||||
|
||||
const hex=(value:unknown):value is string=>typeof value==='string'&&/^[a-f0-9]{64}$/.test(value)
|
||||
function fence(options:DatabaseSourceObservationOptions){
|
||||
assert(Number.isSafeInteger(options.deadlineAt)&&Date.now()<options.deadlineAt&&!options.signal?.aborted,'Protected source lifetime refused')
|
||||
}
|
||||
async function identity(filename:string,content=false){
|
||||
const s=await lstat(filename,{bigint:true})
|
||||
// BigInt identity is never narrowed to Number. Content changes/permission
|
||||
// changes are checked around the whole verified read, not just open().
|
||||
return [s.dev,s.ino,s.mode,s.uid,s.gid,s.nlink,...(content?[s.size,s.mtimeNs,s.ctimeNs]:[])].map(String).join(':')
|
||||
}
|
||||
async function protectedState(pin:DatabaseSourceDeploymentPin){
|
||||
// Proven POSIX protection refuses unsupported Windows ACL BEFORE file reads.
|
||||
await protectedDirectory(pin.registryDirectory)
|
||||
const directories:Record<string,string>={}
|
||||
for(let p=pin.registryDirectory;;p=path.dirname(p)){
|
||||
directories[p]=await identity(p,p===pin.registryDirectory)
|
||||
if(p===path.dirname(p))break
|
||||
}
|
||||
const names=['operator-public.pem','head-'+pin.currentHeadSha256+'.json']
|
||||
const content:Record<string,Buffer>={},identities:Record<string,string>={}
|
||||
const collect=async(name:string,maximum:number,expected?:string)=>{
|
||||
const filename=path.join(pin.registryDirectory,name)
|
||||
const before=await identity(filename,true)
|
||||
content[name]=await readProtected(filename,maximum,expected)
|
||||
assert.equal(await identity(filename,true),before,'Protected source file changed during read')
|
||||
identities[name]=before
|
||||
}
|
||||
await collect(names[0],4096)
|
||||
await collect(names[1],8192,pin.currentHeadSha256)
|
||||
const head=JSON.parse(new TextDecoder('utf8',{fatal:true}).decode(content[names[1]]))
|
||||
assert(hex(head?.body?.bindingSha256),'Pinned source binding digest required')
|
||||
const bindingName='binding-'+head.body.bindingSha256+'.json'
|
||||
await collect(bindingName,8192,head.body.bindingSha256)
|
||||
await protectedDirectory(pin.registryDirectory)
|
||||
for(const [directory,wanted]of Object.entries(directories))assert.equal(await identity(directory,directory===pin.registryDirectory),wanted,'Protected source directory changed')
|
||||
return {directories,identities,content}
|
||||
}
|
||||
function same(before:Awaited<ReturnType<typeof protectedState>>,after:Awaited<ReturnType<typeof protectedState>>){
|
||||
assert.deepEqual(after.directories,before.directories,'Protected source directory drift')
|
||||
assert.deepEqual(after.identities,before.identities,'Protected source identity/mode drift')
|
||||
assert.deepEqual(after.content,before.content,'Protected source content drift')
|
||||
}
|
||||
function captured(raw:DatabaseSourceDeploymentPin){
|
||||
const pin=Object.freeze({...raw})
|
||||
// Existing strict deployment schema rejects unknown/malformed pin fields.
|
||||
const verifier=createDatabaseSourceBindingVerifier(pin)
|
||||
return {pin,verifier}
|
||||
}
|
||||
|
||||
/** Signature/head/source DATA plus actual POSIX protection of the pinned files.
|
||||
* The supplied pin still needs independent deployment approval; protection
|
||||
* cannot distinguish a restored clone/generation or authenticate all writers. */
|
||||
export async function readProtectedDatabaseSourceBindingRegistry(raw:DatabaseSourceDeploymentPin,options:DatabaseSourceObservationOptions){
|
||||
const {pin}=captured(raw);const lifetime={...options};fence(lifetime)
|
||||
const before=await protectedState(pin);fence(lifetime)
|
||||
const result=await readDatabaseSourceBindingRegistry(pin);fence(lifetime)
|
||||
const after=await protectedState(pin);fence(lifetime);same(before,after)
|
||||
return Object.freeze({...result,platformProtectionVerified:true as const,
|
||||
generationAuthenticated:false as const,allWriterDrainAuthenticated:false as const,readyAuthorized:false as const})
|
||||
}
|
||||
|
||||
/** Same-facade source verification is retained; filesystem JSON is never a
|
||||
* source observation, writer capability or independent generation authority. */
|
||||
export function createProtectedDatabaseSourceBindingVerifier(raw:DatabaseSourceDeploymentPin){
|
||||
const {pin,verifier}=captured(raw)
|
||||
return Object.freeze({async verify(sql:ProbeStorageSql,observation:DatabaseSourceObservation,options:DatabaseSourceObservationOptions){
|
||||
const lifetime={...options};fence(lifetime)
|
||||
const before=await protectedState(pin);fence(lifetime)
|
||||
const result=await verifier.verify(sql,observation,lifetime);fence(lifetime)
|
||||
const after=await protectedState(pin);fence(lifetime);same(before,after)
|
||||
return Object.freeze({...result,platformProtectionVerified:true as const,
|
||||
generationAuthenticated:false as const,allWriterDrainAuthenticated:false as const,readyAuthorized:false as const})
|
||||
}})
|
||||
}
|
||||
Reference in new issue
Block a user