Files
KArtSell.Aegis/docs/Design/kbx-foundation-v36/scripts/validate-field-governance.mjs
T

62 lines
4.0 KiB
JavaScript

import fs from 'node:fs'
import { spawnSync } from 'node:child_process'
const source = JSON.parse(fs.readFileSync('contracts/fields/kbx.fields.json', 'utf8'))
const errors = []
const seen = new Set()
const aliasOwners = new Map()
const types = new Set(['text','code','integer','decimal','quantity','money','date','datetime','boolean','lookup','status'])
if (!/^\d+\.\d+\.\d+$/.test(source.version ?? '')) errors.push('field dictionary version must be semver')
for (const field of source.fields ?? []) {
if (!/^[a-z][A-Za-z0-9]*$/.test(field.key ?? '')) errors.push(`invalid field key: ${field.key}`)
if (seen.has(field.key)) errors.push(`duplicate field key: ${field.key}`)
seen.add(field.key)
if (!types.has(field.dataType)) errors.push(`invalid data type: ${field.key} ${field.dataType}`)
if ((field.dataType === 'money' || field.dataType === 'quantity' || field.dataType === 'decimal') && (field.precision == null || field.scale == null))
errors.push(`${field.key}: numeric field requires precision and scale`)
if (field.dataType === 'lookup' && !field.lookupEntity) errors.push(`${field.key}: lookup field requires lookupEntity`)
if (field.readonly && field.importable) errors.push(`${field.key}: readonly field cannot be importable`)
if (field.sensitive && !field.masking) errors.push(`${field.key}: sensitive field requires masking metadata`)
if (field.deprecated && !field.replacementKey) errors.push(`${field.key}: deprecated field requires replacementKey`)
for (const alias of [field.key, ...(field.aliases ?? [])]) {
const normalized = alias.trim().toLocaleLowerCase('ko-KR')
if (!normalized) continue
const owner = aliasOwners.get(normalized)
// Collision is only dangerous when both fields can participate in generic import mapping.
if (owner && owner !== field.key) {
const other = source.fields.find(x => x.key === owner)
if (field.importable && other?.importable) errors.push(`import alias collision: "${alias}" => ${owner}, ${field.key}`)
} else aliasOwners.set(normalized, field.key)
}
}
const result = spawnSync(process.execPath, ['scripts/generate-field-contracts.mjs'], { stdio: 'inherit' })
if (result.status !== 0) process.exit(result.status ?? 1)
const manifest = JSON.parse(fs.readFileSync('generated/field-manifest.json', 'utf8'))
if (manifest.count !== source.fields.length) errors.push('generated field manifest count drift')
const ts = fs.readFileSync('packages/kbx-contracts/src/generated/fieldCatalog.ts', 'utf8')
const cs = fs.readFileSync('backend/Shared/Contracts/Generated/KbxFieldCatalog.g.cs', 'utf8')
for (const field of source.fields) {
if (!ts.includes(JSON.stringify(field.key))) errors.push(`TS field missing: ${field.key}`)
if (!cs.includes(`"${field.key}"`)) errors.push(`C# field missing: ${field.key}`)
}
const orderImportTs = fs.readFileSync('apps/web/src/modules/oms/orders/import/order-import.definition.ts', 'utf8')
const orderImportCs = fs.readFileSync('backend/Modules/OMS/Orders/Import/OrderImportDefinition.cs', 'utf8')
if (!orderImportTs.includes("kbxImportField('orderQty')")) errors.push('OMS order import must consume canonical orderQty metadata on frontend')
if (!orderImportCs.includes('I(KbxFieldKeys.OrderQty)')) errors.push('OMS order import must consume canonical orderQty metadata on backend')
if (/\bfield:\s*['"]quantity['"]/.test(fs.readFileSync('apps/web/src/modules/oms/orders/register/order-register.definition.ts','utf8'))) errors.push('OMS order register still uses deprecated quantity field key')
if (!fs.existsSync('backend/Shared/Contracts/KbxFieldOpenApiSchemaFilter.cs')) errors.push('OpenAPI field contract filter is missing')
if (errors.length) {
console.error('field governance FAIL')
for (const e of errors) console.error(`- ${e}`)
process.exit(1)
}
const adoption = spawnSync(process.execPath, ['scripts/report-field-adoption.mjs'], { stdio: 'inherit' })
if (adoption.status !== 0) process.exit(adoption.status ?? 1)
console.log(`field governance PASS: ${source.fields.length} canonical fields`)