Files
platforms/frontend/platform_admin/scripts/audit-check.mjs

1246 lines
27 KiB
JavaScript
Raw Normal View History

2026-07-27 00:18:42 +08:00
import fs from 'node:fs';
import path from 'node:path';
import vm from 'node:vm';
import ts from 'typescript';
const contracts = [
{
"domain": "gas",
"name": "gas_basic",
"resource": "/gas/gas_basic",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "code",
"required": true
},
{
"key": "name",
"required": true
},
{
"key": "credit_code",
"required": false
},
{
"key": "principal",
"required": false
},
{
"key": "address",
"required": false
},
{
"key": "longitude",
"required": false
},
{
"key": "latitude",
"required": false
}
]
},
{
"domain": "gas",
"name": "gas_account",
"resource": "/gas/gas_account",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "username",
"required": true
},
{
"key": "password",
"required": true
},
{
"key": "display_name",
"required": false
},
{
"key": "role_code",
"required": false
},
{
"key": "gas_basic_identity",
"required": true
}
]
},
{
"domain": "delivery",
"name": "delivery_basic",
"resource": "/delivery/delivery_basic",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "delivery_code",
"required": true
},
{
"key": "name",
"required": true
},
{
"key": "gas_basic_identity",
"required": false
},
{
"key": "principal",
"required": false
},
{
"key": "address",
"required": false
}
]
},
{
"domain": "delivery",
"name": "delivery_account",
"resource": "/delivery/delivery_account",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "username",
"required": true
},
{
"key": "password",
"required": true
},
{
"key": "display_name",
"required": false
},
{
"key": "role_code",
"required": false
},
{
"key": "delivery_basic_identity",
"required": true
}
]
},
{
"domain": "delivery",
"name": "delivery_task",
"resource": "/delivery/delivery_task",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "ec_order_identity",
"required": true
},
{
"key": "delivery_basic_identity",
"required": true
},
{
"key": "staff_account_identity",
"required": false
}
]
},
{
"domain": "delivery",
"name": "delivery_track",
"resource": "/delivery/delivery_track",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "delivery_task_identity",
"required": true
},
{
"key": "started_at",
"required": false
},
{
"key": "completed_at",
"required": false
}
]
},
{
"domain": "delivery",
"name": "delivery_track_point",
"resource": "/delivery/delivery_track_point",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "delivery_track_identity",
"required": true
},
{
"key": "point_type",
"required": true
},
{
"key": "occurred_at",
"required": true
},
{
"key": "longitude",
"required": true
},
{
"key": "latitude",
"required": true
}
]
},
{
"domain": "staff",
"name": "staff_account",
"resource": "/staff/account",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "username",
"required": true
},
{
"key": "password",
"required": true
},
{
"key": "name",
"required": true
},
{
"key": "phone",
"required": false
},
{
"key": "avatar",
"required": false
},
{
"key": "role_code",
"required": false
},
{
"key": "gas_basic_identity",
"required": false
},
{
"key": "delivery_basic_identity",
"required": false
},
{
"key": "work_status",
"required": false
}
]
},
{
"domain": "staff",
"name": "staff_credential",
"resource": "/staff/credential",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "staff_account_identity",
"required": true
},
{
"key": "credential_type",
"required": true
},
{
"key": "credential_no",
"required": false
},
{
"key": "expired_at",
"required": false
}
]
},
{
"domain": "user",
"name": "user_account",
"resource": "/user/account",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "username",
"required": true
},
{
"key": "password",
"required": true
},
{
"key": "name",
"required": true
},
{
"key": "phone",
"required": false
},
{
"key": "avatar",
"required": false
},
{
"key": "real_name",
"required": false
}
]
},
{
"domain": "user",
"name": "user_address",
"resource": "/user/address",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "user_account_identity",
"required": true
},
{
"key": "address",
"required": true
},
{
"key": "longitude",
"required": false
},
{
"key": "latitude",
"required": false
},
{
"key": "is_default",
"required": false
}
]
},
{
"domain": "user",
"name": "user_service_relation",
"resource": "/user/service_relation",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "user_account_identity",
"required": true
},
{
"key": "gas_basic_identity",
"required": false
},
{
"key": "delivery_basic_identity",
"required": false
},
{
"key": "staff_account_identity",
"required": false
}
]
},
{
"domain": "device",
"name": "dev_smart_cylinder_valve",
"resource": "/device/dev_smart_cylinder_valve",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "device_no",
"required": true
},
{
"key": "model",
"required": false
},
{
"key": "online_status",
"required": false
},
{
"key": "owner_identity",
"required": false
}
]
},
{
"domain": "device",
"name": "dev_device_binding",
"resource": "/device/dev_device_binding",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "smart_cylinder_valve_identity",
"required": true
},
{
"key": "user_account_identity",
"required": true
},
{
"key": "effective_at",
"required": false
},
{
"key": "expired_at",
"required": false
}
]
},
{
"domain": "device",
"name": "dev_telemetry",
"resource": "/device/dev_telemetry",
"mode": "readonly",
"pageKind": "list",
"fields": [
{
"key": "smart_cylinder_valve_identity",
"required": false
},
{
"key": "recorded_at",
"required": false
},
{
"key": "payload",
"required": false
}
]
},
{
"domain": "safety",
"name": "saf_rule",
"resource": "/safety/saf_rule",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "rule_code",
"required": true
},
{
"key": "version_no",
"required": false
},
{
"key": "threshold",
"required": false
},
{
"key": "action",
"required": false
},
{
"key": "gray_scope",
"required": false
}
]
},
{
"domain": "safety",
"name": "saf_event",
"resource": "/safety/saf_event",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "event_code",
"required": true
},
{
"key": "level",
"required": true
},
{
"key": "title",
"required": true
},
{
"key": "smart_cylinder_valve_identity",
"required": true
},
{
"key": "sla_at",
"required": false
}
]
},
{
"domain": "safety",
"name": "saf_inspection",
"resource": "/safety/saf_inspection",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "user_account_identity",
"required": true
},
{
"key": "staff_account_identity",
"required": true
},
{
"key": "result",
"required": true
},
{
"key": "evidence_uri",
"required": false
}
]
},
{
"domain": "safety",
"name": "saf_event_disposal",
"resource": "/safety/saf_event/:identity/disposals",
"mode": "append_only",
"pageKind": "list",
"fields": [
{
"key": "action",
"required": true
},
{
"key": "reason",
"required": true
}
]
},
{
"domain": "ec",
"name": "ec_category",
"resource": "/ec/ec_category",
"mode": "writable",
"pageKind": "tree",
"fields": [
{
"key": "parent_identity",
"required": false
},
{
"key": "name",
"required": true
},
{
"key": "sort_no",
"required": false
}
]
},
{
"domain": "ec",
"name": "ec_product",
"resource": "/ec/ec_product",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "ec_category_identity",
"required": true
},
{
"key": "product_code",
"required": true
},
{
"key": "name",
"required": true
},
{
"key": "price_amount",
"required": true
},
{
"key": "stock_quantity",
"required": false
}
]
},
{
"domain": "ec",
"name": "ec_product_attribute",
"resource": "/ec/ec_product_attribute",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "ec_product_identity",
"required": true
},
{
"key": "name",
"required": true
},
{
"key": "value",
"required": true
},
{
"key": "sort_no",
"required": false
}
]
},
{
"domain": "ec",
"name": "ec_product_image",
"resource": "/ec/ec_product_image",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "ec_product_identity",
"required": true
},
{
"key": "image_uri",
"required": true
},
{
"key": "sort_no",
"required": false
},
{
"key": "is_cover",
"required": false
}
]
},
{
"domain": "ec",
"name": "ec_cart",
"resource": "/ec/ec_cart",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "user_account_identity",
"required": true
},
{
"key": "ec_product_identity",
"required": true
},
{
"key": "quantity",
"required": true
},
{
"key": "selected",
"required": false
}
]
},
{
"domain": "ec",
"name": "ec_order",
"resource": "/ec/ec_order",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "user_account_identity",
"required": true
},
{
"key": "gas_basic_identity",
"required": false
},
{
"key": "delivery_basic_identity",
"required": false
},
{
"key": "order_no",
"required": true
},
{
"key": "total_amount",
"required": true
}
]
},
{
"domain": "ec",
"name": "ec_order_item",
"resource": "/ec/ec_order_item",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "ec_order_identity",
"required": true
},
{
"key": "ec_product_identity",
"required": true
},
{
"key": "product_snapshot",
"required": true
},
{
"key": "quantity",
"required": true
},
{
"key": "sale_amount",
"required": true
}
]
},
{
"domain": "ec",
"name": "ec_review",
"resource": "/ec/ec_review",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "ec_order_identity",
"required": true
},
{
"key": "ec_product_identity",
"required": true
},
{
"key": "user_account_identity",
"required": true
},
{
"key": "score",
"required": true
},
{
"key": "content",
"required": true
}
]
},
{
"domain": "finance",
"name": "fin_payment",
"resource": "/finance/fin_payment",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "ec_order_identity",
"required": true
},
{
"key": "channel",
"required": true
},
{
"key": "amount",
"required": true
},
{
"key": "paid_at",
"required": false
}
]
},
{
"domain": "finance",
"name": "fin_settlement",
"resource": "/finance/fin_settlement",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "settlement_no",
"required": true
},
{
"key": "subject_type",
"required": true
},
{
"key": "subject_identity",
"required": true
},
{
"key": "period_start",
"required": true
},
{
"key": "period_end",
"required": true
}
]
},
{
"domain": "finance",
"name": "fin_reconciliation",
"resource": "/finance/fin_reconciliation",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "channel",
"required": true
},
{
"key": "bill_date",
"required": true
},
{
"key": "difference_amount",
"required": true
}
]
},
{
"domain": "content",
"name": "cnt_content",
"resource": "/content/cnt_content",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "content_type",
"required": true
},
{
"key": "title",
"required": true
},
{
"key": "body",
"required": true
},
{
"key": "version_no",
"required": false
},
{
"key": "publish_status",
"required": false
}
]
},
{
"domain": "notification",
"name": "ntf_template",
"resource": "/notification/ntf_template",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "template_code",
"required": true
},
{
"key": "channel",
"required": true
},
{
"key": "content",
"required": true
}
]
},
{
"domain": "customer_service",
"name": "cs_ticket",
"resource": "/customer_service/cs_ticket",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "user_account_identity",
"required": true
},
{
"key": "ticket_no",
"required": true
},
{
"key": "category",
"required": true
},
{
"key": "priority",
"required": true
}
]
},
{
"domain": "platform",
"name": "platfrom_account",
"resource": "/platform/platfrom_account",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "username",
"required": true
},
{
"key": "password",
"required": true
},
{
"key": "display_name",
"required": false
},
{
"key": "avatar",
"required": false
},
{
"key": "platform_role_code",
"required": false
},
{
"key": "phone",
"required": false
}
]
},
{
"domain": "platform",
"name": "platform_role",
"resource": "/platform/platform_role",
"mode": "writable",
"pageKind": "list",
"fields": [
{
"key": "role_code",
"required": true
},
{
"key": "name",
"required": true
},
{
"key": "data_scope",
"required": true
}
]
},
{
"domain": "platform",
"name": "platform_menu",
"resource": "/platform/platform_menu",
"mode": "writable",
"pageKind": "tree",
"fields": [
{
"key": "parent_identity",
"required": false
},
{
"key": "menu_code",
"required": true
},
{
"key": "name",
"required": true
},
{
"key": "icon",
"required": false
},
{
"key": "path",
"required": false
},
{
"key": "sort_no",
"required": false
}
]
},
{
"domain": "wallet",
"name": "wallet",
"resource": "/wallet/wallet",
"mode": "readonly",
"pageKind": "list",
"fields": [
{
"key": "owner_identity",
"required": false
},
{
"key": "balance_amount",
"required": false
},
{
"key": "status",
"required": false
}
]
},
{
"domain": "wallet",
"name": "wallet_ledger",
"resource": "/wallet/wallet_ledger",
"mode": "readonly",
"pageKind": "list",
"fields": [
{
"key": "wallet_identity",
"required": false
},
{
"key": "change_amount",
"required": false
},
{
"key": "balance_after",
"required": false
}
]
},
{
"domain": "wallet",
"name": "wallet_recharge",
"resource": "/wallet/wallet_recharge",
"mode": "readonly",
"pageKind": "list",
"fields": [
{
"key": "wallet_identity",
"required": false
},
{
"key": "amount",
"required": false
},
{
"key": "status",
"required": false
}
]
},
{
"domain": "wallet",
"name": "wallet_withdrawal",
"resource": "/wallet/wallet_withdrawal",
"mode": "readonly",
"pageKind": "list",
"fields": [
{
"key": "wallet_identity",
"required": false
},
{
"key": "amount",
"required": false
},
{
"key": "status",
"required": false
}
]
},
{
"domain": "report",
"name": "report",
"resource": "/report/report",
"mode": "readonly",
"pageKind": "list",
"fields": [
{
"key": "report_code",
"required": false
},
{
"key": "name",
"required": false
},
{
"key": "status",
"required": false
}
]
},
{
"domain": "report",
"name": "report_item",
"resource": "/report/report_item",
"mode": "readonly",
"pageKind": "list",
"fields": [
{
"key": "report_identity",
"required": false
},
{
"key": "metric_code",
"required": false
},
{
"key": "value",
"required": false
}
]
},
{
"domain": "report",
"name": "report_metric_snapshot",
"resource": "/report/report_metric_snapshot",
"mode": "readonly",
"pageKind": "list",
"fields": [
{
"key": "report_identity",
"required": false
},
{
"key": "metric_code",
"required": false
},
{
"key": "value",
"required": false
},
{
"key": "captured_at",
"required": false
}
]
},
{
"domain": "audit",
"name": "aud_operation_log",
"resource": "/audit/aud_operation_log",
"mode": "readonly",
"pageKind": "list",
"fields": [
{
"key": "operator_identity",
"required": false
},
{
"key": "action",
"required": false
},
{
"key": "object_identity",
"required": false
},
{
"key": "created_at",
"required": false
}
]
},
{
"domain": "audit",
"name": "aud_export_log",
"resource": "/audit/aud_export_log",
"mode": "readonly",
"pageKind": "list",
"fields": [
{
"key": "operator_identity",
"required": false
},
{
"key": "resource_type",
"required": false
},
{
"key": "created_at",
"required": false
}
]
},
{
"domain": "audit",
"name": "aud_approval",
"resource": "/audit/aud_approval",
"mode": "readonly",
"pageKind": "list",
"fields": [
{
"key": "operator_identity",
"required": false
},
{
"key": "status",
"required": false
},
{
"key": "handled_at",
"required": false
}
]
}
];
const source = fs.readFileSync('src/api/resources.ts', 'utf8');
const backendContractSource = fs.readFileSync('../../backend/api/internal/logic/platform/resource.go', 'utf8');
const backendRouterSource = fs.readFileSync('../../backend/api/internal/routers/platform.go', 'utf8');
const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } }).outputText;
const resourceModule = { exports: {} };
vm.runInNewContext(compiled, { module: resourceModule, exports: resourceModule.exports });
const resources = resourceModule.exports.resources;
const files = (dir) => fs.existsSync(dir) ? fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => entry.isDirectory() ? files(path.join(dir, entry.name)) : [path.join(dir, entry.name)]) : [];
const sameFields = (actual, expected) => Array.isArray(actual) && actual.length === expected.length && actual.every((field, index) => field.key === expected[index].key && Boolean(field.required) === Boolean(expected[index].required));
function checkContracts() {
const failures = [];
if (!Array.isArray(resources) || resources.length !== contracts.length) failures.push(`catalogue: expected ${contracts.length} resources`);
for (const contract of contracts) {
const definition = resources.find((resource) => resource.name === contract.name);
const label = `${contract.domain}/${contract.name}`;
if (!definition || definition.resource !== contract.resource || definition.mode !== contract.mode || definition.pageKind !== contract.pageKind || !sameFields(definition.fields, contract.fields)) failures.push(`${label}: mismatched contract or allowlist`);
if (!backendContractSource.includes(`Domain: "${contract.domain}"`) || !backendContractSource.includes(`Name: "${contract.name}"`)) failures.push(`${label}: absent from backend ExpectedResources`);
if (contract.name !== 'saf_event_disposal' && !backendRouterSource.includes(contract.resource)) failures.push(`${label}: absent from backend router`);
const requiredIdentity = contract.fields.filter((field) => field.required && field.key.endsWith('_identity')).map((field) => field.key);
if (definition && JSON.stringify(definition.requiredIdentities ?? []) !== JSON.stringify(requiredIdentity)) failures.push(`${label}: required identity fields do not match allowlist`);
if (contract.name === 'saf_event_disposal') continue;
const route = path.join('src/router/routes/modules', contract.domain + '.ts');
if (!fs.existsSync(route) || !fs.readFileSync(route, 'utf8').includes(contract.name)) failures.push(`${label}: missing route`);
const page = path.join('src/views', contract.domain, contract.name, contract.pageKind === 'tree' ? 'TreePage.vue' : 'ListPage.vue');
if (!fs.existsSync(page)) failures.push(`${label}: missing view`);
if (contract.mode === 'readonly' && (!fs.existsSync(page) || !fs.readFileSync(page, 'utf8').includes('ReadOnlyListPage'))) failures.push(`${label}: readonly page exposes write surface`);
2026-07-27 00:18:42 +08:00
}
const names = resources.map((resource) => resource.name).sort();
const expectedNames = contracts.map((contract) => contract.name).sort();
if (JSON.stringify(names) !== JSON.stringify(expectedNames)) failures.push('catalogue: resource names do not equal ExpectedResources');
if (resources.some((resource) => resource.fields.some((field) => field.key === 'id' || field.key.endsWith('_id')))) failures.push('catalogue: internal identifier leaked through fields');
const readonlyPage = fs.readFileSync('src/views/shared/ReadOnlyListPage.vue', 'utf8');
if (/(CrudListPage|resourceApi\\.(create|update|archive|updateStatus))/.test(readonlyPage)) failures.push('readonly shared page: contains write surface');
const treePage = fs.readFileSync('src/views/shared/TreePage.vue', 'utf8');
if (!treePage.includes('<a-tree') || !treePage.includes('parent_identity') || /\bparent_id\b|\bid\b/.test(treePage) || treePage.includes('CrudListPage')) failures.push('tree shared page: must be identity-first');
for (const name of ['ec_category', 'platform_menu']) { const view = path.join('src/views', name === 'ec_category' ? 'ec' : 'platform', name, 'TreePage.vue'); if (!fs.existsSync(view)) failures.push(`${name}: must use TreePage`); }
const safetyEvent = resources.find((resource) => resource.name === 'saf_event');
if (!safetyEvent?.detailActions?.some((item) => item.name === 'saf_event_disposal' && item.resource === '/safety/saf_event/:identity/disposals')) failures.push('safety event: missing disposal detail action');
if (files('src/router/routes/modules').some((file) => fs.readFileSync(file, 'utf8').includes('saf_event_disposal')) || fs.existsSync('src/views/safety/saf_event_disposal')) failures.push('saf_event_disposal: must not have independent menu or page');
const crudSource = fs.readFileSync('src/views/shared/CrudListPage.vue', 'utf8');
if (!crudSource.includes("key !== 'id'") || !crudSource.includes("key.endsWith('_id')")) failures.push('detail view: does not remove internal identifiers');
const leakedUiFiles = files('src/api').concat(files('src/views')).filter((file) => /\.(ts|vue)$/.test(file) && !file.endsWith('CrudListPage.vue') && !file.endsWith('ReadOnlyListPage.vue') && !file.endsWith('TreePage.vue')).filter((file) => /\b[A-Za-z][A-Za-z0-9]*_id\b|\bid\b/.test(fs.readFileSync(file, 'utf8')));
if (leakedUiFiles.length) failures.push(`identity leak: ${leakedUiFiles.join(', ')}`);
if (/data-index="(?:id|[^" ]+_id)"|\{\{\s*[^}]*\b(?:id|[A-Za-z][A-Za-z0-9]*_id)\b/.test(treePage)) failures.push('tree page: displays internal identifier');
return failures;
2026-07-27 00:18:42 +08:00
}
const failures = checkContracts();
for (const failure of failures) console.log(failure);
if (failures.length) process.exitCode = 1;