feat(mcp): расширить управление маршрутизацией, доступом и обслуживанием базы

This commit is contained in:
Codex committed 2026-11-13 13:37:55 +07:00
1 parent 9240e8be11
commit cffcfada88
125 files changed
+6021 -602

No files matched your search

@@ -20,7 +20,7 @@ test('fixed artifact is reproducible, resolves the compiled backend graph, and r
const packageJson=JSON.parse(await readFile(path.join(backend,'package.json'),'utf8'))
await mkdir(projectBackend,{recursive:true});await writeFile(path.join(projectBackend,'package.json'),JSON.stringify({type:'module',dependencies:packageJson.dependencies}))
const first=await buildDatabaseOperator({backendDirectory:backend,outputDirectory:output}),second=await buildDatabaseOperator({backendDirectory:backend,outputDirectory:output})
assert.deepEqual(first,second);assert.equal(first.modules.length,OPERATOR_SOURCES.length);assert.equal(first.modules.length,19);assert.deepEqual(first.runtimeDependencies,['dotenv','pg','zod']);assert.equal(first.backendDistModules.length,12)
assert.deepEqual(first,second);assert.equal(first.modules.length,OPERATOR_SOURCES.length);assert.equal(first.modules.length,20);assert.deepEqual(first.runtimeDependencies,['dotenv','pg','zod']);assert.equal(first.backendDistModules.length,12)
const copied=new Set()
async function copyDist(relative){
if(copied.has(relative))return;copied.add(relative)
@@ -36,10 +36,10 @@ test('fixed artifact is reproducible, resolves the compiled backend graph, and r
assert.deepEqual([...copied].sort(),first.backendDistModules.map(item=>item.file).sort())
// Exercise the real build entry point with its default backend/output paths.
const scripts=path.join(projectBackend,'scripts')
assert.equal(await stageDatabaseOperatorScripts(scripts,path.join(backend,'scripts')),21)
assert.equal(await stageDatabaseOperatorScripts(scripts,path.join(backend,'scripts')),OPERATOR_SOURCES.length+2)
const buildScript=path.join(scripts,'build-database-operator.mjs')
const built=spawnSync(process.execPath,[buildScript],{cwd:temporary,env:{...process.env,NODE_OPTIONS:''},encoding:'utf8',timeout:30000})
assert.equal(built.status,0,built.stderr);assert.equal(JSON.parse(built.stdout.trim()).modules,19)
assert.equal(built.status,0,built.stderr);assert.equal(JSON.parse(built.stdout.trim()).modules,20)
assert.deepEqual(JSON.parse(await readFile(path.join(output,'build-manifest.json'),'utf8')),first)
const configFile=path.join(projectBackend,'dist','config.js'),originalConfig=await readFile(configFile,'utf8')
for(const injected of ["import 'tsx';","import '../src/config.js';","import '../outside.js';"]){
@@ -61,9 +61,9 @@ test('fixed artifact is reproducible, resolves the compiled backend graph, and r
const modulesToImport=first.modules.map(item=>path.join(output,item.file))
const importer=path.join(temporary,'import-all.mjs');await writeFile(importer,`import {pathToFileURL} from 'node:url';for(const file of ${JSON.stringify(modulesToImport)})await import(pathToFileURL(file).href);console.log(JSON.stringify({imported:${modulesToImport.length},readyAuthorized:false}));`)
const imported=spawnSync(process.execPath,['--import',pathToFileURL(guard).href,importer],{cwd:temporary,env,encoding:'utf8',timeout:30000})
assert.equal(imported.status,0,imported.stderr);assert.equal(JSON.parse(imported.stdout.trim()).imported,19);assert.equal(JSON.parse(await readFile(telemetry,'utf8')).connections,0)
assert.equal(imported.status,0,imported.stderr);assert.equal(JSON.parse(imported.stdout.trim()).imported,20);assert.equal(JSON.parse(await readFile(telemetry,'utf8')).connections,0)
const refusals=[]
for(const [entry,event,args] of [['database-writer-bootstrap-operator.mjs','application-writer-bootstrap-refused',['invalid']],['database-writer-profile-operator.mjs','application-writer-profile-publication-refused',['invalid']],['database-writer-profile-operator.mjs','database-maintenance-command-refused',['maintenance-inspect']],['database-writer-profile-operator.mjs','database-maintenance-command-refused',['maintenance-resolve']]]){
for(const [entry,event,args] of [['database-writer-bootstrap-operator.mjs','application-writer-bootstrap-refused',['invalid']],['database-writer-profile-operator.mjs','application-writer-profile-publication-refused',['invalid']],['database-writer-profile-operator.mjs','database-maintenance-command-refused',['maintenance-inspect']],['database-writer-profile-operator.mjs','database-maintenance-command-refused',['maintenance-resolve']],['database-writer-profile-operator.mjs','database-source-activation-refused',['source-activate']]]){
const child=spawnSync(process.execPath,['--import',pathToFileURL(guard).href,path.join(output,entry),...args],{cwd:temporary,env,encoding:'utf8',timeout:30000})
assert.equal(child.status,1,child.stderr);assert.equal(child.stdout,'');assert.equal(JSON.parse(child.stderr.trim()).event,event);assert.equal(JSON.parse(await readFile(telemetry,'utf8')).connections,0);refusals.push(event)
}
@@ -2,7 +2,7 @@
export const OPERATOR_SOURCES=Object.freeze([
'database-writer-bootstrap-operator.mjs','database-writer-profile-operator.mts',
'writer-profile/operator-files.mjs','writer-profile/operator-contract.mjs','writer-profile/publisher.mjs','writer-profile/context.mjs',
'writer-profile/protected-source-registry.mts','writer-profile/maintenance-resolution.mts',
'writer-profile/protected-source-registry.mts','writer-profile/maintenance-resolution.mts','writer-profile/source-activation.mts',
'writer-profile/catalogue.mts','writer-profile/catalogue-value-bridge.ts','writer-profile/catalogue-projection.ts','writer-profile/catalogue-leaves.ts',
'writer-profile/application-writer-profile-catalogue.ts','writer-profile/application-writer-profile-catalogue-values.ts',
'writer-profile/application-writer-profile-catalogue-parent-policy.ts','writer-profile/application-writer-profile-canonical.ts',
@@ -8,10 +8,14 @@ import {approval,reconciliationApproval,poolOptions,roleKeys,assertJsDriverEnvir
import {deploymentRequest} from './database-writer-bootstrap-operator.mjs'
const hex=(v:unknown)=>typeof v==='string'&&/^[a-f0-9]{64}$/.test(v)
const parse=(b:Buffer)=>JSON.parse(new TextDecoder('utf-8',{fatal:true}).decode(b))
const sourceCommands=['source-activate','source-revoke','source-status']
export async function main(args:string[]){
if(sourceCommands.includes(args[0]))return (await import('./writer-profile/source-activation.mjs')).sourceActivationMain(args)
if(['maintenance-inspect','maintenance-resolve'].includes(args[0]))return (await import('./writer-profile/maintenance-resolution.mjs')).maintenanceMain(args)
const activePreflight=args[0]==='source-preflight',preflight=activePreflight||args[0]==='preflight'
const flags=['bootstrap','bootstrap-sha256','approval','approval-sha256','definition','definition-sha256','transport','transport-sha256','ca','source-registry','source-spki-sha256','journal']
assert(['publish','reconcile','preflight'].includes(args[0])&&args.length===1+2*flags.length)
if(activePreflight)flags.push('activation-directory','activation-sha256')
assert((preflight||['publish','reconcile'].includes(args[0]))&&args.length===1+2*flags.length)
assertJsDriverEnvironment(process.env)
const values=new Map<string,string>()
for(let i=1;i<args.length;i+=2){assert(args[i].startsWith('--')&&flags.includes(args[i].slice(2))&&!values.has(args[i].slice(2))&&args[i+1]);values.set(args[i].slice(2),args[i+1])}
@@ -32,34 +36,49 @@ export async function main(args:string[]){
// All explicit trust files are checked before app helper/PG imports. No .env URL is used.
// Existing source helper loads config/dotenv. Complete it BEFORE pg's first
// import so a newly loaded NODE_PG_FORCE_NATIVE cannot select another driver.
const {createDatabaseSourceBindingVerifier}=await import('../src/db/database-source-binding.js')
const {createProtectedDatabaseSourceBindingVerifier}=await import('./writer-profile/protected-source-registry.mjs')
assertJsDriverEnvironment(process.env)
const [{default:pg},{createConnectedPublisher},{collectSource44Catalogue,approvedDefinition}]=await Promise.all([
const [{default:pg},{createConnectedPublisher,requireCurrentCatalogueDefinition},{collectSource44Catalogue,approvedDefinition}]=await Promise.all([
import('pg'),import('./writer-profile/publisher.mjs'),import('./writer-profile/catalogue.mjs')])
assertJsDriverEnvironment(process.env)
assert.equal(approvedDefinition(definition),pinned.definitionSha256)
const installation=Object.freeze({...subject,roles:Object.fromEntries(roleKeys.map(k=>[k,request.roles[k].signedOid])),roleBindings:request.roles,expectedSource:request.source})
const reviewedDefinition=approvedDefinition(definition)
assert.equal(reviewedDefinition.definitionSha256,pinned.definitionSha256)
if(args[0]!=='reconcile')requireCurrentCatalogueDefinition(pinned,reviewedDefinition)
const installation=Object.freeze({...subject,roles:Object.fromEntries(roleKeys.map(k=>[k,request.roles[k].signedOid])),roleBindings:request.roles,expectedSource:request.source,tls:Object.freeze({...request.tls})})
const sourcePin={registryDirectory:get('source-registry'),installationId:subject.installationId,publicKeySpkiSha256:get('source-spki-sha256'),currentHeadSha256:request.sourceHeadSha256}
// Preflight is the read-only lifecycle entry for protected registry evidence.
// A refused protection check never falls back to the signature-only verifier.
const verifier=args[0]==='preflight'
?(await import('./writer-profile/protected-source-registry.mjs')).createProtectedDatabaseSourceBindingVerifier(sourcePin)
:createDatabaseSourceBindingVerifier(sourcePin)
const publisher=createConnectedPublisher(installation,(s:Parameters<typeof collectSource44Catalogue>[0])=>collectSource44Catalogue(s,installation),verifier,pinned)
const pool=new pg.Pool(options)
// A pool-level async error never becomes raw stdout or a retry signal.
let asyncFailure=false;pool.on('error',()=>{asyncFailure=true})
let result,primary:unknown,failed=false
try{result=args[0]==='preflight'?await publisher.preflightCurrentProfile(pool,{deadlineAt:Date.now()+120000}):args[0]==='reconcile'?await publisher.reconcilePendingPublication(pool,{journalDirectory:get('journal'),deadlineAt:Date.now()+120000}):await publisher.publishCurrentProfile(pool,{privateKeyFile,expectedHeadSha256:pinned.expectedHeadSha256,journalDirectory:get('journal'),validityMs:pinned.validityMs,deadlineAt:Date.now()+120000})}
catch(error){primary=error;failed=true}
try{await pool.end()}catch(error){primary??=error;failed=true}
if(args[0]==='preflight'&&result?.state==='matched'&&result.platformProtectionVerified===true&&!failed&&!asyncFailure)return {event:'application-writer-profile-preflight',outcome:'matched',epoch:result.epoch,headSha256:result.headSha256,notAfter:result.notAfter,sourceMatched:true,rolesMatched:true,catalogueMatched:true,platformProtectionVerified:true,writerAuthorized:false,cleanupUnconfirmed:false,automaticReplay:false,generationAuthenticated:false,readyAuthorized:false}
if(result?.outcome==='committed')return {event:'application-writer-profile-publication',outcome:'committed',epoch:result.epoch,headSha256:result.headSha256,notAfter:result.notAfter,
cleanupUnconfirmed:result.cleanupUnconfirmed||asyncFailure||failed,journalUnconfirmed:!!result.journalUnconfirmed,automaticReplay:false,generationAuthenticated:false,readyAuthorized:false}
if(args[0]==='reconcile'&&result&&!failed&&!asyncFailure&&['idle','confirmed','not-committed'].includes(result.state))return {event:'application-writer-profile-reconciliation',outcome:result.state,headSha256:result.headSha256??null,cleanupUnconfirmed:false,journalUnconfirmed:false,automaticReplay:false,generationAuthenticated:false,readyAuthorized:false}
throw primary??Error('Publisher terminal unconfirmed')
// Use the same protected registry evidence for inspection and publication.
const verifier=createProtectedDatabaseSourceBindingVerifier(sourcePin)
async function connected(sourceVerifier:typeof verifier,deadlineAt:number){
const publisher=createConnectedPublisher(installation,(s:Parameters<typeof collectSource44Catalogue>[0])=>collectSource44Catalogue(s,installation),sourceVerifier,pinned,reviewedDefinition)
const pool=new pg.Pool(options)
// A pool-level async error never becomes raw stdout or a retry signal.
let asyncFailure=false;pool.on('error',()=>{asyncFailure=true})
let result,primary:unknown,failed=false
try{result=preflight?await publisher.preflightCurrentProfile(pool,{deadlineAt}):args[0]==='reconcile'?await publisher.reconcilePendingPublication(pool,{journalDirectory:get('journal'),deadlineAt}):await publisher.publishCurrentProfile(pool,{privateKeyFile,expectedHeadSha256:pinned.expectedHeadSha256,journalDirectory:get('journal'),validityMs:pinned.validityMs,deadlineAt})}
catch(error){primary=error;failed=true}
try{await pool.end()}catch(error){primary??=error;failed=true}
if(preflight&&result?.state==='matched'&&result.platformProtectionVerified===true&&!failed&&!asyncFailure)return {event:'application-writer-profile-preflight',outcome:'matched',epoch:result.epoch,headSha256:result.headSha256,notAfter:result.notAfter,sourceMatched:true,rolesMatched:true,catalogueMatched:true,platformProtectionVerified:true,writerAuthorized:false,cleanupUnconfirmed:false,automaticReplay:false,generationAuthenticated:false,readyAuthorized:false}
if(result?.outcome==='committed')return {event:'application-writer-profile-publication',outcome:'committed',epoch:result.epoch,headSha256:result.headSha256,notAfter:result.notAfter,
cleanupUnconfirmed:result.cleanupUnconfirmed||asyncFailure||failed,journalUnconfirmed:!!result.journalUnconfirmed,automaticReplay:false,generationAuthenticated:false,readyAuthorized:false}
if(args[0]==='reconcile'&&result&&!failed&&!asyncFailure&&['idle','confirmed','not-committed'].includes(result.state))return {event:'application-writer-profile-reconciliation',outcome:result.state,headSha256:result.headSha256??null,cleanupUnconfirmed:false,journalUnconfirmed:false,automaticReplay:false,generationAuthenticated:false,readyAuthorized:false}
throw primary??Error('Publisher terminal unconfirmed')
}
const deadlineAt=Date.now()+120000
if(!activePreflight)return connected(verifier,deadlineAt)
const {withActiveSourcePreflight}=await import('./writer-profile/source-activation.mjs')
const publisherRole=request.roles.publisher.name
const expectedSource={...request.source,databaseOid:Number(request.source.databaseOid),sessionUser:publisherRole,currentUser:publisherRole}
const result=await withActiveSourcePreflight({directory:get('activation-directory'),expectedSha256:get('activation-sha256'),sourcePin,expectedSource,tlsSpkiSha256:request.tls.spkiSha256,deadlineAt},verifier,
guardedVerifier=>connected(guardedVerifier,deadlineAt))
assert.equal(result.event,'application-writer-profile-preflight')
return {...result,event:'application-writer-profile-source-preflight',sourceActivationMatched:true}
}
if(process.argv[1]&&import.meta.url===pathToFileURL(path.resolve(process.argv[1])).href){
try{const result=await main(process.argv.slice(2));console.log(JSON.stringify(result));if(result.cleanupUnconfirmed||result.journalUnconfirmed)process.exitCode=2}
catch{console.error(JSON.stringify({event:['maintenance-inspect','maintenance-resolve'].includes(process.argv[2])?'database-maintenance-command-refused':'application-writer-profile-publication-refused',automaticReplay:false,inspectPendingBeforeRetry:true,readyAuthorized:false}));process.exitCode=1}
catch(error){
const sourceCommand=sourceCommands.includes(process.argv[2])
const unknown=sourceCommand&&(error as NodeJS.ErrnoException|null)?.code==='SOURCE_ACTIVATION_TERMINAL_UNKNOWN'
console.error(JSON.stringify({event:sourceCommand?(unknown?'database-source-activation-unknown':'database-source-activation-refused'):
['maintenance-inspect','maintenance-resolve'].includes(process.argv[2])?'database-maintenance-command-refused':process.argv[2]==='source-preflight'?'application-writer-profile-source-preflight-refused':'application-writer-profile-publication-refused',automaticReplay:false,inspectPendingBeforeRetry:true,readyAuthorized:false}));process.exitCode=unknown?2:1
}
}
@@ -6,22 +6,38 @@ import fs from 'node:fs/promises'
import {createRequire} from 'node:module'
import {runInNewContext} from 'node:vm'
import {EventEmitter} from 'node:events'
import {generateKeyPairSync} from 'node:crypto'
import path from 'node:path'
import * as format from '../src/db/application-writer-profile-format.ts'
import {MIGRATION_MANIFEST,migrationManifestDigest} from '../src/db/migration-manifest.ts'
import {readCurrentApplicationWriterProfile} from '../src/db/application-writer-profile-registry-read.ts'
import {syntheticFixture,stagedModelClient} from '../src/db/application-writer-profile-registry-read-model.test.ts'
import {approvedDefinition} from './writer-profile/catalogue.mts'
import {BUILD44_PROVENANCE,FIXED44_ROLE_TEMPLATE} from './writer-profile/fixed44-build-definition.ts'
const require=createRequire(new URL('../package.json',import.meta.url)),ts=require('typescript'),pg=require('pg')
const candidate=await fs.readFile(new URL('./writer-profile/publisher.mjs',import.meta.url),'utf8')
interface PreflightDiagnostics {state:string;headSha256:string;sourceMatched:boolean;rolesMatched:boolean;catalogueMatched:boolean;platformProtectionVerified:boolean;writerAuthorized:boolean;generationAuthenticated:boolean;readyAuthorized:boolean}
interface PublisherModule {createConnectedPublisher(installation:unknown,collect:()=>Promise<unknown>,sourceVerifier:{verify:()=>Promise<{platformProtectionVerified:boolean}>},approval:unknown):{preflightCurrentProfile(pool:unknown,options:{deadlineAt:number}):Promise<PreflightDiagnostics>}}
interface OperatorModule {main(args:string[]):Promise<{event:string;outcome:string;platformProtectionVerified:boolean;writerAuthorized:boolean;readyAuthorized:boolean}>}
interface PublisherModule {
requireCurrentCatalogueDefinition(approval:unknown,definition:unknown):string
createConnectedPublisher(installation:unknown,collect:()=>Promise<unknown>,sourceVerifier:{verify:()=>Promise<{platformProtectionVerified:boolean}>},approval:unknown,definition:unknown):{
preflightCurrentProfile(pool:unknown,options:{deadlineAt:number}):Promise<PreflightDiagnostics>
publishCurrentProfile(pool:unknown,options:{privateKeyFile:string;expectedHeadSha256:null;journalDirectory:string;deadlineAt:number}):Promise<{outcome:string}>
}
}
interface OperatorModule {main(args:string[]):Promise<{event:string;outcome:string;platformProtectionVerified:boolean;writerAuthorized:boolean;readyAuthorized:boolean;sourceActivationMatched?:boolean}>}
function compile<T>(source:string,modules:Record<string,unknown>):T{
const exports:Record<string,unknown>={};const compiled=ts.transpileModule(source.split('\nif(process.argv[1]')[0].replaceAll('import.meta.url',JSON.stringify('file:///candidate-not-cli.mts')),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(compiled,{exports,require:(name:string)=>{assert(name in modules,'Unapproved import '+name);return modules[name]},Buffer,URL,TextDecoder,Date,setTimeout,clearTimeout,Object,Error,AggregateError,Promise,Map,Set,process:{env:{},argv:[]}}, {filename:'candidate.mjs'})
// The isolated VM loads the exact source above; tests exercise this narrow export contract.
return exports as T
}
function fixture(fault=''){
const f=syntheticFixture('1'),catalogue={catalogSha256:'f'.repeat(64),definitionSha256:f.policy.definitionSha256,leafCount:117},security={sha256:'e'.repeat(64),roles:{...f.policy.roles}}
const frozenDefinition=approvedDefinition({format:'application-writer-source44-definition-approval-v1',provenance:BUILD44_PROVENANCE,template:FIXED44_ROLE_TEMPLATE})
const frozenMigrations=MIGRATION_MANIFEST.slice(0,44),markerIds=frozenMigrations.map(pin=>pin.file.slice(0,-4))
const markerSql='SELECT id FROM public.schema_migrations ORDER BY id COLLATE "C" LIMIT $1'
function fixture(fault='',forPublication=false){
const f=syntheticFixture('1'),catalogue={...frozenDefinition,catalogSha256:'f'.repeat(64),leafCount:117},security={sha256:'e'.repeat(64),roles:{...f.policy.roles}}
const currentBuild=fault==='current-build'||fault==='current-approved-frozen-definition'
f.policy.manifestSha256=frozenDefinition.manifestSha256;f.policy.definitionSha256=frozenDefinition.definitionSha256
f.policy.catalogSha256=format.sha(format.canonical({base:catalogue.catalogSha256,security:security.sha256}))
if(fault==='signed-source')f.policy.source.databaseOid='999'
if(fault==='signed-role')f.policy.roles.operator='99'
@@ -34,30 +50,49 @@ function fixture(fault=''){
f.state.current[0].head_sha256=top.head_sha256;f.state.policy=[{policy_sha256:hash,policy_bytes:raw}]
if(fault==='tamper')f.state.policy[0].policy_bytes=Buffer.from(raw.toString().replace('synthetic','tampered'))
if(fault==='empty'){f.state.current=[];f.state.latest=[];f.state.policy=[]}
const model=stagedModelClient(f.state),queries:string[]=[],events:string[]=[];let clocks=0
const model=stagedModelClient(f.state),queries:string[]=[],events:string[]=[],markerLimits:unknown[][]=[]
let clocks=0,checkouts=0
const client=Object.assign(new EventEmitter(),{query:async(sql:string,values?:unknown[])=>{
queries.push(sql)
if(sql.includes('clock_timestamp()')&&++clocks===2&&fault==='late-clock')return {rows:[{now:'1'}]}
if(sql===markerSql){
markerLimits.push(values??[])
const ids=fault==='marker-missing'?markerIds.slice(0,-1):fault==='marker-extra'||fault==='marker-drift'&&markerLimits.length===2?[...markerIds,'9999_foreign']:markerIds
return {rows:ids.map(id=>({id}))}
}
if(sql.startsWith('WITH')||sql.includes('clock_timestamp()'))return model.client.query(sql,values)
if(sql.startsWith('SELECT pg_catalog.pg_advisory_unlock'))return {rows:[{released:true}]}
if(sql==='ROLLBACK')return {rows:[],command:fault==='rollback-ack'?'':'ROLLBACK'}
if(sql.startsWith('INSERT INTO mmapp_control.current'))return {rows:[{id:1}]}
return {rows:[],command:sql.split(' ')[0]}
},release:(bad:boolean)=>{events.push('release:'+bad)}})
const pool=new pg.Pool();pool.connect=async()=>client
const pool=new pg.Pool();pool.connect=async()=>{checkouts++;return client}
const source={systemIdentifier:'123',databaseOid:'456',databaseName:'synthetic',serverVersionNum:180004,inRecovery:false}
let contexts=0,verifications=0
const installation={...f.subject,expectedSource:source,roleBindings:{publisher:{name:'publisher',signedOid:'14'}}}
const approval={expiresAt:Date.now()+60000,manifestSha256:'b'.repeat(64),definitionSha256:catalogue.definitionSha256,catalogSha256:format.sha(format.canonical({base:catalogue.catalogSha256,security:security.sha256}))}
const fixtureKey=forPublication?generateKeyPairSync('ed25519'):null
const subject=fixtureKey?{...f.subject,publicKeyPem:fixtureKey.publicKey.export({format:'pem',type:'spki'}).toString(),publicKeySpkiSha256:format.sha(fixtureKey.publicKey.export({format:'der',type:'spki'}))}:f.subject
const installation={...subject,expectedSource:source,roleBindings:{publisher:{name:'publisher',signedOid:'14'}}}
const approval={expiresAt:Date.now()+60000,manifestSha256:['approval-manifest','current-approved-frozen-definition'].includes(fault)?migrationManifestDigest():frozenDefinition.manifestSha256,definitionSha256:catalogue.definitionSha256,catalogSha256:format.sha(format.canonical({base:catalogue.catalogSha256,security:security.sha256}))}
const definition=fault==='static-definition'?{...frozenDefinition,definitionSha256:'0'.repeat(64)}:frozenDefinition
const privateKeyFile=path.resolve('modeled-fixture-key.pem'),journalDirectory=path.resolve('modeled-journal'),files=new Map<string,Buffer>()
if(fixtureKey)files.set(privateKeyFile,Buffer.from(fixtureKey.privateKey.export({format:'pem',type:'pkcs8'})))
function file(filename:string){const value=files.get(filename);if(!value)throw Object.assign(Error('Fixture file absent'),{code:'ENOENT'});return value}
function stat(filename:string){return {size:file(filename).length,dev:1,ino:1,isFile:()=>true,isSymbolicLink:()=>false}}
const modules={
pg,'node:crypto':require('node:crypto'),'node:fs/promises':{},'node:path':require('node:path'),
'./operator-files.mjs':{withJournalLease:()=>{throw Error('Preflight must not use publication journal')},writeFile:()=>{throw Error('No publication writes')}},
pg,'node:crypto':require('node:crypto'),'node:fs/promises':{realpath:async(file:string)=>file,lstat:async(filename:string)=>stat(filename),unlink:async(filename:string)=>files.delete(filename),open:async(filename:string)=>({stat:async()=>stat(filename),readFile:async()=>Buffer.from(file(filename)),close:async()=>{}})},'node:path':path,
'./operator-contract.mjs':{retainPublisherTlsPeer:(actual:unknown)=>{
assert.equal(actual,client)
if(fault==='initial-tls')throw Error('TLS peer refused')
return ()=>{if(fault==='lost-tls'&&verifications===2)throw Error('Retained TLS lost')}
}},
'./operator-files.mjs':{withJournalLease:async(_directory:string,work:()=>Promise<unknown>)=>{assert(forPublication);events.push('journal-lease');return work()},writeFile:async(filename:string,value:Buffer)=>{assert(forPublication);files.set(filename,value)}},
'../../src/db/database-source-observation.js':{captureDatabaseSourceObservation:async()=>{events.push('capture-source');return {}},assertDatabaseSourceObservationCurrent:async()=>{events.push('fresh-source');if(fault==='late-source')throw Error('Source changed')}},
'../../src/db/migration-manifest.js':{MIGRATION_MANIFEST:[],migrationManifestDigest:()=> 'b'.repeat(64)},
'../../src/db/migration-manifest.js':{MIGRATION_MANIFEST:currentBuild?MIGRATION_MANIFEST:frozenMigrations,migrationManifestDigest:()=>currentBuild?migrationManifestDigest():frozenDefinition.manifestSha256},
'../../src/db/application-writer-profile-format.js':format,
'./context.mjs':{ownedContext:async()=>{contexts++;if(fault==='late-actor'&&contexts===2)throw Error('Actor changed');return {pid:1,source}},sameOwned:(a:unknown,b:unknown)=>assert.deepEqual(a,b),sourceCoordinates:(v:{source:typeof source})=>v.source,protectedStructure:async()=>{events.push('roles');if(fault==='role-structure')throw Error('Unsafe role');return security},readCurrent:readCurrentApplicationWriterProfile,HISTORY:'history',CURRENT:'current'},
'./context.mjs':{ownedContext:async()=>{contexts++;if(fault==='late-actor'&&contexts===2)throw Error('Actor changed');return {pid:1,source}},sameOwned:(a:unknown,b:unknown)=>assert.deepEqual(a,b),sourceCoordinates:(v:{source:typeof source})=>v.source,protectedStructure:async()=>{events.push('roles');if(fault==='role-structure')throw Error('Unsafe role');return security},readCurrent:forPublication?async()=>null:readCurrentApplicationWriterProfile,HISTORY:'history',CURRENT:'current'},
}
const api=compile<PublisherModule>(candidate,modules).createConnectedPublisher(installation,async()=>{events.push('catalogue');if(fault==='catalogue-eof')throw Error('Incomplete EOF');return catalogue},{verify:async()=>{verifications++;events.push('verify-source');if(fault==='source-verifier')throw Error('Unsigned source');if(fault==='late-expiry'&&verifications===2)approval.expiresAt=1;return {platformProtectionVerified:!(fault==='initial-unprotected'&&verifications===1||fault==='terminal-unprotected'&&verifications===2)}}},approval)
return {run:()=>api.preflightCurrentProfile(pool,{deadlineAt:Date.now()+30000}),queries,events,f}
const api=compile<PublisherModule>(candidate,modules).createConnectedPublisher(installation,async()=>{events.push('catalogue');if(fault==='catalogue-eof')throw Error('Incomplete EOF');return fault==='catalogue-manifest'?{...catalogue,manifestSha256:migrationManifestDigest()}:fault==='catalogue-definition'?{...catalogue,definitionSha256:'0'.repeat(64)}:catalogue},{verify:async()=>{verifications++;events.push('verify-source');if(fault==='source-verifier')throw Error('Unsigned source');if(fault==='late-expiry'&&verifications===2)approval.expiresAt=1;return {platformProtectionVerified:!(fault==='initial-unprotected'&&verifications===1||fault==='terminal-unprotected'&&verifications===2)}}},approval,definition)
return {run:()=>api.preflightCurrentProfile(pool,{deadlineAt:Date.now()+30000}),publish:()=>api.publishCurrentProfile(pool,{privateKeyFile,journalDirectory,expectedHeadSha256:null,deadlineAt:Date.now()+30000}),queries,events,markerLimits,files,get checkouts(){return checkouts},f}
}
test('current44 preflight authenticates signed current and actual component inputs on one read-only lease',async()=>{
const f=fixture(),result=await f.run();assert.equal(result.state,'matched');assert.equal(result.headSha256,f.f.state.current[0].head_sha256)
@@ -65,9 +100,17 @@ test('current44 preflight authenticates signed current and actual component inpu
assert.equal(result.platformProtectionVerified,true)
assert.equal(result.writerAuthorized,false);assert.equal(result.generationAuthenticated,false);assert.equal(result.readyAuthorized,false)
assert.equal(f.queries.filter(q=>q.startsWith('BEGIN')).length,1);assert(f.queries.includes('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY'));assert.equal(f.queries.filter(q=>q==='ROLLBACK').length,1)
assert.equal(format.canonical(f.markerLimits),'[[45]]')
assert(!f.queries.some(q=>/^(?:INSERT|UPDATE|DELETE|COMMIT|CREATE|ALTER|DROP)\b/.test(q)));assert.equal(f.events.at(-1),'release:false')
assert.deepEqual(f.events.slice(0,6),['capture-source','verify-source','roles','catalogue','verify-source','fresh-source'])
})
test('publisher refuses before SQL on a wrong peer and stops SQL when the retained TLS socket is lost',async()=>{
const initial=fixture('initial-tls')
await assert.rejects(initial.run());assert.deepEqual(initial.queries,[]);assert.deepEqual(initial.events,['release:true'])
const lost=fixture('lost-tls')
await assert.rejects(lost.run());assert.equal(lost.events.at(-1),'release:true')
assert(!lost.queries.some(q=>q==='ROLLBACK'||q.includes('pg_advisory_unlock')))
})
for(const fault of ['initial-unprotected','terminal-unprotected'])test('preflight cannot promote '+fault+' into protected evidence',async()=>{
const f=fixture(fault),result=await f.run();assert.equal(result.platformProtectionVerified,false)
assert.equal(result.writerAuthorized,false);assert.equal(result.readyAuthorized,false)
@@ -76,23 +119,64 @@ for(const fault of ['initial-unprotected','terminal-unprotected'])test('prefligh
for(const fault of ['tamper','empty','expired','signed-source','signed-role','signed-catalogue','signed-definition','signed-manifest','role-structure','catalogue-eof','source-verifier','late-source','late-actor','late-expiry','late-clock','rollback-ack'])test('preflight refuses '+fault+' without any write or replay',async()=>{
const f=fixture(fault);await assert.rejects(f.run());assert(!f.queries.some(q=>/^(?:INSERT|UPDATE|DELETE|COMMIT|CREATE|ALTER|DROP)\b/.test(q)));assert.equal(f.queries.filter(q=>q.startsWith('BEGIN')).length,1)
})
test('existing operator main dispatches preflight only after protected input validation, without private key or publication',async()=>{
for(const fault of ['current-build','current-approved-frozen-definition','approval-manifest','static-definition'])test('incompatible static manifest/definition refuses '+fault+' before either pool checkout or publication lease',async()=>{
const f=fixture(fault,true)
await assert.rejects(f.run(),/Approved catalogue definition is not bound/)
await assert.rejects(f.publish(),/Approved catalogue definition is not bound/)
assert.equal(f.checkouts,0);assert.deepEqual(f.events,[]);assert.deepEqual(f.queries,[])
})
for(const fault of ['catalogue-manifest','catalogue-definition','marker-missing','marker-extra'])test('retained preflight refuses '+fault+' and rolls back',async()=>{
const f=fixture(fault)
await assert.rejects(f.run(),error=>error instanceof Error&&/Catalogue evidence differs|Exact current migration marker/.test((error.cause as Error).message))
assert.equal(f.checkouts,1);assert.equal(f.queries.filter(q=>q==='ROLLBACK').length,1)
assert(!f.queries.some(q=>/^(?:INSERT|UPDATE|DELETE|COMMIT)\b/.test(q)))
if(fault.startsWith('marker-'))assert.equal(format.canonical(f.markerLimits),'[[45]]')
else assert.deepEqual(f.markerLimits,[])
})
test('publication checks exact marker set on both retained transactions and refuses late marker drift before registry writes',async()=>{
const positive=fixture('',true)
assert.equal((await positive.publish()).outcome,'committed')
assert.equal(positive.checkouts,1);assert.equal(format.canonical(positive.markerLimits),'[[45],[45]]')
assert.equal(positive.queries.filter(q=>q==='COMMIT').length,2)
const drift=fixture('marker-drift',true)
await assert.rejects(drift.publish(),error=>error instanceof Error&&/Exact current migration marker/.test((error.cause as Error).message))
assert.equal(drift.checkouts,1);assert.equal(format.canonical(drift.markerLimits),'[[45],[45]]')
assert.equal(drift.queries.filter(q=>q==='COMMIT').length,1);assert.equal(drift.queries.filter(q=>q==='ROLLBACK').length,1)
assert(!drift.queries.some(q=>/^(?:INSERT|UPDATE|DELETE)\b/.test(q)))
assert([...drift.files.keys()].some(filename=>filename.endsWith('pending-publication.json')))
})
test('operator requires the protected source verifier for preflight, publication and reconciliation',async()=>{
const cli=await fs.readFile(new URL('./database-writer-profile-operator.mts',import.meta.url),'utf8'),f=syntheticFixture('1'),reads:string[]=[],events:string[]=[]
const request={installationId:f.subject.installationId,roles:Object.fromEntries(['runtime','operator','capture','publisher','control'].map((k,i)=>[k,{name:k,signedOid:String(i+11)}])),source:f.policy.source,sourceHeadSha256:'a'.repeat(64),tls:{caSha256:'a'.repeat(64)}}
const files:Record<string,unknown>={bootstrap:{format:'application-writer-bootstrap-v1',publicationAuthorized:false,readyAuthorized:false,deploymentRequest:request,verificationSubject:f.subject},approval:{expiresAt:Date.now()+60000,definitionSha256:'b'.repeat(64)},definition:{},transport:{},ca:'fixture'}
let failEnd=false,asyncError=false,protectedRefused=false
const request={installationId:f.subject.installationId,roles:Object.fromEntries(['runtime','operator','capture','publisher','control'].map((k,i)=>[k,{name:k,signedOid:String(i+11)}])),source:f.policy.source,sourceHeadSha256:'a'.repeat(64),tls:{caSha256:'a'.repeat(64),spkiSha256:'d'.repeat(64)}}
const files:Record<string,unknown>={bootstrap:{format:'application-writer-bootstrap-v1',publicationAuthorized:false,readyAuthorized:false,deploymentRequest:request,verificationSubject:f.subject},approval:{expiresAt:Date.now()+60000,definitionSha256:'b'.repeat(64)},definition:{},transport:{},ca:'fixture','signing-private.pem':'fixture'}
let failEnd=false,asyncError=false,protectedRefused=false,activationRefused=false,incompatibleDefinition=false
const retainedPools:Pool[]=[]
class Pool extends EventEmitter{constructor(){super();retainedPools.push(this)}async end(){events.push('pool-end');if(failEnd)throw Error('Pool cleanup unconfirmed')}}
const result={state:'matched',epoch:'1',headSha256:'c'.repeat(64),notAfter:Date.now()+60000,platformProtectionVerified:true}
const protectedVerifier={verify:async()=>({platformProtectionVerified:true})}
const guardedVerifier={...protectedVerifier}
const modules={
'node:assert/strict':assert,'node:path':require('node:path'),'node:url':require('node:url'),'node:crypto':require('node:crypto'),
'./writer-profile/operator-files.mjs':{readProtected:async(file:string)=>{reads.push(file);assert(file in files);return Buffer.from(JSON.stringify(files[file]))},protectedDirectory:async()=>{},sha:format.sha},
'./writer-profile/operator-contract.mjs':{approval:(value:unknown)=>{events.push('approval');return value},reconciliationApproval:()=>{throw Error('No expired reconciliation bypass')},poolOptions:()=>{events.push('transport');return {}},roleKeys:['runtime','operator','capture','publisher','control'],assertJsDriverEnvironment:()=>{}},
'./database-writer-bootstrap-operator.mjs':{deploymentRequest:(value:unknown)=>value},'../src/db/database-source-binding.js':{createDatabaseSourceBindingVerifier:()=>{throw Error('Preflight must never fall back to unprotected source verification')}},
'./writer-profile/operator-contract.mjs':{approval:(value:unknown)=>{events.push('approval');return value},reconciliationApproval:(value:unknown)=>{events.push('reconciliation-approval');return value},poolOptions:()=>{events.push('transport');return {}},roleKeys:['runtime','operator','capture','publisher','control'],assertJsDriverEnvironment:()=>{}},
'./database-writer-bootstrap-operator.mjs':{deploymentRequest:(value:unknown)=>value},
'./writer-profile/protected-source-registry.mjs':{createProtectedDatabaseSourceBindingVerifier:(pin:{registryDirectory:string;currentHeadSha256:string})=>{events.push('protected-source');assert.equal(pin.registryDirectory,'source-registry');assert.equal(pin.currentHeadSha256,request.sourceHeadSha256);if(protectedRefused)throw Error('Protected source refused');return protectedVerifier}},
pg:{Pool},'./writer-profile/publisher.mjs':{createConnectedPublisher:(_installation:unknown,_collect:unknown,verifier:unknown)=>{assert.equal(verifier,protectedVerifier);return {preflightCurrentProfile:async()=>{events.push('preflight');if(asyncError){const activePool=retainedPools.at(-1);assert(activePool);activePool.emit('error',Error('Async pool fault'))}return result},publishCurrentProfile:()=>{throw Error('No publish')},reconcilePendingPublication:()=>{throw Error('No reconcile')}}}},
'./writer-profile/catalogue.mjs':{collectSource44Catalogue:()=>{},approvedDefinition:()=> 'b'.repeat(64)},
'./writer-profile/source-activation.mjs':{withActiveSourcePreflight:async(input:{directory:string;expectedSha256:string;sourcePin:{currentHeadSha256:string};expectedSource:unknown;tlsSpkiSha256:string;deadlineAt:number},verifier:unknown,work:(verifier:unknown)=>Promise<unknown>)=>{
assert.equal(verifier,protectedVerifier);assert.equal(input.directory,'active-registry');assert.equal(input.expectedSha256,'e'.repeat(64))
assert.equal(input.sourcePin.currentHeadSha256,request.sourceHeadSha256);assert.equal(input.tlsSpkiSha256,request.tls.spkiSha256);assert(input.deadlineAt>Date.now())
assert.equal(format.canonical(input.expectedSource),format.canonical({...request.source,databaseOid:Number(request.source.databaseOid),sessionUser:'publisher',currentUser:'publisher'}))
events.push('activation-admission');if(activationRefused)throw Error('Activation refused')
const value=await work(guardedVerifier);events.push('activation-terminal');return value
}},
pg:{Pool},'./writer-profile/publisher.mjs':{requireCurrentCatalogueDefinition:()=>{if(incompatibleDefinition)throw Error('Build manifest refused')},createConnectedPublisher:(_installation:unknown,_collect:unknown,verifier:unknown)=>{
assert.equal(verifier,events.includes('activation-admission')?guardedVerifier:protectedVerifier)
return {
preflightCurrentProfile:async()=>{events.push('preflight');if(asyncError){const activePool=retainedPools.at(-1);assert(activePool);activePool.emit('error',Error('Async pool fault'))}return result},
publishCurrentProfile:async()=>{events.push('publish');return {...result,outcome:'committed'}},
reconcilePendingPublication:async()=>{events.push('reconcile');return {state:'confirmed',headSha256:result.headSha256}},
}
}},
'./writer-profile/catalogue.mjs':{collectSource44Catalogue:()=>{},approvedDefinition:()=>({definitionSha256:'b'.repeat(64),manifestSha256:frozenDefinition.manifestSha256})},
}
const flags=['bootstrap','bootstrap-sha256','approval','approval-sha256','definition','definition-sha256','transport','transport-sha256','ca','source-registry','source-spki-sha256','journal'],args=['preflight',...flags.flatMap(flag=>['--'+flag,flag.endsWith('sha256')?'a'.repeat(64):flag])]
const output=await compile<OperatorModule>(cli,modules).main(args)
@@ -103,5 +187,37 @@ test('existing operator main dispatches preflight only after protected input val
asyncError=false;result.platformProtectionVerified=false;await assert.rejects(compile<OperatorModule>(cli,modules).main(args),/Publisher terminal unconfirmed/)
result.platformProtectionVerified=true;protectedRefused=true;const before=retainedPools.length
await assert.rejects(compile<OperatorModule>(cli,modules).main(args),/Protected source refused/);assert.equal(retainedPools.length,before,'Protection failure must not open a Pool or fall back')
for(const [command,approvalEvent,outcome] of [['publish','approval','committed'],['reconcile','reconciliation-approval','confirmed']]){
const commandArgs=[command,...args.slice(1)]
const poolCount=retainedPools.length
await assert.rejects(compile<OperatorModule>(cli,modules).main(commandArgs),/Protected source refused/)
assert.equal(retainedPools.length,poolCount)
protectedRefused=false
events.length=0;reads.length=0
const terminal=await compile<OperatorModule>(cli,modules).main(commandArgs)
assert.equal(terminal.outcome,outcome);assert.equal(terminal.readyAuthorized,false)
assert.deepEqual(events,[approvalEvent,'transport','protected-source',command,'pool-end'])
assert.equal(reads.includes('signing-private.pem'),command==='publish')
protectedRefused=true
}
const activeArgs=['source-preflight',...args.slice(1),'--activation-directory','active-registry','--activation-sha256','e'.repeat(64)]
protectedRefused=false;activationRefused=true;events.length=0;reads.length=0
const beforeActivation=retainedPools.length
await assert.rejects(compile<OperatorModule>(cli,modules).main(activeArgs),/Activation refused/)
assert.equal(retainedPools.length,beforeActivation)
assert.deepEqual(events,['approval','transport','protected-source','activation-admission'])
activationRefused=false;events.length=0;reads.length=0
const active=await compile<OperatorModule>(cli,modules).main(activeArgs)
assert.equal(active.event,'application-writer-profile-source-preflight');assert.equal(active.sourceActivationMatched,true)
assert.equal(active.readyAuthorized,false);assert.equal(active.writerAuthorized,false)
assert.deepEqual(events,['approval','transport','protected-source','activation-admission','preflight','pool-end','activation-terminal'])
assert.deepEqual(reads,['bootstrap','approval','definition','transport','ca'])
incompatibleDefinition=true;events.length=0;reads.length=0
const beforeBuildRefusal=retainedPools.length
for(const incompatibleArgs of [args,activeArgs,['publish',...args.slice(1)]])await assert.rejects(compile<OperatorModule>(cli,modules).main(incompatibleArgs),/Build manifest refused/)
assert.equal(retainedPools.length,beforeBuildRefusal)
assert(!events.some(event=>event==='protected-source'||event==='activation-admission'))
const historical=await compile<OperatorModule>(cli,modules).main(['reconcile',...args.slice(1)])
assert.equal(historical.outcome,'confirmed');assert.equal(historical.readyAuthorized,false)
})
+151 -5
View File
@@ -6,6 +6,7 @@ import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'
const endpoint = new URL(process.env.OWN_MCP_ENDPOINT), origin = endpoint.origin
assert.equal(endpoint.hostname, '127.0.0.1'); assert.equal(process.env.OWN_EXTERNAL_CLIENT, 'true')
let phase = 'start', next = 0, maxResponseBytes = 0, stderrBytes = 0, bridgePid = null
const responseLimitBytes = 1024 * 1024
const pending = new Map(), cases = []
const activeClients = new Set()
let closing = false
@@ -22,7 +23,7 @@ async function boundedFetch(input, init) {
if (response.body) {
const reader = response.body.getReader()
try {
for (;;) { const item = await reader.read(); if (item.done) break; count += item.value.length; if (count >= 1048576) { await reader.cancel(); throw Error('Own external client response bound') }; chunks.push(item.value) }
for (;;) { const item = await reader.read(); if (item.done) break; count += item.value.length; if (count >= responseLimitBytes) { await reader.cancel(); throw Error('Own external client response bound') }; chunks.push(item.value) }
} finally { reader.releaseLock() }
}
const bytes = Buffer.concat(chunks); maxResponseBytes = Math.max(maxResponseBytes, bytes.length)
@@ -36,14 +37,14 @@ function content(result) {
return parsed
}
async function denied(work) { try { const r = await work(); assert.equal(r?.isError, true) } catch (e) { if (e?.code === 'ERR_ASSERTION') throw e; assert(!/timeout|timed out/i.test(String(e?.message)), 'A timeout is not authorization denial') } }
async function opened(mode, token) {
async function opened(mode, token, maxBufferSize = 131072) {
const client = new Client({ name: 'actual-external-process-' + mode, version: '1.0.0' })
activeClients.add(client)
let transport
if (mode === 'http') transport = new StreamableHTTPClientTransport(endpoint, { requestInit: { headers: { authorization: 'Bearer ' + token } }, fetch: boundedFetch })
else {
transport = new StdioClientTransport({ command: process.execPath, args: ['--import', import.meta.resolve('tsx'), process.env.OWN_BRIDGE_FILE],
cwd: process.cwd(), env: { MCP_URL: endpoint.href, MCP_TOKEN: token }, stderr: 'pipe', maxBufferSize: 131072 })
cwd: process.cwd(), env: { MCP_URL: endpoint.href, MCP_TOKEN: token }, stderr: 'pipe', maxBufferSize })
transport.stderr?.on('data', chunk => { stderrBytes += chunk.length; const text = String(chunk); assert(!text.includes(token)); assert(stderrBytes < 1048576) })
const start = transport.start.bind(transport)
transport.start = async () => { await start(); bridgePid = transport.pid; assert(bridgePid && bridgePid !== process.pid && bridgePid !== Number(process.env.OWN_SERVER_PID)); process.send({ kind: 'bridge-pid', pid: bridgePid }) }
@@ -68,6 +69,36 @@ try {
phase = mode + ':initialize'; let client = await opened(mode, token)
try {
await scoped(client); cases.push(mode + ': separate SDK process initializes, filters tool/host scope, bounds page/result and redacts credentials')
phase = mode + ':policy-edit'
const editScope = async operationIds => {
const response = await boundedFetch(origin + '/api/mcp/connections/' + initial.connectionId, {
method: 'PUT', headers: { authorization: 'Bearer ' + process.env.OWN_PORTAL_JWT, 'content-type': 'application/json' },
body: JSON.stringify({ name: 'Owned external SDK process', mode: 'observer', expiresAt: new Date(Date.now() + 600000).toISOString(),
permissions: ['mm:servers:read'], scope: { serverIds: [Number(process.env.OWN_ALLOWED_SERVER)], operationIds, allowGlobal: false }, limits: { callsPerMinute: 200 } }),
})
assert.equal(response.status, 200)
}
await editScope(['logs.query'])
assert.deepEqual((await client.listTools({}, { timeout: 5000 })).tools.map(tool => tool.name), ['mm_logs_query'])
const allowedServer = Number(process.env.OWN_ALLOWED_SERVER), forbiddenServer = Number(process.env.OWN_FORBIDDEN_SERVER)
const queryLogs = arguments_ => client.callTool({ name: 'mm_logs_query', arguments: arguments_ }, undefined, { timeout: 5000 })
const filters = { q: 'external-sdk-log-', limit: 1 }
const first = content(await queryLogs(filters))
assert.ok(first.nextCursor)
const second = content(await queryLogs({ ...filters, cursor: first.nextCursor }))
assert.equal(second.nextCursor, null)
assert.deepEqual([first.entries, second.entries].map(entries => entries.map(({ serverId, message }) => ({ serverId, message }))), [
[{ serverId: allowedServer, message: 'external-sdk-log-1' }],
[{ serverId: allowedServer, message: 'external-sdk-log-2' }],
])
await denied(() => queryLogs({ ...filters, serverId: forbiddenServer }))
cases.push(mode + ': populated log search pages return only allowed server records and reject the forbidden server')
await denied(() => client.callTool({ name: 'mm_servers_list', arguments: {} }, { timeout: 5000 }))
await editScope([])
assert.deepEqual((await client.listTools({}, { timeout: 5000 })).tools, [])
await editScope(['servers.list'])
await scoped(client)
cases.push(mode + ': established client observes removed and restored tool scope through the web policy API')
if (mode === 'stdio' && process.env.OWN_EXTERNAL_CLEANUP_FAULT === 'failure') throw Error('Owned deliberate external client failure with bridge active')
if (mode === 'stdio' && process.env.OWN_EXTERNAL_CLEANUP_FAULT === 'timeout') { process.send({ kind: 'arm-cleanup-timeout' }); await new Promise(() => {}) }
phase = mode + ':result-bound'; await control('large-result')
@@ -86,7 +117,7 @@ try {
} finally { await client.close().catch(() => {}) }
}
phase = 'http:body-bound'; const bound = await control('new-connection')
const oversized = await fetch(endpoint, { method: 'POST', headers: { authorization: 'Bearer ' + bound.token, 'content-type': 'application/json', accept: 'application/json,text/event-stream' },
const oversized = await fetch(endpoint, { method: 'POST', headers: { authorization: 'Bearer ' + bound.token, 'content-type': 'application/json', accept: 'application/json,text/event-stream', connection: 'close' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'mm_servers_list', arguments: { padding: 'x'.repeat(1048576) } } }), signal: AbortSignal.timeout(5000) })
assert.equal(oversized.status, 413); assert((await oversized.text()).length < 65536)
cases.push('actual HTTP request envelope over1MiB refused before tool execution; successful client results remain bounded')
@@ -126,8 +157,123 @@ try {
await denied(() => oauthClient.callTool({ name: 'mm_servers_list', arguments: {} }, undefined, { timeout: 5000 }))
cases.push('actual OAuth family revocation refuses established and fresh external transport; no implicit new consent granted')
} finally { await oauthClient.close().catch(() => {}) }
phase = 'discovery:issue'
const catalogue = await control('discovery-connection')
const expectedNames = catalogue.names.toSorted()
assert(expectedNames.includes('mm_logs_follow'))
const discoveryByTransport = {}
for (const mode of ['http', 'stdio']) {
phase = `discovery:${mode}`
const client = await opened(mode, catalogue.token, responseLimitBytes)
try {
const names = [], cursors = new Set()
let cursor, largestPageBytes = 0, pages = 0
do {
const page = await client.listTools(cursor ? { cursor } : {}, { timeout: 10000 })
assert(page.tools.length > 0)
for (const tool of page.tools) {
assert.equal(tool.inputSchema.type, 'object')
names.push(tool.name)
}
const encoded = JSON.stringify(page)
assert(!encoded.includes(catalogue.token) && !encoded.includes(process.env.OWN_SERVER_SECRET))
largestPageBytes = Math.max(largestPageBytes, Buffer.byteLength(encoded))
assert(largestPageBytes < responseLimitBytes)
pages++
cursor = page.nextCursor
if (cursor) { assert(!cursors.has(cursor)); cursors.add(cursor) }
assert(names.length <= expectedNames.length)
} while (cursor)
assert.deepEqual(names.toSorted(), expectedNames)
discoveryByTransport[mode] = { tools: names.length, pages, largestPageBytes }
cases.push(mode + ': external SDK discovers every delegated tool exactly once with an object input schema')
} finally { await client.close().catch(() => {}) }
}
phase = 'discovery:revoke'
await control('revoke')
for (const [mode, arm] of [['http', 'arm-log-revocation'], ['stdio', 'arm-log-revocation-after-commit'], ['http', 'arm-log-follow-revocation']]) {
phase = mode + ':' + arm
const { token } = await control(arm)
const logClient = await opened(mode, token)
try {
const follow = arm === 'arm-log-follow-revocation'
const tool = follow ? 'mm_logs_follow' : 'mm_logs_query'
const filters = { q: follow ? 'external-sdk-live-event' : 'external-sdk-log-1', limit: 1 }
if (follow) {
Object.assign(filters, { serverId: Number(process.env.OWN_ALLOWED_SERVER), waitSeconds: 0 })
const start = content(await logClient.callTool({ name: tool, arguments: filters }, undefined, { timeout: 5000 }))
assert.deepEqual(start.entries, [])
assert.equal(typeof start.cursor, 'string')
filters.cursor = start.cursor
filters.waitSeconds = 10
}
const query = logClient.callTool({ name: tool, arguments: filters }, undefined, { timeout: 10000 })
const checked = query.then(result => {
assert.equal(result.isError, true)
const wire = JSON.stringify(result)
assert.match(wire, /OWNER_CLAIMS_REVOKED|UNAUTHORIZED/)
assert.doesNotMatch(wire, /external-sdk-log-1|external-sdk-live-event|"entries"|"nextCursor"|"cursor"/)
})
const deliver = async () => {
if (follow) await control('publish-follow-fixture-row')
await control('deliver-log-revocation')
}
await Promise.all([checked, deliver()])
await assert.rejects(opened(mode, token))
cases.push(mode + ': signed portal revocation ' + (mode === 'stdio' ? 'after' : 'before') + ' audit commit denies ' + tool + ' result and reconnect')
} finally { await logClient.close().catch(() => {}) }
}
phase = 'portal:revoke-export-download'
const exportAccess = await control('new-artifact-connection')
const exportClient = await opened('http', exportAccess.token)
try {
const exported = content(await exportClient.callTool({ name: 'mm_logs_export', arguments: {
serverId: Number(process.env.OWN_ALLOWED_SERVER), q: 'external-sdk-log-1', format: 'json',
} }, undefined, { timeout: 5000 }))
assert.equal(exported.rows, 1)
assert.equal(exported.downloadPath, '/api/mcp/artifacts/' + exported.artifactId)
assert.doesNotMatch(JSON.stringify(exported), /external-sdk-log-1/)
const download = jwt => boundedFetch(origin + exported.downloadPath, { headers: { authorization: 'Bearer ' + jwt } })
const before = await download(exportAccess.ownerJwt)
assert.equal(before.status, 200)
assert.equal(before.headers.get('cache-control'), 'private, no-store')
const bytes = Buffer.from(await before.arrayBuffer())
assert.equal(createHash('sha256').update(bytes).digest('hex'), exported.sha256)
assert.match(bytes.toString('utf8'), /external-sdk-log-1/)
const fresh = await control('revoke-artifact-owner')
for (const jwt of [exportAccess.ownerJwt, fresh.ownerJwt]) {
const refused = await download(jwt)
assert.ok([401, 403].includes(refused.status))
assert.equal(refused.headers.has('content-disposition'), false)
assert.doesNotMatch(await refused.text(), /external-sdk-log-1/)
}
const management = await boundedFetch(origin + '/api/mcp/connections', { headers: { authorization: 'Bearer ' + fresh.ownerJwt } })
assert.equal(management.status, 200)
cases.push('portal owner revocation blocks an existing unexpired log artifact even with refreshed owner login')
} finally { await exportClient.close().catch(() => {}) }
phase = 'portal:revoke-approved-plan'
const planAccess = await control('new-revocable-plan-connection')
const planClient = await opened('http', planAccess.token)
try {
const plan = content(await planClient.callTool({ name: 'mm_logs_settings_update_plan', arguments: {
settings: { enabled: false },
} }, undefined, { timeout: 5000 }))
assert.equal(plan.state, 'awaiting-approval')
const approved = await boundedFetch(origin + '/api/mcp/plans/' + plan.planId + '/decision', {
method: 'POST', headers: { authorization: 'Bearer ' + planAccess.ownerJwt, 'content-type': 'application/json' },
body: JSON.stringify({ approve: true }),
})
assert.equal(approved.status, 200)
assert.deepEqual(await approved.json(), { ok: true })
await control('revoke-approved-plan-owner')
await denied(() => planClient.callTool({ name: 'mm_change_apply', arguments: {
planId: plan.planId, idempotencyKey: randomUUID(),
} }, undefined, { timeout: 5000 }))
cases.push('signed portal revocation invalidates the exact approved log settings plan before apply')
} finally { await planClient.close().catch(() => {}) }
process.send({ kind: 'result', result: { cases: cases.length, names: cases, externalClientPid: process.pid, serverPid: Number(process.env.OWN_SERVER_PID), bridgePid,
sdkOnlyExternalClient: true, maxObservedHttpResponseBytes: maxResponseBytes, clientResponseLimitBytes: 1048576, stdioSdkBufferLimitBytes: 131072,
sdkOnlyExternalClient: true, maxObservedHttpResponseBytes: maxResponseBytes, clientResponseLimitBytes: responseLimitBytes, stdioSdkBufferLimitBytes: 131072,
discoveryByTransport, discoveryStdioSdkBufferLimitBytes: responseLimitBytes,
actualPublicStringLimitObserved: 16000, wholeGenericResultEnvelopeLimitAccepted: false,
syntheticPortalJwtAndConsent: true, liveAuthPortal: false, livePortalSender: false, endUserUi: false, wholeExternalDirectionComplete: false } })
} catch (error) { process.send?.({ kind: 'failure', phase, errorType: error?.name ?? 'Error', completedCases: cases }); process.exitCode = 1 }
@@ -12,7 +12,9 @@ const output = resolve(sourceRoot, scenario === 'bridge-vlan' ? 'tmp/mcp-bridge-
const paths = ['backend/src/scripts/mcp-stdio.ts', 'backend/src/routes/mcp-transport.ts', 'backend/src/routes/mcp-oauth.ts', 'backend/src/modules/mcp/store.ts',
'backend/src/modules/mcp/runtime.ts', 'backend/src/modules/mcp/actions.ts', 'backend/src/modules/mcp/security.ts', 'backend/src/modules/mcp/protocol-limits.ts',
'backend/src/modules/mcp/portal-revocation.ts', 'backend/src/plugins/auth.ts', 'backend/src/index.ts', 'node_modules/@modelcontextprotocol/client/package.json', 'node_modules/@modelcontextprotocol/server/package.json']
if (scenario === 'bridge-vlan') paths.push('backend/scripts/mcp-external-process/bridge-vlan-client.mjs', 'backend/scripts/mcp-external-process/bridge-vlan-fixture.ts',
if (scenario === 'acceptance') paths.push('backend/scripts/mcp-external-process/client.mjs', 'backend/scripts/mcp-external-process/server-fixture.ts',
'backend/src/routes/mcp-portal.ts', 'backend/src/modules/mcp/portal-revocation-event.ts', 'backend/src/services/router-logs-query.ts')
else paths.push('backend/scripts/mcp-external-process/bridge-vlan-client.mjs', 'backend/scripts/mcp-external-process/bridge-vlan-fixture.ts',
'backend/src/modules/mcp/bridge-vlan-actions.ts', 'backend/src/services/bridge-vlan-configuration.ts', 'packages/contracts/src/bridge-vlan.ts')
async function pins() { const result = []; for (const path of paths) result.push({ path, sha256: createHash('sha256').update(await readFile(resolve(sourceRoot, path))).digest('hex') }); return result }
const before = await pins(), directory = await mkdtemp(join(resolve(sourceRoot, 'tmp'), 'mm-mcp-external-')), state = join(directory, 'state'); await mkdir(state)
@@ -1,5 +1,5 @@
import assert from "node:assert/strict"
import { createHash } from "node:crypto"
import { createHash, createHmac, randomUUID } from "node:crypto"
import { spawn } from "node:child_process"
import { createServer } from "node:net"
import { fileURLToPath, pathToFileURL } from "node:url"
@@ -20,6 +20,7 @@ const { pool, closePool } = await import(source("db/index.js"))
const { applySqlMigrations } = await import(source("db/migrate.js"))
const { mcpConnectionCreateSchema } = await import("@mmapp/contracts/mcp")
const { createMcpConnection, rotateConnectionToken, setConnectionStatus, updateMcpSettings } = await import(source("modules/mcp/store.js"))
const { listActions } = await import(source("modules/mcp/actions.js")) as typeof import("../../src/modules/mcp/actions.js")
const { env } = await import(source("config.js"))
const { buildApp } = await import(source("index.js"))
const actor = { id: "owned-external-process-owner", email: "[email protected]", name: "Owned external SDK fixture", apps: ["mm"],
@@ -28,7 +29,27 @@ let currentId: string | undefined, app: Awaited<ReturnType<typeof buildApp>> | u
const credentials: string[] = []
const bridgePids = new Set<number>()
let childExit: Promise<number> | undefined
let releaseLogRead: (() => void) | undefined
let logReadCompleted: Promise<void> | undefined
let revocationOwnerId: string | undefined
const revokedLogRequests: { connectionId: string; tool: string }[] = []
const originalConnect = pool.connect
const delay = (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms))
async function deliverPortalRevocation(ownerId: string) {
const secret = randomUUID() + randomUUID()
process.env.MCP_PORTAL_WEBHOOK_SECRET = secret
const occurredAt = Date.now(), timestamp = String(Math.floor(occurredAt / 1000))
const eventId = randomUUID(), body = JSON.stringify({ version: 2, eventId, userId: ownerId, type: "permissions.changed", eventOccurredAt: occurredAt })
const signature = "sha256=" + createHmac("sha256", secret).update(timestamp + "." + body).digest("hex")
const response = await fetch(origin + "/_internal/mcp/portal/revocations", {
method: "POST", headers: { "content-type": "application/json", "x-mm-timestamp": timestamp, "x-mm-signature": signature },
body, signal: AbortSignal.timeout(5000),
})
assert.equal(response.status, 200)
assert.deepEqual(await response.json(), { ok: true, duplicate: false })
assert.equal((await pool.query("SELECT count(*)::int AS count FROM mcp_portal_events WHERE id=$1", [eventId])).rows[0].count, 1)
return occurredAt
}
function alive(pid: number) { try { process.kill(pid, 0); return true } catch (error) { if ((error as NodeJS.ErrnoException).code === "ESRCH") return false; throw error } }
async function drainChildAndBridges() {
if (child && child.exitCode === null && child.signalCode === null) { child.kill(); await Promise.race([childExit, delay(5000).then(() => { throw Error("Owned external child failed to exit") })]) }
@@ -47,9 +68,18 @@ try {
const secret = "OWNED_DEVICE_CREDENTIAL_MUST_NOT_ESCAPE"
const allowed = (await pool.query("INSERT INTO servers(name,host,enabled,password) VALUES('owned allowed','192.0.2.1',false,$1) RETURNING id", [secret])).rows[0].id
const forbidden = (await pool.query("INSERT INTO servers(name,host,enabled) VALUES('owned forbidden','192.0.2.2',false) RETURNING id")).rows[0].id
await pool.query(`INSERT INTO router_log_entries(server_id,server_name,host,router_id,raw_time,received_at,effective_at,topics,level,message,generation)
SELECT id,name,host,'fixture','00:00:01',now(),now()-n*interval '1 second',ARRAY['system'],'info','external-sdk-log-'||n,0
FROM servers CROSS JOIN generate_series(1,2) AS n WHERE id=ANY($1::int[])`, [[allowed, forbidden]])
await updateMcpSettings({ enabled: true }, actor)
app = await buildApp({ startScheduler: false, logger: false }); await app.listen({ port, host: "127.0.0.1" })
const jwt = await new SignJWT({ sub: actor.id, apps: actor.apps, permissions: actor.permissions }).setProtectedHeader({ alg: "HS256" }).setIssuer(env.authIssuer).setIssuedAt().setExpirationTime("1h").sign(new TextEncoder().encode(env.jwtSecret))
const artifactOwner = { ...actor, id: actor.id + "-artifact" }
const signArtifactOwner = () => new SignJWT({ sub: artifactOwner.id, apps: artifactOwner.apps, permissions: artifactOwner.permissions })
.setProtectedHeader({ alg: "HS256" }).setIssuer(env.authIssuer).setIssuedAt().setExpirationTime("1h").sign(new TextEncoder().encode(env.jwtSecret))
let artifactConnectionId: string | undefined
const planOwner = { ...actor, id: actor.id + "-approved-plan", permissions: [...actor.permissions, "mm:mcp:approve"] }
let planConnectionId: string | undefined
const portalHeaders = { authorization: `Bearer ${jwt}`, "content-type": "application/json" }
assert.equal((await pool.query("SELECT count(*)::int AS count FROM mcp_connections WHERE owner_id=$1", [actor.id])).rows[0].count, 0)
const callback = "http://localhost:45678/callback?registered=1"
@@ -123,10 +153,131 @@ try {
if (message.kind !== "control" || !Number.isSafeInteger(message.id)) return
try {
let value: unknown
if (message.command === "new-connection") {
if (message.command === "new-revocable-plan-connection") {
assert(!planConnectionId)
const issued = await createMcpConnection(mcpConnectionCreateSchema.parse({
name: "Owned approved plan revocation", mode: "approval", permissions: ["mm:settings:admin"],
expiresAt: new Date(Date.now() + 600000).toISOString(),
scope: { serverIds: [Number(allowed), Number(forbidden)], operationIds: ["logs.settings.update", "changes.apply"], allowGlobal: true },
}), planOwner)
currentId = planConnectionId = issued.connection.id
const ownerJwt = await new SignJWT({ sub: planOwner.id, apps: planOwner.apps, permissions: planOwner.permissions })
.setProtectedHeader({ alg: "HS256" }).setIssuer(env.authIssuer).setIssuedAt().setExpirationTime("1h").sign(new TextEncoder().encode(env.jwtSecret))
credentials.push(issued.token, ownerJwt)
value = { token: issued.token, ownerJwt }
} else if (message.command === "revoke-approved-plan-owner") {
assert(planConnectionId && currentId === planConnectionId)
const plans = (await pool.query("SELECT id,state,approved_by FROM mcp_plans WHERE connection_id=$1", [planConnectionId])).rows
assert.equal(plans.length, 1)
assert.equal(plans[0].state, "approved")
assert.equal(plans[0].approved_by, planOwner.id)
await deliverPortalRevocation(planOwner.id)
value = { ok: true }
} else if (message.command === "new-artifact-connection") {
assert(!artifactConnectionId)
const issued = await createMcpConnection(mcpConnectionCreateSchema.parse({
name: "Owned export revocation", mode: "observer", permissions: ["mm:servers:read"],
expiresAt: new Date(Date.now() + 600000).toISOString(),
scope: { serverIds: [Number(allowed)], operationIds: ["logs.export"], allowGlobal: false },
}), artifactOwner)
currentId = artifactConnectionId = issued.connection.id
const ownerJwt = await signArtifactOwner()
credentials.push(issued.token, ownerJwt)
value = { token: issued.token, ownerJwt }
} else if (message.command === "revoke-artifact-owner") {
assert(artifactConnectionId && currentId === artifactConnectionId)
const occurredAt = await deliverPortalRevocation(artifactOwner.id)
while (Math.floor(Date.now() / 1000) <= Math.floor(occurredAt / 1000)) await delay(25)
const ownerJwt = await signArtifactOwner()
credentials.push(ownerJwt)
value = { ownerJwt }
} else if (["arm-log-revocation", "arm-log-revocation-after-commit", "arm-log-follow-revocation"].includes(message.command ?? "")) {
assert(!releaseLogRead)
const afterCommit = message.command === "arm-log-revocation-after-commit"
const follow = message.command === "arm-log-follow-revocation"
const owner = { ...actor, id: `${actor.id}-${follow ? "follow" : afterCommit ? "postcommit" : "precommit"}` }
revocationOwnerId = owner.id
if (follow) {
await pool.query("UPDATE servers SET enabled=true WHERE id=$1", [allowed])
await pool.query("UPDATE router_log_settings SET enabled=true WHERE id=1")
}
const issued = await createMcpConnection(mcpConnectionCreateSchema.parse({
name: "Owned portal revocation during log read", mode: "observer", permissions: ["mm:servers:read"],
expiresAt: new Date(Date.now() + 600000).toISOString(),
scope: { serverIds: [Number(allowed)], operationIds: [follow ? "logs.follow" : "logs.query"], allowGlobal: false },
}), owner)
currentId = issued.connection.id
revokedLogRequests.push({ connectionId: currentId, tool: follow ? "mm_logs_follow" : "mm_logs_query" })
credentials.push(issued.token)
let completed!: () => void
logReadCompleted = new Promise<void>(resolve => { completed = resolve })
const hold = new Promise<void>(resolve => { releaseLogRead = resolve })
pool.connect = ((callback?: unknown) => {
if (typeof callback === "function") return Reflect.apply(originalConnect, pool, [callback])
return (async () => {
const client = await originalConnect.call(pool)
const query = client.query, release = client.release
let completingLog = false
client.query = (async (...args: unknown[]) => {
const params = args[1]
if (typeof args[0] === "string" && args[0].startsWith("UPDATE mcp_requests SET status=$2,result=")
&& Array.isArray(params) && params[1] === "completed" && String(params[2]).includes(follow ? "external-sdk-live-event" : "external-sdk-log-1")) {
completingLog = true
if (!afterCommit) { completed(); await hold }
}
const result = await Reflect.apply(query, client, args)
if (afterCommit && completingLog && args[0] === "COMMIT") { completed(); await hold }
return result
}) as typeof query
client.release = error => {
client.query = query
client.release = release
release(error)
}
return client
})()
}) as typeof originalConnect
value = { token: issued.token }
} else if (message.command === "publish-follow-fixture-row") {
assert(releaseLogRead && currentId && revokedLogRequests.at(-1)?.tool === "mm_logs_follow")
const deadline = Date.now() + 3000
for (;;) {
const live = await pool.query("SELECT count(*)::int AS count FROM mcp_read_leases WHERE connection_id=$1", [currentId])
if (live.rows[0].count === 1) break
assert(Date.now() < deadline, "Owned follow did not acquire a live read lease")
await delay(25)
}
await pool.query(`INSERT INTO router_log_entries(server_id,server_name,host,router_id,raw_time,received_at,effective_at,topics,level,message,generation)
SELECT id,name,host,'fixture','00:00:01',now(),now(),ARRAY['system'],'info','external-sdk-live-event',0
FROM servers WHERE id=$1`, [allowed])
value = { ok: true }
} else if (message.command === "deliver-log-revocation") {
assert(logReadCompleted && releaseLogRead && currentId && revocationOwnerId)
try {
await Promise.race([logReadCompleted, delay(5000).then(() => { throw Error("Owned log read did not reach the barrier") })])
await deliverPortalRevocation(revocationOwnerId)
value = { ok: true }
} finally {
pool.connect = originalConnect
releaseLogRead()
releaseLogRead = undefined
logReadCompleted = undefined
revocationOwnerId = undefined
}
} else if (message.command === "new-connection") {
const issued = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: "Owned external SDK process", mode: "observer", expiresAt: new Date(Date.now() + 1200000).toISOString(),
permissions: ["mm:servers:read"], scope: { serverIds: [Number(allowed)], operationIds: ["servers.list"], allowGlobal: false }, limits: { callsPerMinute: 200 } }), actor)
currentId = issued.connection.id; credentials.push(issued.token); value = { token: issued.token, connectionId: currentId }
} else if (message.command === "discovery-connection") {
const operations = listActions().map(action => action.operation)
const permissions = [...new Set(operations.flatMap(operation => operation.requiredPermissions))]
const issued = await createMcpConnection(mcpConnectionCreateSchema.parse({
name: "Owned full catalogue discovery", mode: "approval", expiresAt: new Date(Date.now() + 600000).toISOString(),
permissions, scope: { serverIds: [Number(allowed)], operationIds: operations.map(operation => operation.id), allowGlobal: true },
}), { ...actor, permissions: [...new Set([...actor.permissions, ...permissions])] })
currentId = issued.connection.id
credentials.push(issued.token)
value = { token: issued.token, names: operations.map(operation => operation.toolName) }
} else if (message.command === "large-result" || message.command === "reset-result") {
await pool.query("UPDATE servers SET comment=$2 WHERE id=$1", [allowed, message.command === "large-result" ? "Z".repeat(40000) + "OWNED_TRUNCATION_TAIL" : ""])
value = { ok: true }
@@ -144,6 +295,22 @@ try {
const code = await childExit; clearTimeout(timer)
if (code !== 0 || !result) { console.log("OWNED_EXTERNAL_PROCESS_FAILURE", JSON.stringify({ code, failure, stdout: stdout.slice(0,1000), stderr: stderr.slice(0,1000) })); throw Error("External SDK process did not complete") }
const logs = JSON.stringify((await pool.query("SELECT tool,input,result,error_code FROM mcp_requests ORDER BY created_at")).rows)
assert.equal(revokedLogRequests.length, 3)
assert.ok(planConnectionId)
const invalidated = (await pool.query("SELECT state FROM mcp_plans WHERE connection_id=$1", [planConnectionId])).rows
assert.deepEqual(invalidated, [{ state: "invalidated" }])
assert.equal((await pool.query("SELECT count(*)::int AS count FROM mcp_operations WHERE connection_id=$1", [planConnectionId])).rows[0].count, 0)
assert.equal((await pool.query("SELECT enabled FROM router_log_settings WHERE id=1")).rows[0].enabled, true)
assert.equal((await pool.query("SELECT count(*)::int AS count FROM mcp_artifacts WHERE connection_id=$1 AND expires_at>now()", [artifactConnectionId])).rows[0].count, 1)
for (const { connectionId, tool } of revokedLogRequests) {
const revokedRead = (await pool.query("SELECT status,error_code,result FROM mcp_requests WHERE connection_id=$1 AND tool=$2 AND status='error'", [connectionId, tool])).rows
assert.equal(revokedRead.length, 1)
assert.equal(revokedRead[0].status, "error")
assert.deepEqual(Object.keys(revokedRead[0].result), ["message"])
assert.doesNotMatch(JSON.stringify(revokedRead[0].result), /external-sdk-log-1|external-sdk-live-event|"entries"|"nextCursor"|"cursor"/)
assert.ok(["OWNER_CLAIMS_REVOKED", "UNAUTHORIZED"].includes(revokedRead[0].error_code))
assert.equal((await pool.query("SELECT count(*)::int AS count FROM mcp_read_leases WHERE connection_id=$1", [connectionId])).rows[0].count, 0)
}
for (const token of [...credentials, jwt, secret]) { assert(!logs.includes(token)); assert(!stdout.includes(token)); assert(!stderr.includes(token)) }
const final = { ...(result as object), actualOwnPg: true, actualProductionHttpServer: true, serverFixturePid: process.pid,
controlTransitions: transcript, auditCredentialLeakAbsent: true, denialWithoutConnection: true,
@@ -151,5 +318,7 @@ try {
await writeFile(process.env.OWN_EXTERNAL_RESULT_FILE!, JSON.stringify(final, null, 2))
console.log("Owned external process SDK result:", JSON.stringify(final))
} finally {
pool.connect = originalConnect
releaseLogRead?.()
try { await drainChildAndBridges() } finally { try { if (app) await app.close() } finally { await closePool() } }
}
@@ -0,0 +1,105 @@
import assert from 'node:assert/strict'
import {execFileSync} from 'node:child_process'
import {randomBytes, X509Certificate} from 'node:crypto'
import {mkdtemp, mkdir, cp, readFile, rm, chmod, access} from 'node:fs/promises'
import {createServer} from 'node:net'
import {tmpdir} from 'node:os'
import {dirname, join, resolve} from 'node:path'
import EmbeddedPostgres from 'embedded-postgres'
import pg from 'pg'
import {poolOptions, retainPublisherTlsPeer} from './writer-profile/operator-contract.mjs'
import {sha} from './writer-profile/operator-files.mjs'
const openssl = process.env.MMAPP_TLS_FIXTURE_OPENSSL || 'openssl'
const host = 'localhost'
const reservePort = async () => {
const listener = createServer()
await new Promise((ready, reject) => listener.once('error', reject).listen(0, '127.0.0.1', ready))
return listener
}
const releasePort = listener => new Promise((ready, reject) => listener.close(error => error ? reject(error) : ready()))
const sourcePort = await reservePort()
const clonePort = await reservePort()
const port = sourcePort.address().port, clonePortNumber = clonePort.address().port
await Promise.all([releasePort(sourcePort), releasePort(clonePort)])
const transport = {host, port, user: 'mcp_test', password: randomBytes(24).toString('hex'), database: 'postgres'}
const certFile = 'server.pem', keyFile = 'server-key.pem'
const root = await mkdtemp(join(tmpdir(), 'mm-writer-tls-owned-'))
const databaseDir = join(root, 'pg'), cloneDir = join(root, 'pg-clone')
const sourceTlsDir = join(root, 'source-tls'), cloneTlsDir = join(root, 'clone-tls')
const cluster = (databaseDir, port, tlsDir) => new EmbeddedPostgres({databaseDir, port, user: transport.user, password: transport.password,
persistent: true, createPostgresUser: false, postgresFlags: ['-h', '127.0.0.1', '-c', 'ssl=on', '-c', `ssl_cert_file=${join(tlsDir, certFile)}`, '-c', `ssl_key_file=${join(tlsDir, keyFile)}`],
onLog: () => {}, onError: () => {}})
const postgres = cluster(databaseDir, port, sourceTlsDir), clone = cluster(cloneDir, clonePortNumber, cloneTlsDir)
let sourceStarted = false, cloneStarted = false
const fingerprint = ca => sha(new X509Certificate(ca).publicKey.export({format:'der', type:'spki'}))
const issue = async tlsDir => {
await mkdir(tlsDir, {mode:0o700})
execFileSync(openssl, ['req', '-new', '-newkey', 'rsa:2048', '-x509', '-nodes', '-days', '1', '-subj', '/CN=localhost',
'-addext', 'subjectAltName=DNS:localhost', '-keyout', keyFile, '-out', certFile], {cwd: tlsDir, stdio: 'ignore', windowsHide: true})
await chmod(join(tlsDir, keyFile), 0o600)
return readFile(join(tlsDir, certFile))
}
const withClient = async (config, work) => {
const pool = new pg.Pool({connectionTimeoutMillis:5000, ...config})
let client
try { client = await pool.connect(); return await work(client) }
finally { client?.release(true); await pool.end() }
}
const query = "SELECT (pg_control_system()).system_identifier::text AS system, oid::text AS oid FROM pg_database WHERE datname=current_database()"
try {
await postgres.initialise()
const first = await issue(sourceTlsDir)
await postgres.start(); sourceStarted = true
const pin = {serverName:host, caSha256:sha(first), spkiSha256:fingerprint(first)}
const request = {tls:pin, roles:{publisher:{name:transport.user}}, source:{databaseName:transport.database}}
const baseline = await withClient(poolOptions(transport, request, first), async client => {
const retained = retainPublisherTlsPeer(client, pin)
retained()
const {rows:[source]} = await client.query(query)
const {rows:[status]} = await client.query('SELECT ssl FROM pg_stat_ssl WHERE pid=pg_backend_pid()')
assert.equal(status.ssl, true)
await client.query("CREATE TABLE owned_tls_fixture (note text PRIMARY KEY); INSERT INTO owned_tls_fixture VALUES ('original')")
const connection = client.connection, socket = connection.stream
connection.stream = undefined
try { assert.throws(retained, /retained TLS peer unavailable/) } finally { connection.stream = socket }
return source
})
await withClient({...transport, ssl:false}, async client => assert.throws(() => retainPublisherTlsPeer(client,pin), /retained TLS peer unavailable/))
await postgres.stop(); sourceStarted = false
await cp(databaseDir, cloneDir, {recursive:true, force:false, errorOnExist:true})
await assert.rejects(access(join(cloneDir, keyFile)), {code:'ENOENT'})
const second = await issue(cloneTlsDir)
assert.notEqual(fingerprint(second), pin.spkiSha256)
await postgres.start(); sourceStarted = true
await clone.start(); cloneStarted = true
const cloneTransport = {...transport, port:clonePortNumber}
const bothTrusted = Buffer.concat([first,second])
await assert.rejects(withClient(poolOptions(cloneTransport, {...request,tls:{...pin,caSha256:sha(bothTrusted)}}, bothTrusted),
() => assert.fail('Wrong physical cluster must not reach application SQL')), /TLS SPKI refused/)
await withClient({...cloneTransport, ssl:{ca:bothTrusted, servername:host, rejectUnauthorized:true}}, async client => {
assert.equal(client.connection.stream.authorized, true)
const {rows:[source]} = await client.query(query)
assert.deepEqual(source, baseline)
assert.throws(() => retainPublisherTlsPeer(client,pin), /TLS SPKI refused/)
retainPublisherTlsPeer(client,{...pin,spkiSha256:fingerprint(second)})()
assert.deepEqual((await client.query('SELECT note FROM owned_tls_fixture')).rows, [{note:'original'}])
await client.query("INSERT INTO owned_tls_fixture VALUES ('clone-only')")
})
await withClient(poolOptions(transport, request, first), async client => {
retainPublisherTlsPeer(client,pin)()
assert.deepEqual((await client.query('SELECT note FROM owned_tls_fixture')).rows, [{note:'original'}])
})
console.log('Genuine PostgreSQL TLS: original accepted; independently writable physical clone with identical DB coordinates and a different CA-valid TLS key refused')
} finally {
const stops = await Promise.allSettled([
cloneStarted ? clone.stop() : undefined,
sourceStarted ? postgres.stop() : undefined,
])
assert(stops.every(result => result.status === 'fulfilled'), 'Owned cluster stop unconfirmed')
assert.equal(dirname(resolve(root)), resolve(tmpdir()))
assert.match(root, /mm-writer-tls-owned-/)
await rm(root, {recursive:true, force:true, maxRetries:20, retryDelay:100})
await assert.rejects(access(root), {code:'ENOENT'})
console.log('Owned PostgreSQL and temporary TLS material removed')
}
+6 -2
View File
@@ -165,6 +165,10 @@ const chrVpn = process.argv.includes('--chr-vpn-catalog')
const chrFilters = process.argv.includes('--chr-filter-write')
const chrLegacyOspf = process.argv.includes('--chr-ospf-legacy')
const greRevision = process.argv.includes('--gre-revision')
const usersBindings = process.argv.includes('--users-bindings')
if (usersBindings && (process.argv.length !== 3 || process.argv[2] !== '--users-bindings')) throw new Error('User bindings acceptance requires its own disposable cluster')
const bridgeVlanConfiguration = process.argv.includes('--bridge-vlan-configuration')
if (bridgeVlanConfiguration && process.argv.slice(2).some(arg => arg !== '--bridge-vlan-configuration')) throw new Error('Bridge VLAN acceptance requires its own disposable cluster')
const applicationAdapters = process.argv.includes('--application-adapters')
const scopedRoutingDiagnostics = process.argv.includes('--scoped-routing-diagnostics')
const interfaceEventsCompatibility = process.argv.includes('--interface-events-compatibility')
@@ -246,7 +250,7 @@ try {
if (regressionOnly) console.log('Selected regression only: primary integration and terminal restore are not run')
if (profilesOnly) console.log('Profiles-only regression matrix: primary integration NOT RUN; terminal restore NOT RUN; full acceptance is not claimed')
const result = regressionOnly || profilesOnly ? 0 : await new Promise((resolve, reject) => {
const child = spawn(process.execPath, pollMemory ? ['--max-old-space-size=192', '--max-semi-space-size=16', fileURLToPath(new URL('../dist-mcp-memory/services/server-poll-memory.owned.test.js', import.meta.url))] : ['--import', 'tsx', principalSecuritySource ? 'src/modules/mcp/principal-security-source.owned.test.ts' : pollJournalSnapshot ? 'src/services/server-poll-journal-snapshot.owned.test.ts' : pollTerminalArchive ? 'src/services/server-poll-terminal-archive.owned.test.ts' : pollArchiveSchema ? 'src/db/server-poll-terminal-archives.sql.test.ts' : telemetrySettingsConfiguration ? 'src/modules/mcp/telemetry-settings-configuration.owned.test.ts' : routingReservationSchema ? 'src/db/routing-reservation-schema.owned.test.ts' : routingForwardRounds ? 'src/modules/mcp/routing-network-forward-rounds.owned.test.ts' : flowActiveServices ? 'src/services/traffic-flow-active-services.owned.test.ts' : flowActiveRebuild ? 'src/services/traffic-flow-active-rebuild.owned.test.ts' : flowQuality ? 'src/services/traffic-flow-quality.owned.test.ts' : flowWorkerIpc ? 'src/services/traffic-flow-worker-ipc.owned.test.ts' : manualUptime ? 'src/modules/mcp/manual-uptime-http.test.ts' : manualPingGroup ? 'src/modules/mcp/manual-ping-group-http.test.ts' : manualTraffic ? 'src/modules/mcp/manual-traffic-http.test.ts' : manualApiPing ? 'src/modules/mcp/manual-api-ping-http.test.ts' : simpleSpeedChr ? 'src/modules/mcp/simple-speed-chr.test.ts' : simpleSpeed ? 'src/modules/mcp/simple-speed-http.test.ts' : externalPeerMetadata ? 'src/services/bandwidth-external-peer-metadata.sql.test.ts' : operatorPeerMetadata ? 'src/services/bandwidth-operator-metadata.sql.test.ts' : publicRegistryMetadata ? 'src/services/bandwidth-public-registry.sql.test.ts' : operatorDockerSql ? 'src/services/bandwidth-operator-docker-stop.sql.test.ts' : probeOperationRetirement ? 'src/services/server-probe-operation-retirement.owned.test.ts' : ordinaryOperationRetirement ? 'src/modules/mcp/ordinary-operation-retirement.owned.test.ts' : pollOperationRetirement ? 'src/services/server-poll-operation-retirement.owned.test.ts' : mcpOperationOrigin ? 'src/modules/mcp/operation-execution-origin.sql.test.ts' : mcpSpeedOriginal ? 'src/modules/mcp/speed-bandwidth-original.sql.test.ts' : routerConfigurationGuard ? 'src/services/router-configuration-guard.sql.test.ts' : scheduledSpeedOriginal ? 'src/services/bandwidth-scheduled-speed-original.sql.test.ts' : bandwidthSpeedWorkflow ? 'src/services/bandwidth-speed-workflow.sql.test.ts' : restSpeedOriginal ? 'src/services/bandwidth-rest-speed-original.sql.test.ts' : operationLocks ? 'src/services/operation-config-lock-lease.sql.test.ts' : serviceExecutionOrigin ? 'src/modules/mcp/service-execution-origin.sql.test.ts' : bandwidthNodeSource ? 'src/services/bandwidth-node-source.sql.test.ts' : bandwidthExternalRegistry ? 'src/services/bandwidth-external-node-registry.sql.test.ts' : bandwidthNodeRegistry ? 'src/services/bandwidth-node-registry.sql.test.ts' : bandwidthControlSource ? 'src/services/bandwidth-control-source.sql.test.ts' : bandwidthAdmission ? 'src/services/bandwidth-admission.sql.test.ts' : serverPollStorageFresh ? 'src/services/server-poll-storage-fresh.sql.test.ts' : serverPollStorage ? 'src/services/server-poll-storage.sql.test.ts' : probeStorageFresh ? 'src/services/server-probe-storage-fresh.sql.test.ts' : probeLegacyOrphans ? 'src/services/server-probe-legacy-orphans.sql.test.ts' : diagnosticCancelHost ? 'src/modules/mcp/diagnostic-cancellation-storage.sql.test.ts' : ipsecExportHttp ? 'src/modules/mcp/ipsec-existing-certificate-export-http.sql.test.ts' : ipsecExportJournal ? 'src/services/ipsec-existing-certificate-export-rest-journal.sql.test.ts' : migrationDataInventory ? 'src/db/migration-data-inventory.sql.test.ts' : probeStorageHistory ? 'src/services/server-probe-storage-history.sql.test.ts' : probeStorageJournal ? 'src/services/server-probe-storage-journal.sql.test.ts' : controlledSchemaReference ? 'src/db/controlled-schema-reference.sql.test.ts' : controlledMigrations ? 'src/db/controlled-migrations.sql.test.ts' : databaseSchemaControl ? 'src/db/database-schema-control.sql.test.ts' : migrationSchemaInventory ? 'src/db/migration-schema-inventory.sql.test.ts' : routingNetworkNative ? 'src/modules/mcp/routing-network-native.sql.test.ts' : routingReservationRuntime ? 'src/modules/mcp/routing-reservation-runtime.sql.test.ts' : routingReservations ? 'src/modules/mcp/routing-reservation.sql.test.ts' : collectorPartitionWriters ? 'src/services/collector-partition-writes.sql.test.ts' : serverPollDurable ? 'src/services/server-poll-durable-session.sql.test.ts' : routingRecoveryAuthority ? 'src/modules/mcp/routing-recovery-authority.sql.test.ts' : legacyFlowRebuild ? 'src/test/legacy-flow-rebuild-sql.ts' : probeJournal ? 'src/services/server-probe-journal.sql.test.ts' : flowHostKeys ? 'src/modules/mcp/flow-host-keys-http.test.ts' : flowRebuildMcp ? 'src/modules/mcp/flow-facts-rebuild-http.test.ts' : flowRebuildLine truncated
const child = spawn(process.execPath, pollMemory ? ['--max-old-space-size=192', '--max-semi-space-size=16', fileURLToPath(new URL('../dist-mcp-memory/services/server-poll-memory.owned.test.js', import.meta.url))] : ['--import', 'tsx', usersBindings ? 'src/modules/mcp/users-bindings-owned.test.ts' : bridgeVlanConfiguration ? 'src/modules/mcp/bridge-vlan-configuration-http.test.ts' : principalSecuritySource ? 'src/modules/mcp/principal-security-source.owned.test.ts' : pollJournalSnapshot ? 'src/services/server-poll-journal-snapshot.owned.test.ts' : pollTerminalArchive ? 'src/services/server-poll-terminal-archive.owned.test.ts' : pollArchiveSchema ? 'src/db/server-poll-terminal-archives.sql.test.ts' : telemetrySettingsConfiguration ? 'src/modules/mcp/telemetry-settings-configuration.owned.test.ts' : routingReservationSchema ? 'src/db/routing-reservation-schema.owned.test.ts' : routingForwardRounds ? 'src/modules/mcp/routing-network-forward-rounds.owned.test.ts' : flowActiveServices ? 'src/services/traffic-flow-active-services.owned.test.ts' : flowActiveRebuild ? 'src/services/traffic-flow-active-rebuild.owned.test.ts' : flowQuality ? 'src/services/traffic-flow-quality.owned.test.ts' : flowWorkerIpc ? 'src/services/traffic-flow-worker-ipc.owned.test.ts' : manualUptime ? 'src/modules/mcp/manual-uptime-http.test.ts' : manualPingGroup ? 'src/modules/mcp/manual-ping-group-http.test.ts' : manualTraffic ? 'src/modules/mcp/manual-traffic-http.test.ts' : manualApiPing ? 'src/modules/mcp/manual-api-ping-http.test.ts' : simpleSpeedChr ? 'src/modules/mcp/simple-speed-chr.test.ts' : simpleSpeed ? 'src/modules/mcp/simple-speed-http.test.ts' : externalPeerMetadata ? 'src/services/bandwidth-external-peer-metadata.sql.test.ts' : operatorPeerMetadata ? 'src/services/bandwidth-operator-metadata.sql.test.ts' : publicRegistryMetadata ? 'src/services/bandwidth-public-registry.sql.test.ts' : operatorDockerSql ? 'src/services/bandwidth-operator-docker-stop.sql.test.ts' : probeOperationRetirement ? 'src/services/server-probe-operation-retirement.owned.test.ts' : ordinaryOperationRetirement ? 'src/modules/mcp/ordinary-operation-retirement.owned.test.ts' : pollOperationRetirement ? 'src/services/server-poll-operation-retirement.owned.test.ts' : mcpOperationOrigin ? 'src/modules/mcp/operation-execution-origin.sql.test.ts' : mcpSpeedOriginal ? 'src/modules/mcp/speed-bandwidth-original.sql.test.ts' : routerConfigurationGuard ? 'src/services/router-configuration-guard.sql.test.ts' : scheduledSpeedOriginal ? 'src/services/bandwidth-scheduled-speed-original.sql.test.ts' : bandwidthSpeedWorkflow ? 'src/services/bandwidth-speed-workflow.sql.test.ts' : restSpeedOriginal ? 'src/services/bandwidth-rest-speed-original.sql.test.ts' : operationLocks ? 'src/services/operation-config-lock-lease.sql.test.ts' : serviceExecutionOrigin ? 'src/modules/mcp/service-execution-origin.sql.test.ts' : bandwidthNodeSource ? 'src/services/bandwidth-node-source.sql.test.ts' : bandwidthExternalRegistry ? 'src/services/bandwidth-external-node-registry.sql.test.ts' : bandwidthNodeRegistry ? 'src/services/bandwidth-node-registry.sql.test.ts' : bandwidthControlSource ? 'src/services/bandwidth-control-source.sql.test.ts' : bandwidthAdmission ? 'src/services/bandwidth-admission.sql.test.ts' : serverPollStorageFresh ? 'src/services/server-poll-storage-fresh.sql.test.ts' : serverPollStorage ? 'src/services/server-poll-storage.sql.test.ts' : probeStorageFresh ? 'src/services/server-probe-storage-fresh.sql.test.ts' : probeLegacyOrphans ? 'src/services/server-probe-legacy-orphans.sql.test.ts' : diagnosticCancelHost ? 'src/modules/mcp/diagnostic-cancellation-storage.sql.test.ts' : ipsecExportHttp ? 'src/modules/mcp/ipsec-existing-certificate-export-http.sql.test.ts' : ipsecExportJournal ? 'src/services/ipsec-existing-certificate-export-rest-journal.sql.test.ts' : migrationDataInventory ? 'src/db/migration-data-inventory.sql.test.ts' : probeStorageHistory ? 'src/services/server-probe-storage-history.sql.test.ts' : probeStorageJournal ? 'src/services/server-probe-storage-journal.sql.test.ts' : controlledSchemaReference ? 'src/db/controlled-schema-reference.sql.test.ts' : controlledMigrations ? 'src/db/controlled-migrations.sql.test.ts' : databaseSchemaControl ? 'src/db/database-schema-control.sql.test.ts' : migrationSchemaInventory ? 'src/db/migration-schema-inventory.sql.test.ts' : routingNetworkNative ? 'src/modules/mcp/routing-network-native.sql.test.ts' : routingReservationRuntime ? 'src/modules/mcp/routing-reservation-runtime.sql.test.ts' : routingReservations ? 'src/modules/mcp/routing-reservation.sql.test.ts' : collectorPartitionWriters ? 'src/services/collector-partition-writes.sql.test.ts' : serverPollDurable ? 'src/services/server-poll-durable-session.sql.test.ts' : routingRecoveryAuthority ? 'src/modules/mcp/routing-recovery-authority.sql.test.ts' : legacyFlowRebuild ? 'src/test/legacy-flow-rebuild-sql.ts' : probeJournal ? 'src/services/server-probe-journal.sql.testLine truncated
cwd: pollMemory ? directory : new URL('../', import.meta.url), stdio: 'inherit', env: { ...(pollMemory ? pollMemoryEnvironment : process.env),
...(routingOriginalEffects ? { MMAPP_OWN_ROUTING_ORIGINAL_EFFECTS: 'true' } : {}),
DATABASE_URL: `postgresql://mcp_test:${password}@127.0.0.1:${port}/postgres`,
@@ -257,7 +261,7 @@ try {
child.once('error', reject); child.once('exit', code => resolve(code ?? 1))
})
exitCode = result
if (result === 0 && !regressionOnly && !profilesOnly && !pollMemory && !principalSecuritySource && !pollJournalSnapshot && !pollArchiveSchema && !telemetrySettingsConfiguration && !routingReservationSchema && !routingForwardRounds && !flowActiveServices && !flowActiveRebuild && !flowQuality && !flowWorkerIpc && !manualUptime && !manualPingGroup && !manualTraffic && !manualApiPing && !simpleSpeed && !simpleSpeedChr && !externalPeerMetadata && !operatorPeerMetadata && !publicRegistryMetadata && !operatorDockerSql && !mcpOperationOrigin && !mcpSpeedOriginal && !routerConfigurationGuard && !scheduledSpeedOriginal && !bandwidthSpeedWorkflow && !restSpeedOriginal && !operationLocks && !serviceExecutionOrigin && !bandwidthNodeSource && !bandwidthNodeRegistry && !bandwidthExternalRegistry && !bandwidthControlSource && !bandwidthAdmission && !probeLegacyOrphans && !probeStorageJournal && !probeStorageHistory && !probeJournal && !chr && !flowWrites && !flowRebuild && !flowRebuildMcp && (process.argv.includes('--restore') || process.env.MMAPP_PG_CLIENT_BIN)) {
if (result === 0 && !regressionOnly && !profilesOnly && !usersBindings && !bridgeVlanConfiguration && !pollMemory && !principalSecuritySource && !pollJournalSnapshot && !pollArchiveSchema && !telemetrySettingsConfiguration && !routingReservationSchema && !routingForwardRounds && !flowActiveServices && !flowActiveRebuild && !flowQuality && !flowWorkerIpc && !manualUptime && !manualPingGroup && !manualTraffic && !manualApiPing && !simpleSpeed && !simpleSpeedChr && !externalPeerMetadata && !operatorPeerMetadata && !publicRegistryMetadata && !operatorDockerSql && !mcpOperationOrigin && !mcpSpeedOriginal && !routerConfigurationGuard && !scheduledSpeedOriginal && !bandwidthSpeedWorkflow && !restSpeedOriginal && !operationLocks && !serviceExecutionOrigin && !bandwidthNodeSource && !bandwidthNodeRegistry && !bandwidthExternalRegistry && !bandwidthControlSource && !bandwidthAdmission && !probeLegacyOrphans && !probeStorageJournal && !probeStorageHistory && !probeJournal && !chr && !flowWrites && !flowRebuild && !flowRebuildMcp && (process.argv.includes('--restore') || process.env.MMAPP_PG_CLIENT_BIN)) {
exitCode = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, ['--import', 'tsx', 'src/modules/mcp/database-restore.test.ts'], {
cwd: new URL('../', import.meta.url), stdio: 'inherit', env: { ...process.env,
@@ -6,10 +6,12 @@ import { randomBytes, createHash } from 'node:crypto'
import { createServer } from 'node:net'
import { spawn } from 'node:child_process'
const allowed = new Set(['--default-scoped', '--main-only', '--check-only'])
if (process.argv.slice(2).some(arg => !allowed.has(arg)) || new Set(process.argv.slice(2)).size !== process.argv.slice(2).length || process.argv.includes('--default-scoped') && process.argv.includes('--main-only')) throw new Error('Usage: node backend/scripts/test-routing-controller-baseline.mjs [--default-scoped | --main-only] [--check-only]')
const modes = ['--default-scoped', '--main-only', '--scoped-bare-readback']
const allowed = new Set([...modes, '--check-only']), args = process.argv.slice(2)
const selected = modes.filter(flag => args.includes(flag))
if (args.some(arg => !allowed.has(arg)) || new Set(args).size !== args.length || selected.length > 1) throw new Error('Usage: node backend/scripts/test-routing-controller-baseline.mjs [--default-scoped | --main-only | --scoped-bare-readback] [--check-only]')
const root = resolve(import.meta.dirname, '../..'), require = createRequire(import.meta.url), ts = require('typescript')
const mode = process.argv.includes('--default-scoped') ? 'default-scoped' : process.argv.includes('--main-only') ? 'main-only' : 'main-and-forward-rounds'
const mode = selected[0]?.slice(2) ?? 'main-and-forward-rounds'
const target = join(root, 'backend/src/modules/mcp/routing-controller-active-writer.ts')
await readFile(target)
await readFile(join(root, 'backend/src/modules/mcp/routing-main-transition.ts'))
+2 -2
View File
@@ -7,7 +7,7 @@ export function approvedDefinition(value:unknown){
assert(value&&typeof value==='object');const v=value as Record<string,unknown>
assert.deepEqual(Object.keys(v).sort(),['format','provenance','template']);assert.equal(v.format,'application-writer-source44-definition-approval-v1')
assert.equal(canonical(v.provenance),canonical(BUILD44_PROVENANCE));assert.equal(canonical(v.template),canonical(FIXED44_ROLE_TEMPLATE))
return BUILD44_PROVENANCE.templateSha256
return Object.freeze({definitionSha256:BUILD44_PROVENANCE.templateSha256,manifestSha256:BUILD44_PROVENANCE.manifestSha256})
}
/** Real incremental DATA verifier on caller's retained transaction. No pool or attestor. */
export async function collectSource44Catalogue(s:{query:(sql:string,values?:unknown[])=>Promise<{rows:Record<string,unknown>[]} >},installation:{roleBindings:ConstructorParameters<typeof Fixed44CatalogueValueBridgeData>[0]}){
@@ -21,7 +21,7 @@ export async function collectSource44Catalogue(s:{query:(sql:string,values?:unkn
await s.query('DECLARE mm_publisher44 NO SCROLL CURSOR WITHOUT HOLD FOR '+FIXED44_CATALOGUE_BUILD_SQL,values);cursor=true
for(;;){const page=(await s.query('FETCH FORWARD 128 FROM mm_publisher44')).rows;bridge.acceptPage(page);if(page.length===0)break}
const result=bridge.finish();await s.query('CLOSE mm_publisher44');cursor=false
return {catalogSha256:sha(canonical({values:result.values.snapshotSha256,catalogue:result.catalogue})),definitionSha256:result.catalogue.definitionSha256,leafCount:result.catalogue.leafRelations}
return {catalogSha256:sha(canonical({values:result.values.snapshotSha256,catalogue:result.catalogue})),definitionSha256:result.catalogue.definitionSha256,manifestSha256:result.catalogue.manifestSha256,leafCount:result.catalogue.leafRelations}
}catch(error){primary=error;failed=true;throw error}
finally{
try{if(cursor)await s.query('CLOSE mm_publisher44');await s.query("SELECT pg_catalog.set_config('search_path',$1,true),pg_catalog.set_config('TimeZone',$2,true),pg_catalog.set_config('DateStyle',$3,true)",[settings[0].path,settings[0].zone,settings[0].dates])}
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict'
import {X509Certificate} from 'node:crypto'
import {checkServerIdentity} from 'node:tls'
import {checkServerIdentity,TLSSocket} from 'node:tls'
import {sha} from './operator-files.mjs'
export const roleKeys=Object.freeze(['runtime','operator','capture','publisher','control'])
export const STARTUP_OPTIONS='-c statement_timeout=10000 -c lock_timeout=5000 -c client_encoding=UTF8'
@@ -18,15 +18,29 @@ export function approval(value){
}
// Pinned historical inspection data only; never publication authorization.
export function reconciliationApproval(value){return parsedApproval(value)}
function peerError(pin,host,cert){
const failed=checkServerIdentity(pin.serverName,cert);if(failed)return failed
try{if(host!==pin.serverName||sha(new X509Certificate(cert.raw).publicKey.export({format:'der',type:'spki'}))!==pin.spkiSha256)return Error('Publisher TLS SPKI refused')}catch{return Error('Publisher TLS certificate refused')}
}
// pg replaces connection.stream with the negotiated TLS socket. Hold its
// identity on the retained client; handshake callbacks are not session evidence.
export function retainPublisherTlsPeer(client,pin){
const socket=client.connection?.stream
const assertCurrent=()=>{
assert(socket instanceof TLSSocket&&client.connection?.stream===socket&&socket.encrypted&&socket.authorized&&!socket.destroyed&&socket.writable,'Publisher retained TLS peer unavailable')
}
assertCurrent()
const failed=peerError(pin,socket.servername,socket.getPeerCertificate());if(failed)throw failed
socket.disableRenegotiation()
return assertCurrent
}
export function poolOptions(transport,request,ca){
assert(exact(transport,['host','port','user','password','database']))
assert(typeof transport.host==='string'&&transport.host===request.tls.serverName&&Number.isSafeInteger(transport.port)&&transport.port>0&&transport.port<=65535&&transport.user===request.roles.publisher.name&&typeof transport.password==='string'&&transport.password.length>0&&transport.password.length<=4096&&transport.database===request.source.databaseName)
assert(sha(ca)===request.tls.caSha256);new X509Certificate(ca)
return {host:transport.host,port:transport.port,user:transport.user,password:transport.password,database:transport.database,max:1,connectionTimeoutMillis:5000,idleTimeoutMillis:5000,statement_timeout:10000,lock_timeout:5000,options:STARTUP_OPTIONS,application_name:'mmapp-writer-publisher',client_encoding:'UTF8',
ssl:{ca,servername:request.tls.serverName,rejectUnauthorized:true,checkServerIdentity(host,cert){
const failed=checkServerIdentity(request.tls.serverName,cert);if(failed)return failed
try{if(host!==request.tls.serverName||sha(new X509Certificate(cert.raw).publicKey.export({format:'der',type:'spki'}))!==request.tls.spkiSha256)return Error('Publisher TLS SPKI refused')}catch{return Error('Publisher TLS certificate refused')}
}}}
ssl:{ca,servername:request.tls.serverName,rejectUnauthorized:true,checkServerIdentity:(host,cert)=>peerError(request.tls,host,cert)}}
}
export function assertPublicationApproval(current,catalogue,structure,pinned,manifestSha256){
assert(pinned.expiresAt>Date.now()&&pinned.manifestSha256===manifestSha256&&pinned.definitionSha256===catalogue.definitionSha256)
+39 -17
View File
@@ -3,6 +3,7 @@ import pg from 'pg'
import {createPrivateKey,createPublicKey,sign} from 'node:crypto'
import {open,lstat,realpath,unlink} from 'node:fs/promises'
import {writeFile,withJournalLease} from './operator-files.mjs'
import {retainPublisherTlsPeer} from './operator-contract.mjs'
import path from 'node:path'
import {captureDatabaseSourceObservation,assertDatabaseSourceObservationCurrent} from '../../src/db/database-source-observation.js'
import {MIGRATION_MANIFEST,migrationManifestDigest} from '../../src/db/migration-manifest.js'
@@ -10,7 +11,13 @@ import {MIGRATION_MANIFEST,migrationManifestDigest} from '../../src/db/migration
import {ownedContext,sameOwned,sourceCoordinates,protectedStructure,readCurrent,HISTORY,CURRENT} from './context.mjs'
import {bytes,canonical,sha,nextEpoch,POLICY_DOMAIN,HEAD_DOMAIN,KEY,liveValidity,policySchema,headSchema,publicKey} from '../../src/db/application-writer-profile-format.js'
export function createConnectedPublisher(installation,collectFixedCatalogue,sourceVerifier,approval){
export function requireCurrentCatalogueDefinition(approval,definition){
const manifestSha256=migrationManifestDigest()
if(approval.manifestSha256!==manifestSha256||definition.manifestSha256!==manifestSha256||definition.definitionSha256!==approval.definitionSha256)throw Error('Approved catalogue definition is not bound to this build migration manifest')
return manifestSha256
}
export function createConnectedPublisher(installation,collectFixedCatalogue,sourceVerifier,approval,definition){
const PUBLISHER=installation.roleBindings.publisher.name
class ProfilePublicationError extends Error{
automaticReplay=false
@@ -29,13 +36,18 @@ async function privateKey(filename){
// detached application callbacks or cleanup/COMMIT races.
async function session(pool,deadlineAt,work){
if(!(pool instanceof pg.Pool)||!Number.isSafeInteger(deadlineAt)||deadlineAt<=Date.now()||deadlineAt-Date.now()>120000)throw Error('Bounded genuine publisher pool/lifetime required')
let client,retired=false,released=false,locked=false,begun=false,commitSent=false,committed=false,confirmedValue,proposal,phase='checkout',stopReject,cleanupUnconfirmed=false
let client,assertTransport,retired=false,released=false,locked=false,begun=false,commitSent=false,committed=false,confirmedValue,proposal,phase='checkout',stopReject,cleanupUnconfirmed=false
const stopped=new Promise((_,reject)=>{stopReject=reject});void stopped.catch(()=>{})
const release=bad=>{if(!client||released)return;released=true;client.off('error',fault);client.off('end',fault);try{client.release(bad)}catch{cleanupUnconfirmed=true}}
const fault=()=>{if(retired)return;retired=true;cleanupUnconfirmed=true;release(true);stopReject(Error('Publisher connection/lifetime ended'))}
const timer=setTimeout(fault,Math.max(1,deadlineAt-Date.now())),bounded=p=>Promise.race([p,stopped])
const query=async(text,values)=>{if(retired||Date.now()>=deadlineAt)throw Error('Publisher lifetime closed');return bounded(client.query(text,values))}
const fence=()=>{
if(retired||Date.now()>=deadlineAt)throw Error('Publisher lifetime closed')
try{assertTransport()}catch(error){fault();throw error}
}
const query=async(text,values)=>{fence();return bounded(client.query(text,values))}
const state={query,phase:value=>{phase=value},proposal:value=>{proposal=Object.freeze(value)},begun:value=>{begun=value},commit:async value=>{
fence()
confirmedValue=Object.freeze({...value})
commitSent=true
const response=await bounded(client.query('COMMIT').then(result=>{if(result.command==='COMMIT'){committed=true;begun=false}return result}))
@@ -45,6 +57,7 @@ async function session(pool,deadlineAt,work){
try{
const pending=pool.connect().then(value=>{client=value;if(retired){release(true);return value}client.on('error',fault);client.on('end',fault);return value});void pending.catch(()=>{})
await bounded(pending);if(retired)throw Error('Late publisher checkout')
try{assertTransport=retainPublisherTlsPeer(client,installation.tls)}catch(error){retired=true;release(true);throw error}
await query("SET statement_timeout='10s'");await query("SET lock_timeout='5s'")
phase='exclusive-admission';await query('SELECT pg_catalog.pg_advisory_lock($1::bigint)',[KEY]);locked=true
value=await work(state)
@@ -62,7 +75,16 @@ async function session(pool,deadlineAt,work){
if(failed||cleanupUnconfirmed||retired)throw new ProfilePublicationError(commitSent||begun?'unknown':'not-committed',phase,proposal,cleanupUnconfirmed||retired,primary)
return value
}
async function publishCurrentProfile(pool,{privateKeyFile,expectedHeadSha256,journalDirectory,validityMs=86400000,deadlineAt=Date.now()+120000}){
async function observeCurrentCatalogue(s,manifestSha256){
const security=await protectedStructure(s,installation),catalogue=await collectFixedCatalogue(s)
if(catalogue.manifestSha256!==manifestSha256||catalogue.definitionSha256!==definition.definitionSha256)throw Error('Catalogue evidence differs from the approved migration manifest/definition')
// Inspect one extra row so an unexpected marker cannot hide after the expected prefix.
const expected=MIGRATION_MANIFEST.map(pin=>pin.file.slice(0,-4))
const markers=(await s.query('SELECT id FROM public.schema_migrations ORDER BY id COLLATE "C" LIMIT $1',[expected.length+1])).rows.map(row=>row.id)
if(canonical(markers)!==canonical(expected))throw Error('Exact current migration marker metadata required')
return {security,catalogue,catalogSha256:sha(canonical({base:catalogue.catalogSha256,security:security.sha256}))}
}
async function publishCurrentProfile(pool,{privateKeyFile,expectedHeadSha256,journalDirectory,validityMs=86400000,deadlineAt=Date.now()+120000},manifestSha256){
if(!Number.isSafeInteger(validityMs)||validityMs<1||validityMs>86400000||expectedHeadSha256!==null&&!(/^[a-f0-9]{64}$/).test(expectedHeadSha256))throw Error('Explicit previous head and bounded validity required')
const key=await privateKey(privateKeyFile),directory=await realpath(journalDirectory),pendingFile=path.join(directory,'pending-publication.json')
try{await lstat(pendingFile);throw Error('Pending publication must be reconciled before signing')}catch(error){if(error.code!=='ENOENT')throw error}
@@ -73,18 +95,15 @@ async function publishCurrentProfile(pool,{privateKeyFile,expectedHeadSha256,jou
const observation=await captureDatabaseSourceObservation(facade,options)
await sourceVerifier.verify(facade,observation,options)
if(canonical(sourceCoordinates(first))!==canonical(installation.expectedSource))throw Error('Pinned source mismatch')
const security=await protectedStructure(s,installation),catalogue=await collectFixedCatalogue(s)
const markers=(await s.query('SELECT id FROM public.schema_migrations ORDER BY id LIMIT 45')).rows.map(row=>row.id)
if(canonical(markers)!==canonical(MIGRATION_MANIFEST.map(pin=>pin.file.slice(0,-4))))throw Error('Exact current migration marker metadata required')
const {security,catalogue,catalogSha256}=await observeCurrentCatalogue(s,manifestSha256)
const old=await readCurrent(s,installation,{allowEmpty:true,validNow:false})
if((old?.headSha256??null)!==expectedHeadSha256)throw Error('Expected protected current head mismatch')
const catalogSha256=sha(canonical({base:catalogue.catalogSha256,security:security.sha256}))
if(approval.expiresAt<=Date.now()||approval.manifestSha256!==migrationManifestDigest()||approval.definitionSha256!==catalogue.definitionSha256||approval.catalogSha256!==catalogSha256)throw Error('Independently approved policy differs from actual source44 catalogue')
if(old&&(canonical(old.policy.source)!==canonical(sourceCoordinates(first))||canonical(old.policy.roles)!==canonical(security.roles)||old.policy.manifestSha256!==migrationManifestDigest()||old.policy.definitionSha256!==catalogue.definitionSha256||old.policy.catalogSha256!==catalogSha256))throw Error('Renewal cannot adopt a changed source/subject/policy')
if(approval.expiresAt<=Date.now()||approval.catalogSha256!==catalogSha256)throw Error('Independently approved policy differs from actual catalogue')
if(old&&(canonical(old.policy.source)!==canonical(sourceCoordinates(first))||canonical(old.policy.roles)!==canonical(security.roles)||old.policy.manifestSha256!==manifestSha256||old.policy.definitionSha256!==catalogue.definitionSha256||old.policy.catalogSha256!==catalogSha256))throw Error('Renewal cannot adopt a changed source/subject/policy')
await sourceVerifier.verify(facade,observation,options);await assertDatabaseSourceObservationCurrent(facade,observation,options);sameOwned(first,await ownedContext(s,installation,PUBLISHER,'publisher-read'))
await s.query('COMMIT');s.begun(false)
const number=old?nextEpoch(old.epoch):'1',now=Date.now()
const body=policySchema.parse({format:'application-writer-policy-v1',installationId:installation.installationId,epoch:number,issuedAt:now,notBefore:now,notAfter:now+validityMs,source:sourceCoordinates(first),sourceObservationSha256:sha(canonical(observation)),roles:security.roles,manifestSha256:migrationManifestDigest(),definitionSha256:catalogue.definitionSha256,catalogSha256,futureLeaves:'fixed12-native-policy-v1'})
const body=policySchema.parse({format:'application-writer-policy-v1',installationId:installation.installationId,epoch:number,issuedAt:now,notBefore:now,notAfter:now+validityMs,source:sourceCoordinates(first),sourceObservationSha256:sha(canonical(observation)),roles:security.roles,manifestSha256,definitionSha256:catalogue.definitionSha256,catalogSha256,futureLeaves:'fixed12-native-policy-v1'})
const envelope=(domain,body)=>bytes({body,signature:sign(null,Buffer.from(domain+canonical(body)),key).toString('base64url')})
const policyBytes=envelope(POLICY_DOMAIN,body),policySha256=sha(policyBytes)
const head=headSchema.parse({format:'application-writer-head-v1',installationId:installation.installationId,epoch:number,policySha256,previousHeadSha256:old?.headSha256??null})
@@ -101,8 +120,8 @@ async function publishCurrentProfile(pool,{privateKeyFile,expectedHeadSha256,jou
await s.query(`SELECT id FROM mmapp_control.${CURRENT} WHERE id=1 FOR UPDATE`)
const current=await readCurrent(s,installation,{allowEmpty:true,validNow:false})
if((current?.headSha256??null)!==expectedHeadSha256)throw Error('CAS predecessor changed')
const againSecurity=await protectedStructure(s,installation),againCatalogue=await collectFixedCatalogue(s)
if(sha(canonical({base:againCatalogue.catalogSha256,security:againSecurity.sha256}))!==catalogSha256)throw Error('Policy drift during retained exclusive publication')
const again=await observeCurrentCatalogue(s,manifestSha256)
if(again.catalogSha256!==catalogSha256)throw Error('Policy drift during retained exclusive publication')
if(Date.now()>=body.notAfter||Date.now()>=approval.expiresAt)throw Error('Policy or approval expired before publication dispatch')
await s.query(`INSERT INTO mmapp_control.${HISTORY}(installation_id,epoch,head_sha256,previous_installation_id,previous_epoch,previous_head_sha256,policy_sha256,policy_bytes,head_bytes) VALUES($1::uuid,$2::bigint,$3,$4::uuid,$5::bigint,$6,$7,$8::bytea,$9::bytea)`,[installation.installationId,number,headSha256,old?installation.installationId:null,old?.epoch??null,old?.headSha256??null,policySha256,policyBytes,headBytes])
const changed=old?await s.query(`UPDATE mmapp_control.${CURRENT} SET epoch=$1::bigint,head_sha256=$2 WHERE id=1 AND installation_id=$3::uuid AND epoch=$4::bigint AND head_sha256=$5 RETURNING id`,[number,headSha256,installation.installationId,old.epoch,old.headSha256]):await s.query(`INSERT INTO mmapp_control.${CURRENT}(id,installation_id,epoch,head_sha256) VALUES(1,$1::uuid,$2::bigint,$3) RETURNING id`,[installation.installationId,number,headSha256])
@@ -136,17 +155,17 @@ async function inspectPublication(pool,proposal,{deadlineAt=Date.now()+120000}={
// Read-only diagnostics on the actual protected publisher session. This does
// not admit application/operator writes, bootstrap, restore, or READY.
async function preflightCurrentProfile(pool,{deadlineAt=Date.now()+120000}={}){
const manifestSha256=requireCurrentCatalogueDefinition(approval,definition)
return session(pool,deadlineAt,async s=>{
s.phase('profile-preflight');await s.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');s.begun(true)
const first=await ownedContext(s,installation,PUBLISHER,'publisher-read')
if(canonical(sourceCoordinates(first))!==canonical(installation.expectedSource))throw Error('Pinned preflight source mismatch')
const facade=Object.freeze({query:(sql,values)=>s.query(sql,values)}),options={deadlineAt}
const observation=await captureDatabaseSourceObservation(facade,options),initialSource=await sourceVerifier.verify(facade,observation,options)
const security=await protectedStructure(s,installation),catalogue=await collectFixedCatalogue(s)
const {security,catalogue,catalogSha256}=await observeCurrentCatalogue(s,manifestSha256)
const current=await readCurrent(s,installation)
if(!current)throw Error('Current signed policy required for preflight')
const catalogSha256=sha(canonical({base:catalogue.catalogSha256,security:security.sha256})),manifestSha256=migrationManifestDigest()
if(approval.expiresAt<=Date.now()||approval.manifestSha256!==manifestSha256||approval.definitionSha256!==catalogue.definitionSha256||approval.catalogSha256!==catalogSha256)throw Error('Independent preflight approval differs from actual source44 catalogue')
if(approval.expiresAt<=Date.now()||approval.catalogSha256!==catalogSha256)throw Error('Independent preflight approval differs from actual catalogue')
if(canonical(current.policy.source)!==canonical(sourceCoordinates(first))||canonical(current.policy.roles)!==canonical(security.roles)||current.policy.manifestSha256!==manifestSha256||current.policy.definitionSha256!==catalogue.definitionSha256||current.policy.catalogSha256!==catalogSha256)throw Error('Current signed policy differs from actual source/roles/catalogue')
const terminalSource=await sourceVerifier.verify(facade,observation,options);await assertDatabaseSourceObservationCurrent(facade,observation,options)
sameOwned(first,await ownedContext(s,installation,PUBLISHER,'publisher-read'))
@@ -173,7 +192,10 @@ async function reconcilePendingPublication(pool,{journalDirectory,deadlineAt=Dat
}
return Object.freeze({
preflightCurrentProfile,
publishCurrentProfile:(pool,options)=>withJournalLease(options.journalDirectory,()=>publishCurrentProfile(pool,options)),
publishCurrentProfile:async(pool,options)=>{
const manifestSha256=requireCurrentCatalogueDefinition(approval,definition)
return withJournalLease(options.journalDirectory,()=>publishCurrentProfile(pool,options,manifestSha256))
},
inspectPublication,
reconcilePendingPublication:(pool,options)=>withJournalLease(options.journalDirectory,()=>reconcilePendingPublication(pool,options)),
})
@@ -0,0 +1,150 @@
/** Deployment operator state. Store outside PGDATA, backups and runtime mounts. */
import assert from 'node:assert/strict'
import {constants} from 'node:fs'
import {lstat,open,rename} from 'node:fs/promises'
import path from 'node:path'
import {z} from 'zod'
import {sourceBindingBytes,sourceBindingCanonical,type DatabaseSourceBindingBody} from '../../src/db/database-source-binding.js'
import {protectedDirectory,readProtected,sha,withJournalLease} from './operator-files.mjs'
import {readProtectedDatabaseSourceBindingRegistry, type createProtectedDatabaseSourceBindingVerifier} from './protected-source-registry.mjs'
const digest=z.string().regex(/^[a-f0-9]{64}$/),uuid=z.string().uuid()
const sourcePin=z.object({registryDirectory:z.string().min(1).max(4096),installationId:uuid,publicKeySpkiSha256:digest,currentHeadSha256:digest}).strict()
const directoryPath=z.string().min(1).max(4096)
const approval=z.object({format:z.literal('database-source-activation-approval-v1'),activationDirectory:directoryPath,sourcePin,tlsSpkiSha256:digest,
expectedActivationSha256:digest.nullable()}).strict()
const generation=z.object({generation:uuid,headSha256:digest,bindingSha256:digest,tlsSpkiSha256:digest}).strict()
const stateSchema=z.object({format:z.literal('database-source-activation-v1'),activationDirectory:directoryPath,installationId:uuid,sourceIdentityId:uuid,
publicKeySpkiSha256:digest,status:z.enum(['active','revoked']),generations:z.array(generation).min(1).max(256)}).strict()
const maximum=131072, filename='source-activation.json'
type State=z.infer<typeof stateSchema>
const parse=(bytes:Buffer):unknown=>JSON.parse(new TextDecoder('utf8',{fatal:true}).decode(bytes))
async function present(file:string){
try{await lstat(file);return true}catch(error){if((error as NodeJS.ErrnoException).code==='ENOENT')return false;throw error}
}
// Caller holds the same protected lease used by all activation writers.
async function current(directory:string){
const file=path.join(directory,filename)
assert(!await present(file+'.pending'),'Unresolved activation transition')
if(!await present(file))return undefined
const bytes=await readProtected(file,maximum),state=stateSchema.parse(parse(bytes))
assert.equal(state.activationDirectory,directory,'Activation record belongs to another directory')
assert(sourceBindingBytes(state).equals(bytes),'Noncanonical activation record')
for(const field of ['generation','headSha256','tlsSpkiSha256'] as const){
assert.equal(new Set(state.generations.map(item=>item[field].toLowerCase())).size,state.generations.length,'Reused activation identity')
}
return {state,activationSha256:sha(bytes)}
}
async function persist(directory:string,state:State){
await protectedDirectory(directory)
const bytes=sourceBindingBytes(stateSchema.parse(state))
assert(bytes.length<=maximum)
const file=path.join(directory,filename),pending=file+'.pending'
const parent=await open(directory,constants.O_RDONLY|constants.O_DIRECTORY|constants.O_NOFOLLOW)
let confirmed=false,cleanupUnconfirmed=false
try{
const handle=await open(pending,constants.O_WRONLY|constants.O_CREAT|constants.O_EXCL|constants.O_NOFOLLOW,0o600)
try{await handle.writeFile(bytes);await handle.sync()}finally{await handle.close()}
try{await rename(pending,file);await parent.sync()}
catch{throw Object.assign(Error('Activation terminal requires inspection'),{code:'SOURCE_ACTIVATION_TERMINAL_UNKNOWN'})}
confirmed=true
}finally{try{await parent.close()}catch{cleanupUnconfirmed=confirmed}}
return {activationSha256:sha(bytes),cleanupUnconfirmed}
}
function receipt(state:State,activationSha256:string,outcome:'committed'|'observed'){
return {event:'database-source-activation',outcome,activationSha256,status:state.status,
ordinal:state.generations.length,...state.generations.at(-1),generationAuthenticated:false,readyAuthorized:false,automaticReplay:false}
}
type SourceVerifier=ReturnType<typeof createProtectedDatabaseSourceBindingVerifier>
/** Keep revocation and generation changes fenced throughout one read-only SQL preflight. */
export async function withActiveSourcePreflight<T>(input:{directory:string,expectedSha256:string,sourcePin:z.infer<typeof sourcePin>,expectedSource:DatabaseSourceBindingBody['source'],tlsSpkiSha256:string,deadlineAt:number},
verifier:SourceVerifier,work:(guardedVerifier:SourceVerifier)=>Promise<T>){
const {directory,deadlineAt}=input
const expectedSha256=digest.parse(input.expectedSha256),tlsSpkiSha256=digest.parse(input.tlsSpkiSha256)
const pin=sourcePin.parse(input.sourcePin)
return withJournalLease(directory,async()=>{
async function readActive(){
assert(Date.now()<deadlineAt,'Activation preflight expired')
const active=await current(directory)
assert(active?.state.status==='active'&&active.activationSha256===expectedSha256,'Active activation digest changed')
assert(Date.now()<deadlineAt,'Activation preflight expired')
return active.state
}
const state=await readActive(),generation=state.generations.at(-1)!
const registry=await readProtectedDatabaseSourceBindingRegistry(pin,{deadlineAt})
assert(state.publicKeySpkiSha256===registry.publicKeySpkiSha256&&generation.tlsSpkiSha256===tlsSpkiSha256,'Activation key differs from preflight')
function assertGeneration(evidence:{installationId:string,sourceIdentityId:string,generation:string,ordinal:number,headSha256:string,bindingSha256:string}){
assert(evidence.installationId===state.installationId&&evidence.sourceIdentityId===state.sourceIdentityId&&
evidence.generation===generation.generation&&evidence.ordinal===state.generations.length&&
evidence.headSha256===generation.headSha256&&evidence.bindingSha256===generation.bindingSha256,'Activation differs from signed source generation')
}
assertGeneration({...registry.head,headSha256:registry.headSha256})
assert.equal(sourceBindingCanonical(registry.body.source),sourceBindingCanonical(input.expectedSource),'Activation source differs from reviewed configuration')
await readActive()
let sourceChecks=0
const guardedVerifier:SourceVerifier=Object.freeze({async verify(...args:Parameters<SourceVerifier['verify']>){
await readActive()
const evidence=await verifier.verify(...args)
assert(evidence.platformProtectionVerified&&evidence.operatorSignatureVerified&&evidence.currentHeadPinVerified&&evidence.genuineSourceFactsMatched,'Unprotected activation source evidence')
assertGeneration(evidence)
await readActive()
sourceChecks++
return evidence
}})
const result=await work(guardedVerifier)
assert.equal(sourceChecks,2,'Initial and terminal source evidence required')
await readActive()
return result
})
}
export async function sourceActivationMain(args:string[]){
const command=args[0], flags=command==='source-activate'?['directory','approval','approval-sha256']:
command==='source-revoke'?['directory','expected-sha256']:['directory']
assert(['source-activate','source-revoke','source-status'].includes(command)&&args.length===1+2*flags.length)
const values=new Map<string,string>()
for(let i=1;i<args.length;i+=2){
const name=args[i].slice(2)
assert(args[i].startsWith('--')&&flags.includes(name)&&!values.has(name)&&args[i+1]);values.set(name,args[i+1])
}
const directory=values.get('directory')!
return withJournalLease(directory,async()=>{
const before=await current(directory)
if(command==='source-status'){
assert(before,'Activation record absent')
return receipt(before.state,before.activationSha256,'observed')
}
if(command==='source-revoke'){
const expected=digest.parse(values.get('expected-sha256'))
assert(before?.activationSha256===expected&&before.state.status==='active','Active activation digest changed')
const revoked={...before.state,status:'revoked' as const}
const saved=await persist(directory,revoked)
return {...receipt(revoked,saved.activationSha256,'committed'),cleanupUnconfirmed:saved.cleanupUnconfirmed}
}
const request=approval.parse(parse(await readProtected(values.get('approval')!,16384,digest.parse(values.get('approval-sha256')))))
assert.equal(request.activationDirectory,directory,'Approval belongs to another activation directory')
assert.equal(before?.activationSha256??null,request.expectedActivationSha256,'Activation predecessor changed')
const registry=await readProtectedDatabaseSourceBindingRegistry(request.sourcePin,{deadlineAt:Date.now()+120000})
const previous=before?.state.generations??[]
assert.equal(registry.head.ordinal,previous.length+1,'Source ordinal changed')
assert.equal(registry.head.previousHeadSha256,previous.at(-1)?.headSha256??null,'Signed predecessor differs')
if(before){
assert.equal(registry.head.installationId,before.state.installationId)
assert.equal(registry.head.sourceIdentityId,before.state.sourceIdentityId)
assert.equal(registry.publicKeySpkiSha256,before.state.publicKeySpkiSha256)
}
assert(!previous.some(item=>item.generation.toLowerCase()===registry.head.generation.toLowerCase()||item.tlsSpkiSha256===request.tlsSpkiSha256),'Generation or TLS key was already used')
const next:State={format:'database-source-activation-v1',activationDirectory:directory,installationId:registry.head.installationId,
sourceIdentityId:registry.head.sourceIdentityId,publicKeySpkiSha256:registry.publicKeySpkiSha256,status:'active',
generations:[...previous,{generation:registry.head.generation,headSha256:registry.headSha256,
bindingSha256:registry.head.bindingSha256,tlsSpkiSha256:request.tlsSpkiSha256}]}
const saved=await persist(directory,next)
return {...receipt(next,saved.activationSha256,'committed'),cleanupUnconfirmed:saved.cleanupUnconfirmed}
})
}
@@ -0,0 +1,134 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import fs from 'node:fs/promises'
import path from 'node:path'
import os from 'node:os'
import {createHash,generateKeyPairSync,randomUUID} from 'node:crypto'
import {createRequire} from 'node:module'
import {runInNewContext} from 'node:vm'
import {provisionDatabaseSourceBindingOffline} from '../database-source-binding-operator.ts'
import {readDatabaseSourceBindingRegistry,sourceBindingBytes,sourceBindingCanonical,type DatabaseSourceDeploymentPin} from '../../src/db/database-source-binding.ts'
import type * as Activation from './source-activation.mts'
const require=createRequire(import.meta.url),ts=require('typescript')
const source=await fs.readFile(new URL('./source-activation.mts',import.meta.url),'utf8')
const sha=(value:Buffer)=>createHash('sha256').update(value).digest('hex')
test('protected activation transition model preserves revocation and refuses concurrent, stale or reused generations',async()=>{
const root=await fs.realpath(await fs.mkdtemp(path.join(os.tmpdir(),'mm-source-activation-test-')))
try{
// Genuine issuer signatures and filesystem files. Windows models the
// protection/lease boundary and directory fsync used on the POSIX target.
const lock=path.join(root,'test-lease'),state=path.join(root,'source-activation.json')
const files={sha,protectedDirectory:async(directory:string)=>{assert.equal(directory,root)},
readProtected:async(filename:string,maximum:number,expected?:string)=>{
assert.equal(path.dirname(filename),root)
const bytes=await fs.readFile(filename);assert(bytes.length<=maximum)
if(expected)assert.equal(sha(bytes),expected)
return bytes
},async withJournalLease(directory:string,work:()=>Promise<unknown>){
assert.equal(directory,root);const handle=await fs.open(lock,'wx')
try{return await work()}finally{await handle.close();await fs.unlink(lock)}
}}
let parentFailure=''
const modelFs={...fs,open:async(filename:string,flags:number,mode?:number)=>filename===root?
{sync:async()=>{if(parentFailure==='sync')throw Error('fixture directory sync failure')},
close:async()=>{if(parentFailure==='close')throw Error('fixture directory close failure')}}:fs.open(filename,flags,mode)}
const imports:Record<string,unknown>={'node:fs/promises':modelFs,'./operator-files.mjs':files,
'./protected-source-registry.mjs':{readProtectedDatabaseSourceBindingRegistry:readDatabaseSourceBindingRegistry},
'../../src/db/database-source-binding.js':{sourceBindingBytes,sourceBindingCanonical}}
const load=()=>{
const exports={}
const compiled=ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(compiled,{exports,require:(name:string)=>imports[name]??require(name),Buffer,TextDecoder})
return exports as typeof Activation
}
const {withActiveSourcePreflight}=load()
let {sourceActivationMain:run}=load()
const privateKeyFile=path.join(root,'issuer.pem'),key=generateKeyPairSync('ed25519').privateKey
await fs.writeFile(privateKeyFile,key.export({format:'pem',type:'pkcs8'}),{mode:0o600})
const installationId=randomUUID(),sourceIdentityId=randomUUID()
const issue=async(previousPin?:DatabaseSourceDeploymentPin,generation=randomUUID())=>{
return (await provisionDatabaseSourceBindingOffline({privateKeyFile,previousPin,outputDirectory:path.join(root,randomUUID()),
request:{installationId,sourceIdentityId,generation,source:{systemIdentifier:'123',databaseOid:16384,databaseName:'synthetic',
sessionUser:'publisher',currentUser:'publisher',inRecovery:false,serverVersionNum:180004}}})).deploymentPin
}
const status=['source-status','--directory',root]
const revoke=(expected:string)=>['source-revoke','--directory',root,'--expected-sha256',expected]
const activate=async(pin:DatabaseSourceDeploymentPin,tlsSpkiSha256:string,expectedActivationSha256:string|null,activationDirectory=root)=>{
const bytes=sourceBindingBytes({format:'database-source-activation-approval-v1',activationDirectory,sourcePin:pin,tlsSpkiSha256,expectedActivationSha256})
const file=path.join(root,randomUUID()+'.json');await fs.writeFile(file,bytes,{mode:0o600,flag:'wx'})
return ['source-activate','--directory',root,'--approval',file,'--approval-sha256',sha(bytes)]
}
const g1=await issue(undefined,'5ac5e8bd-0eb2-498b-901a-dd2010cd4b31'),g2=await issue(g1),g3=await issue(g2)
await assert.rejects(run(await activate(g1,'1'.repeat(64),null,path.join(root,'other'))),/another activation directory/)
const start=await activate(g1,'1'.repeat(64),null)
const races=await Promise.allSettled([run(start),run(start)])
assert.deepEqual(races.map(value=>value.status).sort(),['fulfilled','rejected'],races.map(value=>value.status==='rejected'?String(value.reason):value.status).join(', '))
const first=await run(status)
assert.equal(first.ordinal,1);assert.equal(first.headSha256,g1.currentHeadSha256)
assert.equal(first.generationAuthenticated,false)
const registry=await readDatabaseSourceBindingRegistry(g1)
const evidence={format:'database-source-binding-evidence-v1' as const,
installationId:registry.head.installationId,sourceIdentityId:registry.head.sourceIdentityId,generation:registry.head.generation,
ordinal:registry.head.ordinal,bindingSha256:registry.head.bindingSha256,headSha256:registry.headSha256,
operatorSignatureVerified:true as const,currentHeadPinVerified:true as const,
genuineSourceFactsMatched:true as const,platformProtectionVerified:true as const,generationAuthenticated:false as const,
allWriterDrainAuthenticated:false as const,readyAuthorized:false as const}
// The actual preflight exercises the checked-out PostgreSQL client. The guard fixture models its source verifier.
type Verifier=Parameters<typeof withActiveSourcePreflight>[1]
const verifierArgs=[] as unknown as Parameters<Verifier['verify']>
const preflight={directory:root,sourcePin:g1,expectedSource:registry.body.source,expectedSha256:first.activationSha256,tlsSpkiSha256:'1'.repeat(64),deadlineAt:Date.now()+30000}
const refuseConnect=()=>{throw Error('Reached SQL connection before activation was trusted')}
const verifier={verify:async()=>evidence}
await assert.rejects(withActiveSourcePreflight({...preflight,expectedSha256:'0'.repeat(64)},verifier,refuseConnect),/activation digest changed/)
await assert.rejects(withActiveSourcePreflight({...preflight,tlsSpkiSha256:'2'.repeat(64)},verifier,refuseConnect),/Activation key differs/)
await assert.rejects(withActiveSourcePreflight({...preflight,sourcePin:g2},verifier,refuseConnect),/differs from signed source generation/)
await assert.rejects(withActiveSourcePreflight({...preflight,expectedSource:{...preflight.expectedSource,currentUser:'different'}},verifier,refuseConnect),/differs from reviewed configuration/)
let verifications=0
const result=await withActiveSourcePreflight(preflight,{verify:async()=>{verifications++;return evidence}},async guarded=>{
assert.equal(await guarded.verify(...verifierArgs),evidence)
await assert.rejects(run(revoke(first.activationSha256)),{code:'EEXIST'})
assert.equal(await guarded.verify(...verifierArgs),evidence)
return 'matched'
})
assert.equal(result,'matched');assert.equal(verifications,2)
await assert.rejects(fs.access(lock),{code:'ENOENT'})
await assert.rejects(withActiveSourcePreflight(preflight,verifier,async guarded=>{
await guarded.verify(...verifierArgs)
}),/Initial and terminal source evidence required/)
let reads=0
await assert.rejects(withActiveSourcePreflight(preflight,{verify:async()=>++reads===1?evidence:{...evidence,bindingSha256:'0'.repeat(64)}},async guarded=>{
await guarded.verify(...verifierArgs)
await guarded.verify(...verifierArgs)
}),/differs from signed source generation/)
await fs.writeFile(state+'.pending','interrupted',{flag:'wx'})
await assert.rejects(withActiveSourcePreflight(preflight,verifier,refuseConnect),/Unresolved activation transition/)
await fs.unlink(state+'.pending')
await assert.rejects(run(start),/predecessor changed/)
const revoked=await run(revoke(first.activationSha256))
await assert.rejects(withActiveSourcePreflight({...preflight,expectedSha256:revoked.activationSha256},verifier,refuseConnect),/activation digest changed/)
run=load().sourceActivationMain
assert.equal((await run(status)).status,'revoked')
await assert.rejects(run(revoke(first.activationSha256)),/digest changed/)
await assert.rejects(run(await activate(g3,'3'.repeat(64),revoked.activationSha256)),/ordinal changed/)
const second=await run(await activate(g2,'2'.repeat(64),revoked.activationSha256))
assert.equal(second.ordinal,2);assert.equal(second.status,'active')
await assert.rejects(run(await activate(g3,'1'.repeat(64),second.activationSha256)),/already used/)
const repeatedGeneration=await issue(g2,first.generation!.toUpperCase())
await assert.rejects(run(await activate(repeatedGeneration,'3'.repeat(64),second.activationSha256)),/already used/)
await assert.rejects(run(await activate({...g3,currentHeadSha256:'0'.repeat(64)},'3'.repeat(64),second.activationSha256)))
assert.equal((await run(status)).activationSha256,second.activationSha256)
parentFailure='sync'
await assert.rejects(run(revoke(second.activationSha256)),{code:'SOURCE_ACTIVATION_TERMINAL_UNKNOWN'})
parentFailure='close'
const observed=await run(status)
assert.equal(observed.status,'revoked')
const third=await run(await activate(g3,'3'.repeat(64),observed.activationSha256))
assert.equal(third.outcome,'committed')
assert('cleanupUnconfirmed' in third && third.cleanupUnconfirmed===true)
await fs.writeFile(state+'.pending','interrupted',{flag:'wx'})
await assert.rejects(run(status),/Unresolved activation transition/)
await assert.rejects(run(revoke(third.activationSha256)),/Unresolved activation transition/)
}finally{await fs.rm(root,{recursive:true,force:true})}
})