Repository navigation
docs(skills): teach the seed modes the loader runs (ignore default, replace deletes nothing) and that a deleted seed row stays deleted - #31097
Conversation
… seed row stays deleted
The objectstack-data seeds reference called upsert the default and said
replace deletes every row first. The loader defaults to ignore
(create-only) and replace deletes nothing. The reference, its seed eval
and the data-modeling seed guide now say so, and teach that a boot or
organization replay does not re-insert a seed row a person deleted,
with seed-replayer { reseed: true } as the way to restore it.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkA4qny8vRgP2NzRgs
|
domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 Contract reviewServed-tier: ① Derived judgmentsInputs: card #31090 (body; item 3 comment 6702892720; the triage; the unlock 6703573368; the claim 6703601270), PR #31083 (its body, "Boundaries, by design"), the PR body, its 4-file list and the net diff against merge base
② Semver level
③ Boundary flags
④ Design judgment
The script exits 0, and the readings match the PR body.
Must-fix: M1. Should-fix: S1, S2, S3, S4, S5. Nit: N1, N2. Implemented-by: VERDICT: FAIL |
…the record The landed-keys rule holds only where AppPlugin runs the load for a known organization. The seed guide now names the loads that keep no record (a publish, a package install or upgrade, the marketplace fallback replayer, a load with no organization or no sys_seed_landed_key), and the seeds reference scopes its sentence the same way and says again when a seed replays. Its two long table rows are trimmed under 120 bytes, the externalId row names every keyed mode, and the seed eval's demo dataset declares upsert so applyOn 'create' is live. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8VVAkA4qny8vRgP2NzRgs
Requested by Irene · project thread
Fixes #31090
Item 3 of the card (the seed
modetext inpackages/spec/src/data/seed.zod.ts, comment 6702892720) is routed to the spec lane on a separate card. This PR does not touchpackages/spec; the wording owed is listed below.Clause-②: no
Metadata-protocol: none
dispatch-gates --tierreads a MANDATORY model tier forskills/**(clause ① of the model-tiering ruling); it is waived by the standing order of the maintainer in this project (2026-11-03), and this card ran on the project's default model. The dispatching seat runs the contract review.维护者速读
objectstack-data技能的references/seeds.md改正种子模式:默认是ignore(只建不改),replace不删任何行,insert每次重放都会重复插入;并补一句「人删掉的种子行不会被重放插回,seed-replayer带{ reseed: true }才恢复」。content/docs/data-modeling/seed-data.mdx新增一小节讲同一规则。upsert(默认)和replace=「先删光再插」,自 [Decision] A restart puts seeded records back: a customer's edit to a seeded row is overwritten by the next boot #30081 起已不成立;PR fix(metadata-protocol,runtime): a seed row a person deleted is no longer inserted again by the next seed replay (per-organization record of landed seed keys) #31083 落地的「已落地键」规则文档里没有,客户想找回示例数据没有文档可查。seeds.mdtoken 1566 → 1555(上限不动);发@objectstack/skillspatch;回滚即 revert。skills/objectstack-data/references/seeds.md的「Import modes」表及其下一段,seed-data.mdx新小节「A Deleted Seed Row Stays Deleted」,对照下文「Code sites」。node scripts/check-skills-token-ratchet.mjs,预期退出 0,skills/objectstack-data/references/seeds.md is 1555 tokens (ceiling 1566; headroom 11)。skills/**属 Tier H(人合),需要os-zhuang或hotlong的一次 approval,之后席位落地。What changes
skills/objectstack-data/references/seeds.md(whole-file sweep):Also in that file:
categorySeed(≈L36) dropsmode: 'upsert', so demo data takes the create-only default (asseed-data.mdxteaches). The referencestatusSeed(≈L26) keepsmode: 'upsert': reference data is the caseupsertis for.applyOnsection: its exampledefineSeed(Invoice, …)gainsmode: 'upsert'. Without it the example ran underignore, whereapplyOn: 'create'changes nothing, so it silently relied on the old default.skills/objectstack-data/evals/hooks-security-seeds-datasources.json, case 4expected_outputonly. The demoOpportunityseed has nomode, yet the expectation saidapplyOn: 'create'is what keeps anupsertreplay offstage, which assumes the old default. It now says theignoredefault already leavesstage, andapplyOn: 'create'is what does it on anupsertdataset. Assertions unchanged.content/docs/data-modeling/seed-data.mdx: a new subsection, "A Deleted Seed Row Stays Deleted", after theinsertsubsection (the landed-keys rule, which loads honour it, the once-more case for a row deleted before the upgrade, and theseed-replayer{ reseed: true }door). Theignorerow of the modes table links to it. The rest of the page (:74-80) already matched the loader.Plus
.changeset/skills-data-seed-modes-landed-keys.md:'@objectstack/skills': patch.Why
An AI that follows the published skill writes seeds expecting
upsertreplays by default and areplacethat clears the table. Neither is what the loader does since #30081. After #31021 (PR #31083), a seed row a person deleted stays deleted across replays, and nothing documented that or how to restore it.Code sites (each taught fact, measured on
mainb631573)modeisignore, and the loader'sdefaultModeis that same defaultpackages/spec/src/data/seed.zod.ts:193mode: SeedMode.default('ignore');packages/spec/src/data/seed-loader.zod.ts:253;packages/metadata-protocol/src/seed-loader.ts:1303dataset.mode || config.defaultModeignore: matched row skipped, missing row insertedpackages/metadata-protocol/src/seed-loader.ts:3298-3304(writeRecord),:3355-3356(decideWriteAction)upsert: matched row updated with the declared columns (skipped only on a no-op), missing row insertedseed-loader.ts:3284-3296,:3350-3354insert: inserts every record, no matchseed-loader.ts:3267-3270,:3357-3360update: matched row updated, missing row skippedseed-loader.ts:3272-3282,:3345-3349replacedeletes nothing: it inserts, "caller should have cleared the table"seed-loader.ts:3306-3310,:3358-3360; no'replace'branch anywhere inpackages/metadata-protocol/srcorpackages/runtime/srcclears a tablesys_seed_landed_key(cascades with the organization)packages/platform-objects/src/system/sys-seed-landed-key.object.ts:47,:88upsert/ignoredatasets read and write itseed-loader.ts:1406-1408seed-loader.ts:1946-1951isWithdrawnRow; option doc:671-690seed-loader.ts:2701(resolveReferenceItemanswerswithdrawnviaisWithdrawnSeedTarget,:4119-4122), handled at:2066and:2851packages/runtime/src/app-plugin.ts:2098(boot),:1996(replayer),:2526(hot reload), all throughseedLoaderOptionsFor:84-98seed-replayer,(organizationId, { reseed: true })app-plugin.ts:1963(signature),:1996(reseed === true→'reseed'),:2042→packages/runtime/src/seed-datasets.ts:41service name; callable by server code holding a plugin context (ctx.getService), e.g.packages/plugins/organizations/src/organizations-plugin.ts:408; it has no HTTP routeseed-loader.ts:2491recordLandedKeys)Acceptance notes
Token budget (
node scripts/check-skills-token-ratchet.mjs, exit 0)mainskills/objectstack-data/references/seeds.mdskills/objectstack-data/SKILL.mdskills/objectstack-data/evals/hooks-security-seeds-datasources.jsonNo ceiling moves; the ratchet script is not touched. The corrected rows plus the landed-keys sentence cost 67 tokens; they were paid by deleting restatement in the same file:
defineSeed()is type-safe: TypeScript checks every record's field keys against the object definition."defineSeed(), neverSeedSchema.parse()— Lose compile-time field checking otherwise"applyOnintro: "A seed is replayed (every dev-server boot, every re-publish)" and "right for master data,"upsert: "every replay writes the declared columns back", "Reference data the package keeps current")applyOnparenthesis: the mode-by-mode restatement, shortened to "updatenever writes it; no other mode updates a matched row"ignore/insert/replacenowis what guarantees that — usingDate.now()will fail CI."new Date()ships the package author's clock to every customer and breaks build determinism."categorySeed:mode: 'upsert',Sweep hits
Grep for
upsert (default),default.*upsertandreplace.*delete(plus the seedmodeclaims by hand) overskills/objectstack-data/SKILL.md,rules/*.md,evals/*.jsonandreferences/:references/seeds.md:52(modedefault),:63(upsert(default)),:64(insert"fail on duplicate"),:66(ignore"silently skip duplicates"),:67(replace"Delete everything"),:160("Default toupsert"),:163(replace"Data-loss footgun"): fixed.references/seeds.md:36(demo seedmode: 'upsert') and:82-86(applyOnexample withoutmode): fixed, as above.:26(reference datamode: 'upsert'): correct, left.evals/hooks-security-seeds-datasources.jsoncase 4: implicit old default inexpected_output, fixed as above.SKILL.md,rules/*.md,evals/objects-fields-relationships.json: no hit. (SKILL.md:104"derived verbs (search/export/upsert/…)" is the API verb, not a seed mode.)Other
content/docspages that restate seed modes: the generated reference pages (references/data/seed.mdx:33,references/data/seed-loader.mdx:198,references/api/package-api.mdx:246,references/kernel/manifest.mdx:100) already sayignoreis the default andreplaceinserts into a table the caller cleared; they pick up the spec wording below when it lands. No hand-written page outsideseed-data.mdxrestates them wrongly. Releases pages were not read for edits (release-owned).Outside both lanes, found while measuring:
packages/cli/src/commands/diff.ts:81still saysreplacemeans "a replay deletes every row of the object and inserts these". It is CLI output text; not touched here.Spec wording owed (spec lane, item 3; not edited here)
packages/spec/src/data/seed.zod.ts::27enum comment'replace', // Delete ALL records in object then insert (Dangerous - use for cache tables)→ e.g.// Insert with no match; the loader deletes nothing, the caller clears the table (cache tables).:24enum comment'insert', // Try to insert, fail on duplicate→ e.g.// Insert every record with no match: a replay duplicates rows unless a unique index refuses them.:171-172docstring "a load inserts the records whose row is missing" and:195-196.describe()"a missing row is inserted and an existing row is left as it is" → add: a missing row whose key already landed in the organization (a person removed it) is not inserted again by AppPlugin's loads (boot, per-organization replay, dev hot reload); theseed-replayerservice with{ reseed: true }inserts it.:116-117the strict-objecthistorystring "on the defaults (mode: upsert, …)" describes the default at the time; accurate as history, optional to annotate.Tests
None: the diff is teaching text and one eval expectation, with no runtime behaviour to test. The gates below are the checks.
Verification
Local gates on 6ed361c: 68 derived, 68 run with exit 0, 0 NOT MEASURED (`dispatch-gates --ran`)
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 68 commands (43 pnpm, 25 direct node) for the 4 changed paths. Each ran under a 900 s timeout withNODE_OPTIONS=--max-old-space-size=8192, its exit code captured before any pipe, after a fullpnpm build(exit 0). All 68 exited 0 on the first run.check-skills-token-ratchet.mjs,check:skill-examples,check:skill-refs,check:skill-compatibility,check:skill-frame-sync,check:skill-identifier-liveness,check:doc-authoring,check:doc-anchors,check:docs,check:dts-closure,check:dual-build-cjs-loads,check-changeset-no-major.mjs,check-empty-changeset.mjsandcheck-comment-mask-corpus.mjs.--ranprinted:68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED (a DERIVED zero — all 68 recorded an exit code and none of them is 3).pnpm check:changeset-fixed(exit 0), the one artifact-roster family whose roster sits under.changeset.node scripts/pm/check-governed-merges.mjs --branch claude/project-thread-ba8x76: GOVERNED, Tier H (人合):skills/**; 70 changed lines (+50 / −20), under 3000.node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD: exit 0 before the push.mainmoved 1 commit after the branch point; it touches none of these files or the ratchet.Human Minimum
packages/specholds no runtime logic. (No schema touched.)main, one pull request.skills/**publishes@objectstack/skills, so the PR carries a'@objectstack/skills': patchchangeset. Both declaration lines are filled in above.packages/specnot touched; nothing to regenerate.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01J8VVAkA4qny8vRgP2NzRgs