import fs from 'node:fs'; import assert from 'node:assert/strict'; import { login, call } from './role-permissions-live.mjs'; // Read-only final audit. Never publishes or changes an existing assignment. const dir = 'reports/bridge-005-authoring-20260916'; const read = name => JSON.parse(fs.readFileSync(`${dir}/${name}.json`, 'utf8')); const fixture = read('pilot'); const before = read('original-assignments'); const session = await login('root'); async function api(endpoint) { const result = await call(`tran/v1/${endpoint}`, session.accessToken); assert.equal(result.status, 200, endpoint); return result.data; } const originals = []; for (const id of ['86', '87', '88', '89']) { const current = await api(`teaching/assignments/${id}`); delete current.serverNow; delete before[id].serverNow; assert.deepEqual(current, before[id], `Original assignment ${id} changed`); const project = await api(`content/projects/${current.contentProjectId}`); assert.equal(project.project.publishedVersionId, current.contentVersionId); originals.push({ id, code: current.code, versionId: current.contentVersionId, unchanged: true }); } const task = await api(`teaching/assignments/${fixture.taskId}`); assert.equal(task.status, 'DRAFT'); assert.equal(task.contentVersionId, fixture.trainingVersionId); const training = await api(`content/projects/${fixture.trainingId}`); const scene = await api(`content/projects/${fixture.sceneId}`); assert.equal(scene.project.publishedVersionId, fixture.sceneVersionId); const content = training.currentVersion.content; assert.equal(content.runtime?.scriptId, undefined); assert.notEqual(content.runtime?.definitionSource, 'SCRIPT'); assert.equal(content.steps.length, 5); assert.equal(content.steps.flatMap(s => s.action.mechanism.steps).length, 6); assert.equal(content.steps[2].action.mechanism.steps[0].deltaDegrees, 90); assert.equal(scene.currentVersion.content.objects.flatMap(o => o.modelPartTargets || []).length, 7); assert.ok(training.currentVersion.dependencies.some(d => d.relationType === 'TRAINING_SCENE' && d.targetVersionId === fixture.sceneVersionId)); const media = content.steps.map((s, index) => { const codes = [s.audio.narrationVideoAssetCode, s.audio.narrationAssetCode]; for (const code of codes) assert.ok(training.currentVersion.assets.some(a => a.code === code && a.status === 'READY'), code); return { step: index + 1, video: codes[0], audio: codes[1] }; }); const edited = await api(`content/projects/${fixture.trainingId}/versions/543`); assert.equal(edited.content.steps[2].action.mechanism.steps[0].deltaDegrees, 75); const evidence = ['editor-result', 'editor-result-90', 'runtime-edited-75', 'runtime-final-90-lan', 'original-tasks-regression', 'original-005-full-regression']; for (const name of evidence) assert.equal(read(name).pass, true, name); const audit = { pass: true, checkedAt: new Date().toISOString(), fixture, originals, steps: 5, operations: 6, partTargets: 7, editedAngle: 75, restoredAngle: 90, media, evidence }; fs.writeFileSync(`${dir}/audit.json`, JSON.stringify(audit, null, 2)); const links = evidence.map(name => `
验收时间:${audit.checkedAt}。独立验证工程,原四个已发布任务数据和版本完整对比一致。
实际编辑场景并发布后,训练重新绑定已发布场景。球阀从 90° 改为 75°,保存、刷新、发布后,完成全部六次鼠标操作及服务端预览校验;随后恢复 90° 并在局域网完整复测。五段视频和五段语音已作为训练资产绑定。
原 005 完整流程回归、原四任务初始场景回归均通过。前端构建与合同测试、后端单元测试和打包通过。当前验证仅覆盖教员预览,不代表新增学员成绩验收。



