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
|
domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 Contract-review-claim: session_01J8VVAj7CvjkGXvgXzd6RZ7 · head b739914 · 2026-11-07T07:43Z Delta review against the FAIL at head 6ed361c (comment 6703924081: must-fix M1, should-fixes S1–S5, nits N1–N2). The delta is one commit answering M1, S1–S3, S5, N1 and N2, plus the PR body (patched by the seat). S4 is filed as #31099. The seat review runs at CONTRACT_REVIEW_TIER in an isolated subagent. |
|
domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 Contract reviewServed-tier: ① Derived judgmentsEach fix from the first review was checked against the code on
② Semver levelUnchanged: ③ Boundary flags
④ Design judgment
These match the PR body.
Must-fix: none. Should-fix: none. Nit: N3, N4, N5, N6. Implemented-by: VERDICT: PASS |
|
domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 维护者速读
席位核对 — ACCEPT · domain:skills#3 · head
|
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 as #31099 (with the stalereplacestring inpackages/cli/src/commands/diff.ts:81). 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每次重放都会重复插入;并补一句「AppPlugin 的启动和按组织重放不会把人删掉的种子行插回(发布、装包不记账,会插回),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 → 1560(上限不动);发@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 1560 tokens (ceiling 1566; headroom 6)。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 4. The demoOpportunityseed had nomode, yet the case requiredapplyOn: 'create'to keep a replay offstage, which only matters under the oldupsertdefault. The prompt now asks for demo rows refreshed from the seed on every replay exceptstage, so the right answer ismode: 'upsert'pluscolumns: { stage: { applyOn: 'create' } }, andexpected_outputsays so. The assertions are unchanged and every one of them is now live (review S3: the dataset declaresupsert, rather than dropping themust_contain).content/docs/data-modeling/seed-data.mdx: a new subsection, "A Deleted Seed Row Stays Deleted", after theinsertsubsection: the landed-keys rule; the loads that keep and honour it (AppPlugin's single-tenant boot seed, per-organization replay and dev hot reload, for a known organization); the loads that keep no record, where a deleted row comes back (a seed publish, a package install or upgrade including the marketplace install, heal and sample-data reseed, a load with no organization, a composition withoutsys_seed_landed_key, the marketplace's fallbackseed-replayer); the once-more case for a row deleted before the upgrade; and theseed-replayer{ reseed: true }door. Theignoreandupsertrows of the modes table link to it, scoped to "a boot or organization replay". 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-98app-plugin.ts:2069(organizationWallActive),:2084(multi-tenant mode — skipping inline seed)landedKeysoption)packages/metadata-protocol/src/protocol-publish-batch.ts:103(publish apply);packages/runtime/src/domains/packages.ts:4729(package install);packages/cloud-connection/src/marketplace-install-local-plugin.ts:2982,:4768,:4880(install-local loads); its fallbackseed-replayer:1689(registerSeedReplayerIntoKernel, only when none is registered)sys_seed_landed_key: no record, said onceseed-loader.ts:4051-4070seed-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. Every added line in
seeds.mdis at most 120 bytes (longest: thereplacerow, 119 B;ignorerow 111 B,upsertrow 106 B). The corrected rows plus the landed-keys lines 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)"applyOnintro: "right for master data,"upsertrow: "Reference data kept 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',Review fixes (contract review 6703924081)
seed-data.mdxnames the loads that keep no record, so a deleted row comes back on a publish or a package install or upgrade, and says the boot seed is the single-tenant boot. Theignorerow's "(unless a person deleted it)" now reads "(a boot or organization replay skips one a person deleted)".seed-replayer, a load with no organization, and a composition withoutsys_seed_landed_key.seeds.mdscopes its sentence to "AppPlugin's boot and organization replays (not a publish or package install)".seeds.mdsays again when a seed replays: "each boot, install and re-publish".upsert(prompt andexpected_output); themust_containlist is unchanged and live.ignoreandupsertrows are 111 B and 106 B.upsertrow links the rule too. N2: theexternalIdrow says "every keyed mode matches on".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:51(externalId"upsert / update lookup", N2),: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 (it requiredapplyOn: 'create'on a dataset with nomode), 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, carried on #31099; not touched here.Spec wording owed (spec lane, item 3, filed as #31099; 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 b739914: 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, over the fullpnpm build(exit 0) made at 6ed361c (the review commit touches no built package). All 68 exited 0 at 6ed361c and again at b739914.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/**; 86 changed lines (+63 / −23), under 3000.node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD: exit 0 before each push.mainmoved after the branch point; none of its commits touches 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