import assert from "node:assert/strict"; import test from "node:test"; import { assembleClaudeAuthoredPromptPlan, contractSha256, validateClaudeAuthoredSetPlan, validateProductSettingBoardSpec, V3_WORKFLOW } from "./reference-first-v3-contracts.mjs"; import { boardSpec, setPlan } from "./reference-first-v3-fixtures.mjs"; test("product setting board contract keeps reusable structure and requires Claude-authored dynamic content", () => { const spec = validateProductSettingBoardSpec(boardSpec()); assert.equal(spec.authored_by, "claude"); assert.equal(spec.multi_views.length, 4); assert.equal(spec.detail_breakdowns.length, 4); assert.equal(spec.scenes.length, 4); assert.throws(() => validateProductSettingBoardSpec({ ...boardSpec(), scenes: [] }), /SETTING_BOARD_FOUR_SCENES_REQUIRED/); }); test("Claude-authored set plan owns all creative prompt modules and Node only assembles them", () => { const source = setPlan(); const validated = validateClaudeAuthoredSetPlan(source, { runId: source.run_id, referenceObservationSha256: "refhash" }); const promptPlan = assembleClaudeAuthoredPromptPlan({ plan: validated, generatedReferences: ["three", "board"], aspectRatio: "1:1" }); assert.equal(promptPlan.workflow, V3_WORKFLOW); assert.equal(promptPlan.images.length, 7); assert.ok(promptPlan.images.every((image) => image.reference_images.join(",") === "three,board")); assert.ok(promptPlan.images[4].generation_prompt.includes(source.pages[4].prompt_modules.scene_relation)); assert.ok(!promptPlan.images[4].generation_prompt.includes(source.pages[4].prompt_modules.negative_constraints)); assert.equal(promptPlan.images[4].negative_prompt, source.pages[4].prompt_modules.negative_constraints); }); test("V3 plan rejects Qwen-like or generic uncertain authorship", () => { assert.throws(() => validateClaudeAuthoredSetPlan({ ...setPlan(), authored_by: "qwen" }), /CLAUDE_SET_PLAN_CONTRACT_INVALID/); const invalid = setPlan(); invalid.pages[4].prompt_modules.scene_relation = "场景自由发挥"; assert.throws(() => validateClaudeAuthoredSetPlan(invalid), /UNCERTAIN_EXPRESSION_REJECTED/); }); test("contract hashes are deterministic for cross-stage binding", () => { assert.equal(contractSha256(boardSpec()), contractSha256(boardSpec())); assert.notEqual(contractSha256(boardSpec()), contractSha256({ ...boardSpec(), product_story: { text: "不同故事", source_type: "creative_assumption", source_ref: "claude.creative_assumption" } })); }); test("one semantic setting-board framework adapts across six product families without code branches", () => { const products = [ ["精品咖啡豆", "包装正面", "居家手冲"], ["香氛洗衣液", "瓶身标签与泵头", "洗衣房取用"], ["运动鞋", "鞋面轮廓与鞋底", "真人行走"], ["机械键盘", "键位与机身边框", "桌面输入"], ["壁挂灯具", "灯体与安装连接", "墙面安装"], ["通勤背包", "软体轮廓与肩带", "真人背负"], ]; const hashes = products.map(([title, focus, scene], index) => { const spec = boardSpec(`run-20260804210${String(index).padStart(3, "0")}-aabbcc`, `obs${index}`); spec.identity.main_title = { text: title, source_type: "visible_observation", source_ref: "original_visual_observation.product" }; spec.hero.focus_area = focus; spec.scenes[0].content = { text: scene, source_type: "creative_assumption", source_ref: "claude.creative_assumption" }; const result = validateProductSettingBoardSpec(spec); assert.equal(result.identity.main_title.text, title); assert.equal(result.scenes.length, 4); return contractSha256(result); }); assert.equal(new Set(hashes).size, products.length); });