Files
MikrotikManager/backend/scripts/writer-profile/expanded44-data-policy.ts
T

94 lines
10 KiB
TypeScript

// 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})
}