diff --git a/scripts/control-plane.test.mjs b/scripts/control-plane.test.mjs index ab0a362..607b2bb 100644 --- a/scripts/control-plane.test.mjs +++ b/scripts/control-plane.test.mjs @@ -257,6 +257,12 @@ test('A4.4 确认卡:写操作等确认、确认后执行、事件齐全、重 assert.ok(await stream.waitFor((event) => event.type === 'approval.requested'), '要推确认卡给界面'); assert.equal(aps.stats.calls['aps.write'], undefined, '没确认前不能动业务系统'); + // 待确认中的执行也要能按 executionId 查到,且只有本人能查 + const pendingView = await fetch(`${server.url}/v1/executions/${receipt.executionId}`, { headers: headersFor(subject) }); + assert.equal((await pendingView.json()).status, 'pending-confirmation'); + const stolenView = await fetch(`${server.url}/v1/executions/${receipt.executionId}`, { headers: headersFor(otherSubject) }); + assert.equal(stolenView.status, 403, '执行记录只有本人能查'); + const decision = await fetch(`${server.url}/v1/approvals/${receipt.approvalId}`, { method: 'POST', headers: headersFor(subject), diff --git a/scripts/core-invoke.test.mjs b/scripts/core-invoke.test.mjs index 52cf322..70764ac 100644 --- a/scripts/core-invoke.test.mjs +++ b/scripts/core-invoke.test.mjs @@ -227,13 +227,13 @@ test('HTTP:注册 Provider 后能力清单非空,执行记录端点可查回 params: { text: '通过 HTTP 查回执' }, }); - const response = await fetch(`${server.url}/v1/executions/${receipt.executionId}`); + const response = await fetch(`${server.url}/v1/executions/${receipt.executionId}`, { headers: { 'x-aps-user': subject.userId, 'x-aps-tenant': subject.tenantId } }); assert.equal(response.status, 200, '执行记录端点应返回 200'); const body = await response.json(); assert.equal(body.executionId, receipt.executionId); assert.equal(body.status, 'succeeded'); - const missing = await fetch(`${server.url}/v1/executions/exec-none`); + const missing = await fetch(`${server.url}/v1/executions/exec-none`, { headers: { 'x-aps-user': subject.userId } }); assert.equal(missing.status, 404, '未知执行记录应 404'); const missingBody = await missing.json(); assert.equal(missingBody.error.code, 'not_found'); diff --git a/server/app/service.ts b/server/app/service.ts index b99a3fe..e5ab446 100644 --- a/server/app/service.ts +++ b/server/app/service.ts @@ -114,9 +114,17 @@ export class ApplicationService { return this.contextRepository?.get(sessionId); } - /** 执行记录查询:GET /v1/executions/{id} */ - execution(executionId: string) { - return this.core.executions.get(executionId)?.receipt; + /** 执行记录查询:GET /v1/executions/{id};待确认中的执行也能查到(回执 status = pending-confirmation) */ + execution(subject: Subject, executionId: string) { + const record = this.core.executions.get(executionId); + if (record) { + if (record.subjectUserId !== subject.userId) throw new AppError('forbidden', '这是别人的执行记录', { status: 403 }); + return record.receipt; + } + const action = this.core.approvals?.byExecution(executionId); + if (!action) return undefined; + if (action.ownerUserId !== subject.userId) throw new AppError('forbidden', '这是别人的执行记录', { status: 403 }); + return action.receipt; } /** 执行追溯(A4.5):按 executionId 追到人、参数摘要、审批与回执;只有本人能看 */ diff --git a/server/entry/http.ts b/server/entry/http.ts index 0cf8c7b..2221554 100644 --- a/server/entry/http.ts +++ b/server/entry/http.ts @@ -100,7 +100,7 @@ async function handleRequest(req: IncomingMessage, res: ServerResponse, service: return sendJson(res, 200, service.capabilities(subject, sessionFilter)); case 'GET /v1/executions/{executionId}': { const executionId = params.get('executionId') ?? ''; - const receipt = service.execution(executionId); + const receipt = service.execution(subject, executionId); if (!receipt) throw new AppError('not_found', `执行记录 不存在`, { status: 404 }); return sendJson(res, 200, receipt); }