Skip to content

feat(lint): warn on replay-unsafe mode:'insert' seeds + document composite externalId (#3434 follow-up)#3460

Merged
os-zhuang merged 1 commit into
mainfrom
claude/seed-loader-insert-duplicates-vlywdf
Jul 24, 2026
Merged

feat(lint): warn on replay-unsafe mode:'insert' seeds + document composite externalId (#3434 follow-up)#3460
os-zhuang merged 1 commit into
mainfrom
claude/seed-loader-insert-duplicates-vlywdf

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

背景

#3442(已合并,close #3434)让 SeedLoader 支持组合 externalId,修好了 join 表种子重放重复的问题。本 PR 是它的防复发 + 文档跟进,分三块:

1. Lint gate — 从源头挡住 mode: 'insert'

mode: 'insert' 是唯一非幂等的模式:loader 的 insert 路径无条件写入、从不查 existing,而 seeds 每次 boot / 重新发布都会重放,所以它每次重启都把表复制一遍(#3434 里 showcase memberships 3→6→9)。这是靠人肉发现的,应当在 authoring 期就被挡住。

  • 新增 validateSeedReplaySafety(@objectstack/lint,纯 (stack) => Finding[] 规则,ADR-0019),并接入 os validate / os lint
  • 对每个 data[] 里声明 mode: 'insert' 的 seed 给出 advisory warning,fix-it 指向幂等模式(ignore / upsert)+ externalId:单字段,或 join 表用组合列表(['team', 'project'],即 fix(seed-loader): support composite externalId so join-table seeds dedupe on replay (#3434) #3442 新增的能力)。
  • 满覆盖单测(6 例:干净通过、各索引定位、无 object 名回退、防御性跳过非对象项)。

选 warning 而非 error:insert 不是 schema 错误——首 boot 能正常加载,缺陷只在第二次 boot 显现——所以给带位置的 fix-it,不硬 gate os compile(与其它 data 类建议规则一致)。

2. 文档 — content/docs/data-modeling/seed-data.mdx

3. Dogfood — 真机复刻

真实 better-sqlite3 文件 DB、真实 SeedLoaderService、真实 showcase ProjectMembership 对象,每种模式跨 3 次 "reboot"(同一 DB 文件、新引擎实例):

Arm 模式 boot1 → boot2 → boot3
对照(bug) mode:'insert' 3 → 6 → 9
修复 mode:'ignore' + externalId:['team','project'] 3 → 3 → 3

对照臂重现 3→6→9 证明 harness 真的跨重启持久化(所以修复臂的 3→3→3 是真的 dedupe,不是 DB 被清空的假象)。

测试

  • @objectstack/lint 全套(21 files / 273 tests)通过,含新规则。
  • 全仓 pnpm build(71/71)通过;@objectstack/cli DTS 编译通过(接入无类型错)。
  • eslint 干净。

Refs #3434, #3442

🤖 Generated with Claude Code

https://claude.ai/code/session_01JqZsDkKaCkpQGpJdEMEWDF


Generated by Claude Code

…osite externalId (#3434 follow-up)

Follow-up to the merged composite-externalId fix (#3442, closing #3434).

- lint: add `validateSeedReplaySafety` (`@objectstack/lint`, a pure
  `(stack) => Finding[]` rule per ADR-0019) and wire it into `os validate` /
  `os lint`. It flags every `data[]` seed declared `mode: 'insert'` — the one
  non-idempotent mode, which duplicates its table on every replay boot — and
  points the author at `ignore`/`upsert` plus an `externalId` (single field, or
  a composite list for a join table). Catches the #3434 footgun at authoring
  time instead of on the second boot. Full unit coverage.

- docs: correct the seed `insert` mode row in `seed-data.mdx` (it does NOT
  "throw on duplicate" — it re-inserts with no check), add an `insert`
  replay-safety warning, and add a "Composite externalId — Join / Junction
  Tables" section plus best-practice and API-reference updates.

Verified end-to-end on a real better-sqlite3 file DB with the real showcase
ProjectMembership object: mode:'insert' reproduces 3 -> 6 -> 9 across reboots,
while mode:'ignore' + externalId:['team','project'] stays 3 -> 3 -> 3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqZsDkKaCkpQGpJdEMEWDF
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Building Building Preview, Comment Jul 24, 2026 3:27pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Jul 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/lint.

19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx (via packages/cli)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint)
  • content/docs/plugins/packages.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli)
  • content/docs/releases/v16.mdx (via @objectstack/cli)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 24, 2026 15:40
@os-zhuang
os-zhuang merged commit 29ff3c2 into main Jul 24, 2026
15 of 16 checks passed
@os-zhuang
os-zhuang deleted the claude/seed-loader-insert-duplicates-vlywdf branch July 24, 2026 15:40
os-zhuang added a commit that referenced this pull request Jul 24, 2026
…3433 follow-up) (#3471)

Follow-ups to the #3433 seed / state-machine exemption:

- lint: `validateSeedStateMachine` — an author-time `os validate` / `os lint`
  warning when a seed record's `state_machine`-governed field carries a value the
  machine does not declare (initialStates ∪ transition keys ∪ targets). #3433
  exempts seeds from the FSM at write time, so this re-adds the "is this even a
  known state?" safety net a typo would otherwise slip past. Advisory, symmetric
  with the #3434 replay-safety rule. Rule id `seed-value-outside-state-machine`.

- test(cloud-connection): a marketplace-install integration test that drives the
  real install handler → runInlineSeed → SeedLoaderService against an engine stub
  reproducing the #3165 initialStates guard, proving a template whose seed spans
  the whole pipeline (prospecting → closed_won) lands every row on install. Red
  without the #3433 exemption (verified).

- docs: note the seed exemption in the state-machine, validation, and seed-data
  guides — seeds are established facts, not lifecycle events; every other
  validation still runs; `os lint` catches an unknown-state typo before boot.

The remaining #3433 follow-up — #3434 (mode:'insert' seeds duplicate on replay) —
was already closed by #3442 (composite externalId) + #3460 (replay-safety lint),
so it needs no change here.

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

seed-loader: mode:'insert' datasets duplicate on every replay boot — showcase memberships 3→6→9 (needs composite externalId or explicit key)

2 participants