diff --git a/patbond-creation/src/test/java/com/patbond/patbond/creation/repository/CreationCatalogSeedTest.java b/patbond-creation/src/test/java/com/patbond/patbond/creation/repository/CreationCatalogSeedTest.java
new file mode 100644
index 0000000..8aa807a
--- /dev/null
+++ b/patbond-creation/src/test/java/com/patbond/patbond/creation/repository/CreationCatalogSeedTest.java
@@ -0,0 +1,113 @@
+package com.patbond.patbond.creation.repository;
+
+import com.patbond.patbond.creation.TestcontainersConfiguration;
+import com.patbond.patbond.creation.domain.MediaKind;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.jdbc.core.simple.JdbcClient;
+import org.springframework.test.context.TestPropertySource;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * V7 目录种子的内容断言。
+ *
+ *
不只是数行数:这里钉住的是几个**跨语言、跨仓库的约定**,它们一旦对不上,
+ * 症状都离根因很远:
+ *
+ *
+ * - {@code provider_code} 必须与 patbond-ai 适配器的 {@code PROVIDER_CODE}
+ * 逐字一致。对不上时 Worker 按任务行冻结的快照查不到适配器,任务会永远
+ * 停在排队——而那看起来像队列坏了,不像两个字符串不一致。
+ * - {@code provider_model_name} 必须是账号**实际开通**的模型 ID。写错会撞
+ * {@code InvalidEndpointOrModel.NotFound},且那是不可重试的失败。
+ * - 目录里不得出现没有适配器的 media_kind:播种一个无人能执行的模型,
+ * 客户端会渲染出一个点了没反应的入口。
+ *
+ */
+@SpringBootTest
+@Import(TestcontainersConfiguration.class)
+@TestPropertySource(properties = "patbond.creation.worker.enabled=false")
+class CreationCatalogSeedTest {
+
+ /**
+ * 与 patbond-ai 的 {@code app/providers/volcengine/video.py} 的
+ * {@code PROVIDER_CODE} 同值。**改任一侧必须同时改另一侧。**
+ */
+ private static final String EXPECTED_PROVIDER_CODE = "volcengine-seedance";
+
+ /** 账号实际开通的两个模型 ID。 */
+ private static final List ACTIVATED_MODEL_IDS = List.of(
+ "doubao-seedance-2-5-260628",
+ "doubao-seedance-2-0-260128");
+
+ @Autowired
+ private JdbcClient jdbcClient;
+
+ @Autowired
+ private CatalogRepository catalog;
+
+ @Test
+ void v7SeedsExactlyTheActivatedVideoModels() {
+ List seeded = jdbcClient.sql("""
+ SELECT provider_model_name FROM creation.generation_models
+ WHERE media_kind = 'video' ORDER BY sort_order
+ """)
+ .query(String.class)
+ .list();
+ assertThat(seeded).containsExactlyElementsOf(ACTIVATED_MODEL_IDS);
+ }
+
+ @Test
+ void everySeededModelPointsAtTheProviderCodeTheAdapterRegistersUnder() {
+ List codes = jdbcClient.sql(
+ "SELECT DISTINCT provider_code FROM creation.generation_models")
+ .query(String.class)
+ .list();
+ assertThat(codes).containsExactly(EXPECTED_PROVIDER_CODE);
+ }
+
+ @Test
+ void seededModelsAreEnabledAndResolvableThroughTheCatalogApi() {
+ // 经真实查询路径取,而不是直接读表——顺带覆盖 enabled 过滤与排序索引。
+ List models = catalog.listEnabledModels(MediaKind.VIDEO);
+ assertThat(models).hasSize(2);
+ assertThat(models).extracting(CatalogRepository.ModelRow::providerCode)
+ .containsOnly(EXPECTED_PROVIDER_CODE);
+ // sort_order 决定客户端的展示顺序:2.5 在前。
+ assertThat(models.get(0).code()).isEqualTo("seedance-2-5");
+ }
+
+ @Test
+ void onlyVideoIsAvailableUntilAnImageAdapterLands() {
+ // 播种一个没有适配器的 media_kind 会让客户端渲染出一个点了没反应的入口。
+ // 图片模型待 gpt-image-2 适配层落地后由后续迁移追加。
+ assertThat(catalog.availableMediaKinds()).containsExactly("video");
+ }
+
+ @Test
+ void noStylesAreSeededAndThatDoesNotBlockJobCreation() {
+ // 风格是产品内容决策,无法从服务商文档推导;style_id 可空,故空表不阻塞。
+ Integer styles = jdbcClient.sql(
+ "SELECT COUNT(*) FROM creation.generation_styles")
+ .query(Integer.class)
+ .single();
+ assertThat(styles).isZero();
+ }
+
+ @Test
+ void modelIdsCarryTheirVersionSoTheVersionSnapshotIsMeaningful() {
+ // 目录没有独立版本列,CatalogRepository 以 provider_model_name 兼作版本
+ // 快照。这只在模型 ID 自带版本时成立——两个 ID 的尾部即日期版本。
+ List models = catalog.listEnabledModels(MediaKind.VIDEO);
+ assertThat(models).allSatisfy(model ->
+ assertThat(model.modelVersion()).isEqualTo(model.providerModelName()));
+ assertThat(models).extracting(CatalogRepository.ModelRow::providerModelName)
+ .allMatch(name -> name.matches(".*-\\d{6}$"),
+ "模型 ID 尾部应为 6 位日期版本");
+ }
+}
diff --git a/patbond-user/src/main/resources/db/migration/V7__creation_catalog_seed.sql b/patbond-user/src/main/resources/db/migration/V7__creation_catalog_seed.sql
new file mode 100644
index 0000000..967816d
--- /dev/null
+++ b/patbond-user/src/main/resources/db/migration/V7__creation_catalog_seed.sql
@@ -0,0 +1,42 @@
+/*
+ V7: creation catalog seed (iteration-4 T4-01b)。
+
+ 目录是生产参照数据(任务表按复合外键指向它),故进版本化迁移链而非 db/dev。
+
+ 与目标模型 patbond_postgresql.sql:1495 的种子**有意不同**:正典那份用的是
+ `provider_code = 'fixture'` 与占位模型名(`patbond-image-v1` / `patbond-video-v1'),
+ 那是在服务商未定时写下的占位。现已接入真实服务商,故按实际开通的模型播种。
+
+ Scope notes:
+ - **只播种视频模型**。图片生成(gpt-image-2)尚未接入,待其适配层落地后由后续
+ 迁移追加——播种一个没有适配器的模型会让任务卡在「找不到 provider」,而症状
+ (任务永远排队)离根因很远。
+ - **不播种风格(generation_styles)**。风格是产品内容决策(提供哪些艺术风格、
+ 叫什么、配图是什么),无法从服务商文档推导;正典那 4 条风格均为 `image` 类且
+ 其 `preview_asset_id` 指向 demo 资产,而本链路不含该 demo 种子,照抄会违反外键。
+ `generation_jobs.style_id` 可空,故空风格表不阻塞任何功能。
+ - `provider_code` 必须与 patbond-ai 适配器的 `PROVIDER_CODE` 逐字一致
+ (`volcengine-seedance`)。对不上时 Java 侧按任务行冻结的
+ `provider_code_snapshot` 查不到适配器,任务会永远停在排队——而这个症状离
+ 「两处字符串不一致」这个根因很远,故在此显式提醒。
+ - id 使用显式固定 UUID 而非列默认值:种子行需要稳定标识以便后续迁移与测试引用。
+ 取 `17000000-0000-7000-8000-0000000000 1x` 段,与正典占位行(…0001/…0002)
+ 区分开,避免有人拿本链路与正典逐行对比时把两者混为一谈。
+
+ 两个模型的能力差异**不进本表**:正典的 generation_models 没有能力列,本迁移
+ 不发明超出已评审模型的列。差异(2.5 时长上限 30 秒但无 4k;2.0 有 4k 但时长
+ 上限 15 秒;2.5 图生视频时 ratio 锁 adaptive)由 patbond-ai 的
+ `app/providers/volcengine/capabilities.py` 承载并在提交前本地预校验。
+ 取证见 patbond-doc docs/development/providers/volcengine-seedance.md §5。
+*/
+
+INSERT INTO creation.generation_models
+ (id, code, display_name, provider_code, provider_model_name, media_kind, sort_order)
+VALUES
+ -- 2.5:时长可到 30 秒、支持 mov 输出与全模态参考(图 0-30/视频 0-10/音频 0-10);
+ -- 1080p 为 10bit + H.265/HEVC,少数播放环境可能不兼容。列为首选。
+ ('17000000-0000-7000-8000-000000000011', 'seedance-2-5', '豆包 Seedance 2.5',
+ 'volcengine-seedance', 'doubao-seedance-2-5-260628', 'video', 10),
+ -- 2.0:时长上限 15 秒,但支持 4k(同为 10bit + H.265/HEVC)。
+ ('17000000-0000-7000-8000-000000000012', 'seedance-2-0', '豆包 Seedance 2.0',
+ 'volcengine-seedance', 'doubao-seedance-2-0-260128', 'video', 20);