feat(mcp): подготовить сервер и доступ к приёмке
This commit is contained in:
commit
bf3d50e1ff
3835 files changed
+537122
No files matched your search
@@ -0,0 +1,9 @@
|
||||
import {createHash} from 'node:crypto'
|
||||
|
||||
/** Exact writer-profile v1 canonical bytes. Pure value codec; no trust/authority. */
|
||||
export function canonical(value:unknown):string {
|
||||
return JSON.stringify(value,(_key,item:unknown)=>item&&typeof item==='object'&&!Array.isArray(item)
|
||||
?Object.fromEntries(Object.entries(item).sort(([a],[b])=>a<b?-1:a>b?1:0)):item)
|
||||
}
|
||||
export const sha=(value:Uint8Array|string)=>createHash('sha256').update(value).digest('hex')
|
||||
export const bytes=(value:unknown)=>Buffer.from(canonical(value)+'\n','utf8')
|
||||
@@ -0,0 +1,9 @@
|
||||
// Fixed12 source-partition DATA policy, candidate build artifact for review.
|
||||
// No observed-schema enrollment, caller policy, expected catalogue or authority.
|
||||
import {canonical,sha} from './application-writer-profile-canonical.js'
|
||||
const PERIODS=Object.freeze({flow_buckets:'d',flow_minute_stats:'d',flow_minute_dims:'d',flow_daily_dims:'m',flow_hour_facts:'d',flow_daily_facts:'m',traffic_samples:'w',servers_rest_ping_samples:'w',uptime_probe_samples:'w',uptime_resource_samples:'w',server_snapshots:'w',internet_path_snapshots:'w'} as const)
|
||||
export type FixedCatalogueParentPeriodData='d'|'w'|'m'
|
||||
export const FIXED_CATALOGUE_PARENT_POLICY_DATA_SHA256=sha(canonical(PERIODS))
|
||||
export function fixedCatalogueParentPeriodData(name:string):FixedCatalogueParentPeriodData|undefined {
|
||||
return Object.hasOwn(PERIODS,name)?PERIODS[name as keyof typeof PERIODS]:undefined
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Private typed VALUE framing only. No SQL/pool/role/current-schema attestation.
|
||||
// Transport limits are inherited unchanged from the accepted paged DATA codec;
|
||||
// they DO NOT expand the legacy2048 total-constraint policy.
|
||||
import {createHash,type Hash} from 'node:crypto'
|
||||
import {canonical as writerCanonical} from './application-writer-profile-canonical.js'
|
||||
|
||||
export type Json = null|boolean|number|string|Json[]|{[key:string]:Json}
|
||||
export type CatalogueKind='global'|'base'|'leaf'
|
||||
export interface CatalogueRecord {kind:CatalogueKind;identity:string;value:{[key:string]:Json}}
|
||||
export const FRAME_LIMITS=Object.freeze({chunkBytes:4096,pageRows:128,recordBytes:2*1024*1024,streamBytes:64*1024*1024,identityBytes:1024,depth:64})
|
||||
export interface StreamMetrics {records:number;chunks:number;pages:number;payloadBytes:number;estimatedDataRowBytes:number;maxPagePayloadBytes:number;maxRecordBytes:number}
|
||||
export interface CatalogueValueDigest {format:'catalogue-values-v2';baseSha256:string;leafSha256:string;snapshotSha256:string;metrics:Readonly<StreamMetrics>;authority:false}
|
||||
const rank:Record<CatalogueKind,number>={global:0,base:1,leaf:2}
|
||||
const domain=(name:string)=>createHash('sha256').update('mmapp:catalogue-'+name+':v2\0')
|
||||
function object(value:unknown):value is Record<string,unknown>{return !!value&&typeof value==='object'&&!Array.isArray(value)&&Object.getPrototypeOf(value)===Object.prototype}
|
||||
function exactJson(value:Json,depth=0):void {
|
||||
if(depth>FRAME_LIMITS.depth)throw Error('Catalogue nesting bound')
|
||||
if(value===null||typeof value==='boolean'||typeof value==='string')return
|
||||
if(typeof value==='number'){if(!Number.isSafeInteger(value)||Object.is(value,-0))throw Error('Exact integer/string catalogue scalar required');return}
|
||||
if(Array.isArray(value)){for(const item of value)exactJson(item,depth+1);return}
|
||||
if(!object(value))throw Error('Plain catalogue JSON required')
|
||||
for(const key of Object.keys(value))exactJson(value[key] as Json,depth+1)
|
||||
}
|
||||
function canonical(value:Json):string{exactJson(value);return writerCanonical(value)}
|
||||
function update(hash:Hash,encoded:Buffer){const length=Buffer.alloc(4);length.writeUInt32BE(encoded.length);hash.update(length);hash.update(encoded)}
|
||||
function record(bytes:Buffer,kind:CatalogueKind,identity:string):CatalogueRecord{
|
||||
const parsed:unknown=JSON.parse(new TextDecoder('utf8',{fatal:true}).decode(bytes))
|
||||
if(!object(parsed)||Object.keys(parsed).sort().join(',')!=='identity,kind,value'||parsed.kind!==kind||parsed.identity!==identity||!object(parsed.value))throw Error('Exact catalogue record shape required')
|
||||
// canonical() also rejects numbers/prototypes outside the scalar contract.
|
||||
canonical(parsed as unknown as Json)
|
||||
return parsed as unknown as CatalogueRecord
|
||||
}
|
||||
export class CatalogueValueStream {
|
||||
private readonly base=domain('base-values');private readonly leaf=domain('leaf-values');private readonly snapshot=domain('snapshot-values')
|
||||
private readonly metrics:StreamMetrics={records:0,chunks:0,pages:0,payloadBytes:0,estimatedDataRowBytes:0,maxPagePayloadBytes:0,maxRecordBytes:0}
|
||||
private next=1n;private parts:Buffer[]=[];private bytes=0;private expectedParts=0;private currentKind?:CatalogueKind;private currentIdentity=''
|
||||
private previous?:{kind:CatalogueKind;identity:string};private finished=false;private accepting=false;private failed=false;private baseRecords=0;private leafRecords=0;
|
||||
*acceptPage(rows:readonly unknown[]):Generator<CatalogueRecord>{
|
||||
if(this.finished||this.accepting||this.failed||!Array.isArray(rows)||rows.length>FRAME_LIMITS.pageRows){this.failed=true;throw Error('Closed/oversized catalogue page')}
|
||||
this.accepting=true;let complete=false
|
||||
try{
|
||||
this.metrics.pages++;let pageBytes=0
|
||||
for(const raw of rows){
|
||||
if(!object(raw)||Object.keys(raw).sort().join(',')!=='chunk_count,chunk_index,identity,kind,payload,record_id')throw Error('Exact cursor row shape required')
|
||||
const {record_id:id,chunk_index:part,chunk_count:count,kind,identity,payload}=raw
|
||||
if(typeof id!=='string'||!/^[1-9][0-9]{0,18}$/.test(id)||BigInt(id)!==this.next||typeof kind!=='string'||!Object.hasOwn(rank,kind)||typeof identity!=='string'||Buffer.byteLength(identity)<1||Buffer.byteLength(identity)>FRAME_LIMITS.identityBytes||!Number.isInteger(part)||!Number.isInteger(count)||typeof part!=='number'||typeof count!=='number'||count<1||count>FRAME_LIMITS.recordBytes/FRAME_LIMITS.chunkBytes||part!==this.parts.length||part>=count||!Buffer.isBuffer(payload)||payload.length<1||payload.length>FRAME_LIMITS.chunkBytes||part<count-1&&payload.length!==FRAME_LIMITS.chunkBytes)throw Error('Catalogue order/chunk continuity refused')
|
||||
const k=kind as CatalogueKind
|
||||
if(part===0){if(this.previous&&(rank[k]<rank[this.previous.kind]||rank[k]===rank[this.previous.kind]&&Buffer.compare(Buffer.from(identity),Buffer.from(this.previous.identity))<=0))throw Error('Strict catalogue identity order required');this.currentKind=k;this.currentIdentity=identity;this.expectedParts=count}
|
||||
if(k!==this.currentKind||identity!==this.currentIdentity||count!==this.expectedParts)throw Error('Changed record framing refused')
|
||||
this.parts.push(Buffer.from(payload));this.bytes+=payload.length;pageBytes+=payload.length;this.metrics.chunks++;this.metrics.payloadBytes+=payload.length
|
||||
// Text protocol bytea uses hex. DataRow header + six field lengths + actual
|
||||
// other text field sizes; RowDescription/ReadyForQuery/JS heap are separate.
|
||||
this.metrics.estimatedDataRowBytes+=31+Buffer.byteLength(id)+String(part).length+String(count).length+Buffer.byteLength(k)+Buffer.byteLength(identity)+2+2*payload.length
|
||||
if(this.bytes>FRAME_LIMITS.recordBytes||pageBytes>FRAME_LIMITS.pageRows*FRAME_LIMITS.chunkBytes||this.metrics.payloadBytes>FRAME_LIMITS.streamBytes)throw Error('Catalogue byte budget exceeded')
|
||||
if(part===count-1){
|
||||
const value=record(Buffer.concat(this.parts,this.bytes),k,identity),encoded=Buffer.from(canonical(value as unknown as Json))
|
||||
if(encoded.length>FRAME_LIMITS.recordBytes)throw Error('Canonical catalogue record overflow')
|
||||
const dynamic=k==='leaf'||k==='global'&&identity==='source_size'
|
||||
update(this.snapshot,encoded);update(dynamic?this.leaf:this.base,encoded)
|
||||
if(dynamic)this.leafRecords++;else this.baseRecords++
|
||||
this.metrics.records++;this.metrics.maxRecordBytes=Math.max(this.metrics.maxRecordBytes,this.bytes);this.previous={kind:k,identity};this.next++;this.parts=[];this.bytes=0;this.expectedParts=0
|
||||
yield value
|
||||
}
|
||||
}
|
||||
this.metrics.maxPagePayloadBytes=Math.max(this.metrics.maxPagePayloadBytes,pageBytes)
|
||||
complete=true
|
||||
}finally{this.accepting=false;if(!complete)this.failed=true}
|
||||
}
|
||||
finish():CatalogueValueDigest{
|
||||
if(this.finished||this.accepting||this.failed||this.parts.length)throw Error('Catalogue stream incomplete/closed')
|
||||
this.finished=true
|
||||
// Each digest includes its own record count, so empty/partitioned streams
|
||||
// cannot acquire an ambiguous concatenation encoding.
|
||||
const digest=(hash:Hash,count:number)=>{const end=Buffer.alloc(8);end.writeBigUInt64BE(BigInt(count));return hash.update(end).digest('hex')}
|
||||
return Object.freeze({format:'catalogue-values-v2',baseSha256:digest(this.base,this.baseRecords),leafSha256:digest(this.leaf,this.leafRecords),snapshotSha256:digest(this.snapshot,this.metrics.records),metrics:Object.freeze({...this.metrics}),authority:false})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Fail-closed DATA counter for the unchanged legacy total constraint ceiling.
|
||||
* Paging is not permission to reset/reinterpret that total on every page. */
|
||||
export class FixedCatalogueConstraintRowBudgetData {
|
||||
private count=0;private failed=false
|
||||
acceptRows(rows:readonly unknown[]):number {
|
||||
if(this.failed||!Array.isArray(rows)){this.failed=true;throw Error('Closed/invalid constraint row budget')}
|
||||
if(this.count+rows.length>2048){this.failed=true;throw Error('Legacy total constraint row cap2048 exceeded')}
|
||||
this.count+=rows.length;return this.count
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Private staged full-catalogue DATA verifier. No pool, issuer, cache, authority or READY.
|
||||
// Fixed source44 candidate is statically imported; callers cannot supply expected JSON/policy.
|
||||
import {canonical,sha} from './application-writer-profile-canonical.js'
|
||||
import {fixedCatalogueParentPeriodData} from './application-writer-profile-catalogue-parent-policy.js'
|
||||
import {type CatalogueRecord} from './application-writer-profile-catalogue-values.js'
|
||||
import {BUILD44_PROVENANCE,FIXED44_ROLE_TEMPLATE} from './fixed44-build-definition.js'
|
||||
import {GLOBALS,GROUPS,object,rows,sorted,text,projectCatalogueData,type Inventory,type Row,type Json} from './catalogue-projection.js'
|
||||
import {validateCatalogueLeafData} from './catalogue-leaves.js'
|
||||
import {EXPANDED44_DATA_POLICY,Expanded44TopologyBudgetData} from './expanded44-data-policy.js'
|
||||
export type LogicalRole='runtime'|'operator'|'capture'|'publisher'|'control'
|
||||
// Same DATA shape as RegistryRoleBindingsData. Provenance must come from protected host separately.
|
||||
export type InstallationRoleBindingsData=Readonly<Record<LogicalRole,Readonly<{name:string;signedOid:string}>>>
|
||||
const ROLE_KEYS:readonly LogicalRole[]=['runtime','operator','capture','publisher','control']
|
||||
const EXTRAS=['source_size','namespace_policy','publisher_column_escape'] as const
|
||||
const exact=(a:unknown,b:unknown,label:string)=>{if(canonical(a)!==canonical(b))throw Error('Fixed44 catalogue refused: '+label)}
|
||||
function captureBindings(input:InstallationRoleBindingsData):Readonly<Record<LogicalRole,string>> {
|
||||
exact(Object.keys(input).sort(),[...ROLE_KEYS].sort(),'binding roles')
|
||||
const names=new Set<string>(),oids=new Set<string>(),result={} as Record<LogicalRole,string>
|
||||
for(const role of ROLE_KEYS){const binding=input[role];exact(Object.keys(binding).sort(),['name','signedOid'],'binding fields')
|
||||
// Deliberately narrow deparse envelope; unsupported quoted/reserved names fail closed.
|
||||
if(!/^[a-z_][a-z0-9_]{0,62}$/.test(binding.name)||!/^[1-9][0-9]{0,9}$/.test(binding.signedOid)||BigInt(binding.signedOid)>4294967295n||names.has(binding.name)||oids.has(binding.signedOid))throw Error('Explicit distinct installation role binding required')
|
||||
names.add(binding.name);oids.add(binding.signedOid);result[role]=binding.name
|
||||
}
|
||||
return Object.freeze(result)
|
||||
}
|
||||
function instantiateExpected(names:Readonly<Record<LogicalRole,string>>):Inventory {
|
||||
const walk=(value:Json,key=''):Json=>{if(typeof value==='string')return value.replace(/@mm-role:(runtime|operator|capture|publisher|control)@/g,(_match,role:LogicalRole)=>names[role]);if(Array.isArray(value)){const result=value.map(v=>walk(v));return key==='acl'?(result as string[]).sort():result}if(value&&typeof value==='object')return Object.fromEntries(Object.entries(value).map(([k,v])=>[k,walk(v,k)]));return value}
|
||||
const data=walk(FIXED44_ROLE_TEMPLATE as unknown as Json) as unknown as Inventory
|
||||
return Object.fromEntries(Object.entries(data).map(([key,data])=>[key,sorted(data)]))
|
||||
}
|
||||
export class Fixed44CatalogueDataVerifier {
|
||||
private readonly names:Readonly<Record<LogicalRole,string>>;private readonly expected:Inventory;private readonly bindingsDigest:string
|
||||
private readonly globals:Inventory={};private headers:Row[]|undefined;private globalDone=false;private closed=false;private failed=false
|
||||
private readonly bases=new Set<string>();private readonly leaves=new Set<string>();private readonly templates=new Map<string,{table:Row;groups:Inventory}>()
|
||||
private readonly topology=new Expanded44TopologyBudgetData();private counts?:Row
|
||||
constructor(bindings:InstallationRoleBindingsData){this.names=captureBindings(bindings);this.expected=instantiateExpected(this.names);this.bindingsDigest=sha(canonical(bindings))}
|
||||
consume(record:CatalogueRecord):void {if(this.closed||this.failed)throw Error('Closed/refused catalogue verifier');try{this.consumeOpen(record)}catch(error){this.failed=true;throw error}}
|
||||
private consumeOpen(record:CatalogueRecord):void {
|
||||
if(record.kind==='global'){
|
||||
if(this.globalDone)throw Error('Late global refused');exact(Object.keys(record.value),['rows'],'global shape');const data=rows(record.value.rows)
|
||||
if(record.identity==='base_tables'){if(this.headers)throw Error('Duplicate base header');this.headers=data;return}
|
||||
if(![...GLOBALS,...EXTRAS].includes(record.identity as typeof GLOBALS[number])||Object.hasOwn(this.globals,record.identity))throw Error('Unknown/duplicate global section')
|
||||
this.globals[record.identity]=data;return
|
||||
}
|
||||
this.closeGlobals()
|
||||
if(record.kind==='leaf'&&record.identity==='!inventory_counts'){if(this.counts||this.bases.size!==104)throw Error('Early/duplicate counts');exact(Object.keys(record.value).sort(),['base','leaf'],'count shape');this.counts=record.value;return}
|
||||
exact(Object.keys(record.value).sort(),['table',...GROUPS].sort(),'relation shape')
|
||||
const table=object(record.value.table),groups:Inventory={}
|
||||
if(table.relname!==record.identity||table.relpersistence!=='p')throw Error('Relation identity/persistence refused')
|
||||
for(const name of GROUPS)groups[name]=rows(record.value[name])
|
||||
if(record.kind==='base'){
|
||||
if(this.counts||table.relispartition!==false||this.bases.has(record.identity))throw Error('Duplicate/late base')
|
||||
const header=this.headers!.find(t=>t.relname===record.identity);if(!header)throw Error('Foreign base refused');exact(table,header,'header/table')
|
||||
const actual=projectCatalogueData({...this.globals,...groups,tables:this.headers!})
|
||||
for(const name of GROUPS)exact(actual[name],this.expected[name].filter(r=>r.relname===record.identity),'base '+name+' '+record.identity)
|
||||
this.topology.acceptRelation('base',record.identity,groups);this.bases.add(record.identity);if(fixedCatalogueParentPeriodData(record.identity))this.templates.set(record.identity,{table,groups});return
|
||||
}
|
||||
if(record.kind!=='leaf'||!this.counts||this.bases.size!==104||this.leaves.has(record.identity))throw Error('Early/duplicate leaf')
|
||||
const edges=groups.inheritance;if(edges.length!==1)throw Error('Exact one leaf edge required')
|
||||
const parent=this.templates.get(text(edges[0].parent));if(!parent)throw Error('Foreign leaf parent refused')
|
||||
const raw:Inventory={...this.globals,tables:[parent.table,table],inheritance:edges}
|
||||
for(const name of GROUPS)if(name!=='inheritance')raw[name]=[...parent.groups[name],...groups[name]]
|
||||
validateCatalogueLeafData(raw,parent.table,table);this.topology.acceptRelation('leaf',record.identity,groups,text(edges[0].parent));this.leaves.add(record.identity)
|
||||
}
|
||||
private closeGlobals():void {
|
||||
if(this.globalDone)return
|
||||
if(!this.headers)throw Error('Missing base header')
|
||||
exact(Object.keys(this.globals).sort(),[...GLOBALS,...EXTRAS].sort(),'complete globals')
|
||||
const blank=Object.fromEntries(GROUPS.map(name=>[name,[]])) as Inventory
|
||||
const projected=projectCatalogueData({...this.globals,...blank,tables:this.headers})
|
||||
exact(projected.tables,this.expected.tables,'exact source44 base identities/owner/ACL')
|
||||
for(const name of GLOBALS)exact(projected[name],this.expected[name],'global '+name)
|
||||
const size=this.globals.source_size
|
||||
if(size.length!==1)throw Error('Source size shape refused')
|
||||
exact(Object.keys(size[0]).sort(),['tables','function_bytes','default_bytes','check_bytes','trigger_bytes'].sort(),'source size fields')
|
||||
if(typeof size[0].tables!=='number'||!Number.isSafeInteger(size[0].tables)||size[0].tables<1||size[0].tables>EXPANDED44_DATA_POLICY.maxRelations)throw Error('Expanded44 total relation DATA cap exceeded')
|
||||
if(['function_bytes','default_bytes','check_bytes','trigger_bytes'].some(k=>typeof size[0][k]!=='string'||!/^[0-9]+$/.test(text(size[0][k]))||BigInt(text(size[0][k]))>2097152n))throw Error('Source byte bound refused')
|
||||
if(this.globals.namespace_policy.length!==1)throw Error('Namespace shape refused')
|
||||
exact(Object.keys(this.globals.namespace_policy[0]).sort(),['relations','rules','routines','types','namespaces','default_acl'].sort(),'namespace fields')
|
||||
if(Object.values(this.globals.namespace_policy[0]).some(v=>v!==false)||this.globals.publisher_column_escape.length)throw Error('Namespace/column escape refused')
|
||||
this.globalDone=true
|
||||
}
|
||||
finish(){if(this.closed||this.failed)throw Error('Closed/refused catalogue verifier');try{this.closeGlobals();if(this.bases.size!==104||this.templates.size!==12||!this.counts||this.counts.base!==104||this.counts.leaf!==this.leaves.size||this.globals.source_size[0].tables!==104+this.leaves.size)throw Error('Incomplete catalogue/counts');this.closed=true
|
||||
return Object.freeze({format:'fixed44-catalogue-expanded-data-v1',policyVersion:EXPANDED44_DATA_POLICY.version,topology:this.topology.finish(),admissionClassificationVerified:false,definitionSha256:BUILD44_PROVENANCE.templateSha256,manifestSha256:BUILD44_PROVENANCE.manifestSha256,baseRelations:this.bases.size,leafRelations:this.leaves.size,bindingsDataSha256:this.bindingsDigest,cleanFourRoleSqlVerified:false,productionInstallationPolicyApproved:false,authority:false,readyAuthorized:false,defaultSupportAuthorized:false})
|
||||
}catch(error){this.failed=true;throw error}}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Typed port of the accepted twelve-parent leaf checks, with duplicate detection in stream owner.
|
||||
import {canonical} from './application-writer-profile-canonical.js'
|
||||
import {fixedCatalogueParentPeriodData} from './application-writer-profile-catalogue-parent-policy.js'
|
||||
import {omit,sorted,text,type Inventory,type Row} from './catalogue-projection.js'
|
||||
const guards=['candidate_application_statement_guard','candidate_truncate_preadmitted','server_partition_writer_guard']
|
||||
const exact=(a:unknown,b:unknown,label:string)=>{if(canonical(a)!==canonical(b))throw Error('Catalogue leaf refused: '+label)}
|
||||
export function validateCatalogueLeafData(raw:Inventory,parent:Row,leaf:Row):void {
|
||||
const parentName=text(parent.relname),name=text(leaf.relname),period=fixedCatalogueParentPeriodData(parentName)
|
||||
const edges=raw.inheritance
|
||||
if(edges.length!==1||edges[0].parent!==parentName||edges[0].child!==name)throw Error('Exact direct leaf parent required')
|
||||
const suffix=name.slice(parentName.length+1)
|
||||
if(!period||!name.startsWith(parentName+'_')||!new RegExp('^'+period+'_[0-9]{'+(period==='m'?6:8)+'}$').test(suffix)||leaf.relkind!=='r'||leaf.relpersistence!=='p'||leaf.relispartition!==true||leaf.owner!==parent.owner||leaf.namespace!=='public'||leaf.relrowsecurity!==false||leaf.relforcerowsecurity!==false||leaf.partition_key!==null)throw Error('Fixed managed leaf identity/owner refused')
|
||||
const stamp=suffix.slice(2),iso=stamp.slice(0,4)+'-'+stamp.slice(4,6)+'-'+(period==='m'?'01':stamp.slice(6,8))
|
||||
const start=new Date(iso+'T00:00:00.000Z'),end=new Date(start)
|
||||
if(!Number.isFinite(+start)||start.toISOString().slice(0,10)!==iso||period==='w'&&start.getUTCDay()!==1)throw Error('Leaf calendar refused')
|
||||
if(period==='m')end.setUTCMonth(end.getUTCMonth()+1);else end.setUTCDate(end.getUTCDate()+(period==='w'?7:1))
|
||||
if(start.getUTCFullYear()<100||end.getUTCFullYear()>9998)throw Error('Bounded leaf calendar refused')
|
||||
const bounds=/^FOR VALUES FROM \('([^']+)'\) TO \('([^']+)'\)$/.exec(text(leaf.partition_bound))
|
||||
const instant=(s:string)=>Date.parse(s.length===10?s+'T00:00:00.000Z':s)
|
||||
if(!bounds||instant(bounds[1])!==+start||instant(bounds[2])!==+end)throw Error('Leaf UTC bound refused')
|
||||
const acl=(r:Row)=>r.acl===null?null:(r.acl as string[]).slice().sort()
|
||||
exact(acl(leaf),acl(parent),'ACL')
|
||||
exact(leaf.reloptions,['flow_hour_facts','flow_daily_facts'].includes(parentName)?['fillfactor=70','autovacuum_vacuum_scale_factor=0.05','autovacuum_vacuum_cost_limit=2000']:null,'storage')
|
||||
exact(raw.columns.filter(r=>r.relname===name).map(r=>({...r,relname:parentName})),raw.columns.filter(r=>r.relname===parentName),'columns/defaults/identity')
|
||||
const functions=new Map(raw.functions.map(f=>[text(f.oid),text(f.namespace)+'.'+text(f.proname)+'('+text(f.arguments)+')']))
|
||||
const user=raw.triggers.filter(t=>t.relname===name&&!t.tgisinternal),source=raw.triggers.filter(t=>t.relname===parentName&&!t.tgisinternal)
|
||||
if(user.length!==3||source.length!==3||new Set(user.map(t=>t.tgname)).size!==3||user.some(t=>!guards.includes(text(t.tgname))))throw Error('Leaf guard set refused')
|
||||
for(const t of user){const p=source.find(p=>p.tgname===t.tgname)
|
||||
if(!p||t.tgtype!==p.tgtype||t.tgenabled!=='A'||functions.get(text(t.tgfoid))!==functions.get(text(p.tgfoid))||t.tgnargs!==0||t.arg_bytes!==0||t.unconditional!==true||t.tgdeferrable!==false||t.tginitdeferred!==false||t.tgoldtable!==null||t.tgnewtable!==null||t.tgconstraint!=='0'||(t.tgname==='server_partition_writer_guard'?t.tgparentid!==p.oid:t.tgparentid!=='0'))throw Error('Leaf trigger structure refused')
|
||||
exact(text(t.definition).replaceAll(name,parentName),p.definition,'guard definition')
|
||||
}
|
||||
const inherited=raw.triggers.filter(t=>t.relname===name&&t.tgisinternal),parentInternal=raw.triggers.filter(t=>t.relname===parentName&&t.tgisinternal)
|
||||
if(inherited.length!==parentInternal.length||new Set(inherited.map(t=>t.tgparentid)).size!==inherited.length||inherited.some(t=>!parentInternal.some(p=>t.tgparentid===p.oid&&t.tgfoid===p.tgfoid&&t.tgtype===p.tgtype&&t.tgenabled===p.tgenabled&&t.tgnargs===p.tgnargs&&t.arg_bytes===p.arg_bytes&&t.unconditional===p.unconditional&&t.tgdeferrable===p.tgdeferrable&&t.tginitdeferred===p.tginitdeferred&&t.tgoldtable===p.tgoldtable&&t.tgnewtable===p.tgnewtable)))throw Error('Inherited FK trigger identity/state refused')
|
||||
const constraints=(relname:string)=>sorted(raw.constraints.filter(r=>r.relname===relname).map(r=>omit(r,['relname','conname'])))
|
||||
exact(constraints(name),constraints(parentName),'constraints')
|
||||
const indexes=(relname:string)=>sorted(raw.indexes.filter(r=>r.relname===relname).map(r=>{const m=/^CREATE (UNIQUE )?INDEX [^]+? ON (?:ONLY )?public\.[a-z_0-9]+ (USING [^]+)$/.exec(text(r.definition));if(!m)throw Error('Leaf index template refused');return {...omit(r,['relname','index_name','definition']),definition:m[2]}}))
|
||||
exact(indexes(name),indexes(parentName),'indexes')
|
||||
exact(raw.roleGrants.filter(r=>r.relname===name).map(r=>omit(r,['relname'])),raw.roleGrants.filter(r=>r.relname===parentName).map(r=>omit(r,['relname'])),'effective grants')
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Private DATA projection. OIDs remain checked through structural readers separately.
|
||||
import {canonical} from './application-writer-profile-canonical.js'
|
||||
export type Json=null|boolean|number|string|Json[]|{[key:string]:Json}
|
||||
export type Row=Record<string,Json>
|
||||
export type Inventory=Record<string,Row[]>
|
||||
export const GROUPS=['columns','triggers','constraints','indexes','roleGrants','inheritance'] as const
|
||||
export const GLOBALS=['database','effectiveDatabase','effectiveFunctions','effectiveSchemas','effectiveSequences','events','functions','identities','memberships','parameters','roles','schemas','settings','sequences'] as const
|
||||
export const sorted=(rows:Row[])=>rows.slice().sort((a,b)=>canonical(a)<canonical(b)?-1:canonical(a)>canonical(b)?1:0)
|
||||
export const omit=(row:Row,names:readonly string[]):Row=>Object.fromEntries(Object.entries(row).filter(([key])=>!names.includes(key)))
|
||||
export function text(value:Json|undefined):string {if(typeof value!=='string')throw Error('Catalogue string required');return value}
|
||||
export function object(value:Json|undefined):Row {if(!value||typeof value!=='object'||Array.isArray(value))throw Error('Catalogue object required');return value}
|
||||
export function rows(value:Json|undefined):Row[] {if(!Array.isArray(value))throw Error('Catalogue array required');return value.map(object)}
|
||||
const acl=(value:Json):Json=>value===null?null:rowsOfStrings(value).slice().sort()
|
||||
function rowsOfStrings(value:Json):string[]{if(!Array.isArray(value)||value.some(v=>typeof v!=='string'))throw Error('Catalogue string array required');return value as string[]}
|
||||
export function projectCatalogueData(raw:Inventory):Inventory {
|
||||
const bases=new Set(raw.tables.filter(t=>t.relispartition===false).map(t=>text(t.relname)))
|
||||
const names=new Map(raw.tables.map(t=>[text(t.oid),text(t.relname)]))
|
||||
const functions=new Map(raw.functions.map(f=>[text(f.oid),text(f.namespace)+'.'+text(f.proname)+'('+text(f.arguments)+')']))
|
||||
const base=(data:Row[])=>data.filter(r=>bases.has(text(r.relname)))
|
||||
const result:Inventory={
|
||||
tables:base(raw.tables).map(r=>({...omit(r,['oid','relpersistence']),acl:acl(r.acl)})),
|
||||
columns:base(raw.columns),identities:raw.identities.map(r=>omit(r,['relation_oid'])),
|
||||
sequences:raw.sequences.map(r=>{const owner=names.get(text(r.owner_relation_oid));if(!owner)throw Error('Sequence owner relation missing');return {...omit(r,['oid','owner_relation_oid']),acl:acl(r.acl),owner_relation:owner}}),
|
||||
functions:raw.functions.map(r=>({...omit(r,['oid']),acl:acl(r.acl)})),
|
||||
triggers:base(raw.triggers).map(t=>({...omit(t,['oid','tgfoid','tgparentid','tgconstraint']),tgname:t.tgisinternal?text(t.tgname).replace(/_[0-9]+$/,'_{oid}'):t.tgname,function:functions.get(text(t.tgfoid))??'pg_catalog#'+text(t.tgfoid),parent:t.tgparentid!=='0',constraint:t.tgconstraint!=='0',definition:t.tgisinternal?text(t.definition).replace(/(RI_ConstraintTrigger_[ac])_[0-9]+/g,'$1_{oid}'):t.definition})),
|
||||
inheritance:raw.inheritance.filter(r=>bases.has(text(r.child))),constraints:base(raw.constraints),indexes:base(raw.indexes),
|
||||
schemas:raw.schemas.map(r=>({...r,acl:acl(r.acl)})),database:raw.database.map(r=>({...r,datname:'current-database',acl:acl(r.acl)})),
|
||||
effectiveSchemas:raw.effectiveSchemas,effectiveDatabase:raw.effectiveDatabase,memberships:raw.memberships,
|
||||
effectiveFunctions:raw.effectiveFunctions.map(r=>omit(r,['oid'])),effectiveSequences:raw.effectiveSequences,
|
||||
events:raw.events.map(r=>{const fn=functions.get(text(r.function_oid));if(!fn)throw Error('Event function missing');return {...omit(r,['function_oid']),function:fn}}),
|
||||
parameters:raw.parameters,settings:raw.settings.map(r=>({...r,database:r.database==='all-databases'?r.database:'current-database'})),roles:raw.roles,roleGrants:base(raw.roleGrants),
|
||||
}
|
||||
return Object.fromEntries(Object.entries(result).map(([name,data])=>[name,sorted(data)]))
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Typed bounded VALUE -> fixed44 semantic bridge. Requires real EOF, never emits authority.
|
||||
// SQL retained cursor/context/source-before-and-after remains the protected host's obligation.
|
||||
import {CatalogueValueStream} from './application-writer-profile-catalogue-values.js'
|
||||
import {Fixed44CatalogueDataVerifier,type InstallationRoleBindingsData} from './application-writer-profile-catalogue.js'
|
||||
export class Fixed44CatalogueValueBridgeData {
|
||||
private readonly values=new CatalogueValueStream();private readonly schema:Fixed44CatalogueDataVerifier
|
||||
private eof=false;private closed=false;private failed=false
|
||||
constructor(bindings:InstallationRoleBindingsData){this.schema=new Fixed44CatalogueDataVerifier(bindings)}
|
||||
acceptPage(page:readonly unknown[]):void {
|
||||
if(this.eof||this.closed||this.failed)throw Error('Closed/refused VALUE catalogue bridge')
|
||||
try{for(const record of this.values.acceptPage(page))this.schema.consume(record);if(page.length===0)this.eof=true}catch(error){this.failed=true;throw error}
|
||||
}
|
||||
finish(){if(this.closed||this.failed||!this.eof)throw Error('Real cursor EOF required for VALUE catalogue bridge');try{const values=this.values.finish(),catalogue=this.schema.finish();this.closed=true;return Object.freeze({values,catalogue,authority:false,sourceGenerationAuthenticated:false,allWriterDrainAuthenticated:false,readyAuthorized:false})}catch(error){this.failed=true;throw error}}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import {Fixed44CatalogueValueBridgeData} from './catalogue-value-bridge.js'
|
||||
import {FIXED44_CATALOGUE_BUILD_SQL} from './fixed44-build-sql.js'
|
||||
import {BUILD44_PROVENANCE,FIXED44_ROLE_TEMPLATE} from './fixed44-build-definition.js'
|
||||
import {canonical,sha} from './application-writer-profile-canonical.js'
|
||||
export function approvedDefinition(value:unknown){
|
||||
assert(value&&typeof value==='object');const v=value as Record<string,unknown>
|
||||
assert.deepEqual(Object.keys(v).sort(),['format','provenance','template']);assert.equal(v.format,'application-writer-source44-definition-approval-v1')
|
||||
assert.equal(canonical(v.provenance),canonical(BUILD44_PROVENANCE));assert.equal(canonical(v.template),canonical(FIXED44_ROLE_TEMPLATE))
|
||||
return BUILD44_PROVENANCE.templateSha256
|
||||
}
|
||||
/** Real incremental DATA verifier on caller's retained transaction. No pool or attestor. */
|
||||
export async function collectSource44Catalogue(s:{query:(sql:string,values?:unknown[])=>Promise<{rows:Record<string,unknown>[]} >},installation:{roleBindings:ConstructorParameters<typeof Fixed44CatalogueValueBridgeData>[0]}){
|
||||
const settings=(await s.query("SELECT pg_catalog.current_setting('search_path') AS path,pg_catalog.current_setting('TimeZone') AS zone,pg_catalog.current_setting('DateStyle') AS dates")).rows
|
||||
assert.equal(settings.length,1);assert(['path','zone','dates'].every(k=>typeof settings[0][k]==='string'))
|
||||
let cursor=false,primary:unknown,failed=false
|
||||
try{
|
||||
await s.query("SELECT pg_catalog.set_config('search_path','pg_catalog, public',true),pg_catalog.set_config('TimeZone','UTC',true),pg_catalog.set_config('DateStyle','ISO, YMD',true)")
|
||||
const bindings=installation.roleBindings,values=['operator','runtime','capture','publisher','control'].map(k=>bindings[k as keyof typeof bindings].name)
|
||||
const bridge=new Fixed44CatalogueValueBridgeData(bindings)
|
||||
await s.query('DECLARE mm_publisher44 NO SCROLL CURSOR WITHOUT HOLD FOR '+FIXED44_CATALOGUE_BUILD_SQL,values);cursor=true
|
||||
for(;;){const page=(await s.query('FETCH FORWARD 128 FROM mm_publisher44')).rows;bridge.acceptPage(page);if(page.length===0)break}
|
||||
const result=bridge.finish();await s.query('CLOSE mm_publisher44');cursor=false
|
||||
return {catalogSha256:sha(canonical({values:result.values.snapshotSha256,catalogue:result.catalogue})),definitionSha256:result.catalogue.definitionSha256,leafCount:result.catalogue.leafRelations}
|
||||
}catch(error){primary=error;failed=true;throw error}
|
||||
finally{
|
||||
try{if(cursor)await s.query('CLOSE mm_publisher44');await s.query("SELECT pg_catalog.set_config('search_path',$1,true),pg_catalog.set_config('TimeZone',$2,true),pg_catalog.set_config('DateStyle',$3,true)",[settings[0].path,settings[0].zone,settings[0].dates])}
|
||||
catch(error){if(failed)throw new AggregateError([primary,error],'Catalogue and context cleanup failed');throw error}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Internal fixed SQL/state verification. No signing, provisioning, pool or issuer.
|
||||
import {canonical} from '../../src/db/application-writer-profile-format.js'
|
||||
import {readProtectedRegistryStructureData} from '../../src/db/application-writer-profile-registry-structure.js'
|
||||
export {readCurrentApplicationWriterProfile as readCurrent,HISTORY,CURRENT} from '../../src/db/application-writer-profile-registry-read.js'
|
||||
|
||||
|
||||
const same=(a,b,phase)=>{if(canonical(a)!==canonical(b))throw Error('Protected registry refused: '+phase)}
|
||||
export async function ownedContext(client,installation,actor,phase){
|
||||
const rows=(await client.query(`SELECT CURRENT_USER::text AS actor,SESSION_USER::text AS login,r.oid::text AS role_oid,
|
||||
d.oid::text AS database_oid,d.datname AS database_name,pg_catalog.pg_backend_pid() AS pid,
|
||||
pg_catalog.pg_current_xact_id_if_assigned()::text AS xid,pg_catalog.current_setting('transaction_read_only') AS read_only,
|
||||
pg_catalog.current_setting('transaction_isolation') AS isolation,pg_catalog.current_setting('server_version_num')::int AS version,
|
||||
pg_catalog.pg_is_in_recovery() AS recovery,control.system_identifier::text AS system_identifier,
|
||||
(EXTRACT(EPOCH FROM pg_catalog.clock_timestamp())*1000)::bigint::text AS server_now,
|
||||
EXISTS(SELECT 1 FROM pg_catalog.pg_locks l WHERE l.pid=pg_catalog.pg_backend_pid() AND l.locktype='advisory'
|
||||
AND l.database=d.oid AND l.classid=0 AND l.objid=8723110 AND l.objsubid=1 AND l.granted
|
||||
AND l.mode=ANY($1::text[])) AS admitted
|
||||
FROM pg_catalog.pg_roles r CROSS JOIN pg_catalog.pg_database d CROSS JOIN pg_catalog.pg_control_system() control
|
||||
WHERE r.rolname=CURRENT_USER AND d.datname=pg_catalog.current_database()`,[phase.startsWith('publisher')?['ExclusiveLock']:['ShareLock','ExclusiveLock']])).rows
|
||||
const c=rows[0],expectedOid=installation.roleBindings.publisher.signedOid
|
||||
if(actor!==installation.roleBindings.publisher.name||rows.length!==1||!c||c.actor!==actor||c.login!==actor||c.role_oid!==expectedOid||c.recovery!==false||c.version<180000||c.version>=190000||c.admitted!==true||!Number.isSafeInteger(c.pid)||!/^[1-9][0-9]*$/.test(c.database_oid)||!(/^[1-9][0-9]{0,19}$/).test(c.system_identifier))throw Error('Distinct actual actor/source/admission context refused')
|
||||
if(phase==='publisher-read'&&(c.read_only!=='on'||c.isolation!=='repeatable read')||phase==='publisher-inspect'&&(c.read_only!=='on'||c.isolation!=='read committed')||['publisher-write','operator'].includes(phase)&&(c.read_only!=='off'||c.isolation!=='read committed'||!(/^[1-9][0-9]*$/).test(c.xid??'')))throw Error('Distinct owned transaction context refused')
|
||||
return c
|
||||
}
|
||||
export function sameOwned(a,b){same(Object.fromEntries(['actor','login','role_oid','database_oid','database_name','pid','xid','read_only','isolation','version','recovery','system_identifier','admitted'].map(k=>[k,a[k]])),Object.fromEntries(['actor','login','role_oid','database_oid','database_name','pid','xid','read_only','isolation','version','recovery','system_identifier','admitted'].map(k=>[k,b[k]])),'backend/fullXID/lifetime')}
|
||||
export const sourceCoordinates=c=>({systemIdentifier:c.system_identifier,databaseOid:c.database_oid,databaseName:c.database_name,serverVersionNum:c.version,inRecovery:false})
|
||||
|
||||
export function protectedStructure(client,installation){return readProtectedRegistryStructureData(client,installation.roleBindings)}
|
||||
@@ -0,0 +1,93 @@
|
||||
// VERSIONED DATA-only candidate. Never installation policy, enrollment or writer permission.
|
||||
import {BUILD44_PROVENANCE,FIXED44_ROLE_TEMPLATE} from './fixed44-build-definition.js'
|
||||
import {fixedCatalogueParentPeriodData} from './application-writer-profile-catalogue-parent-policy.js'
|
||||
import {GROUPS,type Inventory} from './catalogue-projection.js'
|
||||
export const EXPANDED44_POLICY_VERSION='source44-managed-leaves-3000-data-v1' as const
|
||||
const PLANNED=2000,EXPIRED=1000,PER_PARENT=1000,LEAVES=PLANNED+EXPIRED
|
||||
const source=FIXED44_ROLE_TEMPLATE
|
||||
const baseNames=new Set(source.tables.map(row=>String(row.relname)))
|
||||
const parents=[...baseNames].filter(name=>fixedCatalogueParentPeriodData(name)!==undefined)
|
||||
const perRelation=new Map([...baseNames].map(name=>[name,Object.fromEntries(GROUPS.map(group=>[group,source[group].filter(row=>(group==='inheritance'?row.child:row.relname)===name).length])) as Record<typeof GROUPS[number],number>]))
|
||||
const base=Object.freeze(Object.fromEntries(GROUPS.map(group=>[group,source[group].length])) as Record<typeof GROUPS[number],number>)
|
||||
const leafMax=Object.freeze(Object.fromEntries(GROUPS.map(group=>[group,group==='inheritance'?1:Math.max(...parents.map(name=>perRelation.get(name)![group]))])) as Record<typeof GROUPS[number],number>)
|
||||
if(baseNames.size!==104||source.tables.length!==104||parents.length!==12||leafMax.columns!==18||leafMax.constraints!==17||leafMax.triggers!==5||leafMax.indexes!==2||leafMax.roleGrants!==4)throw Error('Exact reviewed source44 topology changed; rederive policy')
|
||||
for(const row of source.constraints)if(parents.includes(String(row.relname))&&!['n','c','p','f'].includes(String(row.contype)))throw Error('Unreviewed parent constraint semantics')
|
||||
const total=Object.freeze(Object.fromEntries(GROUPS.map(group=>[group,base[group]+LEAVES*leafMax[group]])) as Record<typeof GROUPS[number],number>)
|
||||
export const EXPANDED44_DATA_POLICY=Object.freeze({version:EXPANDED44_POLICY_VERSION,baseRelations:baseNames.size,parents:parents.length,maxPlannedLeaves:PLANNED,maxExpiredBacklog:EXPIRED,maxManagedLeaves:LEAVES,maxPerParent:PER_PARENT,maxRelations:baseNames.size+LEAVES,maxWeightedPlan:2000,baseRows:base,maxLeafRows:leafMax,maxRows:total,definitionSha256:BUILD44_PROVENANCE.templateSha256,authority:false,productionInstallationPolicyApproved:false,settingsAdmissionAuthorized:false,readyAuthorized:false})
|
||||
export const expanded44ParentRowCountsData=()=>Object.freeze(Object.fromEntries(parents.map(name=>[name,Object.freeze({...perRelation.get(name)!})])))
|
||||
export interface ManagedLeafData {readonly parent:string;readonly name:string}
|
||||
export function validateManagedLeafNameData(input:ManagedLeafData):void {
|
||||
if(!input||Object.keys(input).sort().join(',')!=='name,parent'||typeof input.parent!=='string'||typeof input.name!=='string')throw Error('Exact managed leaf DATA shape required')
|
||||
const period=fixedCatalogueParentPeriodData(input.parent)
|
||||
if(!period||!input.name.startsWith(input.parent+'_'+period+'_'))throw Error('Unknown/cross-parent managed leaf')
|
||||
const stamp=input.name.slice(input.parent.length+3)
|
||||
if(!new RegExp('^[0-9]{'+(period==='m'?6:8)+'}$').test(stamp))throw Error('Managed leaf name shape refused')
|
||||
const iso=stamp.slice(0,4)+'-'+stamp.slice(4,6)+'-'+(period==='m'?'01':stamp.slice(6,8)),start=new Date(iso+'T00:00:00Z'),end=new Date(start)
|
||||
if(!Number.isFinite(+start)||start.toISOString().slice(0,10)!==iso||period==='w'&&start.getUTCDay()!==1)throw Error('Managed leaf calendar refused')
|
||||
if(period==='m')end.setUTCMonth(end.getUTCMonth()+1);else end.setUTCDate(end.getUTCDate()+(period==='w'?7:1))
|
||||
if(start.getUTCFullYear()<100||end.getUTCFullYear()>9998)throw Error('Managed leaf bounded calendar refused')
|
||||
}
|
||||
/** Only exact template-validated relation counts enter this sticky cumulative DATA budget. */
|
||||
export class Expanded44TopologyBudgetData {
|
||||
private readonly names=new Set<string>();private readonly perParent=new Map<string,number>();private readonly rows=Object.fromEntries(GROUPS.map(g=>[g,0])) as Record<typeof GROUPS[number],number>
|
||||
private bases=0;private leaves=0;private failed=false;private closed=false
|
||||
acceptRelation(kind:'base'|'leaf',name:string,groups:Inventory,parent?:string):void {
|
||||
try{
|
||||
if(this.failed||this.closed||this.names.has(name)||this.names.size>=EXPANDED44_DATA_POLICY.maxRelations)throw Error('Closed/duplicate/total relation DATA envelope')
|
||||
const expected=kind==='base'&&parent===undefined?perRelation.get(name):kind==='leaf'&&parent!==undefined&&parents.includes(parent)?{...perRelation.get(parent)!,inheritance:1}:undefined
|
||||
if(!expected)throw Error('Unknown source44 base/parent')
|
||||
if(kind==='leaf'){
|
||||
validateManagedLeafNameData({name,parent:parent!})
|
||||
if(this.leaves>=LEAVES||(this.perParent.get(parent!)??0)>=PER_PARENT)throw Error('Managed leaf total/per-parent DATA envelope')
|
||||
}
|
||||
for(const group of GROUPS){if(!Array.isArray(groups[group])||groups[group].length!==expected[group]||this.rows[group]+groups[group].length>total[group])throw Error('Exact source44 '+group+' DATA row envelope')}
|
||||
this.names.add(name);for(const group of GROUPS)this.rows[group]+=groups[group].length
|
||||
if(kind==='base')this.bases++;else{this.leaves++;this.perParent.set(parent!,1+(this.perParent.get(parent!)??0))}
|
||||
}catch(error){this.failed=true;throw error}
|
||||
}
|
||||
finish(){if(this.failed||this.closed||this.bases!==104)throw Error('Incomplete/refused expanded topology DATA');this.closed=true;return Object.freeze({baseRelations:this.bases,leafRelations:this.leaves,rows:Object.freeze({...this.rows}),perParent:Object.freeze(Object.fromEntries(this.perParent)),authority:false})}
|
||||
}
|
||||
/** Proposed projected admission DATA, not a trusted expiry observation or permission.
|
||||
* Genuine host must classify current leaves and bind same-session maintenance/settings separately.
|
||||
* Planned can include future leaves not present yet. Unclassified current leaves refuse.
|
||||
*/
|
||||
export function validateExpanded44AdmissionData(input:Readonly<{current:readonly ManagedLeafData[];planned:readonly ManagedLeafData[];expiredBacklog:readonly ManagedLeafData[]}>){
|
||||
if(!input||Object.keys(input).sort().join(',')!=='current,expiredBacklog,planned')throw Error('Exact admission DATA fields required')
|
||||
const parse=(values:readonly ManagedLeafData[],cap:number)=>{if(!Array.isArray(values)||values.length>cap)throw Error('Admission DATA list envelope');const map=new Map<string,ManagedLeafData>();for(const row of values){validateManagedLeafNameData(row);if(map.has(row.name))throw Error('Duplicate admission leaf');map.set(row.name,row)}return map}
|
||||
const current=parse(input.current,LEAVES),planned=parse(input.planned,PLANNED),expired=parse(input.expiredBacklog,EXPIRED)
|
||||
let weighted=0;for(const row of planned.values())weighted+=['flow_hour_facts','flow_daily_facts'].includes(row.parent)?2:1
|
||||
if(weighted>2000)throw Error('Source weighted planned budget exceeded')
|
||||
const projected=new Map(planned)
|
||||
for(const [name,row]of expired){if(projected.has(name)||!current.has(name))throw Error('Overlapping/noncurrent expired DATA');projected.set(name,row)}
|
||||
for(const [name,row]of current){const classified=projected.get(name);if(!classified||classified.parent!==row.parent)throw Error('Unclassified/cross-parent current leaf DATA')}
|
||||
if(projected.size>LEAVES)throw Error('Projected managed leaves DATA envelope')
|
||||
const perParent=new Map<string,number>();for(const row of projected.values()){const count=1+(perParent.get(row.parent)??0);if(count>PER_PARENT)throw Error('Projected per-parent DATA envelope');perParent.set(row.parent,count)}
|
||||
return Object.freeze({version:EXPANDED44_POLICY_VERSION,currentLeaves:current.size,plannedLeaves:planned.size,expiredBacklog:expired.size,projectedLeaves:projected.size,weightedPlan:weighted,perParent:Object.freeze(Object.fromEntries(perParent)),authority:false,expiryClassificationAuthenticated:false,settingsAdmissionAuthorized:false,readyAuthorized:false})
|
||||
}
|
||||
|
||||
/** Exact query meaning, not deparser-byte guesses. Unknown internal tree sizes
|
||||
* remain the unchanged actual SQL scalar preflight's obligation. */
|
||||
export const EXPANDED44_SOURCE_SIZE_SEMANTICS_DATA=Object.freeze({
|
||||
function_bytes:'sum pg_proc.prosrc UTF8 bytes; fixed global functions, no per-leaf routine copy',
|
||||
default_bytes:'sum pg_attrdef.adbin::text pg_node_tree bytes; NOT default_expression deparser bytes',
|
||||
check_bytes:'sum pg_constraint.conbin::text pg_node_tree bytes; NOT pg_get_constraintdef bytes',
|
||||
trigger_bytes:'sum octet_length(tgqual::text)+octet_length(tgargs); NULL qualifier terms are ignored by SQL sum; NOT trigger definition bytes',
|
||||
unchangedEachSourceScalarLimitBytes:2097152,internalTreeSizesDerived:false,actualSqlRequired:true,authority:false
|
||||
})
|
||||
export const expanded44LeafSourceShapeFactsData=()=>Object.freeze(Object.fromEntries(parents.map(parent=>{
|
||||
const columns=source.columns.filter(row=>row.relname===parent),constraints=source.constraints.filter(row=>row.relname===parent),triggers=source.triggers.filter(row=>row.relname===parent)
|
||||
return [parent,Object.freeze({defaultExpressionRows:columns.filter(row=>row.default_expression!==null).length,checkExpressionRows:constraints.filter(row=>row.contype==='c').length,qualifierRows:triggers.filter(row=>row.unconditional!==true).length,argumentBytes:triggers.reduce((sum,row)=>sum+Number(row.arg_bytes),0),routineCopiesPerLeaf:0,internalTreeBytesKnown:false})]
|
||||
})))
|
||||
|
||||
/** Exact extremum of expression ROW COUNTS, not an invented pg_node_tree byte cap.
|
||||
* Unit leaf costs +1000 capacity/parent +3000 total => top3 source costs.
|
||||
* Different scalar extrema may require different valid topologies.
|
||||
*/
|
||||
export function expanded44SourceShapeExtremaData(){
|
||||
const facts=expanded44LeafSourceShapeFactsData()
|
||||
const maximize=(field:'defaultExpressionRows'|'checkExpressionRows')=>{
|
||||
const selected=Object.entries(facts).sort((a,b)=>b[1][field]-a[1][field]||(a[0]<b[0]?-1:1)).slice(0,3)
|
||||
return Object.freeze({perLeafRows:selected.reduce((sum,[,value])=>sum+value[field]*PER_PARENT,0),cohorts:Object.freeze(selected.map(([parent,value])=>Object.freeze({parent,leaves:PER_PARENT,rowsPerLeaf:value[field]})))})
|
||||
}
|
||||
return Object.freeze({baseDefaultExpressionRows:source.columns.filter(row=>row.default_expression!==null).length,baseCheckExpressionRows:source.constraints.filter(row=>row.contype==='c').length,baseQualifiedTriggerRows:source.triggers.filter(row=>row.unconditional!==true).length,maxAddedDefaultExpressionRows:maximize('defaultExpressionRows'),maxAddedCheckExpressionRows:maximize('checkExpressionRows'),perLeafQualifiedTriggerRows:0,perLeafRoutineCopies:0,internalTreeByteUpperBoundKnown:false,actualSqlSourceScalarGateRequired:true,authority:false})
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// OFFLINE source-derived candidate; clean4role SQL verification PENDING. No installation enrollment.
|
||||
import type {Inventory} from "./catalogue-projection.js"
|
||||
export const BUILD44_PROVENANCE={"format":"source44-build-candidate-v1","manifestSha256":"fffae3381732e6f276ba276aca7185f6a87587edf8272ba42763b7f49bc59ef5","reviewed43Sha256":"4dd21cf909628f2750294e0d4398064d5219f6547c1bcebec910efabe9d57dcd","migration44Sha256":"37b88d1679be9bb47f16ccc8cae7373f20f9c05e7a8f662c3775f18d78bb5613","templateSha256":"56bbe286968fd950e5239672158f198eb2d600b7fa2fcb290c2ec5befd08d90c","baseTables":104,"newColumns":13,"newConstraints":21,"newFunctions":3,"newIndexes":5,"newApplicationTriggers":4,"newInheritedWriterGuards":3,"sourceNamingAlgorithm":"PG18 makeObjectName balanced ASCII byte truncation; ChooseConstraintName collision-free branch only; collisions refuse","namingSources":["https://raw.githubusercontent.com/postgres/postgres/REL_18_STABLE/src/backend/commands/indexcmds.c","https://raw.githubusercontent.com/postgres/postgres/REL_18_STABLE/src/backend/catalog/pg_constraint.c"],"cleanFourRoleSqlVerified":false,"productionInstallationPolicyApproved":false,"authority":false} as const
|
||||
export const FIXED44_ROLE_TEMPLATE:Readonly<Inventory>={"columns":[{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"access_scope","attnotnull":false,"attnum":8,"attstorage":"x","default_expression":null,"relname":"mcp_artifacts","type":"jsonb"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"account_private_key","attnotnull":true,"attnum":5,"attstorage":"x","default_expression":"''::text","relname":"acme_settings","type":"text"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"ack_proof","attnotnull":true,"attnum":5,"attstorage":"x","default_expression":null,"relname":"server_poll_acks","type":"text"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"acknowledged_at","attnotnull":false,"attnum":6,"attstorage":"p","default_expression":null,"relname":"mcp_drift","type":"timestamp with time zone"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"action","attnotnull":true,"attnum":3,"attstorage":"x","default_expression":null,"relname":"mcp_audit","type":"text"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"action","attnotnull":true,"attnum":6,"attstorage":"x","default_expression":"'route'::text","relname":"filter_rules","type":"text"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"action_id","attnotnull":true,"attnum":6,"attstorage":"x","default_expression":null,"relname":"vrf_production_tickets","type":"text"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"action_version","attnotnull":true,"attnum":7,"attstorage":"p","default_expression":null,"relname":"vrf_production_tickets","type":"integer"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"active","attnotnull":true,"attnum":6,"attstorage":"p","default_expression":"true","relname":"app_users","type":"boolean"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"actor_id","attnotnull":false,"attnum":3,"attstorage":"x","default_expression":null,"relname":"mcp_requests","type":"text"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"actor_id","attnotnull":true,"attnum":2,"attstorage":"x","default_expression":null,"relname":"mcp_audit","type":"text"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"actor_id","attnotnull":true,"attnum":3,"attstorage":"x","default_expression":null,"relname":"mcp_diagnostic_cancellations","type":"text"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"actor_id","attnotnull":true,"attnum":5,"attstorage":"x","default_expression":null,"relname":"server_probe_executions","type":"text"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"admissions","attnotnull":true,"attnum":2,"attstorage":"p","default_expression":"0","relname":"server_poll_storage_usage","type":"bigint"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"admissions","attnotnull":true,"attnum":2,"attstorage":"p","default_expression":"0","relname":"server_probe_storage_usage","type":"bigint"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"after_sealed","attnotnull":false,"attnum":4,"attstorage":"x","default_expression":null,"relname":"mcp_operation_checkpoints","type":"text"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"api_key","attnotnull":true,"attnum":3,"attstorage":"x","default_expression":"''::text","relname":"evobgp_settings","type":"text"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"applied_at","attnotnull":true,"attnum":2,"attstorage":"p","default_expression":"now()","relname":"schema_migrations","type":"timestamp with time zone"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"approved_at","attnotnull":false,"attnum":12,"attstorage":"p","default_expression":null,"relname":"mcp_plans","type":"timestamp with time zone"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"approved_by","attnotnull":false,"attnum":11,"attstorage":"x","default_expression":null,"relname":"mcp_plans","type":"text"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"archive_bytes","attnotnull":false,"attnum":21,"attstorage":"p","default_expression":null,"relname":"server_poll_storage_admissions","type":"integer"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"archive_bytes","attnotnull":true,"attnum":6,"attstorage":"p","default_expression":"0","relname":"server_poll_storage_usage","type":"bigint"},{"acl":null,"attcompression":"","attgenerated":"","attidentity":"","attname":"archive_proof","attnotnull":true,"attnum":17,"attstorage":"x","default_expression":null,"relname":"server_poll_archives","type":"text"},{"acl":null,"attcompression":"","attgenerateLine truncated
|
||||
@@ -0,0 +1,2 @@
|
||||
// Fixed query from accepted43, role-literal parameters only; limits/framing unchanged. Clean44 SQL proof pending.
|
||||
export const FIXED44_CATALOGUE_BUILD_SQL="WITH c_tables AS NOT MATERIALIZED (SELECT c.oid::text AS oid,n.nspname AS namespace,c.relname,c.relkind,c.relispartition,c.relpersistence,c.relrowsecurity,c.relforcerowsecurity,c.reloptions,\n pg_catalog.pg_get_userbyid(c.relowner) AS owner,c.relacl::text[] AS acl,pg_catalog.pg_get_expr(c.relpartbound,c.oid) AS partition_bound,\n pg_catalog.pg_get_partkeydef(c.oid) AS partition_key FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace\n WHERE n.nspname='public' AND c.relkind IN('r','p')),\nc_columns AS NOT MATERIALIZED (SELECT c.relname,a.attnum::int,a.attname,pg_catalog.format_type(a.atttypid,a.atttypmod) AS type,\n a.attnotnull,a.attidentity,a.attgenerated,a.attstorage,a.attcompression,a.attacl::text[] AS acl,pg_catalog.pg_get_expr(d.adbin,d.adrelid) AS default_expression\n FROM pg_catalog.pg_attribute a JOIN pg_catalog.pg_class c ON c.oid=a.attrelid JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace\n LEFT JOIN pg_catalog.pg_attrdef d ON d.adrelid=a.attrelid AND d.adnum=a.attnum\n WHERE n.nspname='public' AND c.relkind IN('r','p') AND a.attnum>0 AND NOT a.attisdropped),\nc_identities AS NOT MATERIALIZED (SELECT c.relname,c.oid::text AS relation_oid,a.attnum::int AS column_number,a.attname,a.attidentity,\n pg_catalog.pg_get_serial_sequence(pg_catalog.format('%I.%I',n.nspname,c.relname),a.attname) AS sequence_name\n FROM pg_catalog.pg_attribute a JOIN pg_catalog.pg_class c ON c.oid=a.attrelid JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace\n WHERE n.nspname='public' AND c.relkind IN('r','p') AND NOT c.relispartition AND a.attnum>0 AND NOT a.attisdropped AND a.attidentity<>'' LIMIT 33),\nc_sequences AS NOT MATERIALIZED (SELECT s.oid::text AS oid,n.nspname AS namespace,s.relname,pg_catalog.pg_get_userbyid(s.relowner) AS owner,\n s.relacl::text[] AS acl,q.seqtypid::int AS type_oid,q.seqstart::text,q.seqincrement::text,q.seqmax::text,q.seqmin::text,q.seqcache::text,q.seqcycle,\n d.refobjid::text AS owner_relation_oid,d.refobjsubid::int AS owner_column,d.deptype\n FROM pg_catalog.pg_class s JOIN pg_catalog.pg_namespace n ON n.oid=s.relnamespace JOIN pg_catalog.pg_sequence q ON q.seqrelid=s.oid\n LEFT JOIN pg_catalog.pg_depend d ON d.classid='pg_catalog.pg_class'::regclass AND d.objid=s.oid AND d.objsubid=0 AND d.refclassid='pg_catalog.pg_class'::regclass AND d.refobjsubid>0 AND d.deptype IN('a','i')\n WHERE n.nspname='public' LIMIT 33),\nc_functions AS NOT MATERIALIZED (SELECT p.oid::text,n.nspname AS namespace,p.proname,pg_catalog.pg_get_function_identity_arguments(p.oid) AS arguments,\n p.prokind,p.prosecdef,p.proconfig,p.proacl::text[] AS acl,pg_catalog.pg_get_userbyid(p.proowner) AS owner,l.lanname AS language,\n pg_catalog.pg_get_functiondef(p.oid) AS definition FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid=p.pronamespace\n JOIN pg_catalog.pg_language l ON l.oid=p.prolang WHERE n.nspname IN('public','mmapp_control') AND p.prokind IN('f','p') LIMIT 257),\nc_triggers AS NOT MATERIALIZED (SELECT t.oid::text AS oid,c.relname,t.tgname,t.tgtype::int,t.tgenabled,t.tgisinternal,t.tgfoid::text,t.tgparentid::text,t.tgconstraint::text,t.tgdeferrable,t.tginitdeferred,t.tgoldtable,t.tgnewtable,\n t.tgnargs,octet_length(t.tgargs)::int AS arg_bytes,t.tgqual IS NULL AS unconditional,pg_catalog.pg_get_triggerdef(t.oid) AS definition\n FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON c.oid=t.tgrelid JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace\n WHERE n.nspname='public'),\nc_inheritance AS NOT MATERIALIZED (SELECT p.relname AS parent,c.relname AS child FROM pg_catalog.pg_inherits i\n JOIN pg_catalog.pg_class p ON p.oid=i.inhparent JOIN pg_catalog.pg_class c ON c.oid=i.inhrelid JOIN pg_catalog.pg_namespace n ON n.oid=p.relnamespace\n WHERE n.nspname='public' AND p.relkind IN('r','p')),\nc_constraints AS NOT MATERIALIZED (SELECT c.relname,k.conname,k.contype,k.convalidated,k.condeferrable,k.condeferred,\n pg_catalog.pg_get_constraintdef(k.oid) AS definition FROM pg_catalog.pg_constraint k JOIN pg_catalog.pg_class c ON c.oid=k.conrelid\n JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace WHERE n.nspname='public'),\nc_indexes AS NOT MATERIALIZED (SELECT c.relname,i.relname AS index_name,x.indisvalid,x.indisready,x.indisunique,x.indisprimary,\n pg_catalog.pg_get_indexdef(i.oid) AS definition FROM pg_catalog.pg_index x JOIN pg_catalog.pg_class c ON c.oid=x.indrelid\n JOIN pg_catalog.pg_class i ON i.oid=x.indexrelid JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace\n WHERE n.nspname='public'),\nc_schemas AS NOT MATERIALIZED (SELECT nspname,pg_catalog.pg_get_userbyid(nspowner) AS owner,nspacl::text[] AS acl FROM pg_catalog.pg_namespace WHERE nspname IN('public','mmapp_control')),\nc_database AS NOT MATERIALIZED (SELECT datname,pg_catalog.pg_get_userbyid(datdba) AS owner,datacl::text[] AS acl FROM pg_catalog.pg_database WLine truncated
|
||||
@@ -0,0 +1,35 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import {X509Certificate} from 'node:crypto'
|
||||
import {checkServerIdentity} from 'node:tls'
|
||||
import {sha} from './operator-files.mjs'
|
||||
export const roleKeys=Object.freeze(['runtime','operator','capture','publisher','control'])
|
||||
export const STARTUP_OPTIONS='-c statement_timeout=10000 -c lock_timeout=5000 -c client_encoding=UTF8'
|
||||
export function assertJsDriverEnvironment(environment){assert(!Object.hasOwn(environment,'NODE_PG_FORCE_NATIVE'),'Native pg driver selector refused')}
|
||||
const hex=v=>typeof v==='string'&&/^[a-f0-9]{64}$/.test(v)
|
||||
const exact=(v,keys)=>v&&typeof v==='object'&&!Array.isArray(v)&&Object.keys(v).length===keys.length&&keys.every(k=>Object.hasOwn(v,k))
|
||||
function parsedApproval(value){
|
||||
assert(exact(value,['format','manifestSha256','definitionSha256','catalogSha256','expectedHeadSha256','validityMs','expiresAt']))
|
||||
assert(value.format==='application-writer-publication-approval-v1'&&['manifestSha256','definitionSha256','catalogSha256'].every(k=>hex(value[k]))&&(value.expectedHeadSha256===null||hex(value.expectedHeadSha256)))
|
||||
assert(Number.isSafeInteger(value.validityMs)&&value.validityMs>0&&value.validityMs<=86400000&&Number.isSafeInteger(value.expiresAt)&&value.expiresAt>0)
|
||||
return structuredClone(value)
|
||||
}
|
||||
export function approval(value){
|
||||
const pinned=parsedApproval(value);assert(pinned.expiresAt>Date.now());return pinned
|
||||
}
|
||||
// Pinned historical inspection data only; never publication authorization.
|
||||
export function reconciliationApproval(value){return parsedApproval(value)}
|
||||
export function poolOptions(transport,request,ca){
|
||||
assert(exact(transport,['host','port','user','password','database']))
|
||||
assert(typeof transport.host==='string'&&transport.host===request.tls.serverName&&Number.isSafeInteger(transport.port)&&transport.port>0&&transport.port<=65535&&transport.user===request.roles.publisher.name&&typeof transport.password==='string'&&transport.password.length>0&&transport.password.length<=4096&&transport.database===request.source.databaseName)
|
||||
assert(sha(ca)===request.tls.caSha256);new X509Certificate(ca)
|
||||
return {host:transport.host,port:transport.port,user:transport.user,password:transport.password,database:transport.database,max:1,connectionTimeoutMillis:5000,idleTimeoutMillis:5000,statement_timeout:10000,lock_timeout:5000,options:STARTUP_OPTIONS,application_name:'mmapp-writer-publisher',client_encoding:'UTF8',
|
||||
ssl:{ca,servername:request.tls.serverName,rejectUnauthorized:true,checkServerIdentity(host,cert){
|
||||
const failed=checkServerIdentity(request.tls.serverName,cert);if(failed)return failed
|
||||
try{if(host!==request.tls.serverName||sha(new X509Certificate(cert.raw).publicKey.export({format:'der',type:'spki'}))!==request.tls.spkiSha256)return Error('Publisher TLS SPKI refused')}catch{return Error('Publisher TLS certificate refused')}
|
||||
}}}
|
||||
}
|
||||
export function assertPublicationApproval(current,catalogue,structure,pinned,manifestSha256){
|
||||
assert(pinned.expiresAt>Date.now()&&pinned.manifestSha256===manifestSha256&&pinned.definitionSha256===catalogue.definitionSha256)
|
||||
assert(sha(JSON.stringify({base:catalogue.catalogSha256,security:structure.sha256}))===pinned.catalogSha256)
|
||||
assert((current?.headSha256??null)===pinned.expectedHeadSha256)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import {constants} from 'node:fs'
|
||||
import {open,lstat,realpath,link,unlink} from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import {createHash} from 'node:crypto'
|
||||
export const sha=bytes=>createHash('sha256').update(bytes).digest('hex')
|
||||
export async function protectedDirectory(directory){
|
||||
assert(process.platform!=='win32'&&typeof process.getuid==='function','Native Windows ACL backend unavailable')
|
||||
assert(path.isAbsolute(directory)&&await realpath(directory)===directory)
|
||||
const uid=process.getuid(),s=await lstat(directory)
|
||||
assert(s.isDirectory()&&!s.isSymbolicLink()&&s.uid===uid&&(s.mode&0o077)===0)
|
||||
for(let p=path.dirname(directory);;p=path.dirname(p)){const s=await lstat(p);assert(s.isDirectory()&&!s.isSymbolicLink()&&(s.uid===uid||s.uid===0));assert((s.mode&0o022)===0||s.uid===0&&(s.mode&0o1000)!==0);if(p===path.dirname(p))break}
|
||||
}
|
||||
export async function readProtected(file,maximum,expectedSha){
|
||||
await protectedDirectory(path.dirname(file));assert(await realpath(file)===file)
|
||||
const f=await open(file,constants.O_RDONLY|constants.O_NOFOLLOW)
|
||||
try{
|
||||
const a=await f.stat();assert(a.isFile()&&a.uid===process.getuid()&&(a.mode&0o077)===0&&a.size>0&&a.size<=maximum)
|
||||
const out=Buffer.alloc(a.size+1);let size=0
|
||||
while(size<out.length){const r=await f.read(out,size,out.length-size,size);if(!r.bytesRead)break;size+=r.bytesRead}
|
||||
const b=await f.stat();assert(size===a.size&&a.size===b.size&&a.mtimeMs===b.mtimeMs&&a.ctimeMs===b.ctimeMs)
|
||||
const bytes=out.subarray(0,size);if(expectedSha!==undefined)assert(sha(bytes)===expectedSha,'Independently pinned file mismatch');return bytes
|
||||
}finally{await f.close()}
|
||||
}
|
||||
export async function writeFile(file,bytes,options){
|
||||
assert(options?.flag==='wx'&&options.mode===0o600);const parent=path.dirname(file);await protectedDirectory(parent)
|
||||
const next=file+'.pending',f=await open(next,constants.O_WRONLY|constants.O_CREAT|constants.O_EXCL|constants.O_NOFOLLOW,0o600)
|
||||
try{await f.writeFile(bytes);await f.sync()}finally{await f.close()}
|
||||
await link(next,file);await unlink(next)
|
||||
const directory=await open(parent,constants.O_RDONLY|constants.O_DIRECTORY|constants.O_NOFOLLOW);try{await directory.sync()}finally{await directory.close()}
|
||||
}
|
||||
|
||||
/** One cooperative operator owns this journal through SQL AND post-ACK cleanup.
|
||||
* EEXIST/crash residue refuses; never infer stale ownership from PID or time. */
|
||||
export async function withJournalLease(directory,work){
|
||||
await protectedDirectory(directory)
|
||||
const filename=path.join(directory,'publisher-lease.lock')
|
||||
const handle=await open(filename,constants.O_WRONLY|constants.O_CREAT|constants.O_EXCL|constants.O_NOFOLLOW,0o600)
|
||||
let identity,result,primary,failed=false,cleanupFailed=false
|
||||
const syncDirectory=async()=>{const d=await open(directory,constants.O_RDONLY|constants.O_DIRECTORY|constants.O_NOFOLLOW);try{await d.sync()}finally{await d.close()}}
|
||||
try{
|
||||
identity=await handle.stat({bigint:true});await handle.writeFile('mmapp-writer-journal-lease-v1\n');await handle.sync();await syncDirectory()
|
||||
result=await work()
|
||||
}catch(error){primary=error;failed=true}
|
||||
finally{
|
||||
try{
|
||||
await protectedDirectory(directory)
|
||||
const current=await lstat(filename,{bigint:true})
|
||||
if(!identity||!current.isFile()||current.isSymbolicLink()||current.dev!==identity.dev||current.ino!==identity.ino)throw Error('Journal lease identity changed')
|
||||
await unlink(filename);await syncDirectory()
|
||||
}catch(error){cleanupFailed=true;primary??=error}
|
||||
try{await handle.close()}catch(error){cleanupFailed=true;primary??=error}
|
||||
}
|
||||
// Known COMMIT remains known if the filesystem cleanup fails later.
|
||||
if(!failed&&result?.outcome==='committed'&&cleanupFailed)return Object.freeze({...result,journalUnconfirmed:true})
|
||||
if(failed||cleanupFailed)throw primary
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import {mkdtemp, chmod, readdir, writeFile, readFile, unlink, rm} from 'node:fs/promises'
|
||||
import {tmpdir} from 'node:os'
|
||||
import path from 'node:path'
|
||||
import {withJournalLease} from './operator-files.mjs'
|
||||
|
||||
const posix = process.platform !== 'win32'
|
||||
async function owned(work) {
|
||||
const directory = await mkdtemp(path.join(tmpdir(), 'mm-operator-lease-'))
|
||||
try { await chmod(directory, 0o700); await work(directory) }
|
||||
finally { await rm(directory, {recursive: true, force: true}) }
|
||||
}
|
||||
|
||||
test('Windows refuses before callback or lease creation', {skip: posix}, async () => {
|
||||
await owned(async directory => {
|
||||
let called = false
|
||||
await assert.rejects(withJournalLease(directory, async () => { called = true }), /Windows ACL/)
|
||||
assert.equal(called, false)
|
||||
assert.deepEqual(await readdir(directory), [])
|
||||
})
|
||||
})
|
||||
|
||||
test('POSIX cooperative exclusion spans the entire callback and cleanup', {skip: !posix}, async () => {
|
||||
await owned(async directory => {
|
||||
let entered, release
|
||||
const ready = new Promise(resolve => { entered = resolve })
|
||||
const held = new Promise(resolve => { release = resolve })
|
||||
const first = withJournalLease(directory, async () => { entered(); await held; return 'first' })
|
||||
await ready
|
||||
try {
|
||||
let secondCalled = false
|
||||
await assert.rejects(withJournalLease(directory, async () => { secondCalled = true }), {code: 'EEXIST'})
|
||||
assert.equal(secondCalled, false)
|
||||
assert.deepEqual(await readdir(directory), ['publisher-lease.lock'])
|
||||
} finally { release(); await first }
|
||||
assert.deepEqual(await readdir(directory), [])
|
||||
assert.equal(await withJournalLease(directory, async () => 'next'), 'next')
|
||||
})
|
||||
})
|
||||
|
||||
test('POSIX unknown work retains its pending cursor and original error', {skip: !posix}, async () => {
|
||||
await owned(async directory => {
|
||||
const pending = path.join(directory, 'pending-publication.json'), failure = new Error('unknown publication')
|
||||
await assert.rejects(withJournalLease(directory, async () => {
|
||||
await writeFile(pending, 'pending', {flag: 'wx', mode: 0o600})
|
||||
throw failure
|
||||
}), error => error === failure)
|
||||
assert.equal(await readFile(pending, 'utf8'), 'pending')
|
||||
assert.deepEqual(await readdir(directory), ['pending-publication.json'])
|
||||
})
|
||||
})
|
||||
|
||||
test('POSIX foreign replacement remains and known COMMIT survives cleanup refusal', {skip: !posix}, async () => {
|
||||
await owned(async directory => {
|
||||
const lease = path.join(directory, 'publisher-lease.lock')
|
||||
const result = await withJournalLease(directory, async () => {
|
||||
await unlink(lease)
|
||||
await writeFile(lease, 'foreign lease', {flag: 'wx', mode: 0o600})
|
||||
return {outcome: 'committed', epoch: '9', headSha256: 'a'.repeat(64)}
|
||||
})
|
||||
assert.equal(result.outcome, 'committed')
|
||||
assert.equal(result.epoch, '9')
|
||||
assert.equal(result.journalUnconfirmed, true)
|
||||
assert.equal(await readFile(lease, 'utf8'), 'foreign lease')
|
||||
await assert.rejects(withJournalLease(directory, async () => assert.fail('foreign lease was adopted')), {code: 'EEXIST'})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,180 @@
|
||||
// OPERATOR/CLI ONLY. Runtime host must never import this private-key module.
|
||||
import pg from 'pg'
|
||||
import {createPrivateKey,createPublicKey,sign} from 'node:crypto'
|
||||
import {open,lstat,realpath,unlink} from 'node:fs/promises'
|
||||
import {writeFile,withJournalLease} from './operator-files.mjs'
|
||||
import path from 'node:path'
|
||||
import {captureDatabaseSourceObservation,assertDatabaseSourceObservationCurrent} from '../../src/db/database-source-observation.js'
|
||||
import {MIGRATION_MANIFEST,migrationManifestDigest} from '../../src/db/migration-manifest.js'
|
||||
|
||||
|
||||
import {ownedContext,sameOwned,sourceCoordinates,protectedStructure,readCurrent,HISTORY,CURRENT} from './context.mjs'
|
||||
import {bytes,canonical,sha,nextEpoch,POLICY_DOMAIN,HEAD_DOMAIN,KEY,liveValidity,policySchema,headSchema,publicKey} from '../../src/db/application-writer-profile-format.js'
|
||||
export function createConnectedPublisher(installation,collectFixedCatalogue,sourceVerifier,approval){
|
||||
const PUBLISHER=installation.roleBindings.publisher.name
|
||||
class ProfilePublicationError extends Error{
|
||||
automaticReplay=false
|
||||
constructor(outcome,phase,proposal,cleanupUnconfirmed,cause){super('Protected policy publication requires exact inspection',{cause});Object.assign(this,{outcome,phase,proposal,cleanupUnconfirmed})}
|
||||
}
|
||||
async function privateKey(filename){
|
||||
const resolved=await realpath(filename),meta=await lstat(filename)
|
||||
if(resolved!==path.resolve(filename)||!meta.isFile()||meta.isSymbolicLink()||meta.size<1||meta.size>8192)throw Error('Explicit bounded operator-only key file required')
|
||||
const handle=await open(resolved,'r');let data
|
||||
try{const stat=await handle.stat();if(stat.dev!==meta.dev||stat.ino!==meta.ino||stat.size!==meta.size)throw Error('Operator key changed');data=await handle.readFile();if(data.length!==stat.size)throw Error('Operator key size changed')}finally{await handle.close()}
|
||||
let key;try{key=createPrivateKey(data)}finally{data.fill(0)}
|
||||
if(key.asymmetricKeyType!=='ed25519'||sha(createPublicKey(key).export({format:'der',type:'spki'}))!==installation.publicKeySpkiSha256)throw Error('Private key does not match fixed installation')
|
||||
publicKey(installation);return key
|
||||
}
|
||||
// One retained actual Pool client; no injected SQL facade, SET ROLE, new pool,
|
||||
// detached application callbacks or cleanup/COMMIT races.
|
||||
async function session(pool,deadlineAt,work){
|
||||
if(!(pool instanceof pg.Pool)||!Number.isSafeInteger(deadlineAt)||deadlineAt<=Date.now()||deadlineAt-Date.now()>120000)throw Error('Bounded genuine publisher pool/lifetime required')
|
||||
let client,retired=false,released=false,locked=false,begun=false,commitSent=false,committed=false,confirmedValue,proposal,phase='checkout',stopReject,cleanupUnconfirmed=false
|
||||
const stopped=new Promise((_,reject)=>{stopReject=reject});void stopped.catch(()=>{})
|
||||
const release=bad=>{if(!client||released)return;released=true;client.off('error',fault);client.off('end',fault);try{client.release(bad)}catch{cleanupUnconfirmed=true}}
|
||||
const fault=()=>{if(retired)return;retired=true;cleanupUnconfirmed=true;release(true);stopReject(Error('Publisher connection/lifetime ended'))}
|
||||
const timer=setTimeout(fault,Math.max(1,deadlineAt-Date.now())),bounded=p=>Promise.race([p,stopped])
|
||||
const query=async(text,values)=>{if(retired||Date.now()>=deadlineAt)throw Error('Publisher lifetime closed');return bounded(client.query(text,values))}
|
||||
const state={query,phase:value=>{phase=value},proposal:value=>{proposal=Object.freeze(value)},begun:value=>{begun=value},commit:async value=>{
|
||||
confirmedValue=Object.freeze({...value})
|
||||
commitSent=true
|
||||
const response=await bounded(client.query('COMMIT').then(result=>{if(result.command==='COMMIT'){committed=true;begun=false}return result}))
|
||||
if(response.command!=='COMMIT')throw Error('Publisher COMMIT acknowledgement absent')
|
||||
}}
|
||||
let value,primary,failed=false
|
||||
try{
|
||||
const pending=pool.connect().then(value=>{client=value;if(retired){release(true);return value}client.on('error',fault);client.on('end',fault);return value});void pending.catch(()=>{})
|
||||
await bounded(pending);if(retired)throw Error('Late publisher checkout')
|
||||
await query("SET statement_timeout='10s'");await query("SET lock_timeout='5s'")
|
||||
phase='exclusive-admission';await query('SELECT pg_catalog.pg_advisory_lock($1::bigint)',[KEY]);locked=true
|
||||
value=await work(state)
|
||||
}catch(error){failed=true;primary=error}
|
||||
finally{
|
||||
if(!retired&&client){
|
||||
if(begun&&!commitSent)try{const r=await query('ROLLBACK');if(r.command!=='ROLLBACK')throw Error('Publisher rollback unconfirmed');begun=false}catch{fault()}
|
||||
if(commitSent&&!committed){cleanupUnconfirmed=true;retired=true;release(true)}
|
||||
if(!retired&&locked)try{const r=await query('SELECT pg_catalog.pg_advisory_unlock($1::bigint) AS released',[KEY]);if(r.rows.length!==1||r.rows[0].released!==true)throw Error('Publisher unlock unconfirmed');locked=false}catch{fault()}
|
||||
if(!retired)try{await query('RESET lock_timeout');await query('RESET statement_timeout')}catch{fault()}
|
||||
}
|
||||
clearTimeout(timer);release(retired||cleanupUnconfirmed||begun||locked||committed&&failed)
|
||||
}
|
||||
if(committed)return Object.freeze({...confirmedValue,...value,outcome:'committed',proposal,cleanupUnconfirmed:cleanupUnconfirmed||retired||failed,automaticReplay:false})
|
||||
if(failed||cleanupUnconfirmed||retired)throw new ProfilePublicationError(commitSent||begun?'unknown':'not-committed',phase,proposal,cleanupUnconfirmed||retired,primary)
|
||||
return value
|
||||
}
|
||||
async function publishCurrentProfile(pool,{privateKeyFile,expectedHeadSha256,journalDirectory,validityMs=86400000,deadlineAt=Date.now()+120000}){
|
||||
if(!Number.isSafeInteger(validityMs)||validityMs<1||validityMs>86400000||expectedHeadSha256!==null&&!(/^[a-f0-9]{64}$/).test(expectedHeadSha256))throw Error('Explicit previous head and bounded validity required')
|
||||
const key=await privateKey(privateKeyFile),directory=await realpath(journalDirectory),pendingFile=path.join(directory,'pending-publication.json')
|
||||
try{await lstat(pendingFile);throw Error('Pending publication must be reconciled before signing')}catch(error){if(error.code!=='ENOENT')throw error}
|
||||
const result=await session(pool,deadlineAt,async s=>{
|
||||
s.phase('publisher-read');await s.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');s.begun(true)
|
||||
const first=await ownedContext(s,installation,PUBLISHER,'publisher-read')
|
||||
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)
|
||||
if(canonical(sourceCoordinates(first))!==canonical(installation.expectedSource))throw Error('Pinned source mismatch')
|
||||
const security=await protectedStructure(s,installation),catalogue=await collectFixedCatalogue(s)
|
||||
const markers=(await s.query('SELECT id FROM public.schema_migrations ORDER BY id LIMIT 45')).rows.map(row=>row.id)
|
||||
if(canonical(markers)!==canonical(MIGRATION_MANIFEST.map(pin=>pin.file.slice(0,-4))))throw Error('Exact current migration marker metadata required')
|
||||
const old=await readCurrent(s,installation,{allowEmpty:true,validNow:false})
|
||||
if((old?.headSha256??null)!==expectedHeadSha256)throw Error('Expected protected current head mismatch')
|
||||
const catalogSha256=sha(canonical({base:catalogue.catalogSha256,security:security.sha256}))
|
||||
if(approval.expiresAt<=Date.now()||approval.manifestSha256!==migrationManifestDigest()||approval.definitionSha256!==catalogue.definitionSha256||approval.catalogSha256!==catalogSha256)throw Error('Independently approved policy differs from actual source44 catalogue')
|
||||
if(old&&(canonical(old.policy.source)!==canonical(sourceCoordinates(first))||canonical(old.policy.roles)!==canonical(security.roles)||old.policy.manifestSha256!==migrationManifestDigest()||old.policy.definitionSha256!==catalogue.definitionSha256||old.policy.catalogSha256!==catalogSha256))throw Error('Renewal cannot adopt a changed source/subject/policy')
|
||||
await sourceVerifier.verify(facade,observation,options);await assertDatabaseSourceObservationCurrent(facade,observation,options);sameOwned(first,await ownedContext(s,installation,PUBLISHER,'publisher-read'))
|
||||
await s.query('COMMIT');s.begun(false)
|
||||
const number=old?nextEpoch(old.epoch):'1',now=Date.now()
|
||||
const body=policySchema.parse({format:'application-writer-policy-v1',installationId:installation.installationId,epoch:number,issuedAt:now,notBefore:now,notAfter:now+validityMs,source:sourceCoordinates(first),sourceObservationSha256:sha(canonical(observation)),roles:security.roles,manifestSha256:migrationManifestDigest(),definitionSha256:catalogue.definitionSha256,catalogSha256,futureLeaves:'fixed12-native-policy-v1'})
|
||||
const envelope=(domain,body)=>bytes({body,signature:sign(null,Buffer.from(domain+canonical(body)),key).toString('base64url')})
|
||||
const policyBytes=envelope(POLICY_DOMAIN,body),policySha256=sha(policyBytes)
|
||||
const head=headSchema.parse({format:'application-writer-head-v1',installationId:installation.installationId,epoch:number,policySha256,previousHeadSha256:old?.headSha256??null})
|
||||
const headBytes=envelope(HEAD_DOMAIN,head),headSha256=sha(headBytes)
|
||||
const proposal={installationId:installation.installationId,epoch:number,headSha256,previousHeadSha256:head.previousHeadSha256,policySha256,publisherPid:first.pid}
|
||||
s.proposal(proposal)
|
||||
// Public, bounded, exclusively created journal; neither key nor SQL data.
|
||||
await writeFile(path.join(directory,'proposal-'+headSha256+'.json'),bytes(proposal),{flag:'wx',mode:0o600})
|
||||
await writeFile(pendingFile,bytes(proposal),{flag:'wx',mode:0o600})
|
||||
s.phase('publisher-write');await s.query('BEGIN ISOLATION LEVEL READ COMMITTED READ WRITE');s.begun(true)
|
||||
await s.query('SELECT pg_catalog.pg_current_xact_id()');await s.query('SELECT pg_catalog.pg_advisory_xact_lock_shared($1::bigint)',[KEY])
|
||||
const owner=await ownedContext(s,installation,PUBLISHER,'publisher-write')
|
||||
if(owner.pid!==first.pid||canonical(sourceCoordinates(owner))!==canonical(sourceCoordinates(first)))throw Error('Retained publisher source/backend changed')
|
||||
await s.query(`SELECT id FROM mmapp_control.${CURRENT} WHERE id=1 FOR UPDATE`)
|
||||
const current=await readCurrent(s,installation,{allowEmpty:true,validNow:false})
|
||||
if((current?.headSha256??null)!==expectedHeadSha256)throw Error('CAS predecessor changed')
|
||||
const againSecurity=await protectedStructure(s,installation),againCatalogue=await collectFixedCatalogue(s)
|
||||
if(sha(canonical({base:againCatalogue.catalogSha256,security:againSecurity.sha256}))!==catalogSha256)throw Error('Policy drift during retained exclusive publication')
|
||||
if(Date.now()>=body.notAfter||Date.now()>=approval.expiresAt)throw Error('Policy or approval expired before publication dispatch')
|
||||
await s.query(`INSERT INTO mmapp_control.${HISTORY}(installation_id,epoch,head_sha256,previous_installation_id,previous_epoch,previous_head_sha256,policy_sha256,policy_bytes,head_bytes) VALUES($1::uuid,$2::bigint,$3,$4::uuid,$5::bigint,$6,$7,$8::bytea,$9::bytea)`,[installation.installationId,number,headSha256,old?installation.installationId:null,old?.epoch??null,old?.headSha256??null,policySha256,policyBytes,headBytes])
|
||||
const changed=old?await s.query(`UPDATE mmapp_control.${CURRENT} SET epoch=$1::bigint,head_sha256=$2 WHERE id=1 AND installation_id=$3::uuid AND epoch=$4::bigint AND head_sha256=$5 RETURNING id`,[number,headSha256,installation.installationId,old.epoch,old.headSha256]):await s.query(`INSERT INTO mmapp_control.${CURRENT}(id,installation_id,epoch,head_sha256) VALUES(1,$1::uuid,$2::bigint,$3) RETURNING id`,[installation.installationId,number,headSha256])
|
||||
if(changed.rows.length!==1)throw Error('Exactly one protected current CAS required')
|
||||
sameOwned(owner,await ownedContext(s,installation,PUBLISHER,'publisher-write'));if(Date.now()>=body.notAfter||Date.now()>=approval.expiresAt)throw Error('Policy or approval expired before publication terminal');s.phase('commit');const result={epoch:number,headSha256,notAfter:body.notAfter};await s.commit(result)
|
||||
const terminal=await ownedContext(s,installation,PUBLISHER,'publisher-session')
|
||||
if(terminal.pid!==first.pid||canonical(sourceCoordinates(terminal))!==canonical(sourceCoordinates(first)))throw Error('Publisher session continuity unconfirmed after actual ACK')
|
||||
return result
|
||||
})
|
||||
// A journal failure cannot turn a confirmed publication into retryable failure.
|
||||
if(result.outcome==='committed'&&!result.cleanupUnconfirmed){try{const cursor=await boundedPending(pendingFile);if(cursor.headSha256!==result.headSha256)throw Error('Pending cursor differs');await unlink(pendingFile)}catch{return Object.freeze({...result,journalUnconfirmed:true})}}
|
||||
return result
|
||||
}
|
||||
async function inspectPublication(pool,proposal,{deadlineAt=Date.now()+120000}={}){
|
||||
if(!proposal||proposal.installationId!==installation.installationId||!(/^[a-f0-9]{64}$/).test(proposal.headSha256)||proposal.previousHeadSha256!==null&&!(/^[a-f0-9]{64}$/).test(proposal.previousHeadSha256))throw Error('Bounded public proposal required')
|
||||
return session(pool,deadlineAt,async s=>{
|
||||
s.phase('publication-inspection');await s.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');s.begun(true)
|
||||
const first=await ownedContext(s,installation,PUBLISHER,'publisher-read');await protectedStructure(s,installation)
|
||||
if(canonical(sourceCoordinates(first))!==canonical(installation.expectedSource))throw Error('Pinned inspection 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 current=await readCurrent(s,installation,{allowEmpty:true,validNow:false})
|
||||
const proposed=(await s.query(`SELECT epoch::text,head_sha256 FROM mmapp_control.${HISTORY} WHERE installation_id=$1::uuid AND epoch=$2::bigint`,[installation.installationId,proposal.epoch])).rows
|
||||
await sourceVerifier.verify(facade,observation,options);sameOwned(first,await ownedContext(s,installation,PUBLISHER,'publisher-read'))
|
||||
const response=await s.query('ROLLBACK');if(response.command!=='ROLLBACK')throw Error('Inspection terminal unconfirmed');s.begun(false)
|
||||
if(current?.headSha256===proposal.headSha256&&proposed.length===1&&proposed[0].head_sha256===proposal.headSha256)return {state:'confirmed',headSha256:current.headSha256}
|
||||
if((current?.headSha256??null)===proposal.previousHeadSha256&&proposed.length===0)return {state:'not-committed',headSha256:current?.headSha256??null}
|
||||
throw Error('Publication state requires operator inspection; no retry')
|
||||
})
|
||||
}
|
||||
// Read-only diagnostics on the actual protected publisher session. This does
|
||||
// not admit application/operator writes, bootstrap, restore, or READY.
|
||||
async function preflightCurrentProfile(pool,{deadlineAt=Date.now()+120000}={}){
|
||||
return session(pool,deadlineAt,async s=>{
|
||||
s.phase('profile-preflight');await s.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');s.begun(true)
|
||||
const first=await ownedContext(s,installation,PUBLISHER,'publisher-read')
|
||||
if(canonical(sourceCoordinates(first))!==canonical(installation.expectedSource))throw Error('Pinned preflight source mismatch')
|
||||
const facade=Object.freeze({query:(sql,values)=>s.query(sql,values)}),options={deadlineAt}
|
||||
const observation=await captureDatabaseSourceObservation(facade,options);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)
|
||||
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
|
||||
if(clock.length!==1)throw Error('Preflight terminal server clock unavailable')
|
||||
liveValidity(current.policy,Number(clock[0].now))
|
||||
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})
|
||||
})
|
||||
}
|
||||
async function boundedPending(filename){
|
||||
const h=await open(filename,'r');try{const stat=await h.stat();if(!stat.isFile()||stat.size<1||stat.size>2048)throw Error('Bounded operator pending cursor required');const raw=await h.readFile();if(raw.length!==stat.size)throw Error('Pending cursor changed');const p=JSON.parse(raw.toString('utf8'));if(!bytes(p).equals(raw)||Object.keys(p).sort().join(',')!=='epoch,headSha256,installationId,policySha256,previousHeadSha256,publisherPid'||!(/^[1-9][0-9]*$/).test(p.epoch)||!Number.isSafeInteger(p.publisherPid))throw Error('Exact pending cursor refused');return p}finally{await h.close()}
|
||||
}
|
||||
async function reconcilePendingPublication(pool,{journalDirectory,deadlineAt=Date.now()+120000}){
|
||||
const directory=await realpath(journalDirectory),filename=path.join(directory,'pending-publication.json')
|
||||
let proposal;try{proposal=await boundedPending(filename)}catch(error){if(error.code==='ENOENT')return {state:'idle'};throw error}
|
||||
const result=await inspectPublication(pool,proposal,{deadlineAt})
|
||||
const after=await boundedPending(filename);if(canonical(after)!==canonical(proposal))throw Error('Pending cursor changed during protected inspection')
|
||||
await unlink(filename)
|
||||
return {...result,proposal}
|
||||
}
|
||||
return Object.freeze({
|
||||
preflightCurrentProfile,
|
||||
publishCurrentProfile:(pool,options)=>withJournalLease(options.journalDirectory,()=>publishCurrentProfile(pool,options)),
|
||||
inspectPublication,
|
||||
reconcilePendingPublication:(pool,options)=>withJournalLease(options.journalDirectory,()=>reconcilePendingPublication(pool,options)),
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user