feat: expand commercial ai drama platform

This commit is contained in:
xz
2026-08-31 21:09:48 +08:00
parent f06e583c9b
commit 8caf947b80
35 changed files with 10181 additions and 188 deletions
+60 -1
View File
@@ -25,6 +25,61 @@ function expectOk(result, label) {
return result.payload;
}
async function createApprovedLockAsset(headers, { projectId, shotId, kind, name, usageRole }) {
const upload = expectOk(await request("/api/assets/upload", {
method: "POST",
headers,
body: JSON.stringify({
data: Buffer.from(JSON.stringify({ projectId, kind, name, generatedBy: "smoke-project-isolation" })).toString("base64"),
fileName: `${kind}-lock.json`,
kind,
name,
mimeType: "application/json",
metadata: {
detail: `${name} 是隔离验收项目的原创 ${kind} 锁资产。`,
lock: "仅绑定当前项目首个镜头,用于验证连续性治理和项目隔离。"
},
provenance: {
sourceType: "original",
sourceName: projectId,
sourceRef: `smoke://${projectId}/${kind}`,
creator: "local-smoke"
},
tags: ["original", "continuity-lock", kind]
})
}), `upload ${kind} lock asset`);
const assetId = upload.asset.id;
const rights = expectOk(await request(`/api/assets/${encodeURIComponent(assetId)}/rights`, {
method: "POST",
headers,
body: JSON.stringify({
rightsStatus: "approved",
evidence: { reference: `local-evidence/${projectId}/${kind}`, notes: "隔离 smoke 本地原创资产证据" },
provenance: {
sourceType: "original",
sourceName: projectId,
sourceRef: `smoke://${projectId}/${kind}`,
creator: "local-smoke"
},
licenseScope: "commercial-test-local"
})
}), `approve ${kind} lock asset`);
assert.equal(rights.asset.currentVersion.rights_status, "approved", `${kind} asset rights must be approved`);
const locked = expectOk(await request(`/api/assets/${encodeURIComponent(assetId)}/lock`, {
method: "POST",
headers,
body: JSON.stringify({ lockStatus: "locked" })
}), `lock ${kind} asset`);
assert.equal(locked.asset.lock_status, "locked", `${kind} asset must be locked`);
const bound = expectOk(await request(`/api/assets/${encodeURIComponent(assetId)}/bindings`, {
method: "POST",
headers,
body: JSON.stringify({ shotId, usageRole })
}), `bind ${kind} asset`);
assert.ok(bound.asset.bindings.some((binding) => binding.shot_id === shotId), `${kind} asset must bind to isolated shot`);
return bound.asset;
}
let created = false;
let emptyOrganizationCreated = false;
let isolatedAssetPath = "";
@@ -81,11 +136,15 @@ try {
assert.equal(graph.graph.series.title, projectName, "production graph must use the new series");
assert.equal(graph.graph.shots.length, 1, "production graph must contain the new starter shot only");
assert.equal(graph.graph.characters.length, 0, "production graph must not include seeded characters");
const isolatedShotId = scoped.project.shots[0].id;
await createApprovedLockAsset(projectHeaders, { projectId, shotId: isolatedShotId, kind: "character", name: "隔离项目角色锁", usageRole: "character" });
await createApprovedLockAsset(projectHeaders, { projectId, shotId: isolatedShotId, kind: "location", name: "隔离项目场景锁", usageRole: "location" });
await createApprovedLockAsset(projectHeaders, { projectId, shotId: isolatedShotId, kind: "prop", name: "隔离项目道具锁", usageRole: "prop" });
const job = expectOk(await request("/api/jobs", {
method: "POST",
headers: projectHeaders,
body: JSON.stringify({ adapter: "owned-image", kind: "隔离验收关键帧", shotId: scoped.project.shots[0].id, output: `qa/${projectId}/frame.json` })
body: JSON.stringify({ adapter: "owned-image", kind: "隔离验收关键帧", shotId: isolatedShotId, output: `qa/${projectId}/frame.json` })
}), "create isolated job");
const afterJob = expectOk(await request("/api/project", { headers: projectHeaders }), "read isolated project after job");
assert.equal(afterJob.jobs.length, 1, "new project job list must contain only its own job");