# Atlas Seller Kit 交接说明 > 更新:2026-09-30。此文是新 AI 对话、架构讨论与开发工作的首读入口。开始开发前,必须先阅读并遵守 [`development-collaboration-contract.md`](./development-collaboration-contract.md) 和仓库 `AGENTS.md`。下方按日期记录的旧方案仅供追溯;当前状态以本节、2026-09-29 类目佣金记录及最新的 1.47 节为准。 ## 2026-10-04 SKU 主动重算使用最新默认方案 根因:`SkuPricing.calculate` 优先使用 SKU 已保存的 `pricing_draft`,「重新试算」和批量按钮只强制重发相同参数,旧零折扣因此使实际售价与划线价相同。当前集成会话仅调整 Web `SkuPricing`、补充定价引擎公式回归测试及 API/交接说明,不修改服务端业务、HTTP 字段、定价引擎或数据库。 两个主动入口重读目标店铺定价目录,使用最新默认方案的完整参数替换旧 SKU 参数,沿用当前成本与有效包装;无默认方案/费率不可用/读取失败时明确提示,不回退零折扣。自动试算、手工售价输入和试算详情继续使用当前参数。批量跳过已导入 SKU,试算结果仍需保存草稿确认。表单监听保留未注册字段,返回报价直接核对实时表单,避免参数回填的渲染时序误丢弃报价;建立同一计算上下文时不取消已经发出的匹配请求。 验证:ego-browser 隔离真实组件复现旧零折扣两价相同,确认单 SKU 与批量使用最新 50% 折扣、划线价为实际售价两倍,保留成本和包装继承,跳过已上架 SKU;页面打开后将默认方案改为 25% 再重算能读取新值;手工实际售价不覆盖当前折扣;无默认方案、费率不可用、目录失败均不生成回退报价。定价引擎 16 项(新增 CNY/RUB 的 50% 折扣)和定价保存 12 项通过;Web/API 源码与测试类型检查、构建及差异检查通过。临时页面已清理,无真实商品保存或平台发布,未手动启停原有服务。 ## 2026-10-04 实际售价、划线价展示与首次发布价格 本轮由当前集成会话负责,范围为 Web `SkuPricing`、`PricingCalculator`、`ProductPublishActions`、SKU 样式,API 发布映射/校验、发布回归测试和相关文档。按维护者要求,首次上架的 Ozon `price` 改为已确认 `listing.list_price`,不发送 `old_price`;保持 HTTP 字段、定价公式、数据库和 Worker 冻结快照执行方式。实际售价继续用于目标利润计算,用户上线后在 Ozon 调整实际售价;已创建任务沿用原价格快照,已导入 SKU 不自动更新。 SKU 收起摘要和展开定价区显示采购价、实际售价、划线价、预估净利润;试算结果实际售价与划线价同行。非人民币报价按同一报价快照比例换算摘要,不读取当前汇率改写历史价格。确认上架表格同时显示两价,划线价标为「本次上报价」。服务端预检与创建要求划线价有效且不低于实际售价,缺失/零值返回 `LIST_PRICE_REQUIRED`,低于实际售价返回 `LIST_PRICE_INVALID`;零折扣两价相同可发布。 验证:发布 11 项、商品编辑 13 项、定价引擎 15 项、定价保存 12 项全部通过,覆盖十进制划线价冻结/实际模拟 Ozon 请求、实际售价保留、零折扣、缺失/零划线价阻断、幂等和租户隔离。API 源码/测试类型检查及构建、Web 类型检查及构建通过。ego-browser 隔离真实组件检查 721px/390px 下的两价同行与无页面横向溢出、展开区、上架确认和 RUB 报价的人民币摘要;未向真实 Ozon 发布,未修改真实商品,原有服务未手动启停。临时验证页面已清理。 ## 2026-10-04 主题标签格式与 AI 输出校验 本轮由当前集成会话负责,范围为共享 `ozon-hashtags.ts` 规则、Web SPU/SKU 标签提示、API 发布预检/创建校验、Worker AI 全局及 SKU 文案提示词与输出整理,以及相关测试和文档;不修改 HTTP 请求结构、数据库或标签覆盖继承语义。标签逐项以单个 # 开头,正文只允许字母、数字和下划线,无内部空格;每项含 # 最多 30 字符,最多 30 项,发布继续拼为一个 Ozon 属性值。AI 补齐缺失 #,词间空白及连字符转下划线,过滤非法、超长及重复项,并保留同项中文对照。 历史与手工草稿仍允许保存,Web 给出具体格式提示;预检和任务创建按所选 SKU 实际使用的标签拦截,返回 `OZON_HASHTAG_INVALID`,合法 SKU 独立覆盖不受未使用的通用标签阻断。Web 支持以空格拆分多个输入标签,不自动改写已有内容。新规则及示例见商品编辑 API 文档。 验证:Worker 11 项、发布 HTTP/数据库/模拟 Ozon 10 项、商品编辑 13 项通过;contracts/API/Worker/Web 类型检查与构建通过。ego-browser 隔离真实组件验证缺少 #、内部空格、非法标点、合法标签清除提示、空格拆分及 SKU 覆盖独立提示;临时页面已清理。未调用收费 AI 或向真实 Ozon 店铺发布,API/Worker 仍沿用原有开发监听进程,无手动启停。 ## 2026-10-04 Ozon 多主题标签发布修复 本轮由当前集成会话负责,范围为 API 发布映射、发布集成测试和 API/交接文档,无 Web、Worker、数据库迁移或共享类型变更。原代码把每个主题标签分别映射到属性 `23171` 的 `values[]`,被实际 Ozon schema 的单值限制误阻断。读取本地缓存的 Ozon 原始说明确认:支持最多 30 个标签,用空格分隔放在同一个属性值中。草稿继续保存标签数组;发布时先按 SKU 覆盖或 SPU 继承解析标签,再过滤空项、去除首尾空白并拼成一个值,无标签则不提交属性。 先用新增回归复现相同的「#主题标签最多 1 个值」错误,修复后发布 HTTP/数据库/Worker 模拟平台测试全部 8 项通过,覆盖 SKU 覆盖、SPU 继承、30 个标签及空标签,并核对实际组装的 `/v3/product/import` 模拟请求。API 源码/测试类型检查与构建通过,未向真实店铺发布商品。原有 API 开发监听进程已自动重载并继续监听 8200,无手动启停;测试使用临时监听端口并已退出。 ## 2026-10-04 套图生成固定操作区 本轮由当前集成会话负责,仅修改 Web 套图抽屉、底部操作区组件、样式与交接记录,复用现有 API,无契约或数据模型变更。维护者已确认「并列操作」设计:策略内容独立滚动,底部固定「本次生成」,生成/排队时并列显示取消和状态,智能规划有独立取消文案;取消确认及重复操作保护已接入,失败/取消后在同一区域只重试未完成图片。任务摘要使用执行快照,成功图片保留,结果列移除重复进度和重试入口。使用真实组件及模拟任务验证桌面/窄屏、滚动固定、取消和重试状态,Web 类型检查与构建通过;未调用收费模型,未保存商品,临时页面已清理,原有开发服务未手动重启。具体前端交接见 [`../../apps/web/HANDOFF.md`](../../apps/web/HANDOFF.md)。 ## 2026-10-03 智能规划失败诊断 当前集成会话修复范围为 Worker、共享错误码及相关文档,无请求字段、数据库迁移或生图流程变更。用户授权的两次真实 DeepSeek 诊断均在四张图片的 `type` 字段发生枚举校验错误;提示词改用有效示例和明确英文枚举,重试携带具体字段校验规则。Worker 每次校验失败和最终失败打印安全结构化日志,最终诊断写入 operation.result.failure;张数、字段、主体图、主题、尺寸及保存失败使用不同错误码。历史记录不回填推测结果,不保存模型原文、商品资料或凭据。离线测试验证错误路径、纠正反馈和诊断持久化;修复后的真实模型规划尚未再次调用。维护者 API/Worker 仍使用原有 watch 进程,无手动重启。 ## 1. 项目定位 这是一个全新的云端产品,不是 `ozon-seller-kit` 的本地版重构。目标用户可拥有多个门店,每个门店只能绑定一个平台;平台自身可支持多个市场、国家、语言和币种。因此语言、币种、类目和发布规则不能写死为 Ozon 或中文/俄文。 pnpm monorepo 包含 Web、NestJS API、BullMQ Worker、WXT 浏览器扩展、共享契约、数据库包和本地基础设施。认证、工作空间隔离、门店/素材、采集箱、商品编辑草稿、平台级商品参考目录、发布预检和商品 AI 填报已有实现;SKU 定价试算与确认、在线商品同步已有实现,Ozon 首次发布已实现,真实店铺 VAT 和七牛图片交付仍待验收。不要从旧项目复制 SQLite 模型、无鉴权 API、旧 `/trial/:id` 或商品编辑页组件;复用纯规则前须适配多租户和云端边界。 当前运行时基线:Node 24.20.0、pnpm 11.25.0、PostgreSQL 18.6、Redis 7.4.11、MinIO `RELEASE.2025-09-07T16-13-09Z`;Web 默认端口为 `8100`,API 默认端口为 `8200`,扩展 dev server 默认端口为 `8110`(仅本地开发用),Worker 无 HTTP 端口。完整目录、依赖边界与版本表见 [`architecture/project-structure.md`](../architecture/project-structure.md) 和 [`architecture/technology-and-deployment.md`](../architecture/technology-and-deployment.md)。 ## 0. 交接结论(先看这里) ### 2026-10-03:AI 套图生成 智能规划已重新设计为按当前总张数预算规划(`image_count` 1–20、默认 4),不沿用旧项目固定图类配额。使用 Worker `.env` 中的 DeepSeek 配置,按 SKU 文字资料/要求和出图配置安排各张用途;底图 ID 不当作视觉识别证据,资料不足时不编造卖点或参数。严格校验预算、分类、主题、底图与实物单位;业务方案无效时重试一次,失败返回固定脱敏诊断。影响 Web/API/Worker/contracts,数据库结构不变。 已按维护者确认的三列右侧抽屉接入 Web、API、Worker、contracts 与数据库。默认 4 张(白底/场景/核心卖点/细节各 1)、四大类及逐张方案编辑,最多 8 张底图,逐张选用最多 3 张;明确绑定已保存 SKU。DeepSeek 异步规划,Pcore 复用单图链路并汇总为一个套图任务。支持历史结果、后台生成、取消、保留部分成功图与重试;重试代次避免旧轮询/迟到回写污染新任务。结果只回填目标 SKU 表单。迁移 056–059;具体契约与验收范围见 [套图 API](../api/product-image-suites.md)。API/数据库/Worker 自动测试不发起收费模型调用;真实套图质量仍需业务样本验证。 已完成“采集 → 采集箱 → 商品编辑草稿”的主要数据闭环、平台级类目/属性/字典目录与发布预检。编辑页已接入异步「AI 填报」:基于来源资料与 Atlas 类目树生成类目建议、型号/SKU 货号、证据充分的包装建议及 SPU 中俄文案;单 SKU 文案由用户单独生成并确认应用。AI 结果只回填未保存表单,保存草稿仍是持久化边界。Ozon 动态类目属性可按已选类目加载并手工填写,**当前不由 AI 自动填报**。SKU 图片选择现支持采集候选图、工作空间上传图和网络导入图;在弹窗确认后回填草稿,页面保存后持久化。 SKU 定价已打通试算、表单应用与原子保存确认,在线商品同步已有 API/Worker。首次发布 job/import 与结果轮询已接入;生产启用仍需完成 VAT 与七牛图片交付验收;正式定价还需完成 CEL 费率及类目佣金映射审核。AI 类目推荐和文案已能运行,但不能等同于跨来源样本验收;尤其应继续用 1688/淘宝/天猫样本核验 Atlas 类目候选质量,以及俄文文案、包装证据和 SKU 继承行为。AI 套图已接入规划、逐张生成、取消和失败重试;图片「编辑」菜单中的一键 3:4、添加水印和单张 AI 编辑已接入异步素材任务。 当前优先事项:用真实来源样本回归 AI 类目推荐、文案与图片选择,补做真实图片上传/网络导入联调;审核正式物流费率与佣金映射,并用实际结算账单核对 Ozon 平台服务费、收款费和其他扣费;完成首次发布在中国主体跨境 rFBS 测试店铺的 VAT 省略与七牛延迟抓图验收。平台参考目录目前由平台级参考源和同步任务维护,不能把 9 月 10 日的门店级缓存描述当作现行架构。 接手前请先阅读 `git status --short`,确认当前工作区状态;本轮图片选择的 Web、API、contracts、迁移、测试和文档随本报告一起提交。不要用 `git reset --hard` 或批量清理未跟踪文件。共享 contracts 类型需先构建,再执行 Web/API/Worker 检查。开发验证需要时可按 `AGENTS.md` 先确认进程与端口,再启动或重启 API/Worker;下方旧章节中的“不可重启”说法已失效。 ### 2026-09-30:商品编辑页首次上架 - Web、API、Worker、contracts 与 database 同步实现,接口以 [商品编辑与首次上架 §7](../api/product-editor-and-publishing.md) 为准;研究、产品决策及验收见 [实施记录](../product/ozon-first-publish-review-plan.md)。扩展未改动。 - 两个入口为「上架至 OZON」「部分上架」,只提交未导入或失败 SKU,显示已导入/进行中的跳过原因;每批最多 50,先保存草稿、预检和确认,再原子写入不可变快照及 Outbox。成功锁定型号和历史货号,其他资料仍能保存。任务状态可刷新恢复。 - Worker 提交前检查远端货号,导入后轮询;受理结果不确定时仅对账,禁止盲目重发。七牛图片校验 checksum、复制发布对象、WebP 转 JPEG,再签发独立私有 HTTPS 下载地址,默认 7 天;不持久化签名或 URL。 - VAT 按维护者确认首期省略,不默认 `0`;rFBS 首期不提交条码、库存和仓库。生产 `OZON_PUBLISH_ENABLED=auto` 默认关闭,真实店铺与图片验收后显式启用。MinIO 素材不能直接供 Ozon 抓取,需要迁移到七牛;环境项见 `.env.example`。 - `055` 全量空库迁移和本地开发库应用通过。26 项 API/编辑器/素材与 14 项 Worker 测试通过;contracts/API/Worker/Web 类型检查与构建通过,Web 仍有既有 bundle 提示。隔离库浏览器验证了禁选、全部/部分预检、缺字段阻断、提交及刷新恢复,未发送真实 Ozon 导入。 - 维护者原有 API8200、Web8100、Worker 保持运行,通过监听自动加载改动;验证用 API8201、Web8101 已关闭,隔离测试库已清理。 ### 2026-09-29:类目佣金 JSON 导入与定价接入 - 已先编写[实施方案](../product/category-commission-import-plan.md),并完成[API 契约](../api/category-commissions.md)、迁移 `052`、导入解析器、原子启用、管理页面及试算展示。影响 Web、API、contracts、database;未涉及 Worker 和扩展。 - 入口为「系统管理 → 类目佣金」(`/system/commissions`):上传 JSON/TXT 或粘贴 JSON,填写版本、来源、采集日期,预览差异与异常,确认适用范围后启用。支持搜索、只看异常、查看历史版本;百分比显示两位小数。 - 当前限定 Ozon RU / rFBS / RUB 三档佣金;通过已同步平台目录核对二级 `cate_id` 和父类目,映射到 Atlas 商品类型 UUID。查询接口支持内部 UUID 或外部 `description_category_id`,正式报价按最终售价及汇率命中分档,结果展示佣金类目、档位和版本。 - 原始附件共 542 类 / 1,626 档。最初预览错误地排除了两个全档 0% 类目。维护者确认 0% 可能是有效优惠佣金后,已修正为可导入;新版浏览器实测 542 类全部可用,覆盖 7,365 个商品类型。**仅预览,尚未把用户附件启用为正式费率**;管理员需核对 RU/rFBS 适用范围并在页面确认。 - 迁移 `052` 已在本地数据库应用,再次运行迁移无变化。修正后 38 项相关测试通过:解析、数据库真实角色权限、启用/幂等/旧预览、目录异常提示、授权撤销、跨工作空间、后半程写入失败回滚、历史不可变,以及 1500/1500.01/5000/5000.01 ₽ 边界与 CNY 换算。API/Web 类型检查及构建、contracts/database 构建通过;Web 仍有大 bundle 提示。 - 浏览器验证了实际附件上传、预览、异常筛选、确认项使按钮可用、修改来源后预览失效;未在浏览器提交真实费率。启用及历史查询由事务回滚集成测试验证;未做多连接并发压力测试。没有手动启动/停止维护者的 API/Web/Worker,使用现有 8100/8200 服务;浏览器验证窗口已关闭。 - 首版每次启用完整替换上一导入版本,不支持增量或定时发布;同 JSON 内容和同平台版本名称禁止重复导入。映射是启用时目录的快照,后续新增商品类型的映射刷新仍需完善,不能承诺目录同步自动扩展佣金覆盖。正式使用仍须核对附件适用范围及其他物流/结算费用。 ### 2026-09-29:草稿物流费率用于报价与发布预检 - 维护者取消“草稿物流费率报价不能首次上架”的业务限制。开发和生产环境默认允许草稿卡参与方案、试算与 SKU 确认,`PRICING_ALLOW_DRAFT_RATES=false` 可限制新报价;测试环境仍默认关闭。既有 `development_only` 字段继续标记费率尚未审核。 - 首次发布预检不再因草稿卡报价产生 `PRICING_RATE_REVIEW_REQUIRED`;已确认报价、售价、包装、素材与平台属性等校验保持有效。定价工具移除误导性告警,SKU 显示定价自身状态,费率选择仍标明“待审核费率”。 - 系统管理「物流费率审核」仍可用真实账单样本复核 Ozon CEL 费率;草稿卡不会因可试算而自动变为 active。商品编辑页的“选择 SKU 并首次上架”按钮仍为占位,真正 Ozon 首次发布 job 尚未接入。 - 验证:草稿费率配置、定价 HTTP/PostgreSQL 和商品编辑 HTTP/PostgreSQL 共 26 项通过;contracts/API/Web 类型检查和构建通过,`git diff --check` 通过。隔离浏览器打开现有门店的定价方案页,确认旧“当前使用开发草稿费率”提示已消失。未重启维护者服务,未进行真实上架。 ### 2026-09-28:SKU 定价试算与确认 - SKU 采购价失焦/回车自动计算、手动算价、批量计算和保留手动售价;明细弹窗比较运输方案、调整参数并应用当前 SKU。 - 独立试算、门店方案管理和参数另存已实现。Decimal 计算器由服务端统一提供,保存商品时原子确认不可变报价;变更输入使报价失效,首次发布要求有效报价。 - Worker 增加 CBR 汇率同步与过期预览清理。新增迁移 `043`~`048`;CEL 初始化数据保持 draft,不能直接用于正式算价。 - 系统管理补充 CEL 费率审核页与管理员 API;迁移 `049` 保存不可修改的审核证据,`050`/`051` 锁定已审核版本及规则归属。六类真实账单样本须按正式物流计费规则复算后才可启用,当前 CEL 种子依然是 draft。 - `pnpm dev` 会先构建共享依赖;Web 开发读取 contracts 源码,解决新增导出未构建导致启动失败的问题。 - 本轮影响 Web、API、Worker、contracts、database。验收记录及后续工作见[SKU 定价验证记录](../reviews/sku-pricing-verification.md),HTTP 以[定价 API](../api/pricing.md)为准。 ### 2026-09-28:Ozon 定价附加费率调查 - 已将 Ozon Global 官方公开规则、适用条件、来源链接及 Atlas 字段对应关系写入[Ozon 尾程、收款与其他费率调查](../requirements/ozon-fulfillment-and-payment-fees-research.md)。本次仅新增调查文档和交接记录,没有修改定价实现或方案默认值。 - Ozon 合作配送的物流平台接入服务费为每票货值的 2%,每票最低 15 ₽、最高 200 ₽;实际配送费另按承运商规则计算。“尾程费率”不能直接理解为全部尾程配送费。 - 中国卖家的钱包公开费率为 PandaPay 1.10%,LianLian Pay、Payful (GEP)、PingPong 1.20%;“其他费率”没有统一数值。另有适用于 2026-09-01 起订单的国际运输合同服务费,每票货值 0.33%。 - 当前引擎将 `last_mile_rate`、`withdrawal_rate`、`other_rate` 均按单个 SKU 销售价计入成本。下一步须用 Ozon 结算明细及 CEL 合同/账单确认收费主体、基数、是否重复计入;若要用于正式报价,还需处理每票计费、2% 上下限和合包分摊,不能直接把本调查的比例当作已审核规则。 ### 1.1 开发进度(2026-09-02) 今日完成三项工作,全部通过 typecheck、生产构建,并经浏览器或启动实测: **Web 前端框架(`apps/web`)** - 参照 `ozon-seller-kit/studio` 搭建:紫色主题(`colorPrimary #8b5cf6`、圆角 8)、`index.css` CSS 变量、可折叠侧边栏 + 吸顶页头 + 移动端 Drawer 的 `MainLayout`、`@` 路径别名、`StrictMode` 入口。 - 主题显式 `fontSize: 14`;输入类控件默认高度 32→34(`controlHeight`)、小尺寸 24→26(`controlHeightSM`);body 字号同步 14px。 - 依赖使用最新稳定版:React 19.2.8、antd 6.6.2,新增 `@ant-design/icons` 6.3.4;沿用仓库精确版本号约定。 - 左侧菜单按 [`requirements/account-store-watermark.md`](../requirements/account-store-watermark.md) 的导航约定:在线商品(默认首页)、采集箱、店铺管理、水印管理、定价工具,均为占位页;另有菜单外的商品 AI 编辑页 `/products/:id`。 - 基础设施:`services/api.ts`(axios 封装 + 统一错误提取)、`config/env.ts`(`VITE_` 变量)、统一占位组件 `components/PagePlaceholder`。 - Web tsconfig 覆盖 base 的 `module: Node16` 为 `ESNext + bundler`(base 面向 NestJS;Node16 会要求相对导入带扩展名);TypeScript 7 已移除 `baseUrl`,`paths` 直接相对 tsconfig 解析为 `"@/*": ["./src/*"]`。 **注册与登录页面(契约:[`api/auth.md`](../api/auth.md))** - `/login`、`/register` 两页共用 `AuthLayout`(品牌视觉与主布局一致)。注册字段:显示名称(选填 ≤80)、邮箱、密码、确认密码;登录失败统一 401 文案,注册重复邮箱 409 落到邮箱字段提示。 - 会话基础:access token 仅内存持有(`services/authToken.ts`),不进入任何持久化存储;`api.ts` 拦截器自动附加 Bearer 头,401 时用 refresh cookie 换新 token 并重放一次(并发去重),刷新失败整页跳转 `/login`。 - 实测发现并修复:antd v6 中空值会同时触发 `required` 与 `type: 'email'` 两条错误,已改为非空才校验格式的 `emailFormatRule`(`utils/validators.ts`)。 - 浏览器实测通过:表单校验、页面互链、API 未启动时的错误提示路径。密码复杂度按契约不在前端硬编码,仅提示以服务端校验为准。 - 留待 API 认证模块就绪后:接入真实 `/api/auth/*` 调用、主布局路由守卫、刷新页面后的会话恢复;auth 类型届时迁移至 `@atlas/contracts`。 **Worker 与扩展骨架修复** - Worker:`ioredis` 是 BullMQ 的可选 peer 依赖,pnpm 严格隔离下必须由应用显式声明,已新增 `ioredis 6.0.0`;`main.ts` 改为显式构造 IORedis 实例并设置 `maxRetriesPerRequest: null`(原生 ESM 运行必需,也是后续 BullMQ Worker 的硬性要求)。`tsx` dev 与 `node dist/main.js` 两种运行方式均验证可连接本地 Redis。 - 扩展:WXT dev server 从默认的 3000–3010 漂移改为固定 `8110`(`EXTENSION_PORT` 可覆盖,`strictPort` 开启);`.output/` 构建产物已加入 `.gitignore` 并移出 git 跟踪。端口约定同步更新至交接、部署与项目结构三份文档及 `.env.example`。 ### 1.2 开发进度(2026-09-03 下午):店铺管理与平台上下文前端 **Web 接入平台上下文与门店管理 API(契约:[`api/stores-and-platforms.md`](../api/stores-and-platforms.md))** - 页头新增平台选择器:`services/platform.tsx` 的 `PlatformProvider` 加载 `GET /api/platforms` 目录与 `GET /api/platform-context`(`X-Atlas-Platform-Id` header),默认取目录第一家(Ozon 优先);选择结果仅在 localStorage 保存平台 id(非敏感 UI 偏好)。页头组件 `layouts/PlatformSelector.tsx`,tooltip 展示当前平台默认店铺。 - 店铺管理页 `pages/shops/ShopsPage.tsx` 覆盖已实现的门店接口:游标分页列表(“加载更多”)、创建(202 + operation,提示“验证中”)、编辑(`If-Match` 乐观锁,409 时刷新列表并提示重新编辑)、设为默认、手动重新验证凭据、仓库同步(凭据未验证时展示 422 脱敏错误)、软删除(`DEFAULT_STORE_REPLACEMENT_REQUIRED` 转为可操作提示)。API Key 只存在于提交瞬间的内存表单,不写入任何存储。凭据验证与仓库同步的实际执行仍依赖未实现的 Outbox Dispatcher + Worker,店铺会停留在“验证中”。 - 基础设施:`services/api.ts` 新增 `getList`(保留 `{ data, meta }` 分页信封)与 `apiErrorCode`(业务错误码提取);`services/stores.ts` 封装门店接口并按契约生成 `Idempotency-Key`。 - `@atlas/contracts` 补充只读响应类型 `Platform`、`PlatformContext`、`CursorPaginationMeta`、`StoreOperationResponse`(前端 AI 按冻结契约补类型,无行为变更,后端请复核)。 - 修复 API 阻断性 bug(跨领域改动,原因:阻断本功能联调;影响 `apps/api/src/stores/stores.controller.ts`):tsx/esbuild 不生成 `design:paramtypes` 装饰器元数据,控制器裸类型构造参数在 dev 运行时注入 `undefined`,`GET /api/platforms` 返回 500。已改为与 `auth.controller.ts` 一致的显式 `@Inject(...)`;服务层原本就使用显式 token,不受影响。 - 浏览器实测(全新迁移的本地 PostgreSQL 18 + Redis 7):注册 → 平台选择器 → 创建/编辑/设默认/删除保护/同步 422 错误路径 → 刷新后会话恢复与列表持久化,全部通过;contracts/api/web typecheck 与 web production build 通过。 ### 1.3 开发进度(2026-09-03 晚):店铺表单与界面收口 - 店铺创建与编辑表单补齐空白字符拦截;API Key 输入上限与冻结契约的 8192 字符保持一致,不在前端自行收紧。 - 设置页补充取消操作;全局主题目前使用 `borderRadius: 10`、默认控件高度 34,小尺寸控件高度 26,输入类控件单独使用 16px 字号与 40px 高度。按钮保留 Ant Design 默认字号、高度与内边距。 - 页头面包屑改用 12px `RightOutlined` 分隔符,并完成条目、分隔符与侧栏折叠按钮的垂直对齐;页头水平内边距调整为 `0 20px 0 10px`。店铺与设置页的视觉调整不改变接口或领域行为。 - Ozon 平台现支持 `CNY` 与 `RUB`;新建 Ozon 店铺时默认预选 `CNY`。默认币种由平台能力 `default_currency_code` 提供,服务端仍以平台支持币种表校验提交值。 ### 1.4 开发进度(2026-09-04):凭据验证 Worker - Worker 轮询 Transactional Outbox,按工作空间隔离认领事件并投递 BullMQ;任务只携带 store、operation 与 secret ID,绝不携带 API Key。 - `store_credential_verification` 任务仅在 Worker 内读取当前未撤销密钥、短暂解密并调用 Ozon `POST /v1/description-category/tree`(`{ "language": "ZH_HANS" }`)进行低风险验证。该请求已用真实店铺验证可用;仓库接口留待仓库同步模块按其实际请求契约接入。401 表示认证失败;403 单独提示账户/权限不匹配;其他确定性 4xx 记录脱敏 HTTP 状态并标为验证请求错误,不误报为凭据无效。临时网络、限流和 Ozon 服务端错误最多指数退避重试 5 次。 - 验证成功回写 `active / valid`;密钥被替换或门店删除时,旧任务取消且不得覆盖新状态。前端在验证中只显示一个标签,避免与“未验证”同时出现。 ### 1.5 开发进度(2026-09-04):水印模板 API - 已实现字体目录、按工作空间隔离的水印模板列表/详情/创建/编辑/软删除,以及店铺推荐水印绑定与解绑。文字/图片模板均创建不可变渲染版本;仅改名不新增版本,渲染参数或类型变化会新增版本。 - 删除模板会解除所有当前店铺绑定,但不删除历史版本或图片资产。模板与店铺写操作均要求 `Idempotency-Key`;编辑要求 `If-Match`。 - 图片模板只可引用当前工作空间内、已完成安全校验的 `media_asset`;素材直传、图片校验和短时预览 URL 的实现见 1.6,前端仍不能自行拼接对象存储地址。 ### 1.6 开发进度(2026-09-04):水印图片素材直传 - 已实现仅供 `watermark_image` 使用的两阶段素材接口:创建 10 分钟有效的 MinIO/S3 预签名 PUT 上传意图、完成上传后验证对象存在、大小、SHA-256、PNG/JPEG/WebP 魔数与 1–8192 px 尺寸,并创建或复用工作空间内的 `media_asset`。 - 已实现素材短时预览 URL(10 分钟);普通 API 返回永不包含 bucket、object key 或长期 URL。图片模板只接受当前工作空间内、类型为 PNG/JPEG/WebP 的已验证素材。 - 本地环境默认使用 `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` 连接 `http://localhost:9000`;生产必须显式配置最小权限的 `OBJECT_STORAGE_ACCESS_KEY` / `OBJECT_STORAGE_SECRET_KEY`。 ### 1.7 开发进度(2026-09-04):上传意图清理 Worker - Worker 启动时立即执行、之后每 5 分钟扫描一次所有工作空间:将超过有效期仍为 `pending` 的上传意图置为 `expired`。 - 对 `failed` / `expired` 意图,Worker 删除其 MinIO/S3 临时对象;对象不存在也视为已清理。仅对象存储删除成功才写入 `object_cleaned_at`,删除暂时失败会保留记录并在下次扫描重试。 - 新增迁移 `007_media_upload_cleanup.sql`,该字段与清理索引必须在部署 Worker 前执行。 ### 1.8 开发进度(2026-09-04):M2.1 采集箱基础闭环 - 已新增 `008_collection_box_foundation.sql` 与 `009_fix_collection_limit_function.sql`,并在本地 PostgreSQL 实际执行。迁移建立工作空间隔离的采集记录、商品草稿、默认 SKU 与来源素材转存任务;采集箱默认上限为 1000 条,数据库触发器在并发创建/恢复时锁定工作空间并强制限制。 - 平台能力现有固定键:`can_be_source`、`can_be_target`、`can_be_header_context`。Ozon 是首期目标和页头平台;1688、淘宝、天猫仅作为采集来源,API 平台目录不会把它们暴露给 ERP 页头选择器。 - API 已实现手动创建采集草稿、按页头平台列出采集箱、备注的 `If-Match` 更新、批量删除、清空和失败素材重试入口。采集记录始终不绑定目标平台或店铺;本阶段列表的上架摘要固定为“等待首次上架”,不冒充 Ozon 前台可见性。 - 批量删除和清空会软删除 Atlas 采集草稿、创建 `operation` 与 Outbox 事件;Worker 取消尚未执行的素材任务并完成清理操作。它们绝不删除或下架平台商品。 - Web 的采集箱已替换占位页,支持容量显示、标题/来源筛选、手动创建、批量删除、清空确认、空/加载/错误状态。商品编辑入口明确保留给下一任务,未伪造可发布能力。 - `apps/api/test/collection.e2e.test.ts` 覆盖手动创建、幂等重放、列表读取、乐观锁冲突和软删除;该测试、本次受影响的 API/Worker/Web TypeScript 检查及 Web 生产构建均已通过。任务清单见 [`m2.1-collection-box-foundation.md`](./m2.1-collection-box-foundation.md)。 ### 1.9 开发进度(2026-09-05):M2.2 / M2.3 扩展采集首个闭环 - 已实现毛子 ERP 风格的 Atlas Web 登录态同步:扩展打开 Atlas Web 标签,轮询读取本站 localStorage 中的短期 Web access token,保存到 extension storage 并用 Bearer token 检查会话/采集。扩展不读取 refresh Cookie、来源站 Cookie 或来源站 localStorage。 - 已实现 `POST /api/extension/materials`:从扩展身份推导工作空间,创建或复用未绑定目标平台/门店的采集记录、SPU、SKU 草稿和来源素材任务。采集载荷严格限制为来源证据、规范化字段和素材 URL,不能携带店铺、Cookie、API Key 或目标平台。 - Worker 已消费素材转存 outbox 事件,执行 HTTPS 公网地址校验、大小/图片格式限制、哈希去重与 MinIO/Kodo 转存;单个素材失败不回滚采集箱商品。 - 浏览器扩展已实现 Ozon 公开商品页的小型 Shadow DOM 悬浮窗及未登录/已登录状态。Ozon 首版解析器以当前公开 SKU 和公开变体状态为证据,不生成笛卡尔积假 SKU。Ozon Seller 四接口本地观察、Ozon 变体详情逐页补全及 1688/淘宝/天猫解析仍未开始。 - 详情、边界和本地配置见 [`m2.2-m2.3-extension-collection.md`](./m2.2-m2.3-extension-collection.md)。 ### 1.10 开发进度(2026-09-06):M2.4 来源解析器与采集测试页 - 扩展浮窗现覆盖 1688 商品详情、淘宝商品详情和天猫商品详情。1688 仅遍历 `skuMapOriginal` 中实际存在的组合;淘宝/天猫仅在同时取得 `skuBase.skus[].skuId + propPath` 时创建多 SKU,并以 `skuCore.sku2info` 补充参考价格。规格轴本身从不展开为笛卡尔积。 - 缺少真实组合证据时,扩展只保存规格轴,服务端创建一个默认 SKU 并标记为待确认;颜色等轴值图片仍作为候选素材,绝不伪造来源 SKU 图片绑定。 - “编辑上架”暂时不进入未完成的商品编辑模块,改为打开 `/collection-test/{collectionItemId}`。该只读测试页展示实际保存的规范化载荷、原始证据、SKU 草稿和素材转存任务,用于验证每个平台的采集结果。`GET /api/collection-items/{id}` 已提供该工作空间隔离的检查读模型。 - 已通过 API/Worker TypeScript 检查、Web 与扩展生产构建;`apps/api/test/extension.e2e.test.ts` 已覆盖扩展会话、采集幂等复用和测试页所用检查读模型。尚未在真实来源商品页执行样本回归,接手者应按第 6 节先完成该验证。 ### 1.11 开发进度(2026-09-07):扩展网页登录态同步与稳定 ID - 扩展登录采用毛子 ERP 模式:点击后打开并保留 Atlas Web 标签;Web 登录或恢复会话后写入本站 localStorage,扩展每 2 秒读取并验证该短期 Web Bearer token。没有有效网页会话时,用户在同一标签登录后即可完成插件登录。 - Atlas Web access token 写入 localStorage 是维护者批准的窄范围例外;扩展不读取 refresh Cookie、来源站 Cookie 或来源站 localStorage。Token 失效后插件清除缓存并重新要求网页登录。 - 不再需要 `EXTENSION_COLLECTOR_REDIRECT_URI` 或 `identity` 权限;方案 A 的网页确认 + PKCE 配对设计单独记录在 [`extension-login-alternatives.md`](./extension-login-alternatives.md),当前不作为插件默认路径。 - `apps/extension/wxt.config.ts` 的 manifest 带有仅公开的 RSA `key`,Chrome 为解压加载和自托管包保持稳定扩展 ID;私钥未写入仓库。`wxt build` 已确认最终 `manifest.json` 含该 key。 - 已通过 contracts、API(含测试 TS)、Web、扩展的类型/生产构建,以及 `apps/api/test/extension.e2e.test.ts`:Web Bearer 会话检查 → 工作空间解析 → 采集幂等上报。 - 同一工作空间内的同一来源商品再次采集时,系统保留 `collection_item` / `product` ID,但用最新页面快照覆盖来源标题、价格、证据、SKU 与候选素材。迁移 `013_collection_source_snapshot_refresh.sql` 为记录和素材任务增加快照版本;旧转存任务取消,Worker 仅处理当前版本,不能将旧页面内容回写到新快照。 - Ozon 公开商品页解析器现从 JSON-LD `BreadcrumbList` 提取来源类目(可见面包屑为回退),保存当前页面语言的类目路径及对应面包屑证据;解析器版本为 `ozon-public-1.1.0`。 ### 1.12 开发进度(2026-09-07):Ozon 公开多 SKU 采集与归并 - 解析器改为优先读取 Ozon 已渲染的 `state-webAspects-*.data-state.aspects[].variants[]`,不再用脚本文本正则猜测 SKU。保存的三 SKU 页面快照 `packages/reference/ozon1.html` 已确认能直接得到 `5387004839`、`5387005639`、`5387005221`。 - 增加只限 Ozon MAIN world 的页面 JSON bridge:只接受数字 SKU 和固定的 `modal/aspectsNew` / `product/` 路径,白名单返回 `webAspects`、`webProductHeading`、`webPrice`、`webGallery` 状态;不暴露 Cookie、请求头或任意请求能力。扩展以并发 3、最多 100 SKU 队列补全实际变体详情;个别失败保留已发现变体和证据。 - Ozon 公开页面没有稳定 SPU/组 ID;当前 URL SKU 仅保存为入口证据。API 对 Ozon 用本次 `source_sku` 集合与已有 `product_variants.source_sku` 的交集复用并覆盖同一采集快照,允许用户从同组任一 SKU 页面重复采集。多个既有记录命中时返回 `SOURCE_VARIANT_GROUP_AMBIGUOUS`,不静默合并。 ### 1.13 开发进度(2026-09-07):Ozon 商品特征、描述与素材预览 - `/collection-test/{collectionItemId}` 的“素材转存任务”现在直接展示转存成功素材的缩略图,并显示关联来源 SKU。页面为每个 `succeeded + asset_id` 的任务按 4 路并发申请素材短时预览 URL;URL 只保存在浏览器内存,不能写回采集记录、产品或对象存储路径。没有 SKU 直接关联的规格轴图片会明确显示为“商品级”。已在现有三 SKU 采集记录 `7172fa38-0e4d-485e-9d53-1bd92aced79a` 实测确认 37 张素材可显示。 - Ozon 解析器升级为 `ozon-public-2.2.0`。本地页面优先读取 `webShortCharacteristics`、`webDetailedCharacteristics`、`webCharacteristics` 的 `characteristics[]`;当前入口 SKU 的 `/product//` 补全响应会再读取完整 `webCharacteristics`,去重后写入 `common.attributes`。这沿用旧插件已验证的数据形状,不通过页面文本或全页 DOM 猜测参数。 - 固定的 Ozon 详情页段仍只读取 `webDescription` / `webRichContent`。其中 `webDescription.richAnnotationJson` 被转换为纯文本写入 `common.description`,原 JSON 同时保留在 `raw_value` 供检查;富文本中的图片只走既有详情素材白名单,不会混入描述文本。 - MAIN world bridge 的返回白名单已经增加上述三个 Characteristics widget,仍然只接受固定 Ozon 同源路径和数字 SKU,不暴露 Cookie、请求头或任意请求能力。 - **兼容性提醒:** 已有采集记录不会自动重新解析历史 `normalized_payload`。例如上面的现有记录仍标记为 `ozon-public-2.1.0`,只能验证新素材预览;需重新在来源商品页执行“编辑上架”,系统会覆盖同一采集快照,届时在规范化载荷查看新版属性与描述。 - 已通过扩展 `wxt prepare + tsc + build`、Web `vite build`、`tsx --test test/*.test.mts`(5 项,包括 Ozon 页面快照中“类型/原产国/材料”等 5 个特征和富文本描述样本)以及浏览器测试页实测。 ### 1.14 开发进度(2026-09-08):重采集素材任务外键修复 - 修复重复“编辑上架”覆盖来源快照时 `collection_asset_transfers.workspace_id` 为空导致的数据库非空约束错误。根因不在新任务插入:旧的复合外键 `(workspace_id, source_variant_id) ... ON DELETE SET NULL` 在删除旧 SKU 时,会按 PostgreSQL 规则同时清空两个引用列,误将不可为空的 `workspace_id` 设为 `NULL`。 - 覆盖流程现在先将历史素材任务的可空 `source_variant_id` 显式置空,再删除旧 SKU。迁移 `014_collection_asset_variant_fk.sql` 同时把外键改为 `ON DELETE SET NULL (source_variant_id)`,从 schema 层杜绝同类删除操作再次触发该错误。已在本地数据库实际执行并核验最终约束定义。 - 该错误发生在同一数据库事务内,因此报错的旧请求会完整回滚,不会留下半成品变体、转存任务或 Outbox 事件。扩展可在 API 开发进程热重载后直接再次点击“编辑上架”。 - 已通过 API TypeScript 检查、`apps/api/test/extension.e2e.test.ts`(首次采集 → 同组 SKU 重采集 → 快照覆盖)和新增的转存任务 `workspace_id` 断言。 ### 1.15 已确认设计(2026-09-08):采集图片按需物化 - **新规则取代“采集即转存”:** 采集阶段仅保存候选来源 URL、图片角色、SKU/规格关联、排序与证据;测试页直接加载来源 URL,以最快速度核对且不产生对象存储和下载费用。`media_asset` 只代表 Atlas 已验证、可稳定编辑与发布的正式素材。 - 用户选图进入编辑时建议转存;加水印可在单次任务中临时下载来源图、仅存水印结果(来源失效后不可重试);AI 改图等需要图片字节,通常先转存或在受控任务中临时下载;AI 成功输出和正式发布图片必须是 Atlas 控制的 `media_asset`。不得把来源 URL 直接提交到目标平台,也不得为下载图片读取来源 Cookie 或绕过防盗链。 - 首期**不做自动删除已选择但未发布素材**。该能力涉及引用、编辑草稿、处理中任务、恢复和误删风险,当前不值得引入。以后如需控成本,只能先考虑清理从未被任何商品、派生任务或发布快照引用的素材,并单独立项。 - 完整决策、场景表、AI/水印边界与按需物化要求见 [`media-storage-strategy.md`](../architecture/media-storage-strategy.md)。后续采集、编辑、AI、水印和发布任务开始前必须先读此文;按需物化已通过 `pending_selection` 候选状态和 `POST /api/collection-items/{id}/asset-materializations` 落地,正式商品编辑页尚未接入选图动作。 ### 1.16 开发进度(2026-09-08):采集图片按需转存与来源预览 - 已新增迁移 `015_collection_asset_pending_selection.sql`、`016_collection_asset_pending_selection_default.sql`:为素材处理枚举增加 `pending_selection`,并将新候选的默认状态改为该值。枚举变更和使用它的默认值/索引刻意拆为两个迁移事务,避免 PostgreSQL 新枚举值在同一事务中立即使用的限制。 - `POST /api/extension/materials` 现在只保存来源图片候选,绝不自动创建 `collection.assets.materialize.requested` Outbox 事件或下载图片;快照覆盖会取消旧快照中 `pending_selection`、`queued`、`running` 的候选/任务。返回的 `materialization` 新增 `pending_selection` 计数。 - 已新增受 `Idempotency-Key` 保护的 `POST /api/collection-items/{id}/asset-materializations`:只能选当前快照的 `pending_selection` 候选,转为 `queued` 后创建 operation 并投递现有来源素材 Worker。商品编辑、AI、水印和发布后续均复用此入口;采集测试页不会调用它。 - `/collection-test/{collectionItemId}` 现在直接显示候选的来源 URL;已转存素材仍通过短时受控 URL 预览。页面明确区分“候选,未转存”“等待转存”“转存中”“已转存”等状态,方便核对而无需等待或消耗存储。 ### 1.17 开发进度(2026-09-08):采集候选视频(首期) - 已新增并在本地开发数据库执行 `017_collection_video_candidates.sql`:`collection_asset_transfers` 新增 `media_type`(`image` / `video`)和可选 `poster_url`。共享采集契约的素材项新增 `mediaType`(默认为 `image`)及 `posterUrl`;检查读模型和测试页可区分媒体类型。 - Ozon 仅从固定 `webGallery` / `webVideo` 状态读取视频;1688 仅从旧采集器验证过的 `gallery.fields.video|videos` 状态读取;淘宝、天猫仅从旧采集器验证过的 `item.videos` 状态读取。三者都不扫描页面 `video` 标签,因此不会将评价、推荐或广告视频误采为商品视频。 - 首期候选仅接受 HTTPS 的直接 MP4/WebM URL,并保留可用封面 URL。`blob:`、HLS (`m3u8`)、DRM 与未知格式不采集;Worker 按需转存时以 MP4/WebM 文件魔数复核,图片仍限制 20MB,视频限制 200MB。暂不实现视频转码、封面抽帧、时长/尺寸解析、视频水印或发布。 - `/collection-test/{collectionItemId}` 对未转存视频显示 `preload=none` 原生播放器,对已转存视频使用 Atlas 短时预览 URL;与图片一样,采集本身不下载也不产生存储成本。完整规则见 [`media-storage-strategy.md`](../architecture/media-storage-strategy.md)。 ### 1.18 开发进度(2026-09-08):旧采集器结构化路径迁移 - Ozon 解析器已对齐旧采集器的结构化合并方案:`webDescription/webRichContent` 富文本优先,`schema.org Product.description` 为兜底;首屏 `webShortCharacteristics`、默认商品页和详情页返回的 `webCharacteristics/webDetailedCharacteristics` 都会合并去重。`ozon-public-2.3.0` 是对应版本;旧快照必须重新采集。 - 1688、淘宝、天猫已加入受限 MAIN-world 商品状态桥:1688 仅投影公开商品的画廊、SKU、价格、参数等固定字段;淘宝/天猫仅投影 `__ICE_APP_CONTEXT__` 中的公开商品结果。桥不提供任意 window 读取、Cookie、请求头或通用 fetch。新采集器将这些结构化数据、JSON-LD、受限详情 DOM 与既有图片 profile 映射为 Atlas 新 `CollectedProductPayload`。 - `/collection-test/{collectionItemId}` 现单独展示商品描述、类目路径、全部特征表、规格轴、SKU 草稿、素材候选,并保留规范化载荷和原始证据,供解析核对。扩展解析器测试覆盖 JSON-LD 描述兜底、完整特征合并和图片白名单。 ### 1.19 开发进度(2026-09-09):M3.0 商品编辑领域基础 - 新增迁移 `018_product_editor_foundation.sql`:采集期最小 `products` / `product_variants` 扩展为可编辑的产品聚合,并建立平台发布配置、SKU 平台 listing、平台属性及未映射属性草稿表。所有表启用并强制工作空间 RLS。 - `product_variant_platform_listings` 同时保存 `product_id`,以 profile→product 和 variant→product 两条复合外键强制同商品归属;数据库会拒绝同一工作空间中“商品 A profile + 商品 B SKU”的错误组合。属性和未映射属性也受 profile/variant 与 product 的复合外键约束。 - 已实现 `POST /api/collection-items/{id}/editor-contexts`:只有当前页头目标平台存在已验证默认门店时,才原子取得或创建产品的该平台 profile,并为现有 SKU 建立初始 `not_listed` listing。`GET /api/products/{id}/editor` 返回产品、profile、SKU listing、属性和待处理未映射属性的只读快照。 - `products.version` 已迁移为统一命名的 `revision`;新增共享版本冲突辅助。M3.1 的完整草稿写入仍必须以 `product.revision + profile.revision` 条件更新为边界,并覆盖并发保存。 - API 集成测试覆盖默认店铺前置、上下文幂等复用、读模型、跨商品外键拒绝和未映射属性读回。正式编辑 Web、草稿保存、类目切换、素材选择、定价与发布尚未开始。 ### 1.20 开发进度(2026-09-09):M3.1 商品草稿保存与正式编辑入口 - 已实现 `PUT /api/products/{id}/editor-draft`:产品基础资料、当前平台标题/描述/标签/履约方式,以及完整 SKU 集合和未上架 listing 的 `offer_id` 在同一事务保存。新 SKU 以 `client_key` 回传稳定 ID;省略现有 SKU 会软删除 Atlas 草稿,绝不触发平台下架。 - 请求必须携带 `If-Match: "{product_revision}:{profile_revision}"` 与 `Idempotency-Key`。服务端先后执行两个带 revision 条件的更新;任一步冲突或 SKU/货号校验失败都会回滚整笔保存。店铺内货号重复会返回可展示的 `422`,重复请求键安全重放原响应。 - Web 的采集箱“编辑上架”和手动创建现会创建编辑上下文后进入 `/products/{id}/editor`。正式页可编辑产品基础资料、当前平台文案及 SKU/货号,遇到版本冲突会重新加载最新快照。 - 此批刻意不展示未具备平台 schema 或发布校验的数据:类目/属性切换与未映射属性恢复、规格继承、候选媒体物化/选图、定价确认、AI 和首次 Ozon 导入仍待后续纵向任务。 ### 1.21 开发进度(2026-09-10):复采与编辑草稿 P0 修复 - 重复采集不再删除 `product_variants`。未进入编辑器前,SKU 仍可作为最新来源快照投影更新;一旦任一 `product_platform_profile` 存在,复采只更新来源证据/规范化载荷/候选素材,并按稳定 `source_variant_key` 复用既有 SKU ID,不覆盖 Atlas 草稿字段或已建平台 listing。 - 复采新发现的来源 SKU 会为该商品全部现有平台 profile 建立独立 `not_listed` listing;来源不再返回的 SKU 不会被自动删除或恢复。这样既保留用户编辑和发布审计,也不会以级联删除绕开 SKU 与 profile 的同商品归属约束。 - 扩展集成测试已覆盖“进入编辑、保存人工 SKU 字段与 offer 后再次采集”的场景,并确认原映射保留、新 SKU 可编辑。完整 API 集成测试通过。 ### 1.22 开发进度(2026-09-10):M3.2 商品编辑核心 - 编辑快照与 `PUT /api/products/{id}/editor-draft` 已覆盖父商品实物/包装规格、SKU 覆盖规格、有效继承规格、规格轴值、每个 SKU 当前平台状态、以及该平台全部活动且凭据有效的目标店铺候选。 - 切换目标店铺仍通过同一原子草稿保存:仅 `not_listed` / `failed` 等未导入 listing 改为新店铺;`imported` SKU 永远保留其首次导入的店铺映射。规格确认状态为 `confirmed` 时服务端要求相应四项尺寸/重量完整,数值必须大于零。 - Web 编辑页新增父/子规格编辑、规格轴值、复制 SKU、已导入 SKU 删除影响提示、目标店铺选择、上架状态摘要,以及路由离页与浏览器关闭前的未保存修改确认。类目/动态属性、素材选择、定价、AI 与首次发布仍未开始。 - 商品编辑集成测试覆盖规格保存/继承、复制 SKU、店铺切换与已导入 SKU 映射保留;Web production build 通过。 ### 1.23 开发进度(2026-09-10):M3.3 编辑器数据闭环 - 新增迁移 `019_product_editor_ai_source_material.sql`,在商品聚合中持久化结构化 `ai_source_material`。未保存时由当前规范化采集载荷初始化;保存后作为用户整理稿保留,后续复采不覆盖。原始 `normalized_payload` / `raw_evidence` 不被编辑器修改。 - `GET /api/products/{id}/editor` 直接返回 `collection_item_id`、`source_material` 和服务端组装的 `ai_input`,Web 不再扫描采集箱前 100 条记录寻找来源。`ai_input` 同时包含父商品默认实物/包装规格,以及每个 SKU 合并继承后的有效规格和规格轴。 - `PUT /api/products/{id}/editor-draft` 将 AI 来源素材与商品、平台文案、SKU 和规格原子保存,并继续使用 product/profile 双 revision 与幂等键。右侧来源标题和描述的修改现可持久化;未来 AI 动作必须先保存草稿,再从服务端读模型创建任务。 - 商品编辑集成测试覆盖来源载荷初始化、用户素材保存、原始证据不变、复采不覆盖整理稿,以及父/SKU 有效规格进入 AI 输入。 ### 1.24 开发进度(2026-09-10):M3.3.1 来源素材字段级合并 - 新增迁移 `020_product_ai_source_overrides.sql`:将 M3.3 的整份 `ai_source_material` 草稿演进为 `ai_source_material_overrides` 与 `ai_source_material_base_revision`。服务端以最新规范化采集数据为底稿,只叠加用户明确编辑过的顶层字段。 - 编辑快照新增 `source_material_state`:同时返回最新来源、覆盖字段、复采后发生分歧的字段与基线版本。右侧“采集的商品信息”展示合并后的有效值;未编辑字段随复采自动更新,已编辑字段保留,并支持逐项恢复或全部采用最新。 - 保存请求新增 `ai_source_overridden_fields`。来源版本必须与服务端当前版本一致;保存过程中发生复采返回 `409 SOURCE_SNAPSHOT_CHANGED`,防止旧页面覆盖新证据。原始 `normalized_payload` / `raw_evidence` 仍保持只读。 - AI 输入继续由服务端组装:来源部分读取字段级合并后的有效素材;规格部分读取用户在左侧保存的父商品规格、SKU 覆盖值及其继承后的有效值。 ### 1.25 开发进度(2026-09-10):M3.4 类目与动态属性 - 旧迁移 `021_product_category_attribute_schema.sql` 建立了平台级 schema 缓存;当时采用 `022_store_catalog_cache.sql` 另建门店级类目、schema 与字典缓存,发布草稿只引用门店级数据。此处记录已实现历史,2026-09-18 的新目标见[平台级参考目录决策](../architecture/platform-product-reference-catalog.md)。 - 新增编辑器接口:类目候选、类目属性 schema 与属性字典值。类目 schema/字典接口支持传入未保存的 `category_id` 预览,因此用户选择新类目后不必先保存一次才能填写字段。 - 编辑器草稿可原子保存 `store_product_category_id`、类目来源与商品/SKU 属性;服务端派生 `platform_type_id`,复核类型、值数量、字典值与 SKU 归属。类目变更或 schema 下线的旧属性会转入 `product_unmapped_attributes`,不会默默丢失或进入新的发布 payload。 - Web 编辑页已提供类目选择、商品/SKU 维度动态字段、字典选择、未映射属性告警;上架店铺/主题标签保持标准 40px 控件高度,父商品包装重量与三项包装尺寸宽度分别为 150px / 120px。 ### 1.26 开发进度(2026-09-10):M3.5 Ozon 类目与属性缓存同步 - 已实现类目树、类目属性 schema、字典值缓存的数据表、Worker 同步逻辑、Transactional Outbox 和读 API;缓存属于门店级发布元数据,不属于某个商品草稿。浏览器、operation 响应与 outbox 事件都不含 API Key。 - 当前旧实现仍把“同步 Ozon 类目”暴露为商品编辑页操作;这已被最新产品决策否定,不能作为完成状态使用。属性 schema 仍是按类目读取/按需同步的现状,后续需单独确认是否也改为启动预热。 - 字典不会全量拉取:字段先读取已有缓存;首次打开某个字典属性时,按需从 Ozon `/attribute/values` 按游标同步首批最多 1000 项,当前类目下同一字典仅自动触发一次。输入至少两个字符后延迟调用 Ozon `/attribute/values/search`,补充首批以外的候选;本地缓存每页返回 200 项并支持下拉滚动续读。同步失败应保留旧缓存和可读错误状态。 ### 1.27 历史状态(2026-09-10):平台级预热方案已废弃 - 原交接要求的“Worker 启动时以指定门店预热一份平台共享类目树”当时被门店级方案替代。该时期要求类目、属性和字典按门店隔离,不恢复平台级共享缓存或以环境变量任意指定客户门店作为共享源。 - 保留的原则是:浏览器、日志、operation 和 outbox 事件均不携带 API Key;编辑器只读缓存,不提供类目树手动同步入口。 - 本节只记录当时决策;2026-09-18 的[平台级参考目录决策](../architecture/platform-product-reference-catalog.md)已取代该目标。现有门店级实现未随文档决策自动迁移。 ### 1.28 开发进度(2026-09-10):门店类目同步与来源回填 - Ozon 门店凭据验证成功后,在同一事务中创建门店类目同步 operation 与 outbox 事件;Worker 使用该门店 `Client-Id + Api-Key` 请求中文类目树,并写入 `store_product_categories`。失败只标记类目同步 operation,不会把已验证的凭据改为无效。 - 已删除编辑器的手动类目树同步接口和客户端调用。属性 schema 与字典值仍按当前类目按需同步;字典支持打开控件时同步首批值和按关键词搜索,均使用原有 Outbox/Worker 链路。 - Cascader 显示完整缓存树路径,但表单与草稿 API 始终保存末级类目 UUID,修复了路径数组不能直接提交到 `editor-draft` 的问题。 - 对 Ozon 来源商品,进入或读取编辑器时会将 `common.categories` 的规范化层级路径与活动缓存节点精确匹配;仅当 profile 未保存类目时写入匹配项并标记 `source_capture`。已有手工选择或此前保存的类目永远不被复采覆盖。API 集成测试覆盖回填和保护规则。 - 已新增 API 集成测试覆盖来源回填和保护规则;仍需在有效 Ozon 门店、真实 Ozon 样本与浏览器页面上执行最终回归。本地 Docker 基础设施未启动时,该集成测试无法实际执行。 ### 1.29 当前完成情况与下一步(2026-09-10) 已完成: - Cascader 使用完整路径显示、末级 UUID 保存;初始回显、手动切换和服务端门店归属校验均已接入。切换目标门店会清空旧门店类目及动态属性,再加载新门店缓存。 - 门店验证成功后在同一事务创建 `store_category_tree_sync` operation/outbox;Worker 用该门店 `Client-Id + Api-Key` 和 `ZH_HANS` 拉取树。BullMQ 任务使用 5 次指数退避重试;同步失败写 operation,不使已验证门店变为无效。 - 编辑器旧手动类目树同步接口及客户端调用已删除;来源面包屑自动匹配只在空类目时写入 `source_capture`,不会覆盖手选或已保存选择。 - Contracts、API、Worker、Web TypeScript 检查和 Web 生产构建已通过;`git diff --check` 已通过。 未完成 / 下一步: 1. 为 `store_category_tree_sync` 增加门店级去重:已有有效缓存跳过;同一门店的并发验证/重复事件只允许一次实际 Ozon 请求。 2. 新增 Worker 集成测试,覆盖空缓存首次同步、缓存跳过、并发互斥、Ozon 失败后的指数重试和 API Key 不进入队列/日志/数据库。 3. 启动本地 PostgreSQL 与 Redis,执行 `022_store_catalog_cache.sql`,运行 API 集成测试并检查 RLS、外键和历史 profile 兼容性。 4. 使用真实已验证 Ozon 门店和样本,在 `http://localhost:8100` 回归:类目树回显、切换店铺、保存 UUID、来源回填、不覆盖手选类目以及属性/字典按需同步。 ### 1.30 开发进度(2026-09-11):M3.5 类目缓存可靠性收口 - 已使用专用 migrator 实际执行 `022_store_catalog_cache.sql`;三张门店类目缓存表均已创建,并实际检查了强制 RLS、`product_platform_profiles` 的门店类目外键及旧 `platform_product_category_id` 列的兼容保留。 - 门店凭据验证完成后,树同步调度现在以已锁定的门店行为临界区:已有 `synced_at` 缓存直接跳过;同一凭据的并发验证或重复事件只保留一条 queued/running 同步;凭据替换会取消旧任务并为新密钥安排新任务。队列载荷始终仅含门店、密钥记录、operation 与平台 ID,不含 API Key 明文。 - 新增 `apps/api/test/worker-catalog-scheduler.e2e.test.ts`,在真实 PostgreSQL 上覆盖空缓存的并发互斥、缓存跳过、无明文密钥 outbox 载荷,以及 BullMQ 的 5 次指数退避配置。现有 `product-editor.e2e.test.ts` 同时修复了 Ozon 来源测试数据必须将 `source_kind` 设为 `platform` 的约束问题。 - 已执行 contracts、API(含测试)、Worker TypeScript 检查,`product-editor.e2e.test.ts` 与 Worker 调度测试共 10 项通过,Web production build 通过。 - 真实浏览器回归:重新验证一个已有 Ozon 门店后,类目树同步 operation 成功,写入 568 个门店级类目;编辑器 Cascader 能加载完整树。再次验证同一门店后,类目树 operation 仍只有 1 条,确认有效缓存不会触发重复同步。已采集样本的公开来源层级与 Seller API 树不完全一致时,不会错误自动落类目,仍保留人工选择或 AI 推荐入口;这符合“仅精确路径匹配才自动回填”的保护规则。 M3.5 与 M3.6 均已收口。真实来源回归中若要提升 Ozon 自动类目命中率,需先确认是否可使用稳定的 Ozon 类目 ID 映射,不能用名称模糊匹配替代。 ### 1.31 开发进度(2026-09-11):M3.6 候选素材选择与物化 - 新增迁移 `023_product_listing_media.sql`:最终图片归属 `SKU × 平台 listing`,而不是来源商品或门店全局;复合外键强制同一工作空间、商品、profile 与 SKU 的归属。每个 listing 最多 15 张,素材和排序均不可重复,表启用强制 RLS。 - 编辑器快照返回每个 SKU 已选的 Atlas 图片素材;草稿请求的 `media_asset_ids` 是最终顺序。服务端只接受当前采集快照中已经成功转存、且关系为该 SKU 或商品通用的候选。旧的已选资产在复采后仍可保留,不会因来源快照更新而丢失。 - 保存时二次校验 JPG/PNG/WebP、20MB、10000px、主图短边至少 700px、最多 15 张及去重。候选 `source_url` 只用于页面预览,发布事实永远是 `media_asset_id`,调用方无法引用任意工作空间素材。 - Web 在展开 SKU 后按来源关系显示候选图:`pending_selection` 显示“转存后选择”,已转存候选显示“选择 / 已选择”,选中后主图立即跟随第一个素材。复制 SKU 默认不复制来源绑定图片,避免把原 SKU 的专属候选误带到新 SKU。 - 已实际执行 `023`;`product-editor.e2e.test.ts` 8 项通过(含候选→正式素材顺序保存和读回);contracts、API、Web TypeScript 检查与 Web production build 通过。ego-browser 验证了真实编辑器的 SKU 展开、候选显示、选择状态和主图即时回显。浏览器手工草稿因未填类目、品牌、型号和包装规格被既有表单校验阻止提交;持久化路径由集成测试完成验证。 下一步:在真实采集来源中回归首次按需物化、Worker 下载失败重试与已选图片保存;然后实施定价确认。来源域限流、429/503 延迟重试和跨候选 URL 下载去重属于 Worker 下载器的后续可靠性加固,尚未在本 M3.6 变更中完成。 ### 1.32 开发进度(2026-09-18):Ozon 动态属性与 SKU 表单 P0 - 编辑器选择类目后立即加载属性 schema。`is_required=true` 的商品级属性始终展示在「产品属性」,`is_aspect=true` 的必填属性始终展示在对应 SKU 卡片;必填状态仅使用 Label 前的红色 `*`,选填属性仍可折叠。 - 动态属性控件已拆为独立组件,统一处理字典、布尔、数值、文本和多值字段;商品级与 SKU 级属性只负责各自的分区编排。主要信息继续保留类目、标题、型号、品牌和默认包装规格,SKU 展开区可覆盖包装及商品规格。 - 草稿保存不会因 Ozon 必填属性或包装规格为空而失败;已提交的非空属性仍由 API 校验类型、数量、字典归属、类目归属及 `is_aspect` 对应的商品/SKU scope。 - 新增 `GET /api/product-platform-profiles/{profile_id}/validation`,在独立校验模块中返回商品级问题和逐 SKU 问题。已导入 SKU 标为 `not_publishable_again`;该阶段的必填通用属性和必填变体属性当时以门店类目 schema 为准,现已由 1.36 的第 3 步迁移为平台 schema。 - 类目 schema 新增 `editor_field` 语义绑定,严格按 Ozon 属性 ID 识别品牌 `85`、型号名称 `9048`、类型 `8229`、主题标签 `23171`、JSON 富内容 `11254` 和简介/产品描述 `4191`。这些字段由编辑器已有控件承载,动态属性区不再重复展示或预取字典;名称和翻译文本不参与判断。 - Contracts、API 与 Web TypeScript 检查、Web production build 和 `git diff --check` 已通过。API 集成测试已补充通用/逐 SKU 必填缺失和 scope 伪造场景;本轮环境无法连接 PostgreSQL/Redis,因此未实际跑通该集成测试,且未按约定启动 API 或 Worker。 后续 P1/P2 仍包括:新 SKU 通过 `client_key` 在首次保存时关联动态属性、SKU 级条形码、结构化字典值,以及首次发布任务复用 validation 并回填 Ozon 错误。详见 [`ozon-dynamic-attributes-and-sku-form-plan.md`](../product/ozon-dynamic-attributes-and-sku-form-plan.md)。 ### 1.33 开发进度与交接(2026-09-18):平台级商品参考目录、系统后台与同步维护 #### 1.33.1 已冻结的数据归属 - 类目/商品类型树、类型属性定义和字典候选值归属 `platform_id`,同一平台的全部门店共用,不再按门店复制。Ozon 与未来 Wildberries 等平台分别使用自己的适配器和外部 ID 语义;本阶段**不创建、不读取门店差异层**。 - 门店连接、卖家凭据、商品/SKU 已填属性、发布状态和错误仍是工作空间业务数据。平台参考目录不保存客户商品、价格、库存或发布事实。 - 商品编辑器和首次发布预检已由迁移 `027` 与新版 API 切到平台级类目 UUID、属性和字典缓存。`022_store_catalog_cache.sql` 的门店表与列暂时保留为兼容/回滚数据;真正的 Ozon 发布 job 复用预检服务并完成稳定性验证前不得删除。 #### 1.33.2 类目树、属性和字典的标识规则 | 参考数据 | Atlas 表与唯一作用域 | Ozon 字段及处理规则 | |---|---|---| | 类目/类型树 | `platform_product_categories`;`platform_id + external_category_id + external_type_id` | `external_category_id = description_category_id`,`external_type_id = type_id`。类型节点可能不重复返回类目 ID,映射器会继承最近父类目的 `description_category_id`。纯目录节点使用 `type_id = 0`,只用于树结构,不允许作为建品类型;可发布类型必须同时使用 `description_category_id + type_id`,不能按名称或单独 ID 匹配。`parent_id` 和 `path` 由树同步统一重建,Ozon `disabled` 或本次全量树中消失的节点标为非活动,不物理删除。 | | 类型属性定义 | `platform_product_category_attributes`;平台 + 类型节点 + `platform_attribute_id + complex_id` | 保存 Ozon 属性 `id`、`attribute_complex_id/complex_id`、名称、说明、分组、类型、`is_required`、`is_aspect`、`is_collection`、`max_value_count`、`dictionary_id` 和 `category_dependent`。属性同步是某一 `description_category_id + type_id` 的完整定义;成功后本次未返回的旧属性标为非活动。 | | 字典候选值 | `platform_catalog_dictionary_values`;`attribute_id + external_value_id` | 调用字典接口时必须同时传 `attribute_id + description_category_id + type_id`。即使 Ozon 返回相同 `dictionary_id`,也不能只按字典 ID 跨类型合并。当前每页 2000 项,单任务最多读取 5 页;不足 5 页且 `has_next=false` 才记录 `is_complete=true`,只有完整同步才停用本次未返回的旧值。 | 商品编辑器隐藏品牌 `85`、型号名称 `9048`、类型 `8229`、主题标签 `23171`、JSON 富内容 `11254`、简介/产品描述 `4191` 时,仍严格使用 Ozon 属性 ID;名称只用于展示。管理后台是参考数据检查工具,会展示完整属性定义,不应用编辑器的隐藏规则。 #### 1.33.3 数据库和安全边界 迁移 `024_platform_catalog_management.sql` 已新增或扩展以下对象: - `system_admin_grants`:独立的全局系统管理员授权。工作空间 `owner/admin` 不等于系统管理员;`atlas_app` 被撤销该表的写权限,不能通过普通 HTTP 接口自助提权。 - `platform_catalog_sources`:每个平台一份专用参考源。Ozon `Api-Key` 使用现有 AES-256-GCM 边界加密,随机 nonce 与不可逆 HMAC fingerprint 分开保存;响应只返回脱敏 Client-Id,不回显密钥、密文或 fingerprint。 - `platform_catalog_dictionary_values`:替代旧平台字典表不足以表达类目/属性作用域的问题;旧表暂不删除。 - `platform_catalog_sync_runs`:平台级树、属性和字典任务事实,记录触发方式、状态、来源版本、尝试次数、结果数量和脱敏错误。 - `platform_catalog_sync_requests`:把系统管理员的 `Idempotency-Key` 持久映射到任务;即使手动请求复用了一个已存在的定时任务,重试也不会在原任务完成后创建第二个任务。 - `platform_catalog_outbox_events`:不带 `workspace_id` 的平台级 Transactional Outbox。平台任务不能借用租户 `operations/outbox_events`,避免伪造工作空间归属。 配置来源时,API 在同一事务更新密文和 `revision`,取消旧来源尚未完成的任务,并排队一次树同步。Worker 在领取和提交结果时都会比较 `source_revision`;来源已经更换的旧任务只能取消,不能用新凭据执行或覆盖新数据。API Key 只在 API 加密入口和 Worker 解密调用 Ozon 的短暂内存中出现,不进入浏览器回显、任务载荷或日志。 #### 1.33.4 系统管理员后台 - 独立后台路由为 `/system/platform-catalog`,布局不依赖工作台的门店选择器。普通登录用户可调用 `GET /api/system/access` 获取布尔值;所有管理读写 API 还会在服务端实时校验 `system_admin_grants`,前端隐藏菜单不是权限边界。 - 页面名称为「平台类目与属性」。可选择平台、查看参考源状态和类目树最近成功时间;选择可发布类型后查看属性 ID、必填、SKU 变体、数据类型及已缓存候选值数量;字典在 Drawer 中分页搜索 Atlas 缓存并显示完整/部分状态。 - 「配置同步来源」提交 Client-Id 与 Api-Key。首次配置或实际变更会自动排队树同步;重复提交同一凭据不增加来源版本或重复任务。 - 「同步类目树」「同步当前类型属性」「同步此字典」都只向 API 排队,不在 HTTP 请求中直接访问 Ozon。最近任务表每 5 秒轮询一次;任务由 `queued → running → succeeded/failed/cancelled` 转换,完成后页面重新读取相应缓存。 - 管理 API 契约、首位管理员初始化 SQL 和错误边界见 [`platform-catalog-admin.md`](../api/platform-catalog-admin.md)。目前数据库已经存在一名有效系统管理员授权;此前反馈过用户菜单没有显示入口,数据库授权已确认正确,但刷新/当前 Web 与 API 进程版本下的菜单展示仍需浏览器回归。 #### 1.33.5 手动同步与自动同步的共同链路 ```text 系统管理员页面 / 定时调度器 ↓ 同一事务创建 run + platform outbox PlatformCatalogDispatcher(每 2 秒,批量最多 20 条) ↓ 投递 atlas-dispatch / platform_catalog_sync.requested BullMQ Worker(最多 5 次,5 秒起指数退避) ↓ 使用平台参考源凭据调用 Ozon 事务内 upsert 参考数据并完成 run ↓ 管理页面轮询任务并重新读取 Atlas 缓存 ``` - 手动接口要求 UUID `Idempotency-Key`。同一平台和 `scope_key` 只允许一个 `queued/running` 任务;已有活动任务时返回并映射到该任务。 - Worker 启动时立即执行一次调度,之后每 60 秒扫描一次。每个平台最多保留 6 个活动任务,优先级依次为树、属性、字典;树和属性成功后 24 小时内不重复,字典成功后 7 天内不重复,任一作用域创建任务后 1 小时内不会被定时器重复排队。 - Ozon 单次 HTTP 超时为 30 秒。401/403 转换为凭据错误;429、5xx、网络错误和响应结构异常交给 BullMQ 重试;其他确定性 4xx 直接记录请求被拒绝。最终失败只更新任务错误,不清空上次成功缓存。 - 树、某类型的属性和某属性的字典分别在单个数据库事务中发布。读取方在事务提交前仍看到旧缓存;P0 尚未实现不可变版本快照、内容哈希和 `next_refresh_at`,当前依赖事务 upsert、`synced_at` 与任务历史保证一致性和可追踪性。 #### 1.33.6 当前实际进度快照 - `024_platform_catalog_management.sql` 已于 2026-09-18 实际应用,系统管理员授权已写入并回读验证。 - Worker 曾因先于 `024` 迁移启动而无法初始化平台 Outbox Dispatcher;迁移后 Worker 已恢复。最新数据库事实显示 Outbox 已被分发、任务 `attempt_count=1`,证明平台 Dispatcher、BullMQ 消费和 Ozon 调用链路当前正在工作。 - 两次 Ozon 类目树任务均已成功,每次映射结果为 7933 条;当前活动节点为 7365 条,差额主要是纯目录/禁用节点。定时属性预热已经开始:检查时 18 个属性任务成功,当前活动属性定义 436 条,单类型返回约 27~50 个属性。 - 字典候选值当前仍为 0,尚未进入有效的初始预热阶段。现有调度固定优先全部属性,再处理字典;在数千个类型下可能导致字典长期排在属性之后,这是已知的 P0 调度公平性问题,不能把“属性同步已开始”误认为字典已经可供全部用户使用。 - 已通过 API/Worker/Web TypeScript 检查、Web production build、3 项系统管理员/参数单元测试、6 项 Ozon 树/属性/字典映射与分页测试及 `git diff --check`。由于功能落地后才执行迁移,尚未新增并运行覆盖真实 PostgreSQL 的平台目录端到端测试;现有成功任务提供了真实 Ozon 链路验证,但不能替代自动化数据库回归。 #### 1.33.7 明确未完成项 1. 调整定时调度公平性:树之后不能等待全部类型属性完成才开始字典;应在属性和字典之间设置配额/轮转,并优先近期使用或管理员选中的类型。 2. 为超过 10000 项的大字典实现可续传游标或分片同步周期。当前任务会正确标记 `is_complete=false`,但下一次仍从头读取,不能持续推进到第 10001 项以后。 3. ~~将商品编辑器由门店级缓存迁移到平台级目录。~~ 已由第 3 步完成,发布预检也已迁移;后续只在真正的 Ozon 发布 job 复用预检并完成稳定性验证后讨论清理 `022` 表。 4. 增加平台目录 PostgreSQL 集成测试,覆盖系统管理员隔离、凭据版本替换、手动幂等复用、定时去重、失败保留旧缓存、完整字典停用旧值及跨平台 ID 拒绝。 5. 在浏览器完成管理员菜单、直达路由、凭据弹窗、任务轮询、类目/属性/字典展示的回归,并确认 API/Worker 进程都使用迁移后的当前代码。 完整架构边界见 [`platform-product-reference-catalog.md`](../architecture/platform-product-reference-catalog.md),HTTP 契约见 [`platform-catalog-admin.md`](../api/platform-catalog-admin.md)。 ### 1.34 开发进度与交接(2026-09-20):平台目录刷新方案第 1 步 第 1 步后端基础已完成,尚未开始系统后台页面、商品编辑器和发布链路迁移: - 新增并实际应用 `025_platform_catalog_refresh_policy.sql`、`026_platform_catalog_refresh_policy_audit.sql`:按平台保存类型属性/字典的 14/21 天默认策略及不可变的阈值修改前后值;按 tree、attributes、dictionary、dictionary_search 保存独立资源状态;为同步 run 增加 high/low 优先级和提升时间;增加管理员属性批次及固定类型快照。没有创建门店级新表,也没有删除旧缓存。 - 迁移从历史成功 run 回填资源状态。应用后实际回读:Ozon 共有 7365 个活动可发布类型,其中 687 个类型属性 fresh、6678 个 missing,stale/expired 均为 0;另回填 1 个树状态和 1 个字典状态。默认策略已为所有现有平台写入 14/21 天。 - 平台目录调度器已改为只自动维护每周类目树。类型属性和字典不再进入周期自动候选;属性只从管理员批次中每个平台最多 6 个活动任务地逐步领取,字典保留单项任务。管理员批次支持 `missing_expired`、`missing_stale_expired`、`all` 三种冻结范围。 - 新增刷新策略、属性状态汇总、批次创建/列表/详情管理 API。相同平台一次只允许一个活动属性批次;空批次返回明确错误;批次允许部分失败。现有树/单属性/单字典接口保持兼容。 - 平台 run 和独立 BullMQ 队列 `atlas-platform-catalog` 已支持 high/low 优先级;missing/严格超过过期阈值为 high,恰好 21 天仍为 low。低优先级 queued 任务可提升并复用同一 run,不能打断已经 running 的请求;旧 `atlas-dispatch` 暂时保留消费历史平台任务的兼容分支。 - 平台同步成功会在同一发布事务更新 `last_success_at/is_complete` 并清除错误;失败只记录资源错误,不延长新鲜期或清空上次成功缓存。字典管理读取已改用资源状态判断完整性。 - 已通过 API/Worker/Database TypeScript 检查、5 项系统管理员参数单元测试和 9 项 Worker 测试;迁移已在真实 PostgreSQL 应用,并用回滚事务验证批次/平台外键。`git diff --check` 通过。 运行态提醒:维护者管理的 API/Worker 进程不会由 AI 重启。若当前 Worker 是修改前启动的旧进程,它仍会执行旧的 24 小时属性候选;需要维护者在检查代码后自行重启 API 和 Worker,新的每周树/无属性字典周期调度才会生效。 第 1 步已经用户确认,随后完成的第 2 步见下一节;此前已有的类目树固定搜索框和展开修复已保留。 ### 1.35 开发进度与交接(2026-09-20):平台目录刷新方案第 2 步 第 2 步系统后台页面已完成,尚未开始商品编辑器和发布链路迁移: - 「平台类目与属性」页已接入平台刷新策略,可配置类型属性和属性字典各自的新鲜/过期天数;类目树固定 7 天。表单执行 `1..365` 和“过期严格大于新鲜”校验,保存说明明确不会立即创建全量任务。 - 缓存新鲜度表单现已从主页面移入弹窗;操作区通过“缓存新鲜度设置”按钮打开。保存成功后关闭弹窗并刷新策略、类目及当前属性状态,保存失败时保留弹窗和用户输入。 - “最近同步任务”的手动刷新按钮现显示请求中的 loading 状态;刷新成功提示“同步任务刷新成功”,失败使用统一 API 错误消息。后台每 5 秒静默轮询仍保持无提示,避免周期性打扰管理员。 - 类型属性区展示 fresh/stale/expired/missing 汇总以及最近批次。三个操作分别覆盖 missing+expired、missing+stale+expired、全部类型;前两个显示命中数并在 0 时禁用,“同步全部类型属性”为红色危险按钮,必须在含类型数量和耗时提示的二次确认后才请求 API。 - 批次表展示完成比例、成功/运行/等待/失败数量与最终状态;同平台存在活动批次时禁用新建。页面每 5 秒静默刷新任务和批次,不让操作按钮周期性闪烁加载态。 - 类目树保留此前完成的固定搜索框和手动展开修复,并在可发布叶子展示类型属性缓存状态。选择类型后,属性卡标题重复显示类型状态;字典属性在表格和候选值 Drawer 中展示独立缓存状态、最近成功时间、完整/部分状态及候选值数量,仍保留单字典手动同步。 - 最近任务表新增 high/low 优先级以及 weekly、admin_batch、user_access、publish 等触发来源文案。页面主文件只协调状态和请求;刷新策略、属性批次、缓存标签、任务表均拆为独立组件。 - API 的类目和属性读模型补充最近资源错误及字典新鲜度字段,用于后台观测;没有改变普通商品编辑 API。 - 浏览器实测:页面无横向溢出;类目搜索可自动展开到匹配叶子;missing/fresh 类型状态正确;fresh 类型属性表和 missing 字典 Drawer 正常;全量同步仅打开危险确认层,验收过程没有创建批次或同步任务。 验证:Web TypeScript 与 production build、API TypeScript/build 和 5 项管理 API 单元测试通过,`git diff --check` 通过。页面在维护者现有 API 上完成浏览器回归;Vite 验收服务使用 5174 并在验收后关闭。API/Worker 未由 AI 启动、重启或停止。 第 2 步已验收,第 3 步完成情况见下一节;第 4 步也已在用户确认后完成,见 1.37。 ### 1.36 开发进度与交接(2026-09-20):平台目录刷新方案第 3 步 - 新增并实际应用 `027_product_editor_platform_catalog.sql`。迁移按 `platform_id + description_category_id + type_id` 将既有 `store_product_category_id` 回填到 `platform_product_category_id`,同步回填未映射属性的原平台类目;旧门店列和三张门店缓存表继续保留,新保存会尽量反写对应门店类目 UUID,便于回滚。 - 商品编辑器类目树、类型属性和字典全部改读平台级表。切换同平台目标店铺不再清空类目;动态属性 schema 返回 `attribute_id`,字典 API 用该平台属性 UUID 限定候选值,不再只按可能重复的 `dictionary_id` 定位。 - 普通读取已实现 14/21 天状态机:fresh 直接返回;stale 返回旧缓存并创建/复用 low `user_access` run;expired/missing 创建/复用 high run,API 最多等待 5 秒,未完成则返回 HTTP `202`、空当前选项和 cache 元数据。前端最多轮询 20 次,过期旧值不进入当前控件。 - 平台级部分字典的本地未命中关键词现在使用独立 `dictionary_search:{attribute_id}:{hash}` 作用域。`platform_catalog_sync_runs` 保存规范化 `search_key`;Worker 调 Ozon 搜索接口,命中值先写入 Atlas,再更新该词的成功状态,不会把局部搜索误标为完整字典。 - 草稿保存和本地 validation 已改用平台 schema/字典。expired/missing schema 下,只允许原样保留已有属性并保存其他商品字段;类目变化或属性改写会返回刷新中错误。expired/missing 字典不能校验新候选值,旧草稿值仍可保留。 - 产品 API 契约更新为 `platform_product_category_id`,过渡期仍接受旧 `store_product_category_id` 并通过外部 ID 映射。属性/字典响应统一返回 `cache.status`、最近成功时间、fresh/expire 时间、完整性、刷新 run 和重试间隔。 - 验证已通过:contracts/API/Worker/Web TypeScript;`product-editor.e2e.test.ts` 与 `platform-catalog-admin.unit.test.ts` 共 13 项;迁移在本地 PostgreSQL 成功执行;`027` 约束和旧引用回填已实际验证。API/Worker 未由 AI 启动、重启或停止。 第 4 步首次发布预检已完成,见下一节。旧门店目录表暂不清理;需要等待真正的 Ozon 发布 job 复用预检服务并完成稳定性验证后另行决策。 ### 1.37 开发进度与交接(2026-09-20):平台目录刷新方案第 4 步 - 新增 `POST /api/product-platform-profiles/{profile_id}/publish-preflights`。请求明确给出本次待首次上架的 `variant_ids`;只接受当前 profile 下 `not_listed` / `failed` 的未删除 SKU,拒绝 imported、submitting、跨商品和已删除 SKU。 - 发布预检先检查当前 `platform_product_category_id` 的 schema。fresh 直接使用;stale 使用缓存并创建/复用 low `publish` 任务;expired/missing 创建/复用 high `publish` 任务。低优先级 queued 任务在资源过期后复用原 run 并提升为 high。 - schema 可用后,只从本次 SKU 集合的已确认商品/SKU 属性草稿提取实际使用的字典。未填写的可选字典、其他 SKU 和同类型下未使用字典都不会创建任务;不扫描其他类型,也不使用旧门店目录。 - 多个 high schema/字典任务共用一个最多 5 秒等待窗口。schema 刷新成功后会基于新 schema 再计算字典依赖;窗口内未完成返回 HTTP `202` 和 `status=refreshing`,客户端可重试同一预检,不能退回过期缓存。 - 最终本地校验除标题、型号、品牌、必填属性、offer ID、价格、包装和图片外,新增“所用字典缓存未过期且候选 ID 仍为活动值”检查。stale 字典可以完成本次发布校验并在后台刷新;expired/missing 会返回刷新中问题。 - 预检响应包含 schema 与每个已用字典的统一 cache 元数据,以及本次创建/复用的目录 runs。真正的 `product_publish_job`、发布快照、Ozon `/v3/product/import` 调用和轮询仍未实现;预检 ready 不能解释为已提交或已发布。 - 代码按目录编排、最终校验、HTTP 入口拆分在独立组件中,没有把逻辑继续堆入 `product-editor.service.ts`。API 集成测试覆盖 stale 的 low publish 任务、expired 的 high 提升、只刷新已用字典和不触达未用字典。 - 当前边界:预检只校验 profile 引用的平台类目仍存在且活动,并刷新该类型 schema/实际使用字典;尚未检查整棵类目树的缓存新鲜度,也不会因树过期创建树任务。本地类目无效时返回 Atlas 自己的 `422 CATEGORY_INVALID`,不是 Ozon 响应。真正发布 job 接入前需补齐或确认此边界。 ### 1.38 文档同步与当前交接结论(2026-09-20) - 已把平台级目录、第 1~4 步刷新方案、发布预检、缓存设置弹窗和最近任务刷新反馈同步到交接、架构、商品编辑、管理 API 与 Ozon 集成文档。 - 当前事实统一为:类目树每 7 天自动同步且支持管理员手动同步;类型属性和字典不做周期性全量扫描;普通访问和发布预检按 fresh/stale/expired/missing 创建或复用 high/low Worker 任务;同平台所有门店共享一份参考目录。 - 管理页面的缓存新鲜度设置由按钮打开弹窗;最近任务手动刷新有 loading 及成功/失败 message,5 秒自动轮询静默执行。 - 旧门店目录章节保留为带日期的历史实现记录,不代表当前读写路径。当前未完成重点是真正的 Ozon 发布 job、树级发布新鲜度边界、大字典续传以及平台目录 PostgreSQL 集成测试。 ### 1.39 开发进度与交接(2026-09-23):商品 AI 填报与编辑页收口 **已实现的链路** - `029_product_ai_autofill.sql` 定义 AI 任务、中俄对照与 SKU 可空发布标题;API 提供全局填报、单 SKU 文案任务和任务状态读取。API 在工作空间事务内固化输入快照、写任务与 Outbox;Worker 从 `DEEPSEEK_API_KEY`、`DEEPSEEK_MODEL_NAME` 读取配置调用 DeepSeek。任务不直接写商品草稿,页面在完成后核对商品、profile 和来源快照版本,再把合格结果填入未保存表单。API、Worker、Web 与共享类型均有对应代码;迁移是否已应用到当前目标数据库,本次文档更新未核验。 - 全局填报读取 Atlas 平台级活动类目树,按层向模型提供有限候选;对大分支分段初选、再决选,只接受给定选项。明确结果可回填类目,有多个命中则在「确认 AI 推荐类目」弹窗中由用户选择。已有类目不被覆盖;未确认类目时不能生成依赖它的型号和货号。型号是 SPU 共用稳定码,未导入 SKU 的货号分别生成并检查长度、连接符及冲突。 - 全局文案只生成 SPU 的俄文标题、主题标签、产品描述及逐项中文对照;描述要求概述与「Характеристики:」分段、项目符号。SKU 默认继承 SPU 三项文案;用户可打开当前 SKU 的 AI 弹窗,预览后明确点击应用,才写入该 SKU 的未保存表单。SPU/SKU 都有中俄对照查看入口,俄文被手工修改后旧中文对照标为待更新。 - 包装重量和尺寸仅在来源有可逐字核对的证据时建议;缺证据则留空。原计划的「AI 填写 Ozon 类目属性」已撤回:当前只在类目确定后按 schema 动态加载商品/SKU 属性,用户自行填写。`030_product_ai_attribute_fill.sql` 与历史 `attribute_fill` 类型保留兼容,不代表当前仍会创建或执行该任务。 - 编辑页已把「内部商品名」文案改为「中文商品名」;「俄文标题」默认在 SPU,支持切换为按 SKU 设置并持久化模式。草稿保存允许标题留空,首次上架预检按通用或逐 SKU 模式检查。SKU 的 Ozon 变体字段已并入「SKU 信息」,不另设「Ozon 变体属性」标题。底部 `editor-footer` 固定在视口底部;AI 类目候选弹窗已调整为说明、单选候选和明确确认操作。类目输入框清除时的空值处理已修正。 - 最新俄文文案规则同时作用于 SPU 与 SKU Prompt:`title.ru`、`tags[].ru`、`description.ru` 不得出现汉字;来源中文事实应翻译为自然俄文,只有有依据的拉丁品牌/型号及阿拉伯数字可保留。Worker 结果校验会丢弃仍含汉字的对应字段,中文对照与包装原文证据不受此限制。旧已保存文案不会自动改写,需重新生成或手工修订。 **验证状态与下一步** - 最近一轮俄文禁汉字改动已通过 Worker `product-ai.test.mjs` 7 项测试、Worker TypeScript 检查和 `git diff --check`;本次交接文档更新未再发起真实 DeepSeek 请求。其他页面/API 变更有此前的构建及集成测试记录,但不能视为本次对整个脏工作区的重新验收。 - 下一步先核对目标环境的 `029`/`030` 迁移与 API/Worker 当前进程版本,再使用 Ozon、1688、淘宝、天猫各类样本回归:类目候选准确性与确认、中文来源到纯俄文输出、无包装证据、多个 SKU 的共用型号与独立货号、SPU/SKU 文案继承和保存后中文对照。重点复查此前 1688 类目不足提示与 DeepSeek 失败场景;不能仅以单元测试认定质量已稳定。 - 之后继续定价确认、素材按需物化回归、平台目录大字典续传与管理员回归,并将真正的首次发布 job 接到现有发布预检。发布预检 `ready` 不是已上架;在线商品同步也尚未实现。 详细字段规则与任务接口见 [`ai-autofill-plan.md`](../product/ai-autofill-plan.md) 和 [`product-editor-and-publishing.md`](../api/product-editor-and-publishing.md)。 ### 1.40 开发进度与交接(2026-09-24):SKU 图片选择 **已完成的链路** - 新采集商品的 SKU 默认没有已选图片,编辑页显示「选择图片」入口。弹窗标题为「SKU 图片选择」;左侧已选图片最多 30 张,第一张为主图,可移出、调整顺序、批量选择和预览;右侧按 AI 生图记录、采集图片、网络图片、上传图片展示候选图,采集主图/SKU 图/详情图使用默认展开的折叠面板。左右图片区各自滚动;图片卡片与预览复用 Ant Design 组件。弹窗确认只更新当前编辑表单,页面「保存」才持久化选图。 - 图片交互拆为 `SkuImageCard`、`SkuImageSourceTabs`、`useSkuImageLibrary`、`skuImageSelection` 与弹窗组件;`ProductVariantSection` 负责 SKU 表单集成。左侧上传完成后进入已选区;右侧上传只进入工作空间候选图库,由用户决定是否加入。网络图片按行导入,过滤重复或无效链接;已选图与候选图通过边框及角标标识状态。 - API 扩展现有两阶段素材上传,新增 `product_image` 用途、20 MiB/最大边长 10,000 px 校验,并提供最近 100 张工作空间上传图及短时预览地址。网络图通过 `POST /api/media-assets/product-imports` 导入为正式素材,支持最近 100 条候选记录查询;仅接收 HTTPS JPG/PNG、最多 5 MiB,逐次校验重定向目标并固定到已检查的公网 IPv4,校验实际文件内容与尺寸。来源 URL 保存在独立的工作空间隔离记录中,预览 URL 不写入商品草稿。 - 已选素材通过 `media_asset_ids` 顺序随草稿保存;采集候选素材按原有按需转存链路物化,服务端新增对已上传/已导入素材归属及状态的验证。共享契约、服务端校验与数据库顺序约束均调整为每个 SKU 最多 30 张;迁移为 `031_sku_image_limit.sql` 和 `032_product_image_imports.sql`。接口与产品说明见 [`product-editor-and-publishing.md`](../api/product-editor-and-publishing.md)、[`watermarks-and-assets.md`](../api/watermarks-and-assets.md) 和 [`product-ai-editor.md`](../product/product-ai-editor.md)。 **验证与后续范围** - 本地 `031`、`032` 迁移已执行。contracts、database、API、Web TypeScript 检查及 API/Web 生产构建通过;API 媒体单元测试共 6 项、商品编辑集成测试共 11 项通过。集成测试覆盖 30 张选图保存、已上传和网络导入素材的草稿引用、非法内网网络图片链接拒绝。 - 尚未用真实外部图片 URL、浏览器直传 MinIO 和完整编辑页操作做端到端回归;上线前需联调短时预览地址刷新、上传失败重试、跨 SKU 选图与重新打开弹窗。AI 套图生图和批量翻译由后续迭代接入;单张 AI 编辑、一键 3:4、添加水印、设为封图、下载和移出已选可用。下载对当前工作空间素材签发带 `Content-Disposition: attachment` 的短时地址,采集候选图先按需转存,避免跨域预览链接只打开图片。 ### 1.41 开发进度(2026-09-25):SKU 图片一键 3:4 - 图片弹窗的单张菜单和左侧批量操作已接入一键 3:4。采集候选图先按需转存,再以正式素材 ID 创建异步派生任务;Worker 等比缩放,留白从对应边缘像素平滑过渡到该侧窄带的平均底色,避免出现贯穿留白的条纹,生成独立的 1200×1600 JPG,保留原图。成功项替换弹窗中的图片,失败项不变;确认写入当前表单,页面保存后持久化。 - API、共享类型、Worker 与 `033_media_aspect_ratio_derivatives.sql` 已同步;任务、operation 和 Outbox 在同一事务创建,任务表受工作空间 RLS 约束。结果查询只签发短时预览 URL。新增图像处理库 sharp,项目包管理器统一为 pnpm 11.25.0。 - `033` 已在当前本地 `atlas` 数据库应用。API/Worker 受限角色可读写新表;图像转换、任务执行与媒体 API 单元测试已通过。临时账号和图片的 API → MinIO → Outbox → Worker → 结果查询实测返回独立的 1200×1600 JPEG,测试记录与对象已清理。仍需在真实编辑页点击单张和批量入口,回归弹窗状态与保存后的重读。 ### 1.42 开发进度(2026-09-26):商品图片主动应用水印 - “添加水印”打开标题为「选择水印」的三列模板弹窗,左侧「水印管理」在新标签页打开。选择后对单张或批量图片逐张创建水印派生任务;成功项替换弹窗选图,失败项保留原图,当前弹窗支持撤销本次水印替换。确认只写入编辑表单,页面保存后才持久化。 - `036_media_watermark_derivatives.sql` 保存源素材、锁定的模板版本及处理结果;API 创建任务时原子写入 operation 和 Outbox,Worker 读取不可变模板版本,以文字或图片水印生成独立 JPG 素材。结果查询只签发短时预览 URL。 - 本地已应用 `036` 并核对受限应用角色可读写新表;contracts、API、Worker、Web 类型检查与 Web 生产构建通过,API 单元测试 26 项、图像 Worker 测试 13 项及新增的水印 API/数据库集成测试通过。尚需在真实商品编辑页回归选模板、批量处理、撤销与保存后重读。API 全量集成测试仍有一项既有发布预检优先级断言失败,与本轮水印路径无关。 - 水印图片大小、文字字号、圆角和边距以管理页 240×320 预览框为基准,Worker 按源图显示尺寸同比例换算。此前直接把预览的 80px 水印贴到 1200×1600 等大图,缩小展示后明显偏小且过于贴底;已修正渲染计算,旧的派生图不会自动重生成,需要重新应用水印。 - 打开「SKU 图片选择」时前端静默请求当前工作空间默认水印。单张和批量「添加水印」等待这次请求:有默认模板就直接执行已有派生处理,没有默认模板或请求失败才打开选模板弹窗。默认模板仅在用户主动点击后应用,不会因打开图片弹窗而修改草稿。 ### 1.43 开发进度(2026-09-26):SKU 图片单张 AI 编辑 - 参考旧项目 `studio/src/pages/trial/AiImageGenModal.tsx`,图片「编辑 → AI 编辑」现打开单张抽屉:固定主图、最多两张辅助图、编辑要求、图片文字翻译、背景、五种风格及自定义提示词、尺寸标注、3:4/1:1/4:5 比例与仅扩图。Pcore `gpt-image-2` 任务由 Worker 异步提交、持久化供应商任务 ID,再经延迟 Outbox 轮询;仅扩图完成后覆盖回原图区域。生成结果独立保存为商品关联素材,用户点击「添加到当前 SKU」后插在原图后方;确认选图并保存页面后才持久化 SKU 顺序。右侧「AI 生图记录」可重新选择当前商品的最近 100 张成功结果。 - API 契约见 `docs/api/product-editor-and-publishing.md`;`039_media_ai_edits.sql` 建立工作空间隔离任务表,`040_media_ai_edit_product_scope.sql` 关联商品。两项迁移已应用到当前本地 `atlas` 数据库,受限应用角色读取检查通过。contracts、API、Worker、Web 类型检查通过;Web 生产构建通过;API 定向测试 4 项与 Worker 定向测试 3 项通过(含仅扩图回填原图、供应商异步状态推进)。浏览器中以模拟素材挂载抽屉,验证了翻译开关、尺寸标注选项、仅扩图模式和窄视口布局;临时前端服务已停止。本次未向真实 Pcore 提交收费任务,也未登录真实商品编辑页做完整操作回归;联调时需验证 Pcore 结果地址、任务取消与页面重新打开后的图片选择。 ### 1.44 开发进度(2026-09-26):商品图片任务入口 - 参考旧项目 `studio/src/pages/trial/TrialTaskCenter.tsx`,商品编辑页右下角增加固定「图片任务」入口和右侧任务抽屉。按进行中、最近任务展示当前商品任务,支持手动刷新、单张编辑取消和按需签发短时地址查看结果;单张编辑提交即刷新,运行中每 3 秒轮询。API `GET /api/products/{product_id}/image-tasks` 在工作空间事务内分页读取任务,首期聚合单张编辑;共享 `ProductImageTask` 已预留 `ai_suite` 类型。后续套图生成落地时需把套图表查询接入聚合服务,并补上套图任务的取消与结果操作。API 与 Web 类型检查、Web 生产构建、API 定向测试通过;尚未用真实登录商品页做端到端界面回归。 - 单张 AI 编辑抽屉已移除「生成方式」选择,提交固定使用 `edit` 模式;抽屉标题改为「AI编辑」。服务端仍保留既有 `outpaint` 契约,供其他调用端或后续能力使用。 - 商品编辑页底部操作栏已将原「AI 生图」入口更名为「AI 套图生成」,并把图片任务入口放在其后;进行中时显示任务数。套图按钮当前仍为待开发提示。 ### 1.45 故障排查(2026-09-26):单张 AI 编辑任务失败 - 本地 4 条失败任务都已成功提交到 Pcore,供应商任务 ID 已持久化;Pcore 轮询返回 `status=failed` 和“请求无法用于生成图像,可能因安全政策被拦截,或不适合进行图像生成”。4 条任务使用同一张蜘蛛侠玩具主图,切换背景后仍遭相同拒绝;供应商未指明具体触发项,不能断定是图片、辅助图或提示词中的哪一项。`PCORE_API_KEY` 与 `PCORE_BASE_URL` 已在当前根 `.env` 配置,Worker 通过根 `.env` 读取并实际调用了 `gpt-image-2` 的 `/v1/images/edits`;本次排查只读查询已有供应商任务,没有提交新的付费生图。 - Worker 现在识别 Pcore 的终态错误:内容策略/输入不适合时写 `AI_IMAGE_CONTENT_REJECTED`,其他终态写 `AI_IMAGE_PROVIDER_FAILED`,不再对同一终态任务重复尝试 5 次;前端显示对应中文提示。历史 4 条任务仍保留原通用错误码 `AI_IMAGE_GENERATION_FAILED`。Worker 单元测试 4 项、Worker/Web 类型检查和 Web 生产构建通过。排查时确认维护者的 API/Worker `tsx watch` 均在运行,Worker 子进程已随代码变更重新加载;尚未对其他图片发起实际生图验证。前端无法绕过 Pcore 内容判定。 - 2026-09-27 复核时新增的第 5 条失败任务仍是同一张主图,但没有辅图、背景为 `auto`、编辑要求为空。Pcore 返回 `status=failed`、`error.type=image_task_error`、`error.code=failed`,消息仅称“可能因安全政策被拦截,或不适合进行图像生成”,没有审核阶段或类别;因此不能将原因确定归于主图、辅助图、提示词或生成结果。对照旧项目的 `/v1/images/edits`、`gpt-image-2`、异步轮询、`images[].image_url` 和 3:4 尺寸均一致;Atlas 的差异是发出前会压缩为 JPEG,旧项目发送原始图片字节。当前输入图本身已是 1170×1170、197 KB 的 JPEG,尚无证据表明转码导致拒绝。界面提示已改为不指定具体拦截阶段。没有提交新的付费诊断任务。 ### 1.46 Pcore 图片编辑协议修正(2026-09-27) - 根 `.env` 的 `PCORE_BASE_URL=https://pcore.ai` 没有隐藏空白;Worker 原本拼接为 `/v1/images/edits`,并能拿到供应商任务 ID,故少写 `/v1` 不是失败原因。现兼容根地址与文档的 `/v1` 地址,均规范化为同一接口路径;`.env.example` 推荐文档写法,现有根 `.env` 不必改动。 - 用同一密钥做三项单张对照:合成花瓶图通过 JSON `images[].image_url` 提交后返回 `queued`,随后以笼统的 `image_task_error/failed` 终止;纯文生图通过 `/v1/images/generations` 成功;同一花瓶图改用 `multipart/form-data` 的 `image` 文件上传到 `/v1/images/edits` 后成功。因此笼统错误消息不能证明内容审核拒绝,此次故障定位为图片编辑请求格式与当前 Pcore 通道不兼容。 - Worker 现在以 multipart 上传主图及各辅图,扩图时上传 PNG 图片和 `mask` 文件,保留原有异步任务 ID 与轮询。仅在供应商明确给出审核类错误码时标记 `AI_IMAGE_CONTENT_REJECTED`,笼统终态失败标记 `AI_IMAGE_PROVIDER_FAILED`。旧任务状态不回写;需重新发起生成来验证真实商品图。对照诊断提交了三条可能计费的供应商任务,其中两条成功、一条失败。 ### 1.47 图片任务展示 Pcore 原始错误(2026-09-27) - Pcore 图片编辑失败时,Worker 将供应商错误对象原文保存在对应 operation 的 `result.provider_error_raw`,稳定错误码及通用提示仍保留。商品图片任务列表在工作空间事务内联结 operation,响应增加 `provider_error_raw`;前端失败任务卡片直接展示该内容,其他任务为 `null`。供应商任务 ID、密钥及结果图片地址不随任务列表返回。历史失败任务此前未保存原始错误,需按已持久化的供应商任务 ID 补取;无法补取的记录继续显示通用提示。当前根 `.env` 指向的本地 `atlas` 数据库经只读核对为 0 个商品、0 条失败的 AI 编辑任务,因此本次无历史记录可补取。 ### 1.48 单张 AI 编辑请求对齐旧项目(2026-09-27) - 复核旧项目 SQLite 中最近两条成功的单张编辑任务:均使用 `gpt-image-2`、3:4、一张参考图,并经 `/v1/images/edits` 的 JSON `images[].image_url` 提交。两项目配置的 Pcore key 与 base URL 相同。旧项目也存在一次同端点的笼统失败,因此上一节用合成图得出的“JSON 格式与通道不兼容”不能推广到所有输入图。 - 按维护者要求,Atlas Worker 的普通编辑改为 JSON 请求,`model/prompt/n/size/async/images` 结构与旧项目一致;图片以原始 JPG/PNG/WebP 字节生成 data URI,不再压缩、铺白或转码。仅扩图仍先构造 PNG 画布和蒙版,再用同一 JSON 协议发送 `images` 与 `mask.image_url`。异步轮询、原始错误记录与生成结果保存流程不变。Worker 定向测试 6 项、类型检查和构建通过;尚未以真实商品图重新向 Pcore 发起收费任务。 ### 1.49 Pcore 已完成任务的结果解析修正(2026-09-27) - Pcore 官方图片文档区分 `/v1/images/generations` 文生图和 `/v1/images/edits` 图生图;单张 AI 编辑传原图,应继续调用后者。文档的 JSON 图生图输入写为 URL 或 data URI 字符串或字符串数组;Atlas 当前的 `images[].image_url` 对象数组与旧项目成功任务一致,而且最新任务在 Pcore 返回 `completed`,故这次失败并非提交参数被拒绝。 - 读取 Joey 工作空间内最近失败任务及 BullMQ 栈后确认:最近三条任务都取得供应商任务 ID,Pcore 返回 `data[0].url`,并同时给出空字符串 `b64_json`。Atlas 原先优先解码空 `b64_json`,产生空 Buffer,Sharp 抛出 `Input Buffer is empty`;BullMQ 重试五次后写通用失败。已完成任务的 Pcore 内容 URL 经只读 GET 返回 HTTP 200、`image/png`、1,580,645 字节。Worker 现仅使用非空的 base64 数据,否则回退到结果 URL。定向测试 7 项、Worker 类型检查和构建通过;未提交新的收费生图任务。此前失败记录仍为失败状态,未自动改写业务数据。 ### 1.50 Pcore 结果下载 DNS 回调修正(2026-09-27) - 前一节修复后再次提交的两条 Atlas 任务都已取得 Pcore 任务 ID,却在读取结果 URL 时以 `Invalid IP address: undefined` 失败。BullMQ 栈指向 `downloadProviderImage` 的自定义 DNS `lookup` 回调;Node 请求 `options.all=true` 时要求回调返回地址数组,旧实现仍返回单个地址,导致 TLS 连接前失败。旧项目使用 `httpx` 直接读取结果 URL,没有这一分支。 - Worker 的 DNS 固定逻辑现按 `options.all` 返回地址数组或单个地址,公网地址校验与 TLS 域名校验保持不变。用已经完成的 Pcore 任务调用修复后的 Atlas 下载函数,只读取回有效 PNG(1,734,068 字节,1086×1448);没有发起新的收费生图。Worker 定向测试 8 项、类型检查与构建通过。历史失败记录仍未改写。 ## 2. 已确认产品决策 | 主题 | 决策 | |---|---| | 商品模型 | 每个商品是一个 SPU,且始终至少有一条 SKU;单规格商品使用默认 SKU,多规格商品使用多条 SKU。 | | 平台可售单元 | 一个 SKU 对应一个独立平台 offer;Ozon 首个平台中对应 `/v3/product/import.items[]` 的一项和独立 `offer_id`。 | | 通用属性 | 标题、品牌、型号、主题标签、简介、条形码、采买地址、货源备注及未来商品级属性;类目和平台属性属于独立的平台发布配置。首期不做富内容。 | | SKU 属性 | SKU 名称、图片、货号、采购价、售价、划线价、包装重量和尺寸、商品重量和尺寸、商品颜色、颜色名称等。首期不支持视频。 | | 型号/货号 | 型号(`model`)是父商品通用字段;`offer_id` 是当前平台下的每个 SKU 字段。采集入箱时两者可为空;编辑页可手填,也可在用户主动点击“AI 填报”后生成并填入表单,保存和发布时仍校验唯一性。被成功导入的 `offer_id` 作为历史事实锁定。详见[AI 填报方案](../product/ai-autofill-plan.md)。 | | 规格 | 实物规格与包装物流规格严格分离;尺寸图只读取实物规格,计价/物流/发布只读取包装规格。 | | 继承 | 可继承字段的 `null` 表示该字段没有覆盖父商品默认值,不表示 SKU 不存在、名称为空或商品为单规格。 | | 尺寸图 | 必须逐 SKU 生成,任务保存 SKU ID 和实物规格快照。 | | 定价 | SKU 上点“定价”打开弹窗,以有效包装数据计算;用户确认后回填 SKU 销售价和划线价。 | | 编辑体验 | 新商品 AI 编辑页取代旧商品编辑页;旧试算页不再追加多 SKU 功能。 | | 采集插件 | 采集 Ozon、1688、淘宝、天猫详情页;未登录只显示登录入口,已登录显示“编辑上架 / 采集商品 / 进入 ERP”。采集箱为工作空间共用,采集记录不关联目标平台或门店;用户进入 ERP 编辑时,按页头当前平台(首期默认 Ozon)取得该平台发布配置并预选默认店铺。A/B 平台的上架状态独立。详情见 [`requirements/collector-browser-extension.md`](../requirements/collector-browser-extension.md)。 | | 采集 SKU | 只有来源给出稳定 SKU/组合证据时创建多个 SKU;不能把规格轴做笛卡尔积。没有专属 SKU 图时,真实 SKU 继承父商品默认图而不复制资产。 | | 媒体 | 采集主图/详情图是商品候选池;来源明确给出 SKU、或可靠的规格轴值(如颜色)与图片关系时才自动绑定。其余素材由编辑页人工选择。 | | 发布与在线商品 | 商品采集、编辑、定价、AI 媒体和首次导入至 Ozon 后台是核心闭环。正式发布使用官方 Seller API,按一 SKU 一 import item/offer 执行并轮询结果;首期不传库存或仓库,用户在 Ozon 后台设置库存。已导入 SKU 不可再次上架,未上架 SKU 仍可首次上架;SPU 在当前平台汇总为未上架/已部分上架/已上架。在线商品由异步 Ozon API 同步,按 Ozon 商品 SKU 展示,亦包含非 Atlas 发布商品。 | | 平台商品参考目录 | `platform_id` 下共享类目/类型树、属性定义和字典候选值;同平台门店共用,本阶段不维护门店差异层。定时同步、系统管理员页面、手动排队、商品编辑器读取和发布预检已实现;真正的 Ozon 发布 job 已实现,真实店铺联调待验收。 | ## 3. Ozon 已核对结论 仓内旧项目的 `docs/ozon-seller-api/04-product-import.md` 记录:Ozon `/v3/product/import` 每个 item 都有独立 `offer_id`、价格、包装尺寸/重量和图片,尺寸/重量不能为零;单次最多 100 项。`03-category-attributes.md` 记录类目属性的 `is_aspect=true` 表示变体属性。 因此 Ozon 适配器应以“一 SKU 一 import item/offer”为最小稳定单位。现有资料没有可强制把多个 item 合并为一个 Ozon 前台变体组的 API 契约,不能依赖自动归并;这是未来 Ozon 联调阶段要在真实店铺或沙盒验证的能力。 ## 4. 云端架构底线 1. 使用 PostgreSQL;所有业务行必须受 `workspace_id` 或可安全追溯至工作空间的外键约束。 2. 首期注册后静默创建一个个人工作空间,并只建立一个 `owner` 成员;不展示工作空间、成员、角色或店铺级权限管理。后续有团队协作需求时再扩展。 3. 门店只能绑定一个平台,不能跨平台使用;市场/国家、默认语言、支持语言和支持币种属于平台能力配置,不属于门店。门店保留已确认的默认计价币种与加密凭据;上述信息均不得写进商品核心表。 4. 本地素材使用 MinIO;共享测试和生产素材使用七牛 Kodo,不使用应用服务器本地目录作为持久存储。 5. AI、生图、导出、采集转存和发布使用 Redis + BullMQ 队列/Worker,任务可重试、可恢复且携带工作空间与 SKU 上下文。关键可重试写操作使用持久化幂等记录;异步任务与业务事实在同一 PostgreSQL 事务写入 Transactional Outbox,再由 Dispatcher 投递队列。Redis/BullMQ 不作为业务事实唯一来源。 6. 用户界面语言、采集源语言、商品编辑语言和发布目标语言相互独立;文案及属性值需要支持多语言版本。 7. 一期实现插件中显式开启的 Ozon Seller 网页本地观察:在用户浏览器的已登录 Seller 标签页调用四个已记录内部接口,并仅上传净化结果;不得提取完整、保存或上传 Cookie,不得阻塞标准采集、不得替代官方 Seller API 发布或把未验证经营数据视为本店事实。加密保存 Cookie 仅作为未来二级实验能力预留。 建议的核心层级: ```text User → Workspace → Membership / Role → Store → Platform connection / default currency → Product (SPU) → ProductVariant (SKU) → attributes / media / pricing snapshots / tasks ``` ## 5. 已冻结的第一阶段 API 契约 第一阶段 HTTP API 的接口范围已讨论完成,当前状态为**设计冻结、分模块实施**。认证、门店、采集、商品编辑、平台参考目录、发布预检与 AI 任务已有接口和实现;定价、真正的商品发布及在线商品同步仍未形成完整接口/Worker 闭环。后续编码以 [`docs/api/`](../api/README.md) 为唯一 HTTP 契约来源;旧 [`product/api.md`](../product/api.md) 仅作历史背景,不能作为实现依据。 - 覆盖账户认证、平台/门店、水印与素材、定价、浏览器采集/采集箱、商品编辑/首次发布、在线商品读取与同步。 - 所有接口遵循统一鉴权、工作空间隔离、页头平台上下文、金额字符串、错误格式、`Idempotency-Key`、`If-Match` 和异步 `operation` 约定。 - **响应信封是强制契约:** 成功单资源或同步动作返回 `{ "data": T }`,列表返回 `{ "data": T[], "meta": PaginationMeta }`,失败返回 `{ "error": ApiError }`,`204 No Content` 不返回响应体。项目不采用完整 JSON:API;任何调用方不得自行增加嵌套 `data` 或以 `data: null` 表示错误。完整规则见 [`api/conventions.md`](../api/conventions.md)。 - 关键异步动作必须在同一事务写入业务记录、`operations`(如需用户可见进度)和 `outbox_events`;Dispatcher 至少一次投递 BullMQ,Worker 必须幂等。 - 早期 DDL 已通过本地隔离 PostgreSQL 验证;迁移执行器、认证/工作空间基础、幂等与 Outbox 写入闭环、Outbox Dispatcher、Ozon 凭据验证、采集清理、来源素材转存及商品 AI Worker 已有实现。新近 `029`/`030` 迁移在目标环境中的应用状态需单独核对;商品发布和在线商品同步 Worker 已实现,真实平台联调状态见最新记录。 仍需在对应模块开始编码前完成的 P1/P2 决策和数据模型补丁,集中记录在 [`reviews/implementation-readiness-open-items.md`](../reviews/implementation-readiness-open-items.md)。 ## 6. 下一步建议 下一步建议按以下顺序推进: > 注意:下面的清单不能覆盖 2026-09-18 的 [Ozon 参考目录决策](../architecture/platform-product-reference-catalog.md)。平台目录、后台、同步基础链路和商品编辑器迁移已经实现;旧门店目录只作为兼容/回滚数据保留。 当前执行顺序(2026-09-24): 1. 核对 AI 任务迁移、运行进程版本和四类来源的真实样本;回归类目选择、中俄文案(俄文字段不含汉字)、包装证据、SKU 文案继承及候选媒体按需物化,并联调 SKU 图片上传、网络导入与保存。 2. 完成定价确认、平台目录大字典续传与管理员浏览器回归;旧门店目录继续保留,暂不清理。 3. 首次发布 job、快照、Ozon 导入/轮询和在线同步已实现;下一步完成真实中国主体跨境 rFBS 店铺的 VAT 省略和七牛图片交付验收。 ### 历史计划(仅供追溯) 1. 在真实 Ozon 多 SKU 页面加载已构建扩展,确认 MAIN world bridge 未被页面策略阻断、三 SKU 都写入采集测试页、SKU 级画廊关系正确,并在新版规范化载荷确认完整特征与描述;再以任一兄弟 SKU 页面重采集,确认服务端覆盖同一记录。 2. 用允许保存的单 SKU、单规格轴和颜色×尺码页面样本,在 1688、淘宝、天猫逐项验证采集结果;页面状态未暴露组合数据时补站点适配器,但绝不以规格轴猜测 SKU。之后独立实施 Seller 网页四接口本地观察实验,默认关闭、仅保存净化结果且不影响标准采集。 3. 将当前 `/collection-test/{collectionItemId}` 只读检查面替换为正式商品编辑入口:按页头平台创建发布配置、预选同平台默认门店,并接入 SKU/媒体/类目编辑。 4. 进入商品编辑/在线同步前,关闭评审中的 P1:SKU 与平台发布配置同商品归属、未映射属性持久化、统一乐观锁 Repository 规则;平台能力 Schema 已在 M2.1 按固定键落地。在线商品归档策略和 Client ID 编辑边界仍待确认。 5. **账户认证待开发:忘记密码。** 登录页提供入口;请求接口始终返回同一结果以防邮箱枚举,使用短时有效的一次性不透明邮件令牌(数据库仅存哈希),确认重置后撤销该用户全部 session。实施前先补充认证 API 契约、邮件投递配置和迁移;交付 API、Web、邮件链接与限流/令牌失效回归测试。详情见 [`requirements/account-store-watermark.md`](../requirements/account-store-watermark.md) §2.3。 账户资料的已确认补充:`users.avatar_asset_id` 只保存素材引用,绝不保存公开 URL;`users.revision` 用于 `PATCH /api/auth/me` 的 `If-Match`。资料更新已实现幂等与版本冲突检测;修改密码已实现旧密码校验、按 IP/用户限流、撤销旧会话并轮换新会话。头像上传必须复用素材模块的预签名直传与校验流程,不引入 base64 直传;素材模块尚未实现时头像只能为 `null`。 ## 7. 文档导航 - [文档总索引](../README.md) - [第一阶段 HTTP API 索引](../api/README.md) - [API 统一约定](../api/conventions.md) - [基础模块数据模型与 DDL](../architecture/foundation-data-model.md) - [商品领域数据模型与 DDL](../architecture/product-domain-data-model.md) - [平台级商品参考目录决策](../architecture/platform-product-reference-catalog.md) - [幂等与 Transactional Outbox](../architecture/transactional-outbox-and-idempotency.md) - [数据库角色与 RLS](../architecture/database-security.md) - [商品采集图片与存储策略](../architecture/media-storage-strategy.md) - [实现前待决与待落地事项](../reviews/implementation-readiness-open-items.md) - [浏览器采集插件需求与技术方案](../requirements/collector-browser-extension.md) - [Ozon 尾程、收款与其他费率调查](../requirements/ozon-fulfillment-and-payment-fees-research.md) - [M2.2 / M2.3 扩展采集交接](./m2.2-m2.3-extension-collection.md) - [M2.4 来源解析与测试页交接](./m2.4-source-parsers-and-inspection.md) - [技术栈、异步任务与部署方案](../architecture/technology-and-deployment.md) ### 2026-10-05 采集页面地址回填货源链接 当前集成会话修改 API `extension.service.ts` 新采集商品写入、`product-editor.service.ts`/models 编辑读模型、extension HTTP 集成回归以及 API/交接文档。插件既有解析器已上报 `source.canonicalUrl`,问题是地址只保存于采集记录,未用于商品货源字段。新建商品同时初始化 `purchase_url`;编辑读模型优先使用手工非空链接,空值回退关联采集记录 `source_url`,历史商品无需复采,手动无来源商品仍为空。清空表示恢复来源地址,复采不覆盖手工非空链接。不修改插件、HTTP 字段或数据库,无历史草稿批量写入。 采集授权/素材 HTTP 2 项与商品编辑 13 项通过,覆盖新 1688 地址初始化、历史空字段回填、Ozon 地址、复采保留手工链接和既有商品编辑边界;API 源码/测试类型检查与构建、Web 类型检查/构建及差异检查通过。在用户当前商品的独立浏览器验证页确认 1688 来源地址已显示、打开按钮启用,输入框与按钮均 35px 且对齐。原用户页面未重载或保存,避免丢失编辑;API 开发进程自动重载,无手动启停。未向真实平台发布,未重新采集或写入真实商品。 ## 2026-10-05:Pcore 同源图片接口与开发代理 DNS 兼容 - 最新单张任务在 Pcore 为 `completed`,结果域名 `pcore.ai` 被系统 DNS 解析为 `198.18.1.69`,原通用公网图片下载校验因此拒绝。修复范围是 Worker 的单张/套图共用取图链路及内部安全说明;不新增 HTTP 字段、数据库迁移或前端行为。 - 对与 `PCORE_BASE_URL` 同源且严格匹配已保存任务 `/v1/images/content/{provider_task_id}/0` 的结果,从服务端配置与任务 ID 重建取图请求,保留 TLS 校验、禁止重定向、无 API Key、25 MiB 上限;其他地址继续执行原公网 DNS 与固定 IPv4 校验。没有全局放行 Fake-IP 或内网地址。 - 同源接口下载沿用 60 秒无数据超时,并增加 5 分钟总上限;持续传输的慢速图片不再被固定 60 秒总超时中断。连接和下载超时归为 Pcore 取图失败,诊断信息不记录供应商 URL 或密钥。 - Worker 定向测试 10 项、类型检查、构建与差异检查通过,覆盖任务路径绑定、错误地址拒绝、重定向拒绝、声明/流式大小限制、base64/URL 两种完成结果和不重新提交生图。维护者的 Worker `tsx watch` 已自动加载修改,没有手动重启 API/Worker,也没有启动前端验证服务。 - 使用修复后的真实下载函数成功读取最新任务的 PNG:2,038,121 字节、1152×1536,耗时 201 秒。仅对本次单张失败任务保留原供应商 ID,在工作空间事务内重排任务与 Outbox,以恢复取图和保存;没有提交新的收费生图任务,没有改写 SKU 选图,也没有批量重试其他历史任务。 - 恢复任务 `30f32836-706d-45a9-a301-f536305dd7a9` 已由维护者现有 Worker 处理为 `succeeded`,正式结果素材已登记;operation 失败诊断已随成功回写清除,可通过原任务或当前商品 AI 生图记录查看结果。 ## 2026-10-05 已上架 SKU 本地价格重新试算 本轮跨 Web `SkuPricing`、定价 API、商品编辑读模型、共享契约与接口/交接文档。已上架 SKU 开放「重新试算」和「试算详情」,应用价格前确认:「该 SKU 已上架。本次价格调整仅保存为本地记录,不会同步至 Ozon;如需修改线上价格,请前往 Ozon 后台。」取消不修改价格,确认后更新草稿,仍须保存商品。批量计算和自动试算继续跳过已导入项。 编辑快照新增 `variants[].listing.store_id`。已上架 SKU 固定读取首次导入店铺的定价目录和币种,即使 profile 的将来目标店铺已变更。API 允许此 SKU 生成预览,跨店铺试算返回 `PRICING_VARIANT_STORE_MISMATCH`;显式 `pricing_selection` 按已有有效期、输入、规则和幂等边界确认新本地报价,普通草稿保存保留原报价。保持首次导入状态、店铺、货号、平台商品 ID 和导入时间,不创建发布或价格同步任务,无数据库迁移、Worker 或扩展业务改动。 定价保存 13 项、首次发布 11 项、商品编辑 13 项和采集 2 项集成测试通过,覆盖已上架新报价、错误店铺拦截、失效报价原子回滚、幂等重放、普通保存保留报价及零平台任务。Contracts/API/Web/Worker 类型检查与构建通过;Web 存在原有大包体警告。ego-browser 隔离真实组件验证两个入口均可用,取消不改变草稿,确认后实际售价 120 / 划线价 240,始终使用首次导入店铺,批量不自动改价。临时页面与代理浏览器已清理;未保存真实商品或操作 Ozon,没有手动启停维护者服务。 ## 2026-10-05 采集插件编辑上架进入商品编辑器 修改扩展 `collector.content.ts`、API `extension.service.ts`、Web 编辑页及 `productEditor` 服务和 API/交接文档。根因是采集响应仍提供测试页路径,且新商品尚未初始化平台编辑配置。采集成功响应改为 `/products/{product_id}/editor?collection_item_id={collection_item_id}`,扩展继续使用响应入口并提示「已打开商品编辑页」。Web 携带采集 ID 调用现有 `editor-contexts`,核对商品 ID 后读取快照;当前页头平台和默认店铺规则沿用既有契约。重复采集进入原商品,既有上下文保留资料和上架状态;「采集商品」仍不打开编辑页,没有自动上架、媒体下载或平台绑定写入采集事务。测试页和旧手工商品入口保留,无新增 HTTP 字段或数据库迁移。 API 采集/商品编辑集成回归 15 项通过,覆盖新入口实际商品 ID、幂等重放、复采路径稳定以及首次上下文初始化。API 源码/测试类型检查与构建、Web 类型检查/构建、扩展 WXT prepare/类型检查/Chrome MV3 构建通过;Web 原有包体大小警告保留。ego-browser 隔离真实前端服务验证首次入口先初始化后读快照,普通既有入口直接读取,商品不匹配及无默认店铺阻断读取,并沿用当前平台。临时页面和任务浏览器已清理,没有采集/保存真实商品或调用 Ozon,未手动启停维护者服务。扩展新产物位于 `apps/extension/.output/chrome-mv3`,已加载静态扩展的浏览器需重载扩展并刷新来源页以更新成功提示;既有插件已使用响应入口,API 更新即可更改下一次跳转地址。 ## 2026-10-05 采集插件最小化入口 本轮仅调整扩展 `collector.content.ts`、新增展示模块 `lib/collector-panel.ts` 及扩展交接文档。原 9px 黄点点击后删除整个浮窗,改为 32px 横线最小化按钮;最小化隐藏面板、显示 48px 圆形「A」,点击或键盘操作可恢复,焦点移至当前可见入口。保留同页折叠状态及最小化前的采集提示,登录态/来源 URL 刷新展示时继续沿用折叠状态。没有 HTTP、登录、采集、编辑跳转或数据库变更。 扩展类型检查和 Chrome MV3 构建通过。ego-browser 隔离真实 Shadow DOM 展示模块验证已登录/未登录面板、按钮 32×32px 与图标 18×18px、圆形 A 48×48px、展开/折叠、提示保留、页面更新保持折叠、键盘操作及 1280/390px 布局,最小化/恢复不额外触发采集动作。截图工具超时,外观尺寸和状态以实际 DOM/CSS 测量验证;临时预览及任务浏览器已清理。新产物在 `apps/extension/.output/chrome-mv3`,静态加载需重载扩展并刷新来源页;未手动启停现有服务。 ## 2026-10-05 采集参数错误诊断与后台消息传递 用户报告 1688 商品 616913767097 点击编辑上架出现「Uncaught Error: 请求参数不符合要求」。浏览器安全策略明确拒绝直接读取该来源页,未改用其他浏览器、网络或间接手段获取页面。现有本地 1688 样本完整载荷通过共享校验,尚未获得该商品的失败字段明细,不能声称实际参数根因已解决。已请用户重载扩展并复制新版浮窗的完整字段提示,下一轮据此处理。 本轮仅修改扩展后台、API 客户端和内容脚本消息处理,新增 `collection-payload-validation.ts` 与 `request-errors.ts`。后台失败返回可序列化错误,内容脚本解包后由现有 try/catch 显示;保留服务端 error.details 最多三项字段原因,提交前按共享载荷契约校验,不静默截断真实 SKU 或放宽服务端约束。提示支持长字段换行,不展示请求、令牌或授权头。HTTP/共享契约、服务端、数据库和来源解析规则无改动;校验库仅进入后台包,不增加页面内容脚本依赖体积。 扩展解析与诊断回归 13 项、类型检查、Chrome MV3 构建和差异检查通过。ego-browser 隔离真实后台与浮窗验证模拟 422 明细能显示字段名和原因,无未处理 Promise 异常、不打开编辑页;这里的模拟字段不代表用户商品的实际问题。临时页面与任务浏览器已清理。既有开发服务未手动启停,新静态产物需重载扩展并刷新来源页。 ## 2026-10-06 采集与编辑保存支持超过 100 个 SKU 用户补充的明确字段错误为 `variants: Too big: expected array to have <=100 items`,关闭上一轮待定位事项:根因是插件与 API 共享的采集数组上限为 100,且编辑完整草稿也有独立的 100 项上限,真实 1688 组合超过此值即失败。当前集成会话修改共享契约、API 采集响应统计、扩展/API 回归与 API/交接文档。新增 `PRODUCT_VARIANT_LIMIT=1000`,采集和商品完整保存统一使用;超限中文提示,不静默截断 SKU。采集响应的 `variant_count` 改为实际未删除 SKU 数。首次发布每批 50 个、媒体数量、其他字段约束、工作空间采集商品配额及 Ozon 公开页有界补全规则均保持原边界,无数据库迁移或 Worker/Web 业务逻辑修改。 扩展回归 15 项通过,验证 1688 200 条明确可售记录完整解析、101/1000 条接受、1001 条拒绝和上架 50/51 条边界。API 采集/商品编辑 16 项通过,新增完整 1000 SKU 的采集入库、编辑初始化、读取末尾 SKU、完整草稿保存、复采保留 ID 与人工修改、超限请求不改变来源版本或 SKU 数;测试数据隔离,无真实采集或 Ozon 发布。Contracts/API(含测试)/Web/Worker/Extension 类型检查和构建通过;Web 原有包体警告保留,差异检查通过。维护者现有 API 开发进程随源码自动重载,未手动启停服务;插件静态产物已重建,需重载扩展并刷新来源页后再次采集。 ## 2026-10-06 SKU 数量误采与旧开发包校验 用户反馈页面不足 100 个 SKU,却继续报旧 `<=100` 校验。纠正上轮只凭校验错误认定页面 SKU 超过 100 的判断:错误代表解析载荷数量,不能代替实际页面数量。检查本地产物确认正式包已更新而 `chrome-mv3-dev/background.js` 仍包含旧 100 上限;同时本地合成脚本复现仅 2 个真实 SKU、前置字段名字符串加 150 个元数据对象被旧解析器当成 150 SKU 的问题。浏览器安全策略仍禁止直接读取该来源页,本轮没有换浏览器或间接获取源页;此复现证明代码缺陷,不声称已核验真实页面的具体 SKU 数。 仅修改扩展解析器、构建配置、浮窗提示、回归和交接文档。脚本回退只识别对象属性后直接的对象/数组字面值,不再从字符串引用、null 跳到后续无关对象;优先使用同一 skuModel 的规格与 SKU 表。表内无来源 ID 的记录须具备完整规格匹配和可售证据,元数据不生成 SKU;真实无 ID 组合、零库存组合及大于 100 的真实表仍保留。WXT 构建添加共享契约源码 alias,正式包直接读取源码,避免旧 dist 依赖;修改触发既有开发服务重建,已核对开发/正式包均包含新上限和新解析数量提示。浮窗在提交和成功时显示本次解析 SKU 数量。保留已批准的 1000 容量,不继续提高限制。 扩展回归 18 项、类型检查、正式构建及差异检查通过,覆盖误采 150→2、null 字段回退、元数据排除、无 ID 明确组合保留、原始 16 SKU 样本和真实 200 SKU 表。既有 8110 开发服务自行重建,未手动重启 API/Worker/Web 或关闭浏览器。开发包旧 100 校验已消失,正式包和开发包均有新数量提示;用户需刷新来源页更新已有内容脚本。没有真实采集、商品保存或平台发布。 ## 2026-10-06 1688 CSS 背景画廊与视频补采 用户提供商品 1012823100668 的画廊 HTML:主图位于 `.od-picture-gallery-list` 缩略项 CSS background-image,包含视频封面、15 张独立照片和参数图标。旧规则要求特定外层容器且过滤 200px 以下缩略项,造成主图漏采。本轮仅修改扩展 `product-media.ts`、`source-parsers.ts`,新增用户 HTML 样本与 4 项回归,并更新交接文档,无 HTTP 字段、API、数据库、Worker 或 Web 业务变更。主图按明确画廊位置读取背景地址、还原原图、保留顺序并去重;排除视频控件、参数图标和 undefined 占位地址,视频封面的重复照片仍可作为主图。 视频读取仅限商品画廊播放器的 currentSrc/src/source,或明确 gallery 字面值中的公开视频字段;支持 HTTPS MP4/WebM,与结构化候选去重并补充封面,不读取画廊外推荐视频、blob 或 HLS,也不从 JPG 猜测视频地址。用户片段只有视频封面,尚未提供实际播放器或公开视频地址,已提出补充请求;因此仅能确认视频读取路径已补齐,不能声称此商品实际视频已成功采集。来源域曾被浏览器安全策略禁止直接读取,本轮只使用用户 HTML 和离线合成视频,没有通过其他工具访问实际来源页。 扩展 22 项测试、类型检查、正式 Chrome MV3 构建通过。ego-browser 本地真实 DOM 回归确认 60px 缩略图仍得到 15 张原图、无参数/undefined 候选,封面不会伪造视频,挂载合成画廊播放器后得到 1 个视频。浏览器阻止所有 HTTPS 请求,不下载远程媒体;临时 QA 页面与 TaskSpace 7 已清理。维护者既有服务未手动启停。静态扩展需重载扩展并刷新来源页后重新采集,开发包需确认自动更新内容脚本;没有真实商品入库、保存或 Ozon 发布。 ## 2026-10-06 SKU 货号自动型号前缀 维护者要求 SKU 货号固定以型号为前缀、用户只输入商品后缀。修改范围仅 Web `ProductEditPage`、`SkuInfoPanel`、`ProductVariantSection` 及 CSS,新增 `skuOfferId.ts` 与 `SkuOfferIdInput.tsx`,同步 API/AI/决策与 Web 交接文档。型号修改同步全部未导入 SKU 的完整货号并保留后缀,收起项从完整表单读取,不丢其他 SKU 字段;清空型号保留后缀,空后缀不生成只有前缀的货号。旧自定义货号完整保留为后缀,补前缀只进入待保存草稿。AI 返回货号先拆生成型号前缀再按表单型号组合,不覆盖用户非空后缀。导入/进行中货号只读且保持原值;型号已有锁定规则继续生效。完整货号沿用 50 字符限制,超限有明确保存提示,不截断旧后缀。接口仍提交完整 offer_id,无 API/共享类型、数据库、Worker 或扩展代码变更。 Web 类型检查/生产构建与差异检查通过,保留原有包体大小警告。ego-browser TaskSpace 8 本地真实组件/表单验证展开和收起 SKU、连续型号变更和清空、后缀清空、旧货号保留、采购价/图片资料保留、导入货号只读、输入整串 50 字符上限、型号变长时后缀不截断以及 390/1280px 布局。临时验证文件与代理浏览器已清理,未读写真实商品、调用 Ozon 或手动启停现有服务。 ## 2026-10-06 SKU 文案用户要求优先与弹窗重设计 维护者要求当前 SKU 文案生成可输入用户描述/要求,并优先于采集商品信息。本轮跨 Web 弹窗、编辑页和请求客户端,Worker 文案提示词,共享契约(导出既有 1000 字符限制与请求类型),新增 API 单元回归、Worker 回归及 API/AI/Web 文档。沿用既有 `hint`,服务端已固化到 AI 任务输入与幂等摘要,无 HTTP 字段/校验规则变更、数据库迁移或扩展改动。Worker 单独传用户要求,系统提示明确优先于采集资料、旧 SKU 信息和已有文案;仍遵守俄文、JSON、中俄对照及主题标签输出契约,当前 SKU 摘要不携带其他 SKU 专属信息。 弹窗主要输入展示用户要求(最多 1000 字符),采集资料折叠为辅助参考,结果分字段展示中俄文;要求按 SKU 暂存页面内,不改写采集资料。修改要求使旧预览失效,需重新生成才能应用。生成时禁用输入,关闭/切换后异步旧结果由请求序号隔离;结果 SKU 和来源/商品/profile 版本需匹配。应用当前标题时若从通用切换逐 SKU,其他 SKU 原先继承的标题及中文对照保留,不被清空。 Contracts/API(源码及测试)/Worker/Web 类型检查和构建通过,API 2 项单元测试与 Worker 12 项文案测试通过。ego-browser TaskSpace 9 在真实 ProductEditPage 配合模拟接口验证输入传递、结果失效、按 SKU 要求保留、关闭后旧任务隔离、当前 SKU 应用/模拟保存、其他 SKU 通用标题和对照保留、空要求、1000 字符边界及桌面/390px 弹窗无横向溢出;底层编辑页原有窄屏溢出保留为既有布局问题。临时 QA 文件与代理浏览器已清理,没有读写真实商品、调用收费 AI 或 Ozon,未手动启停现有服务。Web 原有大包体警告保留。 ## 2026-10-06 商品上传与网络图片归属隔离 维护者要求 SKU 选图中的上传图片、网络图片绑定当前商品。根因是候选列表和保存资格均按工作空间读取,以及前端切换商品后保留候选缓存。本轮跨 Web 图片客户端/选图 Hook/套图参考图/商品变体上传入口、API 媒体控制器与服务/编辑器保存校验、共享素材契约、数据库迁移 060 和相关契约/测试文档。商品上传意图与网络导入请求必须传 product_id;候选 GET 必须带 product_id 查询参数。水印上传继续不传商品;上传完成沿用意图归属。同商品 SKU 共用候选,跨商品需各自主动上传或导入才能绑定相同哈希素材。数据库复合外键确保归属与工作空间一致,网络来源 URL 唯一性改为工作空间+商品+URL,原有 RLS 继续强制隔离。保存 SKU 同步按商品校验资格。 历史上传/导入记录没有可信商品归属,保留空归属但退出全局候选;已有 SKU 当前选图可继续原样保存。前端切换商品清空候选和文件指纹,并用商品世代号隔离延迟上传、导入和列表响应;直接上传到 SKU 的结果由编辑会话隔离。没有自动改写历史 SKU 选图。 迁移 060 已在本地应用并重复执行验证;Contracts/API(源码及测试)/Database/Web 类型检查和构建通过。服务端 28 项相关单元/数据库/HTTP 测试验证归属、重复素材独立绑定、幂等、FK/RLS、无商品参数拒绝及 SKU 保存。ego-browser TaskSpace 10 用真实候选 Hook 和模拟接口验证跨商品无混图、同商品 SKU 共用、旧请求隔离、相同文件在不同商品可重新上传、加载期间无旧候选,临时 QA 文件和浏览器已清理。未手动启停维护者服务;API 8200 自动热重载,Web 8100 继续运行,未调用收费 AI 或 Ozon、未修改真实商品。Web 保留原有包体大小警告。 ## 2026-10-06 SKU 名称与货号恢复同行 修复型号前缀控件引入时将货号设置为整行的布局回归。Web `SkuInfoPanel` 将名称与货号放入独立两列基础资料行,`ProductVariantSection.css` 调整行布局和长型号前缀截断宽度;即使其余发布字段在窄容器中切为单列,基础资料仍保持同行。型号前缀、后缀编辑、完整货号与锁定规则不变,无 API/共享契约/数据库变更。Web 类型检查、生产构建和差异检查通过;ego-browser TaskSpace 11 验证 1280/620/390px 名称和货号输入同行、不溢出,并确认后缀修改仍组成完整货号。临时 QA 文件和浏览器已清理,未手动启停服务或修改真实商品,保留既有包体警告。 ## 2026-10-06 七牛直传 Failed to fetch 排查与修复 用户报告 SKU 图片上传失败 Failed to fetch。前端使用上传意图返回的 S3 预签名地址直接 PUT;只读排查确认当前七牛开发空间 atlas-dev-assets 无 CORS 配置,OPTIONS 返回 403 AccessForbidden / CORS is not enabled for this bucket,无 Access-Control-Allow-Origin。已通过 S3 API 配置 atlas-local-web-upload,仅允许本地 Web Origin http://localhost:8100 与 http://127.0.0.1:8100,方法 PUT/GET/HEAD、请求头 content-type、MaxAge 600 秒;读取现有规则后保留其他规则(本次原为空)。此次修复为开发空间配置与上传契约文档更新,无业务代码/API 字段/迁移变更。 修复后真实预检返回 200 并允许本地 Origin;ego-browser TaskSpace 12 在本地页面通过真实七牛预签名 PUT 上传临时 PNG,浏览器返回 200,HEAD 确认内容类型 image/png 与长度一致,验证对象已立即删除。临时 QA 页面与代理浏览器已清理,未修改真实商品和用户图片,未手动启停服务。新建空间或切换 Web 域名时必须重新配置对应 Origin;前端不能用 no-cors 绕过。 ## 2026-10-06 SKU 候选图片删除与七牛清理 维护者要求右侧候选图片「编辑」菜单底部提供删除、二次确认并同步删除七牛文件。本轮跨 Web 选图弹窗/商品变体草稿、API 媒体与相关素材读取、Worker 存储清理、共享契约和数据库迁移 061/062。新增商品范围的删除、待处理列表、状态读取与失败重试接口,详见 `docs/api/watermarks-and-assets.md`。左侧「移出已选」仍仅调整选图;右侧删除确认后移除候选及未保存选图,异步清理完成才提示存储文件已删除。失败保留数据库任务,弹窗显示重试入口。 API 锁定素材、校验商品归属与全部使用引用;已保存 SKU、头像、水印、其他商品候选或活跃图片任务引用返回 409,须先移除引用并保存。未转存采集候选只取消本商品候选,已有存储素材标记删除并释放哈希去重,保留历史审计。删除记录、operation 与 Outbox 同事务登记;Worker 幂等删除素材及去重上传的存储对象,完成后回写成功,重试耗尽回写失败。新对象路径增加随机标识,避免重新上传同图与待删除旧对象冲突;兼容当前七牛和既有 local_minio 存储。数据库新增引用触发器禁止引用已删除素材,删除触发器保护已使用素材,保持工作空间 RLS 与复合外键。 迁移 061/062 已在本地应用;Contracts/API(含测试)/Database/Worker/Web 类型检查与构建通过。相关 API 单元/数据库/HTTP 回归 47 项及迁移 062 引用保护的定向回归通过,Worker 媒体/发布回归 26 项通过,差异检查通过。使用隔离临时商品和真实七牛 PNG 验证现有 Worker 完成清理,HEAD 返回 404,测试对象和数据库记录已清理。ego-browser TaskSpace 13 使用真实弹窗和模拟接口验证删除菜单顺序、确认前不提交、取消保留图片、引用拒绝、失败不报成功及重试恢复;临时 QA 页面和任务浏览器已清理。没有删除真实用户图片、保存真实商品或调用 Ozon;没有手动启停维护者服务,API/Worker 自动重载,Web 8100 继续运行。Web 保留原有包体大小警告。 ## 2026-10-06 SKU 商品颜色回显与多选提交 维护者报告保存草稿后首个颜色显示字典编号,以及多颜色上架被单值校验拦截。本轮跨 Web 动态属性控件、API 草稿保存/发布映射、共享商品属性规则、发布回归与 API 文档。商品颜色统一按 Ozon 属性 ID 10096 识别,最多 100 个有效字典值;颜色编辑、保存和发布共用数量规则,覆盖目录中的单值标记,其他属性沿用自身 schema。草稿保存多个字典 ID,发布逐项映射 ID 与文本,不拼接或截断。此前草稿多选按中文名称识别、发布仍直接校验 max_value_count,导致规则不一致;此次去除数量规则对名称语言的依赖。 Web 初始化主动加载颜色字典,独立保留当前属性的已选名称,搜索或保存回填更换候选页后已选标签仍显示文本;字典尚未加载时采用既有 Ozon 颜色 ID 对应的俄文名称兜底,未知值显示名称待加载而不展示编号,不改变实际提交值。类目属性 UUID 变化后不复用旧名称缓存。无数据库迁移、Worker 或扩展业务改动。 Contracts/API(含测试)/Web 类型检查和构建通过;API 商品编辑与发布 HTTP/数据库回归 25 项通过,新增双颜色保存/重读/发布快照、俄文属性名称、错误字典值拒绝与其他属性单值数量校验。ego-browser TaskSpace 14 使用真实 Form.List 内颜色控件和模拟字典验证加载前文本兜底、保存回填首个标签、搜索切换保留名称、重新展开、三色多选字典 ID 以及类目缓存隔离;临时 QA 文件和浏览器已清理。未修改真实用户商品或向 Ozon 发布,未手动启停维护者服务,Web 原有包体大小警告保留。 ## 2026-10-06 产品描述上报与复制保留换行 维护者要求简介/产品描述上报和复制保留换行。本轮修改 Worker 发布请求适配,新增 ozon-description.ts;Web 新增 CopyTextButton 并接入通用描述、SKU 描述、中俄对照和 SKU AI 结果预览;同步 API 契约说明与发布集成回归,无 API 字段、数据库迁移或草稿存储格式变更。Ozon 官方商品描述规范明确简介支持 br 换行标签。Worker 仅在提交边界将属性 4191 的纯文本 CRLF/CR/LF 转为 br,空行保留连续 br,&/< /> 转义为实体;不修改持久化发布快照或其他属性。编辑与剪贴板继续使用含换行的原文,SKU 空白描述复制有效 SPU 继承值。修复只影响后续提交,不自动更新已导入商品。 API 源码/测试、Worker 和 Web 类型检查及构建通过;13 项发布 HTTP/数据库/Worker 模拟回归通过,新增 SKU 覆盖、SPU 继承、空行/特殊字符、实际请求内容和快照不可变验证。ego-browser TaskSpace 16 先核对官方规则,再使用真实组件和模拟剪贴板验证通用/SKU/继承描述以及中俄对照、AI 结果复制内容逐字一致,显示使用 pre-wrap;临时 QA 页面和浏览器已清理。没有修改真实商品、调用收费 AI 或向 Ozon 发布,没有手动启停维护者服务,Web 原有包体警告保留。 ### 2026-10-06:SKU 选图水印结果显示与归档修复 维护者要求「编辑 → 添加水印」仅处理当前图,结果进入图片处理记录,未选候选图不得自动加入左侧已选区。本轮修改 Web `SkuImageEditorModal`、`ProductVariantSection` 与水印相关产品/API 说明,沿用现有派生任务与商品记录接口,没有 HTTP 字段、API/Worker、共享契约或数据库变更。候选卡片即时显示水印结果;已选图仅替换原位置,未选图保持未选且不受 30 张选图上限影响。成功结果立即进入处理记录,较早的列表响应不能覆盖新结果;刷新失败时已有记录仍显示。撤销恢复卡片,保留归档结果。只读核查本地 16 次水印任务均成功,无水印失败队列记录;原候选卡片未更新是本次发现的展示问题。 Web 类型检查、生产构建、差异检查通过,Worker 既有水印渲染回归 5 项通过。ego-browser TaskSpace 17 用真实弹窗/真实水印客户端及模拟接口验证:满 30 张仍可处理单张候选,另一候选不变且左侧数量不增加;默认模板与无默认模板选择;候选/已选图撤销;手动选用处理记录;重开恢复记录;失败保留原图且无新增选图/记录。临时回归页面与任务浏览器已清理,未保存真实商品、未手动启停服务。生产构建保留既有包体大小警告。 ### 2026-10-07:Ozon 图片下载缺失与发布结果修复 维护者指定商品 `26662645-a6cf-4978-8fa1-154945bf2510` 的「月读鼬【高约22CM】盒装」作为样例:快照 3 图,Ozon 仅 1 图。只读核查确认前两张的签名有效且文件可下载,平台报 host unreachable;七牛交付域名为 china 加速,无访问限制。Atlas 22 秒结束任务,Ozon 约 90 秒才完成图片处理。修复范围为 Worker 发布结果核对、Web 发布结果警告样式、API 发布集成回归和文档,沿用既有接口/状态/Outbox/RLS,无共享字段或数据库迁移。imported 后等待图片查询,缺图和下载错误保留 imported 身份及脱敏提示,不因缺图允许重复创建商品;完整图片结果继续显示正常成功。 15 项发布闭环回归、6 项 Worker 既有媒体/包装回归、Worker/Web 类型检查与构建、API 测试类型检查通过。ego-browser TaskSpace 18 核对官方文档,并用真实结果组件验证缺图警告与完整成功样式;临时页面已清理。未手动启停维护者服务,保留 Web 原有包体警告。真实三图恢复尚未执行:涉及七牛域名改为 global 与指定 SKU 图片专用更新,需要维护者确认。详细证据与操作范围见 `docs/reviews/ozon-image-delivery-diagnosis.md`。 ### 2026-10-07:商品编辑页保存草稿快捷键 本轮仅修改 Web `ProductEditPage`、新增 `useDraftSaveShortcut` 及产品说明,无 API、共享契约、Worker 或数据库变更。Command/Ctrl + S 在页面输入框内复用 `form.submit()`,保留现有校验、版本冲突处理与保存反馈,拦截浏览器保存网页。加载、保存、输入法组合输入、按键重复或可见弹窗期间不提交背景草稿;离开编辑页移除监听。保存入口新增同步锁,避免表单校验完成后并发保存;按钮提示与可访问性属性说明快捷键。 Web 类型检查、生产构建和差异检查通过。ego-browser TaskSpace 19 使用真实 Hook 与 AntD 表单验证 Mac/Windows 快捷键、当前输入与多行描述、必填校验、保存中拦截、弹窗输入拦截、按键重复/组合输入/其他组合键边界、非英文键盘物理 S 键及卸载清理;未保存真实用户草稿。临时页面与浏览器已清理,未手动启停现有服务,保留既有包体大小警告。 七牛后续状态:维护者已批准 global 切换,2026-10-07 20:56:36 请求成功受理;最后观测为 processing。维护者随后要求停止监控,已停止本会话轮询进程,尚未向 Ozon 提交图片恢复。须等待维护者通知就绪后再执行后续动作,具体范围与暂停记录见 `docs/reviews/ozon-image-delivery-diagnosis.md`。 ### 2026-10-07:七牛全球生效后的指定 SKU 三图恢复完成 维护者通知配置完成并要求继续后,只读确认七牛 global/success,三张发布副本下载和 SHA-256 校验通过。仅使用 Ozon 图片专用接口更新 `ShouBan-HYRZ-V1-001`,未提交标题、价格或属性更新,未修改 Atlas 草稿/历史发布记录。第一轮任务 `5770626999` 无下载错误但旧替代主图保留,内容最终顺序为 3、1、2;核对官方 V2 schema 后,第二轮任务 `5770634249` 以 primary_image 指定原第一张,images 传原第二、三张,最终 imported,无错误。 最终图片专用查询为 1 主图 + 2 附加图,图片错误为空;下载实际平台图片并与原图内容比对,顺序 1、2、3、平均像素差均 0。前后标题、价格和类目相关字段一致。临时恢复脚本/状态文件和 ego-browser TaskSpace 20 已清理,无后续监控进程,未手动启停现有服务。此前业务代码回归结果继续有效,本轮仅外部图片恢复及结果文档更新,无新增代码或契约变更。详细证据见 `docs/reviews/ozon-image-delivery-diagnosis.md`。