feat: API-Runtime 版本兼容协议 (API-AI-072)
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 46s

- pollJobs: 记录/更新 RuntimeInstance (capabilities + heartbeat)
- submitResult: 校验 schemaVersion 匹配 job.outputSchemaVersion
- heartbeat: 首次调用设置 startedAt
- 错误码: RESULT_SCHEMA_UNSUPPORTED + RUNTIME_VERSION_INCOMPATIBLE

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-06-11 21:42:02 +08:00
parent 00ac32a103
commit 012e26b950

View File

@ -35,6 +35,24 @@ export class RuntimeInternalService {
},
});
// Register / update RuntimeInstance with capabilities
if (supSnapshot.length > 0 || supOutput.length > 0) {
await this.prisma.runtimeInstance.upsert({
where: { runtimeInstanceId },
create: {
runtimeInstanceId,
status: 'active',
lastHeartbeatAt: new Date(),
capabilities: capabilities as any,
},
update: {
status: 'active',
lastHeartbeatAt: new Date(),
capabilities: capabilities as any,
},
});
}
// Post-filter by snapshotVersion if needed (requires snapshot join)
if (supSnapshot.length > 0) {
const snapshotIds = [...new Set(jobs.map(j => j.snapshotId).filter(Boolean))];
@ -92,14 +110,14 @@ export class RuntimeInternalService {
const now = new Date();
const lockUntil = new Date(now.getTime() + 60_000);
// First heartbeat: locked → running; subsequent heartbeats: extend lockUntil
// First heartbeat: locked → running + set startedAt; subsequent: extend lockUntil
const result = await this.prisma.aiRuntimeJob.updateMany({
where: {
id: jobId,
lockedBy: runtimeInstanceId,
status: { in: ['locked', 'running'] },
},
data: { lockUntil, status: 'running' },
data: { lockUntil, status: 'running', startedAt: new Date() },
});
if (result.count === 0) {
@ -176,6 +194,14 @@ export class RuntimeInternalService {
throw new ConflictException({ errorCode: 'JOB_NOT_ACTIVE', message: `Job is ${job.status}, cannot accept result` });
}
// Validate schema version compatibility
if (job.outputSchemaVersion && dto.schemaVersion !== job.outputSchemaVersion) {
throw new BadRequestException({
errorCode: 'RESULT_SCHEMA_UNSUPPORTED',
message: `Result schemaVersion ${dto.schemaVersion} does not match job outputSchemaVersion ${job.outputSchemaVersion}`,
});
}
const resultIdempotencyKey = `${jobId}:${dto.attemptNo}:${dto.outputHash ?? ''}`;
// Check duplicate