feat: add self-operated analytics and enforce Lastleg module boundaries

This commit is contained in:
2026-10-12 20:27:35 +08:00
parent c06e788b0f
commit a1f1d93902
174 changed files with 13514 additions and 490 deletions
@@ -13,6 +13,7 @@ const sourceRoots = {
const internalPrefix = 'com.baiyue.lastleg.';
const sliceRules = [
['selfoperated-analytics', /^com\.baiyue\.lastleg\.selfoperated\.analytics(?:\.|$)/],
['fleet', /^com\.baiyue\.lastleg\.fleet(?:\.|$)/],
['security-projection', /(?:^|\.)security\.(?:controller|dto|service)(?:\.|$)/],
['dispatch-delivery', /(?:^|\.)(?:dispatch|delivery|outbound)(?:\.|$)/],
@@ -10,6 +10,27 @@ const analyzer = path.join(moduleRoot, 'scripts', 'analyze-lastleg-dependencies.
const contract = path.join(moduleRoot, 'src', 'test', 'resources', 'architecture', 'lastleg-slice-dependencies.json');
const vehiclePrefix = 'com.baiyue.lastleg.masterdata.vehicle';
test('selfoperated analytics is independent of customer outbound and inventory slices', () => {
const report = analyze();
const prefix = 'com.baiyue.lastleg.selfoperated.analytics.';
const classes = report.classes.filter(entry => entry.fqcn.startsWith(prefix));
assert.ok(classes.length > 0);
assert.ok(classes.some(entry => entry.packageName.endsWith('.inventory')));
assert.ok(classes.some(entry => entry.packageName.endsWith('.outbound')));
assert.ok(classes.every(entry => entry.slice === 'selfoperated-analytics'));
assert.deepEqual(report.sliceEdges.filter(edge => edge.source === 'selfoperated-analytics'
|| edge.target === 'selfoperated-analytics'), [{ source: 'selfoperated-analytics', target: 'shared-technical', count: 1 }]);
assert.deepEqual(report.classDependencies.filter(edge => edge.source.startsWith(prefix) && !edge.target.startsWith(prefix)), [{
source: prefix + 'request.SelfopEgressController', target: 'com.baiyue.lastleg.security.InternalServiceTokenGuard',
}]);
assert.deepEqual(report.packageSccs.filter(cycle => cycle.some(pkg => pkg.startsWith(prefix))), []);
const baseline = JSON.parse(readFileSync(contract, 'utf8'));
const source = classes.find(entry => entry.packageName.endsWith('.inventory')).fqcn;
const target = 'com.baiyue.lastleg.inventory.query.service.InventoryQueryService';
report.classDependencies.push({ source, target });
assert.ok(contractViolations(report, baseline).some(message => message.includes(`${source}->${target}`)));
});
test('fleet stays a business slice and its policies have no infrastructure dependencies', () => {
const report = analyze();
const classes = report.classes.filter(entry => entry.fqcn.startsWith('com.baiyue.lastleg.fleet.'));
@@ -30,6 +51,10 @@ test('fleet stays a business slice and its policies have no infrastructure depen
&& edge.target.startsWith('com.baiyue.lastleg.fleet.'));
assert.ok(dispatchEdges.length > 0);
assert.ok(dispatchEdges.every(edge => /\.(FleetDispatchGateway|FleetDispatchBindingGuard|FleetTripExecutionService|FleetExecutionModels|FleetCompletionModels)$/.test(edge.target)
|| edge.source === 'com.baiyue.lastleg.dispatch.fleet.FleetDispatchPlanningAdapter'
&& /\.(FleetBookingIncomingGateway|FleetDispatchPlanningGateway)$/.test(edge.target)
|| edge.source === 'com.baiyue.lastleg.dispatch.fleet.FleetDispatchPlanningMapper'
&& edge.target === 'com.baiyue.lastleg.fleet.trip.FleetDispatchPlanningGateway'
|| edge.source === 'com.baiyue.lastleg.dispatch.fleet.PdaFleetHandoverController'
&& edge.target === 'com.baiyue.lastleg.fleet.exception.FleetHandoverPdaQuery'),
'dispatch must use fleet ports and guards, not its persistence mappers');
@@ -42,6 +67,7 @@ function analyze(sourceSet = 'main') {
return JSON.parse(execFileSync(process.execPath, [analyzer, '--source-set', sourceSet, '--format', 'json'], {
cwd: moduleRoot,
encoding: 'utf8',
maxBuffer: 8 * 1024 * 1024,
}));
}
@@ -85,7 +111,10 @@ test('vehicle master data remains the reviewed low-coupling first-slice candidat
target: 'com.baiyue.lastleg.security.OverseasPermissions',
}]);
assert.ok(incoming.length > 0);
assert.ok(incoming.every(edge => edge.source.startsWith('com.baiyue.lastleg.fleet.') && edge.target.startsWith(vehiclePrefix + '.application.')));
assert.ok(incoming.every(edge => edge.source.startsWith('com.baiyue.lastleg.fleet.') && edge.target.startsWith(vehiclePrefix + '.application.')
|| edge.source === 'com.baiyue.lastleg.export.LastlegExportProviderService'
&& [vehiclePrefix + '.application.VehicleApplicationService', vehiclePrefix + '.application.model.VehicleModels'].includes(edge.target)),
'vehicle consumers use application contracts; the export composition has two exact read-only references');
assert.equal(report.packageSccs.some(component => component.some(name => name.startsWith(vehiclePrefix))), false);
assert.equal(classes.some(entry => entry.kind === 'cross-service-adapter'), false);
assert.equal(classes.some(entry => entry.imports.some(imported => /RestClient|\.client\./.test(imported))), false);
@@ -126,6 +155,22 @@ test('reviewed references do not allow another consumer into the same slice', ()
assert.ok(contractViolations(report, baseline).some(message => message.includes(`${source}->${target}`)));
});
test('export composition approvals do not grant persistence access or new consumers', () => {
const baseline = JSON.parse(readFileSync(contract, 'utf8'));
const source = 'com.baiyue.lastleg.export.LastlegExportProviderService';
const cases = [
{ source, target: vehiclePrefix + '.adapter.out.persistence.VehicleMapper' },
{ source, target: 'com.baiyue.lastleg.masterdata.exchange.mapper.MonthlyExchangeRateMapper' },
{ source: 'com.baiyue.lastleg.export.LastlegExportColumns', target: 'com.baiyue.lastleg.fleet.quote.FleetQuoteService' },
];
for (const edge of cases) {
const report = analyze();
report.classDependencies.push(edge);
assert.ok(contractViolations(report, baseline).some(message => message.includes(`${edge.source}->${edge.target}`)),
'each unreviewed consumer/target pair must still fail');
}
});
test('restoring a retired projection or importing its missing type still fails', () => {
const baseline = JSON.parse(readFileSync(contract, 'utf8'));
const report = analyze();