import fs from 'node:fs' import path from 'node:path' const read=p=>JSON.parse(fs.readFileSync(p,'utf8')) const src=read('contracts/configuration/kbx.configuration.json') const providers=read('contracts/providers/kbx.providers.json') const manifest=read('generated/configuration-manifest.json') const errors=[] const keys=new Set(),envs=new Set() for(const s of src.settings??[]){ if(keys.has(s.key))errors.push(`duplicate configuration key ${s.key}`);keys.add(s.key) if(envs.has(s.env))errors.push(`duplicate environment variable ${s.env}`);envs.add(s.env) if(s.secret && s.default!==undefined)errors.push(`secret setting must not define a default: ${s.key}`) if(s.secret && !(s.allowedSources??[]).every(x=>['environment','secret-store','user-secrets'].includes(x)))errors.push(`secret setting has unsafe source: ${s.key}`) if(s.type==='enum' && !(s.allowedValues?.length))errors.push(`enum setting missing allowedValues: ${s.key}`) if((s.minimum!==undefined||s.maximum!==undefined)&&s.type!=='integer')errors.push(`numeric bounds only supported for integer in v22: ${s.key}`) } for(const e of src.environments??[]){ if(!['Development','Test','Staging','Production'].includes(e.id))errors.push(`unsupported environment profile ${e.id}`) } const production=src.environments.find(x=>x.id==='Production') if(!production||production.migrationStrategy!=='predeploy')errors.push('Production must use predeploy migration strategy') if(!production?.requireHttps)errors.push('Production must require HTTPS') if(production?.providerEndpointOverrideAllowed)errors.push('Production provider endpoint overrides must be forbidden') const test=src.environments.find(x=>x.id==='Test') if(!test||test.providerNetwork!=='forbidden')errors.push('Test environment must forbid live provider network access') const providerCredentialKeys=[] for(const p of providers.providers??[]){ const policy=p.kbxPolicy??{} for(const prop of ['credentialKey','appKeyCredential','appSecretCredential','environmentKey'])if(policy[prop])providerCredentialKeys.push(policy[prop]) } for(const key of providerCredentialKeys)if(!keys.has(key))errors.push(`provider configuration key missing from configuration catalog: ${key}`) if(manifest.settingCount!==src.settings.length||manifest.environmentCount!==src.environments.length)errors.push('generated configuration manifest count drift') const ts=fs.readFileSync('packages/kbx-contracts/src/generated/configurationCatalog.ts','utf8') const cs=fs.readFileSync('backend/Shared/Configuration/Generated/KbxConfigurationCatalog.g.cs','utf8') if(!ts.includes(manifest.sourceSha256)||!cs.includes(manifest.sourceSha256))errors.push('configuration generated source SHA parity missing') const envTemplate=fs.readFileSync('deploy/kbx/.env.kbx.example','utf8') for(const s of src.settings){ if(!envTemplate.includes(`${s.env}=`))errors.push(`env template missing ${s.env}`) if(s.secret){ const line=envTemplate.split(/\r?\n/).find(x=>x.startsWith(`${s.env}=`))??'' const raw=line.slice(s.env.length+1).split(' # ')[0] if(raw.trim())errors.push(`generated env template contains a secret value: ${s.env}`) } } function walk(dir){ const out=[] if(!fs.existsSync(dir))return out for(const e of fs.readdirSync(dir,{withFileTypes:true})){ const p=path.join(dir,e.name) if(e.isDirectory())out.push(...walk(p));else out.push(p) } return out } // Direct environment/config access outside the configuration boundary becomes hidden configuration debt. for(const file of walk('backend').filter(x=>x.endsWith('.cs')&&!x.includes('/Shared/Configuration/'))){ const text=fs.readFileSync(file,'utf8') if(/Environment\.GetEnvironmentVariable\s*\(/.test(text))errors.push(`direct environment read outside configuration boundary: ${file}`) if(/IConfiguration\s*\[/.test(text))errors.push(`direct IConfiguration index usage outside configuration boundary: ${file}`) } // Heuristic committed-secret guard for known provider/alerting key names. for(const file of walk('.').filter(x=>/\.(json|ya?ml|md|env|txt)$/i.test(x)&&!x.includes('/.git/')&&!x.includes('governance/baselines/'))){ if(file==='contracts/configuration/kbx.configuration.json'||file==='contracts/providers/kbx.providers.json'||file==='deploy/kbx/.env.kbx.example')continue const text=fs.readFileSync(file,'utf8') if(/(?:AUTH_KEY|crtfc_key|AppSecret|BotToken)\s*[=:]\s*["']?[A-Za-z0-9_\-]{16,}/i.test(text))errors.push(`possible committed secret material: ${file}`) } for(const f of ['backend/Shared/Configuration/KbxConfigurationStartupValidator.cs','backend/Shared/Configuration/KbxConfigurationFingerprint.cs','deploy/kbx/environment-matrix.generated.json','docs/configuration-reference.generated.md'])if(!fs.existsSync(f))errors.push(`missing v22 configuration artifact ${f}`) if(errors.length){console.error('configuration governance FAIL');for(const e of errors)console.error(`- ${e}`);process.exit(1)} console.log(`configuration governance PASS: settings=${src.settings.length}, environments=${src.environments.length}, provider credentials covered=${providerCredentialKeys.length}`)