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

vercelBot commented Jul 24, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specBuildingBuildingPreview, CommentJul 24, 2026 3:27pm

Request Review

@github-actionsgithub-actionsBot 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 mainJul 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

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

@os-zhuang@claude