fix(mcp): обновить снимок приёмки маршрутизации и базы данных

This commit is contained in:
Denozordec committed 2026-11-03 23:38:19 +07:00
1 parent bf3d50e1ff
commit 6a286a1f66
79 files changed
+10003 -97

No files matched your search

+6 -2
View File
@@ -16,7 +16,7 @@ permissions:
packages: write
jobs:
backend-preview:
if: ${{ gitea.event_name == 'workflow_dispatch' }}
if: ${{ gitea.event_name == 'workflow_dispatch' && gitea.ref == 'refs/heads/codex/mcp-acceptance-current-20261103' }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
@@ -24,6 +24,7 @@ jobs:
EXPECTED_SHA: ${{ inputs.expected_sha }}
DISPATCH_SHA: ${{ gitea.sha }}
DISPATCH_EVENT: ${{ gitea.event_name }}
DISPATCH_REF: ${{ gitea.ref }}
REPOSITORY: ${{ gitea.repository }}
RUN_ID: ${{ gitea.run_id }}
RUN_ATTEMPT: ${{ gitea.run_attempt }}
@@ -42,6 +43,7 @@ jobs:
run: |
set -euo pipefail
test "$DISPATCH_EVENT" = workflow_dispatch
test "$DISPATCH_REF" = refs/heads/codex/mcp-acceptance-current-20261103
[[ "$EXPECTED_SHA" =~ ^[0-9a-f]{40}$ ]]
test "$EXPECTED_SHA" = "$DISPATCH_SHA"
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
@@ -139,7 +141,7 @@ jobs:
if-no-files-found: error
retention-days: 14
frontend-preview:
if: ${{ gitea.event_name == 'workflow_dispatch' }}
if: ${{ gitea.event_name == 'workflow_dispatch' && gitea.ref == 'refs/heads/codex/mcp-acceptance-current-20261103' }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
@@ -147,6 +149,7 @@ jobs:
EXPECTED_SHA: ${{ inputs.expected_sha }}
DISPATCH_SHA: ${{ gitea.sha }}
DISPATCH_EVENT: ${{ gitea.event_name }}
DISPATCH_REF: ${{ gitea.ref }}
REPOSITORY: ${{ gitea.repository }}
RUN_ID: ${{ gitea.run_id }}
RUN_ATTEMPT: ${{ gitea.run_attempt }}
@@ -178,6 +181,7 @@ jobs:
run: |
set -euo pipefail
test "$DISPATCH_EVENT" = workflow_dispatch
test "$DISPATCH_REF" = refs/heads/codex/mcp-acceptance-current-20261103
[[ "$EXPECTED_SHA" =~ ^[0-9a-f]{40}$ ]]
test "$EXPECTED_SHA" = "$DISPATCH_SHA"
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
+7
View File
@@ -576,6 +576,13 @@
"operations": [],
"reason": "Управление самим MCP, транспорт или машинная авторизация: отдельного AI-инструмента нет."
},
{
"method": "GET",
"path": "/api/mcp/database/receipts/:id/audit-status",
"status": "excluded",
"operations": [],
"reason": "Человеческая административная сверка аудита внешней квитанции восстановления: управление самим MCP, без отдельного AI-инструмента."
},
{
"method": "GET",
"path": "/api/mcp/devices/:serverId/:resource",
+2 -2
View File
@@ -54,7 +54,7 @@
"test:ospf-optimizer": "tsx src/services/ospf-cost-optimization.test.ts && tsx src/modules/mcp/ospf-legacy-source.test.ts && tsx src/services/ospf-legacy-optimization.test.ts && tsx src/services/ospf-template-flags.test.ts && tsx src/services/ospf-route-proof.test.ts && tsx src/services/ospf-link-catalog.test.ts && tsx src/services/ospf-identity-catalog.test.ts && tsx src/services/ospf-template-cost-write.test.ts && tsx src/services/ospf-template-cost-http.test.ts && tsx src/services/ospf-optimizer-identity.test.ts && tsx src/services/ospf-optimizer-metrics.test.ts && tsx src/services/ospf-optimizer-plan.test.ts && tsx src/services/ospf-effective-disabled.test.ts && tsx src/routes/ospf-optimizer.test.ts",
"test:containers": "tsx src/services/containers-live.test.ts && tsx src/services/containers-state.test.ts && tsx src/services/container-removal.test.ts && tsx src/routes/containers-state.test.ts",
"test:certificates": "tsx src/services/certificate-installation.test.ts && tsx src/services/certificate-renew-collector.test.ts && tsx src/services/certificate-issue-execution.test.ts && tsx src/services/certificate-issue-request.test.ts && tsx src/routes/certificates-issue.test.ts && tsx src/services/certificate-issue-dto.test.ts && tsx src/services/certificate-parse.test.ts && tsx src/services/certificates-state.test.ts && tsx src/routes/certificates-state.test.ts && tsx src/services/acme-settings-test.test.ts && tsx src/services/acme-settings-update-model.test.ts && tsx src/modules/mcp/acme-settings-actions.test.ts",
"test:system-database": "tsx src/services/bounded-database-dump.test.ts && tsx src/services/database-dump-connection.test.ts && tsx src/services/system-database-partition-restore.test.ts && tsx src/services/system-database-cache.test.ts && tsx src/services/database-background-work.test.ts && tsx src/services/database-request-gate.test.ts && tsx src/services/traffic-flow-worker-shutdown.test.ts && tsx src/services/system-database-restore-execution.test.ts && tsx src/services/system-database-operation.test.ts && tsx src/services/system-database-schema-readiness.test.ts && tsx src/routes/system-database.test.ts && tsx src/plugins/auth.system-database.test.ts && tsx src/services/database-control-archive-boundary.test.ts && tsx src/db/database-schema-control-model.test.ts && tsx src/db/database-schema-control.test.ts && tsx src/db/controlled-migrations.test.ts && tsx src/db/controlled-schema-verification.test.ts && tsx --test src/db/migration-data-inventory.test.ts",
"test:system-database": "tsx src/services/bounded-database-dump.test.ts && tsx src/services/database-dump-connection.test.ts && tsx src/services/system-database-partition-restore.test.ts && tsx src/services/system-database-cache.test.ts && tsx src/services/database-background-work.test.ts && tsx src/services/database-request-gate.test.ts && tsx --test src/services/detached-database-admission.test.ts && tsx src/services/traffic-flow-worker-shutdown.test.ts && tsx src/services/system-database-restore-execution.test.ts && tsx src/services/system-database-operation.test.ts && tsx src/services/system-database-schema-readiness.test.ts && tsx --test src/routes/mcp-oauth-database-admission.test.ts && tsx src/routes/system-database.test.ts && tsx src/plugins/auth.system-database.test.ts && tsx src/services/database-control-archive-boundary.test.ts && tsx src/db/database-schema-control-model.test.ts && tsx src/db/database-schema-control.test.ts && tsx src/db/controlled-migrations.test.ts && tsx src/db/controlled-schema-verification.test.ts && tsx --test src/db/migration-data-inventory.test.ts && tsx --test src/services/database-maintenance-startup.test.ts && tsx --test src/services/database-restore-audit.test.ts && tsx --test src/services/database-maintenance-resolution.test.ts && tsx --test scripts/database-maintenance-resolution.test.mts",
"test:all": "npm run test:logs && npm run test:probes && npm run test:exec && npm run test:alert-engine && npm run test:alerts-telegram && npm run test:auth && npm run test:wireguard && npm run test:ipsec && npm run test:traffic-rate && npm run test:traffic-flow && npm run test:users && npm run test:pg && npm run test:sidebar-counts && npm run test:backups && npm run test:scheduler && npm run test:live-maps && npm run test:ospf-optimizer && npm run test:recursive-routes && npm run test:config-sync && npm run test:ros-file && npm run test:containers && npm run test:certificates && npm run test:netflow-settings && npm run test:netflow-purge && npm run test:evolution && npm run test:system-database && npm run test:server-privacy && npm run test:mcp:module-completion && npm run test:mcp:loopback-agent && npm run test:server-probe-management && npm run test:server-poll-management && npm run test:mcp:routing-authority && npm run test:mcp:native-readers && npm run test:mcp:chr-acquisition && npm run test:mcp:shared-context && npm run test:mcp:telemetry-configuration",
"test:server-privacy": "tsx ../packages/contracts/src/servers.test.ts && tsx src/routes/server-secrets-sql.test.ts && tsx --test src/services/server-connection-test.test.ts",
"test:mcp:flow-rebuild:pg": "node scripts/test-mcp-postgres.mjs --flow-rebuild-mcp",
@@ -140,7 +140,7 @@
"test:server-poll-storage-fresh:pg": "node scripts/test-mcp-postgres.mjs --server-poll-storage-fresh --restore",
"test:partition-writers": "tsx src/services/partition-write-transaction.test.ts && tsx src/services/collector-partition-writes.test.ts && tsx src/db/partitions-writer.test.ts && tsx src/db/migrate-partition-admission.test.ts && tsx src/db/migration-manifest.test.ts && tsx src/db/sqlite-import-partition-admission.test.ts && tsx src/services/system-database-partition-restore.test.ts && tsx src/services/traffic-flow-write-transaction.test.ts && tsx src/services/traffic-flow-facts-rebuild-cli.test.ts",
"test:mcp:routing-authority": "tsx --test src/modules/mcp/routing-switch-ledger-contract.test.ts && tsx src/modules/mcp/routing-profile-authority.test.ts && tsx src/modules/mcp/routing-recovery-authority.test.ts && tsx src/modules/mcp/routing-reservation.test.ts && tsx src/modules/mcp/routing-community-reserved-source.test.ts && tsx src/modules/mcp/routing-network-verifier-model.test.ts && tsx --test src/test/committed-scoped-checkpoint-observer.test.ts && node --test src/modules/mcp/routing-forward-transport-charge.test.cjs",
"test:mcp:native-readers": "tsx src/modules/mcp/routing-network-verifier-model.test.ts && tsx src/modules/mcp/routing-network-native-parser.test.ts && tsx src/modules/mcp/routing-network-native-session.test.ts && tsx src/db/migration-schema-inventory.test.ts && tsx src/modules/mcp/routing-network-observation-assembly.test.ts && tsx src/modules/mcp/routing-network-configuration-bridge.test.ts",
"test:mcp:native-readers": "tsx src/modules/mcp/routing-network-verifier-model.test.ts && tsx src/modules/mcp/routing-network-native-parser.test.ts && tsx src/modules/mcp/routing-network-native-session.test.ts && tsx src/db/migration-schema-inventory.test.ts && tsx src/modules/mcp/routing-network-observation-assembly.test.ts && tsx src/modules/mcp/routing-network-configuration-bridge.test.ts && tsx --test src/services/routing-table-fib.test.ts src/services/routing-topology-observation.test.ts && node --test src/modules/mcp/routing-original-effects.test.mjs src/modules/mcp/routing-original-effects-caller.test.mjs src/modules/mcp/routing-original-effects-controller.test.mjs src/modules/mcp/routing-network-execution-origin.test.mjs",
"test:mcp:native-readers:pg": "node scripts/test-mcp-postgres.mjs --routing-network-native --restore",
"test:mcp:schema-inventory:pg": "node scripts/test-mcp-postgres.mjs --migration-schema-inventory --restore",
"test:mcp:native-tls": "tsx src/modules/mcp/routing-network-native-tls.test.ts",
@@ -10,6 +10,7 @@ import ts from 'typescript'
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/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',
@@ -19,7 +19,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,17);assert.deepEqual(first.runtimeDependencies,['dotenv','pg','zod']);assert.equal(first.backendDistModules.length,9)
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)
const copied=new Set()
async function copyDist(relative){
if(copied.has(relative))return;copied.add(relative)
@@ -37,7 +37,7 @@ test('fixed artifact is reproducible, resolves the compiled backend graph, and r
for(const file of OPERATOR_SOURCES){const target=path.join(projectBackend,'scripts',file);await mkdir(path.dirname(target),{recursive:true});await copyFile(path.join(backend,'scripts',file),target)}
const buildScript=path.join(projectBackend,'scripts','build-database-operator.mjs');await copyFile(fileURLToPath(new URL('./build-database-operator.mjs',import.meta.url)),buildScript)
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,17)
assert.equal(built.status,0,built.stderr);assert.equal(JSON.parse(built.stdout.trim()).modules,19)
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';"]){
@@ -59,10 +59,10 @@ 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,17);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,19);assert.equal(JSON.parse(await readFile(telemetry,'utf8')).connections,0)
const refusals=[]
for(const [entry,event] of [['database-writer-bootstrap-operator.mjs','application-writer-bootstrap-refused'],['database-writer-profile-operator.mjs','application-writer-profile-publication-refused']]){
const child=spawnSync(process.execPath,['--import',pathToFileURL(guard).href,path.join(output,entry),'invalid'],{cwd:temporary,env,encoding:'utf8',timeout:30000})
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']]]){
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)
}
const receipt={node:process.version,projection:temporary,modules:first.modules.length,compiledBackendModules:copied.size,buildCliExitCode:built.status,importedModules:modulesToImport.length,invalidArgumentsRefused:refusals,developmentDependenciesBlocked:denied,networkConnections:0,productionActivated:false,readyAuthorized:false}
@@ -0,0 +1,100 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {mkdtemp,mkdir,writeFile,readFile,readdir,rm,unlink,lstat} from 'node:fs/promises'
import {createHash,randomUUID} from 'node:crypto'
import {rootCertificates} from 'node:tls'
import {EventEmitter} from 'node:events'
import path from 'node:path'
import {fileURLToPath,pathToFileURL} from 'node:url'
import {runInNewContext} from 'node:vm'
import {createRequire} from 'node:module'
import * as strictReader from '../src/services/database-maintenance-startup.js'
import * as grammar from '../src/services/database-maintenance-resolution.js'
import * as manifest from '../src/db/migration-manifest.js'
import * as contract from './writer-profile/operator-contract.mjs'
const backend=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..'),require=createRequire(import.meta.url),ts=require('typescript'),hash=(value:Buffer|string)=>createHash('sha256').update(value).digest('hex')
async function own(work:(root:string)=>Promise<void>){const temp=path.resolve(backend,'../tmp');await mkdir(temp,{recursive:true});const root=await mkdtemp(path.join(temp,'owned-resolution-command-'));try{await work(root)}finally{assert.equal(path.dirname(root),temp);assert(path.basename(root).startsWith('owned-resolution-command-'));await rm(root,{recursive:true,force:true})}}
async function model(root:string,localHost?:string){
let owners=0,sql=0,releaseFailure=false,poolEndFailure=false,published=0,wrongOwner=false,controlSchema=false,observationFailure:Error|undefined
const capturedScopes:unknown[]=[]
const state=path.join(root,'state');await mkdir(state);await mkdir(path.join(state,'restore-vault'));await writeFile(path.join(state,'access-generation'),Buffer.alloc(32,7));const uncertain=randomUUID(),at=new Date().toISOString(),record=(value:string)=>JSON.stringify({operationId:uncertain,state:value,at})+'\n';await writeFile(path.join(state,'database-maintenance.jsonl'),record('intent')+record('unconfirmed'))
const source={systemIdentifier:'1234567890',databaseOid:'12345',databaseName:'managed',serverVersionNum:180004,inRecovery:false}
const schema={sha256:'a'.repeat(64),observedRaw:{sha256:'b'.repeat(64)},observedPhysical:{sha256:'c'.repeat(64)},sections:{relations:manifest.CURRENT_REQUIRED_TABLES.map(name=>({namespace:'public',name,kind:'r',persistence:'p',row_security:false,force_row_security:false})),columns:[{namespace:'public',relation:'schema_migrations',name:'id',dropped:false,type:'text'}]}}
class Pool {on(){};constructor(options:unknown){const ssl=(options as {ssl:false|{rejectUnauthorized:boolean}}).ssl;if(ssl===false)assert(['127.0.0.1','::1'].includes((options as {host:string}).host));else assert(ssl.rejectUnauthorized)}async connect(){owners++;const client=new EventEmitter();return Object.assign(client,{release:()=>{owners--;if(releaseFailure)throw Error('Synthetic release uncertainty')},query:async(text:string)=>{sql++;if(text.includes('pg_try_advisory_lock'))return {rows:[{locked:true}],command:'SELECT'};if(text.includes('pg_advisory_unlock'))return {rows:[{unlocked:true}],command:'SELECT'};if(text.includes('pg_namespace'))return {rows:controlSchema?[{nspname:'mmapp_control'}]:[],command:'SELECT'};if(text.includes('pg_control_system')){if(observationFailure)throw observationFailure;return {rows:[source],command:'SELECT'}};if(text.startsWith('SELECT id FROM public.schema_migrations'))return {rows:manifest.MIGRATION_MANIFEST.map(pin=>({id:pin.file.slice(0,-4)})),command:'SELECT'};return {rows:[],command:text.split(' ')[0]}}})}async end(){assert.equal(owners,0);if(poolEndFailure)throw Error('Synthetic pool retirement uncertainty')}}
const protectedSource=await readFile(path.join(backend,'scripts/writer-profile/operator-files.mjs'),'utf8'),protectedExports:Record<string,unknown>={},actualUid=(await lstat(state)).uid
const protectedCode=ts.transpileModule(protectedSource,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(protectedCode,{exports:protectedExports,require,Buffer,process:{platform:'linux',getuid:()=>actualUid+1},Object,Error})
const protectedDirectory=async(directory:string)=>{assert(path.resolve(directory).startsWith(root+path.sep)||path.resolve(directory)===root);if(wrongOwner)await (protectedExports.protectedDirectory as (path:string)=>Promise<void>)(directory);await readdir(directory)}
// Explicit MODELED protection adapter: native POSIX mode/ownership and hardlink
// fsync publication remain separate Linux acceptance, never a Windows claim.
const files={sha:hash,protectedDirectory,readProtected:async(file:string,maximum:number,pin?:string)=>{assert(file.startsWith(root+path.sep));const raw=await readFile(file);assert(raw.length>0&&raw.length<=maximum);if(pin)assert.equal(hash(raw),pin);return raw},writeFile:async(file:string,raw:Buffer)=>{assert(file.startsWith(root+path.sep));await writeFile(file,raw,{flag:'wx'});published++},withJournalLease:async(directory:string,work:()=>Promise<unknown>)=>{const lock=path.join(directory,'publisher-lease.lock');await writeFile(lock,'owned',{flag:'wx'});try{return await work()}finally{await unlink(lock)}}}
const readRecords=async(directory:string)=>{const records=path.join(directory,'maintenance-acceptances');let names:string[];try{names=await readdir(records)}catch(error){if((error as NodeJS.ErrnoException).code==='ENOENT')return [];throw error};assert(names.every(name=>/^[a-f0-9-]{36}\.json$/.test(name)));return Promise.all(names.sort().map(name=>readFile(path.join(records,name))))}
const moduleFile=path.join(backend,'scripts/writer-profile/maintenance-resolution.mts'),sourceText=(await readFile(moduleFile,'utf8')).replaceAll('import.meta.url',JSON.stringify(pathToFileURL(moduleFile).href)),exports:Record<string,unknown>={}
const dependencies:Record<string,unknown>={'./operator-files.mjs':files,'./operator-contract.mjs':contract,'../../src/services/database-maintenance-startup.js':{...strictReader,readMaintenanceAcceptanceRecords:readRecords},'../../src/services/database-maintenance-resolution.js':grammar,'../../src/db/migration-schema-inventory.js':{captureMigrationSchema:async(_client:unknown,options:{scope:unknown})=>{capturedScopes.push(options.scope);return schema}},'../../src/db/migration-manifest.js':manifest,pg:{Pool}}
const code=ts.transpileModule(sourceText,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(code,{exports,require:(name:string)=>name in dependencies?dependencies[name]:require(name),Buffer,URL,process:{env:{}},Date,Object,Map,Set,Error,console})
const command=exports.maintenanceMain as (args:string[])=>Promise<Record<string,unknown>>
const tlsTransport={format:'database-maintenance-transport-v1',connection:{host:'db.example.invalid',port:5432,user:'existing-maintenance-user',password:'synthetic-only',database:'managed'},tls:{serverName:'db.example.invalid',spkiSha256:'f'.repeat(64)}},localTransport=(host:string)=>({format:'database-maintenance-transport-v1',mode:'local-loopback',connection:{...tlsTransport.connection,host}})
const configs={transport:localHost===undefined?tlsTransport:localTransport(localHost),'runtime-target':{format:'database-maintenance-runtime-target-v1',databaseUrl:'postgresql://db.example.invalid:5432/managed?sslmode=require'}}
const args=['--state-dir',state]
for(const [name,value]of Object.entries(configs)){const raw=Buffer.from(JSON.stringify(value)),file=path.join(root,name+'.json');await writeFile(file,raw);args.push('--'+name,file,'--'+name+'-sha256',hash(raw))}
const ca=Buffer.from(rootCertificates[0]),caFile=path.join(root,'ca.pem');await writeFile(caFile,ca);if(localHost===undefined)args.push('--ca',caFile,'--ca-sha256',hash(ca))
const output=path.join(root,'inspection.json')
async function inspect(destination=output){return command(['maintenance-inspect',...args,'--output',destination])}
async function approve(change?:(value:Record<string,unknown>)=>void){const raw=await readFile(output),inspection=JSON.parse(raw.toString()),approval={format:'database-maintenance-current-state-approval-v1',resolutionId:randomUUID(),decision:'accept-observed-current-state',actorId:'host-human-reviewer',inspectionSha256:hash(raw),issuedAt:new Date().toISOString(),expiresAt:new Date(Date.now()+60000).toISOString(),managedBackendStopped:true,historicOutcomesRemainUnknown:true,automaticRestore:false};assert(inspection.createdAt<=approval.issuedAt);change?.(approval);const bytes=Buffer.from(JSON.stringify(approval)),file=path.join(root,'approval.json');await writeFile(file,bytes);return ['maintenance-resolve',...args,'--inspection',output,'--inspection-sha256',hash(raw),'--approval',file,'--approval-sha256',hash(bytes)]}
const toLocal=async(host='127.0.0.1')=>{const raw=Buffer.from(JSON.stringify(localTransport(host)));await writeFile(path.join(root,'transport.json'),raw);args[args.indexOf('--transport-sha256')+1]=hash(raw);for(const flag of ['--ca','--ca-sha256']){const at=args.indexOf(flag);if(at>=0)args.splice(at,2)}}
const configuration=exports.maintenancePoolConfiguration as (value:unknown,ca?:Buffer,pin?:string)=>{options:Record<string,unknown>;transportBinding:{mode:string;endpointSha256:string}}
return {command,inspect,approve,toLocal,configuration,readRecords,state,output,sql:()=>sql,published:()=>published,failRelease:()=>{releaseFailure=true},failEnd:()=>{poolEndFailure=true},driftSchema:()=>{schema.sha256='0'.repeat(64)},driftTarget:()=>{source.databaseOid='54321'},wrongOwner:()=>{wrongOwner=true},withControlSchema:()=>{controlSchema=true},scopes:()=>capturedScopes,failObservation:(error:Error)=>{observationFailure=error}}
}
test('actual operator commands inspect then separately approved resolve; old uncertain bytes never rewritten',()=>own(async root=>{
const fixture=await model(root),journal=path.join(fixture.state,'database-maintenance.jsonl'),before=await readFile(journal),inspected=await fixture.inspect();assert.equal(inspected.humanApprovalRequired,true);assert.equal((await fixture.readRecords(fixture.state)).length,0)
const result=await fixture.command(await fixture.approve());assert.equal(result.outcome,'committed');assert.equal(result.historicOutcomesChanged,false);assert.equal(result.archiveDataEquivalenceProved,false);assert.equal(result.automaticRestore,false)
assert.deepEqual(await readFile(journal),before);const records=await fixture.readRecords(fixture.state),facts=await strictReader.readMaintenanceRecoveryFacts(fixture.state);assert.equal(records.length,1);const accepted=grammar.evaluateMaintenanceAcceptances(records,facts.journal,facts.vault.evidence,grammar.runtimeDatabaseTargetSha256('postgresql://db.example.invalid:5432/managed?sslmode=require'));assert.equal(accepted.count,1)
fixture.driftSchema();const afterStatus=await fixture.inspect(path.join(root,'after-inspection.json'));assert.equal(afterStatus.state,'historically-accepted');assert.equal(afterStatus.humanApprovalRequired,false);assert.equal(afterStatus.historicOutcomesChanged,false)
await assert.rejects(fixture.command(await fixture.approve()),/No new unresolved|already|assert|Assertion/i)
}))
for(const type of ['expired','not-stopped','wrong-inspection'])test('actual resolve refuses '+type+' approval before connecting SQL',()=>own(async root=>{
const fixture=await model(root);await fixture.inspect();const args=await fixture.approve(value=>{if(type==='expired')value.expiresAt=new Date(Date.now()-1).toISOString();if(type==='not-stopped')value.managedBackendStopped=false;if(type==='wrong-inspection')value.inspectionSha256='0'.repeat(64)}),before=fixture.sql();await assert.rejects(fixture.command(args));assert.equal(fixture.sql(),before);assert.equal((await fixture.readRecords(fixture.state)).length,0)
}))
for(const type of ['release','pool-end','schema-drift','target-drift','external-drift'])test('actual resolve refuses '+type+' before acceptance publication',()=>own(async root=>{
const fixture=await model(root);await fixture.inspect();const args=await fixture.approve();if(type==='release')fixture.failRelease();if(type==='pool-end')fixture.failEnd()
if(type==='schema-drift')fixture.driftSchema();if(type==='target-drift')fixture.driftTarget()
if(type==='external-drift')await appendFileSafe(path.join(fixture.state,'database-maintenance.jsonl'))
await assert.rejects(fixture.command(args));assert.equal((await fixture.readRecords(fixture.state)).length,0)
}))
async function appendFileSafe(file:string){await writeFile(file,await readFile(file,'utf8')+JSON.stringify({operationId:randomUUID(),state:'intent',at:new Date().toISOString()})+'\n')}
test('existing shipped main dispatches both commands before writer-profile role/source flags',async()=>{
const source=await readFile(path.join(backend,'scripts/database-writer-profile-operator.mts'),'utf8');assert(source.indexOf("['maintenance-inspect','maintenance-resolve']")<source.indexOf("const flags="));assert(source.includes("import('./writer-profile/maintenance-resolution.mjs')"))
})
test('existing protectedDirectory owner contract refuses a foreign CLI UID before SQL or writes',()=>own(async root=>{
const fixture=await model(root);fixture.wrongOwner();await assert.rejects(fixture.inspect());assert.equal(fixture.sql(),0);assert.equal(fixture.published(),0);assert.equal((await fixture.readRecords(fixture.state)).length,0)
}))
test('explicit local-loopback supports both IP families and binds selected mode in actual inspect/resolve',async()=>{
for(const host of ['127.0.0.1','::1'])await own(async root=>{const fixture=await model(root,host);await fixture.inspect();const result=await fixture.command(await fixture.approve());assert.equal(result.outcome,'committed');const records=await fixture.readRecords(fixture.state);assert.equal(JSON.parse(records[0].toString()).knownCurrent.transport.mode,'local-loopback')})
})
test('plaintext remote/DNS/unspecified/mapped hosts refuse before SQL and never fall back',async()=>{
for(const host of ['db.example.invalid','localhost','0.0.0.0','192.0.2.1','[::1]','::ffff:127.0.0.1'])await own(async root=>{const fixture=await model(root,host);await assert.rejects(fixture.inspect());assert.equal(fixture.sql(),0);assert.equal(fixture.published(),0)})
})
test('an approval for TLS observation cannot resolve through a changed local-loopback transport',()=>own(async root=>{
const fixture=await model(root);await fixture.inspect();await fixture.toLocal();await assert.rejects(fixture.command(await fixture.approve()));assert.equal((await fixture.readRecords(fixture.state)).length,0)
}))
test('local mode explicitly disables SSL/environment substitution; TLS remains verified default without fallback',()=>own(async root=>{
const fixture=await model(root),ConnectionParameters=require('pg/lib/connection-parameters'),raw={format:'database-maintenance-transport-v1',mode:'local-loopback',connection:{host:'127.0.0.1',port:5432,user:'existing-user',password:'synthetic-only',database:'managed'}}
const previous={PGHOST:process.env.PGHOST,PGSSLMODE:process.env.PGSSLMODE,PGOPTIONS:process.env.PGOPTIONS};process.env.PGHOST='remote.invalid';process.env.PGSSLMODE='require';process.env.PGOPTIONS='-c role=foreign'
try{const config=fixture.configuration(raw),actual=new ConnectionParameters(config.options);assert.equal(actual.host,'127.0.0.1');assert.equal(actual.ssl,false);assert.equal(actual.options,contract.STARTUP_OPTIONS);assert.equal(config.transportBinding.mode,'local-loopback')}
finally{for(const [key,value]of Object.entries(previous)){if(value===undefined)delete process.env[key];else process.env[key]=value}}
const ca=Buffer.from(rootCertificates[0]),tls={format:'database-maintenance-transport-v1',connection:{...raw.connection,host:'db.example.invalid'},tls:{serverName:'db.example.invalid',spkiSha256:'f'.repeat(64)}}
assert.throws(()=>fixture.configuration(tls));const secure=fixture.configuration(tls,ca,hash(ca));assert.equal(secure.transportBinding.mode,'tls');assert.equal((secure.options.ssl as {rejectUnauthorized:boolean}).rejectUnauthorized,true)
assert.throws(()=>fixture.configuration({...raw,mode:'plaintext'}));assert.throws(()=>fixture.configuration({...tls,mode:null},ca,hash(ca)));assert.throws(()=>fixture.configuration(raw,ca,hash(ca)))
}))
// A normal installation has no optional writer-profile schema. Observation must
// select its exact namespace set without requiring installation of that system.
test('maintenance observes with and without the optional control namespace',async()=>{
for(const present of [false,true])await own(async root=>{const fixture=await model(root);if(present)fixture.withControlSchema();await fixture.inspect();assert.equal(JSON.stringify(fixture.scopes()),JSON.stringify([{applicationNamespaces:['public'],excludedControlNamespaces:present?['mmapp_control']:[]}]))})
})
test('original observation failure survives confirmed rollback/unlock/release',()=>own(async root=>{
const fixture=await model(root),failure=Error('Synthetic original observation failure');fixture.failObservation(failure);await assert.rejects(fixture.inspect(),error=>error===failure);assert.equal(fixture.published(),0);assert.equal((await fixture.readRecords(fixture.state)).length,0)
}))
@@ -9,6 +9,7 @@ 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))
export async function main(args:string[]){
if(['maintenance-inspect','maintenance-resolve'].includes(args[0]))return (await import('./writer-profile/maintenance-resolution.mjs')).maintenanceMain(args)
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)
assertJsDriverEnvironment(process.env)
@@ -38,7 +39,12 @@ export async function main(args:string[]){
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 verifier=createDatabaseSourceBindingVerifier({registryDirectory:get('source-registry'),installationId:subject.installationId,publicKeySpkiSha256:get('source-spki-sha256'),currentHeadSha256:request.sourceHeadSha256})
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.
@@ -47,7 +53,7 @@ export async function main(args:string[]){
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'&&!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,writerAuthorized:false,cleanupUnconfirmed:false,automaticReplay:false,generationAuthenticated:false,readyAuthorized:false}
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}
@@ -55,5 +61,5 @@ export async function main(args:string[]){
}
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:'application-writer-profile-publication-refused',automaticReplay:false,inspectPendingBeforeRetry:true,readyAuthorized:false}));process.exitCode=1}
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}
}
@@ -11,9 +11,9 @@ import {readCurrentApplicationWriterProfile} from '../src/db/application-writer-
import {syntheticFixture,stagedModelClient} from '../src/db/application-writer-profile-registry-read-model.test.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;writerAuthorized:boolean;generationAuthenticated:boolean;readyAuthorized:boolean}
interface PublisherModule {createConnectedPublisher(installation:unknown,collect:()=>Promise<unknown>,sourceVerifier:{verify:()=>Promise<void>},approval:unknown):{preflightCurrentProfile(pool:unknown,options:{deadlineAt:number}):Promise<PreflightDiagnostics>}}
interface OperatorModule {main(args:string[]):Promise<{event:string;outcome:string;writerAuthorized:boolean;readyAuthorized:boolean}>}
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}>}
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'})
@@ -56,17 +56,23 @@ function fixture(fault=''){
'../../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'},
}
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}},approval)
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}
}
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)
assert.equal(result.sourceMatched,true);assert.equal(result.rolesMatched,true);assert.equal(result.catalogueMatched,true)
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(!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'])
})
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)
assert(!f.queries.some(q=>/^(?:INSERT|UPDATE|DELETE|COMMIT|CREATE|ALTER|DROP)\b/.test(q)))
})
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)
})
@@ -74,23 +80,28 @@ test('existing operator main dispatches preflight only after protected input val
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
let failEnd=false,asyncError=false,protectedRefused=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}
const result={state:'matched',epoch:'1',headSha256:'c'.repeat(64),notAfter:Date.now()+60000,platformProtectionVerified:true}
const protectedVerifier={verify:async()=>({platformProtectionVerified:true})}
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:()=>({})},
pg:{Pool},'./writer-profile/publisher.mjs':{createConnectedPublisher:()=>({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')}})},
'./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/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)},
}
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)
assert.equal(output.event,'application-writer-profile-preflight');assert.equal(output.outcome,'matched');assert.equal(output.writerAuthorized,false);assert.equal(output.readyAuthorized,false)
assert.deepEqual(reads,['bootstrap','approval','definition','transport','ca']);assert.deepEqual(events,['approval','transport','preflight','pool-end'])
assert.equal(output.event,'application-writer-profile-preflight');assert.equal(output.outcome,'matched');assert.equal(output.platformProtectionVerified,true);assert.equal(output.writerAuthorized,false);assert.equal(output.readyAuthorized,false)
assert.deepEqual(reads,['bootstrap','approval','definition','transport','ca']);assert.deepEqual(events,['approval','transport','protected-source','preflight','pool-end'])
failEnd=true;await assert.rejects(compile<OperatorModule>(cli,modules).main(args),/Pool cleanup unconfirmed/)
failEnd=false;asyncError=true;await assert.rejects(compile<OperatorModule>(cli,modules).main(args),/Publisher terminal unconfirmed/)
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')
})
@@ -0,0 +1,129 @@
/** Explicit host-operator inspection/acceptance. No restore, service restart, role
* enrollment or source/READY issuer. Populated data is accepted, not certified. */
import assert from 'node:assert/strict'
import path from 'node:path'
import {fileURLToPath} from 'node:url'
import {mkdir,readdir} from 'node:fs/promises'
import {readProtected,protectedDirectory,writeFile,withJournalLease,sha} from './operator-files.mjs'
import {poolOptions,STARTUP_OPTIONS,assertJsDriverEnvironment} from './operator-contract.mjs'
import {readMaintenanceRecoveryFacts,readMaintenanceAcceptanceRecords} from '../../src/services/database-maintenance-startup.js'
import {parseMaintenanceJSON,runtimeDatabaseTargetSha256,evaluateMaintenanceAcceptances,validateMaintenanceKnownCurrent,validateMaintenanceAcceptance,MAINTENANCE_ACCEPTANCE_FORMAT,type MaintenanceKnownCurrent} from '../../src/services/database-maintenance-resolution.js'
import {captureMigrationSchema} from '../../src/db/migration-schema-inventory.js'
import {MIGRATION_MANIFEST,CURRENT_REQUIRED_TABLES,migrationManifestDigest,inventoryMigrationDirectory} from '../../src/db/migration-manifest.js'
import type {Pool,PoolClient} from 'pg'
const hex=(v:unknown):v is string=>typeof v==='string'&&/^[a-f0-9]{64}$/.test(v)
function object(v:unknown):asserts v is Record<string,unknown>{assert(v&&typeof v==='object'&&!Array.isArray(v))}
function exact(v:unknown,keys:string[]):asserts v is Record<string,unknown>{object(v);assert.equal(Object.keys(v).sort().join('\0'),keys.sort().join('\0'))}
const stamp=(v:unknown):v is string=>typeof v==='string'&&Number.isFinite(Date.parse(v))&&new Date(v).toISOString()===v
const stable=(v:unknown)=>JSON.stringify(v)
const flags=['state-dir','transport','transport-sha256','runtime-target','runtime-target-sha256']
const validityMs=900000
/** Explicit local-container transport never falls back from TLS or environment.
* Caller launch review supplies --network container:<exact reviewed PG id>;
* this module has no Docker socket/API and claims only loopback + physical SQL proof. */
export function maintenancePoolConfiguration(value:unknown,ca?:Buffer,caSha256?:string){
object(value);const mode=value.mode===undefined?'tls':value.mode;assert(mode==='tls'||mode==='local-loopback')
exact(value,mode==='local-loopback'?['format','mode','connection']:value.mode===undefined?['format','connection','tls']:['format','mode','connection','tls'])
assert.equal(value.format,'database-maintenance-transport-v1');exact(value.connection,['host','port','user','password','database']);const connection=value.connection
assert(typeof connection.host==='string'&&typeof connection.user==='string'&&/^[^\x00-\x1f\x7f]{1,63}$/.test(connection.user)&&typeof connection.password==='string'&&connection.password.length>0&&connection.password.length<=4096&&!connection.password.includes('\0')&&typeof connection.database==='string'&&/^[^\x00-\x1f\x7f]{1,63}$/.test(connection.database)&&typeof connection.port==='number'&&Number.isSafeInteger(connection.port)&&Number(connection.port)>0&&Number(connection.port)<=65535)
let options,identity:unknown
if(mode==='tls'){
exact(value.tls,['serverName','spkiSha256']);assert(typeof value.tls.serverName==='string'&&/^[a-zA-Z0-9][a-zA-Z0-9.-]{0,252}$/.test(value.tls.serverName)&&hex(value.tls.spkiSha256));assert(ca&&hex(caSha256))
options=poolOptions(connection,{roles:{publisher:{name:connection.user}},source:{databaseName:connection.database},tls:{...value.tls,caSha256}},ca)
identity={mode,host:connection.host,port:connection.port,database:connection.database,tls:{...value.tls,caSha256}}
}else{
assert(connection.host==='127.0.0.1'||connection.host==='::1','Plaintext maintenance requires exact loopback and reviewed PostgreSQL container namespace');assert(ca===undefined&&caSha256===undefined)
options={host:connection.host,port:connection.port,user:connection.user,password:connection.password,database:connection.database,max:1,connectionTimeoutMillis:5000,idleTimeoutMillis:5000,statement_timeout:10000,lock_timeout:5000,options:STARTUP_OPTIONS,application_name:'mmapp-maintenance-acceptance',client_encoding:'UTF8',ssl:false}
identity={mode,host:connection.host,port:connection.port,database:connection.database}
}
return {options,transportBinding:{mode,endpointSha256:sha(stable(identity))} as MaintenanceKnownCurrent['transport']}
}
export function maintenanceApproval(value:unknown,inspectionSha256:string,inspectionCreatedAt:string){
exact(value,['format','resolutionId','decision','actorId','inspectionSha256','issuedAt','expiresAt','managedBackendStopped','historicOutcomesRemainUnknown','automaticRestore'])
assert(value.format==='database-maintenance-current-state-approval-v1'&&value.decision==='accept-observed-current-state'&&typeof value.resolutionId==='string'&&/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(value.resolutionId))
assert(typeof value.actorId==='string'&&/^[^\x00-\x1f\x7f]{1,160}$/.test(value.actorId)&&value.inspectionSha256===inspectionSha256&&stamp(value.issuedAt)&&stamp(value.expiresAt))
const now=Date.now();assert(Date.parse(value.issuedAt)>=Date.parse(inspectionCreatedAt)&&Date.parse(value.issuedAt)<=now&&now<Date.parse(value.expiresAt)&&Date.parse(value.expiresAt)-Date.parse(value.issuedAt)<=validityMs)
assert(value.managedBackendStopped===true&&value.historicOutcomesRemainUnknown===true&&value.automaticRestore===false)
return value as unknown as {resolutionId:string;actorId:string;issuedAt:string;expiresAt:string}
}
/** Genuine observation, not a supplied snapshot factory. Owner/transaction ends
* must be confirmed before a caller can publish an acceptance. Exclusive key
* protects cooperative observations; stopped-backend assumption spans publication. */
export async function observeMaintenanceCurrent(pool:Pick<Pool,'connect'>,transport:MaintenanceKnownCurrent['transport']){
let client:PoolClient|undefined,locked=false,begun=false,bad=false,faulted=false
const fault=()=>{faulted=true},live=()=>{assert(!faulted)}
try{
client=await pool.connect();client.on('error',fault);client.on('end',fault)
const query=async(text:string,values:unknown[]=[])=>{live();const result=await client!.query(text,values);live();return result}
await query("SET statement_timeout='10s'");await query("SET lock_timeout='5s'")
const lease=await query('SELECT pg_catalog.pg_try_advisory_lock(8723110::bigint) AS locked');assert(lease.rows.length===1&&lease.rows[0].locked===true);locked=true
await query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');begun=true
for(const sql of ["SET LOCAL search_path=pg_catalog","SET LOCAL TimeZone='UTC'","SET LOCAL DateStyle='ISO, YMD'","SET LOCAL standard_conforming_strings=on","SET LOCAL statement_timeout='10s'"])await query(sql)
const identity=await query(`SELECT c.system_identifier::text AS "systemIdentifier",d.oid::text AS "databaseOid",d.datname AS "databaseName",pg_catalog.current_setting('server_version_num')::int AS "serverVersionNum",pg_catalog.pg_is_in_recovery() AS "inRecovery" FROM pg_catalog.pg_database d CROSS JOIN pg_catalog.pg_control_system() c WHERE d.datname=pg_catalog.current_database()`)
assert.equal(identity.rows.length,1)
// The optional writer-profile control schema is not installed by ordinary
// application migrations. Observe its presence in this same transaction;
// detailed capture still refuses every other unexpected namespace.
const control=await query("SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname='mmapp_control'")
assert(control.rows.length<=1&&control.rows.every(row=>row.nspname==='mmapp_control'))
const schema=await captureMigrationSchema({query:async(text,values)=>query(text,values)},{scope:{applicationNamespaces:['public'],excludedControlNamespaces:control.rows.length?['mmapp_control']:[]},deadlineAt:Date.now()+60000})
const marker=schema.sections.relations.find(row=>row.namespace==='public'&&row.name==='schema_migrations'),column=schema.sections.columns.find(row=>row.namespace==='public'&&row.relation==='schema_migrations'&&row.name==='id'&&row.dropped===false)
assert(marker?.kind==='r'&&marker.persistence==='p'&&marker.row_security===false&&marker.force_row_security===false&&column?.type==='text')
for(const table of CURRENT_REQUIRED_TABLES)assert(schema.sections.relations.some(row=>row.namespace==='public'&&row.name===table&&['r','p'].includes(String(row.kind))&&row.row_security===false&&row.force_row_security===false))
const markers=(await query('SELECT id FROM public.schema_migrations ORDER BY id LIMIT $1',[MIGRATION_MANIFEST.length+1])).rows
assert.equal(stable(markers),stable(MIGRATION_MANIFEST.map(entry=>({id:entry.file.slice(0,-4)}))))
const knownCurrent={transport,source:identity.rows[0],schemaSha256:schema.sha256,rawSchemaSha256:schema.observedRaw.sha256,physicalSchemaSha256:schema.observedPhysical.sha256,manifestSha256:migrationManifestDigest(),markersSha256:sha(stable(markers))}
validateMaintenanceKnownCurrent(knownCurrent)
const end=await query('ROLLBACK');assert.equal(end.command,'ROLLBACK');begun=false
const unlock=await query('SELECT pg_catalog.pg_advisory_unlock(8723110::bigint) AS unlocked');assert(unlock.rows.length===1&&unlock.rows[0].unlocked===true);locked=false
return {knownCurrent,schema}
}catch(error){bad=true;throw error}finally{
if(client){let cleanupFailed=false
try{if(begun&&!faulted){const end=await client.query('ROLLBACK');assert.equal(end.command,'ROLLBACK')}if(locked&&!faulted){const end=await client.query('SELECT pg_catalog.pg_advisory_unlock(8723110::bigint) AS unlocked');assert(end.rows[0]?.unlocked===true)}}catch{cleanupFailed=true}
try{client.off('error',fault);client.off('end',fault)}catch{cleanupFailed=true}
try{client.release(bad||faulted||cleanupFailed)}catch{cleanupFailed=true}
assert(!cleanupFailed&&!faulted,'Read-only maintenance owner cleanup unconfirmed')
}
}
}
async function snapshot(stateDirectory:string,target:string,records:readonly Buffer[]){
const state=await readMaintenanceRecoveryFacts(stateDirectory);assert(state.kind==='journal'&&state.generation)
const accepted=evaluateMaintenanceAcceptances(records,state.journal,state.vault.evidence,target)
const unresolvedOperations=state.operations.filter(op=>op.state!=='applied'),vaultCoverage=state.vault.evidence.filter(marker=>state.vault.unresolvedFiles.includes(marker.file))
const newUnresolved=unresolvedOperations.some(op=>!accepted.acceptedCoreIds.has(op.operationId))||state.vault.unresolvedFiles.some(file=>!accepted.acceptedVaultFiles.has(file))
return {newUnresolved,external:{journalCoverage:{prefixBytes:state.journal.length,sha256:sha(state.journal),unresolvedOperations},vaultCoverage,generationObservedSha256:sha(state.generation),previousResolutionSha256:accepted.headSha256}}
}
export async function maintenanceMain(args:string[]){
assertJsDriverEnvironment(process.env);const resolve=args[0]==='maintenance-resolve';assert(resolve||args[0]==='maintenance-inspect')
const required=[...flags,...(resolve?['inspection','inspection-sha256','approval','approval-sha256']:['output'])],names=[...required,'ca','ca-sha256'],values=new Map<string,string>()
assert(args.length===1+required.length*2||args.length===1+(required.length+2)*2)
for(let i=1;i<args.length;i+=2){const name=args[i].slice(2);assert(args[i].startsWith('--')&&names.includes(name)&&!values.has(name)&&args[i+1]);values.set(name,args[i+1])}
for(const name of required)assert(values.has(name));assert(values.has('ca')===values.has('ca-sha256'));const get=(name:string)=>values.get(name)!
for(const name of [...values.keys()].filter(name=>name.endsWith('sha256')))assert(hex(get(name)))
const stateDirectory=get('state-dir');await protectedDirectory(stateDirectory)
const transport=parseMaintenanceJSON(await readProtected(get('transport'),16384,get('transport-sha256')));object(transport);const mode=transport.mode===undefined?'tls':transport.mode;assert(mode==='tls'||mode==='local-loopback');assert(values.has('ca')===(mode==='tls'))
const ca=mode==='tls'?await readProtected(get('ca'),65536,get('ca-sha256')):undefined
const runtime=parseMaintenanceJSON(await readProtected(get('runtime-target'),16384,get('runtime-target-sha256')));exact(runtime,['format','databaseUrl']);assert(runtime.format==='database-maintenance-runtime-target-v1'&&typeof runtime.databaseUrl==='string');const runtimeUrl=new URL(runtime.databaseUrl);assert(!runtimeUrl.username&&!runtimeUrl.password)
const target=runtimeDatabaseTargetSha256(runtime.databaseUrl),{options,transportBinding}=maintenancePoolConfiguration(transport,ca,mode==='tls'?get('ca-sha256'):undefined)
exact(transport.connection,['host','port','user','password','database']);assert.equal(decodeURIComponent(runtimeUrl.pathname.slice(1)),transport.connection.database)
const records=await readMaintenanceAcceptanceRecords(stateDirectory);if(resolve)assert(records.length<64,'Maintenance acceptance history bound reached');const currentState=await snapshot(stateDirectory,target,records),before=currentState.external;if(resolve)assert(currentState.newUnresolved,'No new unresolved history to accept')
await inventoryMigrationDirectory(path.join(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url)))),'drizzle'))
let inspection:Record<string,unknown>|undefined,approved:ReturnType<typeof maintenanceApproval>|undefined
if(resolve){inspection=parseMaintenanceJSON(await readProtected(get('inspection'),67108864,get('inspection-sha256'))) as Record<string,unknown>;exact(inspection,['format','createdAt','expiresAt','runtimeTargetSha256','acceptanceRequired','external','knownCurrent','schema']);assert(inspection.format==='database-maintenance-current-state-inspection-v1'&&stamp(inspection.createdAt)&&stamp(inspection.expiresAt)&&Date.parse(inspection.createdAt)<=Date.now()&&Date.now()<Date.parse(inspection.expiresAt)&&Date.parse(inspection.expiresAt)-Date.parse(inspection.createdAt)<=validityMs&&inspection.runtimeTargetSha256===target&&inspection.acceptanceRequired===true);validateMaintenanceKnownCurrent(inspection.knownCurrent);assert.equal(stable(inspection.external),stable(before));approved=maintenanceApproval(parseMaintenanceJSON(await readProtected(get('approval'),16384,get('approval-sha256'))),get('inspection-sha256'),inspection.createdAt)}
const {default:pg}=await import('pg');assertJsDriverEnvironment(process.env)
const runProof=async()=>{const pool=new pg.Pool(options);let failed=false;pool.on('error',()=>{failed=true});try{const result=await observeMaintenanceCurrent(pool,transportBinding);await pool.end();assert(!failed);return result}catch(error){try{await pool.end()}catch{/* preserve refusal */}throw error}}
if(!resolve){const result=await runProof();assert.equal(stable((await snapshot(stateDirectory,target,await readMaintenanceAcceptanceRecords(stateDirectory))).external),stable(before));const nowMs=Date.now(),now=new Date(nowMs).toISOString(),out={format:'database-maintenance-current-state-inspection-v1',createdAt:now,expiresAt:new Date(nowMs+validityMs).toISOString(),runtimeTargetSha256:target,acceptanceRequired:currentState.newUnresolved,external:before,...result};const raw=Buffer.from(stable(out));assert(raw.length<=67108864);await writeFile(get('output'),raw,{flag:'wx',mode:0o600});return {event:'database-maintenance-inspection',inspectionSha256:sha(raw),unresolvedCoreOperations:before.journalCoverage.unresolvedOperations.length,unresolvedVaultFiles:before.vaultCoverage.length,state:currentState.newUnresolved?'inspection-required':before.previousResolutionSha256?'historically-accepted':'no-unresolved-history',historicalAcceptanceHeadSha256:before.previousResolutionSha256,humanApprovalRequired:currentState.newUnresolved,knownCurrentObserved:true,archiveDataEquivalenceProved:false,historicOutcomesChanged:false,automaticRestore:false}}
const resolutionDirectory=path.join(stateDirectory,'maintenance-acceptances');await mkdir(resolutionDirectory,{recursive:true,mode:0o700});await protectedDirectory(resolutionDirectory)
return withJournalLease(resolutionDirectory,async()=>{
// Existing immutable records are reread while our proven O_EXCL lease is held.
// Runtime reader refuses this lease; never infer stale ownership from PID/time.
const expected=records.map(raw=>{const v=parseMaintenanceJSON(raw) as {resolutionId:string};return v.resolutionId+'.json'}).sort()
assert.equal((await readdir(resolutionDirectory)).sort().join('\0'),[...expected,'publisher-lease.lock'].sort().join('\0'))
for(let i=0;i<records.length;i++){const value=parseMaintenanceJSON(records[i]) as {resolutionId:string};assert.equal(sha(await readProtected(path.join(resolutionDirectory,value.resolutionId+'.json'),1048576)),sha(records[i]))}
const observed=await runProof();assert.equal(stable(observed.knownCurrent),stable(inspection!.knownCurrent));assert.equal(stable(observed.schema),stable(inspection!.schema))
assert.equal(stable((await snapshot(stateDirectory,target,records)).external),stable(before));assert(Date.now()<Date.parse(approved!.expiresAt)&&Date.now()<Date.parse(inspection!.expiresAt as string))
const record={format:MAINTENANCE_ACCEPTANCE_FORMAT,resolutionId:approved!.resolutionId,decision:'accept-observed-current-state',historicOutcomeChanged:false,automaticRestore:false,actor:{kind:'host-operator',id:approved!.actorId},approvalSha256:get('approval-sha256'),inspectionSha256:get('inspection-sha256'),approvedAt:approved!.issuedAt,approvalExpiresAt:approved!.expiresAt,acceptedAt:new Date().toISOString(),runtimeTargetSha256:target,...before,knownCurrent:observed.knownCurrent}
validateMaintenanceAcceptance(record);const raw=Buffer.from(stable(record));assert(raw.length<=1048576,'Maintenance acceptance byte bound exceeded');await writeFile(path.join(resolutionDirectory,record.resolutionId+'.json'),raw,{flag:'wx',mode:0o600})
return {event:'database-maintenance-current-state-accepted',outcome:'committed',resolutionId:record.resolutionId,acceptanceSha256:sha(raw),archiveDataEquivalenceProved:false,historicOutcomesChanged:false,automaticRestore:false}
})
}
@@ -0,0 +1,84 @@
/** Explicit operator/lifecycle read component. No startup, grants or READY. */
import assert from 'node:assert/strict'
import {lstat} from 'node:fs/promises'
import path from 'node:path'
import {protectedDirectory,readProtected} from './operator-files.mjs'
import {createDatabaseSourceBindingVerifier,readDatabaseSourceBindingRegistry,
type DatabaseSourceDeploymentPin} from '../../src/db/database-source-binding.js'
import type {DatabaseSourceObservation,DatabaseSourceObservationOptions} from '../../src/db/database-source-observation.js'
import type {ProbeStorageSql} from '../../src/services/server-probe-journal-maintenance.js'
const hex=(value:unknown):value is string=>typeof value==='string'&&/^[a-f0-9]{64}$/.test(value)
function fence(options:DatabaseSourceObservationOptions){
assert(Number.isSafeInteger(options.deadlineAt)&&Date.now()<options.deadlineAt&&!options.signal?.aborted,'Protected source lifetime refused')
}
async function identity(filename:string,content=false){
const s=await lstat(filename,{bigint:true})
// BigInt identity is never narrowed to Number. Content changes/permission
// changes are checked around the whole verified read, not just open().
return [s.dev,s.ino,s.mode,s.uid,s.gid,s.nlink,...(content?[s.size,s.mtimeNs,s.ctimeNs]:[])].map(String).join(':')
}
async function protectedState(pin:DatabaseSourceDeploymentPin){
// Proven POSIX protection refuses unsupported Windows ACL BEFORE file reads.
await protectedDirectory(pin.registryDirectory)
const directories:Record<string,string>={}
for(let p=pin.registryDirectory;;p=path.dirname(p)){
directories[p]=await identity(p,p===pin.registryDirectory)
if(p===path.dirname(p))break
}
const names=['operator-public.pem','head-'+pin.currentHeadSha256+'.json']
const content:Record<string,Buffer>={},identities:Record<string,string>={}
const collect=async(name:string,maximum:number,expected?:string)=>{
const filename=path.join(pin.registryDirectory,name)
const before=await identity(filename,true)
content[name]=await readProtected(filename,maximum,expected)
assert.equal(await identity(filename,true),before,'Protected source file changed during read')
identities[name]=before
}
await collect(names[0],4096)
await collect(names[1],8192,pin.currentHeadSha256)
const head=JSON.parse(new TextDecoder('utf8',{fatal:true}).decode(content[names[1]]))
assert(hex(head?.body?.bindingSha256),'Pinned source binding digest required')
const bindingName='binding-'+head.body.bindingSha256+'.json'
await collect(bindingName,8192,head.body.bindingSha256)
await protectedDirectory(pin.registryDirectory)
for(const [directory,wanted]of Object.entries(directories))assert.equal(await identity(directory,directory===pin.registryDirectory),wanted,'Protected source directory changed')
return {directories,identities,content}
}
function same(before:Awaited<ReturnType<typeof protectedState>>,after:Awaited<ReturnType<typeof protectedState>>){
assert.deepEqual(after.directories,before.directories,'Protected source directory drift')
assert.deepEqual(after.identities,before.identities,'Protected source identity/mode drift')
assert.deepEqual(after.content,before.content,'Protected source content drift')
}
function captured(raw:DatabaseSourceDeploymentPin){
const pin=Object.freeze({...raw})
// Existing strict deployment schema rejects unknown/malformed pin fields.
const verifier=createDatabaseSourceBindingVerifier(pin)
return {pin,verifier}
}
/** Signature/head/source DATA plus actual POSIX protection of the pinned files.
* The supplied pin still needs independent deployment approval; protection
* cannot distinguish a restored clone/generation or authenticate all writers. */
export async function readProtectedDatabaseSourceBindingRegistry(raw:DatabaseSourceDeploymentPin,options:DatabaseSourceObservationOptions){
const {pin}=captured(raw);const lifetime={...options};fence(lifetime)
const before=await protectedState(pin);fence(lifetime)
const result=await readDatabaseSourceBindingRegistry(pin);fence(lifetime)
const after=await protectedState(pin);fence(lifetime);same(before,after)
return Object.freeze({...result,platformProtectionVerified:true as const,
generationAuthenticated:false as const,allWriterDrainAuthenticated:false as const,readyAuthorized:false as const})
}
/** Same-facade source verification is retained; filesystem JSON is never a
* source observation, writer capability or independent generation authority. */
export function createProtectedDatabaseSourceBindingVerifier(raw:DatabaseSourceDeploymentPin){
const {pin,verifier}=captured(raw)
return Object.freeze({async verify(sql:ProbeStorageSql,observation:DatabaseSourceObservation,options:DatabaseSourceObservationOptions){
const lifetime={...options};fence(lifetime)
const before=await protectedState(pin);fence(lifetime)
const result=await verifier.verify(sql,observation,lifetime);fence(lifetime)
const after=await protectedState(pin);fence(lifetime);same(before,after)
return Object.freeze({...result,platformProtectionVerified:true as const,
generationAuthenticated:false as const,allWriterDrainAuthenticated:false as const,readyAuthorized:false as const})
}})
}
@@ -0,0 +1,85 @@
/** Genuine Ed25519/files; POSIX permission metadata is modeled on Windows.
* No SQL, service, deployment or real POSIX acceptance is claimed. */
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 crypto from 'node:crypto'
import {createRequire} from 'node:module'
import type {Stats,BigIntStats} from 'node:fs'
import type {DatabaseSourceDeploymentPin} from '../../src/db/database-source-binding.ts'
import type * as ProtectedApi from './protected-source-registry.mts'
import {runInNewContext} from 'node:vm'
import {readProtectedDatabaseSourceBindingRegistry} from './protected-source-registry.mts'
import {readDatabaseSourceBindingRegistry,sourceBindingBytes,sourceBindingCanonical,sourceBindingSha256,DATABASE_SOURCE_SIGNATURE_DOMAINS} from '../../src/db/database-source-binding.ts'
const require=createRequire(import.meta.url),ts=require('typescript')
const helperSource=await fs.readFile(new URL('./operator-files.mjs',import.meta.url),'utf8')
const source=await fs.readFile(new URL('./protected-source-registry.mts',import.meta.url),'utf8')
const deadline=()=>({deadlineAt:Date.now()+10000})
function compile(text:string){return ts.transpileModule(text,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText}
function evaluate<T=Record<string,unknown>>(text:string,imports:Record<string,unknown>,processModel:unknown=process):T{
const exports:Record<string,unknown>={}
runInNewContext(compile(text),{exports,require:(name:string)=>imports[name]??require(name),process:processModel,Buffer,TextDecoder,console})
return exports as T
}
interface Fixture {pin:DatabaseSourceDeploymentPin;faults:Record<string,boolean>;api:typeof ProtectedApi;verificationReads:()=>number;root:string}
async function own(work:(f:Fixture)=>Promise<void>){
const root=await fs.mkdtemp(path.join(os.tmpdir(),'mm-protected-source-'))
try{
const key=crypto.generateKeyPairSync('ed25519'),installationId=crypto.randomUUID()
const signed=(body:unknown,domain:string)=>sourceBindingBytes({body,signature:crypto.sign(null,Buffer.from(domain+sourceBindingCanonical(body)),key.privateKey).toString('base64url')})
const body={format:'database-source-binding-v1',installationId,sourceIdentityId:crypto.randomUUID(),generation:crypto.randomUUID(),ordinal:1,
source:{systemIdentifier:'123',databaseOid:16384,databaseName:'synthetic',sessionUser:'capture',currentUser:'capture',inRecovery:false,serverVersionNum:180004}}
const binding=signed(body,DATABASE_SOURCE_SIGNATURE_DOMAINS.binding),bindingSha256=sourceBindingSha256(binding)
const head=signed({format:'database-source-registry-head-v1',installationId,sourceIdentityId:body.sourceIdentityId,generation:body.generation,ordinal:1,bindingSha256,previousHeadSha256:null},DATABASE_SOURCE_SIGNATURE_DOMAINS.head)
const pin={registryDirectory:root,installationId,publicKeySpkiSha256:sourceBindingSha256(key.publicKey.export({format:'der',type:'spki'})),currentHeadSha256:sourceBindingSha256(head)}
await fs.writeFile(path.join(root,'operator-public.pem'),key.publicKey.export({format:'pem',type:'spki'}),{mode:0o600})
await fs.writeFile(path.join(root,'head-'+pin.currentHeadSha256+'.json'),head,{mode:0o600})
await fs.writeFile(path.join(root,'binding-'+bindingSha256+'.json'),binding,{mode:0o600})
const faults={modeFile:false,modeDirectory:false,symlink:false,replacement:false,lateMode:false,changed:false};let verificationReads=0
const metadata=(s:Stats|BigIntStats,filename:string,big:boolean)=>{
const n=(value:number)=>big?BigInt(value):value,isRoot=filename===root,isFile=s.isFile()
const mode=(isFile?0o100600:0o40700)|((isFile&&faults.modeFile||isRoot&&faults.modeDirectory||isFile&&faults.lateMode&&faults.changed)?0o022:0)
return {...s,mode:n(mode),uid:n(1000),gid:n(1000),ino:faults.replacement&&faults.changed&&isFile?(typeof s.ino==='bigint'?s.ino+1n:s.ino+1):s.ino,
isFile:()=>isFile,isDirectory:()=>s.isDirectory(),isSymbolicLink:()=>faults.symlink&&isRoot}
}
const modelFs={...fs,lstat:async(filename:string,options?:{bigint?:boolean})=>metadata(await fs.lstat(filename,{bigint:options?.bigint===true}),filename,options?.bigint===true),open:async(filename:string,flags:number)=>{
const file=await fs.open(filename,flags)
return {stat:async()=>metadata(await file.stat(),filename,false),read:file.read.bind(file),close:file.close.bind(file)}
}}
const processModel={platform:'linux',getuid:()=>1000}
const proven=evaluate(helperSource,{'node:fs/promises':modelFs},processModel)
const bindingModule={readDatabaseSourceBindingRegistry:async(p:DatabaseSourceDeploymentPin)=>{
verificationReads++;const result=await readDatabaseSourceBindingRegistry(p);faults.changed=true;return result
},createDatabaseSourceBindingVerifier:()=>({verify:async()=>{verificationReads++;faults.changed=true;return {genuineSourceFactsMatched:true,generationAuthenticated:false,readyAuthorized:false}}})}
const api=evaluate<typeof ProtectedApi>(source,{'node:fs/promises':modelFs,'./operator-files.mjs':proven,'../../src/db/database-source-binding.js':bindingModule},processModel)
await work({pin,faults,api,verificationReads:()=>verificationReads,root})
}finally{const rel=path.relative(os.tmpdir(),root);assert(rel&&!rel.startsWith('..')&&!path.isAbsolute(rel));await fs.rm(root,{recursive:true,force:true})}
}
test('unsupported Windows refuses before registry signature reads',{skip:process.platform!=='win32'},async()=>{
await assert.rejects(readProtectedDatabaseSourceBindingRegistry({registryDirectory:path.resolve('not-created-protected-source'),installationId:crypto.randomUUID(),publicKeySpkiSha256:'a'.repeat(64),currentHeadSha256:'b'.repeat(64)},deadline()),/Windows ACL/)
})
test('proven POSIX helpers plus genuine signature/head bytes retain false generation/READY',()=>own(async f=>{
const value=await f.api.readProtectedDatabaseSourceBindingRegistry(f.pin,deadline())
assert.equal(value.platformProtectionVerified,true);assert.equal(value.generationAuthenticated,false);assert.equal(value.allWriterDrainAuthenticated,false);assert.equal(value.readyAuthorized,false)
assert.equal(value.headSha256,f.pin.currentHeadSha256);assert.equal(f.verificationReads(),1)
}))
for(const fault of ['modeFile','modeDirectory','symlink'])test(fault+' refuses before cryptographic registry read',()=>own(async f=>{
f.faults[fault]=true;await assert.rejects(f.api.readProtectedDatabaseSourceBindingRegistry(f.pin,deadline()));assert.equal(f.verificationReads(),0)
}))
for(const fault of ['replacement','lateMode'])test(fault+' during genuine signature verification refuses protected output',()=>own(async f=>{
f.faults[fault]=true;await assert.rejects(f.api.readProtectedDatabaseSourceBindingRegistry(f.pin,deadline()));assert.equal(f.verificationReads(),1)
}))
test('expired/aborted lifetime refuses before filesystem or source access',()=>own(async f=>{
const abort=new AbortController();abort.abort()
for(const options of [{deadlineAt:Date.now()-1},{deadlineAt:Date.now()+1000,signal:abort.signal}])await assert.rejects(f.api.readProtectedDatabaseSourceBindingRegistry(f.pin,options))
assert.equal(f.verificationReads(),0)
}))
test('source verifier drift cannot grant protected output',()=>own(async f=>{
f.faults.lateMode=true
const verify=f.api.createProtectedDatabaseSourceBindingVerifier(f.pin).verify
await assert.rejects(verify({} as Parameters<typeof verify>[0],{} as Parameters<typeof verify>[1],deadline()))
assert.equal(f.verificationReads(),1)
}))
+3 -3
View File
@@ -141,14 +141,14 @@ async function preflightCurrentProfile(pool,{deadlineAt=Date.now()+120000}={}){
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);await sourceVerifier.verify(facade,observation,options)
const observation=await captureDatabaseSourceObservation(facade,options),initialSource=await sourceVerifier.verify(facade,observation,options)
const security=await protectedStructure(s,installation),catalogue=await collectFixedCatalogue(s)
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(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')
await sourceVerifier.verify(facade,observation,options);await assertDatabaseSourceObservationCurrent(facade,observation,options)
const terminalSource=await sourceVerifier.verify(facade,observation,options);await assertDatabaseSourceObservationCurrent(facade,observation,options)
sameOwned(first,await ownedContext(s,installation,PUBLISHER,'publisher-read'))
// Recheck the live interval after the potentially lengthy catalogue walk.
const clock=(await s.query('SELECT (EXTRACT(EPOCH FROM pg_catalog.clock_timestamp())*1000)::bigint::text AS now')).rows
@@ -157,7 +157,7 @@ async function preflightCurrentProfile(pool,{deadlineAt=Date.now()+120000}={}){
if(approval.expiresAt<=Date.now())throw Error('Preflight approval expired before terminal')
const terminal=await s.query('ROLLBACK');if(terminal.command!=='ROLLBACK')throw Error('Preflight terminal unconfirmed');s.begun(false)
return Object.freeze({state:'matched',epoch:current.epoch,headSha256:current.headSha256,notAfter:current.policy.notAfter,
sourceMatched:true,rolesMatched:true,catalogueMatched:true,writerAuthorized:false,generationAuthenticated:false,readyAuthorized:false})
sourceMatched:true,rolesMatched:true,catalogueMatched:true,platformProtectionVerified:initialSource?.platformProtectionVerified===true&&terminalSource?.platformProtectionVerified===true,writerAuthorized:false,generationAuthenticated:false,readyAuthorized:false})
})
}
async function boundedPending(filename){
+5
View File
@@ -3,10 +3,14 @@ import { applySqlMigrations } from "./migrate.js"
import { dropExpiredPartitions, ensurePartitionsAround } from "./partitions.js"
import { importSqliteToPostgres, shouldImportSqlite, sqliteFileLooksPresent } from "./sqlite-import.js"
import { env } from "../config.js"
import { mcpStateDirectory } from "../modules/mcp/security.js"
import { assertDatabaseMaintenanceStartup } from "../services/database-maintenance-startup.js"
import { reconcileDatabaseRestoreAudits } from "../services/database-restore-audit.js"
const ETL_LOCK = 8723101
export async function initDatabase(): Promise<void> {
await assertDatabaseMaintenanceStartup(mcpStateDirectory(), env.DATABASE_URL)
await pool.query("SELECT 1")
await applySqlMigrations(pool)
await ensurePartitionsAround(pool)
@@ -39,4 +43,5 @@ export async function initDatabase(): Promise<void> {
}
await dropExpiredPartitions(pool)
await ensurePartitionsAround(pool)
await reconcileDatabaseRestoreAudits(pool, mcpStateDirectory())
}
+4
View File
@@ -4,6 +4,8 @@ import { serializerCompiler, validatorCompiler } from "@fastify/type-provider-zo
import { monitorEventLoopDelay } from "node:perf_hooks"
import { env } from "./config.js"
import { initDatabase } from "./db/bootstrap.js"
import { mcpStateDirectory } from "./modules/mcp/security.js"
import { assertDatabaseMaintenanceStartup } from "./services/database-maintenance-startup.js"
import { closePool } from "./db/index.js"
import authPlugin, { requireApiAuth } from "./plugins/auth.js"
import { installDatabaseRequestGate } from "./plugins/database-request-gate.js"
@@ -186,6 +188,8 @@ const isMain =
if (isMain) {
try {
// Resolve external maintenance before init/migrations or consumer startup.
await assertDatabaseMaintenanceStartup(mcpStateDirectory(), env.DATABASE_URL)
await initDatabase()
const app = await buildApp()
let shuttingDown = false
@@ -1,3 +1,4 @@
import { isConfirmedRoutingFibTable } from "../../services/routing-table-fib.js"
import type { RouteAiProfile } from "@mmapp/contracts/mcp-routing"
import { nativeBgpRows, type NativeBgpRow } from "../../services/bgp-export-model.js"
import { stableBgpPolicyRow } from "../../services/bgp-filter-binding-model.js"
@@ -56,7 +57,7 @@ export async function readControlPathContext(profile: RouteAiProfile, candidateI
return matching[0]
}
const sourceId = profile.serverId, table = unique(await get(sourceId, "/routing/table"), profile.probeVrf)
if (![true, "true", "yes"].includes(table.fib as string | boolean) || [true, "true", "yes"].includes(table.disabled as string | boolean) || [true, "true", "yes"].includes(table.invalid as string | boolean)) throw new McpError("STATE_UNAVAILABLE", "FIB probe VRF контрольного пути не подтверждён", 409)
if (!isConfirmedRoutingFibTable(table)) throw new McpError("STATE_UNAVAILABLE", "FIB probe VRF контрольного пути не подтверждён", 409)
const vrfs = await get(sourceId, "/ip/vrf"), effectiveVrfs: NativeBgpRow[] = []
await enabled(sourceId, "/ip/vrf", unique(vrfs, profile.probeVrf))
for (const row of vrfs) effectiveVrfs.push([true, "true", "yes"].includes(row.disabled as string | boolean) ? row : await enabled(sourceId, "/ip/vrf", row))
@@ -31,6 +31,15 @@ const verifyData = process.env.MMAPP_OWN_TERMINAL_DATA_EQUIVALENCE === "true"
let dataExpectation: Awaited<ReturnType<typeof import("./database-restore-data-equivalence.sql-helper.js")["prepareOwnedRestoreDataExpectation"]>> | undefined
const originalConnect = pool.connect
let migrationCaptures = 0
async function waitForDatabaseRetirement() {
let timer: ReturnType<typeof setTimeout> | undefined
try {
await Promise.race([sharedDatabaseGate.whenOpen(), new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(Error("Terminal restore did not retire its database admission")), 10000)
})])
} finally { clearTimeout(timer) }
}
try {
// Every owned restore profile archives inert domain collectors, not only the
// data-equivalence profile. Otherwise restart can launch unrelated downloads
@@ -138,6 +147,10 @@ try {
await assert.rejects(assertPortalClaimsCurrent(revokedOwner))
await assert.rejects(authenticateMcpToken(issued.token))
await assert.rejects(readRestoreReceipt(operation.id, { ...actor, permissions: ["mm:mcp:read"] }))
// The applied receipt is durable before the terminal callback releases its
// last SQL lease/exclusive admission. Await that existing owner, rather than
// racing its cleanup with the stale-callback/no-replay assertion below.
await waitForDatabaseRetirement()
// A stale callback or process replay cannot invoke pg_restore a second time.
await pool.query("UPDATE mcp_restore_acceptance_marker SET value='after-receipt'")
await executeMcpDatabaseRestore(operation.id, principal)
@@ -165,6 +178,7 @@ try {
await delay(100)
}
assert.equal(receipt.state, "blocked", JSON.stringify(receipt))
await waitForDatabaseRetirement()
assert.equal(await accessGeneration(), generationBeforeCorruption)
assert.equal((await pool.query("SELECT value FROM mcp_restore_acceptance_marker")).rows[0].value, "after-receipt")
console.log("✓ Tampered archive is blocked before data replacement")
+15 -3
View File
@@ -5,7 +5,7 @@ import { z } from "zod"
import { pool } from "../../db/index.js"
import { hasPermission, type AuthUser } from "../../lib/permissions.js"
import { validateSystemDatabaseRestoreArchive } from "../../services/system-database-restore-execution.js"
import { restoreSystemDatabaseBackup, assertSystemDatabaseRestoreTools } from "../../services/system-database-backup.js"
import { restoreSystemDatabaseBackup, assertSystemDatabaseRestoreTools, withSystemDatabaseRestoreAdmission } from "../../services/system-database-backup.js"
import { withVerifiedConfigurationChange } from "../../services/config-mutation-policy.js"
import { accessGeneration, decryptBinary, encryptBinary, decryptSecret, McpError, mcpStateDirectory } from "./security.js"
import { audit, validatePrincipal, type McpPrincipal } from "./store.js"
@@ -14,6 +14,7 @@ import type { add } from "./actions.js"
import type { ChangeEvidence } from "./action-types.js"
import { withApplicationMutationLock } from "../../services/config-mutation-lock.js"
import {assertOriginalMcpTemporaryGrant,type OriginalServiceGrant} from "./temporary-grant-origin.js"
import {reconcileRestoreReceiptAudit,inspectRestoreReceiptAudit} from "../../services/database-restore-audit.js"
export const MAX_RESTORE_ARCHIVE_BYTES = 64 * 1024 * 1024
const metadataSchema = z.object({ id: z.string().uuid(), ownerId: z.string().min(1), sha256: z.string().regex(/^[a-f0-9]{64}$/), sizeBytes: z.number().int().positive().max(MAX_RESTORE_ARCHIVE_BYTES), generation: z.string(), expiresAt: z.string().datetime(), createdAt: z.string().datetime() }).strict()
@@ -76,6 +77,10 @@ export async function readRestoreReceipt(id: string, actor: AuthUser) {
try { return JSON.parse(await readFile(filename(id, ".receipt.json"), "utf8")) as Record<string, unknown> }
catch { throw new McpError("NOT_FOUND", "Внешняя квитанция восстановления не найдена", 404) }
}
export async function readRestoreReceiptAuditStatus(id:string,actor:AuthUser){
await readRestoreReceipt(id,actor)
return inspectRestoreReceiptAudit(pool,mcpStateDirectory(),id)
}
export function registerDatabaseRestore(register: typeof add) {
register("database.restore", "mm_database_restore_plan", "Восстановить БД из загруженного человеком архива", "settings", "mm:settings:admin", "write", "global", z.object({ archiveRef: z.string().uuid(), expectedSha256: z.string().regex(/^[a-f0-9]{64}$/) }).strict(), {
prepare: async (input, context) => {
@@ -91,6 +96,7 @@ export function registerDatabaseRestore(register: typeof add) {
}
/** This terminal operation must never hold a database-request admission while asking for an exclusive restore. */
export async function executeMcpDatabaseRestore(operationId: string, principal: McpPrincipal,originalGrant?:OriginalServiceGrant) {
return withSystemDatabaseRestoreAdmission(async () => {
const lease = await pool.connect()
let locked = false, unsafe = false
try {
@@ -100,6 +106,7 @@ export async function executeMcpDatabaseRestore(operationId: string, principal:
if (locked) try { await lease.query("SELECT pg_advisory_unlock(hashtextextended($1,0))", ["mcp-operation:" + operationId]) } catch { unsafe = true }
lease.release(unsafe)
}
})
}
async function executeRestore(operationId: string, principal: McpPrincipal,originalGrant?:OriginalServiceGrant) {
const { loadPlan } = await import("./runtime.js")
@@ -110,7 +117,7 @@ async function executeRestore(operationId: string, principal: McpPrincipal,origi
let receipt: Record<string, unknown> = { operationId, archiveRef: payload.archiveRef, state: "running", startedAt: new Date().toISOString(), automaticRetry: false }
try { await durableWrite(filename(operationId, ".receipt.json"), JSON.stringify(receipt), true) }
catch (error) { if (error instanceof Error && "code" in error && error.code === "EEXIST") return; throw error }
let attempted = false, stage = "authority"
let attempted = false, stage = "authority", restoreActorId:string|undefined
const assertAuthority = async () => {
await assertOriginalMcpTemporaryGrant(pool,principal.connectionId,principal.credentialId,originalGrant)
const connection = await validatePrincipal(principal)
@@ -124,6 +131,7 @@ async function executeRestore(operationId: string, principal: McpPrincipal,origi
}
try {
const { connection, ids } = await assertAuthority(), source = await archive(payload.archiveRef, connection.owner_id)
restoreActorId=connection.owner_id
stage = "archive-read"
if (source.sha256 !== payload.expectedSha256) throw new McpError("STATE_CONFLICT", "Архив подменён", 409)
const buffer = await decryptBinary(await readFile(filename(source.id, ".archive.bin")))
@@ -139,7 +147,6 @@ async function executeRestore(operationId: string, principal: McpPrincipal,origi
receipt = { ...receipt, state: "applied", finishedAt: new Date().toISOString(), accessRevoked: true, warnings: Object.keys(outcome).filter(key => key.endsWith("Error") && outcome[key as keyof typeof outcome]) }
stage = "receipt-publish"
await durableWrite(filename(operationId, ".receipt.json"), JSON.stringify(receipt))
await audit(pool, connection.owner_id, "database.restore.receipt", null, receipt).catch(() => {})
} catch (error) {
// The isolated destructive acceptance runner can print bounded diagnostics
// without archive SQL, server credentials, or arbitrary error text.
@@ -154,4 +161,9 @@ async function executeRestore(operationId: string, principal: McpPrincipal,origi
await durableWrite(filename(operationId, ".receipt.json"), JSON.stringify(receipt))
if (!attempted) await pool.query("UPDATE mcp_operations SET state='rejected',error_code=$2,finished_at=now() WHERE id=$1", [operationId, receipt.errorCode]).catch(() => {})
}
// Audit failure is a separate pending/unconfirmed import AFTER durable
// applied receipt publication. It must never relabel confirmed restore.
if(receipt.state==="applied"){
try{await reconcileRestoreReceiptAudit(pool,mcpStateDirectory(),operationId,restoreActorId)}catch{/* Startup reconciles the existing external receipt; never replay restore. */}
}
}
@@ -9,6 +9,7 @@ import { performance } from "node:perf_hooks"
import type { QueryConfig, PoolClient } from "pg"
import { mcpTemporaryGrantSchema, type McpObjectRef } from "@mmapp/contracts/mcp"
import { pool } from "../../db/index.js"
import { sharedDatabaseGate } from "../../services/shared-database-gate.js"
import { assertNoConfigurationLockContext } from "../../services/config-mutation-lock.js"
import { assertNoOperationConfigurationLockContext } from "../../services/operation-config-lock-lease.js"
import { validatePrincipal, audit, type McpPrincipal, type StoredOperation } from "./store.js"
@@ -200,7 +201,10 @@ async function interrupt(entry: Admission) {
const stopped = new Promise<never>((_yes, no) => { timeout = setTimeout(() => { retired = true; client?.release(true); client = undefined; no(Error("Operation root cleanup deadline")) }, 2500) })
const work = async () => {
const owned = await connect
const query = (text: string, values: unknown[] = []) => owned.query({ text, values, query_timeout: Math.max(1, Math.min(1000, deadline - Date.now())) } as QueryConfig)
const query = (text: string, values: unknown[] = []) => {
if (retired || Date.now() >= deadline || !sharedDatabaseGate.hasActiveAdmission()) throw Error("Late cleanup SQL")
return owned.query({ text, values, query_timeout: Math.max(1, Math.min(1000, deadline - Date.now())) } as QueryConfig)
}
if ((await query("BEGIN")).command !== "BEGIN") throw Error("No cleanup BEGIN ACK")
const result = await query(`UPDATE public.mcp_operations SET state='unknown',verification='inconclusive',error_code='PROCESS_INTERRUPTED',
result=jsonb_build_object('needsReview',true,'automaticRetry',false),finished_at=now() WHERE id=$1 AND plan_id=$2 AND connection_id=$3
@@ -217,11 +221,21 @@ async function interrupt(entry: Admission) {
try { await Promise.race([work(), stopped]) } catch { console.error("MCP operation root cleanup deferred", { operationId: entry.operationId, retryAllowed: false }) }
finally { retired = true; clearTimeout(timeout); if (client) { client.release(!safe); client = undefined } }
}
/** Cleanup SQL owns a live request/exclusive admission. Waiting for repair is
* detached from runner slots; only admission is retried, never native work. */
async function interruptWithinDatabaseAdmission(entry: Admission): Promise<void> {
if (sharedDatabaseGate.hasActiveAdmission()) return sharedDatabaseGate.withinAdmission(() => interrupt(entry))
if (!sharedDatabaseGate.isPaused()) return sharedDatabaseGate.request(() => interrupt(entry))
void (async () => {
while (sharedDatabaseGate.isPaused()) await sharedDatabaseGate.whenOpen()
await sharedDatabaseGate.request(() => interrupt(entry))
})().catch(() => console.error("MCP operation root cleanup deferred", { operationId: entry.operationId, retryAllowed: false }))
}
/** Aborted admission/queue ownership only. A serialized/foreign cap is inert. */
export async function retireMcpOperationAdmission(admission: unknown): Promise<void> {
const entry = admission && typeof admission === "object" ? admissions.get(admission) : undefined
if (!entry) return
admissions.delete(admission as object); await interrupt(entry)
admissions.delete(admission as object); await interruptWithinDatabaseAdmission(entry)
}
/** Called exactly by the ordinary runtime before its first pool checkout/lock.
* The callback is fixed runner code; callers supply no metadata/authorizer. */
@@ -270,7 +284,7 @@ export async function withMcpOperationExecutionOrigin(admission: unknown, work:
await roots.run(entry, work)
} finally {
if (entry) { entry.active = false; clearTimeout(entry.timer); entry.controller.abort() }
if (interruptOwn) await interrupt(admitted)
if (interruptOwn) await interruptWithinDatabaseAdmission(admitted)
}
}
export async function captureMcpOperationExecutionOrigin(): Promise<McpOperationExecutionOrigin> {
@@ -1,3 +1,5 @@
import { isConfirmedRoutingFibTable } from "../../services/routing-table-fib.js"
import { prepareOriginalEffectSelection } from "./routing-original-effects.js"
import type { RouteAiProfile } from "@mmapp/contracts/mcp-routing"
import { pool } from "../../db/index.js"
import { nativeBgpRows, bgpTemplateReferences, type NativeBgpRow } from "../../services/bgp-export-model.js"
@@ -96,7 +98,7 @@ async function readContext(profile: RouteAiProfile, options: RouteContextOptions
}
const sourceId = profile.serverId, tables = await get(sourceId, "/routing/table"), vrfs = await get(sourceId, "/ip/vrf")
const table = unique(tables, profile.table)
if (![true, "true", "yes"].includes(table.fib as string | boolean) || [true, "true", "yes"].includes(table.disabled as string | boolean) || [true, "true", "yes"].includes(table.invalid as string | boolean)) throw new McpError("STATE_UNAVAILABLE", "FIB таблицы назначения не подтверждён", 409)
if (!isConfirmedRoutingFibTable(table)) throw new McpError("STATE_UNAVAILABLE", "FIB таблицы назначения не подтверждён", 409)
await enabled(sourceId, "/ip/vrf", unique(vrfs, profile.probeVrf))
// Keep ordered VRF membership (first matching row wins), never equate an
// arbitrary policy table with the diagnostic VRF's same-name table.
@@ -140,7 +142,7 @@ async function readContext(profile: RouteAiProfile, options: RouteContextOptions
for (const property of ["vrf", "routing-table"]) if (!values[property] && instance) values[property] = bgpFilterName(await post(sourceId, "/routing/bgp/instance/get", { ".id": instance[".id"], "value-name": property }))
if (!values.vrf || !values["routing-table"] || !values.afi || values["input.filter"] === null) throw new McpError("STATE_UNAVAILABLE", "Не подтверждены effective BGP table/VRF/AFI/input binding", 409)
const boundTable = unique(tables, values["routing-table"])
if (![true, "true", "yes"].includes(boundTable.fib as string | boolean) || [true, "true", "yes"].includes(boundTable.disabled as string | boolean) || [true, "true", "yes"].includes(boundTable.invalid as string | boolean)) throw new McpError("STATE_UNAVAILABLE", "FIB BGP таблицы не подтверждён", 409)
if (!isConfirmedRoutingFibTable(boundTable)) throw new McpError("STATE_UNAVAILABLE", "FIB BGP таблицы не подтверждён", 409)
await enabled(sourceId, "/ip/vrf", unique(vrfs, values.vrf))
bindings.push({ connectionId: connection[".id"], instance: instance ? String(instance.name) : null, vrf: values.vrf, table: values["routing-table"]!, afi: values.afi, inputChain: values["input.filter"]!, outputChain: values["output.filter-chain"] ?? "" })
}
@@ -177,7 +179,7 @@ async function readContext(profile: RouteAiProfile, options: RouteContextOptions
let gateway: ReturnType<typeof routeGatewayContext>
try { gateway = routeGatewayContext(candidate.gateway) } catch { throw new McpError("STATE_UNAVAILABLE", "Gateway кандидата не подтверждён", 409) }
const resolver = unique(tables, gateway.resolverTable)
if (![true, "true", "yes"].includes(resolver.fib as string | boolean) || [true, "true", "yes"].includes(resolver.disabled as string | boolean) || [true, "true", "yes"].includes(resolver.invalid as string | boolean)) throw new McpError("STATE_UNAVAILABLE", "FIB resolver таблицы не подтверждён", 409)
if (!isConfirmedRoutingFibTable(resolver)) throw new McpError("STATE_UNAVAILABLE", "FIB resolver таблицы не подтверждён", 409)
if (unavailableIds.includes(candidate.jumpHostId) || unavailableIds.includes(candidate.exitNodeId)) throw new McpError("DEVICE_UNAVAILABLE", "Источник кандидата отсутствует или отключён", 409)
} catch (error) { unavailableCandidate(candidate.id, error) }
}
@@ -250,6 +252,14 @@ async function readContext(profile: RouteAiProfile, options: RouteContextOptions
nativeRuleId: selected && current ? filters.find(row => row.chain === selected.inputChain)![".id"] : null, ownedCompilerRecognized: !!current,
sharedConnectionIds, bindings, disabledConnectionIds: disabledBindings.map(binding => binding.connectionId), externalReferences, executionAvailable: reason === null, executionReason: reason, scopedExecutionAvailable: scopedReason === null, scopedExecutionReason: scopedReason, targetRuleProof, dependencyProof, sourceProofs: sources.expected }
return { before, clients, fence, probeEndpoints, ownedTarget,
captureOriginalEffectSelection: async (candidateId: string) => {
try {
const routes = await get(sourceId, "/routing/route"), sessions = await get(sourceId, "/routing/bgp/session")
await fence()
return prepareOriginalEffectSelection(profile, candidateId, selected, { routes, connections, sessions,
candidate: candidateRows.get(candidateId), probe: probeEndpoints.get(candidateId) })
} catch { await fence(); return { status: "unknown" as const, reason: "prepared-effect-catalogue-unavailable" } }
},
signEvidence: (value: unknown) => sign(pathProofTag("community") + fingerprint({ dependencyProof, targetRuleProof, value })),
verifyEvidence: (value: unknown, proof: unknown) => typeof proof === "string" && constantEqual(sign(pathProofTag("community") + fingerprint({ dependencyProof, targetRuleProof, value })), proof),
}
@@ -1,6 +1,10 @@
import { createRoutingReservationStore } from "./routing-reservation-store.js"
import { observeCommittedScopedCheckpoint } from "../../test/committed-scoped-checkpoint-observer.js"
import { prepareRoutingControllerBaseline } from "./routing-controller-baseline.js"
import { prepareRoutingControllerBaseline, advanceRoutingControllerBaseline } from "./routing-controller-baseline.js"
import { loadScopedConfigurationPacket, loadScopedNetworkCheckpoint } from "./routing-community-scoped-checkpoint.js"
import { assertScopedCommunityRequest } from "./routing-community-scoped-request.js"
import { originalEffectReportSchema, originalEffectSelectionSchema } from "./routing-original-effects.js"
import { tickRoutingProfiles } from "./routing-profiles.js"
import { saveControllerProfileCas } from "./routing-controller-fence.js"
import { withApplicationMutationLock, withConfigMutationLock } from "../../services/config-mutation-lock.js"
import { performance } from "node:perf_hooks"
@@ -31,7 +35,7 @@ import { registerManagementChannel } from "./management.js"
import { isSchedulerJobsPaused, pauseSchedulerJobsAndWait, resumeSchedulerJobs } from "../../services/scheduler-running.js"
/** Disposable SQL + loopback HTTP only. Does not access or configure CHR. */
export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options: { originalCredentialRevocationOnly?: boolean; forwardRoundsOnly?: boolean; manualInverseChargeOnly?: boolean; manualInverseChargeCase?: "positive" | "quota" | "concurrent" | "unknown" | "revoked" | "nonpersistent" } = {}) {
export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options: { originalEffectsOnly?: boolean; originalCredentialRevocationOnly?: boolean; forwardRoundsOnly?: boolean; manualInverseChargeOnly?: boolean; manualInverseChargeCase?: "positive" | "quota" | "concurrent" | "unknown" | "revoked" | "nonpersistent" | "mixed-forward" } = {}) {
const sid = 131, writer: AuthUser = { ...actor, permissions: [...new Set([...actor.permissions, "mm:filters:write"])] }
const initial = compileManagedFilterRules([{ community: "65000:10", gateway: "192.0.2.1", outIface: "", action: "route", description: "" }, { community: "65000:20", gateway: "192.0.2.5", outIface: "", action: "route", description: "" }])
const filters = ["a", "b"].map(key => ({ ".id": "*f" + key, chain: "custom-" + key, rule: initial, disabled: "false", comment: managedComment("NEVER_SCOPED_COMMENT_" + key), opaque: "NEVER_SCOPED_OPAQUE" }))
@@ -42,6 +46,22 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
let forwardRoundBulk = 0, afterRoundLogRead: (() => Promise<void>) | undefined
let primaryDown = false, primaryControlRequests = 0, pendingInversePost = false, inversePostChecks = 0
const observedVrfs = new Set<string>()
// Only this separately selected SQL/localhost phase changes its wire data.
// These are declared synthetic RouterOS responses, never actual CHR facts.
let originalEffectCase: "transition" | "no-op" | "ambiguous" = "transition"
const originalEffectWire: Array<{ method: string; path: string; afterPatch: boolean }> = []
const originalEffectChanged = () => originalEffectCase === "no-op" || filters[0].rule !== initial
const effectRoutes = () => {
const changed = originalEffectChanged()
const row = { ".id": "*effect1", afi: "ip4", "routing-table": "tenant-a", "dst-address": "203.0.113.0/24",
bgp: "true", connect: "false", static: "false", active: "true", contribution: "active", ecmp: "false",
disabled: "false", filtered: "false", unreachable: "false", blackhole: "false", "bgp.communities": "65000:10",
gateway: changed ? "192.0.2.9" : "192.0.2.1", "immediate-gw": (changed ? "203.0.113.9" : "203.0.113.1") + "%wan-a" }
return originalEffectCase === "ambiguous" ? [row, { ...row, ".id": "*effect2" }] : [row]
}
const effectConnection = { ".id": "*ca", name: "peer-a", instance: "default", vrf: "main", "routing-table": "tenant-a", afi: "ip",
"input.filter": "custom-a", "output.filter-chain": "", templates: "", disabled: "false", "use-bfd": "false",
"local.address": "198.18.0.1", "remote.address": "198.18.0.2", "remote.as": "65001" }
const collections = (path: string): Record<string, unknown>[] => ({
"/system/resource": [{ version: "7.20.1" }],
...Object.fromEntries(["/routing/route", "/routing/bgp/session", "/routing/bfd/session", "/log"].map(resource => [resource,
@@ -54,12 +74,25 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
"/ip/route": [1, 9].map(value => ({ ".id": "*r" + value, "dst-address": "192.0.2." + value + "/32", "routing-table": "main", disabled: "false", comment: managedComment("recursive"), opaque: "NEVER_SCOPED_ROUTE" })),
"/interface": ["wan-a", "wan-b", "hop", "target"].map((name, index) => ({ ".id": "*if" + index, name, disabled: "false", running: "true" })),
"/ip/address": ["wan-a", "wan-b", "hop", "target"].map((name, index) => ({ ".id": "*ad" + index, interface: name, address: "198.51.100." + (index + 1) + "/32", disabled: "false", invalid: "false" })),
...(options.originalEffectsOnly ? {
"/routing/table": ["main", "tenant-a", "tenant-b", "probe-a", "probe-b"].map((name, index) => ({ ".id": "*t" + index, name, fib: "", disabled: "false", invalid: "false" })),
"/routing/route": effectRoutes(), "/routing/bgp/connection": [effectConnection],
"/routing/bgp/session": [{ ...effectConnection, ".id": "*sa", "local.afi": "ip", "remote.afi": "ip", established: "true" }],
"/routing/bfd/session": [], "/log": [{ ".id": "*l1", topics: "bgp,route,info", message: "Declared synthetic localhost effect observation; interval coverage unknown" }],
"/ip/route": [1, 9].map(value => ({ ".id": "*r" + value, "dst-address": "192.0.2." + value + "/32", "routing-table": "main", disabled: "false",
"immediate-gw": "203.0.113." + value + "%wan-a", comment: managedComment("recursive"), opaque: "NEVER_SCOPED_ROUTE" })),
} : {}),
} as Record<string, Record<string, unknown>[]>)[path] ?? []
let requestFailure: { error: unknown } | undefined
const handler: RequestListener = async (req, res) => {
requests++; res.setHeader("content-type", "application/json")
if (options.originalEffectsOnly) {
assert.equal(req.socket.localAddress, "127.0.0.1")
assert.equal(req.headers.authorization, "Basic " + Buffer.from("admin:NEVER_SCOPED_PASSWORD").toString("base64"))
}
let raw = ""; for await (const chunk of req) raw += chunk
const args = raw ? JSON.parse(raw) : {}, path = decodeURIComponent((req.url ?? "").replace(/^\/rest/, ""))
if (options.originalEffectsOnly) originalEffectWire.push({ method: req.method ?? "", path, afterPatch: filters[0].rule !== initial })
if (req.socket.localAddress === "127.0.0.1") {
if (/^\/(routing|tool)\//.test(path) || path === "/system/license") primaryControlRequests++
if (primaryDown) { req.socket.destroy(); return }
@@ -203,6 +236,111 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
return { id, version: 1 }
}
const disablePersistent = (id: string) => pool.query("UPDATE mcp_routing_profiles SET enabled=false WHERE id=$1", [id])
if (options.originalEffectsOnly) {
const cases: Array<Record<string, unknown>> = []
for (const effectCase of ["transition", "no-op", "ambiguous"] as const) {
reset(); await refresh(); originalEffectCase = effectCase
const config = profile("a")
for (const candidate of config.candidates) candidate.controlProbe!.sourceAddress = "198.51.100.1"
assert.equal(config.autoRecovery, false)
// The baseline and persistent authority are issued by the SAME actual
// fixed readers/CAS used by existing tests, not hand-written proofs.
const persistent = await persistentFixture(config)
try {
const { plan, input } = await prepare("a", persistent, config)
const sealedSelection = originalEffectSelectionSchema.parse(input.originalEffectSelection)
assert.equal(sealedSelection.status, effectCase === "ambiguous" ? "unknown" : "prepared")
const storedPlan = await loadPlan(plan.planId)
assert.equal(storedPlan.approved_by, writer.id)
assert(!JSON.stringify(await publicPlan(storedPlan)).includes("originalEffectSelection"))
const beforeWrites = writes, wireStart = originalEffectWire.length
const operation = await execute(plan.planId)
assert.equal(operation.state, "applied", "Optional effect unknown must preserve genuine manual configuration/probe acceptance")
assert.equal(writes - beforeWrites, 1)
const rows: Array<{ created_at: Date; state: string; operation_id: string; operation_version: number; step_state: string;
result: { configurationVerified: boolean; controlProbeVerified: boolean; originalEffects: unknown } }> = (await pool.query(`SELECT o.created_at,o.state,p.operation_id,p.operation_version,s.state AS step_state,s.result
FROM mcp_operations o JOIN mcp_plans p ON p.id=o.plan_id JOIN mcp_operation_steps s ON s.operation_id=o.id
WHERE o.id=$1 AND o.connection_id=$2 AND o.plan_id=$3 AND s.step_index=0`, [operation.id, connectionId, plan.planId])).rows
assert.equal(rows.length, 1); assert.equal(rows[0].operation_id, "routing.community.switch")
assert.equal(rows[0].state, "applied"); assert.equal(rows[0].step_state, "verified")
assert.equal(rows[0].result.configurationVerified, true); assert.equal(rows[0].result.controlProbeVerified, true)
const report = originalEffectReportSchema.parse(rows[0].result.originalEffects)
const requestSeal = await assertScopedCommunityRequest(input)
const configuration = await loadScopedConfigurationPacket(input, requestSeal, { operationId: operation.id, stepIndex: 0 })
assert(configuration); assert.equal(configuration.packet.operationId, operation.id)
assert.equal(configuration.packet.revision, 2); assert.equal(configuration.packet.phase, "CONFIG_APPLIED")
const network = await loadScopedNetworkCheckpoint(input, requestSeal)
assert(network); assert.equal(network.status, "passed"); assert.deepEqual(network.originalEffects, report)
const runningWire = originalEffectWire.slice(wireStart)
const ribBefore = runningWire.filter(row => row.method === "GET" && row.path === "/routing/route" && !row.afterPatch).length
const ribAfter = runningWire.filter(row => row.method === "GET" && row.path === "/routing/route" && row.afterPatch).length
console.log("Owned original effect diagnostic:", JSON.stringify({ case: effectCase, state: operation.state,
status: report.status, reason: report.reason, rounds: report.rounds.length, budget: report.budget ?? null,
ribBefore, ribAfter, runtimeNativeRequests: runningWire.length, lastNativePaths: runningWire.slice(-8).map(row => row.path) }))
assert.equal(ribBefore, effectCase === "ambiguous" ? 0 : 1)
assert.equal(ribAfter, effectCase === "ambiguous" ? 0 : 2)
assert.equal(report.status, effectCase === "transition" ? "observed-expected" : "unknown")
assert.equal(report.switchEffectObserved, effectCase === "transition")
assert.equal(report.logCoverage, "unknown"); assert.equal(report.routeOriginVerified, false)
assert.equal(report.forwardingVerified, false); assert.equal(report.semanticProof, false); assert.equal(report.autoRecoveryAllowed, false)
if (sealedSelection.status === "prepared") {
assert.equal(report.selectionIdentity, fingerprint(sealedSelection)); assert.equal(report.rounds.length, 2)
assert(report.budget && report.budget.calls > 18 && report.budget.calls <= 256)
assert(report.budget.bytes > 0 && report.budget.bytes <= 8 * 1048576)
assert(report.budget.rows > 0 && report.budget.rows <= 10000)
assert(report.budget.deadline <= rows[0].created_at.getTime() + 105000, "Original120s horizon is narrowed by15s publication reserve")
assert(report.rounds.every(round => Date.parse(round.observedAt) < report.budget!.deadline && round.controlProbe === "passed" && round.bgp === "observed-expected" && round.bfd === "not-configured"))
assert.deepEqual(report.transition?.changedPrefixes, effectCase === "transition" ? ["203.0.113.0/24"] : [])
assert.equal(report.transitionAttribution, "unproven")
}
const row = (await pool.query("SELECT state,grant_expires_at FROM mcp_routing_profiles WHERE id=$1 AND version=1", [persistent.id])).rows[0]
assert(row?.state.baseline)
const advance = () => advanceRoutingControllerBaseline(row.state.baseline, persistent.id, 1, config, row.grant_expires_at.toISOString(),
{ principal, connection: principalConnection }, plan.planId, operation.id)
const principalConnection = await effectiveMcpConnection(principal), beforeAdvanceReads = requests
if (effectCase === "transition") {
const advanced = await advance(); assert.equal(advanced.packet.candidateId, "b")
assert.equal(advanced.packet.transition?.operationId, operation.id); assert.equal(advanced.packet.transition?.checkpointProof, configuration.checkpointProof)
} else {
await assert.rejects(advance, (error: unknown) => error instanceof McpError && error.code === "STATE_CONFLICT")
assert.equal(requests, beforeAdvanceReads, "Unknown/no-transition effect gate refuses before another native context read")
}
// Check actual HMAC loading, not only a caller-visible result object.
if (effectCase === "transition") {
const audit: Array<{ id: string; payload: Record<string, unknown> }> = (await pool.query("SELECT id,payload FROM mcp_audit WHERE connection_id=$1 AND action='routing.scoped.network-check' AND payload->>'receiptId'=$2", [connectionId, input.receiptId])).rows
assert.equal(audit.length, 1)
try {
await pool.query("UPDATE mcp_audit SET payload=jsonb_set(payload,'{originalEffects,switchEffectObserved}','false'::jsonb) WHERE id=$1", [audit[0].id])
assert.equal(await loadScopedNetworkCheckpoint(input, requestSeal), null, "Modified effect vector cannot reuse its original network HMAC")
} finally { await pool.query("UPDATE mcp_audit SET payload=$2::jsonb WHERE id=$1", [audit[0].id, JSON.stringify(audit[0].payload)]) }
assert.deepEqual((await loadScopedNetworkCheckpoint(input, requestSeal))?.originalEffects, report)
}
const repeatReads = requests
const repeated = await applyPlan(principal, plan.planId, randomUUID())
assert.equal(repeated.id, operation.id, "Completed plan returns its original operation without replay")
assert.equal(repeated.state, "applied")
assert.equal(Number((await pool.query("SELECT count(*) AS n FROM mcp_operations WHERE plan_id=$1", [plan.planId])).rows[0].n), 1)
assert.equal(writes - beforeWrites, 1); assert.equal(requests, repeatReads, "Completed approved plan cannot repeat native work")
// Preserve the existing scoped-controller activation guard. A real
// tick pauses HERE before effect dispatch; never call this an
// effect-triggered scheduler pause or enable that unsupported lane.
await tickRoutingProfiles()
const paused = (await pool.query("SELECT enabled,state FROM mcp_routing_profiles WHERE id=$1", [persistent.id])).rows[0]
assert.equal(paused.enabled, false); assert.equal(typeof paused.state.pausedReason, "string")
assert.deepEqual(paused.state.baseline, row.state.baseline)
cases.push({ case: effectCase, manualState: operation.state, configurationCheckpoint: true, actualPrivateExecutionOrigin: true,
effectStatus: report.status, observedFibTransition: report.switchEffectObserved, nativeRibBefore: ribBefore, nativeRibAfter: ribAfter,
sharedBudget: report.budget ?? null, effectBaselineAdvanceAccepted: effectCase === "transition", effectGateNativeReads: effectCase === "transition" ? "fresh-native-checked" : 0,
actualTickPausedByExistingScopedGuard: true, tickEffectPauseAccepted: false, publicManualRevision2EffectOwnerAccepted: false })
} finally { await disablePersistent(persistent.id) }
}
assert.deepEqual((await pool.query("SELECT * FROM filter_rules WHERE server_id=$1 ORDER BY id", [sid])).rows, dbBefore)
const result = { accepted: true, cases, persistentAuthorityCases: 0, realMcpDispatcher: true, realSql: true, routerOsTransport: "declared-synthetic-localhost-http",
genuineApprovedRunningOrigin: true, actualConfigurationCheckpoint: true, signedEffectTamperRejected: true,
originalAutoRecoveryEnabled: false, fullRoutingAccepted: false, scopedControllerEnabled: false, nativeChrAccepted: false, forwardingVerified: false }
console.log("Original effect SQL/runtime component:", JSON.stringify(result))
return result
}
const assertOriginalCredentialRevocation = async () => {
console.log(JSON.stringify({stage:'original-credential-revocation:init'}))
reset(); await refresh()
@@ -260,6 +398,7 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
const id = randomUUID(), current = await effectiveMcpConnection(principal), config = profile("a")
assert.equal(config.autoRecovery, false)
if (chargeCase === "quota") config.maxSwitchesPerHour = 1
if (chargeCase === "mixed-forward") config.maxSwitchesPerHour = 2
const expiry = new Date(Math.min(Date.now() + 600000, current.owner_valid_until.getTime(), Date.parse(principal.credentialExpiresAt), Date.parse(current.policy.expiresAt)))
await withApplicationMutationLock(() => withConfigMutationLock([sid], async () => {
const baseline = await prepareRoutingControllerBaseline(id, 1, config, expiry.toISOString(), { principal, connection: current })
@@ -292,6 +431,10 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
}
console.log(JSON.stringify({stage:"manual-inverse:forward-prepare",chargeCase}))
const { plan, input } = await prepare("a", persistent ? { id, version: 1 } : undefined, config)
// Prepare the competing forward while the ORIGINAL native baseline is
// current. Admission must charge it even if execution later refuses the
// changed network state. No invented request/authority or reserved lane.
const competingForward = chargeCase === "mixed-forward" ? await prepare("a", { id, version: 1 }, config) : undefined
// Existing known partial configuration path: genuine committed CONFIG_APPLIED,
// then failed control probe, then genuine manual inverse positive to A.
mode = "post-failed"
@@ -348,6 +491,54 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
await assert.rejects(applyPlan(principal, recovery.planId, randomUUID()))
assert.deepEqual(await snapshot(), afterForward, "No approval means no debit/charge/native write")
await approvePlan(recovery.planId, writer, true)
if (chargeCase === "mixed-forward") {
assert(competingForward)
const attempts = [
{ lane: "forward", planId: competingForward.plan.planId, key: randomUUID(), cost: 2 },
{ lane: "manual-inverse", planId: recovery.planId, key: randomUUID(), cost: 1 },
] as const
const initialPlans = await Promise.all(attempts.map(attempt => loadPlan(attempt.planId)))
assert(initialPlans.every(value => value.state === "approved"))
const admitted = await Promise.allSettled(attempts.map(attempt => applyPlan(principal, attempt.planId, attempt.key)))
const winners = admitted.flatMap((value, index) => value.status === "fulfilled" ? [index] : [])
assert.equal(winners.length, 1, "Two writer lanes must compete for the same last profile slot")
const winnerIndex = winners[0], loserIndex = 1 - winnerIndex
const winner = admitted[winnerIndex], loser = admitted[loserIndex]
assert(winner.status === "fulfilled" && loser.status === "rejected")
assert(loser.reason instanceof McpError && loser.reason.code === "STATE_CONFLICT", "The losing lane must fail profile admission")
assert.equal(fingerprint(await loadPlan(attempts[loserIndex].planId)), fingerprint(initialPlans[loserIndex]), "Losing plan is completely unchanged")
assert.equal(Number((await pool.query("SELECT count(*)::int AS n FROM mcp_operations WHERE plan_id=$1", [attempts[loserIndex].planId])).rows[0].n), 0)
assert.equal(Number((await pool.query("SELECT count(*)::int AS n FROM mcp_routing_forward_charges WHERE source_plan_id=$1", [attempts[loserIndex].planId])).rows[0].n), 0)
assert.equal(Number((await pool.query("SELECT count(*)::int AS n FROM mcp_routing_manual_inverse_charges WHERE recovery_plan_id=$1", [attempts[loserIndex].planId])).rows[0].n), 0)
const deadline = Date.now() + 140000
let terminalState = "pending"
while (["pending", "running"].includes(terminalState) && Date.now() < deadline) {
terminalState = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [winner.value.id])).rows[0].state
if (["pending", "running"].includes(terminalState)) await delay(20)
}
assert(!["pending", "running"].includes(terminalState), "Winner must retire before fixture cleanup")
if (requestFailure) throw requestFailure.error
snapshotStage = "mixed-writer-terminal"
const afterMixed = await snapshot()
assert.equal(afterMixed.total, 2)
assert.equal(afterMixed.forward, winnerIndex === 0 ? 2 : 1)
assert.equal(afterMixed.manualInverse, winnerIndex === 1 ? 1 : 0)
assert.equal(afterMixed.reserved, 0); assert.equal(afterMixed.legacy, 0)
assert.equal(afterMixed.generic, afterForward.generic + attempts[winnerIndex].cost, "Losing writer cannot debit generic quota")
if (winnerIndex === 1) {
const charges = (await pool.query("SELECT source_operation_id,source_step_index,recovery_operation_id FROM mcp_routing_manual_inverse_charges WHERE recovery_operation_id=$1", [winner.value.id])).rows
assert.equal(charges.length, 1); assert.equal(charges[0].source_operation_id, source.id)
assert.equal(charges[0].source_step_index, 0); assert.equal(charges[0].recovery_operation_id, winner.value.id)
assert.equal(terminalState, "applied"); assert.equal(afterMixed.writes, 2)
}
const beforeReplay = fingerprint(await snapshot())
assert.equal((await applyPlan(principal, attempts[winnerIndex].planId, attempts[winnerIndex].key)).id, winner.value.id)
await delay(30)
assert.equal(fingerprint(await snapshot()), beforeReplay, "Same-key observation cannot recharge or replay either lane")
return { accepted: true, mixedWriterAdmission: true, winnerLane: attempts[winnerIndex].lane, before, afterForward, afterMixed,
exactlyOneAdmission: true, losingPlanOperationChargeAndGenericRollback: true, sameKeyReplayUnchanged: true,
realSql: true, allWriterLedgerComplete: false, reservedDispatchEnabled: false, autoRecoveryEnabled: false }
}
console.log(JSON.stringify({ stage: "manual-inverse:human-recovery-apply" }))
if (chargeCase === "quota" || chargeCase === "revoked") {
if (chargeCase === "revoked") await pool.query("UPDATE mcp_routing_profiles SET enabled=false WHERE id=$1", [id])
@@ -1,3 +1,4 @@
import { originalEffectReportSchema, type OriginalEffectReport } from "./routing-original-effects.js"
import { z } from "zod"
import type { PoolClient } from "pg"
import { pool } from "../../db/index.js"
@@ -19,7 +20,7 @@ const checkpointSchema = z.union([manualCheckpointSchema, manualCheckpointSchema
export type ScopedConfigurationPacket = z.infer<typeof checkpointSchema>
type Checkpoint = ScopedConfigurationPacket
const networkSchema = z.object({ receiptId: z.string().uuid(), requestSeal: proof, configurationCheckpointProof: proof,
check: z.object({ id: z.string().uuid(), candidateId: z.string(), status: z.enum(["passed", "failed"]), observedAt: z.string().datetime() }).strict(), networkSeal: proof }).strict()
check: z.object({ id: z.string().uuid(), candidateId: z.string(), status: z.enum(["passed", "failed"]), observedAt: z.string().datetime() }).strict(), originalEffects: originalEffectReportSchema.optional(), networkSeal: proof }).strict()
const intended = (input: Record<string, unknown>) => JSON.stringify(redact({ kind: "service", service: "routing.community.scoped", input }))
const connection = (input: Record<string, unknown>) => z.object({ connectionId: z.string().uuid() }).parse(input.principal).connectionId
const seal = (value: Checkpoint) => privateSecretFingerprint(fingerprint(value), `route-ai-scoped-config-checkpoint-v${value.revision}`)
@@ -72,12 +73,13 @@ export async function loadScopedConfigurationCheckpoint(input: Record<string, un
return value ? { checkpointProof: value.checkpointProof, observedAt: value.observedAt } : null
}
export async function saveScopedNetworkCheckpoint(input: Record<string, unknown>, requestSeal: string, check: { id: string; candidateId: string; status: "passed" | "failed"; observedAt: string }) {
export async function saveScopedNetworkCheckpoint(input: Record<string, unknown>, requestSeal: string, check: { id: string; candidateId: string; status: "passed" | "failed"; observedAt: string }, originalEffects?: OriginalEffectReport) {
await assertPersistentScopedProfileAuthority(input)
const configuration = await loadScopedConfigurationCheckpoint(input, requestSeal)
if (!configuration) throw new McpError("STATE_CONFLICT", "Нет подтверждённого CONFIG_APPLIED", 409)
const packet = { receiptId: input.receiptId, requestSeal, configurationCheckpointProof: configuration.checkpointProof,
check: { id: check.id, candidateId: check.candidateId, status: check.status, observedAt: check.observedAt } }
check: { id: check.id, candidateId: check.candidateId, status: check.status, observedAt: check.observedAt },
...(originalEffects === undefined ? {} : { originalEffects: originalEffectReportSchema.parse(originalEffects) }) }
const networkSeal = await privateSecretFingerprint(fingerprint(packet), "route-ai-scoped-network-checkpoint-v1")
await assertPersistentScopedProfileAuthority(input)
await audit(pool, String(input.ownerId), "routing.scoped.network-check", connection(input), { ...packet, networkSeal })
@@ -92,5 +94,5 @@ export async function loadScopedNetworkCheckpoint(input: Record<string, unknown>
if (!parsed.success) return null
const { networkSeal, ...packet } = parsed.data
if (packet.configurationCheckpointProof !== configuration.checkpointProof || packet.check.candidateId !== input.candidateId || Date.parse(packet.check.observedAt) < Date.parse(configuration.observedAt) || Date.parse(packet.check.observedAt) > Date.now() || networkSeal !== await privateSecretFingerprint(fingerprint(packet), "route-ai-scoped-network-checkpoint-v1")) return null
return { status: packet.check.status, networkSeal }
return { status: packet.check.status, networkSeal, ...(packet.originalEffects === undefined ? {} : { originalEffects: packet.originalEffects }) }
}
@@ -1,3 +1,7 @@
import { createRoutingNetworkSessionForCurrentExecution } from "./routing-network-execution-session.js"
import { readRoutingNetworkScopedCommunityContext } from "./routing-network-scoped-context.js"
import { resolveRoutingNetworkForwardRoundConfigurationContext, closeRoutingNetworkForwardRoundConfigurationContext } from "./routing-network-configuration-bridge.js"
import { originalEffectSelectionSchema, unknownOriginalEffects, originalEffectBaselineMatches, inspectOriginalEffectRounds, type OriginalEffectReport } from "./routing-original-effects.js"
import { randomUUID } from "node:crypto"
import { z } from "zod"
import { routeAiProfileSchema } from "@mmapp/contracts/mcp-routing"
@@ -25,6 +29,11 @@ import { createRoutingReservationStore } from "./routing-reservation-store.js"
const principalSchema = z.object({ connectionId: z.string().uuid(), credentialId: z.string().uuid(), credentialExpiresAt: z.string() }).strict()
const fail = (message: string, code = "STATE_CONFLICT") => new McpError(code, message, 409)
const effectReadFailure = (stage: "original-effect-rounds-unavailable" | "effect-publication-revalidation-unavailable", error: unknown) => {
const code = error instanceof McpError && ["READ_CANCELLED", "PLAN_EXPIRED", "LIMIT_EXCEEDED", "STATE_CONFLICT", "RESET_REQUIRED", "STATE_UNAVAILABLE"].includes(error.code)
? error.code : "STATE_UNAVAILABLE"
return unknownOriginalEffects(stage + ":" + code)
}
export const scopedProfileIds = (profile: z.infer<typeof routeAiProfileSchema>) => [...new Set([profile.serverId, ...profile.candidates.flatMap(candidate => [candidate.jumpHostId, candidate.exitNodeId])])].sort((a, b) => a - b)
const publicRule = (rule: { community: string; gateway: string; outIface: string }) => ({ community: rule.community, action: "route", gateway: rule.gateway, gatewayTunnelId: rule.outIface })
type ObservationPhase = "admission" | "configuration"
@@ -80,7 +89,7 @@ async function receipt(input: Record<string, unknown>, action: string) {
await assertPersistentScopedProfileAuthority(input)
await audit(pool, String(input.ownerId), action, principalSchema.parse(input.principal).connectionId, { receiptId: input.receiptId, requestSeal, revision: input.proofRevision })
}
const matches = (input: Record<string, unknown>, context: Awaited<ReturnType<typeof readRouteCommunityContext>>) => scopedConfigurationMatches({ dependencyProof: input.dependencyProof, originalTargetProof: input.originalTargetProof, desiredTargetProof: input.desiredTargetProof, candidateId: input.candidateId }, context.before)
const matches = (input: Record<string, unknown>, context: { before: Parameters<typeof scopedConfigurationMatches>[1] }) => scopedConfigurationMatches({ dependencyProof: input.dependencyProof, originalTargetProof: input.originalTargetProof, desiredTargetProof: input.desiredTargetProof, candidateId: input.candidateId }, context.before)
async function original(input: Record<string, unknown>, verified: boolean) {
await sealedInput(input)
const value = await observe(input, undefined, verified)
@@ -128,7 +137,8 @@ export async function prepareScopedCommunitySwitch(input: Record<string, unknown
try { managementBindingCiphertext = await encryptSecret(JSON.stringify(await prepareManagementTransportBinding(profile.serverId, { authorize: value.authorize, signal: action.signal, deadline: Math.min(Date.now() + 120000, Date.parse(action.principal.credentialExpiresAt), action.connection.owner_valid_until.getTime(), Date.parse(action.connection.policy.expiresAt)) }))) }
catch (error) { if (!(error instanceof McpError) || error.code !== "APPROVAL_REQUIRED") throw error }
}
const sealed: Record<string, unknown> = { ...withPath, nativeId: owned.nativeId, outIface, contextSources: value.context.before.sourceProofs, dependencyProof: value.context.before.dependencyProof,
const originalEffectSelection = await value.context.captureOriginalEffectSelection(String(input.candidateId))
const sealed: Record<string, unknown> = { ...withPath, originalEffectSelection, nativeId: owned.nativeId, outIface, contextSources: value.context.before.sourceProofs, dependencyProof: value.context.before.dependencyProof,
originalTargetProof: value.context.before.targetRuleProof, desiredTargetProof: owned.desiredTargetProof(compiled.compiledBody), receiptId: randomUUID(), proofRevision: persistent ? 3 : 2,
originalCandidateId: value.context.before.currentCandidateId, originalRuleCiphertext: await encryptSecret(String(owned.rawRow.rule)), desiredRuleCiphertext: await encryptSecret(compiled.compiledBody), managementBindingCiphertext,
rules: [publicRule(compiled.proposed)], beforeRules: [publicRule(compiled.before)] }
@@ -156,7 +166,26 @@ export function registerScopedCommunityService() {
if (await receiptExists(input, "routing.scoped.attempt")) throw fail("Повтор PATCH запрещён", "MUTATION_OUTCOME_UNKNOWN")
await receipt(input, "routing.scoped.attempt")
const before = await original(input, true), owned = before.context.ownedTarget!
let effectSession: Awaited<ReturnType<typeof createRoutingNetworkSessionForCurrentExecution>> | undefined
let effectBaseline: Parameters<typeof originalEffectBaselineMatches>[1] | undefined
let originalEffects: OriginalEffectReport | undefined
const effectSelection = originalEffectSelectionSchema.safeParse(input.originalEffectSelection)
if (effectSelection.success) originalEffects = unknownOriginalEffects(effectSelection.data.status === "unknown" ? effectSelection.data.reason : "effect-acquisition-unavailable")
try {
// Optional original-effect vector is never an extra write/recovery grant.
// Old manual configuration+probe acceptance remains authoritative.
if (context?.phase === "forward" && input.proofRevision === 3 && effectSelection.success && effectSelection.data.status === "prepared") {
try {
const selected = effectSelection.data
effectSession = await createRoutingNetworkSessionForCurrentExecution({ servers: scopedProfileIds(before.profile),
probes: [{ key: selected.probe.key, serverId: selected.probe.serverId, destination: selected.probe.destination,
source: selected.probe.source, interface: selected.probe.interface, probeVrf: selected.probe.probeVrf }] })
effectBaseline = await effectSession.readSnapshot("before")
if (!originalEffectBaselineMatches(selected, effectBaseline)) {
effectSession.close(); effectSession = undefined; originalEffects = unknownOriginalEffects("prepared-effect-baseline-drift")
}
} catch { effectSession?.close(); effectSession = undefined }
}
await executeGuardedConfigurationWrite(before.profile.serverId, "PATCH", "/routing/filter/rule/" + owned.nativeId, async () => {
// This runs AFTER the durable configuration.intent, immediately before transport.
const fresh = await original(input, true); await before.context.fence()
@@ -170,13 +199,47 @@ export function registerScopedCommunityService() {
await saveScopedConfigurationCheckpoint(input, await sealedInput(input))
const check = await checkCandidatePath(after.profile, after.candidate.id, principalSchema.parse(input.principal).connectionId, { authorize: after.authorize })
if (check.status === "unknown") throw fail("Control probe неизвестен", "MUTATION_OUTCOME_UNKNOWN")
// A probe can be long: prove the whole configuration again before a durable VERIFIED receipt.
if (effectSession && effectBaseline && effectSelection.success && effectSelection.data.status === "prepared") {
let roundContext: Awaited<ReturnType<typeof resolveRoutingNetworkForwardRoundConfigurationContext>> | undefined
try {
const principal = principalSchema.parse(input.principal), action = { principal, connection: await effectiveMcpConnection(principal) }
roundContext = await resolveRoutingNetworkForwardRoundConfigurationContext(effectSession.sharedContext, action)
const first = await effectSession.readConfigurationBoundForwardSnapshot(roundContext)
const second = await effectSession.readConfigurationBoundForwardSnapshot(roundContext)
const observed = await inspectOriginalEffectRounds(effectSelection.data, [first, second], effectBaseline), budget = effectSession.readBudget()
originalEffects = { ...observed, budget: { deadline: budget.deadline, calls: budget.calls, bytes: budget.bytes, rows: budget.rows } }
} catch (error) { originalEffects = effectReadFailure("original-effect-rounds-unavailable", error) }
finally { if (roundContext) closeRoutingNetworkForwardRoundConfigurationContext(roundContext) }
}
// Publication revalidation uses the existing native owner's fixed
// context reader and therefore its SAME counters, without a snapshot.
let sharedPublication = false
if (effectSession && !effectSession.readBudget().closed) {
try {
const published = await readRoutingNetworkScopedCommunityContext(effectSession.sharedContext)
if (!matches(input, published).configurationMatchesDesired) throw fail("Состояние изменилось после effect-check")
await published.fence()
const budget = effectSession.readBudget()
originalEffects = { ...originalEffects!, budget: { deadline: budget.deadline, calls: budget.calls, bytes: budget.bytes, rows: budget.rows } }
sharedPublication = true
} catch (error) { originalEffects = effectReadFailure("effect-publication-revalidation-unavailable", error) }
}
if (!sharedPublication) {
// This is the OLD manual final read, relocated after optional work,
// never duplicated or counted as a completed effect-owner read.
// Closed/exhausted effect owners cannot waive this fresh guard.
const final = await observe(input, undefined, true, undefined, "configuration")
if (!matches(input, final.context).configurationMatchesDesired) throw fail("Состояние изменилось после probe")
await final.context.fence()
await saveScopedNetworkCheckpoint(input, await sealedInput(input), { ...check, status: check.status })
}
if (effectSession && originalEffects) {
const budget = effectSession.readBudget()
originalEffects = { ...originalEffects, budget: { deadline: budget.deadline, calls: budget.calls, bytes: budget.bytes, rows: budget.rows } }
}
await saveScopedNetworkCheckpoint(input, await sealedInput(input), { ...check, status: check.status }, originalEffects)
return verifiedResult(input)
} catch { throw fail("Результат переключения не подтверждён; повтор запрещён, требуется сверка", "MUTATION_OUTCOME_UNKNOWN") }
finally { effectSession?.close() }
})),
verify: async (input, result) => {
const expected = await verifiedResult(input)
@@ -200,6 +263,7 @@ async function verifiedResult(input: Record<string, unknown>) {
const network = await loadScopedNetworkCheckpoint(input, await sealedInput(input))
const result = { outcome: network?.status === "passed" ? "applied" : "configuration-applied-network-unverified", nativeWrites: 1, forwardingVerified: false, configurationVerified: true, receiptId: input.receiptId, proofRevision: input.proofRevision,
configurationCheckpointAvailable: !!checkpoint, configurationCheckpointProof: checkpoint?.checkpointProof ?? null, controlProbeVerified: network?.status === "passed", postProbeStatus: network?.status ?? "unknown", networkCheckpointProof: network?.networkSeal ?? null,
...(network?.originalEffects === undefined ? {} : { originalEffects: network.originalEffects }),
nativeVerificationSeal: await privateSecretFingerprint(fingerprint({ request: await sealedInput(input), dependency: input.dependencyProof, desired: input.desiredTargetProof, candidate: input.candidateId, configuration: checkpoint?.checkpointProof ?? null, network: network?.networkSeal ?? null }), `route-ai-scoped-verified-v${input.proofRevision}`) }
await assertPersistentScopedProfileAuthority(input)
return result
@@ -4,6 +4,7 @@ import { findMcpOperation } from "./registry.js"
import { assertScopedCommunityRequest } from "./routing-community-scoped-request.js"
import { assertPersistentScopedProfileAuthority, routingProfileAuthorityBindingSchema } from "./routing-profile-authority.js"
import { loadScopedConfigurationPacket, loadScopedNetworkCheckpoint } from "./routing-community-scoped-checkpoint.js"
import { originalEffectSelectionSchema } from "./routing-original-effects.js"
import { z } from "zod"
import { routeAiProfileSchema, type RouteAiProfile } from "@mmapp/contracts/mcp-routing"
import { hasPermission } from "../../lib/permissions.js"
@@ -166,6 +167,13 @@ export async function advanceRoutingControllerBaseline(rawBaseline: unknown, pro
const configuration = await loadScopedConfigurationPacket(input, requestSeal, { operationId, stepIndex: 0 })
const network = await loadScopedNetworkCheckpoint(input, requestSeal)
if (!configuration || configuration.packet.revision !== 2 || !network || network.status !== "passed") throw fail()
// A genuinely prepared effect check may pause controller advancement without
// erasing the separate successful manual configuration/control-probe receipt.
if (Object.hasOwn(input, "originalEffectSelection")) {
const effects = originalEffectSelectionSchema.safeParse(input.originalEffectSelection)
if (!effects.success || effects.data.status !== "prepared" || network.originalEffects?.status !== "observed-expected" ||
network.originalEffects.selectionIdentity !== fingerprint(effects.data) || network.originalEffects.switchEffectObserved !== true || !network.originalEffects.transition?.changedPrefixes.length) throw fail()
}
const packet = configuration.packet, result = row.result
if (packet.operationId !== operationId || packet.stepIndex !== 0 || packet.nativeId !== original.nativeId ||
packet.originalCandidateId !== original.candidateId || packet.originalTargetProof !== original.targetProof ||
+22 -8
View File
@@ -1,3 +1,5 @@
import { readRoutingTopology } from "../../services/routing-topology-observation.js"
import { createRoutingSourceContext, readRoutingEndpoints } from "./routing-observation-source.js"
import { z } from "zod"
import { dedupeVrfAliasSnapshots } from "../../services/vrf-production-action-binding.js"
import { pool } from "../../db/index.js"
@@ -177,14 +179,26 @@ export function registerRoutingControls(register: typeof add) {
},
})
register("routing.topology.read", "mm_routing_topology_get", "Наблюдаемая конфигурация маршрутизации", "network", "mm:network:read", "read", "servers", z.object({ serverId }).strict(), {
read: async input => {
const { client } = await routerClient(Number(input.serverId))
const paths = { interfaces: "/interface", ipv4: "/ip/route", ipv6: "/ipv6/route", tables: "/routing/table", rules: "/routing/rule", vrfs: "/ip/vrf", ospfNeighbors: "/routing/ospf/neighbor", bgpSessions: "/routing/bgp/session" }
const entries = await Promise.all(Object.entries(paths).map(async ([key, path]) => {
try { const values = await client.get<unknown>(path); const rows = Array.isArray(values) ? values : [values]; return [key, { items: rows.slice(0, 500), truncated: rows.length > 500, observed: true }] }
catch { return [key, { items: [], observed: false, reason: "Устройство не подтвердило возможность чтения" }] }
}))
return { serverId: input.serverId, observedAt: new Date().toISOString(), resources: Object.fromEntries(entries), observedPath: false }
read: async (input, context) => {
const sid = Number(input.serverId)
if (!context.connection.policy.scope.serverIds.includes(sid)) throw new McpError("SERVER_DENIED", "Источник топологии вне разрешённой области", 403)
const signal = AbortSignal.any([AbortSignal.timeout(30000), ...(context.signal ? [context.signal] : [])])
const authorize = async () => {
if (signal.aborted) throw new McpError("READ_CANCELLED", "Чтение топологии отменено или превысило срок", 409)
const current = await effectiveMcpConnection(context.principal)
if (!hasPermission(current.owner_permissions, "mm:network:read") || !hasPermission(current.policy.permissions, "mm:network:read")) throw new McpError("PERMISSION_DENIED", "Право чтения сети отозвано", 403)
if (fingerprint(current.policy) !== fingerprint(context.connection.policy) || fingerprint(current.owner_permissions) !== fingerprint(context.connection.owner_permissions)) throw new McpError("RESET_REQUIRED", "Допуск изменился; повторите чтение топологии", 409)
}
await authorize()
const source = await createRoutingSourceContext(await readRoutingEndpoints([sid]))
if (source.expected.length !== 1) throw new McpError("DEVICE_UNAVAILABLE", "Источник топологии отсутствует или выключен", 409)
const beforeRead = async () => {
await authorize(); await source.assertCurrent()
if (signal.aborted) throw new McpError("READ_CANCELLED", "Чтение топологии отменено или превысило срок", 409)
}
await beforeRead()
const { client } = await routerClient(sid)
return { serverId: sid, ...await readRoutingTopology(client, beforeRead, signal) }
},
})
register("routing.ecmp.update", "mm_ecmp_plan", "Равная стоимость маршрутов ECMP и hash policy", "network", "mm:network:write", "write", "servers", z.object({ serverId, family, routeIds: z.array(objectId).min(2).max(8).refine(ids => new Set(ids).size === ids.length), distance: z.number().int().min(1).max(255).default(1), hashPolicy: z.enum(["l3", "l4", "l3-inner"]).optional() }).strict(), {
@@ -201,12 +201,13 @@ test("missing genuine checkpoint closes shared owner without accepting equality"
test("forward round context reuses one admitted source only through its exact ephemeral acquisition", async () => {
const f = await fixture(), token = await f.resolveRound()
assert.equal(f.stats().sharedReads, 0, "Round admission grants no native-success observation")
for (let round = 0; round < 2; round++) {
const before = await f.readRound(token), after = await f.readRound(token)
assert.equal(before.operationId, f.row.id); assert.equal(after.securityProof, false)
assert.equal(after.forwardingVerified, false); assert.equal(after.sharedNetworkBudgetVerified, true)
}
assert.equal(f.stats().sharedReads, 5)
assert.equal(f.stats().sharedReads, 4)
await assert.rejects(f.resolveShared(), /already issued/)
})
@@ -221,3 +222,16 @@ for (const fault of ["clone", "owner", "body", "dependency", "checkpoint", "revo
await assert.rejects(f.readRound(fault === "clone" ? JSON.parse(JSON.stringify(token)) : token, fault === "owner" ? {} : f.acquisition))
assert.equal(f.stats().sharedClosed, !["clone", "close"].includes(fault))
})
for (const drift of ["body", "dependency", "checkpoint", "revoke"] as const)
test("first forward-round read detects " + drift + " after source-only admission", async () => {
const f = await fixture(), token = await f.resolveRound()
assert.equal(f.stats().nativeReads, 0)
if (drift === "body" || drift === "dependency") f.drift(drift)
if (drift === "checkpoint") f.checkpointChanged()
if (drift === "revoke") f.revoke()
await assert.rejects(f.readRound(token))
assert.equal(f.stats().sharedClosed, true)
await assert.rejects(f.readRound(token))
})
@@ -68,7 +68,9 @@ export async function resolveRoutingNetworkConfigurationContextFromSharedContext
* as the existing bridge. Only its owning session can use it inside a queued
* round acquisition; JSON copies and caller-supplied observations cannot. */
export async function resolveRoutingNetworkForwardRoundConfigurationContext(sharedContext: RoutingNetworkSharedContext, action: ActionContext): Promise<RoutingNetworkForwardRoundConfigurationContext> {
const initial = await resolveRoutingNetworkConfigurationContextFromSharedContext(sharedContext, action)
const binding = inspectRoutingNetworkSharedContext(sharedContext)
if (fingerprint(binding.principal) !== fingerprint(action.principal)) throw fail("Shared configuration principal changed")
const initial = await resolveConfigurationContext(binding.sourceOperationId, action, sharedContext, true)
const entry = contexts.get(initial)
if (!entry) throw fail("Configuration bridge admission disappeared")
contexts.delete(initial)
@@ -96,7 +98,8 @@ export function closeRoutingNetworkForwardRoundConfigurationContext(context: Rou
roundContexts.delete(context); entry?.close()
}
async function resolveConfigurationContext(sourceOperationId: string, action: ActionContext, sharedContext?: RoutingNetworkSharedContext): Promise<RoutingNetworkConfigurationContext> {
async function resolveConfigurationContext(sourceOperationId: string, action: ActionContext, sharedContext?: RoutingNetworkSharedContext, deferredRoundReadback = false): Promise<RoutingNetworkConfigurationContext> {
if (deferredRoundReadback && !sharedContext) throw fail("Forward round requires its original shared owner")
z.string().uuid().parse(sourceOperationId)
const principal = freeze(structuredClone(action.principal)), connection = freeze(structuredClone(action.connection))
const abort = new AbortController()
@@ -190,7 +193,11 @@ async function resolveConfigurationContext(sourceOperationId: string, action: Ac
bodyIdentity: fingerprint(original.packet.desiredRule), dependencyIdentity: original.packet.dependencyProof },
securityProof: false as const, forwardingVerified: false as const, sharedNetworkBudgetVerified: !!sharedContext })
}
await readback()
// A forward-round token carries no native success observation. Its fixed
// owner must perform a fresh read immediately BEFORE every snapshot and
// another immediately AFTER. Avoid an unused eager native read here;
// independent/shared one-shot bridge semantics remain unchanged.
if (!deferredRoundReadback) await readback()
const token = Object.freeze({}) as RoutingNetworkConfigurationContext
contexts.set(token, { consumed: false, close, read: readback, consume: async () => {
try { return await readback() }
@@ -0,0 +1,71 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import test from 'node:test';
import { createHash } from 'node:crypto';
const require = createRequire(new URL('../../../package.json', import.meta.url));
const ts = require('typescript');
const code = ts.transpileModule(await readFile(new URL('./routing-network-execution-session.ts', import.meta.url), 'utf8'), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }
}).outputText;
function fixture() {
const now = Date.now();
const metadata = { approved: true, phase: 'forward', stepIndex: 0, operationName: 'routing.community.switch', operationVersion: 7,
serviceName: 'routing.community.scoped', operationId: 'real-operation', planId: 'real-plan', principal: { connectionId: 'real-connection', credentialExpiresAt: new Date(now + 90000).toISOString() },
serviceInput: { persistentProfileAuthority: { packet: { validUntil: new Date(now + 70000).toISOString() } } } };
metadata.sealedEvidenceProof = createHash('sha256').update('evidence-ciphertext').digest('hex');
metadata.sealedPayloadProof = createHash('sha256').update('payload-ciphertext').digest('hex');
const original = structuredClone(metadata);
const row = { created_at: new Date(now - 40000), expires_at: new Date(now + 100000), evidence: { sealed: 'evidence-ciphertext' }, payload: { sealed: 'payload-ciphertext' },
intended_state: { kind: 'service', service: metadata.serviceName, input: structuredClone(metadata.serviceInput) } };
const lifetime = { expiresAt: now + 60000, monotonicDeadline: performance.now() + 60000, signal: new AbortController().signal };
let calls = 0, inspections = 0, closed = 0, options, query, originAvailable = true, afterCreate = () => {};
const connection = { owner_valid_until: new Date(now + 110000), policy: { expiresAt: new Date(now + 120000).toISOString() } };
const token = Object.freeze({});
const providers = {
'node:crypto': { createHash },
'../../db/index.js': { pool: { query: async value => { query = value; return { rows: [row] }; } } },
'../../services/config-mutation-policy.js': {
captureVerifiedMcpServiceExecution: async () => { if (!originAvailable) throw Error('no-origin'); return token; },
inspectVerifiedMcpServiceExecution: async value => { assert.equal(value, token); inspections++; return structuredClone(metadata); },
inspectVerifiedMcpServiceExecutionLifetime: async value => { assert.equal(value, token); return lifetime; }
},
'./store.js': { effectiveMcpConnection: async principal => { assert.deepEqual(principal, original.principal); return connection; } },
'./registry.js': { findMcpOperation: () => ({ version: 7 }) },
'./security.js': { fingerprint: JSON.stringify, redact: value => value, McpError: Error },
'./routing-profile-authority.js': { routingProfileAuthorityBindingSchema: { parse: value => value } },
'./routing-network-native-session.js': { createRoutingNetworkNativeSession: async value => { calls++; options = value; afterCreate(); return { close() { closed++; } }; } }
};
const exports = {};
new Function('exports', 'require', code)(exports, name => { assert.ok(providers[name], name); return providers[name]; });
const manifest = { servers: [1, 2], probes: [] };
return { metadata, row, lifetime, connection, manifest, now, run: () => exports.createRoutingNetworkSessionForCurrentExecution(manifest),
revokeOrigin: () => { originAvailable = false; }, afterCreate: fn => { afterCreate = fn; },
stats: () => ({ calls, closed, inspections, options, query }) };
}
test('real origin supplies operation, principal, signal and minimum immutable horizon', async () => {
const f = fixture(); await f.run(); const { options, query } = f.stats();
assert.equal(options.sourceOperationId, f.metadata.operationId); assert.equal(options.context.signal, f.lifetime.signal);
assert.equal(options.deadline, f.lifetime.expiresAt - 15000); assert.equal(options.manifest, f.manifest);
assert.deepEqual(query.values, [f.metadata.operationId, f.metadata.principal.connectionId, f.metadata.planId]);
assert.match(query.text, /o.state='running'/); assert.match(query.text, /s.state='intent'/);
});
test('elapsed original 120 seconds cannot be renewed from current time', async () => {
const f = fixture(); f.row.created_at = new Date(f.now - 120001); await assert.rejects(f.run); assert.equal(f.stats().calls, 0);
});
for (const [name, change] of [
['plan', f => { f.row.expires_at = new Date(f.now + 30000); }],
['owner', f => { f.connection.owner_valid_until = new Date(f.now + 30000); }],
['policy', f => { f.connection.policy.expiresAt = new Date(f.now + 30000).toISOString(); }],
['profile', f => { f.metadata.serviceInput.persistentProfileAuthority.packet.validUntil = new Date(f.now + 30000).toISOString(); f.row.intended_state.input = structuredClone(f.metadata.serviceInput); }],
['operation', f => { f.row.created_at = new Date(f.now - 90000); }]
]) test(`minimum ${name} expiry is conserved with publication reserve`, async () => { const f = fixture(); change(f); await f.run(); assert.equal(f.stats().options.deadline, f.now + 15000); });
for (const [key, value] of [['approved', false], ['phase', 'recovery'], ['stepIndex', 1], ['operationName', 'operations.recover'], ['operationVersion', 6], ['serviceName', 'routing.community']]) {
test(`wrong live origin ${key} is denied before native factory`, async () => { const f = fixture(); f.metadata[key] = value; await assert.rejects(f.run); assert.equal(f.stats().calls, 0); });
}
test('outside verified origin cannot supply authority', async () => { const f = fixture(); f.revokeOrigin(); await assert.rejects(f.run); assert.equal(f.stats().calls, 0); });
test('persisted intended state must match the live service input', async () => { const f = fixture(); f.row.intended_state.input = {}; await assert.rejects(f.run); assert.equal(f.stats().calls, 0); });
for (const field of ['evidence', 'payload']) test(`changed randomized sealed ${field} is denied before native`, async () => { const f = fixture(); f.row[field].sealed = 'changed'; await assert.rejects(f.run); assert.equal(f.stats().calls, 0); });
test('monotonic expiry denies open even with unexpired wall horizon', async () => { const f = fixture(); f.lifetime.monotonicDeadline = performance.now() - 1; await assert.rejects(f.run); assert.equal(f.stats().calls, 0); });
test('late origin change closes the newly opened native owner', async () => { const f = fixture(); f.afterCreate(() => { f.metadata.planId = 'changed'; }); await assert.rejects(f.run); assert.equal(f.stats().closed, 1); });
test('returned native authorize callback remains tied to original metadata', async () => { const f = fixture(); await f.run(); f.metadata.operationId = 'changed'; await assert.rejects(f.stats().options.authorize); });
@@ -0,0 +1,62 @@
import { createHash } from "node:crypto"
import type { QueryConfig } from "pg"
import { pool } from "../../db/index.js"
import { captureVerifiedMcpServiceExecution, inspectVerifiedMcpServiceExecution, inspectVerifiedMcpServiceExecutionLifetime } from "../../services/config-mutation-policy.js"
import { effectiveMcpConnection } from "./store.js"
import { findMcpOperation } from "./registry.js"
import { fingerprint, McpError, redact } from "./security.js"
import { routingProfileAuthorityBindingSchema } from "./routing-profile-authority.js"
import { createRoutingNetworkNativeSession, type RoutingNetworkNativeManifest } from "./routing-network-native-session.js"
/** Private producer for the existing native/configuration-round reader. The
* manifest selects reads, never identity or authority. Its exact scope, probe
* ownership and whole-session budget are still checked by the native factory.
* Only the original live verified service step can open or use this session.
* No operation/principal/deadline/authorization callback is caller supplied.
* This grants no write, recovery or authenticated log-interval coverage. */
export async function createRoutingNetworkSessionForCurrentExecution(manifest: RoutingNetworkNativeManifest): Promise<Awaited<ReturnType<typeof createRoutingNetworkNativeSession>>> {
const fail = () => new McpError("STATE_CONFLICT", "Нет исходного running scoped execution для native acquisition", 409)
const origin = await captureVerifiedMcpServiceExecution()
const initial = await inspectVerifiedMcpServiceExecution(origin)
if (!initial.approved || initial.phase !== "forward" || initial.stepIndex !== 0 ||
initial.operationName !== "routing.community.switch" || initial.operationVersion !== findMcpOperation(initial.operationName)?.version ||
initial.serviceName !== "routing.community.scoped") throw fail()
const lifetime = await inspectVerifiedMcpServiceExecutionLifetime(origin)
const authorize = async () => {
const current = await inspectVerifiedMcpServiceExecution(origin)
if (fingerprint(current) !== fingerprint(initial)) throw fail()
}
await authorize()
const connection = await effectiveMcpConnection(initial.principal)
await authorize()
// The service lifetime may be narrower than plan expiry, but it does not
// replace the operation-created-at +120s native horizon. Never renew at now.
const query: QueryConfig & { query_timeout: number } = { text: `SELECT o.created_at,p.expires_at,
CASE WHEN octet_length(p.evidence::text)<=1048576 THEN p.evidence END AS evidence,
CASE WHEN octet_length(p.payload::text)<=1048576 THEN p.payload END AS payload,
CASE WHEN octet_length(s.intended_state::text)<=1048576 THEN s.intended_state END AS intended_state FROM mcp_operations o
JOIN mcp_plans p ON p.id=o.plan_id JOIN mcp_operation_steps s ON s.operation_id=o.id
WHERE o.id=$1 AND o.connection_id=$2 AND o.plan_id=$3 AND o.state='running'
AND s.step_index=0 AND s.state='intent' LIMIT 2`,
values: [initial.operationId, initial.principal.connectionId, initial.planId],
query_timeout: Math.max(1, Math.min(10000, lifetime.expiresAt - Date.now())) }
const source = await pool.query(query)
await authorize()
if (source.rows.length !== 1 || !(source.rows[0].created_at instanceof Date) || !(source.rows[0].expires_at instanceof Date)) throw fail()
const sha = (value: string) => createHash("sha256").update(value).digest("hex")
if (typeof source.rows[0].evidence?.sealed !== "string" || typeof source.rows[0].payload?.sealed !== "string" ||
sha(source.rows[0].evidence.sealed) !== initial.sealedEvidenceProof || sha(source.rows[0].payload.sealed) !== initial.sealedPayloadProof) throw fail()
if (fingerprint(source.rows[0].intended_state) !== fingerprint(redact({ kind: "service", service: initial.serviceName, input: initial.serviceInput }))) throw fail()
const authorityExpiry = initial.serviceInput.persistentProfileAuthority === undefined ? Infinity :
Date.parse(routingProfileAuthorityBindingSchema.parse(initial.serviceInput.persistentProfileAuthority).packet.validUntil)
const deadline = Math.min(source.rows[0].created_at.getTime() + 120000, source.rows[0].expires_at.getTime(), lifetime.expiresAt,
Date.parse(initial.principal.credentialExpiresAt), connection.owner_valid_until.getTime(), Date.parse(connection.policy.expiresAt), authorityExpiry)
// Reserve publication/current-authority time for the existing manual result.
// This only narrows the original horizon; it never creates a renewed grant.
const readDeadline = deadline - 15000
if (!Number.isFinite(readDeadline) || Date.now() >= readDeadline || lifetime.signal.aborted || performance.now() >= lifetime.monotonicDeadline) throw fail()
const session = await createRoutingNetworkNativeSession({ sourceOperationId: initial.operationId,
context: { principal: initial.principal, connection, signal: lifetime.signal }, authorize, deadline: readDeadline, manifest })
try { await authorize(); return session }
catch (error) { session.close(); throw error }
}
@@ -15,6 +15,8 @@ assert.equal(target.search, ""); assert.equal(target.hash, "")
assert(process.env.MCP_STATE_DIR && isAbsolute(process.env.MCP_STATE_DIR))
assert.equal(basename(resolve(process.env.MCP_STATE_DIR)), "state")
assert(basename(dirname(resolve(process.env.MCP_STATE_DIR))).startsWith("mm-mcp-test-"))
const originalEffects = process.env.MMAPP_OWN_ROUTING_ORIGINAL_EFFECTS === "true"
assert(!(originalEffects && process.env.MMAPP_OWN_ROUTING_WORKER_REPRO === "true"), "Owned effect acceptance cannot mix with the worker repro profile")
process.env.AUTH_REQUIRED = "true"
process.env.AUTH_JWT_SECRET = "owned-routing-forward-rounds-acceptance-secret"
process.env.AUTH_ISSUER = "https://owned-routing-forward-rounds.test"
@@ -32,7 +34,8 @@ try {
if(process.env.MMAPP_OWN_ROUTING_WORKER_REPRO==="true") {
console.log("Worker fixture repro:",await (await import("./routing-worker-fixture-isolation.sql-helper.js")).acceptRoutingWorkerFixtureIsolation(actor))
}
const result = await (await import("./routing-community-scoped-acceptance.js")).acceptScopedRouteCommunitySwitch(actor, process.env.MMAPP_OWN_ROUTING_WORKER_REPRO==="true"?{}:{ forwardRoundsOnly: true })
const result = await (await import("./routing-community-scoped-acceptance.js")).acceptScopedRouteCommunitySwitch(actor,
originalEffects ? { originalEffectsOnly: true } : process.env.MMAPP_OWN_ROUTING_WORKER_REPRO==="true"?{}:{ forwardRoundsOnly: true })
assert.equal(result.accepted, true)
} finally {
await (await import("../../services/scheduler.js")).stopSchedulerForDatabaseOperation()
@@ -353,3 +353,21 @@ test("DNS cancellation retains uncancellable lookup capacity and a late answer c
authorize: async () => { if (++checks > 1) throw new SessionError("RESET_REQUIRED", "revoked during DNS") } }), (error: { code?: string }) => error.code === "RESET_REQUIRED")
assert.equal(lookups, 1)
})
test("complete authority fences bound repeated runtime authorization while preserving every native boundary", async () => {
const f = await fixture(), session = await f.create(f.options)
const before = f.stats()
await session.readSnapshot("before")
const after = f.stats(), native = after.nativeCalls - before.nativeCalls, callbacks = after.callbacks - before.callbacks
assert.equal(native, 6)
// Two full fences and the direct dispatch guard stay fresh for every call.
// SQL/file leaves inside those fences must not recursively multiply them.
assert(callbacks >= native * 4)
assert(callbacks <= 50, "Six native reads must fit a bounded number of complete runtime revalidations")
f.afterNative(f.revoke)
await assert.rejects(session.readSnapshot("after-forward"))
assert.equal(f.stats().nativeCalls, after.nativeCalls + 1)
await assert.rejects(session.readSnapshot("after-forward"))
assert.equal(f.stats().nativeCalls, after.nativeCalls + 1)
})
@@ -119,8 +119,16 @@ export async function createRoutingNetworkNativeSession(options: {
try { return await work() }
finally { active(); await trustedAuthorize(); active() }
}
// Primitive source reads keep the immutable lifetime checks. The complete
// authority fence surrounds their batch; native dispatch retains its own
// fresh before/after authorization. Avoid recursively re-running the full
// runtime authorization around every SQL/file leaf inside that same fence.
const bounded = async <T>(work: () => Promise<T>) => {
active()
try { return await work() } finally { active() }
}
const currentAuthority = async () => {
const current = await guarded(() => effectiveMcpConnection(principal))
const current = await bounded(() => effectiveMcpConnection(principal))
if (current.id !== connection.id || current.owner_id !== connection.owner_id || current.generation !== connection.generation ||
fingerprint(current.policy) !== fingerprint(connection.policy) || fingerprint(current.owner_permissions) !== fingerprint(connection.owner_permissions) ||
!hasPermission(current.owner_permissions, "mm:network:read") || !hasPermission(current.policy.permissions, "mm:network:read") ||
@@ -129,13 +137,14 @@ export async function createRoutingNetworkNativeSession(options: {
const sourceRow = async () => {
const query: QueryConfig & { query_timeout: number } = { text: "SELECT o.id,o.state,o.connection_id,o.created_at,p.id AS plan_id,p.operation_id,p.operation_version,p.approved_by,p.expires_at,s.state AS step_state,CASE WHEN octet_length(s.intended_state::text)<=1048576 THEN s.intended_state END AS intended_state,CASE WHEN octet_length(p.evidence::text)<=1048576 THEN p.evidence END AS evidence FROM mcp_operations o JOIN mcp_plans p ON p.id=o.plan_id JOIN mcp_operation_steps s ON s.operation_id=o.id AND s.step_index=0 WHERE o.id=$1 AND o.connection_id=$2 LIMIT 2",
values: [sourceOperationId, principal.connectionId], query_timeout: Math.max(1, Math.min(10000, deadline - Date.now())) }
const result = await guarded(() => pool.query(query))
const result = await bounded(() => pool.query(query))
if (result.rows.length !== 1) throw fail("NOT_FOUND", "Exact source native session недоступен")
const row = result.rows[0]
if (!["running", "applied", "partial"].includes(row.state) || !["intent", "applied", "verified"].includes(row.step_state) || row.operation_id !== "routing.community.switch" || row.operation_version !== findMcpOperation(row.operation_id)?.version || !row.approved_by || !row.evidence?.sealed || !(row.created_at instanceof Date) || !(row.expires_at instanceof Date)) throw fail("STATE_CONFLICT", "Нет совместимого подтверждённого source intent")
return row
}
try {
await guarded(async () => {})
await currentAuthority()
const row = await sourceRow()
if (row.state !== "running" || row.step_state !== "intent") throw fail("STATE_CONFLICT", "Native session требует исходный running intent")
@@ -176,14 +185,14 @@ export async function createRoutingNetworkNativeSession(options: {
if (endpoints.length !== fullScope.length || endpoints.some(endpoint => !endpoint.enabled) || fingerprint(endpoints.map(endpoint => endpoint.id).sort((a,b) => a-b)) !== fingerprint(fullScope)) throw fail("DEVICE_UNAVAILABLE", "Полный источник native manifest недоступен")
const sources = await guarded(() => createRoutingSourceContext(endpoints))
if (fingerprint(sources.expected) !== fingerprint(input.contextSources)) throw fail("STATE_CONFLICT", "Исходный endpoint manifest изменился")
const fence = async () => {
const fence = async () => guarded(async () => {
await currentAuthority()
if (await guarded(() => accessGeneration()) !== generation) throw fail("RESET_REQUIRED", "Access generation native session изменён")
await guarded(() => assertPersistentScopedProfileAuthority(input, signal))
if (await bounded(() => accessGeneration()) !== generation) throw fail("RESET_REQUIRED", "Access generation native session изменён")
await bounded(() => assertPersistentScopedProfileAuthority(input, signal))
const current = await sourceRow()
if (fingerprint({ id: current.id, createdAt: current.created_at, planId: current.plan_id, evidence: current.evidence, expiresAt: current.expires_at, intendedState: current.intended_state }) !== sourceIdentity) throw fail("STATE_CONFLICT", "Исходный native intent изменён")
await guarded(() => sources.assertCurrent())
}
await bounded(() => sources.assertCurrent())
})
const pins = await guarded(() => resolveRoutingNetworkDestinationPins(endpoints, { deadline, signal, authorize: fence }))
await fence()
const clients = routeContextClients(freeze(structuredClone(endpoints)), signal, { deadline, maxCalls: 256, maxBytes: 8 * 1048576, pins })
@@ -17,7 +17,8 @@ const { installLoopbackFixtureAgent } = await import("../../test/loopback-http-a
const { mcpConnectionPolicySchema } = await import("@mmapp/contracts/mcp")
const { routeAiProfileSchema } = await import("@mmapp/contracts/mcp-routing")
const security = await import("./security.js")
const { effectiveMcpConnection } = await import("./store.js")
const { effectiveMcpConnection, authenticateMcpToken } = await import("./store.js")
const { invokeMcpTool } = await import("./runtime.js")
const { createRoutingSourceContext, readRoutingEndpoints } = await import("./routing-observation-source.js")
const { resolveRoutingProfileAuthority } = await import("./routing-profile-authority.js")
await import("./actions.js")
@@ -28,7 +29,7 @@ const { captureRoutingNetworkObservationAssembly } = await import("./routing-net
const { readRoutingNetworkScopedCommunityContext } = await import("./routing-network-scoped-context.js")
type Options = Parameters<typeof createRoutingNetworkNativeSession>[0]
type Mode = "normal" | "zero-ping" | "redirect" | "auth" | "malformed" | "oversized" | "rows" | "total-bytes" | "total-rows"
type Mode = "topology-unknown" | "normal" | "zero-ping" | "redirect" | "auth" | "malformed" | "oversized" | "rows" | "total-bytes" | "total-rows"
let mode: Mode = "normal", onRequest: (() => Promise<void> | void) | undefined
const requests: { method: string; path: string; body: Record<string, string> }[] = []
let responseBytes = 0, writes = 0, sinks = 0
@@ -82,6 +83,7 @@ const router = http.createServer(async (req, res) => {
assert.equal(body.address, "203.0.113.9"); assert.equal(body.interface, "wan1"); assert.equal(body.vrf, "probe"); assert.equal(body["max-hops"], "16")
payload = [{ ".section": "0", address: "203.0.113.9", sent: "3", loss: "0", last: "1ms" }]
} else { writes++; throw Error("Unexpected native mutation") }
if (mode === "topology-unknown" && path === "/routing/route") payload = [{ ...collections(path)[0], "immediate-gw": "17", "bgp.peer-cache-id": "*s1" }]
if (mode === "rows") payload = Array.from({ length: 1001 }, () => ({ version: "7.26" }))
if (mode === "total-rows" && path !== "/system/resource") payload = Array.from({ length: 1000 }, () => ({ value: "fixture" }))
if (mode === "total-bytes") payload = [{ version: "7.26", padding: "x".repeat(600000) }]
@@ -133,6 +135,12 @@ async function source(withProbes = true) {
try {
await applySqlMigrations(pool)
// Other genuine phases may have already created authenticated checkpoints in
// this owned database. Read-only acceptance must preserve every existing row
// and add none; raw SQL JSON also preserves timestamp microseconds that
// conversion to a JavaScript Date would truncate.
const checkpointBaselineRows = (await pool.query("SELECT to_jsonb(c)::text AS inventory_row FROM mcp_operation_checkpoints c ORDER BY operation_id,step_index")).rows
const checkpointBaseline = security.fingerprint(checkpointBaselineRows)
assert.equal((await pool.query("SELECT count(*)::int AS n FROM servers")).rows[0].n, 0)
await pool.query("UPDATE mcp_settings SET enabled=true WHERE id=1")
serverId = Number((await pool.query("INSERT INTO servers(name,host,port,enabled,username,password,use_ssl,verify_ssl) VALUES('owned-native-reader','127.0.0.1',$1,true,'fixture','inert-only',false,true) RETURNING id", [bound.port])).rows[0].id)
@@ -140,9 +148,11 @@ try {
const f = await source(), originalAuthorize = f.options.authorize, session = await createRoutingNetworkNativeSession(f.options), before = responseBytes
const first = await session.readSnapshot("before")
assert.equal(first.securityProof, false); assert.equal(first.logCoverage, "unknown"); assert.equal(first.probes.length, 1)
const stamp = Date.now(), server = first.servers[0]
const server = first.servers[0]
const manifest = { revision: 1, phase: "forward", identity: { operationId: f.operationId, stepIndex: 0, profileId: f.profileId, profileVersion: 1, sourceEpoch: "owned-fixture-epoch", authorityIdentity: "owned-fixture-authority" },
sourceCreatedAt: stamp - 1000, deadline: f.options.deadline, observationStartAt: stamp - 500, maximumSampleAgeMs: 30000, serverIds: [serverId],
// Bind the diagnostic fixture to the actual acquisition interval. A guessed
// lookback after SQL/HTTP completes can put its source after the first read.
sourceCreatedAt: Date.parse(first.startedAt), deadline: f.options.deadline, observationStartAt: Date.parse(first.observedAt), maximumSampleAgeMs: 30000, serverIds: [serverId],
configuration: { serverId, nativeId: "*r1", table: "tenant", bodyIdentity: "not-a-configuration-checkpoint", dependencyIdentity: "not-a-publication-proof" },
routes: [{ key: "route1", serverId, family: "ipv4", prefix: "203.0.113.0/24", table: "tenant", sessionKey: "peer1", nextHop: "192.0.2.1", interface: "wan1", resolverTable: "main", resolutionChain: [{ address: "192.0.2.1", table: "main", interface: "wan1" }] }],
bgp: [{ key: "peer1", serverId, family: "ipv4", connectionId: "*c1", instance: "default", localAddress: "192.0.2.2", remoteAddress: "192.0.2.1", remoteAsn: 65001 }],
@@ -233,9 +243,59 @@ try {
const f = await source(false), session = await createRoutingNetworkNativeSession(f.options); mode = "total-rows"
await session.readSnapshot("before"); await assert.rejects(session.readSnapshot("after-forward")); assert(session.readBudget().rows > 10000)
})
const topologyObserver = async () => {
const f = await source(false), { connectionId, credentialId } = f.options.context.principal
const policy = mcpConnectionPolicySchema.parse({ ...f.options.context.connection.policy, mode: "observer", permissions: ["mm:network:read"],
scope: { serverIds: [serverId!], operationIds: ["routing.topology.read"], allowGlobal: false } })
await pool.query("UPDATE mcp_connections SET policy=$2::jsonb,owner_permissions=$3::jsonb WHERE id=$1", [connectionId, JSON.stringify(policy), JSON.stringify(["mm:network:read"])])
const token = security.randomToken("bearer")
await pool.query("UPDATE mcp_credentials SET digest=$2 WHERE id=$1", [credentialId, security.tokenDigest(token)])
return { principal: await authenticateMcpToken(token), connectionId }
}
await run("actual public topology bearer reader observes final gateway without operation binding or namespace joins", async () => {
const f = await topologyObserver(), count = requests.length, plans = Number((await pool.query("SELECT count(*) AS n FROM mcp_plans")).rows[0].n)
const result = await invokeMcpTool(f.principal, "mm_routing_topology_get", { serverId }) as { routeResolution: { status: string; items: Array<{ status: string; immediateGateway: { address: string; interface: string }; semanticProof: boolean; routeOriginVerified: boolean; recursiveChainVerified: boolean }> } }
assert.equal(requests.length - count, 11)
assert.equal(result.routeResolution.status, "observed")
const route = result.routeResolution.items[0]
assert.equal(route.status, "observed"); assert.deepEqual(route.immediateGateway, { address: "192.0.2.1", interface: "wan1" })
assert.equal(route.semanticProof, false); assert.equal(route.routeOriginVerified, false); assert.equal(route.recursiveChainVerified, false)
assert.equal(Number((await pool.query("SELECT count(*) AS n FROM mcp_plans")).rows[0].n), plans)
assert(!JSON.stringify(result).includes("peer-cache-id")); assert(!JSON.stringify(result).includes("bgp.session"))
})
await run("actual public topology preserves unknown numeric gateway despite matching BGP cache/session strings", async () => {
const f = await topologyObserver(); mode = "topology-unknown"
const result = await invokeMcpTool(f.principal, "mm_routing_topology_get", { serverId }) as { routeResolution: { items: Array<{ status: string; reason: string }> } }
assert.equal(result.routeResolution.items[0].status, "unknown")
assert.equal(result.routeResolution.items[0].reason, "unconfirmed-immediate-endpoint")
})
await run("actual public topology bearer and server scope/disabled checks refuse before native I/O", async () => {
const f = await topologyObserver(), count = requests.length
await assert.rejects(authenticateMcpToken("not-a-token"))
await assert.rejects(invokeMcpTool(f.principal, "mm_routing_topology_get", { serverId: serverId! + 100000 }))
await pool.query("UPDATE servers SET enabled=false WHERE id=$1", [serverId])
await assert.rejects(invokeMcpTool(f.principal, "mm_routing_topology_get", { serverId }))
await pool.query("UPDATE servers SET enabled=true WHERE id=$1", [serverId])
assert.equal(requests.length, count)
})
await run("actual public topology pause endpoint ABA and cancellation retire at first response without publication", async () => {
for (const fault of ["pause", "endpoint-aba", "cancel"] as const) {
const f = await topologyObserver(), count = requests.length, cancel = new AbortController()
onRequest = async () => {
if (fault === "pause") await pool.query("UPDATE mcp_connections SET policy=jsonb_set(policy,'{status}','\"paused\"') WHERE id=$1", [f.connectionId])
else if (fault === "endpoint-aba") {
await pool.query("UPDATE servers SET verify_ssl=NOT verify_ssl WHERE id=$1", [serverId])
await pool.query("UPDATE servers SET verify_ssl=NOT verify_ssl WHERE id=$1", [serverId])
} else cancel.abort()
}
await assert.rejects(invokeMcpTool(f.principal, "mm_routing_topology_get", { serverId }, cancel.signal))
assert.equal(requests.length, count + 1)
}
})
assert.equal(writes, 0)
assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_operation_checkpoints")).rows[0].n, 0, "Read-only acceptance must not manufacture CONFIG_APPLIED or network checkpoints")
console.log("Owned routing native reader SQL/HTTP:", JSON.stringify({ cases: cases.length, names: cases, requests: requests.length, nativeWrites: writes, actualSql: true, actualHttp: true, syntheticEncryptedSourcePlans: true, genuinePrepareAccepted: false, configurationCheckpointFabricated: false, signedNetworkOutcomePublished: false, logCoverage: "unknown", reservedDispatchEnabled: false, realChr: false }))
assert.equal(security.fingerprint((await pool.query("SELECT to_jsonb(c)::text AS inventory_row FROM mcp_operation_checkpoints c ORDER BY operation_id,step_index")).rows), checkpointBaseline,
"Read-only acceptance must preserve existing checkpoints exactly and create none")
console.log("Owned routing native reader SQL/HTTP:", JSON.stringify({ cases: cases.length, names: cases, requests: requests.length, nativeWrites: writes, actualSql: true, actualHttp: true, syntheticEncryptedSourcePlans: true, genuinePrepareAccepted: false, configurationCheckpointFabricated: false, priorCheckpointsPreserved: checkpointBaselineRows.length, checkpointInventoryUnchanged: true, signedNetworkOutcomePublished: false, logCoverage: "unknown", reservedDispatchEnabled: false, realChr: false }))
} finally {
if (profiles.length) await pool.query("UPDATE mcp_routing_profiles SET enabled=false WHERE id=ANY($1::uuid[])", [profiles]).catch(() => {})
if (connections.length) await pool.query("UPDATE mcp_connections SET policy=jsonb_set(policy,'{status}','\"paused\"') WHERE id=ANY($1::uuid[])", [connections]).catch(() => {})
@@ -65,6 +65,7 @@ for (const ipv6 of [false, true]) for (const inverse of [false, true]) test(`fix
})
const invalid = {
"source-after-first-read": (f: ReturnType<typeof captured>) => { f.manifest.sourceCreatedAt = Date.parse(f.snapshots[0].startedAt) + 1 },
"foreign-operation": (f: ReturnType<typeof captured>) => { f.snapshots[1].sourceOperationId = "00000000-0000-4000-8000-000000000099" },
"duplicate-server": (f: ReturnType<typeof captured>) => { f.snapshots[1].servers.push(f.snapshots[1].servers[0]) },
"missing-probe": (f: ReturnType<typeof captured>) => { f.snapshots[1].probes = [] },
@@ -0,0 +1,54 @@
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { readFile } from 'node:fs/promises';
import { randomUUID } from 'node:crypto';
import test from 'node:test';
const require=createRequire(new URL('../../../package.json',import.meta.url)),ts=require('typescript'),{z}=require('zod');
const code=ts.transpileModule(await readFile(new URL('./routing-community-scoped-service.ts',import.meta.url),'utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText;
async function fixture(mode='prepared',failure){
const calls=[],principal={connectionId:randomUUID(),credentialId:randomUUID(),credentialExpiresAt:new Date(Date.now()+120000).toISOString()},connection={id:principal.connectionId,owner_id:'owner',generation:'generation',owner_permissions:['mm:filters:write','mm:network:read'],policy:{mode:'copilot',permissions:['mm:filters:write','mm:network:read'],scope:{serverIds:[1]},expiresAt:new Date(Date.now()+120000).toISOString()},owner_valid_until:new Date(Date.now()+120000)};
const profile={serverId:1,family:'ipv4',table:'tenant',probeVrf:'probe',community:'65000:10',autoRecovery:false,candidates:[{id:'a',jumpHostId:1,exitNodeId:1,gateway:'192.0.2.8',gatewayRef:{resource:'ipv4_route',id:'*r0'},controlProbe:{}},{id:'b',jumpHostId:1,exitNodeId:1,gateway:'192.0.2.9',gatewayRef:{resource:'ipv4_route',id:'*r'},controlProbe:{}}],excludedCandidateIds:[],pinnedCandidateId:null};
const input={serverId:1,profile,candidateId:'b',principal,ownerId:'owner',policyProof:JSON.stringify(connection.policy),connectionGeneration:'generation',generation:'access',proofRevision:3,originalCandidateId:'a',nativeId:'*1',outIface:'',originalRuleCiphertext:'original',desiredRuleCiphertext:'desired',desiredTargetProof:'desired-proof',beforeRules:[],receiptId:randomUUID()};
if(mode!=='legacy')input.originalEffectSelection=mode==='prepared'?{status:'prepared',probe:{key:'b',serverId:1,destination:'198.51.100.20',source:'198.51.100.10',interface:'wan',probeVrf:'probe'}}:{status:'unknown',reason:'unsupported-source'};
let wrote=false,handler,network,foreign=false,reads=0,rounds=0,closed=0,ownerClosed=false,nativeCalls=100;
const unknown=reason=>({scope:'prepared-route-fib-adjacency-control-effects',status:'unknown',reason,rounds:[],logCoverage:'unknown',semanticProof:false,forwardingVerified:false,autoRecoveryAllowed:false});
const report={...unknown(null),status:'observed-expected',rounds:[{controlProbe:'passed'},{controlProbe:'passed'}]};
const session={sharedContext:{},readSnapshot:async stage=>{calls.push('baseline:'+stage);return {};},readConfigurationBoundForwardSnapshot:async()=>{calls.push('round:'+(++rounds));if(failure==='round'||failure==='round-closed'){ownerClosed=failure==='round-closed';throw Error('unknown');}return {};},readBudget:()=>({deadline:Date.now()+10000,calls:nativeCalls,bytes:1000,rows:50,closed:ownerClosed}),close:()=>{closed++;ownerClosed=true;calls.push('close-session');}};
const providers={
'node:crypto':{randomUUID},zod:{z},'@mmapp/contracts/mcp-routing':{routeAiProfileSchema:{parse:x=>x}},'../../db/index.js':{pool:{query:async sql=>({rows:String(sql).includes('FROM mcp_path_checks')?[{id:randomUUID(),connection_id:principal.connectionId,status:'passed',created_at:new Date(),evidence:{}}]:[]})}},
'../../lib/permissions.js':{hasPermission:(list,value)=>list.includes(value)},
'../../services/config-mutation-policy.js':{assertVerifiedConfigurationAccess:async()=>{},executeGuardedConfigurationWrite:async(_sid,_method,_path,work)=>{calls.push('guarded-intent');return work();}},
'../../services/config-mutation-lock.js':{withApplicationMutationLock:work=>work(),withConfigMutationLock:(_ids,work)=>work()},
'./store.js':{effectiveMcpConnection:async()=>connection,audit:async()=>{}},
'./security.js':{accessGeneration:async()=>'access',decryptSecret:async x=>x,encryptSecret:async x=>x,fingerprint:JSON.stringify,McpError:Error,privateSecretFingerprint:async()=>'seal',redact:x=>x},
'./routing-community-scoped-checkpoint.js':{saveScopedConfigurationCheckpoint:async()=>{calls.push('CONFIG_APPLIED');},loadScopedConfigurationCheckpoint:async()=>({checkpointProof:'configuration'}),saveScopedNetworkCheckpoint:async(_input,_seal,check,originalEffects)=>{calls.push('save-network');network={status:check.status,networkSeal:'network',...(originalEffects?{originalEffects}:{})};},loadScopedNetworkCheckpoint:async()=>network},
'./routing-community-scoped-request.js':{assertScopedCommunityRequest:async()=>'request'},'./management-transport.js':{prepareManagementTransportBinding:async()=>({})},
'./routing-community-context.js':{readRouteCommunityContext:async()=>{reads++;return {before:{scopedExecutionAvailable:true,currentCandidateId:wrote?'b':'a',dependencyProof:'dep',sourceProofs:[{serverId:1,proof:'source'}]},ownedTarget:{nativeId:'*1',rawRow:{rule:wrote?'desired':'original'},endpoint:{},desiredTargetProof:()=> 'desired-proof'},fence:async()=>{},clients:new Map([[1,{get:async()=>[{'.id':'*r'}]}]]),captureOriginalEffectSelection:async()=>{calls.push('capture-prepared-selection');return input.originalEffectSelection;}};}},
'./routing-community-context-model.js':{candidateMatchesGateway:()=>true},'./routing-model.js':{rankRoutingCandidates:()=>profile.candidates.map(candidate=>({candidate,eligible:true}))},'./routing-observations.js':{readRoutingObservations:async()=>[]},
'./path-verification.js':{requireRecentPath:async()=>'path-check',checkCandidatePath:async()=>{calls.push('legacy-probe');return {id:randomUUID(),candidateId:'b',status:'passed',observedAt:new Date().toISOString()};}},
'./routing-community-scoped-model.js':{compileScopedCommunitySwitch:()=>({compiledBody:'desired',proposed:{community:'65000:10',gateway:'192.0.2.9',outIface:''},before:{community:'65000:10',gateway:'192.0.2.8',outIface:''}}),scopedConfigurationMatches:()=>({configurationMatchesOriginal:!wrote,configurationMatchesDesired:wrote&&!foreign})},
'./routing-community-scoped-native.js':{patchScopedCommunity:async()=>{calls.push('PATCH');wrote=true;}},'./service-registry.js':{registerMcpService:(_name,value)=>{handler=value;}},
'./routing-profile-authority.js':{assertPersistentScopedProfileAuthority:async()=>{},resolveRoutingProfileAuthority:async()=>{},routingProfileAuthorityBindingSchema:{parse:x=>x},scopedProfileAuthorityTransport:async()=>({})},
'./routing-observation-source.js':{createRoutingSourceContext:async()=>({}),readRoutingEndpoints:async()=>[]},'./routing-reservation-store.js':{createRoutingReservationStore:()=>({})},
'./routing-network-execution-session.js':{createRoutingNetworkSessionForCurrentExecution:async options=>{calls.push('open-session');assert.deepEqual(Object.keys(options).sort(),['probes','servers']);if(failure==='open')throw Error('unknown');return session;}},
'./routing-network-configuration-bridge.js':{resolveRoutingNetworkForwardRoundConfigurationContext:async()=>{calls.push('configuration-round-owner');return {};},closeRoutingNetworkForwardRoundConfigurationContext:()=>{calls.push('close-round-owner');}},
'./routing-network-scoped-context.js':{readRoutingNetworkScopedCommunityContext:async()=>{calls.push('shared-publication');if(failure==='publication')throw Error('DO_NOT_EXPOSE_TEST_DETAIL');nativeCalls+=10;return{before:{dependencyProof:'dep',targetRuleProof:'desired-proof',currentCandidateId:'b'},fence:async()=>{}};}},
'./routing-original-effects.js':{originalEffectSelectionSchema:{safeParse:x=>x?{success:true,data:x}:{success:false}},unknownOriginalEffects:unknown,originalEffectBaselineMatches:()=>failure!=='baseline',inspectOriginalEffectRounds:async()=>{calls.push('inspect-effects');if(failure==='foreign')foreign=true;return report;}}
};
const exports={};new Function('exports','require',code)(exports,name=>{assert.ok(providers[name],name);return providers[name];});exports.registerScopedCommunityService();
return {calls,input,run:()=>handler.execute(input,{operationId:randomUUID(),phase:'forward',stepIndex:0}),prepare:()=>exports.prepareScopedCommunitySwitch({profile,candidateId:'b'},{principal,connection}),stats:()=>({wrote,closed,reads}),network:()=>network};
}
test('actual service consumer baselines before the single PATCH and persists separate effects after genuine CONFIG_APPLIED and manual probe',async()=>{const f=await fixture(),result=await f.run();const c=f.calls;assert(c.indexOf('baseline:before')<c.indexOf('PATCH'));assert(c.indexOf('CONFIG_APPLIED')<c.indexOf('configuration-round-owner'));assert(c.indexOf('legacy-probe')<c.indexOf('round:1'));assert(c.indexOf('round:2')<c.indexOf('shared-publication'));assert(c.indexOf('shared-publication')<c.indexOf('save-network'));assert.equal(c.filter(x=>x==='PATCH').length,1);assert.equal(result.configurationVerified,true);assert.equal(result.controlProbeVerified,true);assert.equal(result.forwardingVerified,false);assert.equal(result.originalEffects.status,'observed-expected');assert.equal(result.originalEffects.budget.calls,110);assert.equal(f.stats().closed,1);assert.deepEqual(result.originalEffects,f.network().originalEffects);});
for(const mode of ['legacy','unknown'])test(`existing ${mode} manual path does not open native effect session`,async()=>{const f=await fixture(mode),result=await f.run();assert(!f.calls.includes('open-session'));assert.equal(result.configurationVerified,true);assert.equal(result.controlProbeVerified,true);assert.equal(f.calls.filter(x=>x==='PATCH').length,1);});
for(const failure of ['open','baseline','round','round-closed','publication'])test(`optional ${failure} uncertainty preserves actual manual config/control receipt with effects unknown`,async()=>{const f=await fixture('prepared',failure),result=await f.run();assert.equal(result.configurationVerified,true);assert.equal(result.controlProbeVerified,true);assert.equal(result.originalEffects.status,'unknown');assert.equal(f.calls.filter(x=>x==='PATCH').length,1);assert(f.calls.includes('save-network'));});
test('foreign configuration drift after optional effect observations still refuses publication, never repeats PATCH',async()=>{const f=await fixture('prepared','foreign');await assert.rejects(f.run);assert.equal(f.calls.filter(x=>x==='PATCH').length,1);assert(!f.calls.includes('save-network'));assert.equal(f.stats().closed,1);});
test('revision2 remains on existing manual path even with a prepared-like field',async()=>{const f=await fixture();f.input.proofRevision=2;await f.run();assert(!f.calls.includes('open-session'));});
test('actual preparation consumes the required real-context selection method and includes it in sealed service input',async()=>{const f=await fixture(),evidence=await f.prepare();assert(f.calls.includes('capture-prepared-selection'));assert.deepEqual(evidence.steps[0].input.originalEffectSelection,f.input.originalEffectSelection);assert.equal(evidence.steps[0].input.requestProof,'seal');assert(!f.calls.includes('PATCH'));});
test('closed effect owner falls back to exactly the old final context read without bypassing publication guard',async()=>{const legacy=await fixture('legacy');await legacy.run();const f=await fixture('prepared','round-closed');await f.run();assert(!f.calls.includes('shared-publication'));assert.equal(f.stats().reads,legacy.stats().reads);});
test('optional publication error exposes only a bounded failure category', async()=>{
const f=await fixture('prepared','publication'), result=await f.run();
assert.equal(result.originalEffects.reason,'effect-publication-revalidation-unavailable:STATE_UNAVAILABLE');
assert(!JSON.stringify(result).includes('DO_NOT_EXPOSE_TEST_DETAIL'));
assert.equal(result.configurationVerified,true);assert.equal(result.controlProbeVerified,true);
});
@@ -0,0 +1,44 @@
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { readFile } from 'node:fs/promises';
import { randomUUID, createHash } from 'node:crypto';
import test from 'node:test';
const require=createRequire(new URL('../../../package.json',import.meta.url)),ts=require('typescript'),{z}=require('zod');
const hash=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex'),sign=async(value,purpose)=>hash([purpose,value]);
const code=ts.transpileModule(await readFile(new URL('./routing-controller-baseline.ts',import.meta.url),'utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText;
const effectCode=ts.transpileModule(await readFile(new URL('./routing-original-effects.ts',import.meta.url),'utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText,effectExports={};
new Function('exports','require',effectCode)(effectExports,name=>{if(name==='node:net')return require('node:net');if(name==='zod')return{z};if(name==='./security.js')return{fingerprint:hash};if(name==='./routing-community-context-model.js')return{};throw Error(name);});
const preparedValue={status:'prepared',revision:1,serverId:1,routeSelectionScope:'table-afi-community-observation',candidateId:'b',family:'ipv4',table:'tenant',community:'65000:10',expectedGateway:'192.0.2.9',expectedImmediate:{address:'198.51.100.2',interface:'ether2'},binding:{connectionId:'*c',sessionId:'*s',instance:'default',table:'tenant',vrf:'main',localAddress:'192.0.2.1',remoteAddress:'192.0.2.2',remoteAsn:65001,afi:'ip'},bfd:{applicability:'not-configured',evidence:'explicit-use-bfd-false'},routes:[{nativeId:'*r',prefix:'203.0.113.0/24',originalGateway:'192.0.2.8'}],probe:{key:'b',serverId:1,destination:'198.51.100.20',source:'198.51.100.10',interface:'wan',probeVrf:'probe',expectedHops:['198.51.100.20']}};
async function fixture(selection='absent',effectStatus='observed-expected',switched=true){
const principal={connectionId:randomUUID(),credentialId:randomUUID(),credentialExpiresAt:new Date(Date.now()+120000).toISOString()},profileId=randomUUID(),planId=randomUUID(),operationId=randomUUID(),grant=new Date(Date.now()+60000).toISOString();
const profile={serverId:1,table:'tenant',probeVrf:'probe',candidates:[{id:'a',jumpHostId:1,exitNodeId:1},{id:'b',jumpHostId:1,exitNodeId:1}]},perms=['mm:network:read','mm:network:write','mm:filters:write'];
const connection={id:principal.connectionId,owner_id:'owner',owner_permissions:perms,owner_valid_until:new Date(Date.now()+120000),owner_claims_issued_at:new Date(Date.now()-1000),generation:'access',policy:{status:'active',mode:'copilot',permissions:perms,scope:{serverIds:[1],operationIds:['routing.analyze','routing.community.switch','routing.path.verify']},expiresAt:new Date(Date.now()+120000).toISOString()}};
const context={principal,connection},sourceProofs=[{serverId:1,proof:'c'.repeat(64)}],dependency='d'.repeat(64),originalTarget='a'.repeat(64),desiredTarget='b'.repeat(64),checkpointProof='e'.repeat(64),networkSeal='f'.repeat(64),requestSeal='1'.repeat(64);
let advancing=false,nativeReads=0,row;
const input={proofRevision:3,profile,principal,ownerId:'owner',nativeId:'*1',originalCandidateId:'a',originalTargetProof:originalTarget,desiredTargetProof:desiredTarget,dependencyProof:dependency,contextSources:sourceProofs,candidateId:'b',receiptId:randomUUID(),persistentProfileAuthority:{packet:{profileId,profileVersion:1}}};
if(selection!=='absent')input.originalEffectSelection=selection?.status==='prepared'&&Object.keys(selection).length===1?structuredClone(preparedValue):selection;
const packet={revision:2,operationId,stepIndex:0,nativeId:'*1',originalCandidateId:'a',originalTargetProof:originalTarget,dependencyProof:dependency,contextSources:sourceProofs,desiredCandidateId:'b',desiredTargetProof:desiredTarget,desiredRule:'desired'};
const network={status:'passed',networkSeal,...(effectStatus==='missing'?{}:{originalEffects:{status:effectStatus,switchEffectObserved:switched,selectionIdentity:hash(input.originalEffectSelection??null),transition:{changedPrefixes:switched?['203.0.113.0/24']:[]}}})};
const providers={
'./routing-main-transition.js':{},'../../db/index.js':{pool:{query:async()=>({rows:[row]})}},'./registry.js':{findMcpOperation:()=>({version:7})},
'./routing-community-scoped-request.js':{assertScopedCommunityRequest:async()=>requestSeal},'./routing-profile-authority.js':{assertPersistentScopedProfileAuthority:async()=>{},routingProfileAuthorityBindingSchema:{parse:x=>x}},
'./routing-community-scoped-checkpoint.js':{loadScopedConfigurationPacket:async()=>({packet,checkpointProof,observedAt:new Date(Date.now()-500).toISOString()}),loadScopedNetworkCheckpoint:async()=>network},
zod:{z},'@mmapp/contracts/mcp-routing':{routeAiProfileSchema:{parse:x=>x}},'../../lib/permissions.js':{hasPermission:(list,value)=>list.includes(value)},'./store.js':{effectiveMcpConnection:async()=>connection},
'./security.js':{accessGeneration:async()=>'access',constantEqual:(a,b)=>a===b,decryptSecret:async x=>x,fingerprint:hash,redact:x=>x,McpError:Error,privateSecretFingerprint:sign},
'./routing-community-context.js':{readRouteCommunityContext:async()=>{nativeReads++;return {before:{scopedExecutionAvailable:true,currentCandidateId:advancing?'b':'a',targetRuleProof:advancing?desiredTarget:originalTarget,dependencyProof:dependency,sourceProofs,nativeRuleId:'*1'},ownedTarget:{nativeId:'*1',rawRow:{rule:advancing?'desired':'original'}},fence:async()=>{}};}},
'./routing-original-effects.js':{originalEffectSelectionSchema:effectExports.originalEffectSelectionSchema}
};
const exports={};new Function('exports','require',code)(exports,name=>{assert.ok(providers[name],name);return providers[name];});
const baseline=await exports.prepareRoutingControllerBaseline(profileId,1,profile,grant,context);advancing=true;
const step={kind:'service',service:'routing.community.scoped',input};
row={id:operationId,plan_id:planId,state:'applied',step_state:'verified',operation_id:'routing.community.switch',operation_version:7,approved_by:'owner',created_at:new Date(Date.now()-1000),evidence:{sealed:JSON.stringify({steps:[step],target:{scope:'servers',serverIds:[1]}})},intended_state:step};
row.result={nativeVerificationSeal:await sign(hash({request:requestSeal,dependency,desired:desiredTarget,candidate:'b',configuration:checkpointProof,network:networkSeal}),'route-ai-scoped-verified-v3'),configurationCheckpointProof:checkpointProof,networkCheckpointProof:networkSeal,receiptId:input.receiptId,proofRevision:3,nativeWrites:1,configurationVerified:true,postProbeStatus:'passed',controlProbeVerified:true};
return {advance:()=>exports.advanceRoutingControllerBaseline(baseline,profileId,1,profile,grant,context,planId,operationId),nativeReads:()=>nativeReads,row,network};
}
test('actual controller preserves old advancement only when new selection field is absent',async()=>{const f=await fixture();assert.equal((await f.advance()).packet.candidateId,'b');assert.equal(f.nativeReads(),2);});
for(const [name,selection]of[['unknown',{status:'unknown',reason:'unsupported'}],['malformed',{}],['malformed prepared',{status:'prepared',revision:2}],['null',null],['array',[]]])test(`actual controller rejects present ${name} before desired native read`,async()=>{const f=await fixture(selection);await assert.rejects(f.advance);assert.equal(f.nativeReads(),1);assert.equal(f.row.result.configurationVerified,true);assert.equal(f.row.result.controlProbeVerified,true);});
for(const status of ['unknown','observed-other','missing'])test(`actual controller rejects prepared selection with ${status} durable effect result`,async()=>{const f=await fixture({status:'prepared'},status);await assert.rejects(f.advance);assert.equal(f.nativeReads(),1);});
test('actual controller rejects matched poststate without an observed selected FIB transition',async()=>{const f=await fixture({status:'prepared'},'observed-expected',false);await assert.rejects(f.advance);assert.equal(f.nativeReads(),1);});
test('actual controller advances after durable expected selected FIB transition',async()=>{const f=await fixture({status:'prepared'});assert.equal((await f.advance()).packet.candidateId,'b');assert.equal(f.nativeReads(),2);});
test('actual controller rejects effect vector belonging to a different prepared selection',async()=>{const f=await fixture({status:'prepared'});f.network.originalEffects.selectionIdentity='0'.repeat(64);await assert.rejects(f.advance);assert.equal(f.nativeReads(),1);});
test('actual controller rejects transition flag without an actual changed selected prefix',async()=>{const f=await fixture({status:'prepared'});f.network.originalEffects.transition.changedPrefixes=[];await assert.rejects(f.advance);assert.equal(f.nativeReads(),1);});
@@ -0,0 +1,51 @@
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
import { isIP } from 'node:net';
import { randomUUID, createHash } from 'node:crypto';
const require=createRequire(new URL('../../../package.json',import.meta.url)), ts=require('typescript'),{z}=require('zod');
const source=await readFile(new URL('./routing-original-effects.ts',import.meta.url),'utf8');
const exports={};
const gateway=value=>{const match=/^([^@%\s,]+)(?:%([^@%\s,]+))?(?:@([^@%\s,]+))?$/.exec(value);if(!match||!isIP(match[1]))throw Error();return {address:match[1],family:isIP(match[1]),interface:match[2]??null,resolverTable:match[3]??'main'};};
const providers={ 'node:net':{isIP},'node:crypto':{randomUUID},zod:{z},'./routing-community-context-model.js':{routeGatewayContext:gateway},'./security.js':{fingerprint:value=>createHash('sha256').update(JSON.stringify(value)).digest('hex')},
'./path-verification.js':{inspectControlPing:rows=>{const row=rows.at(-1),sent=Number(row.sent),received=Number(row.received);return {confirmedFailure:sent===3&&received===0,ping:{measured:sent===3&&Number.isInteger(received)&&received>=0&&received<=3,loss:(sent-received)/sent*100}};},
inspectTrace:(rows,destination,expected)=>({verified:Array.isArray(rows)&&rows.at(-1)?.address===destination&&expected.every(x=>rows.some(r=>r.address===x))})}};
new Function('exports','require',ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText)(exports,name=>{assert.ok(providers[name],name);return providers[name];});
function fixture(){
const profile={serverId:1,family:'ipv4',table:'tenant',probeVrf:'probe',community:'65000:10',candidates:[{id:'b',wanInterface:'wan',gateway:'192.0.2.9',gatewayRef:{resource:'ipv4_route',id:'*9'},controlProbe:{targetInterface:'target'}}]};
const binding={connectionId:'*c',instance:'default',table:'tenant',vrf:'main'};
const connection={'.id':'*c',instance:'default','routing-table':'tenant',vrf:'main','local.address':'192.0.2.1','remote.address':'192.0.2.2','remote.as':'65001',afi:'ip',templates:'',disabled:'false','use-bfd':'false'};
const session={...connection,'.id':'*s','local.afi':'ip','remote.afi':'ip',established:'true'};
const route={'.id':'*r',afi:'ip4','routing-table':'tenant','dst-address':'203.0.113.0/24',bgp:'true','bgp.communities':'65000:10',gateway:'192.0.2.8',active:'true',contribution:'active',ecmp:'false','immediate-gw':'198.51.100.1%ether1'};
const raw={connections:[connection],sessions:[session],routes:[route],candidate:{'.id':'*9','immediate-gw':'198.51.100.2%ether2'},probe:{source:'198.51.100.10',destination:'198.51.100.20',expected:['198.51.100.20']}};
const selection=exports.prepareOriginalEffectSelection(profile,'b',binding,raw);
const sample=()=>({observedAt:new Date().toISOString(),servers:[{serverId:1,routes:{payload:[{...route,gateway:'192.0.2.9','immediate-gw':'198.51.100.2%ether2'}]},bgp:{payload:[{...session}],connectionsPayload:[{...connection}]},logs:{payload:[{topics:'route,info',message:'bounded diagnostic'}]}}],probes:[{key:'b',pingPayload:[{sent:'3',received:'3','packet-loss':'0'}],tracePayload:[{address:'198.51.100.20'}]}]});
const baseline=sample();baseline.servers[0].routes.payload=structuredClone(raw.routes);
return {profile,binding,raw,selection,sample,baseline,prepare:()=>exports.prepareOriginalEffectSelection(profile,'b',binding,raw),inspect:samples=>exports.inspectOriginalEffectRounds(selection,samples,baseline)};
}
test('actual captured predicate seals BGP destinations separately from static resolver ID',()=>{const f=fixture();assert.equal(f.selection.status,'prepared');assert.equal(f.selection.routes[0].nativeId,'*r');assert.notEqual(f.selection.routes[0].nativeId,f.raw.candidate['.id']);assert.equal(f.selection.binding.sessionId,'*s');assert.equal('sessionKey'in f.selection.routes[0],false);});
test('two real effect captures can observe expected FIB/adjacency/probe while logcoverage and stronger claims stay false',async()=>{const f=fixture(),result=await f.inspect([f.sample(),f.sample()]);assert.equal(result.status,'observed-expected');assert.equal(result.logCoverage,'unknown');assert.equal(result.routeOriginVerified,false);assert.equal(result.recursiveChainVerified,false);assert.equal(result.forwardingVerified,false);assert.equal(result.autoRecoveryAllowed,false);assert.equal(result.rounds[0].relatedTopicRows,1);});
for(const [name,change]of[
['missing affected BGP route',f=>{f.raw.routes=[];}],['ambiguous prefix',f=>{f.raw.routes.push({...f.raw.routes[0],'.id':'*r2'});}],
['connected resolver is not affected BGP route',f=>{f.raw.routes[0].bgp='false';f.raw.routes[0].connect='true';}],
['unresolved inheritance',f=>{f.raw.connections[0].templates='template';}],['BFD not explicitly known',f=>{delete f.raw.connections[0]['use-bfd'];}],
['router-selected source retained as unsupported effect subset',f=>{delete f.raw.probe.source;}],['mixed session identity',f=>{f.raw.sessions.push({...f.raw.sessions[0],'.id':'*s2'});}],
['noncanonical AFI prefix',f=>{f.raw.routes[0]['dst-address']='2001:db8::/64';}]
])test(`prepared ambiguity/unsupported remains unknown: ${name}`,()=>{const f=fixture();change(f);assert.equal(f.prepare().status,'unknown');});
test('baseline binds saved route IDs/prefix/gateway and does not infer operation UUID',()=>{const f=fixture(),base=f.sample();base.servers[0].routes.payload=structuredClone(f.raw.routes);assert.equal(exports.originalEffectBaselineMatches(f.selection,base),true);base.servers[0].routes.payload[0]['.id']='*different';assert.equal(exports.originalEffectBaselineMatches(f.selection,base),false);});
for(const [name,change,expected]of[
['unexpected immediate next-hop',s=>{s.servers[0].routes.payload[0]['immediate-gw']='198.51.100.3%ether3';},'observed-other'],
['unchanged connected route',s=>{s.servers[0].routes.payload[0].bgp='false';s.servers[0].routes.payload[0].connect='true';},'unknown'],
['same prefix competition',s=>{s.servers[0].routes.payload.push({...s.servers[0].routes.payload[0],'.id':'*other'});},'unknown'],
['new community target scope',s=>{s.servers[0].routes.payload.push({...s.servers[0].routes.payload[0],'.id':'*other','dst-address':'203.0.114.0/24'});},'unknown'],
['reconnected session ID',s=>{s.servers[0].bgp.payload[0]['.id']='*new';},'unknown'],
['observed adjacency down',s=>{s.servers[0].bgp.payload[0].established='false';},'observed-other'],
['BFD applicability changed',s=>{s.servers[0].bgp.connectionsPayload[0]['use-bfd']='true';},'unknown'],
['unreachable route',s=>{s.servers[0].routes.payload[0].unreachable='true';},'unknown'],
['terminal control loss',s=>{s.probes[0].pingPayload=[{sent:'3',received:'0','packet-loss':'100'}];},'observed-other']
])test(`effect observation: ${name}`,async()=>{const f=fixture(),s=f.sample();change(s);const result=await f.inspect([s,structuredClone(s)]);assert.equal(result.status,expected);assert.equal(result.forwardingVerified,false);});
test('one round is not upgraded to two',async()=>{const f=fixture();assert.equal((await f.inspect([f.sample()])).status,'unknown');});
test('unchanged expected poststate is not labelled a switch effect',async()=>{const f=fixture();f.baseline.servers[0].routes.payload[0].gateway=f.selection.expectedGateway;f.baseline.servers[0].routes.payload[0]['immediate-gw']='198.51.100.2%ether2';f.selection.routes[0].originalGateway=f.selection.expectedGateway;const result=await f.inspect([f.sample(),f.sample()]);assert.equal(result.status,'unknown');assert.equal(result.switchEffectObserved,false);assert.deepEqual(result.transition.changedPrefixes,[]);assert.equal(result.reason,'poststate-matched-without-selected-fib-transition');});
test('actual selected destination gateway/final endpoint transition is recorded separately from origin',async()=>{const f=fixture(),result=await f.inspect([f.sample(),f.sample()]);assert.equal(result.status,'observed-expected');assert.equal(result.switchEffectObserved,true);assert.deepEqual(result.transition.changedPrefixes,['203.0.113.0/24']);assert.equal(result.transition.baselineObservedAt,f.baseline.observedAt);assert.equal(result.routeOriginVerified,false);});
test('logical gateway change with unchanged final forwarding endpoint is not a FIB switch',async()=>{const f=fixture();f.baseline.servers[0].routes.payload[0]['immediate-gw']='198.51.100.2%ether2';const result=await f.inspect([f.sample(),f.sample()]);assert.equal(result.status,'unknown');assert.equal(result.switchEffectObserved,false);assert.equal(result.routeSelectionScope,'table-afi-community-observation');assert.equal(result.transitionAttribution,'unproven');assert.deepEqual(result.transition.changedPrefixes,[]);});
@@ -0,0 +1,174 @@
import { isIP } from "node:net"
import { z } from "zod"
import type { RouteAiProfile } from "@mmapp/contracts/mcp-routing"
import { routeGatewayContext } from "./routing-community-context-model.js"
import { fingerprint } from "./security.js"
type Row = Record<string, unknown>
const id = z.string().regex(/^\*[a-z0-9]+$/i).max(100)
const text = z.string().min(1).max(200)
const ip = text.refine(value => isIP(value) !== 0)
const flag = (value: unknown): boolean | undefined => [true, "true", "yes"].includes(value as string | boolean) ? true : [false, "false", "no"].includes(value as string | boolean) ? false : undefined
const communities = (row: Row) => Array.isArray(row["bgp.communities"]) ? row["bgp.communities"] as unknown[] : typeof row["bgp.communities"] === "string" ? row["bgp.communities"].split(",") : []
const family = (row: Row) => ["ip", "ip4"].includes(String(row.afi)) ? "ipv4" : ["ipv6", "ip6"].includes(String(row.afi)) ? "ipv6" : null
const unique = (rows: Row[], nativeId: string) => rows.filter(row => row[".id"] === nativeId)
const tupleSchema = z.object({ connectionId: id, sessionId: id, instance: text, table: text, vrf: text,
localAddress: ip, remoteAddress: ip, remoteAsn: z.number().int().positive().max(4294967295), afi: z.enum(["ip", "ipv6"]) }).strict()
const prepared = z.object({ status: z.literal("prepared"), revision: z.literal(1), serverId: z.number().int().positive(),
routeSelectionScope: z.literal("table-afi-community-observation"),
candidateId: text, family: z.enum(["ipv4", "ipv6"]), table: text, community: text, expectedGateway: text,
expectedImmediate: z.object({ address: ip, interface: text }).strict(), binding: tupleSchema,
bfd: z.object({ applicability: z.literal("not-configured"), evidence: z.literal("explicit-use-bfd-false") }).strict(),
routes: z.array(z.object({ nativeId: id, prefix: text, originalGateway: text }).strict()).min(1).max(32),
probe: z.object({ key: text, serverId: z.number().int().positive(), destination: ip, source: ip, interface: text, probeVrf: text,
expectedHops: z.array(ip).min(1).max(8) }).strict(),
}).strict()
export const originalEffectSelectionSchema = z.union([prepared, z.object({ status: z.literal("unknown"), reason: text }).strict()])
export type OriginalEffectSelection = z.infer<typeof originalEffectSelectionSchema>
type Prepared = z.infer<typeof prepared>
const state = z.enum(["observed-expected", "observed-other", "unknown"])
const roundSchema = z.object({ observedAt: z.string().datetime(), routes: z.array(z.object({ prefix: text, status: state }).strict()).max(32),
bgp: state, bfd: z.enum(["not-configured", "unknown"]), controlProbe: z.enum(["passed", "failed", "unknown"]),
relatedTopicRows: z.number().int().nonnegative().max(1000) }).strict()
export const originalEffectReportSchema = z.object({ scope: z.literal("prepared-route-fib-adjacency-control-effects"), status: state,
reason: text.nullable(), rounds: z.array(roundSchema).max(3), logSource: z.literal("routeros-rest-ring"), logCoverage: z.literal("unknown"),
selectionIdentity: z.string().regex(/^[a-f0-9]{64}$/).nullable(),
routeSelectionScope: z.literal("table-afi-community-observation"), transitionAttribution: z.literal("unproven"),
switchEffectObserved: z.boolean(),
transition: z.object({ baselineObservedAt: z.string().datetime(), changedPrefixes: z.array(text).max(32) }).strict().optional(),
budget: z.object({ deadline: z.number().int().positive(), calls: z.number().int().nonnegative().max(256), bytes: z.number().int().nonnegative().max(8 * 1048576),
rows: z.number().int().nonnegative().max(10000) }).strict().optional(),
semanticProof: z.literal(false), routeOriginVerified: z.literal(false), recursiveChainVerified: z.literal(false), forwardingVerified: z.literal(false), autoRecoveryAllowed: z.literal(false) }).strict()
export type OriginalEffectReport = z.infer<typeof originalEffectReportSchema>
export const unknownOriginalEffects = (reason: string): OriginalEffectReport => ({ scope: "prepared-route-fib-adjacency-control-effects", status: "unknown", reason,
rounds: [], logSource: "routeros-rest-ring", logCoverage: "unknown", selectionIdentity: null, routeSelectionScope: "table-afi-community-observation", transitionAttribution: "unproven", switchEffectObserved: false, semanticProof: false, routeOriginVerified: false, recursiveChainVerified: false, forwardingVerified: false, autoRecoveryAllowed: false })
function tuple(row: Row, expected: Prepared["binding"], session: boolean) {
return row.instance === expected.instance && row["routing-table"] === expected.table && row.vrf === expected.vrf &&
row["local.address"] === expected.localAddress && row["remote.address"] === expected.remoteAddress && Number(row["remote.as"]) === expected.remoteAsn &&
(session ? row["local.afi"] === expected.afi && row["remote.afi"] === expected.afi : row.afi === expected.afi)
}
function selectedRows(rows: Row[], selection: Pick<Prepared, "table" | "family" | "community">) {
return rows.filter(row => row["routing-table"] === selection.table && family(row) === selection.family && flag(row.bgp) === true && communities(row).includes(selection.community))
}
/** Called only by the real private context reader. This selects observed route
* effects by table/AFI/Community, never a route→session provenance relationship.
* The initial bounded subset requires explicit flat peer identity, explicit
* BFD-off and an already configured explicit probe source. Other manual paths
* keep their existing configuration/probe behavior, with effects unknown. */
export function prepareOriginalEffectSelection(profile: RouteAiProfile, candidateId: string, binding: { connectionId: string; instance: string | null; vrf: string; table: string } | null,
raw: { routes: Row[]; connections: Row[]; sessions: Row[]; candidate: Row | undefined; probe: { destination: string; expected: string[]; source?: string } | undefined }): OriginalEffectSelection {
try {
if (!binding || !binding.instance) throw Error()
const candidate = profile.candidates.find(value => value.id === candidateId), probe = raw.probe
if (!candidate?.controlProbe || !probe?.source || !raw.candidate) throw Error()
const connection = unique(raw.connections, binding.connectionId)
if (connection.length !== 1 || !(connection[0].templates === "" || Array.isArray(connection[0].templates) && connection[0].templates.length === 0) ||
flag(connection[0].disabled) !== false || flag(connection[0]["use-bfd"]) !== false) throw Error()
const peer = connection[0], afi = profile.family === "ipv4" ? "ip" : "ipv6"
const wanted = tupleSchema.parse({ connectionId: peer[".id"], sessionId: "*0", instance: binding.instance, table: binding.table, vrf: binding.vrf,
localAddress: peer["local.address"], remoteAddress: peer["remote.address"], remoteAsn: Number(peer["remote.as"]), afi })
if (!tuple(peer, wanted, false)) throw Error()
const sessions = raw.sessions.filter(row => tuple(row, wanted, true))
if (sessions.length !== 1 || flag(sessions[0].established) === undefined) throw Error()
wanted.sessionId = id.parse(sessions[0][".id"])
const affected = selectedRows(raw.routes, profile)
if (!affected.length || affected.length > 32 || new Set(affected.map(row => row["dst-address"])).size !== affected.length || new Set(affected.map(row => row[".id"])).size !== affected.length) throw Error()
for (const row of affected) {
const [address, mask, extra] = String(row["dst-address"]).split("/")
if (extra !== undefined || isIP(address) !== (profile.family === "ipv4" ? 4 : 6) || !/^\d+$/.test(mask ?? "") || Number(mask) > (profile.family === "ipv4" ? 32 : 128)) throw Error()
routeGatewayContext(String(row.gateway))
}
// A selected destination cannot silently choose among competing origins.
if (affected.some(row => raw.routes.filter(other => other["routing-table"] === profile.table && other["dst-address"] === row["dst-address"]).length !== 1)) throw Error()
const endpoint = candidate.gatewayRef.resource === "gre" ? { address: routeGatewayContext(candidate.gateway).address, interface: String(raw.candidate.name) } : routeGatewayContext(String(raw.candidate["immediate-gw"]))
if (!endpoint.interface) throw Error()
return prepared.parse({ status: "prepared", revision: 1, serverId: profile.serverId, candidateId, family: profile.family, table: profile.table,
routeSelectionScope: "table-afi-community-observation",
community: profile.community, expectedGateway: candidate.gateway, expectedImmediate: { address: endpoint.address, interface: endpoint.interface }, binding: wanted,
bfd: { applicability: "not-configured", evidence: "explicit-use-bfd-false" },
routes: affected.map(row => ({ nativeId: row[".id"], prefix: row["dst-address"], originalGateway: row.gateway })).sort((a, b) => String(a.prefix).localeCompare(String(b.prefix))),
probe: { key: candidateId, serverId: profile.serverId, destination: probe.destination, source: probe.source, interface: candidate.wanInterface,
probeVrf: profile.probeVrf, expectedHops: probe.expected } })
} catch { return { status: "unknown", reason: "prepared-effect-selection-unsupported-or-ambiguous" } }
}
type Snapshot = { observedAt: string; servers: Array<{ serverId: number; routes: { payload: Row[] }; bgp: { payload: Row[]; connectionsPayload: Row[] }; logs: { payload: Row[] } }>;
probes: Array<{ key: string; pingPayload: Row[]; tracePayload?: Row[] }> }
export function originalEffectBaselineMatches(selection: Prepared, sample: Snapshot) {
const source = sample.servers.find(row => row.serverId === selection.serverId)
if (!source) return false
const actual = selectedRows(source.routes.payload, selection)
return actual.length === selection.routes.length && selection.routes.every(wanted => actual.filter(row => row[".id"] === wanted.nativeId && row["dst-address"] === wanted.prefix && row.gateway === wanted.originalGateway).length === 1)
}
/** Actual bounded native captures only; all stronger causal/whole-path claims
* remain false. Related log inspection is reported without completeness. */
export async function inspectOriginalEffectRounds(rawSelection: unknown, samples: Snapshot[], baseline: Snapshot) {
const selection = prepared.parse(rawSelection), rounds: z.infer<typeof roundSchema>[] = []
if (!originalEffectBaselineMatches(selection, baseline)) return unknownOriginalEffects("prepared-effect-baseline-drift")
const { inspectControlPing, inspectTrace } = await import("./path-verification.js")
for (const sample of samples) {
const source = sample.servers.find(row => row.serverId === selection.serverId)
if (!source) return unknownOriginalEffects("effect-source-missing")
const scope = selectedRows(source.routes.payload, selection)
const scopeChanged = fingerprint(scope.map(row => row["dst-address"]).sort()) !== fingerprint(selection.routes.map(row => row.prefix).sort())
const routes = selection.routes.map(wanted => {
const matching = source.routes.payload.filter(row => row["routing-table"] === selection.table && row["dst-address"] === wanted.prefix && family(row) === selection.family)
let status: z.infer<typeof state> = "unknown"
if (!scopeChanged && matching.length === 1) {
const row = matching[0]
if (flag(row.bgp) === true && flag(row.connect) !== true && flag(row.static) !== true && flag(row.active) !== undefined && row.ecmp !== undefined && flag(row.ecmp) === false &&
["disabled", "filtered", "unreachable", "blackhole", "invalid"].every(key => row[key] === undefined || flag(row[key]) === false)) {
try {
const final = routeGatewayContext(String(row["immediate-gw"]))
status = flag(row.active) === true && row.contribution === "active" && row.gateway === selection.expectedGateway && final.address === selection.expectedImmediate.address && final.interface === selection.expectedImmediate.interface ? "observed-expected" : "observed-other"
} catch { /* no final resolution evidence */ }
}
}
return { prefix: wanted.prefix, status }
})
const connections = unique(source.bgp.connectionsPayload, selection.binding.connectionId)
const sessions = source.bgp.payload.filter(row => tuple(row, selection.binding, true))
let bgp: z.infer<typeof state> = "unknown"
if (connections.length === 1 && tuple(connections[0], selection.binding, false) && flag(connections[0].disabled) === false && flag(connections[0]["use-bfd"]) === false &&
sessions.length === 1 && sessions[0][".id"] === selection.binding.sessionId && flag(sessions[0].established) !== undefined) bgp = flag(sessions[0].established) ? "observed-expected" : "observed-other"
const probe = sample.probes.find(row => row.key === selection.probe.key)
let controlProbe: "passed" | "failed" | "unknown" = "unknown"
if (probe) {
const ping = inspectControlPing(z.array(z.record(z.string(), z.string())).max(256).parse(probe.pingPayload))
if (ping.confirmedFailure) controlProbe = "failed"
else if (ping.ping.measured && ping.ping.loss === 0 && inspectTrace(probe.tracePayload, selection.probe.destination, selection.probe.expectedHops).verified) controlProbe = "passed"
}
rounds.push(roundSchema.parse({ observedAt: sample.observedAt, routes, bgp,
bfd: connections.length === 1 && flag(connections[0]["use-bfd"]) === false ? "not-configured" : "unknown", controlProbe,
relatedTopicRows: source.logs.payload.filter(row => typeof row.topics === "string" && row.topics.split(",").some(topic => ["bgp", "bfd", "route", "interface"].includes(topic))).length }))
}
if (rounds.length !== 2) return unknownOriginalEffects("two-effect-rounds-required")
const all = rounds.flatMap(round => [...round.routes.map(route => route.status), round.bgp, round.bfd === "unknown" ? "unknown" : "observed-expected", round.controlProbe === "passed" ? "observed-expected" : round.controlProbe === "failed" ? "observed-other" : "unknown"])
const before = baseline.servers.find(row => row.serverId === selection.serverId)!
// Native IDs alone may churn; they never count as a forwarding transition.
// Require an actual selected active destination's final forwarding endpoint
// to differ from the expected post-state seen consistently in both rounds.
// A changed logical gateway resolving to the SAME endpoint is not a FIB switch.
const changedPrefixes = selection.routes.filter(wanted => {
if (!rounds.every(round => round.routes.find(row => row.prefix === wanted.prefix)?.status === "observed-expected")) return false
const row = before.routes.payload.find(row => row[".id"] === wanted.nativeId && row["dst-address"] === wanted.prefix)!
if (flag(row.bgp) !== true || flag(row.active) !== true || row.contribution !== "active" || flag(row.connect) === true || flag(row.static) === true || flag(row.ecmp) !== false ||
["disabled", "filtered", "unreachable", "blackhole", "invalid"].some(key => row[key] !== undefined && flag(row[key]) !== false)) return false
try {
const endpoint = routeGatewayContext(String(row["immediate-gw"]))
routeGatewayContext(String(row.gateway))
if (!endpoint.interface) return false
return endpoint.address !== selection.expectedImmediate.address || endpoint.interface !== selection.expectedImmediate.interface
} catch { return false }
}).map(row => row.prefix)
const postState = all.includes("unknown") ? "unknown" : all.includes("observed-other") ? "observed-other" : "observed-expected"
return originalEffectReportSchema.parse({ ...unknownOriginalEffects(""), rounds, reason: null,
selectionIdentity: fingerprint(selection),
transition: { baselineObservedAt: baseline.observedAt, changedPrefixes }, switchEffectObserved: postState === "observed-expected" && changedPrefixes.length > 0,
status: postState === "observed-expected" && !changedPrefixes.length ? "unknown" : postState,
...(postState === "observed-expected" && !changedPrefixes.length ? { reason: "poststate-matched-without-selected-fib-transition" } : {}) })
}
@@ -1,9 +1,23 @@
import { assertOwnedRoutingReservationSql, acceptRoutingReservationSql } from "./routing-reservation.sql-helper.js"
assertOwnedRoutingReservationSql()
process.env.AUTH_REQUIRED = "true"
process.env.AUTH_JWT_SECRET = "owned-routing-mixed-writer-acceptance-secret"
process.env.AUTH_ISSUER = "https://owned-routing-mixed-writer.test"
const { pool, closePool } = await import("../../db/index.js")
try {
const { applySqlMigrations } = await import("../../db/migrate.js")
await applySqlMigrations(pool)
console.log("private routing reservation runtime admission SQL acceptance:", JSON.stringify(await acceptRoutingReservationSql({ runtimeAdmission: true })))
} finally { await closePool() }
const actor = { id: "owned-routing-mixed-writer", email: "routing-mixed@example.invalid", name: "Owned mixed writer",
apps: ["mm"], permissions: ["mm:settings:admin", "mm:mcp:admin", "mm:mcp:approve", "mm:network:read", "mm:network:write", "mm:filters:write", "mm:servers:admin"],
issuedAt: Date.now() - 1000, expiresAt: Date.now() + 900000 }
await (await import("./store.js")).updateMcpSettings({ enabled: true }, actor)
await import("./actions.js")
const mixed = await (await import("./routing-community-scoped-acceptance.js")).acceptScopedRouteCommunitySwitch(actor,
{ manualInverseChargeOnly: true, manualInverseChargeCase: "mixed-forward" })
console.log("private routing mixed forward/manual inverse admission SQL acceptance:", JSON.stringify(mixed))
} finally {
await (await import("../../services/scheduler.js")).stopSchedulerForDatabaseOperation()
await closePool()
}
@@ -0,0 +1,138 @@
import assert from "node:assert/strict"
import { test } from "node:test"
import Fastify, { type FastifyInstance, type FastifyPluginAsync } from "fastify"
import { readFile } from "node:fs/promises"
import { runInNewContext } from "node:vm"
import * as crypto from "node:crypto"
import { z } from "zod"
import ts from "typescript"
import { DatabaseRequestGate } from "../services/database-request-gate.js"
import * as operation from "../services/system-database-operation.js"
// Real Fastify + plugin and shared gate/readiness wiring; SQL is inert and
// counted. No configured pool, credentials, native process or network is opened.
async function fixture() {
async function load<T extends object>(file: URL, modules: Record<string, unknown>): Promise<T> {
const exports: Record<string, unknown> = {}
const compiled = ts.transpileModule(await readFile(file, "utf8"), { 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] }, URL, URLSearchParams, Buffer, Date, Promise, Error, process: { env: {} } })
return exports as T
}
const shared = await load<{ sharedDatabaseGate: DatabaseRequestGate; databaseSchemaReadiness: ReturnType<typeof operation.createDatabaseSchemaReadiness> }>(new URL("../services/shared-database-gate.ts", import.meta.url), {
"./database-request-gate.js": { DatabaseRequestGate }, "./system-database-operation.js": operation,
})
const gate = shared.sharedDatabaseGate as DatabaseRequestGate
const readiness = shared.databaseSchemaReadiness as ReturnType<typeof operation.createDatabaseSchemaReadiness>
let admissions = 0, sqlCalls = 0, transactions = 0, audits = 0
let holdClientRead: (() => Promise<void>) | undefined
const events: string[] = [], resource = "http://localhost:8000/mcp", generation = "current-owned-generation"
const query = async (sql: string) => {
sqlCalls++; events.push(sql.startsWith("INSERT INTO mcp_credentials") ? "credential-write" : "sql")
if (sql.startsWith("SELECT id FROM mcp_oauth_clients")) {
await holdClientRead?.(); return { rows: [{ id: "owned-client" }] }
}
if (sql.startsWith("SELECT * FROM mcp_oauth_clients")) return { rows: [{ id: "owned-client", redirect_uris: ["http://localhost/callback"] }] }
if (sql.startsWith("SELECT * FROM mcp_credentials")) return { rows: [{ id: "refresh-id", client_id: "owned-client", audience: resource, expires_at: new Date(Date.now() + 600000), revoked_at: null, connection_id: "connection-id", family_id: "family-id" }] }
if (sql.startsWith("SELECT * FROM mcp_oauth_clients ORDER")) return { rows: [] }
return { rows: [], rowCount: 1 }
}
const request = gate.request.bind(gate)
Object.defineProperty(gate, "request", { value: function<T>(run: () => Promise<T>, cancel?: () => void) { admissions++; return request(run, cancel) } })
class McpError extends Error { constructor(readonly code: string, message: string, readonly statusCode = 403) { super(message) } }
const oauth = await load<{ mcpOauth: FastifyPluginAsync }>(new URL("./mcp-oauth.ts", import.meta.url), {
"node:crypto": crypto, zod: { z }, "../db/index.js": { pool: { query } },
"../services/shared-database-gate.js": shared,
"../config.js": { env: { authRequired: true, CORS_ORIGIN: "http://localhost:3000" } },
"../plugins/auth.js": { requireAuth: async (req: { authUser?: { id: string } }) => { req.authUser = { id: "owned-admin" } } },
"../modules/mcp/store.js": {
audit: async () => { audits++ }, getConnection: async () => ({ generation, policy: { status: "active", expiresAt: new Date(Date.now() + 600000).toISOString() }, owner_valid_until: new Date(Date.now() + 600000) }),
transaction: async (work: (db: { query: typeof query }) => Promise<unknown>) => { transactions++; events.push("transaction-start"); const result = await work({ query }); events.push("transaction-settled"); return result },
},
"../modules/mcp/security.js": { McpError, accessGeneration: async () => generation, constantEqual: (a: string, b: string) => a === b, randomToken: (kind: string) => kind + "-owned-inert", tokenDigest: (token: string) => crypto.createHash("sha256").update(token).digest("hex") },
"./mcp-transport.js": { mcpPublicUrl: () => new URL(resource) },
"../modules/mcp/portal-revocation.js": { assertPortalClaimsCurrent: async () => {} },
})
const api = await load<{ installDatabaseRequestGate(app: FastifyInstance): void }>(new URL("../plugins/database-request-gate.ts", import.meta.url), {
"../services/shared-database-gate.js": shared,
"../services/config-mutation-policy.js": { withConfigurationActor: (_actor: string, work: () => unknown) => work() },
})
const app = Fastify()
api.installDatabaseRequestGate(app)
await app.register(oauth.mcpOauth)
const publicRequests = () => [
{ method: "GET" as const, url: "/oauth/authorize?" + new URLSearchParams({ client_id: "owned-client", redirect_uri: "http://localhost/callback", response_type: "code", code_challenge: "a".repeat(43), code_challenge_method: "S256", resource }).toString() },
{ method: "POST" as const, url: "/oauth/token", payload: { grant_type: "refresh_token", client_id: "owned-client", resource, refresh_token: "owned-refresh" } },
{ method: "POST" as const, url: "/oauth/revoke", payload: { token: "owned-refresh", client_id: "owned-client" } },
]
return { app, gate, readiness, events, publicRequests, counts: () => ({ admissions, sqlCalls, transactions, audits }), hold: (work?: () => Promise<void>) => { holdClientRead = work } }
}
function deferred() { let resolve!: () => void; const promise = new Promise<void>(done => { resolve = done }); return { promise, resolve } }
async function unavailable(f: Awaited<ReturnType<typeof fixture>>) {
const before = f.counts()
for (const options of f.publicRequests()) {
const response = await f.app.inject(options)
assert.equal(response.statusCode, 503); assert.deepEqual(response.json(), { error: "temporarily_unavailable" }); assert.equal(response.headers["cache-control"], "no-store")
}
assert.equal(f.counts().sqlCalls, before.sqlCalls); assert.equal(f.counts().transactions, before.transactions); assert.equal(f.counts().audits, before.audits)
}
test("public OAuth authorize/token/revoke refuse before SQL while exclusive maintenance runs", async () => {
const f = await fixture(), entered = deferred(), release = deferred()
const maintenance = f.gate.exclusive(async () => { entered.resolve(); await release.promise })
try { await entered.promise; await unavailable(f) }
finally { release.resolve(); await maintenance; await f.app.close() }
})
test("in-flight actual token transaction settles before exclusive replacement begins", async () => {
const f = await fixture(), entered = deferred(), release = deferred()
f.hold(async () => { entered.resolve(); await release.promise })
const token = f.app.inject(f.publicRequests()[1]).then(response => response)
await entered.promise
let replacements = 0
const maintenance = f.gate.exclusive(async () => { replacements++; f.events.push("replacement"); f.readiness.beginReplacement() })
try {
await unavailable(f); assert.equal(replacements, 0)
release.resolve(); const response = await token; assert.equal(response.statusCode, 200); assert.equal(response.json().access_token, "access-owned-inert")
await maintenance; assert.equal(replacements, 1)
assert(f.events.indexOf("transaction-settled") < f.events.indexOf("replacement")); assert.equal(f.events.filter(event => event === "credential-write").length, 2)
await unavailable(f)
} finally { release.resolve(); await token; await maintenance; await f.app.close() }
})
test("sticky replacement refusal survives exclusive finally and only explicit repair reopens OAuth SQL", async () => {
const f = await fixture()
try {
await f.gate.exclusive(async () => { f.readiness.beginReplacement() })
await unavailable(f)
await f.gate.exclusive(async () => {})
await unavailable(f)
await f.gate.exclusive(async () => { f.readiness.confirmSchema() })
const responses = []
for (const options of f.publicRequests()) responses.push(await f.app.inject(options))
assert.deepEqual(responses.map(response => response.statusCode), [302, 200, 200]); assert(f.counts().sqlCalls > 0)
} finally { await f.app.close() }
})
test("both pure discovery endpoints remain200 with no SQL or admission while maintenance is sticky-closed", async () => {
const f = await fixture()
try {
f.readiness.beginReplacement(); const before = f.counts()
for (const url of ["/.well-known/oauth-protected-resource", "/.well-known/oauth-authorization-server"]) assert.equal((await f.app.inject(url)).statusCode, 200)
assert.deepEqual(f.counts(), before)
} finally { await f.app.close() }
})
test("public OAuth and existing API admin handlers acquire exactly one gate admission", async () => {
const f = await fixture()
try {
const before = f.counts().admissions
assert.equal((await f.app.inject(f.publicRequests()[0])).statusCode, 302)
assert.equal(f.counts().admissions - before, 1)
const middle = f.counts().admissions
assert.equal((await f.app.inject("/api/mcp/oauth/clients")).statusCode, 200)
assert.equal(f.counts().admissions - middle, 1)
} finally { await f.app.close() }
})
test("ordinary invalid OAuth input preserves its existing protocol error and no-store behavior", async () => {
const f = await fixture()
try {
const response = await f.app.inject({ method: "POST", url: "/oauth/token", payload: {} })
assert.equal(response.statusCode, 400); assert.deepEqual(response.json(), { error: "invalid_request" }); assert.equal(response.headers["cache-control"], "no-store"); assert.equal(f.counts().sqlCalls, 0)
} finally { await f.app.close() }
})
+10
View File
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto"
import type { FastifyPluginAsync } from "fastify"
import { z } from "zod"
import { pool } from "../db/index.js"
import { sharedDatabaseGate } from "../services/shared-database-gate.js"
import { env } from "../config.js"
import { requireAuth } from "../plugins/auth.js"
import { audit, getConnection, transaction } from "../modules/mcp/store.js"
@@ -18,6 +19,15 @@ export function verifyPkce(verifier: string, challenge: string): boolean {
return /^[A-Za-z0-9._~-]{43,128}$/.test(verifier) && constantEqual(createHash("sha256").update(verifier).digest("base64url"), challenge)
}
export const mcpOauth: FastifyPluginAsync = async app => {
// Public OAuth SQL is outside /api: own its full handler admission here.
// Pure discovery stays available; /api consent/admin use the API gate.
app.addHook("onRoute", options => {
if (!options.url.startsWith("/oauth/")) return
const original = options.handler
options.handler = async function (request, reply) {
return sharedDatabaseGate.request(async () => await original.call(this, request, reply))
}
})
app.setErrorHandler((error, _request, reply) => {
reply.header("Cache-Control", "no-store")
if (error instanceof z.ZodError) return reply.code(400).send({ error: "invalid_request" })
+2 -1
View File
@@ -22,7 +22,7 @@ import { routeAiProfileSchema } from "@mmapp/contracts/mcp-routing"
import { revalidateMcpOwner } from "../modules/mcp/store.js"
import { resourceById } from "../modules/mcp/resources.js"
import { registerManagementChannel } from "../modules/mcp/management.js"
import { MAX_RESTORE_ARCHIVE_BYTES, uploadRestoreArchive, readRestoreReceipt } from "../modules/mcp/database-restore.js"
import { MAX_RESTORE_ARCHIVE_BYTES, uploadRestoreArchive, readRestoreReceipt, readRestoreReceiptAuditStatus } from "../modules/mcp/database-restore.js"
import { assertPortalClaimsCurrent } from "../modules/mcp/portal-revocation.js"
import { readFile } from "node:fs/promises"
import { checkMcpProtocol, type ProtocolProbe } from "../modules/mcp/readiness.js"
@@ -102,6 +102,7 @@ export function createMcpRoutes(checkClaims = assertPortalClaimsCurrent, options
app.post("/mcp/database/archives", { bodyLimit: MAX_RESTORE_ARCHIVE_BYTES }, async request => uploadRestoreArchive(request.body as Buffer, actorOf(request.authUser)))
app.get("/mcp/database/receipts/:id", async request => readRestoreReceipt(idOf(request.params), actorOf(request.authUser)))
app.get("/mcp/database/receipts/:id/audit-status", async request => readRestoreReceiptAuditStatus(idOf(request.params), actorOf(request.authUser)))
app.post("/mcp/database/plan", async request => {
const { connectionId, archiveRef, expectedSha256 } = z.object({ connectionId: uuid, archiveRef: uuid, expectedSha256: z.string().regex(/^[a-f0-9]{64}$/) }).strict().parse(request.body)
const actor = actorOf(request.authUser)
@@ -0,0 +1,107 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import path from 'node:path'
import {mkdtemp,mkdir,writeFile,readFile,appendFile,rm,lstat,chmod,symlink} from 'node:fs/promises'
import * as filesystem from 'node:fs/promises'
import {fileURLToPath} from 'node:url'
import {createRequire} from 'node:module'
import {randomUUID,createHash} from 'node:crypto'
import {runInNewContext} from 'node:vm'
import {parseMaintenanceJournal,evaluateMaintenanceAcceptances,runtimeDatabaseTargetSha256,MAINTENANCE_ACCEPTANCE_FORMAT,type MaintenanceAcceptance} from './database-maintenance-resolution.js'
import {inspectDatabaseMaintenanceStartup,readMaintenanceRecoveryFacts} from './database-maintenance-startup.js'
const here=path.dirname(fileURLToPath(import.meta.url)),backend=path.resolve(here,'../..'),temp=path.resolve(backend,'../tmp'),require=createRequire(import.meta.url),ts=require('typescript'),digest=(v:Buffer|string)=>createHash('sha256').update(v).digest('hex')
const target='postgresql://service:old-password@db.example.invalid:5432/managed?sslmode=require'
const record=(operationId:string,state:string)=>JSON.stringify({operationId,state,at:'2026-11-01T00:00:00.000Z'})+'\n'
const source={systemIdentifier:'1234567890',databaseOid:'12345',databaseName:'managed',serverVersionNum:180004,inRecovery:false as const}
const knownCurrent={transport:{mode:'tls' as const,endpointSha256:'f'.repeat(64)},source,schemaSha256:'a'.repeat(64),rawSchemaSha256:'b'.repeat(64),physicalSchemaSha256:'c'.repeat(64),manifestSha256:'d'.repeat(64),markersSha256:'e'.repeat(64)}
async function own(work:(root:string,uncertain:string,applied:string)=>Promise<void>){await mkdir(temp,{recursive:true});const root=await mkdtemp(path.join(temp,'owned-maintenance-resolution-'));try{
await mkdir(path.join(root,'restore-vault'));await writeFile(path.join(root,'access-generation'),Buffer.alloc(32,7));const uncertain=randomUUID(),applied=randomUUID()
await writeFile(path.join(root,'database-maintenance.jsonl'),record(uncertain,'intent')+record(uncertain,'unconfirmed')+record(applied,'intent')+record(applied,'applied'))
for(const [id,state]of [[uncertain,'unknown'],[applied,'applied']]){const archiveRef=randomUUID(),at='2026-11-01T00:00:00.000Z';await writeFile(path.join(root,'restore-vault',archiveRef+'.used'),JSON.stringify({operationId:id,at}));await writeFile(path.join(root,'restore-vault',id+'.receipt.json'),JSON.stringify({operationId:id,archiveRef,state,startedAt:at,automaticRetry:false,...(state==='applied'?{finishedAt:at,accessRevoked:true,warnings:[]}:{errorCode:'RESTORE_UNCONFIRMED',finishedAt:at})}))}
await work(root,uncertain,applied)
}finally{assert(path.dirname(root)===temp&&path.basename(root).startsWith('owned-maintenance-resolution-'));await rm(root,{recursive:true,force:true})}}
export async function modeledStartup(){
// This is deliberately a MODELED POSIX ownership adapter over actual owned
// files on Windows, not evidence that Windows/Linux protection was accepted.
const sourceText=await readFile(path.join(backend,'src/services/database-maintenance-startup.ts'),'utf8'),exports:Record<string,unknown>={}
const modeledStat=(stat:Awaited<ReturnType<typeof lstat>>)=>new Proxy(stat,{get(object,key){if(key==='uid')return typeof object.uid==='bigint'?0n:0;if(key==='mode')return typeof object.mode==='bigint'?(object.mode&~0o777n)|(object.isDirectory()?0o700n:0o600n):(object.mode&~0o777)|(object.isDirectory()?0o700:0o600);const value=Reflect.get(object,key);return typeof value==='function'?value.bind(object):value}})
const fs={...filesystem,lstat:async(...args:Parameters<typeof filesystem.lstat>)=>modeledStat(await Reflect.apply(filesystem.lstat,filesystem,args)),open:async(...args:Parameters<typeof filesystem.open>)=>{const handle=await Reflect.apply(filesystem.open,filesystem,args);return new Proxy(handle,{get(object,key){if(key==='stat')return async(...args:Parameters<typeof handle.stat>)=>modeledStat(await Reflect.apply(handle.stat,handle,args));const value=Reflect.get(object,key);return typeof value==='function'?value.bind(object):value}})}}
const code=ts.transpileModule(sourceText,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(code,{exports,require:(name:string)=>name==='node:fs/promises'?fs:name==='node:process'?{platform:'linux',getuid:()=>0,env:{DATABASE_URL:target}}:require(name),Buffer,TextDecoder,Date,Map,Set,Object,Error})
return exports as unknown as {inspectDatabaseMaintenanceStartup:typeof inspectDatabaseMaintenanceStartup;readAppliedRestoreAuditSources:(root:string)=>Promise<readonly {operationId:string}[]>}
}
async function acceptance(root:string,previousResolutionSha256:string|null=null):Promise<MaintenanceAcceptance>{const facts=await readMaintenanceRecoveryFacts(root);assert(facts.generation);return {format:MAINTENANCE_ACCEPTANCE_FORMAT,resolutionId:randomUUID(),decision:'accept-observed-current-state',historicOutcomeChanged:false,automaticRestore:false,actor:{kind:'host-operator',id:'explicit-test-operator'},approvalSha256:'1'.repeat(64),inspectionSha256:'2'.repeat(64),approvedAt:'2026-11-02T00:00:00.000Z',approvalExpiresAt:'2026-11-02T00:15:00.000Z',acceptedAt:'2026-11-02T00:01:00.000Z',runtimeTargetSha256:runtimeDatabaseTargetSha256(target),previousResolutionSha256,journalCoverage:{prefixBytes:facts.journal.length,sha256:digest(facts.journal),unresolvedOperations:facts.operations.filter(op=>op.state!=='applied')},vaultCoverage:facts.vault.evidence.filter(marker=>facts.vault.unresolvedFiles.includes(marker.file)),generationObservedSha256:digest(facts.generation),knownCurrent}}
async function publish(root:string,value:MaintenanceAcceptance){await mkdir(path.join(root,'maintenance-acceptances'),{recursive:true});const raw=Buffer.from(JSON.stringify(value));await writeFile(path.join(root,'maintenance-acceptances',value.resolutionId+'.json'),raw);return raw}
test('explicit historical acceptance allows restart after approval expiry, preserves unknown and imports only actual applied',()=>own(async(root,uncertain,applied)=>{
const modeled=await modeledStartup(),before=await readFile(path.join(root,'restore-vault',uncertain+'.receipt.json'))
assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
await publish(root,await acceptance(root));const result=await modeled.inspectDatabaseMaintenanceStartup(root)
assert.equal(result.classification,'accepted-current-state');assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root,target.replace('old-password','rotated-password'))).classification,'accepted-current-state');assert.equal(result.acceptedHistoricResolutions,1);assert.equal(result.historicOutcomesChanged,false)
assert.equal(JSON.stringify((await modeled.readAppliedRestoreAuditSources(root)).map(value=>value.operationId)),JSON.stringify([applied]));assert.deepEqual(await readFile(path.join(root,'restore-vault',uncertain+'.receipt.json')),before)
}))
test('completed suffix and legitimate later schema/partition changes do not require a perpetual SQL fingerprint gate',()=>own(async root=>{
const modeled=await modeledStartup();await publish(root,await acceptance(root));const later=randomUUID();await appendFile(path.join(root,'database-maintenance.jsonl'),record(later,'intent')+record(later,'applied'))
// Reader has no SQL pool/schema query at all. External generation can rotate
// without reviving old credentials or invalidating historical acceptance.
await writeFile(path.join(root,'access-generation'),Buffer.alloc(32,9));assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'accepted-current-state')
await appendFile(path.join(root,'database-maintenance.jsonl'),record(randomUUID(),'intent'));assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('new explicit acceptance can cover a later uncertainty without altering old records',()=>own(async root=>{
const modeled=await modeledStartup(),first=await publish(root,await acceptance(root));await appendFile(path.join(root,'database-maintenance.jsonl'),record(randomUUID(),'intent'))
assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required');const second=await acceptance(root,digest(first));second.acceptedAt='2026-11-02T00:02:00.000Z';await publish(root,second)
assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'accepted-current-state');assert.deepEqual(await readFile(path.join(root,'maintenance-acceptances',JSON.parse(first.toString()).resolutionId+'.json')),first)
}))
test('covered bytes drift, missing proof, forks and malformed tail never unlock startup',()=>own(async(root,uncertain)=>{
const modeled=await modeledStartup(),value=await acceptance(root),raw=await publish(root,value),filename=path.join(root,'maintenance-acceptances',value.resolutionId+'.json'),journal=path.join(root,'database-maintenance.jsonl'),original=await readFile(journal)
for(const mutation of ['receipt','tail','record','oversized-record','fork','target','generation']){
const receipt=path.join(root,'restore-vault',uncertain+'.receipt.json'),receiptBefore=await readFile(receipt)
if(mutation==='receipt')await writeFile(receipt,receiptBefore.toString().replace('RESTORE_UNCONFIRMED','OTHER_UNCONFIRMED'))
if(mutation==='tail')await appendFile(journal,'{"operationId":')
if(mutation==='record')await writeFile(filename,raw.subarray(0,raw.length-1));if(mutation==='oversized-record')await writeFile(filename,Buffer.alloc(1048577))
let forkFile='';if(mutation==='fork'){const fork={...value,resolutionId:randomUUID()};forkFile=path.join(root,'maintenance-acceptances',fork.resolutionId+'.json');await writeFile(forkFile,JSON.stringify(fork))}
if(mutation==='generation')await writeFile(path.join(root,'access-generation'),Buffer.alloc(31))
const result=await modeled.inspectDatabaseMaintenanceStartup(root,mutation==='target'?target.replace('/managed','/other'):target);assert.equal(result.classification,'inspection-required',mutation)
await writeFile(journal,original);await writeFile(filename,raw);await writeFile(receipt,receiptBefore);await writeFile(path.join(root,'access-generation'),Buffer.alloc(32,7));if(forkFile)await rm(forkFile)
}
}))
test('credential rotation is excluded; host/database/TLS identity changes are bound',()=>{
const expected=runtimeDatabaseTargetSha256(target);assert.equal(runtimeDatabaseTargetSha256(target.replace('old-password','new-password')),expected)
for(const changed of [target.replace('db.example','other.example'),target.replace('/managed','/different'),target.replace('sslmode=require','sslmode=disable')])assert.notEqual(runtimeDatabaseTargetSha256(changed),expected)
assert.throws(()=>runtimeDatabaseTargetSha256(target+'&unreviewed_target_option=true'))
})
test('native Windows never treats modeled POSIX acceptance as real protected authorization',()=>own(async root=>{
if(process.platform!=='win32')return
await publish(root,await acceptance(root));assert.equal((await inspectDatabaseMaintenanceStartup(root,target)).classification,'inspection-required')
}))
test('acceptance never accepts torn/duplicate transition grammar or fabricated current proof',()=>own(async root=>{
const facts=await readMaintenanceRecoveryFacts(root),recordValue=await acceptance(root),raw=Buffer.from(JSON.stringify(recordValue))
assert.throws(()=>parseMaintenanceJournal(Buffer.from(record(randomUUID(),'applied'))));assert.throws(()=>parseMaintenanceJournal(Buffer.from('{bad}')))
const wrong={...recordValue,knownCurrent:{...knownCurrent,source:{...source,inRecovery:true}}};assert.throws(()=>evaluateMaintenanceAcceptances([Buffer.from(JSON.stringify(wrong))],facts.journal,facts.vault.evidence,recordValue.runtimeTargetSha256))
const empty={...recordValue,journalCoverage:{...recordValue.journalCoverage,unresolvedOperations:[]},vaultCoverage:[]};assert.throws(()=>evaluateMaintenanceAcceptances([Buffer.from(JSON.stringify(empty))],facts.journal,facts.vault.evidence,recordValue.runtimeTargetSha256));assert(evaluateMaintenanceAcceptances([raw],facts.journal,facts.vault.evidence,recordValue.runtimeTargetSha256).acceptedCoreIds.size)
}))
test('without an acceptance file existing completed-history startup gains no new target-parser prerequisite',()=>own(async(root,uncertain,applied)=>{
await writeFile(path.join(root,'database-maintenance.jsonl'),record(applied,'intent')+record(applied,'applied'))
const receipt=JSON.parse((await readFile(path.join(root,'restore-vault',uncertain+'.receipt.json'))).toString());await rm(path.join(root,'restore-vault',uncertain+'.receipt.json'));await rm(path.join(root,'restore-vault',receipt.archiveRef+'.used'))
assert.equal((await inspectDatabaseMaintenanceStartup(root,'postgres:///managed?host=%2Ftmp&options=existing')).classification,'completed-core-maintenance')
}))
test('new unresolved vault marker blocks even without a new core journal record',()=>own(async root=>{
const modeled=await modeledStartup();await publish(root,await acceptance(root));const operationId=randomUUID(),archiveRef=randomUUID(),startedAt='2026-11-03T00:00:00.000Z',file=path.join(root,'restore-vault',operationId+'.receipt.json')
await writeFile(file,JSON.stringify({operationId,archiveRef,state:'running',startedAt,automaticRetry:false}));assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
await writeFile(file,JSON.stringify({operationId,archiveRef,state:'blocked',startedAt,automaticRetry:false,errorCode:'PERMISSION_DENIED',finishedAt:startedAt}));assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'accepted-current-state')
const dangling=randomUUID();await writeFile(path.join(root,'restore-vault',dangling+'.used'),JSON.stringify({operationId:randomUUID(),at:startedAt}));assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('native POSIX immutable fsynced acceptance is consumed by actual startup; mode and symlink drift refuse',async context=>{
if(process.platform==='win32'){context.skip('Native POSIX filesystem acceptance must run directly on Linux; Windows model is not proof');return}
await own(async root=>{
const {writeFile:writeProtected}=await import('../../scripts/writer-profile/operator-files.mjs'),value=await acceptance(root),directory=path.join(root,'maintenance-acceptances'),filename=path.join(directory,value.resolutionId+'.json')
await mkdir(directory,{mode:0o700});await writeProtected(filename,Buffer.from(JSON.stringify(value)),{flag:'wx',mode:0o600})
assert.equal((await inspectDatabaseMaintenanceStartup(root,target)).classification,'accepted-current-state')
await chmod(filename,0o644);assert.equal((await inspectDatabaseMaintenanceStartup(root,target)).classification,'inspection-required');await chmod(filename,0o600)
const alias=path.join(directory,randomUUID()+'.json');await symlink(filename,alias);assert.equal((await inspectDatabaseMaintenanceStartup(root,target)).classification,'inspection-required');await rm(alias)
const later=randomUUID();await appendFile(path.join(root,'database-maintenance.jsonl'),record(later,'intent')+record(later,'applied'));assert.equal((await inspectDatabaseMaintenanceStartup(root,target)).classification,'accepted-current-state')
})
})
@@ -0,0 +1,68 @@
/** Explicit historical acceptance, not restore success or permanent schema/data admission. */
import {createHash} from 'node:crypto'
export const MAINTENANCE_ACCEPTANCE_FORMAT='database-maintenance-current-state-acceptance-v1'
const sha=(value:Buffer|string)=>createHash('sha256').update(value).digest('hex')
const hex=(v:unknown):v is string=>typeof v==='string'&&/^[a-f0-9]{64}$/.test(v)
const uuid=(v:unknown):v is string=>typeof v==='string'&&/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v)
function fail():never{throw Error('Explicit maintenance acceptance unavailable or conflicting')}
function exact(value:unknown,keys:string[]):asserts value is Record<string,unknown>{if(!value||typeof value!=='object'||Array.isArray(value)||Object.keys(value).sort().join('\0')!==keys.sort().join('\0'))fail()}
const instant=(v:unknown):v is string=>typeof v==='string'&&Number.isFinite(Date.parse(v))&&new Date(v).toISOString()===v
export function parseMaintenanceJSON(raw:Buffer):unknown{const text=new TextDecoder('utf8',{fatal:true}).decode(raw),value:unknown=JSON.parse(text);if(JSON.stringify(value)!==text.trim())fail();return value}
export interface MaintenanceOperation {operationId:string;state:'intent'|'applied'|'unconfirmed'}
export function parseMaintenanceJournal(raw:Buffer):MaintenanceOperation[]{
if(!raw.length||raw.length>8388608)fail();const text=new TextDecoder('utf8',{fatal:true}).decode(raw);if(!text.endsWith('\n'))fail()
const lines=text.slice(0,-1).split('\n');if(lines.length>65536)fail();const operations=new Map<string,MaintenanceOperation['state']>()
for(const line of lines){if(!line||Buffer.byteLength(line)>256)fail();const value=parseMaintenanceJSON(Buffer.from(line));exact(value,['operationId','state','at']);if(!uuid(value.operationId)||!instant(value.at))fail()
if(value.state==='intent'){if(operations.has(value.operationId))fail();operations.set(value.operationId,'intent')}
else if(value.state==='applied'||value.state==='unconfirmed'){if(operations.get(value.operationId)!=='intent')fail();operations.set(value.operationId,value.state)}else fail()
}
return [...operations].map(([operationId,state])=>({operationId,state}))
}
export function runtimeDatabaseTargetSha256(raw:string):string{
const url=new URL(raw);if(!['postgres:','postgresql:'].includes(url.protocol)||!url.hostname||url.hash)fail()
const port=Number(url.port||5432);if(!Number.isInteger(port)||port<1||port>65535)fail();const database=decodeURIComponent(url.pathname.slice(1));if(!database||/[\x00-\x1f\x7f/]/.test(database))fail()
const relevant=new Set(['sslmode','ssl','sslrootcert','sslcert','sslkey','channel_binding','gssencmode','target_session_attrs']),ignored=new Set(['user','password','application_name','connect_timeout','statement_timeout','lock_timeout'])
const tls:Record<string,string>={};for(const [key,value]of url.searchParams){if(relevant.has(key)){if(Object.hasOwn(tls,key))fail();tls[key]=value}else if(!ignored.has(key))fail()}
return sha(JSON.stringify({format:'database-target-selector-v1',host:url.hostname.toLowerCase(),port,database,tls:Object.fromEntries(Object.entries(tls).sort())}))
}
export interface MaintenanceMarker {file:string;bytes:number;sha256:string}
export interface MaintenanceKnownCurrent {transport:{mode:'tls'|'local-loopback';endpointSha256:string};source:{systemIdentifier:string;databaseOid:string;databaseName:string;serverVersionNum:number;inRecovery:false};schemaSha256:string;rawSchemaSha256:string;physicalSchemaSha256:string;manifestSha256:string;markersSha256:string}
export interface MaintenanceAcceptance {
format:typeof MAINTENANCE_ACCEPTANCE_FORMAT;resolutionId:string;decision:'accept-observed-current-state';historicOutcomeChanged:false;automaticRestore:false;
actor:{kind:'host-operator';id:string};approvalSha256:string;inspectionSha256:string;approvedAt:string;approvalExpiresAt:string;acceptedAt:string;
runtimeTargetSha256:string;previousResolutionSha256:string|null;journalCoverage:{prefixBytes:number;sha256:string;unresolvedOperations:MaintenanceOperation[]};vaultCoverage:MaintenanceMarker[];generationObservedSha256:string;knownCurrent:MaintenanceKnownCurrent
}
export function validateMaintenanceKnownCurrent(value:unknown):asserts value is MaintenanceKnownCurrent{
exact(value,['transport','source','schemaSha256','rawSchemaSha256','physicalSchemaSha256','manifestSha256','markersSha256']);if(!['schemaSha256','rawSchemaSha256','physicalSchemaSha256','manifestSha256','markersSha256'].every(key=>hex(value[key])))fail()
exact(value.transport,['mode','endpointSha256']);if(!['tls','local-loopback'].includes(String(value.transport.mode))||!hex(value.transport.endpointSha256))fail()
exact(value.source,['systemIdentifier','databaseOid','databaseName','serverVersionNum','inRecovery']);const s=value.source
if(typeof s.systemIdentifier!=='string'||!/^[1-9][0-9]{0,19}$/.test(s.systemIdentifier)||BigInt(s.systemIdentifier)>18446744073709551615n||typeof s.databaseOid!=='string'||!/^[1-9][0-9]{0,9}$/.test(s.databaseOid)||BigInt(s.databaseOid)>4294967295n||typeof s.databaseName!=='string'||!/^[^\x00-\x1f\x7f]{1,63}$/.test(s.databaseName)||!Number.isInteger(s.serverVersionNum)||Number(s.serverVersionNum)<180000||Number(s.serverVersionNum)>=190000||s.inRecovery!==false)fail()
}
export function validateMaintenanceAcceptance(value:unknown):asserts value is MaintenanceAcceptance{
exact(value,['format','resolutionId','decision','historicOutcomeChanged','automaticRestore','actor','approvalSha256','inspectionSha256','approvedAt','approvalExpiresAt','acceptedAt','runtimeTargetSha256','previousResolutionSha256','journalCoverage','vaultCoverage','generationObservedSha256','knownCurrent'])
if(value.format!==MAINTENANCE_ACCEPTANCE_FORMAT||!uuid(value.resolutionId)||value.decision!=='accept-observed-current-state'||value.historicOutcomeChanged!==false||value.automaticRestore!==false)fail()
exact(value.actor,['kind','id']);if(value.actor.kind!=='host-operator'||typeof value.actor.id!=='string'||!/^[^\x00-\x1f\x7f]{1,160}$/.test(value.actor.id))fail()
if(!['approvalSha256','inspectionSha256','runtimeTargetSha256','generationObservedSha256'].every(key=>hex(value[key]))||(value.previousResolutionSha256!==null&&!hex(value.previousResolutionSha256)))fail()
if(!instant(value.approvedAt)||!instant(value.approvalExpiresAt)||!instant(value.acceptedAt)||Date.parse(value.approvedAt)>Date.parse(value.acceptedAt)||Date.parse(value.acceptedAt)>=Date.parse(value.approvalExpiresAt))fail()
exact(value.journalCoverage,['prefixBytes','sha256','unresolvedOperations']);const coverage=value.journalCoverage
if(!Number.isSafeInteger(coverage.prefixBytes)||Number(coverage.prefixBytes)<1||Number(coverage.prefixBytes)>8388608||!hex(coverage.sha256)||!Array.isArray(coverage.unresolvedOperations)||coverage.unresolvedOperations.length>65536)fail()
const ids=new Set<string>();for(const operation of coverage.unresolvedOperations){exact(operation,['operationId','state']);if(!uuid(operation.operationId)||!['intent','unconfirmed'].includes(String(operation.state))||ids.has(operation.operationId))fail();ids.add(operation.operationId)}
if(!Array.isArray(value.vaultCoverage)||value.vaultCoverage.length>4096)fail();const files=new Set<string>();for(const marker of value.vaultCoverage){exact(marker,['file','bytes','sha256']);if(typeof marker.file!=='string'||!/^([a-f0-9-]{36})\.(?:used|receipt\.json)$/.test(marker.file)||!uuid(marker.file.slice(0,36))||!Number.isSafeInteger(marker.bytes)||Number(marker.bytes)<1||Number(marker.bytes)>65536||!hex(marker.sha256)||files.has(marker.file))fail();files.add(marker.file)}
if(!ids.size&&!files.size)fail();validateMaintenanceKnownCurrent(value.knownCurrent)
}
export function evaluateMaintenanceAcceptances(rawRecords:readonly Buffer[],journal:Buffer,markers:readonly MaintenanceMarker[],targetSha256:string){
if(rawRecords.length>64)fail();const records=new Map<string,MaintenanceAcceptance>(),ids=new Set<string>()
for(const raw of rawRecords){if(!raw.length||raw.length>1048576)fail();const value=parseMaintenanceJSON(raw);validateMaintenanceAcceptance(value);const digest=sha(raw);if(records.has(digest)||ids.has(value.resolutionId)||value.runtimeTargetSha256!==targetSha256)fail();records.set(digest,value);ids.add(value.resolutionId)
const prefix=journal.subarray(0,value.journalCoverage.prefixBytes);if(prefix.length!==value.journalCoverage.prefixBytes||sha(prefix)!==value.journalCoverage.sha256)fail()
const unresolved=parseMaintenanceJournal(prefix).filter(op=>op.state!=='applied');if(JSON.stringify(unresolved)!==JSON.stringify(value.journalCoverage.unresolvedOperations))fail()
for(const marker of value.vaultCoverage){if(!markers.some(actual=>JSON.stringify(actual)===JSON.stringify(marker)))fail()}
}
if(!records.size)return {headSha256:null as string|null,acceptedCoreIds:new Set<string>(),acceptedVaultFiles:new Set<string>(),count:0}
const parents=new Set([...records.values()].map(value=>value.previousResolutionSha256).filter((v):v is string=>v!==null)),heads=[...records.keys()].filter(key=>!parents.has(key));if(heads.length!==1)fail()
let current:string|null=heads[0],child:MaintenanceAcceptance|undefined;const visited=new Set<string>(),acceptedCoreIds=new Set<string>(),acceptedVaultFiles=new Set<string>()
while(current!==null){if(visited.has(current))fail();const value=records.get(current);if(!value)fail();visited.add(current)
if(child){if(child.journalCoverage.prefixBytes<value.journalCoverage.prefixBytes||Date.parse(child.acceptedAt)<Date.parse(value.acceptedAt)||value.vaultCoverage.some(marker=>!child!.vaultCoverage.some(later=>JSON.stringify(later)===JSON.stringify(marker))))fail()}
value.journalCoverage.unresolvedOperations.forEach(op=>acceptedCoreIds.add(op.operationId));value.vaultCoverage.forEach(marker=>acceptedVaultFiles.add(marker.file));child=value;current=value.previousResolutionSha256
}
if(visited.size!==records.size)fail();return {headSha256:heads[0],acceptedCoreIds,acceptedVaultFiles,count:records.size}
}
@@ -0,0 +1,168 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {mkdtemp,readFile,writeFile,appendFile,mkdir,rm,readdir,link} from 'node:fs/promises'
import * as filesystem from 'node:fs/promises'
import {randomUUID} from 'node:crypto'
import path from 'node:path'
import {fileURLToPath,pathToFileURL} from 'node:url'
import {spawnSync} from 'node:child_process'
import {createRequire} from 'node:module'
import {runInNewContext} from 'node:vm'
import {inspectDatabaseMaintenanceStartup,assertDatabaseMaintenanceStartup,DatabaseMaintenanceStartupError} from './database-maintenance-startup.js'
const testFile=fileURLToPath(import.meta.url),here=path.dirname(testFile),backend=path.resolve(here,'../..'),area=path.resolve(backend,'../tmp'),require=createRequire(import.meta.url),ts=require('typescript')
const stamp='2026-11-03T00:00:00.000Z'
// Same installed loader as the backend's owned test conventions, resolved to
// an absolute file URL rather than assuming a child's package resolution.
if(process.argv[2]==='--inspect-owned-maintenance'){
const value=await inspectDatabaseMaintenanceStartup(process.argv[3])
process.stdout.write(JSON.stringify(value)+'\n');process.exit(value.classification==='inspection-required'?1:0)
}
const record=(operationId:string,state:string)=>JSON.stringify({operationId,state,at:stamp})+'\n'
async function own(work:(root:string)=>Promise<void>){
await mkdir(area,{recursive:true});const root=await mkdtemp(path.join(area,'owned-maintenance-startup-'))
try{await work(root)}finally{assert(path.dirname(root)===area&&path.basename(root).startsWith('owned-maintenance-startup-'));await rm(root,{recursive:true,force:true})}
}
async function generation(root:string){await writeFile(path.join(root,'access-generation'),Buffer.alloc(32,7))}
async function completed(root:string){const id=randomUUID();await generation(root);await writeFile(path.join(root,'database-maintenance.jsonl'),record(id,'intent')+record(id,'applied'));return id}
async function outer(root:string,state:string,used=true,wrongClaim=false){
const vault=path.join(root,'restore-vault');await mkdir(vault,{recursive:true})
const operationId=randomUUID(),archiveRef=randomUUID()
const value:Record<string,unknown>={operationId,archiveRef,state,startedAt:stamp,automaticRetry:false}
if(state==='applied')Object.assign(value,{finishedAt:stamp,accessRevoked:true,warnings:['migrationError']})
else if(state!=='running')Object.assign(value,{errorCode:'RESTORE_UNCONFIRMED',finishedAt:stamp})
await writeFile(path.join(vault,operationId+'.receipt.json'),JSON.stringify(value))
if(used)await writeFile(path.join(vault,archiveRef+'.used'),JSON.stringify({operationId:wrongClaim?randomUUID():operationId,at:stamp}))
return {vault,operationId,archiveRef}
}
function child(root:string){
const value=spawnSync(process.execPath,['--import',pathToFileURL(require.resolve('tsx')).href,testFile,'--inspect-owned-maintenance',root],{cwd:backend,encoding:'utf8',timeout:10000})
assert.equal(value.error,undefined);assert.equal(value.signal,null)
return {code:value.status,inspection:JSON.parse(value.stdout)}
}
test('missing external state is read-only fresh legacy; empty existing state does not create authority',()=>own(async root=>{
const absent=path.join(root,'not-created'),before=await readdir(root)
assert.equal((await inspectDatabaseMaintenanceStartup(absent)).classification,'fresh-legacy')
assert.deepEqual(await readdir(root),before)
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'fresh-legacy')
assert.deepEqual(await readdir(root),before)
}))
test('empty journal is only before-intent when an existing external access generation is readable',()=>own(async root=>{
await writeFile(path.join(root,'database-maintenance.jsonl'),'')
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
await generation(root)
const result=await inspectDatabaseMaintenanceStartup(root);assert.equal(result.classification,'empty-before-intent');assert.equal(result.accessGenerationObserved,true)
}))
test('actual fresh process refuses dangling intent and unconfirmed without replay or rewriting',()=>own(async root=>{
await generation(root);const file=path.join(root,'database-maintenance.jsonl'),id=randomUUID()
await writeFile(file,record(id,'intent'))
const before=await readFile(file)
const first=child(root);assert.equal(first.code,1);assert.equal(first.inspection.reason,'UNFINISHED_MAINTENANCE')
assert.deepEqual(await readFile(file),before)
await appendFile(file,record(id,'unconfirmed'))
const unknown=child(root);assert.equal(unknown.code,1);assert.equal(unknown.inspection.reason,'UNCONFIRMED_MAINTENANCE')
assert.equal(unknown.inspection.automaticRestore,false)
}))
test('core applied allows ordinary bootstrap but never infers outer success, audit import or repaired schema',()=>own(async root=>{
await completed(root)
const result=child(root);assert.equal(result.code,0);assert.equal(result.inspection.classification,'completed-core-maintenance')
assert.equal(result.inspection.outerOperationInferred,false);assert.equal(result.inspection.auditImportStatus,'not-verified');assert.equal(result.inspection.postRepairSchemaStatus,'not-inspected')
}))
for(const state of ['running','unknown'])test('core applied cannot promote outer '+state+' receipt to completed',()=>own(async root=>{
await completed(root);await outer(root,state)
const result=await inspectDatabaseMaintenanceStartup(root);assert.equal(result.classification,'inspection-required');assert.equal(result.reason,'EXTERNAL_OPERATION_UNRESOLVED')
}))
test('outer applied receipt requires exact matching persisted used claim',()=>own(async root=>{
await completed(root);const f=await outer(root,'applied',true)
assert.equal((await inspectDatabaseMaintenanceStartup(root)).outerAppliedReceipts,1)
await writeFile(path.join(f.vault,f.archiveRef+'.used'),JSON.stringify({operationId:randomUUID(),at:stamp}))
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('blocked predispatch operation may share an archive later successfully consumed by a different operation',()=>own(async root=>{
await completed(root);const a=await outer(root,'blocked',false),operationB=randomUUID()
await writeFile(path.join(a.vault,operationB+'.receipt.json'),JSON.stringify({operationId:operationB,archiveRef:a.archiveRef,state:'applied',startedAt:stamp,automaticRetry:false,finishedAt:stamp,accessRevoked:true,warnings:[]}))
await writeFile(path.join(a.vault,a.archiveRef+'.used'),JSON.stringify({operationId:operationB,at:stamp}))
const result=await inspectDatabaseMaintenanceStartup(root)
assert.equal(result.classification,'completed-core-maintenance');assert.equal(result.blockedReceipts,1);assert.equal(result.outerAppliedReceipts,1)
await writeFile(path.join(a.vault,a.archiveRef+'.used'),JSON.stringify({operationId:a.operationId,at:stamp}))
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('missing journal with attempt markers is not a new clean install',()=>own(async root=>{
await outer(root,'applied',true)
const value=await inspectDatabaseMaintenanceStartup(root);assert.equal(value.classification,'inspection-required');assert.equal(value.reason,'JOURNAL_ABSENT_WITH_ATTEMPT_MARKERS')
}))
test('orphan used claim, unknown publication temporary file and blocked-with-used refuse',()=>own(async root=>{
await completed(root);const f=await outer(root,'blocked',true)
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
await rm(path.join(f.vault,f.operationId+'.receipt.json'))
assert.equal((await inspectDatabaseMaintenanceStartup(root)).reason,'EXTERNAL_OPERATION_UNRESOLVED')
await rm(path.join(f.vault,f.archiveRef+'.used'));await writeFile(path.join(f.vault,'receipt-publication.tmp'),'partial')
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('malformed tails, duplicates, unknown fields/states and impossible transitions never admit startup',()=>own(async root=>{
await generation(root);const id=randomUUID(),good=record(id,'intent')
const bad=[good.slice(0,-1),good+'{',good+record(id,'intent'),good+record(id,'applied')+record(id,'applied'),record(id,'applied'),good+record(id,'future'),JSON.stringify({operationId:id,state:'intent',at:stamp,ready:true})+'\n',`{"operationId":"${id}","state":"intent","state":"applied","at":"${stamp}"}\n`]
for(const content of bad){await writeFile(path.join(root,'database-maintenance.jsonl'),content);assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')}
}))
test('journal byte bound refuses without accepting a truncated history',()=>own(async root=>{
await generation(root);const file=path.join(root,'database-maintenance.jsonl')
await writeFile(file,Buffer.alloc(8*1024*1024+1,32));assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('hardlinked external files are not accepted as complete history',async t=>own(async root=>{
await generation(root);const file=path.join(root,'database-maintenance.jsonl')
await writeFile(file,record(randomUUID(),'intent'))
try{await link(file,path.join(root,'other-link'))}catch(error){if((error as NodeJS.ErrnoException).code==='EPERM'){t.skip('Host filesystem policy refuses creating an owned hardlink');return}throw error}
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('actual patched bootstrap issues no SQL, migration or consumer work before recovery guard',()=>own(async root=>{
await generation(root);await writeFile(path.join(root,'database-maintenance.jsonl'),record(randomUUID(),'intent'))
const source=await readFile(path.join(backend,'src/db/bootstrap.ts'),'utf8'),events:string[]=[],exports:Record<string,unknown>={}
const query=async()=>{events.push('sql');return {rows:[{sqlite_imported_at:'done'}]}},client={query,release:()=>{}}
const modules:Record<string,unknown>={
'./index.js':{pool:{query,connect:async()=>{events.push('connect');return client}}},
'./migrate.js':{applySqlMigrations:async()=>{events.push('migrations')}},
'./partitions.js':{dropExpiredPartitions:async()=>{events.push('drop')},ensurePartitionsAround:async()=>{events.push('partitions')}},
'./sqlite-import.js':{shouldImportSqlite:async()=>false,sqliteFileLooksPresent:()=>false,importSqliteToPostgres:async()=>{events.push('import')}},
'../config.js':{env:{DATABASE_PATH:'synthetic-only'}},'../modules/mcp/security.js':{mcpStateDirectory:()=>root},
'../services/database-maintenance-startup.js':{assertDatabaseMaintenanceStartup:async(directory:string)=>{events.push('guard');return assertDatabaseMaintenanceStartup(directory)}},
'../services/database-restore-audit.js':{reconcileDatabaseRestoreAudits:async()=>{events.push('audit-reconcile')}},
}
const code=ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText
runInNewContext(code,{exports,require:(name:string)=>{assert(name in modules);return modules[name]},console})
const initialize=exports.initDatabase as ()=>Promise<void>
await assert.rejects(initialize(),error=>error instanceof DatabaseMaintenanceStartupError)
assert.deepEqual(events,['guard'])
await writeFile(path.join(root,'database-maintenance.jsonl'),'');events.length=0
await initialize();assert.equal(events[0],'guard');assert(events.includes('sql')&&events.includes('migrations'))
const main=await readFile(path.join(backend,'src/index.ts'),'utf8')
const start=main.indexOf('if (isMain)');assert(start>=0)
const body=main.slice(start);assert(body.indexOf('await assertDatabaseMaintenanceStartup')<body.indexOf('await initDatabase()'));assert(body.indexOf('await initDatabase()')<body.indexOf('await app.listen'))
}))
test('actual journal append between metadata capture and open refuses an unstable snapshot',()=>own(async root=>{
await generation(root);const operation=randomUUID(),file=path.join(root,'database-maintenance.jsonl')
await writeFile(file,record(operation,'intent'))
const source=await readFile(path.join(backend,'src/services/database-maintenance-startup.ts'),'utf8'),exports:Record<string,unknown>={};let changed=false
const model={...filesystem,lstat:async(...args:Parameters<typeof filesystem.lstat>)=>{
const value=await Reflect.apply(filesystem.lstat,filesystem,args)
if(!changed&&args[0]===file){changed=true;await appendFile(file,record(operation,'applied'))}
return value
}}
const code=ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(code,{exports,require:(name:string)=>name==='node:fs/promises'?model:require(name),Buffer,TextDecoder,Date,Map,Set,Object,Error})
const inspect=exports.inspectDatabaseMaintenanceStartup as typeof inspectDatabaseMaintenanceStartup
const value=await inspect(root);assert.equal(changed,true);assert.equal(value.classification,'inspection-required')
assert.equal(value.reason,'EXTERNAL_STATE_UNAVAILABLE_OR_MALFORMED')
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'completed-core-maintenance','a later distinct stable inspection may observe actual terminal bytes without rewriting them')
}))
test('existing non-directory or unverifiable ancestor never becomes a missing clean state',()=>own(async root=>{
await writeFile(path.join(root,'ordinary-file'),'not-directory')
assert.equal((await inspectDatabaseMaintenanceStartup(path.join(root,'ordinary-file','state'))).classification,'inspection-required')
const source=await readFile(path.join(backend,'src/services/database-maintenance-startup.ts'),'utf8'),exports:Record<string,unknown>={}
const model={...filesystem,realpath:async(file:Parameters<typeof filesystem.realpath>[0])=>{
if(file===root)throw Object.assign(Error('Synthetic ancestor became unverifiable'),{code:'ENOENT'})
return filesystem.realpath(file)
}}
const code=ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(code,{exports,require:(name:string)=>name==='node:fs/promises'?model:require(name),Buffer,TextDecoder,Date,Map,Set,Object,Error})
const inspect=exports.inspectDatabaseMaintenanceStartup as typeof inspectDatabaseMaintenanceStartup
assert.equal((await inspect(path.join(root,'not-created-state'))).classification,'inspection-required')
}))
@@ -0,0 +1,181 @@
/** Read-only classification of the EXISTING external maintenance grammar.
* No restore, journal rewrite, generation rotation, SQL, role issuer or READY. */
import process from 'node:process'
import {constants,type BigIntStats} from 'node:fs'
import {lstat,open,readdir,realpath} from 'node:fs/promises'
import path from 'node:path'
import {createHash} from 'node:crypto'
import {parseMaintenanceJournal,evaluateMaintenanceAcceptances,runtimeDatabaseTargetSha256,type MaintenanceMarker} from './database-maintenance-resolution.js'
const MAX_JOURNAL_BYTES=8*1024*1024,MAX_VAULT_ENTRIES=4096,MAX_RECEIPT_BYTES=65536
const uuid=/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/
type Reason='NONE'|'UNFINISHED_MAINTENANCE'|'UNCONFIRMED_MAINTENANCE'|'EXTERNAL_OPERATION_UNRESOLVED'|'JOURNAL_ABSENT_WITH_ATTEMPT_MARKERS'|'EXTERNAL_STATE_UNAVAILABLE_OR_MALFORMED'
export interface MaintenanceStartupInspection {
readonly classification:'fresh-legacy'|'empty-before-intent'|'completed-core-maintenance'|'accepted-current-state'|'inspection-required'
readonly reason:Reason
readonly journalOperations:number
readonly coreApplied:number
readonly outerAppliedReceipts:number
readonly blockedReceipts:number
readonly accessGenerationObserved:boolean
readonly outerOperationInferred:false
readonly auditImportStatus:'not-verified'
readonly postRepairSchemaStatus:'not-inspected'
readonly acceptedHistoricResolutions:number
readonly historicOutcomesChanged:false
readonly automaticRestore:false
}
class Refusal extends Error {constructor(readonly reason:Reason='EXTERNAL_STATE_UNAVAILABLE_OR_MALFORMED'){super('External maintenance state requires inspection')}}
function refuse(reason?:Reason):never{throw new Refusal(reason)}
function plain(value:unknown):value is Record<string,unknown>{return value!==null&&typeof value==='object'&&!Array.isArray(value)}
function exact(value:unknown,keys:readonly string[]):asserts value is Record<string,unknown>{if(!plain(value)||Object.keys(value).sort().join('\0')!==[...keys].sort().join('\0'))refuse()}
function id(value:unknown):asserts value is string{if(typeof value!=='string'||!uuid.test(value))refuse()}
function instant(value:unknown):asserts value is string{if(typeof value!=='string'||!Number.isFinite(Date.parse(value))||new Date(value).toISOString()!==value)refuse()}
function json(raw:Buffer):unknown{
const text=new TextDecoder('utf8',{fatal:true}).decode(raw)
// All existing writers produce JSON.stringify output. Exact roundtrip also
// refuses duplicate keys and ambiguous/trailing non-JSON representations.
const value:unknown=JSON.parse(text)
if(JSON.stringify(value)!==text.trim())refuse()
return value
}
function identity(s:BigIntStats){return [s.dev,s.ino,s.size,s.mode,s.nlink,s.mtimeNs,s.ctimeNs].map(String).join(':')}
async function directory(file:string,optional=false){
let s
try{s=await lstat(file,{bigint:true})}
catch(error){if(optional&&(error as NodeJS.ErrnoException).code==='ENOENT')return null;throw error}
if(!s.isDirectory()||s.isSymbolicLink())refuse()
// Only initial lstat absence is a new/legacy absence. An existing ancestor
// whose realpath disappears or cannot be verified is ambiguous, not fresh.
try{if(await realpath(file)!==file)refuse()}catch{refuse()}
return identity(s)
}
async function existingAncestors(file:string){
for(let current=path.dirname(file);;current=path.dirname(current)){
await directory(current,true)
if(current===path.dirname(current))break
}
}
async function bounded(file:string,maximum:number,optional=false){
let before
try{before=await lstat(file,{bigint:true})}catch(error){if(optional&&(error as NodeJS.ErrnoException).code==='ENOENT')return null;throw error}
if(!before.isFile()||before.isSymbolicLink()||before.nlink!==1n||before.size>BigInt(maximum)||await realpath(file)!==file)refuse()
const signature=(s:typeof before)=>[s.dev,s.ino,s.size,s.mode,s.nlink,s.mtimeNs,s.ctimeNs].map(String).join(':')
const handle=await open(file,constants.O_RDONLY|(constants.O_NOFOLLOW??0))
try{
const current=await handle.stat({bigint:true});if(signature(current)!==signature(before))refuse()
const data=Buffer.alloc(Number(current.size)+1);let size=0
while(size<data.length){const got=await handle.read(data,size,data.length-size,size);if(!got.bytesRead)break;size+=got.bytesRead}
if(BigInt(size)!==current.size||signature(await handle.stat({bigint:true}))!==signature(before)||signature(await lstat(file,{bigint:true}))!==signature(before))refuse()
return data.subarray(0,size)
}finally{await handle.close()}
}
export interface AppliedRestoreAuditSource {readonly operationId:string;readonly archiveRef:string;readonly finishedAt:string;readonly receiptSha256:string;readonly receipt:Readonly<Record<string,unknown>>}
interface VaultFacts {markers:number;applied:number;blocked:number;unresolved:boolean;auditSources:readonly AppliedRestoreAuditSource[];evidence:readonly MaintenanceMarker[];unresolvedFiles:readonly string[]}
async function vaultFacts(stateDirectory:string):Promise<VaultFacts>{
const root=path.join(stateDirectory,'restore-vault'),before=await directory(root,true)
if(before===null)return {markers:0,applied:0,blocked:0,unresolved:false,auditSources:[],evidence:[],unresolvedFiles:[]}
const auditSources:AppliedRestoreAuditSource[]=[],evidence:MaintenanceMarker[]=[],unresolvedFiles=new Set<string>()
const names=(await readdir(root)).sort();if(names.length>MAX_VAULT_ENTRIES)refuse()
const signatures=new Map<string,string>(),used=new Map<string,{operationId:string}>(),receipts=new Map<string,{state:string;archiveRef:string}>()
for(const name of names){
const match=/^([a-f0-9-]{36})\.(used|receipt\.json|archive\.json|archive\.bin)$/.exec(name)
if(!match)refuse();id(match[1])
const file=path.join(root,name),stat=await lstat(file,{bigint:true});if(!stat.isFile()||stat.isSymbolicLink()||stat.nlink!==1n||await realpath(file)!==file)refuse()
signatures.set(name,identity(stat))
if(match[2]==='used'){
const raw=(await bounded(file,4096))!;evidence.push({file:name,bytes:raw.length,sha256:createHash('sha256').update(raw).digest('hex')});const value=json(raw);exact(value,['operationId','at']);id(value.operationId);instant(value.at)
used.set(match[1],{operationId:value.operationId})
}else if(match[2]==='receipt.json'){
const raw=(await bounded(file,MAX_RECEIPT_BYTES))!,value=json(raw);evidence.push({file:name,bytes:raw.length,sha256:createHash('sha256').update(raw).digest('hex')})
if(!plain(value)||!['running','applied','unknown','blocked'].includes(String(value.state)))refuse()
const fields=['operationId','archiveRef','state','startedAt','automaticRetry']
if(value.state==='applied')fields.push('finishedAt','accessRevoked','warnings')
else if(value.state!=='running')fields.push('errorCode','finishedAt')
exact(value,fields);id(value.operationId);id(value.archiveRef);instant(value.startedAt)
if(value.operationId!==match[1]||value.automaticRetry!==false)refuse()
if(value.state!=='running')instant(value.finishedAt)
if(value.state==='applied'){
const allowed=new Set(['listenerError','schedulerError','cleanupError','migrationError','maintenanceError'])
if(value.accessRevoked!==true||!Array.isArray(value.warnings)||value.warnings.length>5||value.warnings.some(w=>typeof w!=='string'||!allowed.has(w))||new Set(value.warnings).size!==value.warnings.length)refuse()
auditSources.push(Object.freeze({operationId:value.operationId,archiveRef:value.archiveRef,finishedAt:value.finishedAt as string,receiptSha256:createHash('sha256').update(raw).digest('hex'),receipt:Object.freeze({...value})}))
}else if(value.state!=='running'&&(typeof value.errorCode!=='string'||!/^[A-Z][A-Z0-9_]{0,79}$/.test(value.errorCode)))refuse()
receipts.set(match[1],{state:String(value.state),archiveRef:value.archiveRef})
}
}
let applied=0,blocked=0,unresolved=false
for(const [operationId,receipt]of receipts){
const claim=used.get(receipt.archiveRef)
if(receipt.state==='applied'){if(!claim||claim.operationId!==operationId)refuse();applied++}
else if(receipt.state==='blocked'){if(claim?.operationId===operationId)refuse();blocked++}
else{unresolved=true;unresolvedFiles.add(operationId+'.receipt.json')}
}
for(const [archiveRef,claim]of used){const receipt=receipts.get(claim.operationId);if(!receipt||receipt.archiveRef!==archiveRef||receipt.state!=='applied'){unresolved=true;unresolvedFiles.add(archiveRef+'.used')}}
if(await directory(root)!==before||(await readdir(root)).sort().join('\0')!==[...names].sort().join('\0'))refuse()
for(const [name,signature]of signatures)if(identity(await lstat(path.join(root,name),{bigint:true}))!==signature)refuse()
return {markers:used.size+receipts.size,applied,blocked,unresolved,auditSources:Object.freeze(auditSources),evidence:Object.freeze(evidence),unresolvedFiles:Object.freeze([...unresolvedFiles].sort())}
}
/** Strict inspection facts; no acceptance decision and no SQL. Operator commands
* use the same grammar, not a looser recovery parser. Existing single managed
* host/operator assumption applies; bounded scans are not a concurrent fence. */
export async function readMaintenanceRecoveryFacts(rawDirectory:string){
if(typeof rawDirectory!=='string'||!path.isAbsolute(rawDirectory)||path.resolve(rawDirectory)!==rawDirectory)refuse()
await existingAncestors(rawDirectory);const stateBefore=await directory(rawDirectory,true)
if(stateBefore===null)return {kind:'fresh-legacy' as const,journal:Buffer.alloc(0),operations:[],vault:{markers:0,applied:0,blocked:0,unresolved:false,auditSources:[],evidence:[],unresolvedFiles:[]} as VaultFacts,generation:null}
const journalFile=path.join(rawDirectory,'database-maintenance.jsonl'),raw=await bounded(journalFile,MAX_JOURNAL_BYTES,true),vault=await vaultFacts(rawDirectory)
if(await directory(rawDirectory)!==stateBefore)refuse()
if(raw===null||raw.length===0){if(vault.markers)refuse('JOURNAL_ABSENT_WITH_ATTEMPT_MARKERS');if(raw===null)return {kind:'fresh-legacy' as const,journal:Buffer.alloc(0),operations:[],vault,generation:null}
const generation=await bounded(path.join(rawDirectory,'access-generation'),32);if(generation?.length!==32)refuse();return {kind:'empty-before-intent' as const,journal:raw,operations:[],vault,generation}
}
const generation=await bounded(path.join(rawDirectory,'access-generation'),32);if(generation?.length!==32)refuse();const operations=parseMaintenanceJournal(raw)
if(!(await bounded(journalFile,MAX_JOURNAL_BYTES))!.equals(raw)||await directory(rawDirectory)!==stateBefore)refuse()
return {kind:'journal' as const,journal:raw,operations,vault,generation}
}
/** Acceptance grants startup admission, so POSIX ownership/modes are verified.
* Native Windows protection has no implementation and refuses, never modeled. */
export async function readMaintenanceAcceptanceRecords(stateDirectory:string):Promise<readonly Buffer[]>{
const root=path.join(stateDirectory,'maintenance-acceptances'),before=await directory(root,true);if(before===null)return []
if(process.platform==='win32'||typeof process.getuid!=='function')refuse()
const uid=BigInt(process.getuid()),protectedStat=async(file:string,isDirectory:boolean)=>{const s=await lstat(file,{bigint:true});if(s.uid!==uid||(s.mode&0o077n)!==0n||(isDirectory?!s.isDirectory():!s.isFile()))refuse()}
await protectedStat(root,true)
for(let current=path.dirname(root);;current=path.dirname(current)){const stat=await lstat(current,{bigint:true});if(stat.isSymbolicLink()||!stat.isDirectory()||(stat.uid!==uid&&stat.uid!==0n)||((stat.mode&0o022n)!==0n&&!(stat.uid===0n&&(stat.mode&0o1000n)!==0n)))refuse();if(current===path.dirname(current))break}
const names=(await readdir(root)).sort();if(names.length>64)refuse();const records:Buffer[]=[]
for(const name of names){if(!/^[a-f0-9-]{36}\.json$/.test(name))refuse();id(name.slice(0,36));const file=path.join(root,name);await protectedStat(file,false);const raw=(await bounded(file,1048576))!,value=json(raw);if(!plain(value)||value.resolutionId!==name.slice(0,36))refuse();records.push(raw)}
if(await directory(root)!==before||(await readdir(root)).sort().join('\0')!==names.join('\0'))refuse();return records
}
const configuredTarget=()=>process.env.DATABASE_URL??'postgres://mmapp:mmapp@127.0.0.1:5432/mmapp'
export async function inspectDatabaseMaintenanceStartup(rawDirectory:string,runtimeTarget=configuredTarget()):Promise<MaintenanceStartupInspection>{
const facts={journalOperations:0,coreApplied:0,outerAppliedReceipts:0,blockedReceipts:0,accessGenerationObserved:false,outerOperationInferred:false as const,auditImportStatus:'not-verified' as const,postRepairSchemaStatus:'not-inspected' as const,acceptedHistoricResolutions:0,historicOutcomesChanged:false as const,automaticRestore:false as const}
const result=(classification:MaintenanceStartupInspection['classification'],reason:Reason):MaintenanceStartupInspection=>Object.freeze({classification,reason,...facts})
try{
const state=await readMaintenanceRecoveryFacts(rawDirectory),records=await readMaintenanceAcceptanceRecords(rawDirectory)
facts.outerAppliedReceipts=state.vault.applied;facts.blockedReceipts=state.vault.blocked;facts.accessGenerationObserved=state.generation!==null
if(state.kind!=='journal'){if(records.length)refuse();return result(state.kind,'NONE')}
facts.journalOperations=state.operations.length;facts.coreApplied=state.operations.filter(op=>op.state==='applied').length
const accepted=evaluateMaintenanceAcceptances(records,state.journal,state.vault.evidence,records.length?runtimeDatabaseTargetSha256(runtimeTarget):'0'.repeat(64));facts.acceptedHistoricResolutions=accepted.count
const unresolved=state.operations.filter(op=>op.state!=='applied'&&!accepted.acceptedCoreIds.has(op.operationId))
if(unresolved.some(op=>op.state==='intent'))return result('inspection-required','UNFINISHED_MAINTENANCE')
if(unresolved.some(op=>op.state==='unconfirmed'))return result('inspection-required','UNCONFIRMED_MAINTENANCE')
if(state.vault.unresolvedFiles.some(file=>!accepted.acceptedVaultFiles.has(file)))return result('inspection-required','EXTERNAL_OPERATION_UNRESOLVED')
return result(accepted.count?'accepted-current-state':'completed-core-maintenance','NONE')
}catch(error){return result('inspection-required',error instanceof Refusal?error.reason:'EXTERNAL_STATE_UNAVAILABLE_OR_MALFORMED')}
}
export class DatabaseMaintenanceStartupError extends Error {
readonly code='DATABASE_MAINTENANCE_INSPECTION_REQUIRED'
constructor(readonly inspection:MaintenanceStartupInspection){super('External database maintenance requires read-only inspection before managed startup')}
}
export async function assertDatabaseMaintenanceStartup(directory:string,runtimeTarget=configuredTarget()):Promise<MaintenanceStartupInspection>{
const result=await inspectDatabaseMaintenanceStartup(directory,runtimeTarget)
if(result.classification==='inspection-required')throw new DatabaseMaintenanceStartupError(result)
return result
}
/** Audit recovery consumes the same strict grammar/claims as startup. No core
* applied record can manufacture an applied outer receipt or original actor. */
export async function readAppliedRestoreAuditSources(directory:string,runtimeTarget=configuredTarget()):Promise<readonly AppliedRestoreAuditSource[]>{
await assertDatabaseMaintenanceStartup(directory,runtimeTarget)
const before=await vaultFacts(directory)
await assertDatabaseMaintenanceStartup(directory,runtimeTarget)
const after=await vaultFacts(directory)
if(JSON.stringify(before)!==JSON.stringify(after))throw new DatabaseMaintenanceStartupError(await inspectDatabaseMaintenanceStartup(directory))
return after.auditSources
}
+44 -8
View File
@@ -3,14 +3,47 @@ function conflict(message: string): Error & { statusCode: number } {
return Object.assign(new Error(message), { statusCode: 409 })
}
type DatabaseAdmission = { active: boolean; kind: "request" | "exclusive"; requestAncestry: boolean; children: Set<Promise<void>> }
/** Handler lifetime, not socket lifetime: disconnect does not finish an active write. */
export class DatabaseRequestGate {
private readonly requestContext = new AsyncLocalStorage<boolean>()
assertNoRequestContext(): void { if (this.requestContext.getStore()) throw conflict("Scheduler bootstrap inside request context") }
private readonly requestContext = new AsyncLocalStorage<DatabaseAdmission>()
assertNoRequestContext(): void { if (this.requestContext.getStore()?.requestAncestry) throw conflict("Scheduler bootstrap inside request context") }
/** Live private scope only; detached children cannot retain a settled admission. */
hasActiveAdmission(): boolean { return this.requestContext.getStore()?.active === true }
/** Registration is synchronous; the parent drains this continuation and its
* descendants even if the caller deliberately does not await its result. */
async withinAdmission<T>(run: () => Promise<T>): Promise<T> {
const parent = this.requestContext.getStore()
if (!parent?.active) throw conflict("Database admission is no longer active")
const admission: DatabaseAdmission = { active: true, kind: parent.kind, requestAncestry: parent.requestAncestry, children: new Set() }
let release!: () => void
const settled = new Promise<void>(resolve => { release = resolve })
parent.children.add(settled)
try { return await this.requestContext.run(admission, run) }
finally {
await this.drainAdmission(admission)
parent.children.delete(settled)
release()
}
}
private async drainAdmission(admission: DatabaseAdmission): Promise<void> {
admission.active = false
await Promise.all(admission.children)
}
private openWaiters = new Set<() => void>()
isPaused(): boolean { return this.paused }
// Replacement uncertainty survives the temporary exclusive operation.
private admissionClosed = false
closeAdmission(): void { this.admissionClosed = true }
openAdmission(): void { this.admissionClosed = false; this.notifyOpen() }
private notifyOpen(): void {
if (this.isPaused()) return
const waiting = [...this.openWaiters]; this.openWaiters.clear()
for (const resolve of waiting) resolve()
}
isPaused(): boolean { return this.paused || this.admissionClosed }
whenOpen(): Promise<void> {
if (!this.paused) return Promise.resolve()
if (!this.isPaused()) return Promise.resolve()
return new Promise(resolve => { this.openWaiters.add(resolve) })
}
private paused = false
@@ -19,10 +52,13 @@ export class DatabaseRequestGate {
async request<T>(run: () => Promise<T>, cancelStream?: () => void): Promise<T> {
if (this.paused) throw conflict("Выполняется операция с базой приложения; повторите запрос позже")
if (this.admissionClosed) throw Object.assign(new Error("Схема базы после восстановления не подтверждена; требуется проверка или явное восстановление"), { statusCode: 503 })
const lease = Symbol()
this.active.set(lease, cancelStream)
try { return await this.requestContext.run(true, run) }
const admission: DatabaseAdmission = { active: true, kind: "request", requestAncestry: true, children: new Set() }
try { return await this.requestContext.run(admission, run) }
finally {
await this.drainAdmission(admission)
this.active.delete(lease)
if (!this.active.size) this.idle?.()
}
@@ -38,13 +74,13 @@ export class DatabaseRequestGate {
this.idle = resolve
timer = setTimeout(() => reject(conflict("Не удалось дождаться завершения API-запросов; операция с базой отменена")), timeoutMs)
})
return await run()
const admission: DatabaseAdmission = { active: true, kind: "exclusive", requestAncestry: this.requestContext.getStore()?.requestAncestry === true, children: new Set() }
try { return await this.requestContext.run(admission, run) } finally { await this.drainAdmission(admission) }
} finally {
if (timer) clearTimeout(timer)
this.idle = undefined
this.paused = false
const waiting = [...this.openWaiters]; this.openWaiters.clear()
for (const resolve of waiting) resolve()
this.notifyOpen()
}
}
}
@@ -0,0 +1,110 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {mkdtemp,mkdir,writeFile,readFile,rm} from 'node:fs/promises'
import path from 'node:path'
import {fileURLToPath} from 'node:url'
import {randomUUID} from 'node:crypto'
import {EventEmitter} from 'node:events'
import type {Pool} from 'pg'
import {createRequire} from 'node:module'
import {runInNewContext} from 'node:vm'
import {reconcileRestoreReceiptAudit,inspectRestoreReceiptAudit,reconcileDatabaseRestoreAudits} from './database-restore-audit.js'
const here=path.dirname(fileURLToPath(import.meta.url)),backend=path.resolve(here,'../..'),temp=path.resolve(backend,'../tmp'),require=createRequire(import.meta.url),ts=require('typescript')
async function own(work:(root:string,operation:string,receipt:Record<string,unknown>)=>Promise<void>,old=false){
await mkdir(temp,{recursive:true});const root=await mkdtemp(path.join(temp,'owned-restore-audit-'))
try{
const operation=randomUUID(),archive=randomUUID(),core=randomUUID(),stamp=new Date(Date.now()-(old?181*86400000:1000)).toISOString()
const receipt={operationId:operation,archiveRef:archive,state:'applied',startedAt:stamp,automaticRetry:false,finishedAt:stamp,accessRevoked:true,warnings:[]}
await writeFile(path.join(root,'access-generation'),Buffer.alloc(32,11));await mkdir(path.join(root,'restore-vault'))
await writeFile(path.join(root,'database-maintenance.jsonl'),['intent','applied'].map(state=>JSON.stringify({operationId:core,state,at:stamp})).join('\n')+'\n')
await writeFile(path.join(root,'restore-vault',archive+'.used'),JSON.stringify({operationId:operation,at:stamp}))
await writeFile(path.join(root,'restore-vault',operation+'.receipt.json'),JSON.stringify(receipt))
await work(root,operation,receipt)
}finally{assert(path.dirname(root)===temp&&path.basename(root).startsWith('owned-restore-audit-'));await rm(root,{recursive:true,force:true})}
}
type Row={actor_id:string;connection_id:null;action:string;payload:unknown;created_at?:string}
function model(){
const rows:Row[]=[],queries:string[]=[],releases:boolean[]=[];let lostCommit=false,insertFailure=false,releaseFailure=false,offFailure=false,inserts=0
const pool={connect:async()=>{
let pending:Row[]=[]
const client=new EventEmitter(),originalOff=client.off.bind(client)
return Object.assign(client,{off:(name:string,listener:()=>void)=>{if(offFailure)throw Error('Synthetic listener cleanup failure');return originalOff(name,listener)},release:(bad:boolean)=>{releases.push(bad);if(releaseFailure)throw Error('Synthetic release failure')},query:async(config:{text:string;values?:unknown[]})=>{
const text=config.text,values=config.values??[];queries.push(text)
if(text.startsWith('SELECT audit_retention'))return {rows:[{days:180,now:String(Date.now())}],command:'SELECT'}
if(text.startsWith('SELECT actor_id'))return {rows:rows.filter(row=>(row.payload as {operationId:string}).operationId===values[1]).slice(0,2),command:'SELECT'}
if(text.startsWith('INSERT')){if(insertFailure)throw Error('Synthetic audit unavailable');inserts++;pending.push({actor_id:String(values[0]),action:String(values[1]),connection_id:null,payload:JSON.parse(String(values[2])),created_at:String(values[3])});return {rows:[],command:'INSERT'}}
if(text==='COMMIT'){rows.push(...pending);pending=[];if(lostCommit){lostCommit=false;throw Error('Synthetic COMMIT delivery loss')}return {rows:[],command:'COMMIT'}}
if(text==='ROLLBACK')pending=[]
return {rows:[],command:text.split(' ')[0]}
}})
}} as unknown as Pick<Pool,'connect'>
return {pool,rows,queries,releases,inserts:()=>inserts,loseCommit:()=>{lostCommit=true},failInsert:()=>{insertFailure=true},failRelease:()=>{releaseFailure=true},failOff:()=>{offFailure=true}}
}
test('live and startup audit writers converge through read-first without changing external receipt',()=>own(async(root,id,receipt)=>{
const m=model(),file=path.join(root,'restore-vault',id+'.receipt.json'),before=await readFile(file)
const live=await reconcileRestoreReceiptAudit(m.pool,root,id,'actual-owner');assert.equal(live.status,'imported');assert.equal(live.originalActorKnown,true)
assert.equal(m.rows[0].action,'database.restore.receipt');assert.deepEqual(m.rows[0].payload,receipt);assert.equal(m.rows[0].actor_id,'actual-owner')
await reconcileDatabaseRestoreAudits(m.pool,root);assert.equal(m.inserts(),1)
const status=await inspectRestoreReceiptAudit(m.pool,root,id);assert.equal(status.status,'already-recorded');assert.equal(m.inserts(),1)
assert(m.queries.some(q=>q==='BEGIN ISOLATION LEVEL READ COMMITTED READ ONLY'));assert(m.queries.every(q=>!/^CREATE|^ALTER|^DROP|pg_restore|psql/.test(q)))
assert.deepEqual(await readFile(file),before)
}))
test('legacy missing audit imports exact receipt evidence under system importer without inventing human',()=>own(async(root,id)=>{
const m=model();await reconcileDatabaseRestoreAudits(m.pool,root)
assert.equal(m.rows.length,1);assert.equal(m.rows[0].actor_id,'database-maintenance-reconciler');assert.equal(m.rows[0].action,'database.restore.receipt.recovered')
assert.equal((m.rows[0].payload as {originalActorKnown:boolean}).originalActorKnown,false)
assert.equal((await reconcileRestoreReceiptAudit(m.pool,root,id,'actual-owner')).status,'already-recorded');assert.equal(m.inserts(),1)
}))
test('lost audit COMMIT destroys owner and reconciles observed existing row without another insert',()=>own(async(root,id)=>{
const m=model();m.loseCommit()
assert.equal((await reconcileRestoreReceiptAudit(m.pool,root,id)).status,'unconfirmed');assert.equal(m.releases[0],true)
assert.equal(m.queries.filter(value=>value==='ROLLBACK').length,0,'Uncertain COMMIT owner must be destroyed, not rolled back or reused')
assert.equal((await reconcileRestoreReceiptAudit(m.pool,root,id)).status,'already-recorded');assert.equal(m.inserts(),1)
}))
test('conflicting or duplicate audit evidence is never treated as imported',()=>own(async(root,id)=>{
const m=model();m.rows.push({actor_id:'foreign',connection_id:null,action:'database.restore.receipt',payload:{operationId:id,foreign:true}})
assert.equal((await reconcileRestoreReceiptAudit(m.pool,root,id)).status,'conflict');assert.equal(m.inserts(),0)
await assert.rejects(reconcileDatabaseRestoreAudits(m.pool,root),error=>error instanceof Error&&'code'in error&&error.code==='DATABASE_RESTORE_AUDIT_PENDING')
}))
test('audit retention is not reset by receipt reimport',()=>own(async(root,id)=>{
const m=model();assert.equal((await reconcileRestoreReceiptAudit(m.pool,root,id)).status,'outside-retention');assert.equal(m.inserts(),0)
},true))
test('audit outage remains pending and never overwrites fsynced applied receipt',()=>own(async(root,id)=>{
const m=model();m.failInsert();const file=path.join(root,'restore-vault',id+'.receipt.json'),before=await readFile(file)
assert.equal((await reconcileRestoreReceiptAudit(m.pool,root,id)).status,'pending');assert.deepEqual(await readFile(file),before)
}))
for(const fault of ['release','listener'])test('confirmed audit COMMIT survives '+fault+' cleanup failure without relabeling restore',()=>own(async(root,id)=>{
const m=model();if(fault==='release')m.failRelease();else m.failOff()
const file=path.join(root,'restore-vault',id+'.receipt.json'),before=await readFile(file)
const result=await reconcileRestoreReceiptAudit(m.pool,root,id,'actual-owner')
assert.equal(result.status,'imported');assert.equal(result.cleanupUnconfirmed,true);assert.equal(m.rows.length,1)
assert.equal(m.releases.length,1,'Listener cleanup failure must not bypass actual owner release')
assert.deepEqual(await readFile(file),before)
}))
for(const legacyImport of [false,true])test('actual bootstrap audit follows migrations and legacy import='+legacyImport+' before business startup',()=>own(async(root)=>{
const source=await readFile(path.join(backend,'src/db/bootstrap.ts'),'utf8'),events:string[]=[],exports:Record<string,unknown>={}
const query=async()=>{events.push('sql');return {rows:[{sqlite_imported_at:'done'}]}},client={query,release:()=>{}}
const modules:Record<string,unknown>={
'./index.js':{pool:{query,connect:async()=>client}},'./migrate.js':{applySqlMigrations:async()=>events.push('migrations')},
'./partitions.js':{dropExpiredPartitions:async()=>events.push('drop'),ensurePartitionsAround:async()=>events.push('partitions')},
'./sqlite-import.js':{shouldImportSqlite:async()=>legacyImport,sqliteFileLooksPresent:()=>false,importSqliteToPostgres:async()=>{events.push('import');return {durationMs:0,tables:{}}}},
'../config.js':{env:{DATABASE_PATH:'synthetic'}},'../modules/mcp/security.js':{mcpStateDirectory:()=>root},
'../services/database-maintenance-startup.js':{assertDatabaseMaintenanceStartup:async()=>events.push('guard')},
'../services/database-restore-audit.js':{reconcileDatabaseRestoreAudits:async()=>{events.push('audit');throw Error('Pending audit import')}},
}
runInNewContext(ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText,{exports,require:(name:string)=>{assert(name in modules);return modules[name]},console})
await assert.rejects((exports.initDatabase as ()=>Promise<void>)(),/Pending audit import/)
assert.equal(events[0],'guard');assert.equal(events.at(-1),'audit');assert(events.indexOf('migrations')<events.indexOf('audit'));if(legacyImport)assert(events.indexOf('import')<events.indexOf('audit'))
}))
test('live audit recovery call is outside the catch that can relabel restore',async()=>{
const source=await readFile(path.join(backend,'src/modules/mcp/database-restore.ts'),'utf8'),tree=ts.createSourceFile('restore.ts',source,ts.ScriptTarget.ES2022,true)
let callAncestors:unknown[]=[]
function visit(node:{kind:number;expression?:{getText:()=>string};getChildren:()=>unknown[]},ancestors:unknown[]=[]){
if(node.kind===ts.SyntaxKind.CallExpression&&node.expression?.getText()==='reconcileRestoreReceiptAudit')callAncestors=ancestors
for(const child of node.getChildren())visit(child as typeof node,[...ancestors,node])
}
visit(tree)
const tries=callAncestors.filter((node:unknown)=>(node as {kind:number}).kind===ts.SyntaxKind.TryStatement) as {getText:()=>string}[]
assert.equal(tries.length,1);assert(!tries[0].getText().includes('state: attempted ? "unknown"'))
assert(source.includes('restoreActorId=connection.owner_id'))
})
@@ -0,0 +1,80 @@
/** Specific external receipt -> audit import. Never dispatches restore or
* changes journal/generation/receipt. Both live and startup writers use it. */
import type {Pool,PoolClient,QueryConfig,QueryResultRow} from 'pg'
import {isDeepStrictEqual} from 'node:util'
import {env} from '../config.js'
import {readAppliedRestoreAuditSources,type AppliedRestoreAuditSource} from './database-maintenance-startup.js'
const LIVE='database.restore.receipt',RECOVERED='database.restore.receipt.recovered',IMPORTER='database-maintenance-reconciler'
export type RestoreAuditStatus='imported'|'already-recorded'|'missing'|'outside-retention'|'pending'|'unconfirmed'|'conflict'
export interface RestoreAuditInspection {readonly operationId:string;readonly receiptSha256:string;readonly status:RestoreAuditStatus;
/** Recorded audit actor or live validated owner; not cryptographic provenance. */
readonly originalActorKnown:boolean;readonly cleanupUnconfirmed:boolean;readonly automaticRestore:false}
const boundedQuery=(text:string,values:unknown[]=[]):QueryConfig&{query_timeout:number}=>({text,values,query_timeout:10000})
type AuditRow=QueryResultRow&{actor_id:string;connection_id:string|null;action:string;payload:unknown}
function recoveryPayload(source:AppliedRestoreAuditSource){return {operationId:source.operationId,externalReceiptSha256:source.receiptSha256,originalActorKnown:false,receipt:source.receipt}}
function status(source:AppliedRestoreAuditSource,value:RestoreAuditStatus,known=false):RestoreAuditInspection{return Object.freeze({operationId:source.operationId,receiptSha256:source.receiptSha256,status:value,originalActorKnown:known,cleanupUnconfirmed:false,automaticRestore:false})}
async function one(pool:Pick<Pool,'connect'>,source:AppliedRestoreAuditSource,write:boolean,actorId?:string):Promise<RestoreAuditInspection>{
let client:PoolClient|undefined,begun=false,commitSent=false,committed=false,bad=false,faulted=false,cleanupUnconfirmed=false,result:RestoreAuditInspection
const fault=()=>{faulted=true}
const check=()=>{if(faulted)throw Error('Audit owner unavailable')}
try{
result=await(async()=>{
client=await pool.connect();client.on('error',fault);client.on('end',fault)
const query=async<R extends QueryResultRow=QueryResultRow>(text:string,values:unknown[]=[])=>{check();const result=await client!.query<R>(boundedQuery(text,values));check();return result}
await query(write?'BEGIN ISOLATION LEVEL READ COMMITTED READ WRITE':'BEGIN ISOLATION LEVEL READ COMMITTED READ ONLY');begun=true
await query("SET LOCAL statement_timeout='10s'");await query("SET LOCAL lock_timeout='5s'")
await query('SELECT pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended($1,0))',['mcp-restore-audit:'+source.operationId])
const settings=await query<{days:number;now:string}>('SELECT audit_retention_days AS days,(EXTRACT(EPOCH FROM pg_catalog.clock_timestamp())*1000)::bigint::text AS now FROM public.mcp_settings WHERE id=1 LIMIT 2')
const config=settings.rows[0],finished=Date.parse(source.finishedAt)
if(settings.rows.length!==1||!config||!Number.isInteger(config.days)||config.days<30||config.days>3650||!/^\d{10,16}$/.test(config.now)||!Number.isSafeInteger(Number(config.now))||!Number.isFinite(finished)||finished>Number(config.now)+5000)throw Error('Audit retention/clock unavailable')
if(finished<Number(config.now)-config.days*86400000){const end=await query('ROLLBACK');if(end.command!=='ROLLBACK')throw Error('Audit rollback unconfirmed');begun=false;return status(source,'outside-retention')}
const existing=await query<AuditRow>("SELECT actor_id,connection_id,action,payload FROM public.mcp_audit WHERE action=ANY($1::text[]) AND payload->>'operationId'=$2 ORDER BY id LIMIT 2",[[LIVE,RECOVERED],source.operationId])
if(existing.rows.length>1)throw Object.assign(Error('Audit receipt conflict'),{auditConflict:true})
if(existing.rows.length){
const row=existing.rows[0]
const live=row.action===LIVE&&typeof row.actor_id==='string'&&row.actor_id.length>0&&row.connection_id===null&&isDeepStrictEqual(row.payload,source.receipt)&&(!actorId||row.actor_id===actorId)
const recovered=row.action===RECOVERED&&row.actor_id===IMPORTER&&row.connection_id===null&&isDeepStrictEqual(row.payload,recoveryPayload(source))
if(!live&&!recovered)throw Object.assign(Error('Audit receipt conflict'),{auditConflict:true})
const end=await query('ROLLBACK');if(end.command!=='ROLLBACK')throw Error('Audit rollback unconfirmed');begun=false
return status(source,'already-recorded',live)
}
if(!write){const end=await query('ROLLBACK');if(end.command!=='ROLLBACK')throw Error('Audit rollback unconfirmed');begun=false;return status(source,'missing')}
if(actorId!==undefined&&(typeof actorId!=='string'||!actorId))throw Error('Audit actor unavailable')
const actor=actorId??IMPORTER,action=actorId?LIVE:RECOVERED,payload=actorId?source.receipt:recoveryPayload(source)
await query('INSERT INTO public.mcp_audit(actor_id,action,connection_id,payload,created_at) VALUES($1,$2,NULL,$3::jsonb,$4::timestamptz)',[actor,action,JSON.stringify(payload),source.finishedAt])
commitSent=true;const end=await client!.query(boundedQuery('COMMIT'));if(end.command!=='COMMIT')throw Error('Audit COMMIT acknowledgement unavailable');committed=true;begun=false
if(faulted){bad=true;cleanupUnconfirmed=true}
return status(source,'imported',!!actorId)
})()
}catch(error){
bad=true
result=status(source,commitSent&&!committed?'unconfirmed':error&&typeof error==='object'&&'auditConflict'in error?'conflict':'pending')
}finally{
if(client){
if(begun&&!commitSent&&!faulted)try{const end=await client.query(boundedQuery('ROLLBACK'));if(end.command!=='ROLLBACK'){bad=true;cleanupUnconfirmed=true}}catch{bad=true;cleanupUnconfirmed=true}
try{client.off('error',fault)}catch{bad=true;cleanupUnconfirmed=true}
try{client.off('end',fault)}catch{bad=true;cleanupUnconfirmed=true}
try{client.release(bad||faulted||commitSent&&!committed)}catch{cleanupUnconfirmed=true}
}
}
return cleanupUnconfirmed?Object.freeze({...result,cleanupUnconfirmed:true}):result
}
export async function reconcileRestoreReceiptAudit(pool:Pick<Pool,'connect'>,directory:string,operationId:string,actorId?:string):Promise<RestoreAuditInspection>{
const sources=await readAppliedRestoreAuditSources(directory,env.DATABASE_URL),source=sources.find(value=>value.operationId===operationId)
if(!source)throw Error('Confirmed external receipt unavailable')
return one(pool,source,true,actorId)
}
export async function inspectRestoreReceiptAudit(pool:Pick<Pool,'connect'>,directory:string,operationId:string):Promise<RestoreAuditInspection>{
const sources=await readAppliedRestoreAuditSources(directory,env.DATABASE_URL),source=sources.find(value=>value.operationId===operationId)
if(!source)throw Error('Confirmed external receipt unavailable')
return one(pool,source,false)
}
export async function reconcileDatabaseRestoreAudits(pool:Pick<Pool,'connect'>,directory:string):Promise<void>{
for(const source of await readAppliedRestoreAuditSources(directory,env.DATABASE_URL)){
const result=await one(pool,source,true)
if(result.cleanupUnconfirmed||!['imported','already-recorded','outside-retention'].includes(result.status))throw Object.assign(Error('External restore audit requires reconciliation before managed startup'),{code:'DATABASE_RESTORE_AUDIT_PENDING',auditStatus:result.cleanupUnconfirmed?'cleanup-unconfirmed':result.status})
}
}
@@ -0,0 +1,212 @@
import assert from "node:assert/strict"
import { test } from "node:test"
import { readFile } from "node:fs/promises"
import { runInNewContext } from "node:vm"
import { AsyncLocalStorage } from "node:async_hooks"
import { EventEmitter } from "node:events"
import * as crypto from "node:crypto"
import { performance } from "node:perf_hooks"
import path from "node:path"
import os from "node:os"
import { z } from "zod"
import ts from "typescript"
import type { PoolClient } from "pg"
import type { McpPrincipal } from "../modules/mcp/store.js"
import { DatabaseRequestGate } from "./database-request-gate.js"
import * as operation from "./system-database-operation.js"
import * as maintenance from "../modules/mcp/database-maintenance.js"
async function load<T extends object>(file: URL, modules: Record<string, unknown>): Promise<T> {
const exports = {}
const source = ts.transpileModule(await readFile(file, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, esModuleInterop: true } }).outputText
runInNewContext(source, { exports, require: (name: string) => { assert(name in modules, `Unapproved import ${name}`); return modules[name] }, Buffer, Date, Error, Promise, structuredClone, AbortController, setTimeout, clearTimeout, queueMicrotask, console, process: { env: {} } })
return exports as T
}
function deferred() { let resolve!: () => void; const promise = new Promise<void>(done => { resolve = done }); return { promise, resolve } }
async function bounded<T>(work: Promise<T>): Promise<T> {
let timer: ReturnType<typeof setTimeout> | undefined
try { return await Promise.race([work, new Promise<never>((_, reject) => { timer = setTimeout(() => reject(Error("Owned pure check deadline")), 1500) })]) }
finally { clearTimeout(timer) }
}
const principal: McpPrincipal = { connectionId: "00000000-0000-4000-8000-000000000001", credentialId: "00000000-0000-4000-8000-000000000002", credentialExpiresAt: new Date(Date.now() + 600000).toISOString() }
class McpError extends Error { constructor(readonly code: string, message: string, readonly statusCode = 403) { super(message) } }
async function originFixture(gate: DatabaseRequestGate) {
let checkouts = 0, sqlCalls = 0, commits = 0
let hold: (() => Promise<void>) | undefined
const committed = deferred(), events: string[] = [], generation = Buffer.alloc(32, 71)
const cleanupQuery = async (raw: string | { text: string }) => {
assert(gate.hasActiveAdmission(), "Cleanup SQL must own live admission")
const text = typeof raw === "string" ? raw : raw.text; sqlCalls++; events.push(text)
if (text === "COMMIT") { commits++; if (commits === 2) committed.resolve() }
return { command: text === "BEGIN" || text === "COMMIT" ? text : "UPDATE", rows: text.startsWith("UPDATE public.mcp_operations") ? [{ plan_id: "owned-plan" }] : [] }
}
const pool = { connect: async () => { assert(gate.hasActiveAdmission()); checkouts++; await hold?.(); return { query: cleanupQuery, release() {} } } }
const connection = { generation: generation.toString("hex"), policy: { version: 1, expiresAt: new Date(Date.now() + 600000).toISOString() }, owner_valid_until: new Date(Date.now() + 600000) }
const origin = await load<typeof import("../modules/mcp/operation-execution-origin.js")>(new URL("../modules/mcp/operation-execution-origin.ts", import.meta.url), {
"node:async_hooks": { AsyncLocalStorage }, "node:crypto": crypto, "node:path": path, "node:perf_hooks": { performance },
"node:fs": { readFileSync: (file: string) => Buffer.alloc(32, file.endsWith("access-generation") ? 71 : 72) },
"node:fs/promises": { readFile: async () => Buffer.from(generation) },
"@mmapp/contracts/mcp": { mcpTemporaryGrantSchema: { parse: (value: unknown) => value } },
"../../db/index.js": { pool }, "../../services/shared-database-gate.js": { sharedDatabaseGate: gate },
"../../services/config-mutation-lock.js": { assertNoConfigurationLockContext() {} }, "../../services/operation-config-lock-lease.js": { assertNoOperationConfigurationLockContext() {} },
"./store.js": { validatePrincipal: async () => connection, audit: async (db: { query: typeof cleanupQuery }) => db.query("INSERT INTO inert_audit") },
"./security.js": { McpError, mcpStateDirectory: () => "/inert-owned-state", fingerprint: () => "inert", decryptSecret: async (value: string) => value, privateFingerprinterExisting: async () => () => "inert-key" },
"./actions.js": { findActionByOperation: () => ({ operation: { version: 1 } }) }, "./policy.js": { evaluateMcpPolicy: () => ({ decision: "allowed" }) }, "./change-cost.js": { changeMutationCost: () => 1 },
"./temporary-grant-origin.js": { assertOriginalMcpTemporaryGrant: async () => {} },
})
const mint = async () => {
let id = "", planId = ""
const result = (command: string, rows: unknown[]) => ({ command, rows, rowCount: rows.length })
const client = { query: async (sql: string, args: string[] = []) => {
if (sql.startsWith("SELECT pg_current_xact_id")) return result("SELECT", [{ creating_xid: "123" }])
if (sql.startsWith("INSERT INTO mcp_operations")) { [id, planId] = args; return result("INSERT", [{ id, plan_id: planId, connection_id: principal.connectionId, state: "pending" }]) }
assert(sql.includes("AS creating_transaction")); return result("SELECT", [{ id, plan_id: planId, connection_id: principal.connectionId, creating_transaction: true, operation_id: "users.create", operation_version: 1, policy_version: 1, generation: connection.generation, expires_at: new Date(Date.now() + 600000), evidence: { sealed: "owned-evidence" }, payload: { sealed: "owned-payload" } }])
} }
const start = origin.beginMcpOperationAdmission(principal)
return (await origin.insertMcpOperationAdmission(client as unknown as PoolClient, start, crypto.randomUUID(), crypto.randomUUID(), { proof: "inert", expiresAt: null, monotonicEnd: Infinity, admissionExpiresAt: Date.now() + 600000, admissionMonotonicEnd: performance.now() + 600000 })).admission
}
return { origin, mint, events, committed, counts: () => ({ checkouts, sqlCalls, commits }), hold: (work?: () => Promise<void>) => { hold = work } }
}
async function actualBoundedRunner() {
const runtime = await readFile(new URL("../modules/mcp/runtime.ts", import.meta.url), "utf8")
const begin = runtime.indexOf("let activeRunners = 0"), end = runtime.indexOf("async function policyInput", begin)
assert(begin > 0 && end > begin)
// Execute the existing private slot owner directly, not a mirrored semaphore.
const code = ts.transpileModule(runtime.slice(begin, end) + "\nexports.runBounded = runBounded", { compilerOptions: { target: ts.ScriptTarget.ES2022 } }).outputText
const exports = {} as { runBounded(work: () => Promise<void>): Promise<void> }
runInNewContext(code, { exports, Promise }); return exports.runBounded
}
test("deferred one-shot cleanup owns no bounded runner slot and emits no SQL until repair admission", async () => {
const gate = new DatabaseRequestGate(), f = await originFixture(gate), runBounded = await actualBoundedRunner(), first = await f.mint(), second = await f.mint()
gate.closeAdmission()
await bounded(Promise.all([runBounded(() => f.origin.retireMcpOperationAdmission(first)), runBounded(() => f.origin.retireMcpOperationAdmission(second))]))
assert.deepEqual(f.counts(), { checkouts: 0, sqlCalls: 0, commits: 0 })
await bounded(runBounded(() => gate.exclusive(async () => { assert.equal(f.counts().sqlCalls, 0); gate.openAdmission() })))
await bounded(f.committed.promise); assert.equal(f.counts().commits, 2)
const before = f.counts(); await f.origin.retireMcpOperationAdmission(first); await f.origin.retireMcpOperationAdmission(second); assert.deepEqual(f.counts(), before)
})
test("cleanup already inside admitted work completes while exclusive drain waits, without inversion", async () => {
const gate = new DatabaseRequestGate(), f = await originFixture(gate), cap = await f.mint(), entered = deferred(), release = deferred()
f.hold(async () => { entered.resolve(); await release.promise })
const request = gate.request(() => f.origin.retireMcpOperationAdmission(cap)); await entered.promise
let replaced = false; const exclusive = gate.exclusive(async () => { assert.equal(f.counts().commits, 1); replaced = true })
assert.equal(replaced, false); release.resolve(); await bounded(Promise.all([request, exclusive])); assert.equal(replaced, true)
})
test("temporary exclusive also defers outside cleanup until its callback has completed", async () => {
const gate = new DatabaseRequestGate(), f = await originFixture(gate), cap = await f.mint(), entered = deferred(), release = deferred()
const exclusive = gate.exclusive(async () => { entered.resolve(); await release.promise }); await entered.promise
await bounded(f.origin.retireMcpOperationAdmission(cap)); assert.equal(f.counts().checkouts, 0)
release.resolve(); await exclusive
await bounded(gate.request(async () => { while (f.counts().commits !== 1) await new Promise<void>(resolve => setTimeout(resolve, 0)) }))
})
test("settled request ancestry cannot grant cleanup admission or be laundered into scheduler bootstrap", async () => {
const gate = new DatabaseRequestGate(), entered = deferred(), release = deferred()
let stale: Promise<void> | undefined
await gate.request(async () => { stale = (async () => { entered.resolve(); await release.promise; assert.equal(gate.hasActiveAdmission(), false); assert.throws(() => gate.assertNoRequestContext(), /request context/); await gate.exclusive(async () => { assert(gate.hasActiveAdmission()); assert.throws(() => gate.assertNoRequestContext(), /request context/) }) })() })
await entered.promise; release.resolve(); await stale
await gate.exclusive(async () => { assert(gate.hasActiveAdmission()); assert.doesNotThrow(() => gate.assertNoRequestContext()) })
})
async function serviceFixture(gate: DatabaseRequestGate) {
const calls: string[] = [], readiness = operation.createDatabaseSchemaReadiness()
const spawn = (command: string) => {
calls.push(command); const child = new EventEmitter() as EventEmitter & { stdout: EventEmitter; stderr: EventEmitter; stdin: { end(): void } }
child.stdout = new EventEmitter(); child.stderr = new EventEmitter(); child.stdin = { end: () => queueMicrotask(() => child.emit("close", 0)) }; return child
}
const service = await load<typeof import("./system-database-backup.js")>(new URL("./system-database-backup.ts", import.meta.url), {
"node:crypto": crypto, "node:async_hooks": { AsyncLocalStorage }, "node:fs/promises": { open: async () => ({ writeFile: async () => {}, sync: async () => {}, close: async () => {} }), chmod: async () => {}, mkdir: async () => {}, rm: async () => {}, writeFile: async () => {} }, "node:os": os, "node:path": path, "node:child_process": { spawn },
"../modules/events/service/events-service.js": { appendEvent: async () => {} }, "./database-dump-connection.js": { runPrivateDatabaseDump: async () => Buffer.alloc(0) }, "./database-control-archive-boundary.js": { DATABASE_CONTROL_DUMP_EXCLUDE_SCHEMA_ARG: "--exclude-schema=mmapp_control" },
"./shared-database-gate.js": { sharedDatabaseGate: gate, databaseSchemaReadiness: readiness }, "./config-mutation-policy.js": { assertDatabaseRestoreAllowed: async () => {} }, "../modules/mcp/security.js": { accessGeneration: async () => "inert", invalidateMcpAccess: async () => {}, mcpStateDirectory: () => "/inert-owned-state" }, "../modules/mcp/database-maintenance.js": maintenance,
"../db/migrate.js": { applySqlMigrations: async () => { assert(gate.hasActiveAdmission()); calls.push("migrations") } }, "./system-database-restore-execution.js": { validateSystemDatabaseRestoreArchive() {}, executeSystemDatabaseRestore: async (_buffer: Buffer, _url: string, deps: { run(args: string[]): Promise<void> }) => { await deps.run(["owned.dump"]); return {} } },
"../config.js": { env: { DATABASE_URL: "inert-owned-database" } }, "../db/index.js": { pool: { connect: () => assert.fail("Native SQL forbidden") } }, "../db/operation-lock.js": { withOperationLock: async (_connect: unknown, _key: number, work: () => Promise<unknown>) => { assert(gate.hasActiveAdmission()); calls.push("operation-lock"); return work() } }, "./system-database-operation.js": operation,
"./scheduler.js": { stopSchedulerForDatabaseOperation: async () => {}, restartSchedulerAfterDatabaseOperation: async () => { calls.push("scheduler-restart") } }, "./traffic-flow-ripe.js": { pauseRipeBackgroundWork: async () => {}, resumeRipeBackgroundWork() {} }, "./system-database-cache.js": { invalidateSystemDatabaseCaches() {} },
"./system-database-partition-restore.js": { restorePartitionedDatabase: async (_file: string, _url: string, deps: { beforeTargetReplacement(): void }) => { assert(gate.hasActiveAdmission()); assert(gate.isPaused()); deps.beforeTargetReplacement(); calls.push("replacement") } }, "./traffic-flow-ingest.js": { stopTrafficFlowListenerForDatabaseOperation: async () => {}, restartTrafficFlowListenerAfterDatabaseOperation: async () => {} },
})
return { service, calls }
}
test("terminal helper grants matching inner restore exactly one exclusive admission and revokes stale handoff", async () => {
const gate = new DatabaseRequestGate(), f = await serviceFixture(gate), exclusive = gate.exclusive.bind(gate); let entries = 0
Object.defineProperty(gate, "exclusive", { value: <T>(work: () => Promise<T>) => { entries++; return exclusive(work) } })
let late: Promise<unknown> | undefined; const release = deferred()
await f.service.withSystemDatabaseRestoreAdmission(async () => { await f.service.restoreSystemDatabaseBackup(Buffer.from("PGDMP")); late = release.promise.then(() => f.service.restoreSystemDatabaseBackup(Buffer.from("PGDMP"))) })
assert.equal(entries, 1); assert(f.calls.includes("replacement")); release.resolve(); await late; assert.equal(entries, 2)
await f.service.withSystemDatabaseBackupAdmission(async () => { await assert.rejects(f.service.restoreSystemDatabaseBackup(Buffer.from("PGDMP")), /уже выполняется/) })
})
test("actual terminal first pool checkout and claim are inside exclusive, never during another restore", async () => {
const gate = new DatabaseRequestGate(), f = await serviceFixture(gate); let checkouts = 0, claims = 0
const terminal = await load<typeof import("../modules/mcp/database-restore.js")>(new URL("../modules/mcp/database-restore.ts", import.meta.url), {
"node:crypto": crypto, "node:fs/promises": {}, "node:path": path, zod: { z }, "../../db/index.js": { pool: { connect: async () => { assert(gate.hasActiveAdmission()); assert(gate.isPaused()); checkouts++; return { query: async () => { assert(gate.hasActiveAdmission()); claims++; return { rows: [{ locked: false }] } }, release: () => assert(gate.hasActiveAdmission()) } } } },
"../../lib/permissions.js": {}, "../../services/system-database-restore-execution.js": {}, "../../services/system-database-backup.js": f.service, "../../services/config-mutation-policy.js": {}, "./security.js": {}, "./store.js": {}, "./state-file-publish.js": {}, "../../services/config-mutation-lock.js": {}, "./temporary-grant-origin.js": {}, "../../services/database-restore-audit.js": {},
})
const entered = deferred(), release = deferred(), other = gate.exclusive(async () => { entered.resolve(); await release.promise }); await entered.promise
await assert.rejects(terminal.executeMcpDatabaseRestore(crypto.randomUUID(), principal), /уже выполняется/); assert.equal(checkouts, 0)
release.resolve(); await other
await terminal.executeMcpDatabaseRestore(crypto.randomUUID(), principal); assert.equal(checkouts, 1); assert.equal(claims, 1)
})
test("detached cleanup begun in a live parent pins admission after that parent callback returns", async () => {
const gate = new DatabaseRequestGate(), f = await originFixture(gate), cap = await f.mint()
const checkout = deferred(), releaseCheckout = deferred(), parentReturned = deferred()
f.hold(async () => { checkout.resolve(); await releaseCheckout.promise })
let cleanup!: Promise<void>
const request = gate.request(async () => {
cleanup = f.origin.retireMcpOperationAdmission(cap)
await checkout.promise
parentReturned.resolve()
// Deliberately do not await the detached cleanup.
})
await parentReturned.promise
let replaced = false
const exclusive = gate.exclusive(async () => { assert.equal(f.counts().commits, 1); replaced = true })
await new Promise<void>(resolve => setTimeout(resolve, 10))
assert.equal(replaced, false); assert.equal(f.counts().sqlCalls, 0)
releaseCheckout.resolve()
await bounded(Promise.all([request, cleanup, exclusive]))
assert.equal(replaced, true)
})
test("detached matching restore handoff pins exclusive through an already-started inner restore", async () => {
const gate = new DatabaseRequestGate(), f = await serviceFixture(gate), entered = deferred(), release = deferred()
const scoped = gate.withinAdmission.bind(gate)
Object.defineProperty(gate, "withinAdmission", { value: <T>(work: () => Promise<T>) => scoped(async () => {
entered.resolve(); await release.promise; return work()
}) })
let inner!: Promise<unknown>
const outer = f.service.withSystemDatabaseRestoreAdmission(async () => {
inner = f.service.restoreSystemDatabaseBackup(Buffer.from("PGDMP"))
await entered.promise
})
await entered.promise
await new Promise<void>(resolve => setTimeout(resolve, 10))
assert(gate.isPaused())
await assert.rejects(gate.exclusive(async () => {}), /уже выполняется/)
assert(!f.calls.includes("operation-lock")); assert(!f.calls.includes("replacement"))
release.resolve(); await bounded(Promise.all([outer, inner]))
assert(f.calls.includes("replacement")); assert.equal(gate.isPaused(), false)
})
test("retained child rejection preserves drain and never grants a stale child admission", async () => {
const gate = new DatabaseRequestGate(), started = deferred(), release = deferred()
let child!: Promise<void>, late!: () => Promise<unknown>
const request = gate.request(async () => {
child = gate.withinAdmission(async () => {
const inherited = AsyncLocalStorage.snapshot()
late = () => inherited(() => gate.withinAdmission(async () => assert.fail("Stale work cannot run")))
started.resolve(); await release.promise; throw Error("Expected child rejection")
})
void child.catch(() => {})
await started.promise
})
await started.promise
let replaced = false; const exclusive = gate.exclusive(async () => { replaced = true })
assert.equal(replaced, false); release.resolve()
await assert.rejects(child, /Expected child rejection/)
await bounded(Promise.all([request, exclusive]))
assert.equal(replaced, true)
// Invoke in the inherited child context, not this outside test context.
await assert.rejects(late(), /no longer active/)
})
@@ -0,0 +1,13 @@
import assert from "node:assert/strict"
import test from "node:test"
import { isConfirmedRoutingFibTable } from "./routing-table-fib.js"
test("native FIB switch and explicit enabled representations preserve the table", () => {
for (const fib of ["", true, "true", "yes"]) assert.equal(isConfirmedRoutingFibTable({ name: "main", fib, invalid: "false" }), true)
})
test("absent or false FIB never confirms a forwarding table", () => {
for (const fib of [undefined, null, false, "false", "no", "unknown", "0", 1]) assert.equal(isConfirmedRoutingFibTable({ fib }), false)
})
test("disabled, invalid and ambiguous status cannot be promoted by the FIB switch", () => {
for (const key of ["disabled", "invalid"]) for (const value of [true, "true", "yes", "", null, "unknown"]) assert.equal(isConfirmedRoutingFibTable({ fib: "", [key]: value }), false)
})
@@ -0,0 +1,8 @@
/** RouterOS fib is a CLI switch: its enabled REST representation may be "".
* Do not interpret empty strings as true for general boolean properties.
* https://manual.mikrotik.com/docs/cli-reference/routing/table/ */
export function isConfirmedRoutingFibTable(row: Readonly<Record<string, unknown>>): boolean {
const fib = row.fib
if (![true, "true", "yes", ""].includes(fib as string | boolean)) return false
return ["disabled", "invalid"].every(key => row[key] === undefined || [false, "false", "no"].includes(row[key] as string | boolean))
}
@@ -0,0 +1,56 @@
import assert from "node:assert/strict"
import { test } from "node:test"
import { projectRoutingImmediateGateways, readRoutingTopology } from "./routing-topology-observation.js"
const route = { ".id": "*r", afi: "ip", "dst-address": "192.0.2.0/24", "routing-table": "main", active: "true", contribution: "active", gateway: "198.51.100.1", "immediate-gw": "198.51.100.1%wan", comment: "NEVER_PRIVATE", "bgp.peer-cache-id": "*opaque", "bgp.session": "*opaque" }
const ports = [{ name: "wan", running: "true" }], tables = [{ name: "main", fib: "true" }]
const project = (row = route, version = "7.24.5 (stable)", interfaces: unknown = ports, catalogue: unknown = tables) => projectRoutingImmediateGateways(version, [row], interfaces, catalogue)
test("documented IPv4 final gateway is reachable without operation/BGP namespace assertions", () => {
const result = project(); assert.equal(result.status, "observed"); assert.equal(result.items[0].status, "observed")
const item = result.items[0]; if (item.status !== "observed") throw Error("Missing observation")
assert.deepEqual(item.immediateGateway, { address: "198.51.100.1", interface: "wan" })
assert.equal(item.routeOriginVerified, false); assert.equal(item.recursiveChainVerified, false); assert.equal(item.semanticProof, false)
assert(!JSON.stringify(result).includes("NEVER_PRIVATE")); assert(!JSON.stringify(result).includes("opaque"))
})
test("IPv6 endpoint and directly connected interface are observed separately from full recursion", () => {
const ipv6 = project({ ...route, afi: "ipv6", "dst-address": "2001:db8::/64", "immediate-gw": "2001:db8::1%wan" })
assert.equal(ipv6.items[0].status, "observed")
const direct = project({ ...route, connect: "true", "immediate-gw": "wan" } as typeof route)
assert.equal(direct.items[0].status, "observed")
})
for (const [name, changed] of Object.entries({ multipath: { ecmp: "true" }, absentActive: { active: undefined }, disabled: { disabled: "true" }, unavailable: { "immediate-gw": "" }, numericOnly: { "immediate-gw": "17" }, wrongFamily: { "immediate-gw": "2001:db8::1%wan" }, wrongVrf: { "immediate-gw": "198.51.100.1%wan@other" } })) test(name + " stays unknown", () => {
assert.equal(project({ ...route, ...changed } as typeof route).items[0].status, "unknown")
})
test("unsupported version, missing/duplicate/down interface and missing/nonfib table stay unknown", () => {
assert.equal(project(route, "6.49").status, "unknown")
for (const ports of [[], [{ name: "wan", running: "false" }], [{ name: "wan", running: "true" }, { name: "wan", running: "true" }]]) assert.equal(project(route, "7.24.5", ports).items[0].status, "unknown")
for (const tables of [[], [{ name: "main", fib: "false" }]]) assert.equal(project(route, "7.24.5", ports, tables).items[0].status, "unknown")
assert.equal(projectRoutingImmediateGateways("7.24.5", [route, route], ports, tables).status, "unknown")
})
test("actual topology reader uses fixed GETs/fences/signal/wire bound and publishes no raw RIB", async () => {
const calls: string[] = []; let fences = 0
const client = { async get<T>(path: string, timeout?: number, signal?: AbortSignal, bytes?: number, beforeDispatch?: () => Promise<unknown>) {
assert(timeout! <= 10000); assert.equal(bytes, 1048576); assert(signal); await beforeDispatch?.(); calls.push(path)
return structuredClone(path === "/system/resource" ? [{ version: "7.24.5" }] : path === "/interface" ? ports : path === "/routing/table" ? tables : path === "/routing/route" ? [route] : []) as T
} }
const result = await readRoutingTopology(client, async () => { fences++ }, new AbortController().signal)
assert.equal(calls.length, 11); assert.equal(result.routeResolution.items[0].status, "observed"); assert(fences >= calls.length * 3)
assert(!JSON.stringify(result).includes("NEVER_PRIVATE")); assert.equal(result.observedPath, false); assert.equal(result.routeResolution.captureAtomic, false)
})
test("post-read access failure cannot be swallowed as provider unknown", async () => {
let denied = false
const client = { async get<T>() { denied = true; return [] as T } }
await assert.rejects(readRoutingTopology(client, async () => { if (denied) throw Error("revoked") }), /revoked/)
})
test("cancellation during the last asynchronous publication fence refuses the result", async () => {
const cancel = new AbortController(); let fences = 0
const client = { async get<T>(path: string) { return (path === "/system/resource" ? [{ version: "7.24.5" }] : path === "/interface" ? ports : path === "/routing/table" ? tables : path === "/routing/route" ? [route] : []) as T } }
await assert.rejects(readRoutingTopology(client, async () => { if (++fences === 23) { await Promise.resolve(); cancel.abort() } }, cancel.signal), /Topology read cancelled/)
assert.equal(fences, 23)
})
test("actual RouterOS FIB switch encoding is distinct from an absent or invalid table", () => {
const native = [{ ".id": "*0", name: "main", dynamic: "true", fib: "", invalid: "false" }]
assert.equal(project(route, "7.24.5 (stable)", ports, native).items[0].status, "observed")
for (const table of [{ name: "main" }, { name: "main", fib: "false" }, { name: "main", fib: "", invalid: "true" }, { name: "main", fib: "", disabled: "true" }]) assert.equal(project(route, "7.24.5", ports, [table]).items[0].status, "unknown")
})
@@ -0,0 +1,82 @@
import { isConfirmedRoutingFibTable } from "./routing-table-fib.js"
import { isIP } from "node:net"
import type { MikrotikClient } from "./mikrotik.js"
type Row = Record<string, unknown>
const paths = { interfaces: "/interface", ipv4: "/ip/route", ipv6: "/ipv6/route", tables: "/routing/table", rules: "/routing/rule", vrfs: "/ip/vrf", ospfNeighbors: "/routing/ospf/neighbor", bgpSessions: "/routing/bgp/session" } as const
const flag = (value: unknown) => value === true || value === "true" || value === "yes" ? true : value === false || value === "false" || value === "no" ? false : undefined
const literal = (value: unknown, max = 200): value is string => typeof value === "string" && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value)
const rows = (raw: unknown): Row[] => {
const values = Array.isArray(raw) ? raw : [raw]
if (values.length > 10000 || values.some(row => !row || typeof row !== "object" || Array.isArray(row))) throw Error("Unsupported rows")
return values as Row[]
}
const limits = { semanticProof: false, recursiveChainVerified: false, routeOriginVerified: false, forwardingVerified: false } as const
/** Project documented immediate-gw only. Numeric nexthop/link/interface indexes
* and BGP peer-cache/session namespaces are deliberately never joined.
* https://manual.mikrotik.com/docs/cli-reference/routing/route/ */
export function projectRoutingImmediateGateways(version: unknown, rib: unknown, interfaces: unknown, tables: unknown) {
const unknown = (reason: string) => ({ status: "unknown" as const, reason, items: [], truncated: false, ...limits })
if (!literal(version, 128) || !/^7\.2[0-6](?:\.\d{1,3})?(?: \((?:stable|long-term)\))?$/.test(version)) return unknown("unsupported-routeros-version")
let routes: Row[], ports: Row[], catalogue: Row[]
try { routes = rows(rib); ports = rows(interfaces); catalogue = rows(tables) } catch { return unknown("unavailable-or-malformed-catalogue") }
if (routes.some(row => !literal(row[".id"], 100)) || new Set(routes.map(row => row[".id"])).size !== routes.length) return unknown("ambiguous-route-identity")
const selected = routes.filter(row => ["ip", "ip4", "ipv6", "ip6"].includes(String(row.afi)))
const items = selected.slice(0, 500).map(row => {
const identity = { nativeId: String(row[".id"]), ...limits }
const fail = (reason: string) => ({ ...identity, status: "unknown" as const, reason })
const prefix = row["dst-address"], table = row["routing-table"]
if (!literal(prefix) || !literal(table)) return fail("unconfirmed-prefix-or-table")
const [address, mask, extra] = prefix.split("/"), family = isIP(address)
if (!family || extra !== undefined || !/^\d+$/.test(mask ?? "") || Number(mask) > (family === 4 ? 32 : 128) || family !== (["ip", "ip4"].includes(String(row.afi)) ? 4 : 6)) return fail("unconfirmed-route-family")
if (flag(row.active) !== true || row.contribution !== "active" || ["disabled", "filtered", "unreachable", "blackhole"].some(key => row[key] !== undefined && flag(row[key]) !== false)) return fail("route-not-confirmed-active")
if (row.ecmp !== undefined && flag(row.ecmp) !== false) return fail("multipath-selection-unknown")
const matchingTables = catalogue.filter(entry => entry.name === table)
if (matchingTables.length !== 1 || !isConfirmedRoutingFibTable(matchingTables[0])) return fail("unconfirmed-fib-table")
const immediate = row["immediate-gw"]
if (!literal(immediate)) return fail("immediate-gateway-unavailable")
const endpoint = /^([^%@,\s]+)%([^%@,]+)(?:@([^%@,]+))?$/.exec(immediate)
const connected = flag(row.connect) === true
const iface = endpoint?.[2] ?? (connected && !/[%@,]/.test(immediate) ? immediate : undefined)
if (!iface || endpoint && (isIP(endpoint[1]) !== family || endpoint[3] !== undefined && endpoint[3] !== table)) return fail("unconfirmed-immediate-endpoint")
const matchingPorts = ports.filter(entry => entry.name === iface)
if (matchingPorts.length !== 1 || flag(matchingPorts[0].running) !== true || matchingPorts[0].disabled !== undefined && flag(matchingPorts[0].disabled) !== false) return fail("unconfirmed-immediate-interface")
return { ...identity, status: "observed" as const, family: family === 4 ? "ipv4" as const : "ipv6" as const, prefix, table,
immediateGateway: { address: endpoint?.[1] ?? null, interface: iface }, observationScope: "routeros-reported-final-next-hop" as const }
})
return { status: "observed" as const, routerOsVersion: version, items, truncated: selected.length > 500, ...limits }
}
/** Existing topology reader: bounded GETs, same caller/endpoint fence at actual
* dispatch and publication. Failure remains per-resource observational unknown. */
export async function readRoutingTopology(client: Pick<MikrotikClient, "get">, beforeRead: () => Promise<unknown>, signal?: AbortSignal) {
const deadline = Date.now() + 30000
const acquire = async (path: string) => {
await beforeRead()
const remaining = deadline - Date.now()
if (remaining <= 0 || signal?.aborted) throw Error("Topology read cancelled")
try {
const value = await client.get<unknown>(path, Math.min(10000, remaining), signal, 1048576, beforeRead)
await beforeRead()
return { available: true as const, value: rows(value) }
} catch {
await beforeRead()
return { available: false as const }
}
}
const first = await acquire("/system/resource")
const captured: Partial<Record<keyof typeof paths, Row[]>> = {}, resources: Record<string, unknown> = {}
for (const [key, path] of Object.entries(paths) as Array<[keyof typeof paths, string]>) {
const result = await acquire(path)
if (result.available) {
captured[key] = result.value
resources[key] = { items: result.value.slice(0, 500), truncated: result.value.length > 500, observed: true }
} else resources[key] = { items: [], observed: false, reason: "Устройство не подтвердило возможность чтения" }
}
const rib = await acquire("/routing/route"), last = await acquire("/system/resource")
await beforeRead()
if (signal?.aborted || Date.now() >= deadline) throw Error("Topology read cancelled")
const version = first.available && last.available && first.value.length === 1 && last.value.length === 1 && first.value[0].version === last.value[0].version ? first.value[0].version : undefined
const routeResolution = projectRoutingImmediateGateways(version, rib.available ? rib.value : undefined, captured.interfaces, captured.tables)
return { observedAt: new Date().toISOString(), observedPath: false, routeResolution: { ...routeResolution, captureAtomic: false, sourceGenerationIssued: false }, resources }
}
@@ -1,4 +1,14 @@
import { DatabaseRequestGate } from "./database-request-gate.js"
import { createDatabaseSchemaReadiness } from "./system-database-operation.js"
// One lifetime gate for HTTP handlers, MCP calls, runners and background controllers.
export const sharedDatabaseGate = new DatabaseRequestGate()
// Same process-local readiness boundary for consumers and request admission.
// Exclusive admin restore remains available to repair an unconfirmed schema.
const schemaReadiness = createDatabaseSchemaReadiness()
export const databaseSchemaReadiness = {
beginReplacement() { schemaReadiness.beginReplacement(); sharedDatabaseGate.closeAdmission() },
confirmSchema() { schemaReadiness.confirmSchema(); sharedDatabaseGate.openAdmission() },
assertReady: schemaReadiness.assertReady,
}
+24 -11
View File
@@ -4,7 +4,7 @@ import { AsyncLocalStorage } from "node:async_hooks"
import { runPrivateDatabaseDump } from "./database-dump-connection.js"
import { DATABASE_CONTROL_DUMP_EXCLUDE_SCHEMA_ARG } from "./database-control-archive-boundary.js"
import { open } from "node:fs/promises"
import { sharedDatabaseGate } from "./shared-database-gate.js"
import { databaseSchemaReadiness as schemaReadiness, sharedDatabaseGate } from "./shared-database-gate.js"
import { assertDatabaseRestoreAllowed } from "./config-mutation-policy.js"
import { accessGeneration, invalidateMcpAccess, mcpStateDirectory } from "../modules/mcp/security.js"
import { executeMcpDatabaseMaintenance } from "../modules/mcp/database-maintenance.js"
@@ -17,7 +17,7 @@ import { executeSystemDatabaseRestore, validateSystemDatabaseRestoreArchive, typ
import { env } from "../config.js"
import { pool } from "../db/index.js"
import { withOperationLock } from "../db/operation-lock.js"
import { createDatabaseSchemaReadiness, executeDatabaseOperation, type DatabaseOperationWarnings } from "./system-database-operation.js"
import { executeDatabaseOperation, type DatabaseOperationWarnings } from "./system-database-operation.js"
import { restartSchedulerAfterDatabaseOperation, stopSchedulerForDatabaseOperation } from "./scheduler.js"
import { pauseRipeBackgroundWork, resumeRipeBackgroundWork } from "./traffic-flow-ripe.js"
import { invalidateSystemDatabaseCaches } from "./system-database-cache.js"
@@ -29,9 +29,9 @@ import {
const BACKUP_LOCK_KEY = 8_723_102
const backupAdmission = new AsyncLocalStorage<{ active: boolean }>()
// Shared across operations: a later export must not restart consumers after an
// unconfirmed restore. This latch intentionally makes no cross-process claim.
const schemaReadiness = createDatabaseSchemaReadiness()
const restoreAdmission = new AsyncLocalStorage<{ active: boolean }>()
// The shared readiness latch also refuses new request/worker admission after
// unconfirmed replacement; startup recovery supplies the cross-process guard.
/** Acquire before worker/config locks so admitted requests can drain without a lock inversion. */
export function withSystemDatabaseBackupAdmission<T>(work: () => Promise<T>) {
return sharedDatabaseGate.exclusive(async () => {
@@ -40,6 +40,14 @@ export function withSystemDatabaseBackupAdmission<T>(work: () => Promise<T>) {
})
}
/** Terminal MCP claim and restore share one exclusive admission from first SQL. */
export function withSystemDatabaseRestoreAdmission<T>(work: () => Promise<T>) {
return sharedDatabaseGate.exclusive(async () => {
const admission = { active: true }
try { return await restoreAdmission.run(admission, work) } finally { admission.active = false }
})
}
function fmtTimestamp(date = new Date()): string {
const pad = (n: number) => String(n).padStart(2, "0")
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}_${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`
@@ -70,7 +78,7 @@ function spawnCollected(command: string, args: string[], opts?: { stdin?: Buffer
})
}
async function withDatabaseOperation<T extends object>(fn: () => Promise<T>, admittedBackup = false): Promise<T & DatabaseOperationWarnings> {
async function withDatabaseOperation<T extends object>(fn: () => Promise<T>, admittedKind?: "backup" | "restore"): Promise<T & DatabaseOperationWarnings> {
const execute = () => withOperationLock(() => pool.connect(), BACKUP_LOCK_KEY, () => executeDatabaseOperation(fn, {
stopListener: stopTrafficFlowListenerForDatabaseOperation,
beforeRestart: schemaReadiness.assertReady,
@@ -84,7 +92,8 @@ async function withDatabaseOperation<T extends object>(fn: () => Promise<T>, adm
await restartSchedulerAfterDatabaseOperation()
},
}), "Операция с базой приложения уже выполняется")
return admittedBackup && backupAdmission.getStore()?.active ? execute() : sharedDatabaseGate.exclusive(execute)
const admission = admittedKind === "backup" ? backupAdmission.getStore() : admittedKind === "restore" ? restoreAdmission.getStore() : undefined
return admission?.active ? sharedDatabaseGate.withinAdmission(execute) : sharedDatabaseGate.exclusive(execute)
}
export async function exportSystemDatabaseBackup(options?: { signal?: AbortSignal; authorize?: () => Promise<void> }): Promise<{ filename: string; buffer: Buffer } & DatabaseOperationWarnings> {
@@ -100,7 +109,7 @@ export async function exportSystemDatabaseBackup(options?: { signal?: AbortSigna
filename: `mikrotik-manager_${fmtTimestamp()}.dump`,
buffer,
}
}, true)
}, "backup")
}
/** REST and MCP share the same export event; event failure never erases a completed dump. */
@@ -123,7 +132,7 @@ export async function restoreSystemDatabaseBackup(buffer: Buffer, options?: { be
await accessGeneration()
const journal = await open(path.join(mcpStateDirectory(), "database-maintenance.jsonl"), "a", 0o600), operationId = randomUUID()
const record = async (state: string) => { await journal.writeFile(JSON.stringify({ operationId, state, at: new Date().toISOString() }) + "\n"); await journal.sync() }
return executeMcpDatabaseMaintenance(async () => {
const outcome = await executeMcpDatabaseMaintenance(async () => {
const result = await executeSystemDatabaseRestore(buffer, env.DATABASE_URL, {
allocate: async () => {
const tempDir = path.join(os.tmpdir(), "mmapp-db-restore")
@@ -142,11 +151,15 @@ export async function restoreSystemDatabaseBackup(buffer: Buffer, options?: { be
remove: async file => { await Promise.all([rm(file, { force: true }), rm(`${file}.sql`, { force: true })]) },
invalidate: invalidateSystemDatabaseCaches,
})
try { await applySqlMigrations(pool); schemaReadiness.confirmSchema() }
try { await applySqlMigrations(pool) }
catch { result.migrationError = "База восстановлена, но миграции приложения не завершены. Требуется проверка схемы; не повторяйте restore." }
return result
}, { record, invalidateAccess: invalidateMcpAccess, close: () => journal.close() })
})
// Neither uncertain replacement nor unconfirmed external completion may
// release request admission or restart consumers. Explicit restore can repair.
if (!outcome.migrationError && !outcome.maintenanceError) schemaReadiness.confirmSchema()
return outcome
}, "restore")
}
export async function assertSystemDatabaseRestoreTools() {
@@ -1,4 +1,5 @@
import assert from "node:assert/strict"
import Fastify from "fastify"
import { test } from "node:test"
import { readFile } from "node:fs/promises"
import { EventEmitter } from "node:events"
@@ -7,6 +8,7 @@ import os from "node:os"
import path from "node:path"
import { runInNewContext } from "node:vm"
import ts from "typescript"
import { DatabaseRequestGate } from "./database-request-gate.js"
import * as operation from "./system-database-operation.js"
import * as restore from "./system-database-restore-execution.js"
import * as partitionRestore from "./system-database-partition-restore.js"
@@ -17,7 +19,17 @@ import * as archiveBoundary from "./database-control-archive-boundary.js"
// configured pool, native subprocess, filesystem journal or network is opened.
async function fixture() {
const calls: string[] = []
let failure: "decode" | "psql" | "migration" | "cleanup" | undefined
// Execute the actual shared singleton wiring in a fresh inert module.
const shared = {} as { sharedDatabaseGate: DatabaseRequestGate; databaseSchemaReadiness: ReturnType<typeof operation.createDatabaseSchemaReadiness> }
const sharedSource = await readFile(new URL("./shared-database-gate.ts", import.meta.url), "utf8")
const sharedCompiled = ts.transpileModule(sharedSource, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText
runInNewContext(sharedCompiled, { exports: shared, require: (name: string) => {
if (name === "./database-request-gate.js") return { DatabaseRequestGate }
if (name === "./system-database-operation.js") return operation
throw Error(`Unapproved shared import ${name}`)
} })
const { sharedDatabaseGate: gate, databaseSchemaReadiness } = shared
let failure: "decode" | "psql" | "migration" | "cleanup" | "journal-applied" | "journal-close" | "revoke" | undefined
const step = (name: string) => async () => { calls.push(name) }
const spawn = (command: string, args: string[]) => {
const child = new EventEmitter() as EventEmitter & { stdout: EventEmitter; stderr: EventEmitter; stdin: { end(): void } }
@@ -33,7 +45,7 @@ async function fixture() {
return child
}
const files = {
open: async () => ({ writeFile: async () => {}, sync: async () => {}, close: async () => {} }),
open: async () => { let state = ""; return { writeFile: async (raw: string) => { state = JSON.parse(raw).state }, sync: async () => { if (failure === "journal-applied" && state === "applied") throw Error("synthetic applied fsync failure") }, close: async () => { if (failure === "journal-close") throw Error("synthetic close failure") } } },
chmod: async () => {}, mkdir: async () => {}, writeFile: async () => {},
rm: async () => { if (failure === "cleanup") throw Error("synthetic cleanup failure") },
}
@@ -43,9 +55,9 @@ async function fixture() {
"../modules/events/service/events-service.js": { appendEvent: async () => {} },
"./database-dump-connection.js": { runPrivateDatabaseDump: async () => { throw Error("Unused private dump") } },
"./database-control-archive-boundary.js": archiveBoundary,
"./shared-database-gate.js": { sharedDatabaseGate: { exclusive: (work: () => unknown) => work() } },
"./shared-database-gate.js": { sharedDatabaseGate: gate, databaseSchemaReadiness },
"./config-mutation-policy.js": { assertDatabaseRestoreAllowed: async () => {} },
"../modules/mcp/security.js": { accessGeneration: async () => "fixture", invalidateMcpAccess: async () => {}, mcpStateDirectory: () => "inert-state" },
"../modules/mcp/security.js": { accessGeneration: async () => "fixture", invalidateMcpAccess: async () => { if (failure === "revoke") throw Error("synthetic revoke failure") }, mcpStateDirectory: () => "inert-state" },
"../modules/mcp/database-maintenance.js": maintenance,
"../db/migrate.js": { applySqlMigrations: async () => { calls.push("migration"); if (failure === "migration") throw Error("synthetic migration failure") } },
"./system-database-restore-execution.js": restore,
@@ -63,7 +75,7 @@ async function fixture() {
const source = await readFile(new URL("./system-database-backup.ts", import.meta.url), "utf8")
const compiled = ts.transpileModule(source, { 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, process: { env: {} }, Error, Date, Promise }, { filename: "system-database-backup.ts" })
return { calls, exports, fail: (phase?: typeof failure) => { failure = phase } }
return { calls, exports, gate, fail: (phase?: typeof failure) => { failure = phase } }
}
const archive = Buffer.from("PGDMPinert")
const restartCalls = (calls: string[]) => calls.filter(c => ["start listener", "start scheduler", "resume ripe"].includes(c))
@@ -73,23 +85,48 @@ test("actual restore leaves consumers stopped after migration failure, including
const result = await f.exports.restoreSystemDatabaseBackup(archive)
assert.match(String(result.migrationError), /миграции/); assert.match(String(result.schedulerError), /не подтверждена/)
assert.equal(f.calls.filter(c => c === "psql").length, 1); assert.deepEqual(restartCalls(f.calls), [])
let dispatched = 0
await assert.rejects(f.gate.request(async () => { dispatched++ }), /не подтверждена/)
assert.equal(dispatched, 0); assert.equal(f.gate.isPaused(), true)
let resumed = false
const waiter = f.gate.whenOpen().then(() => { resumed = true })
f.fail(); await f.exports.exportSystemDatabaseBackup(); assert.deepEqual(restartCalls(f.calls), [])
await Promise.resolve(); assert.equal(resumed, false)
void waiter
await assert.rejects(f.gate.request(async () => { dispatched++ }), /не подтверждена/)
assert.equal(dispatched, 0)
})
test("actual restore preserves unknown replacement failure without restarting or retrying", async () => {
const f = await fixture(); f.fail("psql")
await assert.rejects(f.exports.restoreSystemDatabaseBackup(archive), /synthetic psql failure/)
assert.equal(f.calls.filter(c => c === "psql").length, 1); assert(!f.calls.includes("migration")); assert.deepEqual(restartCalls(f.calls), [])
assert.equal(f.gate.isPaused(), true)
let dispatched = 0
await assert.rejects(f.gate.request(async () => { dispatched++ }), /не подтверждена/)
assert.equal(dispatched, 0)
// Repair uses the existing exclusive service boundary, outside request admission.
let resumed = false
const waiter = f.gate.whenOpen().then(() => { resumed = true })
await Promise.resolve(); assert.equal(resumed, false)
f.fail("revoke"); await assert.rejects(f.exports.restoreSystemDatabaseBackup(archive), /synthetic revoke failure/)
await Promise.resolve(); assert.equal(resumed, false); assert.equal(f.calls.filter(c => c === "psql").length, 1)
await assert.rejects(f.gate.request(async () => { dispatched++ }), /не подтверждена/)
f.fail(); await f.exports.restoreSystemDatabaseBackup(archive)
await waiter; assert.equal(resumed, true); assert.equal(f.gate.isPaused(), false)
await f.gate.request(async () => { dispatched++ }); assert.equal(dispatched, 1)
})
test("archive decoding failure before target replacement permits normal restart", async () => {
const f = await fixture(); f.fail("decode")
await assert.rejects(f.exports.restoreSystemDatabaseBackup(archive), /synthetic decode failure/)
assert(!f.calls.includes("psql")); assert.deepEqual(restartCalls(f.calls), ["start listener", "resume ripe", "start scheduler"])
await f.gate.request(async () => {})
})
test("acknowledged schema restarts consumers even when temporary cleanup fails", async () => {
const f = await fixture(); f.fail("cleanup")
const result = await f.exports.restoreSystemDatabaseBackup(archive)
assert.match(String(result.cleanupError), /временный файл/); assert.equal(result.migrationError, undefined)
assert(f.calls.indexOf("migration") < f.calls.indexOf("start listener"))
await f.gate.request(async () => {})
assert.deepEqual(restartCalls(f.calls), ["start listener", "resume ripe", "start scheduler"])
})
test("ordinary backup retains its restart behavior", async () => {
@@ -103,5 +140,45 @@ test("a separate explicit restore clears the latch only after acknowledged migra
const result = await f.exports.restoreSystemDatabaseBackup(archive)
assert.equal(result.migrationError, undefined); assert.equal(result.schedulerError, undefined)
assert(f.calls.indexOf("migration") < f.calls.indexOf("start listener"))
await f.gate.request(async () => {})
assert.deepEqual(restartCalls(f.calls), ["start listener", "resume ripe", "start scheduler"])
})
for (const phase of ["journal-applied", "journal-close"] as const) test(`acknowledged replacement with ${phase} stays closed until explicit repair`, async () => {
const f = await fixture(); f.fail(phase)
const result = await f.exports.restoreSystemDatabaseBackup(archive)
assert.equal(typeof result.maintenanceError, "string"); assert.deepEqual(restartCalls(f.calls), [])
let dispatched = 0, resumed = false
await assert.rejects(f.gate.request(async () => { dispatched++ }), /не подтверждена/)
const waiting = f.gate.whenOpen().then(() => { resumed = true })
await Promise.resolve(); assert.equal(resumed, false); assert.equal(dispatched, 0)
f.fail(); await f.exports.restoreSystemDatabaseBackup(archive); await waiting
await f.gate.request(async () => { dispatched++ }); assert.equal(dispatched, 1)
})
test("actual HTTP gate preserves only the existing backup/restore repair exclusions", async () => {
const f = await fixture(), app = Fastify()
const plugin = {} as { installDatabaseRequestGate(app: ReturnType<typeof Fastify>): void }
const source = await readFile(new URL("../plugins/database-request-gate.ts", import.meta.url), "utf8")
const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText
runInNewContext(compiled, { exports: plugin, require: (name: string) => {
if (name === "../services/shared-database-gate.js") return { sharedDatabaseGate: f.gate }
if (name === "../services/config-mutation-policy.js") return { withConfigurationActor: (_actor: string, work: () => unknown) => work() }
throw Error(`Unapproved HTTP adapter import ${name}`)
} })
plugin.installDatabaseRequestGate(app)
let ordinarySql = 0
app.get("/api/system/database/status", async () => { ordinarySql++; return { ok: true } })
app.post("/api/system/database/restore", async () => f.exports.restoreSystemDatabaseBackup(archive))
app.get("/api/system/database/backup", async () => f.exports.exportSystemDatabaseBackup())
try {
f.fail("psql")
assert.equal((await app.inject({ method: "POST", url: "/api/system/database/restore" })).statusCode, 500)
assert.equal((await app.inject("/api/system/database/status")).statusCode, 503); assert.equal(ordinarySql, 0)
f.fail()
assert.equal((await app.inject("/api/system/database/backup")).statusCode, 200)
assert.equal((await app.inject("/api/system/database/status")).statusCode, 503); assert.equal(ordinarySql, 0)
assert.equal((await app.inject({ method: "POST", url: "/api/system/database/restore" })).statusCode, 200)
assert.equal((await app.inject("/api/system/database/status")).statusCode, 200); assert.equal(ordinarySql, 1)
} finally { await app.close() }
})
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,36 @@
{
"date": "2026-11-03",
"status": "source-applied-lifecycle-http13-build-lint-passed",
"patchSha256": "c06b311797bbcdefbc1b5ab526adeb3cc5c98b447f4c9fef8c22aa3a52ddcdec",
"files": [
{
"path": "backend/src/services/database-request-gate.ts",
"appliedSha256": "03f9f8f6a50cdb1b20281281e8e59912924cb805980e54cc7b00cfd157006d86"
},
{
"path": "backend/src/services/shared-database-gate.ts",
"appliedSha256": "3e5977c000b548ff6fcac66a7da2ea0dab5c420b3a0021e6c3c3872271145217"
},
{
"path": "backend/src/services/system-database-backup.ts",
"appliedSha256": "c8731fc719cc98e904461d558b94df17c6e45772678fa91a311f275eb68ffd7e"
},
{
"path": "backend/src/services/system-database-schema-readiness.test.ts",
"appliedSha256": "62db42b482b4e1bf395f54217a516ef4e6ecf97b910759dafc5f25d8c7ff2fa5"
}
],
"originalRequirement": "mcp-implementation-plan.md section3.4: stop streams/background work, exclusive restore, durable outcome and restart reconciliation",
"behavior": "Unconfirmed schema or external maintenance completion keeps HTTP/MCP/worker admission and consumers closed after exclusive operation; explicit human repair remains available. Confirmation waits for migrations and external applied journal/close acknowledgement.",
"testsPassed": 13,
"permanentRegression": "backend/src/services/system-database-schema-readiness.test.ts",
"backendBuildExitCode": 0,
"focusedLintExitCode": 0,
"independentSourceReview": "no concrete blockers; cycles/order/waiter/repair reviewed",
"genuinePgAcceptance": false,
"linuxCurrentImageAcceptance": false,
"deploymentPerformed": false,
"rawExternalSqlFenced": false,
"fullDbDirectionComplete": false,
"fullPlanComplete": false
}
+74
View File
@@ -0,0 +1,74 @@
# Ручная проверка БД после неопределённого восстановления
Команды `maintenance-inspect` и `maintenance-resolve` входят в текущий собранный оператор `backend/dist-operator/database-writer-profile-operator.mjs`. Их Linux/POSIX и PostgreSQL-приёмка прошла через локальное подключение и TLS: [16 сценариев](mcp-linux-maintenance-resolution-acceptance-20261103.json). Опубликованный ранее preview-образ этим отчётом не обновляется.
После неопределённого восстановления обычные HTTP/MCP-запросы, планировщик и приём NetFlow остаются закрытыми. Существующий административный путь явного восстановления доступен. Не повторяйте прежнее действие автоматически: отсутствие ответа не означает, что данные не изменились.
Этот порядок позволяет человеку принять **наблюдаемое текущее состояние**. Он не доказывает соответствие данных исходному архиву и не превращает старые `unknown` в успешные операции.
## Подготовка
1. Остановите управляемый backend штатным способом. Сохраните его состояние и параметры запуска. PostgreSQL должна остаться доступной оператору; параллельное обслуживание БД недопустимо.
2. Выберите проверенный образ с текущими командами оператора. Используйте существующий том состояния MCP, а не новый пустой том.
3. Запускайте оператор от фактического владельца тома, с тем же UID, что использует backend. Каталоги должны принадлежать ему и быть закрыты от других пользователей; файлы входных данных — иметь права `0600`. Команда не исправляет владельцев и права автоматически.
4. Подготовьте отдельный закрытый каталог входных данных и отдельный каталог результатов. Монтируйте их только в контейнер оператора, не в обычный backend/MCP. Пароли передаются в защищённом файле транспорта, не в аргументах команды.
Нужны два независимо проверенных файла:
- `runtime-target.json`: `format=database-maintenance-runtime-target-v1`, `databaseUrl` с адресом, портом и именем настроенной БД **без имени пользователя и пароля**. Это конфигурация назначения backend, а не произвольная другая БД.
- `transport.json`: `format=database-maintenance-transport-v1`, выбранный `mode` и `connection` с полями `host`, `port`, `user`, `password`, `database`.
Для `mode=tls` дополнительно нужны `tls.serverName`, `tls.spkiSha256`, защищённый CA-файл и его проверенный SHA256. TLS — режим по умолчанию; после его ошибки переключения на незашифрованное соединение нет.
Для обычной Docker-установки без TLS поддерживается явный `mode=local-loopback`. Отдельный контейнер оператора должен присоединиться к сетевому пространству **точного проверенного контейнера PostgreSQL**: `--network "container:$REVIEWED_PG_ID"`. В `connection.host` допустимы только `127.0.0.1` или `::1`; параметры CA в этом режиме запрещены. Host networking и Docker socket не нужны. Настроенное назначение backend в `runtime-target.json` при этом сохраняется.
## Инспекция
В отдельном контейнере оператора выполните команду с проверенными путями и SHA256:
```sh
node backend/dist-operator/database-writer-profile-operator.mjs maintenance-inspect \
--state-dir /app/data/mcp \
--transport /run/review/transport.json --transport-sha256 "$TRANSPORT_SHA256" \
--runtime-target /run/review/runtime-target.json --runtime-target-sha256 "$TARGET_SHA256" \
--output /run/output/inspection.json
```
Для TLS добавьте `--ca /run/review/ca.pem --ca-sha256 "$CA_SHA256"`. `/app/data/mcp` — пример: используйте фактический `MCP_STATE_DIR` установки.
Инспекция только читает БД. Она проверяет физическую идентичность PostgreSQL, текущие миграции и схему, внешний журнал, поколение доступа и квитанции. Необязательная служебная схема `mmapp_control` не требуется для обычной установки. Результат содержит частные сведения о схеме; храните его в закрытом каталоге.
Если `humanApprovalRequired=false` и состояние уже принято, повторное разрешение не требуется. Не создавайте вторую запись ради подтверждения потерянного ответа.
## Решение человека
Человек отдельно проверяет инспекцию и создаёт защищённый `approval.json` со следующими полями:
```json
{
"format": "database-maintenance-current-state-approval-v1",
"resolutionId": "<новый UUID>",
"decision": "accept-observed-current-state",
"actorId": "<идентификатор оператора>",
"inspectionSha256": "<проверенный SHA256 inspection.json>",
"issuedAt": "<UTC ISO timestamp>",
"expiresAt": "<UTC ISO timestamp, не более 15 минут после issuedAt>",
"managedBackendStopped": true,
"historicOutcomesRemainUnknown": true,
"automaticRestore": false
}
```
Приложение и модель не создают это решение за человека. Проверенный SHA256 разрешения передаётся отдельно. Инспекция и разрешение должны оставаться действительными до публикации решения.
Вызовите `maintenance-resolve` с теми же параметрами назначения и транспорта, добавив `--inspection FILE --inspection-sha256 SHA --approval FILE --approval-sha256 SHA`. Параметр `--output` здесь не используется.
Оператор повторно читает текущее состояние, сверяет его с инспекцией и разрешением, завершает SQL-владельца и публикует неизменяемую fsync-запись решения вне PostgreSQL. Исходные журнал, квитанции и поколение доступа не переписываются. DDL, восстановление архива и выдача дополнительных SQL-прав не выполняются.
## Возобновление и повторная проверка
После подтверждённого решения запустите backend штатным способом и проверьте его готовность. Проверка при старте должна разрешить только точно покрытую историческую неопределённость. Новая незавершённая операция, изменение покрытых файлов, другое назначение БД, повреждённая запись или оставшийся lease снова блокируют запуск до SQL.
Если ответ `maintenance-resolve` потерян, начните с новой инспекции: существующее историческое решение должно быть прочитано, а не опубликовано повторно. Не удаляйте lease или записи решения по возрасту/PID. Подтверждённое историческое решение сохраняет силу после истечения срока использованного разрешения; новое решение требует нового разрешения.
Исторические `unknown` остаются такими в квитанциях. В SQL-аудит импортируются только действительно подтверждённые квитанции `applied`; при неизвестном исходном авторе используется отдельная служебная запись сверки.
@@ -0,0 +1,51 @@
{
"date": "2026-11-03",
"status": "source-applied-regression9-system-database-build-lint-passed",
"patchSha256": "077a4ba67ebce2777b77d06d3377f4222670baf398169dbbf7493c784c7bcdc8",
"files": [
{
"path": "backend/src/services/database-request-gate.ts",
"appliedSha256": "906480322a25eaa20957725eb02dc52d9eed6ad52846378bb0657afa39952364"
},
{
"path": "backend/src/services/system-database-backup.ts",
"appliedSha256": "1c80b893a60d0e72fe63fca924e7740e0bf918637d94aa4a316e87ef5533e4ca"
},
{
"path": "backend/src/modules/mcp/operation-execution-origin.ts",
"appliedSha256": "beed7abf1dac0b325790ba6cc103c35fe86094ab8fa3cfad846a0b9f7cbfe1d7"
},
{
"path": "backend/src/modules/mcp/database-restore.ts",
"appliedSha256": "c0f386c95cd333715cd4cae7155f055743815edf745551069d604dd7e6597032"
},
{
"path": "backend/src/services/detached-database-admission.test.ts",
"appliedSha256": "79871b92fdb6b4d2ab3808073974492333ce04f80b46b4a21a7cbafce561b116"
},
{
"path": "backend/package.json",
"appliedSha256": "21cfd7026d487a6d0dcb96f79d76361790da07d17982a7e003ceaec79adc4001"
}
],
"originalGap": [
"Detached interruption cleanup could use SQL outside database admission after runner refusal.",
"Terminal restore claimed its operation before acquiring the shared exclusive boundary."
],
"rejectedV1": "An inherited live-admission boolean did not retain ownership across delayed checkout when its detached parent settled.",
"correction": "Retained continuations register synchronously in the active admission and drain before request/exclusive retirement, including descendants. Cleanup waiting for repair releases runner slots. Matching inner restore retains exclusive lifetime. Terminal first checkout and advisory claim are inside exclusive; late cleanup SQL dispatch refuses.",
"focusedPermanentCasesPassed": 9,
"existingGateReadinessOriginChecksPassed": 25,
"systemDatabaseProfileExitCode": 0,
"systemDatabaseNativePosixCaseSkippedOnWindows": true,
"backendBuildExitCode": 0,
"focusedLintExitCode": 0,
"noNewDatabaseRolesOrSourceFramework": true,
"rawExternalSqlFenced": false,
"genuinePgAccepted": false,
"linuxCurrentImageAccepted": false,
"originalServicesChanged": false,
"deploymentPerformed": false,
"fullDbComplete": false,
"fullPlanComplete": false
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,31 @@
{
"date": "2026-11-03",
"status": "audit-seven-accepted-maintenance-loopback-refused-tls-not-run",
"archiveSha256": "f4153fdc55db542f7da6c4d6b589ae10f3c6c48c404442239130bb9bf0787007",
"summary": "tmp/mcp-linux-critical-capsule-20261103/private-linux-run-2c37b62e7ebfbcafd63829f7f53182d42d421ab02dd79a57/summary.json",
"summarySha256": "de09d3e28dfe0c14f9d1197ee973e6f5d4382403f557793f02b05bc1d059393e",
"audit": {
"accepted": true,
"genuineSqlHttpCasesPassed": 7,
"sourceSnapshot": "before-optional-control-schema-fix"
},
"maintenance": {
"loopbackAccepted": false,
"tlsExecuted": false,
"failureFrame": "writer-profile/maintenance-resolution.mjs:128",
"maskedOriginalFailure": true,
"sourceDefectsFound": [
"Unconditionally required optional mmapp_control namespace absent from ordinary44 migrations",
"Primary observation error replaced by cleanup assertion despite confirmed retirement"
],
"fixSourceApplied": true,
"targetedTestsPassed": 17,
"newLinuxAcceptanceRequired": true
},
"originalServicesUnchanged": true,
"ownResourcesRemoved": true,
"remotePacketRemoved": true,
"observedOom": false,
"wholeInstallation2GbAccepted": false,
"fullPlanComplete": false
}
@@ -0,0 +1,65 @@
{
"date": "2026-11-03",
"status": "genuine-linux-loopback-and-tls-accepted",
"archiveSha256": "da38596744f88f4a79b2d8524828b907cd75da4e252181518e5e42eccdfbcdf0",
"summary": "tmp/mcp-linux-maintenance-v2-20261103/private-linux-run-1e241fd4bacaf2a3335389fff03fd7cf3f37643d2652154b/summary.json",
"summarySha256": "c8a73b1b09de7590873f5bdfa6f8deb99d60e5f71897d579db602abc34393011",
"cases": [
{
"case": "maintenance-loopback",
"passed": true,
"genuinePostgres": true,
"nativePosixProtection": true,
"cases": [
"current44",
"unknown-before-sql",
"native-inspect",
"native-resolve",
"applied-audit-only",
"native-permission-refusal",
"inspection-reconciliation",
"new-unknown-before-sql"
],
"report": "tmp/mcp-linux-maintenance-v2-20261103/private-linux-run-1e241fd4bacaf2a3335389fff03fd7cf3f37643d2652154b/run-maintenance-loopback-e5cf38096078701fecc2233db5a67da23bde308191c67887/work/maintenance-result.json",
"reportSha256": "936ff1178e2a2cd5a14882766b28df0d708a91b220782d7f211bf2a1f8785675"
},
{
"case": "maintenance-tls",
"passed": true,
"genuinePostgres": true,
"nativePosixProtection": true,
"cases": [
"current44",
"unknown-before-sql",
"native-inspect",
"native-resolve",
"applied-audit-only",
"native-permission-refusal",
"inspection-reconciliation",
"new-unknown-before-sql"
],
"report": "tmp/mcp-linux-maintenance-v2-20261103/private-linux-run-1e241fd4bacaf2a3335389fff03fd7cf3f37643d2652154b/run-maintenance-tls-508773039b44baa36c448d2425b9a8d4703546c46c7ca9fb/work/maintenance-result.json",
"reportSha256": "95967fc7d67f6e46b12dbf888b0fc62f13d3810df11ca104e54cdb8c453a4470"
}
],
"totalCasesPassed": 16,
"currentMigrations": 44,
"nodeMajor": 22,
"capsMiB": {
"node": 320,
"postgres": 192,
"total": 512
},
"originalServicesUnchanged": true,
"ownResourcesRemoved": true,
"remotePacketRemoved": true,
"oomObserved": false,
"destructiveRestoreDispatched": false,
"syntheticApprovalOnly": true,
"historicalUnknownOutcomesChanged": false,
"archiveDataEquivalenceProved": false,
"componentSnapshotBeforeLaterLiveAdmissionPatch": true,
"wholeInstallation2GbAccepted": false,
"fullDbDirectionComplete": false,
"fullPlanComplete": false
}
@@ -0,0 +1,103 @@
{
"date": "2026-11-03",
"status": "genuine-linux-three-original-effect-sql-runtime-cases-accepted-component",
"archiveSha256": "fe521d7a531115f373e33928501aa4a11ae851023867dcb6bddbd885e7135072",
"manifestSha256": "e711d8d70395ab3c2e2b359bc7492c60e87115a9dcd19881d72813573e7fb82e",
"componentSummary": "tmp\\mcp-linux-routing-effects-v5-20261103\\private-linux-run-773fa7af00073e4ce95a5b48ecdb97a6eaf5f1bac53970e6\\run-routing-original-effects-native-restore-5fa22ea9d2a08889f006eda8c3ce65f1c9f0aa88b5d5018a\\work\\original-effects-sql-runtime-summary.json",
"componentSummarySha256": "bbd681475a648c3a61799d0504e79765b03b88859ef666935aad98f9b5f735e9",
"cases": [
{
"case": "transition",
"manualState": "applied",
"configurationCheckpoint": true,
"actualPrivateExecutionOrigin": true,
"effectStatus": "observed-expected",
"observedFibTransition": true,
"nativeRibBefore": 1,
"nativeRibAfter": 2,
"sharedBudget": {
"deadline": 1793721833129,
"calls": 168,
"bytes": 20246,
"rows": 207
},
"effectBaselineAdvanceAccepted": true,
"effectGateNativeReads": "fresh-native-checked",
"actualTickPausedByExistingScopedGuard": true,
"tickEffectPauseAccepted": false,
"publicManualRevision2EffectOwnerAccepted": false
},
{
"case": "no-op",
"manualState": "applied",
"configurationCheckpoint": true,
"actualPrivateExecutionOrigin": true,
"effectStatus": "unknown",
"observedFibTransition": false,
"nativeRibBefore": 1,
"nativeRibAfter": 2,
"sharedBudget": {
"deadline": 1793721945901,
"calls": 168,
"bytes": 20246,
"rows": 207
},
"effectBaselineAdvanceAccepted": false,
"effectGateNativeReads": 0,
"actualTickPausedByExistingScopedGuard": true,
"tickEffectPauseAccepted": false,
"publicManualRevision2EffectOwnerAccepted": false
},
{
"case": "ambiguous",
"manualState": "applied",
"configurationCheckpoint": true,
"actualPrivateExecutionOrigin": true,
"effectStatus": "unknown",
"observedFibTransition": false,
"nativeRibBefore": 0,
"nativeRibAfter": 0,
"sharedBudget": null,
"effectBaselineAdvanceAccepted": false,
"effectGateNativeReads": 0,
"actualTickPausedByExistingScopedGuard": true,
"tickEffectPauseAccepted": false,
"publicManualRevision2EffectOwnerAccepted": false
}
],
"realMcpDispatcher": true,
"realSql": true,
"routerOsReplies": "declared synthetic localhost HTTP",
"approval": "synthetic owner through actual approvePlan; not Portal/browser human acceptance",
"genuineRunningOrigin": true,
"actualConfigurationCheckpoint": true,
"signedEffectTamperRejected": true,
"completedPlanSameOperationNoNativeReplay": true,
"productionSourcesStillCurrent": true,
"laterTestOnlyChange": [
"backend/src/modules/mcp/routing-network-native.sql.test.ts"
],
"budgetScope": "effect owner only; old manual preflight/readback transports are outside this168-call owner. Not a whole-operation/all-writer budget acceptance.",
"nativeOwnerCallsPerPreparedCase": 168,
"observedRuntimeHttpRequests": [
495,
495,
354
],
"publicManualRevision2OwnerEnabled": false,
"scopedSchedulerEnabled": false,
"tickPause": "existing scoped activation guard, not effect-triggered controller pause",
"autoRecoveryEnabled": false,
"nativeChrAccepted": false,
"forwardingVerified": false,
"fullRoutingComplete": false,
"fullPlanComplete": false,
"routing18Accepted": false,
"terminalRestoreAccepted": false,
"overallCapsulePassed": false,
"followingFailure": "Readonly test incorrectly asserted global checkpoint count0 after three genuine checkpoints from previous phase. Test changed to exact starting inventory preservation.",
"ownedResourcesRemoved": true,
"remotePacketRemoved": true,
"originalServicesUnchanged": true,
"wholeInstallation2GbAccepted": false
}
@@ -0,0 +1,141 @@
{
"date": "2026-11-03",
"status": "v6-effects3-and-routing18-accepted-terminal-no-replay-test-raced-retirement",
"attemptsUsed": 3,
"maxRunsApproved": 3,
"archiveSha256": "862ea6bcb502955016ce1dd6a1217e66e77ee2e74339858b496d41a70b147f65",
"failure": "inet_server_addr()::text produced127.0.0.1/32; fixed wrapper uses host(inet_server_addr()) and still requires exact127.0.0.1",
"effectScenariosRun": 0,
"routing18Run": false,
"terminalRestoreRun": false,
"ownedResourcesRemoved": true,
"remotePacketRemoved": true,
"originalServicesUnchanged": true,
"oomObserved": false,
"wholeInstallation2GbAccepted": false,
"secondAttempt": {
"archiveSha256": "7b78068de5077df69eb11a9222a75d067cccae858a9a672c631af3171b9a3b93",
"status": "genuine-runtime-refused-expected-two-post-rib-reads-observed-one",
"nativeRibBefore": 1,
"nativeRibAfter": 1,
"ownerObservedSeconds": 131.635,
"routeEffectsAccepted": false,
"routing18Run": false,
"terminalRestoreRun": false,
"ownedResourcesRemoved": true,
"remotePacketRemoved": true,
"originalServicesUnchanged": true,
"oomObserved": false
},
"remainingAuthorizedRuns": 0,
"thirdAttempt": {
"archiveSha256": "df58289757e31a988e9ec7d161801abd1a5b8ed6cd228bf4b2935577e5fa436b",
"manifestSha256": "30fe159c05707693f9c51b5dfae4a3b8c32ff279b9fa05697995034befd349c3",
"diagnostic": {
"case": "transition",
"state": "applied",
"status": "unknown",
"reason": "effect-publication-revalidation-unavailable",
"rounds": 0,
"budget": {
"deadline": 1793720690043,
"calls": 186,
"bytes": 21937,
"rows": 227
},
"ribBefore": 1,
"ribAfter": 2,
"runtimeNativeRequests": 540,
"lastNativePaths": [
"/routing/filter/select-rule",
"/routing/bgp/vpn",
"/routing/rule",
"/ip/firewall/mangle",
"/ipv6/firewall/mangle",
"/ip/route",
"/interface",
"/ip/address"
]
},
"summarySha256": "f9517d7821e1767bccbf92103970d27a7446de29d599cfd02a06210d6cdb70b2",
"status": "configuration-applied-two-post-rib-reads-effect-publication-unknown",
"completeTwoRoundCheckAccepted": false,
"effectScenariosAccepted": 0,
"routing18Run": false,
"terminalRestoreRun": false,
"ownedResourcesRemoved": true,
"remotePacketRemoved": true,
"originalServicesUnchanged": true,
"oomObserved": false
},
"currentGrant": {
"startingArchiveSha256": "22d498d1f3a29facd609c1ac98fe70e0efa0624fa27b3af6b5ca49ee000b5bc4",
"maxRuns": 3,
"attemptsUsed": 3,
"remainingRuns": 0
},
"v4Attempt": {
"diagnostic": {
"case": "transition",
"state": "applied",
"status": "observed-expected",
"reason": null,
"rounds": 2,
"budget": {
"deadline": 1793721369665,
"calls": 168,
"bytes": 20246,
"rows": 207
},
"ribBefore": 1,
"ribAfter": 2,
"runtimeNativeRequests": 495,
"lastNativePaths": [
"/routing/filter/select-rule",
"/routing/bgp/vpn",
"/routing/rule",
"/ip/firewall/mangle",
"/ipv6/firewall/mangle",
"/ip/route",
"/interface",
"/ip/address"
]
},
"status": "positive-transition-and-checkpoints-passed-test-wrongly-expected-idempotency-rejection",
"productionIdempotencyChangeRequired": false,
"idempotencyAssertionCorrected": true,
"effectPhaseComplete": false,
"routing18Run": false,
"terminalRestoreRun": false,
"ownedResourcesRemoved": true,
"remotePacketRemoved": true,
"originalServicesUnchanged": true,
"oomObserved": false
},
"v5Attempt": {
"archiveSha256": "fe521d7a531115f373e33928501aa4a11ae851023867dcb6bddbd885e7135072",
"originalEffectsComponentAccepted": true,
"componentReport": "docs/mcp-linux-original-effects-acceptance-20261103.json",
"routing18Accepted": false,
"terminalRestoreRun": false,
"failure": "readonly global checkpoint inventory expected0 but actual3 genuine prior checkpoints",
"ownedResourcesRemoved": true,
"remotePacketRemoved": true,
"originalServicesUnchanged": true,
"oomObserved": false
},
"v6Attempt": {
"archiveSha256": "12ea0c1013c64f9de1703e0d9f0b3c5b924a4b1c25df687548b722c898749f68",
"effects3Accepted": true,
"routing18Accepted": true,
"nativeReadOnlyRequests": 151,
"checkpointsPreserved": 3,
"terminalAppliedRestoreReached": true,
"terminalReplayAndTamperComplete": false,
"failure": "Immediate stale callback raced still-live exclusive after applied receipt publication; production409 was correct",
"ownedResourcesRemoved": true,
"remotePacketRemoved": true,
"originalServicesUnchanged": true,
"oomObserved": false
}
}
@@ -0,0 +1,34 @@
{
"date": "2026-11-03",
"status": "v5-reviewed-second-new-authorized-run-in-progress",
"archive": "tmp\\mcp-linux-routing-effects-v5-20261103\\private-reviewed.tar.gz",
"archiveSha256": "fe521d7a531115f373e33928501aa4a11ae851023867dcb6bddbd885e7135072",
"manifestSha256": "e711d8d70395ab3c2e2b359bc7492c60e87115a9dcd19881d72813573e7fb82e",
"archiveReadbackFiles": 2841,
"staticGraphModules": 607,
"nonliteralDynamicImports": 0,
"pureBoundaryCasesPassed": 8,
"pureOwnerCasesPassed": 5,
"exactMigrations": 44,
"phases": [
"original-effects-three-synthetic-http-genuine-sql-runtime",
"routing-existing18-sql-http",
"genuine-encrypted-terminal-restore-tamper"
],
"memoryMiB": {
"node": 320,
"postgres": 192,
"total": 512
},
"sourceMountedReadOnly": true,
"existingImageSupplies": "Node22, production dependencies and PostgreSQL18 client tools; application JS is current separately pinned build",
"originalServiceStopsAllowed": false,
"workingDatabaseChangesAllowed": false,
"chrChangesAllowed": false,
"nativeForwardingAccepted": false,
"fullRoutingAccepted": false,
"wholeInstallation2GbAccepted": false,
"attemptsReport": "docs/mcp-linux-routing-effects-attempts-20261103.json",
"sourceCorrectionReport": "docs/mcp-routing-forward-context-source-applied-20261103.json",
"testOnlyCorrection": "Existing applyPlan returns the original operation for a completed plan: assert same id/applied state, exactly one SQL operation and zero new native requests/PATCH. Runtime code unchanged."
}
@@ -0,0 +1,50 @@
{
"date": "2026-11-03",
"status": "genuine-linux-routing18-sql-http-component-accepted",
"archiveSha256": "12ea0c1013c64f9de1703e0d9f0b3c5b924a4b1c25df687548b722c898749f68",
"manifestSha256": "a751202d46aaf6537b2149ac637886025553beb29a4aa7ea2359bb4cb6f6844a",
"componentSummary": "tmp\\mcp-linux-routing-effects-v6-20261103\\private-linux-run-ec3d79a5b204d7129a36666f4c14e8acc8ca1b7e691f0901\\run-routing-original-effects-native-restore-bbaf8b71c8d56786930066cacd37c5e7e01d7489d3347173\\work\\routing-sql-http-summary.json",
"summarySha256": "1dd62b5fb161b03111270d5c7309739e9615fc38291e573634724b34b6661e0c",
"cases": [
"actual fixed read-only reader preserves one captured authority/source and cumulative wire budget across three stages",
"actual terminal zero-received ping skips traceroute without inventing log coverage",
"reordered stage permanently closes session without native continuation",
"late profile after actual GET stops subsequent native requests",
"late endpoint-aba after actual GET stops subsequent native requests",
"late cancel after actual GET stops subsequent native requests",
"tampered source request and extended original deadline refuse before HTTP",
"actual redirect response stops without retry or redirect",
"actual auth response stops without retry or redirect",
"actual malformed response stops without retry or redirect",
"actual oversized response stops without retry or redirect",
"actual rows response stops without retry or redirect",
"actual aggregate wire bytes remain charged across forward and inverse",
"actual row count remains charged across phases and rejects total10000 overflow",
"actual public topology bearer reader observes final gateway without operation binding or namespace joins",
"actual public topology preserves unknown numeric gateway despite matching BGP cache/session strings",
"actual public topology bearer and server scope/disabled checks refuse before native I/O",
"actual public topology pause endpoint ABA and cancellation retire at first response without publication"
],
"requests": 151,
"nativeWrites": 0,
"priorCheckpointsPreserved": 3,
"completeCheckpointSqlJsonInventoryUnchanged": true,
"realSql": true,
"realHttp": true,
"publicTopologyBearerRuntimeCasesAccepted": 4,
"routerOsReplies": "declared synthetic localhost HTTP",
"syntheticEncryptedSourcePlans": true,
"genuinePrepareForReadOnlySourceFixtures": false,
"strongOriginNamespaceUnsupported": true,
"logCoverage": "unknown",
"reservedDispatchEnabled": false,
"nativeChrAccepted": false,
"fullRoutingComplete": false,
"wholeInstallation2GbAccepted": false,
"overallCapsulePassed": false,
"terminalRestoreComplete": false,
"followingFailure": "Applied restore receipt was published before its exclusive owner finished final cleanup; immediate stale-callback test raced that still-owned exclusive and received409. Test now waits bounded actual whenOpen before stale replay check.",
"ownedResourcesRemoved": true,
"remotePacketRemoved": true,
"originalServicesUnchanged": true
}
@@ -0,0 +1,29 @@
{
"date": "2026-11-03",
"status": "source-applied-unit17-build-artifact2-passed-linux-pending",
"files": [
{
"path": "backend/scripts/writer-profile/maintenance-resolution.mts",
"sha256": "1bb82e8d4f1747ee962a8236342c99c6b0270c517742e42b7184249f707ca140"
},
{
"path": "backend/scripts/database-maintenance-resolution.test.mts",
"sha256": "32a2219a88848d4e41dbef21710569147f4a46bb6b2dbfe8af2b91a1aa9c8dbf"
}
],
"changes": [
"Observe optional mmapp_control presence inside the same read-only transaction; never require its installation",
"Preserve original observation failure when rollback/unlock/release are confirmed; refuse actual cleanup uncertainty"
],
"checks": {
"targetedOperatorTestsPassed": 17,
"focusedLintExitCode": 0,
"backendBuildExitCode": 0,
"operatorArtifactNode": "22.23.4",
"operatorArtifactTestsPassed": 2,
"operatorModules": 19
},
"newLinuxAcceptanceRequired": true,
"newLinuxAccepted": false,
"databaseOrChrChanges": false
}
@@ -0,0 +1,85 @@
{
"date": "2026-11-03",
"status": "source-applied-local-checks-passed-linux-pending",
"patchSha256": "17f14726932ed73c7d3afea425ea02a75b98ca85a7e78f8cd250f934b38546d2",
"filesChanged": 12,
"candidateReport": "tmp/mcp-maintenance-resolution-candidate-20261103/candidate-report.json",
"behavior": "Explicit host operator inspection and separate approval accept observed current database state; historical unknown outcomes stay unknown. Startup refuses new uncovered uncertainty before SQL.",
"checks": {
"systemDatabaseProfileExitCode": 0,
"backendBuildExitCode": 0,
"operatorModules": 19,
"productionNode": "22.23.4",
"operatorArtifactTestsPassed": 2,
"selectedTestsPassed": 50,
"selectedTestsSkipped": 2
},
"nativeLinuxPosixAccepted": false,
"genuineMaintenanceSqlAccepted": false,
"serverChanged": false,
"deploymentPerformed": false,
"fullPlanComplete": false,
"files": [
{
"path": "backend/package.json",
"appliedSha256": "38e9d101efe13aec2089fdcb081c972d4eb41587a99e0f6d39b91563ffc120a4",
"candidateNormalizedExact": true
},
{
"path": "backend/src/index.ts",
"appliedSha256": "2b1abfdf1574251a56746cfac8e7cd58309cf5afbc82caae230e043a28c39f56",
"candidateNormalizedExact": true
},
{
"path": "backend/src/db/bootstrap.ts",
"appliedSha256": "8b0df1e81fc3fe01b9959f4390f348f9a743f43fb987277eab0e3d75b8c11f07",
"candidateNormalizedExact": true
},
{
"path": "backend/src/services/database-maintenance-startup.ts",
"appliedSha256": "f49efd2fcdc16eb0af6af7c8de4f2e95ffe692da39e89590b86ba7e96d2f97bf",
"candidateNormalizedExact": true
},
{
"path": "backend/src/services/database-maintenance-resolution.ts",
"appliedSha256": "e3c26400ba17264516e8a16a624d91786ad20c0d98c62f1e62c91b2e20f89fc3",
"candidateNormalizedExact": true
},
{
"path": "backend/src/services/database-maintenance-resolution.test.ts",
"appliedSha256": "11f1f4d2d7e4b568943c69d9857df8a4af7580c180dfba7cf3b20eb9a525a30c",
"candidateNormalizedExact": true
},
{
"path": "backend/src/services/database-restore-audit.ts",
"appliedSha256": "84d50bbe98bcfea953c43a4b45bb12878dd2b1d00bd32f7a564406919f0ddaf4",
"candidateNormalizedExact": true
},
{
"path": "backend/scripts/database-writer-profile-operator.mts",
"appliedSha256": "ee8f2b555189e8bd81d02eaf89825e746efaafe0f04cc70f79c462c229ad487c",
"candidateNormalizedExact": true
},
{
"path": "backend/scripts/writer-profile/maintenance-resolution.mts",
"appliedSha256": "0db03d789132dc12e490ec3de6f49b8d4d551db0b9a34bf2dca36656993c2a09",
"candidateNormalizedExact": true
},
{
"path": "backend/scripts/database-maintenance-resolution.test.mts",
"appliedSha256": "29664cbf2140a97b93cb59356b667908453233428349ecc422736a91a4df7cbd",
"candidateNormalizedExact": true
},
{
"path": "backend/scripts/build-database-operator.mjs",
"appliedSha256": "66df17a4a72b341a420e3aec3a9afc87d273438c8aefed6239992047baecece2",
"candidateNormalizedExact": true
},
{
"path": "backend/scripts/build-database-operator.test.mjs",
"appliedSha256": "4801a6c3f308be7fa1335564981ee82a6854f6319bd09e04f8a4682bf4e8f003",
"candidateNormalizedExact": true
}
],
"followUpFixReport": "docs/mcp-maintenance-namespace-fix-source-applied-20261103.json"
}
@@ -0,0 +1,30 @@
{
"date": "2026-11-03",
"status": "source-applied-http6-system-database-build-lint-passed",
"patchSha256": "fa44982f020067b4342e65b02b60d3dbe957e0d42afca43ad73c97aad3e7f0c9",
"files": [
{
"path": "backend/src/routes/mcp-oauth.ts",
"appliedSha256": "d2ea5cb6b7c4fdf0c370d8ffafc7e64792153e43260376f5c093b680449942dd"
},
{
"path": "backend/src/routes/mcp-oauth-database-admission.test.ts",
"appliedSha256": "c4c8637d40d77686b69c191fb2a369bbe6d3f8a512e6fcd2ac4642fd28c31873"
},
{
"path": "backend/package.json",
"appliedSha256": "ceed6c2f453afbc6d9a2a888e56ffd988c4df5249e76bdcbfdd914de97f384b7"
}
],
"behavior": "Existing OAuth handlers enter shared database admission before SQL; active token transactions drain before replacement; sticky closure refuses OAuth with503/no-store until explicit repair. Discovery remains available without SQL.",
"focusedActualFastifyCasesPassed": 6,
"systemDatabaseProfileExitCode": 0,
"systemDatabaseProfilePlatform": "Windows functional tests; native POSIX case explicitly skipped",
"backendBuildExitCode": 0,
"focusedLintExitCode": 0,
"genuinePgAccepted": false,
"linuxCurrentImageAccepted": false,
"deploymentPerformed": false,
"fullDbComplete": false,
"fullPlanComplete": false
}
@@ -0,0 +1,22 @@
{
"date": "2026-11-03",
"status": "source-applied-native24-profile-build-lint-passed",
"files": [
"backend/src/modules/mcp/routing-network-native-session.ts",
"backend/src/modules/mcp/routing-network-native-session.test.ts",
"backend/src/modules/mcp/routing-community-scoped-acceptance.ts"
],
"behavior": "Complete current authority/source/policy checks surround one fence batch; direct native dispatch also retains fresh before/after runtime authorization. Primitive SQL/file leaves retain original deadline/signal checks without recursively repeating full runtime authorization. No decision or connection credentials are cached.",
"targetedNativeSessionCasesPassed": 24,
"completeNativeReadersProfileExitCode": 0,
"backendBuildExitCode": 0,
"focusedLintExitCode": 0,
"diagnostic": "Synthetic SQL/runtime helper logs bounded effect status/reason/counters and fixed path names before assertions; no passwords/tokens/raw rows.",
"originalNativeDeadlineChanged": false,
"nativeRequestByteRowBudgetsChanged": false,
"memoryCapsChanged": false,
"genuineLinuxCorrectionAccepted": false,
"secondLinuxFailureCauseFullyProven": false,
"wholeInstallation2GbAccepted": false,
"fullRoutingComplete": false
}
@@ -0,0 +1,21 @@
{
"date": "2026-11-03",
"status": "source-applied-unit14-build-lint-and-two-native-readers-passed",
"files": [
{
"path": "backend/src/services/routing-topology-observation.ts",
"sha256": "27a9f53191f47478518b9d33e815b9ddc758a9a1be1fb53e67adf11211970c68"
},
{
"path": "backend/src/services/routing-topology-observation.test.ts",
"sha256": "8a7cc989b3444f466316b2baac58e7ebd828bdafbc8410a80ddda602bd68904c"
}
],
"nativeWireObservation": "Both RouterOS7.24.5 tables encode enabled fib switch as empty string. fib-specific switch decoder accepts it; invalid/disabled/absent/nonfib and arbitrary boolean empty values stay unknown.",
"primarySemanticsReference": "https://manual.mikrotik.com/docs/cli-reference/routing/table/",
"unitTestsPassed": 14,
"nativeReport": "docs/mcp-two-chr-current-reader-acceptance-20261103.json",
"backendBuildExitCode": 0,
"focusedLintExitCode": 0,
"fullRoutingDirectionComplete": false
}
@@ -0,0 +1,35 @@
{
"date": "2026-11-03",
"status": "source-applied-bridge46-effects70-native-profile-build-lint-passed",
"files": [
{
"path": "backend/src/modules/mcp/routing-network-configuration-bridge.ts",
"sha256": "cad1b6a8951e5cbb301331752e17fc032738c04206ab5b699d3545cdd09ff4e6"
},
{
"path": "backend/src/modules/mcp/routing-network-configuration-bridge.test.ts",
"sha256": "1666afb5d533f647bd4c33b6aa949ff5d705516f567bfb88c8b4590842b85bde"
},
{
"path": "backend/src/modules/mcp/routing-community-scoped-service.ts",
"sha256": "3a8c92e40fe79a37e93b39645d33a5df842ccf5f43ac2589b03a33d920e37aee"
},
{
"path": "backend/src/modules/mcp/routing-original-effects-caller.test.mjs",
"sha256": "825d4bcda0dc6b9b61984b44c6ded76ffd8a42f2b69b30825e22a4734be8e79b"
}
],
"behavior": "The private forward-round context validates original shared owner/principal/source/checkpoint at admission; its first current native configuration observation is mandatory immediately before the first snapshot. Removed unused eager native read. Both before/after reads of each snapshot and final publication read stay mandatory. Existing independent/shared one-shot bridge behavior unchanged.",
"boundedFailureCategory": "Optional round/publication errors carry only a fixed stage and an allowlisted error code, never raw exception details.",
"bridgePermanentCasesPassed": 46,
"effectOriginCallerControllerCasesPassed": 70,
"completeNativeReadersProfileExitCode": 0,
"backendBuildExitCode": 0,
"focusedLintExitCode": 0,
"sourceDeadlineExpanded": false,
"nativeBudgetsExpanded": false,
"memoryCapsExpanded": false,
"genuineLinuxAccepted": false,
"fullRoutingComplete": false,
"deploymentPerformed": false
}
@@ -0,0 +1,76 @@
{
"date": "2026-11-03",
"status": "source-applied-native-readers-build-lint-passed",
"patchSha256": "b33a745cde76abd046e02e9f3cfbe6e63aaaead13eee6dfc4023d0f52c278978",
"files": [
{
"path": "backend/src/modules/mcp/routing-community-context.ts",
"appliedSha256": "b9c199b30a299fa4b4e339b35a9df5c725fa6b6ada2b2adf8e2699ca415157bb"
},
{
"path": "backend/src/modules/mcp/routing-community-scoped-checkpoint.ts",
"appliedSha256": "492c43a8b80593e669d42f3658aaa6563a5199302ae2dae5ddc6f4f5b432f68a"
},
{
"path": "backend/src/modules/mcp/routing-community-scoped-service.ts",
"appliedSha256": "83754daf7eca5259dba2afa859679364d7b296f8f4305ee9d43574ac1ab57d6a"
},
{
"path": "backend/src/modules/mcp/routing-controller-baseline.ts",
"appliedSha256": "d7aa97275fd00c3371c4b6fec09c176c6ea312e805030a382a32d79e7d42b708"
},
{
"path": "backend/src/modules/mcp/routing-network-execution-origin.test.mjs",
"appliedSha256": "0424c04ada0464dd7819ef5fc9622767d13f198a7f5759cf9d3851e5c7bedd72"
},
{
"path": "backend/src/modules/mcp/routing-network-execution-session.ts",
"appliedSha256": "8862056444f16b2aaee7a75fa41cf5346b7e4f2e68a8399d2829e2e9e20f30fa"
},
{
"path": "backend/src/modules/mcp/routing-original-effects-caller.test.mjs",
"appliedSha256": "994ca8f71a02cb7dc8a6daac8959ea28cedb962c68cdb9406dfe1c277abf6c62"
},
{
"path": "backend/src/modules/mcp/routing-original-effects-controller.test.mjs",
"appliedSha256": "f42d2c9374d8be466ba0106bb7fff9b7a4ab1e1fe8b55ce409cd7aa4280e4d08"
},
{
"path": "backend/src/modules/mcp/routing-original-effects.test.mjs",
"appliedSha256": "4e8ceab93dafff221a82eb3ab1ccbafbeff87d0938a4a0b2b98320bec94b69e9"
},
{
"path": "backend/src/modules/mcp/routing-original-effects.ts",
"appliedSha256": "a3145b08e99ad056975080244dfd30c33e60c85a7a2338e00e8685683e9922eb"
},
{
"path": "backend/package.json",
"appliedSha256": "ceed6c2f453afbc6d9a2a888e56ffd988c4df5249e76bdcbfdd914de97f384b7"
}
],
"behavior": "Prepared destination selection, bounded execution-bound pre-PATCH observation and two post-CONFIG_APPLIED observations share verified running origin and original deadline; signed effect vector remains separate from successful configuration receipt.",
"permanentComponentCasesPassed": 69,
"completeNativeReadersProfileExitCode": 0,
"backendBuildExitCode": 0,
"focusedLintExitCode": 0,
"activation": {
"persistentScopedRevision3OwnerWired": true,
"publicManualRevision2OwnerEnabled": false,
"scopedSchedulerEnabled": false,
"existingScopeActivationGuardPreserved": true,
"autoRecoveryEnabled": false
},
"limits": {
"causalAttributionProven": false,
"bgpOriginVerified": false,
"recursiveGraphVerified": false,
"forwardingVerified": false,
"logCoverage": "unknown"
},
"genuineSqlRuntimeAccepted": false,
"nativeChrEffectAccepted": false,
"linuxCurrentImageAccepted": false,
"deploymentPerformed": false,
"fullRoutingComplete": false,
"fullPlanComplete": false
}
@@ -0,0 +1,42 @@
{
"date": "2026-11-03",
"status": "source-applied-native-readers-profile-build-lint-passed",
"files": [
{
"path": "backend/src/services/routing-table-fib.ts",
"sha256": "a95a850c7ba01685c458db5f3192bfea021d88f1e104d4331572bcdf8ca11426"
},
{
"path": "backend/src/services/routing-table-fib.test.ts",
"sha256": "8e83c8598259eaa2f131566197252ddfe03ffb9c6af160c0109d958eccea74f2"
},
{
"path": "backend/src/services/routing-topology-observation.ts",
"sha256": "88113aaf89518f5d72b167d41e6378ea3dd4849debb4df37b5a7faa6f6058e3d"
},
{
"path": "backend/src/modules/mcp/control-path-context.ts",
"sha256": "01723ae1c2a4d5918dc66d1fae7f20e78f24ae5a00c9454fe2884402899315dd"
},
{
"path": "backend/src/modules/mcp/routing-community-context.ts",
"sha256": "33b25d4b4a2ec0b9407ed6edc0b7ca8cd23e143301316f4ae6aad81b465abfb1"
},
{
"path": "backend/package.json",
"sha256": "f76be3c4d180839c0dcf3a8d64e7d9691a04023ccbc22018bd1bf91e206769e4"
}
],
"behavior": "One FIB switch interpretation for topology, control-path and Community source/destination/resolver tables. Empty switch value supported; missing FIB and invalid/disabled/ambiguous table flags refuse.",
"nativeReadersProfileExitCode": 0,
"targetedSharedAndTopologyTestsPassed": 17,
"controlAndCommunityModelTestsPassed": 9,
"nativeLocalHttpTestsPassed": 6,
"localHttpPermissionRetry": "Initial6 EACCES failures were environmental; isolated permitted-localhost retry passed6/6, no product change needed.",
"backendBuildExitCode": 0,
"focusedLintExitCode": 0,
"priorNativeReaderReport": "docs/mcp-two-chr-current-reader-acceptance-20261103.json",
"newCurrentNativeCapturePending": false,
"fullRoutingDirectionComplete": false,
"currentNativeReport": "docs/mcp-two-chr-shared-fib-reader-acceptance-20261103.json"
}
@@ -0,0 +1,37 @@
{
"date": "2026-11-03",
"status": "source-applied-unit-and-build-passed",
"patchSha256": "2b5900fea0d4d36fe6324987d7967236242eaf573f04fb662a8d023d3f73c1ad",
"files": [
{
"path": "backend/src/modules/mcp/routing-controls.ts",
"appliedSha256": "793b2714dac4bfd4038ef1715e328c5e65647ed0dd10b2f1aab7e5ba9b21b1ac",
"candidateNormalizedExact": true
},
{
"path": "backend/src/modules/mcp/routing-network-native.sql.test.ts",
"appliedSha256": "eb4922c3a00747af41954afbd9fce9980477129cb5b2baad1ec3e92270be7bee",
"candidateNormalizedExact": true
},
{
"path": "backend/src/services/routing-topology-observation.ts",
"appliedSha256": "9fef3d9476a829981afa3fe92567c2da1b8c282167fdd6c61eff243f33ae3a64",
"candidateNormalizedExact": true
},
{
"path": "backend/src/services/routing-topology-observation.test.ts",
"appliedSha256": "3dc1a1f6870f973ff45936ae637f3f85460ed748f6a710a3a70b60506c6025d4",
"candidateNormalizedExact": true
}
],
"newPublicTools": 0,
"existingPublicTool": "mm_routing_topology_get",
"testsPassed": 13,
"backendBuildExitCode": 0,
"genuinePublicSqlGroupsAccepted": 0,
"actualChrAccepted": false,
"recursiveChainVerified": false,
"routeOriginVerified": false,
"forwardingVerified": false,
"fullPlanComplete": false
}
@@ -0,0 +1,57 @@
{
"date": "2026-11-03",
"status": "two-chr-current-reader-accepted",
"cases": [
{
"source": "194.226.121.150",
"version": "7.24.5 (stable)",
"requests": 45,
"actualReaderGets": 11,
"conservationSections": 17,
"configurationConserved": true,
"observedRoutes": 4,
"unknownReasons": {
"multipath-selection-unknown": 2
},
"sourcePins": {
"backend/src/services/routing-topology-observation.ts": "27a9f53191f47478518b9d33e815b9ddc758a9a1be1fb53e67adf11211970c68",
"backend/scripts/acceptance/chr-recursive-observation.mjs": "837390d9a32bf7a254934faf8167d7d360724bbd843f3d6213a4668ea0239de0",
"backend/src/test/chr-vrf-companion-telemetry.ts": "08ed399a57f56bb0c5aa1cc5c224b8b91ca7ab65cec03c3faf27ed5e983750c2"
},
"report": "tmp/mcp-two-chr-current-read-183d9b771f1bd3a73edc95aac1097fbd/chr1/report.json",
"reportSha256": "2b0c7be7e778e862dc142959a2b11ed9728ff1145f64efc4cfa212f6fe04403e"
},
{
"source": "104.165.244.7",
"version": "7.24.5 (stable)",
"requests": 45,
"actualReaderGets": 11,
"conservationSections": 17,
"configurationConserved": true,
"observedRoutes": 5,
"unknownReasons": {
"multipath-selection-unknown": 2
},
"sourcePins": {
"backend/src/services/routing-topology-observation.ts": "27a9f53191f47478518b9d33e815b9ddc758a9a1be1fb53e67adf11211970c68",
"backend/scripts/acceptance/chr-recursive-observation.mjs": "837390d9a32bf7a254934faf8167d7d360724bbd843f3d6213a4668ea0239de0",
"backend/src/test/chr-vrf-companion-telemetry.ts": "08ed399a57f56bb0c5aa1cc5c224b8b91ca7ab65cec03c3faf27ed5e983750c2"
},
"report": "tmp/mcp-two-chr-current-read-183d9b771f1bd3a73edc95aac1097fbd/chr2/report.json",
"reportSha256": "524097e9817b8299df3b6307ae40e5404e10fea266162f9eb022dff243d4c970"
}
],
"totalNativeGets": 90,
"nativeMutations": 0,
"ownedChildrenRetired": true,
"ownedPortsAbsent": true,
"sourceTransport": "Pinned VPS SSH to fixedCHR80, separately verified pinned nativeSSH peers",
"httpLegTlsVerified": false,
"publicMcpSqlAdmissionAccepted": false,
"routeOriginVerified": false,
"recursiveChainVerified": false,
"forwardingVerified": false,
"threeNodePathAccepted": false,
"wholeInstallationMemoryAccepted": false,
"fullPlanComplete": false
}
@@ -0,0 +1,62 @@
{
"date": "2026-11-03",
"status": "exact-current-shared-fib-reader-two-chr-accepted",
"cases": [
{
"source": "194.226.121.150",
"version": "7.24.5 (stable)",
"nativeGets": 45,
"readerGets": 11,
"conservationSections": 17,
"configurationConserved": true,
"observedRoutes": 4,
"unknownReasons": {
"multipath-selection-unknown": 2
},
"sourcePins": {
"backend/src/services/routing-table-fib.ts": "a95a850c7ba01685c458db5f3192bfea021d88f1e104d4331572bcdf8ca11426",
"backend/src/services/routing-topology-observation.ts": "88113aaf89518f5d72b167d41e6378ea3dd4849debb4df37b5a7faa6f6058e3d",
"backend/scripts/acceptance/chr-recursive-observation.mjs": "837390d9a32bf7a254934faf8167d7d360724bbd843f3d6213a4668ea0239de0",
"backend/src/test/chr-vrf-companion-telemetry.ts": "08ed399a57f56bb0c5aa1cc5c224b8b91ca7ab65cec03c3faf27ed5e983750c2"
},
"report": "tmp/mcp-two-chr-current-read-103b434a9dcf642ba1a5577642febdd1/chr1/report.json",
"reportSha256": "b725386abd71070647c2098cad2e2ed60e3ab0e3d21349b8ece09a10bd912ae6"
},
{
"source": "104.165.244.7",
"version": "7.24.5 (stable)",
"nativeGets": 45,
"readerGets": 11,
"conservationSections": 17,
"configurationConserved": true,
"observedRoutes": 5,
"unknownReasons": {
"multipath-selection-unknown": 2
},
"sourcePins": {
"backend/src/services/routing-table-fib.ts": "a95a850c7ba01685c458db5f3192bfea021d88f1e104d4331572bcdf8ca11426",
"backend/src/services/routing-topology-observation.ts": "88113aaf89518f5d72b167d41e6378ea3dd4849debb4df37b5a7faa6f6058e3d",
"backend/scripts/acceptance/chr-recursive-observation.mjs": "837390d9a32bf7a254934faf8167d7d360724bbd843f3d6213a4668ea0239de0",
"backend/src/test/chr-vrf-companion-telemetry.ts": "08ed399a57f56bb0c5aa1cc5c224b8b91ca7ab65cec03c3faf27ed5e983750c2"
},
"report": "tmp/mcp-two-chr-current-read-103b434a9dcf642ba1a5577642febdd1/chr2/report.json",
"reportSha256": "4687af045b119b80e6c75b393c001c1572d25f897e3ec4e342fbdd340c5c54c4"
}
],
"totalNativeGets": 90,
"nativeMutations": 0,
"sourcePinsCurrent": true,
"ownedChildrenRetired": true,
"ownedPortsAbsent": true,
"transport": "Pinned VPS SSH to fixedCHR80, separately verified nativeSSH peers",
"httpLegTlsVerified": false,
"observerPlatform": "Windows local functional observer through the Linux VPS; no Windows capacity/RAM claim",
"publicMcpSqlAdmissionAccepted": false,
"semanticProof": false,
"routeOriginVerified": false,
"recursiveChainVerified": false,
"forwardingVerified": false,
"twoChrFullSchemeComplete": false,
"thirdChrNeededForThisRead": false,
"fullPlanComplete": false
}
+1026
View File
File diff suppressed because it is too large. Load diff