feat(lint): warn on replay-unsafe mode:'insert' seeds + document composite externalId (#3434 follow-up)#3460
Merged
Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
#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 新增的能力)。选 warning 而非 error:
insert不是 schema 错误——首 boot 能正常加载,缺陷只在第二次 boot 显现——所以给带位置的 fix-it,不硬 gateos compile(与其它 data 类建议规则一致)。2. 文档 —
content/docs/data-modeling/seed-data.mdxinsert行(原文写 "throw on duplicate" 是错的——loader 不查重、直接重插;正是 seed-loader: mode:'insert' datasets duplicate on every replay boot — showcase memberships 3→6→9 (needs composite externalId or explicit key) #3434)。insert — Not Idempotent警告小节。externalId— Join / Junction Tables" 小节(用真实 membership 例子)+ 更新 best-practice 表与defineSeed()API 签名(externalId?: string | string[])。3. Dogfood — 真机复刻
真实 better-sqlite3 文件 DB、真实
SeedLoaderService、真实 showcaseProjectMembership对象,每种模式跨 3 次 "reboot"(同一 DB 文件、新引擎实例):mode:'insert'mode:'ignore'+externalId:['team','project']对照臂重现 3→6→9 证明 harness 真的跨重启持久化(所以修复臂的 3→3→3 是真的 dedupe,不是 DB 被清空的假象)。
测试
@objectstack/lint全套(21 files / 273 tests)通过,含新规则。pnpm build(71/71)通过;@objectstack/cliDTS 编译通过(接入无类型错)。Refs #3434, #3442
🤖 Generated with Claude Code
https://claude.ai/code/session_01JqZsDkKaCkpQGpJdEMEWDF
Generated by Claude Code