fix: register current synchronous service dependencies and guard callback boundaries

This commit is contained in:
2026-10-12 20:58:16 +08:00
parent d782b2cd24
commit d4dc4f875a
2 changed files with 84 additions and 16 deletions
@@ -15,6 +15,30 @@ const http = (path, source, target, contexts, access, idempotency, note) => ({
})
export const SYNCHRONOUS_CLIENTS = Object.freeze([
feign('lastleg-service/src/main/java/com/baiyue/lastleg/appointmentplanning/AppointmentForecastClient.java',
'lastleg-service', 'firstleg-service', ['REQUEST'], 'READ', 'SAFE_READ',
'预约影响刷新按柜次批量读取头程不可变船期事实;POST仅查询,刷新写入仅在Lastleg本域,不写Firstleg'),
feign('lastleg-service/src/main/java/com/baiyue/lastleg/appointmentplanning/AppointmentWarehouseClient.java',
'lastleg-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ',
'预约货源、整柜及外接候选读取权威启用仓库目录与主档,不创建仓库或预留资源'),
feign('lastleg-service/src/main/java/com/baiyue/lastleg/service/reference/ReturnCustomerReferenceClient.java',
'lastleg-service', 'iam-service', ['REQUEST'], 'READ', 'SAFE_READ',
'退货订单补全客户快照时按客户ID读取IAM有效客户;历史读取失败保留未知,不调用车队业务或客户资格写操作'),
feign('lastleg-service/src/main/java/com/baiyue/lastleg/transfer/gateway/TransferWarehouseCatalogClient.java',
'lastleg-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ',
'转仓实收按冻结FBA编码读取权威目录,拒绝缺失或歧义;资源预留仍由独立WarehouseResourceGateway处理'),
feign('order/src/main/java/com/baiyue/order/client/LastlegOrderProgressClient.java',
'order-service', 'lastleg-service', ['REQUEST'], 'READ', 'SAFE_READ',
'客户订单工作台按服务端客户ID批量读取尾程进度;POST无业务写入,失败不伪造待办或回退成功'),
http('lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/client/RestLingxingAnalyticsClient.java',
'lastleg-service', 'integration-service', ['BACKGROUND'], 'READ', 'NONE',
'持久任务后台逐页/逐单读取领星适配接口;每次调用先预留账号额度并冻结attempt上下文,真实尝试耗费预算,未知结果持久暂停,禁止传输层自动重试;人工命令仅入队,报表查询不调用此客户端'),
http('integration-service/src/main/java/com/baiyue/integration/provider/lingxing/LingxingHttpBudgetAuthority.java',
'integration-service', 'lastleg-service', ['BACKGROUND'], 'READ_WRITE', 'MIXED',
'后台采集出口向Lastleg申请一次性许可并确认真实尝试;authorize消费持久预算,不可重复获得许可,complete按attempt/operation/hash及结果摘要幂等,冲突拒绝;不递归执行采集,不自动重试或跟随重定向'),
http('integration-service/src/main/java/com/baiyue/integration/provider/lingxing/LingxingWmsClient.java',
'integration-service', 'external-lingxing-wms', ['EXTERNAL'], 'READ', 'NONE',
'受控WMS OMP查询不写供应商业务,但每次HTTP消耗账号额度;一许可仅允许一次尝试,OFF/无独占窗口拒绝,429或结果不确定停止新请求,无自动重试,不等同可无成本重放的SAFE_READ'),
feign('lastleg-service/src/main/java/com/baiyue/lastleg/fleet/reporting/TripFinanceClient.java',
'lastleg-service', 'finance-service', ['REQUEST'], 'READ', 'SAFE_READ',
'车次财务按不可变发车客户及派送来源批量只读查询,POST无业务写入;内部服务令牌保护,实收另按公网权限裁剪,不逐行查询或推算未分配收款'),
@@ -33,6 +33,7 @@ const expectedOnlineEdges = [
'iam-service->warehouse-service',
'lastleg-service->file-service',
'lastleg-service->finance-service',
'lastleg-service->firstleg-service',
'lastleg-service->iam-service',
'lastleg-service->print-service',
'lastleg-service->warehouse-overseas-service',
@@ -41,12 +42,15 @@ const expectedOnlineEdges = [
'order-service->finance-service',
'order-service->firstleg-service',
'order-service->iam-service',
'order-service->lastleg-service',
'order-service->warehouse-service',
'warehouse-overseas-service->print-service',
'warehouse-overseas-service->warehouse-service'
]
const expectedBackgroundEdges = [
'integration-service->lastleg-service',
'lastleg-service->integration-service',
'file-service->finance-service',
'file-service->firstleg-service',
'file-service->iam-service',
@@ -103,7 +107,7 @@ test('all Feign clients have reviewed context, access and idempotency metadata',
const reviewed = SYNCHRONOUS_CLIENTS.filter(client => client.transport === 'FEIGN')
.map(({ path, source, target }) => ({ path, source, target }))
.sort((left, right) => left.path.localeCompare(right.path))
assert.equal(reviewed.length, 72)
assert.equal(reviewed.length, 77)
assert.deepEqual(await discoverFeignEdges(), reviewed)
})
@@ -119,7 +123,7 @@ test('all raw HTTP transports have reviewed context, access and idempotency meta
}
const reviewed = SYNCHRONOUS_CLIENTS.filter(client => client.transport === 'HTTP')
.map(client => client.path).sort()
assert.equal(reviewed.length, 25)
assert.equal(reviewed.length, 28)
assert.deepEqual(discovered.sort(), reviewed)
})
@@ -194,14 +198,14 @@ test('REQUEST, BACKGROUND and full graphs remain separate executable baselines',
])
assert.deepEqual(stronglyConnectedComponents(graphEdges('REQUEST')), [
['auth-service', 'iam-service'],
['finance-service', 'firstleg-service', 'order-service']
['finance-service', 'firstleg-service', 'lastleg-service', 'order-service']
])
assert.deepEqual(stronglyConnectedComponents(graphEdges('BACKGROUND')), [[
'finance-service', 'firstleg-service', 'lastleg-service', 'order-service'
'finance-service', 'firstleg-service', 'integration-service', 'lastleg-service', 'order-service'
]])
assert.deepEqual(stronglyConnectedComponents(graphEdges('ALL')), [[
'auth-service', 'file-service', 'finance-service',
'firstleg-service', 'iam-service', 'lastleg-service', 'order-service'
'firstleg-service', 'iam-service', 'integration-service', 'lastleg-service', 'order-service'
]])
})
@@ -222,7 +226,7 @@ test('unified authority checks remain infrastructure edges and direct events rem
assert.ok(!SYNCHRONOUS_CLIENTS.some(client => ['event-bridge-service','gateway-overseas'].includes(client.source) || ['event-bridge-service','gateway-overseas'].includes(client.target)))
})
test('Order, Firstleg and Finance form the reviewed REQUEST SCC', () => {
test('Order, Firstleg, Finance and Lastleg form the reviewed REQUEST SCC', () => {
const requestClients = SYNCHRONOUS_CLIENTS.filter(client => client.contexts.includes('REQUEST'))
const orderToFirstleg = requestClients.filter(client =>
client.source === 'order-service' && client.target === 'firstleg-service')
@@ -260,17 +264,17 @@ test('known user entries expose every longest simple REQUEST path over the two-h
'iam-service->auth-service',
'iam-service->warehouse-service'
] },
'order-service': { hops: 5, paths: [
'order-service->finance-service->firstleg-service->file-service->iam-service->auth-service',
'order-service->finance-service->firstleg-service->file-service->iam-service->warehouse-service'
'order-service': { hops: 6, paths: [
'order-service->lastleg-service->finance-service->firstleg-service->file-service->iam-service->auth-service',
'order-service->lastleg-service->finance-service->firstleg-service->file-service->iam-service->warehouse-service'
] },
'firstleg-service': { hops: 5, paths: [
'firstleg-service->order-service->finance-service->file-service->iam-service->auth-service',
'firstleg-service->order-service->finance-service->file-service->iam-service->warehouse-service'
'firstleg-service': { hops: 6, paths: [
'firstleg-service->order-service->lastleg-service->finance-service->file-service->iam-service->auth-service',
'firstleg-service->order-service->lastleg-service->finance-service->file-service->iam-service->warehouse-service'
] },
'finance-service': { hops: 5, paths: [
'finance-service->firstleg-service->order-service->file-service->iam-service->auth-service',
'finance-service->firstleg-service->order-service->file-service->iam-service->warehouse-service'
'finance-service': { hops: 6, paths: [
'finance-service->firstleg-service->order-service->lastleg-service->file-service->iam-service->auth-service',
'finance-service->firstleg-service->order-service->lastleg-service->file-service->iam-service->warehouse-service'
] },
'file-service': { hops: 2, paths: [
'file-service->iam-service->auth-service',
@@ -278,7 +282,9 @@ test('known user entries expose every longest simple REQUEST path over the two-h
] },
'lastleg-service': { hops: 6, paths: [
'lastleg-service->finance-service->firstleg-service->order-service->file-service->iam-service->auth-service',
'lastleg-service->finance-service->firstleg-service->order-service->file-service->iam-service->warehouse-service'
'lastleg-service->finance-service->firstleg-service->order-service->file-service->iam-service->warehouse-service',
'lastleg-service->firstleg-service->order-service->finance-service->file-service->iam-service->auth-service',
'lastleg-service->firstleg-service->order-service->finance-service->file-service->iam-service->warehouse-service'
] },
'warehouse-service': { hops: 0, paths: ['warehouse-service'] },
'warehouse-overseas-service': { hops: 1, paths: [
@@ -292,6 +298,44 @@ test('known user entries expose every longest simple REQUEST path over the two-h
}
})
test('self-operated collection and quota callbacks stay outside the REQUEST graph', async () => {
const find = suffix => SYNCHRONOUS_CLIENTS.find(client => client.path.endsWith(suffix))
const collection = find('/selfoperated/analytics/client/RestLingxingAnalyticsClient.java')
const authority = find('/provider/lingxing/LingxingHttpBudgetAuthority.java')
const supplier = find('/provider/lingxing/LingxingWmsClient.java')
assert.deepEqual([collection.contexts, collection.access, collection.idempotency], [['BACKGROUND'], 'READ', 'NONE'])
assert.deepEqual([authority.contexts, authority.access, authority.idempotency], [['BACKGROUND'], 'READ_WRITE', 'MIXED'])
assert.deepEqual([supplier.contexts, supplier.access, supplier.idempotency], [['EXTERNAL'], 'READ', 'NONE'])
assert.equal(graphEdges('REQUEST').includes('lastleg-service->integration-service'), false)
assert.equal(graphEdges('REQUEST').includes('integration-service->lastleg-service'), false)
// The background service graph is bidirectional; the permit callback must not re-enter collection.
const egress = await readFile(path.join(root,
'lastleg-service/src/main/java/com/baiyue/lastleg/selfoperated/analytics/request/SelfopEgressService.java'), 'utf8')
assert.doesNotMatch(egress, /RestLingxingAnalyticsClient|LingxingAnalyticsClient|SelfopExecutionService/)
const callback = await readFile(path.join(root, authority.path), 'utf8')
assert.match(callback, /followRedirects\(HttpClient\.Redirect\.NEVER\)/)
assert.match(callback, /timeout\(Duration\.ofSeconds\(5\)\)/)
assert.doesNotMatch(callback, /RetryTemplate|Retryer|@Retryable|\.retry\(/)
})
test('new domain reference clients are exact readonly internal dependencies', async () => {
const reviewed = [
['lastleg-service/src/main/java/com/baiyue/lastleg/appointmentplanning/AppointmentForecastClient.java', 'firstleg-service', '/internal/firstleg/appointment-forecasts/batch'],
['lastleg-service/src/main/java/com/baiyue/lastleg/appointmentplanning/AppointmentWarehouseClient.java', 'warehouse-service', '/internal/warehouses/order-catalog'],
['lastleg-service/src/main/java/com/baiyue/lastleg/service/reference/ReturnCustomerReferenceClient.java', 'iam-service', '/internal/iam/finance-customers'],
['lastleg-service/src/main/java/com/baiyue/lastleg/transfer/gateway/TransferWarehouseCatalogClient.java', 'warehouse-service', '/internal/warehouses/order-catalog'],
['order/src/main/java/com/baiyue/order/client/LastlegOrderProgressClient.java', 'lastleg-service', '/internal/lastleg/v1/customer-order-progress']
]
for (const [file, target, endpoint] of reviewed) {
const client = SYNCHRONOUS_CLIENTS.find(row => row.path === file)
assert.deepEqual([client.target, client.contexts, client.access, client.idempotency], [target, ['REQUEST'], 'READ', 'SAFE_READ'])
const source = await readFile(path.join(root, file), 'utf8')
assert.ok(source.includes(endpoint))
assert.ok(source.includes('X-Internal-Service-Token'))
assert.doesNotMatch(source, /@(?:PutMapping|DeleteMapping|PatchMapping)/)
}
})
test('delivery fee coverage is a single readonly Finance hop and cannot write financial state', async () => {
const client = await readFile(path.join(root, 'lastleg-service/src/main/java/com/baiyue/lastleg/client/FinanceFeeCoverageClient.java'), 'utf8')
const service = await readFile(path.join(root, 'finance/src/main/java/com/baiyue/finance/service/DeliveryFeeCoverageQueryService.java'), 'utf8')