Skip to content

docs(skills): teach the seed modes the loader runs (ignore default, replace deletes nothing) and that a deleted seed row stays deleted - #31097

Draft
objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/project-thread-ba8x76
Draft

objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/project-thread-ba8x76

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Irene · project thread

Fixes #31090

Item 3 of the card (the seed mode text in packages/spec/src/data/seed.zod.ts, comment 6702892720) is routed to the spec lane on a separate card. This PR does not touch packages/spec; the wording owed is listed below.

Clause-②: no
Metadata-protocol: none

dispatch-gates --tier reads a MANDATORY model tier for skills/** (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.md token 1566 → 1555(上限不动);发 @objectstack/skills patch;回滚即 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):

-| `mode` | `'upsert'` | Import strategy (see below) |
+| `mode` | `'ignore'` | Import strategy (see below) |
@@ Import modes
-| `upsert` (default) | Update by `externalId`, insert if missing. Idempotent. | Reference data, bootstrap rows |
-| `insert` | Insert all; fail on duplicate `externalId`. | Append-only / audit tables |
+| `ignore` (default) | Create-only: insert a missing row, leave a matched row as is. | Data the customer owns once loaded |
+| `upsert` | Insert if missing; every replay writes the declared columns back. | Reference data the package keeps current |
+| `insert` | Insert every record, no match: duplicates on each replay. | Rows loaded once |
 | `update` | Update only existing rows; never create. | Patching existing config |
-| `ignore` | Insert; silently skip duplicates. | Additive bootstrap |
-| `replace` ⚠️ | Delete everything, then insert. **Data loss.** | Cache / lookup tables only — never user data |
+| `replace` ⚠️ | Insert with no match; deletes nothing, the caller clears the table. | Cache / lookup tables only |
+
+A boot or organization replay does not re-insert a keyed row a person deleted
+(landed keys are recorded per organization); `seed-replayer` with
+`{ reseed: true }` restores it.
@@ Seed best practices
-| Default to `upsert` | Idempotent re-runs |
+| Leave `mode` unset; `upsert` only for reference data | Replays keep customer edits |
-| Use `replace` only on cache/lookup tables, with comments | Data-loss footgun |
+| `replace` only on cache tables the caller clears | It never deletes: duplicates otherwise |

Also in that file:

  • Quick start: the demo categorySeed (≈L36) drops mode: 'upsert', so demo data takes the create-only default (as seed-data.mdx teaches). The reference statusSeed (≈L26) keeps mode: 'upsert': reference data is the case upsert is for.
  • applyOn section: its example defineSeed(Invoice, …) gains mode: 'upsert'. Without it the example ran under ignore, where applyOn: 'create' changes nothing, so it silently relied on the old default.

skills/objectstack-data/evals/hooks-security-seeds-datasources.json, case 4 expected_output only. The demo Opportunity seed has no mode, yet the expectation said applyOn: 'create' is what keeps an upsert replay off stage, which assumes the old default. It now says the ignore default already leaves stage, and applyOn: 'create' is what does it on an upsert dataset. Assertions unchanged.

content/docs/data-modeling/seed-data.mdx: a new subsection, "A Deleted Seed Row Stays Deleted", after the insert subsection (the landed-keys rule, which loads honour it, the once-more case for a row deleted before the upgrade, and the seed-replayer { reseed: true } door). The ignore row 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 upsert replays by default and a replace that 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 main b631573)

Taught fact Code site
The default mode is ignore, and the loader's defaultMode is that same default packages/spec/src/data/seed.zod.ts:193 mode: SeedMode.default('ignore'); packages/spec/src/data/seed-loader.zod.ts:253; packages/metadata-protocol/src/seed-loader.ts:1303 dataset.mode || config.defaultMode
ignore: matched row skipped, missing row inserted packages/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 inserted seed-loader.ts:3284-3296, :3350-3354
insert: inserts every record, no match seed-loader.ts:3267-3270, :3357-3360
update: matched row updated, missing row skipped seed-loader.ts:3272-3282, :3345-3349
replace deletes nothing: it inserts, "caller should have cleared the table" seed-loader.ts:3306-3310, :3358-3360; no 'replace' branch anywhere in packages/metadata-protocol/src or packages/runtime/src clears a table
The record of landed keys is the organization-owned sys_seed_landed_key (cascades with the organization) packages/platform-objects/src/system/sys-seed-landed-key.object.ts:47, :88
Only upsert / ignore datasets read and write it seed-loader.ts:1406-1408
A recorded key whose row is gone is not inserted again; a never-recorded key still lands seed-loader.ts:1946-1951 isWithdrawnRow; option doc :671-690
A reference to the removed row is left unwritten, not reported unresolved seed-loader.ts:2701 (resolveReferenceItem answers withdrawn via isWithdrawnSeedTarget, :4119-4122), handled at :2066 and :2851
Loads that honour it: boot seed, per-organization replay, dev hot reload packages/runtime/src/app-plugin.ts:2098 (boot), :1996 (replayer), :2526 (hot reload), all through seedLoaderOptionsFor :84-98
The deliberate re-seed: kernel service seed-replayer, (organizationId, { reseed: true }) app-plugin.ts:1963 (signature), :1996 (reseed === true → 'reseed'), :2042 → packages/runtime/src/seed-datasets.ts:41 service 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 route
A row deleted before this release comes back once, then is remembered PR #31083 body, "Boundaries, by design"; the first load records every row still present (seed-loader.ts:2491 recordLandedKeys)

Acceptance notes

Token budget (node scripts/check-skills-token-ratchet.mjs, exit 0)

File main head Ceiling
skills/objectstack-data/references/seeds.md 1566 1555 1566 (unchanged)
skills/objectstack-data/SKILL.md 6078 6078 (not touched) 6078
skills/objectstack-data/evals/hooks-security-seeds-datasources.json 1753 measured, not ratcheted (eval fixture) —

No 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:

Deleted Surviving home
Intro: "defineSeed() is type-safe: TypeScript checks every record's field keys against the object definition." Same file, Seed best practices row "Always use defineSeed(), never SeedSchema.parse() — Lose compile-time field checking otherwise"
applyOn intro: "A seed is replayed (every dev-server boot, every re-publish)" and "right for master data," The new modes table (upsert: "every replay writes the declared columns back", "Reference data the package keeps current")
applyOn parenthesis: the mode-by-mode restatement, shortened to "update never writes it; no other mode updates a matched row" The modes table rows for ignore / insert / replace
Determinism gate: "CEL + pinned now is what guarantees that — using Date.now() will fail CI." Same file, Dynamic values (CEL): "This is the only correct way … new Date() ships the package author's clock to every customer and breaks build determinism."
Quick start categorySeed: mode: 'upsert', Not restatement: removed as a correction (demo data takes the default).

Sweep hits

Grep for upsert (default), default.*upsert and replace.*delete (plus the seed mode claims by hand) over skills/objectstack-data/SKILL.md, rules/*.md, evals/*.json and references/:

  • references/seeds.md:52 (mode default), :63 (upsert (default)), :64 (insert "fail on duplicate"), :66 (ignore "silently skip duplicates"), :67 (replace "Delete everything"), :160 ("Default to upsert"), :163 (replace "Data-loss footgun"): fixed.
  • references/seeds.md:36 (demo seed mode: 'upsert') and :82-86 (applyOn example without mode): fixed, as above. :26 (reference data mode: 'upsert'): correct, left.
  • evals/hooks-security-seeds-datasources.json case 4: implicit old default in expected_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/docs pages 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 say ignore is the default and replace inserts into a table the caller cleared; they pick up the spec wording below when it lands. No hand-written page outside seed-data.mdx restates them wrongly. Releases pages were not read for edits (release-owned).

Outside both lanes, found while measuring: packages/cli/src/commands/diff.ts:81 still says replace means "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:

  • :27 enum 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).
  • :24 enum 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-172 docstring "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); the seed-replayer service with { reseed: true } inserts it.
  • :116-117 the strict-object history string "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/objectstack derived 68 commands (43 pnpm, 25 direct node) for the 4 changed paths. Each ran under a 900 s timeout with NODE_OPTIONS=--max-old-space-size=8192, its exit code captured before any pipe, after a full pnpm build (exit 0). All 68 exited 0 on the first run.
  • They include 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.mjs and check-comment-mask-corpus.mjs.
  • --ran printed: 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).
  • Also run, outside the derived list: 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.
  • main moved 1 commit after the branch point; it touches none of these files or the ratchet.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No schema touched.)
  • 2. Every name I added uses the casing for its kind. (No names added.)
  • 3. No formatter ran. (Markdown, MDX and one JSON string only.)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. skills/** publishes @objectstack/skills, so the PR carries a '@objectstack/skills': patch changeset. Both declaration lines are filled in above.
  • 6. packages/spec not touched; nothing to regenerate.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English. (The 维护者速读 section is Chinese, per the lane template.)
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8VVAkA4qny8vRgP2NzRgs

… 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
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 7, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7

Contract-review-claim: session_01J8VVAj7CvjkGXvgXzd6RZ7 · head 6ed361c · 2026-11-07T07:22Z

Full contract review of PR #31097 (#31090, claim 6703601270). The seat review runs at CONTRACT_REVIEW_TIER in an isolated subagent.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 6ed361c83cabbb5b93fe8d3ee59e3c60dac3c844
Local-runs: probe — node scripts/check-skills-token-ratchet.mjs in a throwaway worktree at the head (exit 0, removed after), to read the token budget the PR claims; check-governed-merges --pr 31097 is a platform read (GOVERNED, Tier H)
Scope: skills/objectstack-data/references/seeds.md, skills/objectstack-data/evals/hooks-security-seeds-datasources.json, content/docs/data-modeling/seed-data.mdx, .changeset/skills-data-seed-modes-landed-keys.md; cited code, read on origin/main 9c1edbcd2 (none of these paths moved since merge base b63157335): packages/metadata-protocol/src/seed-loader.ts (writeRecord, decideWriteAction, SeedLoaderServiceOptions.landedKeys, openLandedKeyLedger, isWithdrawnRow, the reference withdrawn outcome), packages/runtime/src/app-plugin.ts (seedLoaderOptionsFor, the boot seed, the seed-replayer closure, the hot-reload seeder), packages/runtime/src/seed-datasets.ts, packages/runtime/src/domains/packages.ts, packages/metadata-protocol/src/protocol-publish-batch.ts, packages/cloud-connection/src/marketplace-install-local-plugin.ts (its own loads and its fallback seed-replayer), packages/spec/src/data/seed.zod.ts, packages/spec/src/data/seed-loader.zod.ts, packages/plugins/organizations/src/organizations-plugin.ts, packages/cli/src/commands/diff.ts, scripts/check-skills-token-ratchet.mjs, scripts/pm/check-governed-merges.mjs

① Derived judgments

Inputs: 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 b63157335. Each taught fact was checked against the code on main:

  • The default. TRUE. SeedMode.default('ignore') in seed.zod.ts, and the loader's defaultMode is the same schema default. seeds.md's field table and modes table now say ignore.
  • ignore. TRUE. A matched row is skipped. A missing row is inserted (writeRecord and decideWriteAction agree).
  • upsert. TRUE. A matched row is updated with the declared columns, or skipped on a no-op replay. A missing row is inserted.
  • insert. TRUE. Every record is inserted with no match, so a replay duplicates rows (unless a unique index refuses them).
  • update. TRUE. A matched row is updated. A missing row is skipped.
  • replace deletes nothing. TRUE. Its branch only inserts ("caller should have cleared the table"). No 'replace' branch in the loader or runtime clears a table. The only text that still says it deletes is CLI output in diff.ts:81, which the PR body names and leaves out of lane.
  • applyOn sentence "no other mode updates a matched row". TRUE. ignore skips, and insert / replace never match.
  • Landed keys: where they are kept. TRUE as far as it goes. The record is sys_seed_landed_key, per organization, read and written only by upsert / ignore datasets. A recorded key whose row is gone is not inserted. A never-recorded key still lands. A reference to a removed row is left unwritten, not reported. A row deleted before the upgrade comes back once (the 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 body says so).
  • Landed keys: which loads honour it. Only the three loads AppPlugin builds pass landedKeys: the boot seed, the seed-replayer closure, and the dev hot-reload seeder. Both pages name exactly these. The loads that keep no record are: the package publish apply (protocol-publish-batch.ts), the runtime package install (domains/packages.ts), and the marketplace install-local install, heal and reseed. A load with no organization, or a composition without sys_seed_landed_key, keeps none either.
    ⛔ must-fix M1, seed-data.mdx, new subsection and the ignore row. The same page (L66-69) defines a replay as "every boot …, every package install or upgrade, and every publish". The new section then says "the next replay does not insert it again", and the ignore row says it inserts a missing row "(unless a person deleted it)". Neither is true for a package install, upgrade or publish: those loads keep no record, so a deleted seed row comes back there. The card asks which replays honour the rule and which do not, and the page only names the first set. Add one sentence, for example: "A package install or upgrade (os package install, the marketplace install) and a seed publish keep no record, so a deleted row comes back on those." Also say the boot seed is the single-tenant boot (a multi-tenant boot does not seed). The page has no token budget, so this costs nothing against the ratchet.
  • The reseed door. TRUE. The kernel service is seed-replayer (seed-datasets.ts:41), with signature (organizationId, { userId?, reseed? }). reseed === true maps to 'reseed', which inserts every missing row and records it again. Server code with a plugin context calls it (ctx.getService, as organizations-plugin.ts:408 does). Neither page invents an HTTP route.
  • should-fix S1, the organization replay that is not AppPlugin's. marketplace-install-local-plugin.ts registers its own fallback seed-replayer when no AppPlugin app has seeds. That one takes no reseed and keeps no record. So in that composition, seeds.md's "an organization replay does not re-insert" is false. ({ reseed: true } still has the right effect there, since it never withholds a row.) The no-organization and no-sys_seed_landed_key cases are missing too. One clause in the mdx is enough, for example "only where AppPlugin runs the load and the organization is known". seeds.md can stay as it is if the mdx carries this.
  • Deleted restatements in seeds.md. Four of five homes carry the fact:
    • "defineSeed() is type-safe" is carried by the best-practices row ("Lose compile-time field checking otherwise").
    • "right for master data" is carried by the upsert row ("Reference data the package keeps current").
    • The mode-by-mode parenthesis is carried by the modes table.
    • "CEL + pinned now … Date.now() will fail CI" is carried by Dynamic values (CEL) ("single per-load pinned now", "new Date() … breaks build determinism") plus the gate sentence that stays.
    • should-fix S2: "A seed is replayed (every dev-server boot, every re-publish)" has no surviving home. The named home, the modes table, says "every replay" but not when a replay happens. The re-publish half appears nowhere else in the skill (SKILL.md and rules/*.md were grepped). It matters here because a re-publish is one of the loads that does not honour landed keys. Either keep a short "(each boot and re-publish)" or let the landed-keys line say "a boot or organization replay (not a publish or package install)". That costs about 6 tokens, and there are 11 of headroom.
  • Eval case 4. It is inside the claim's evals clause: the old expectation leaned on an upsert default for a demo dataset with no mode. Only expected_output changed. The assertions are byte-identical. The new text is true: the ignore default never writes a matched row, and applyOn: 'create' is what protects stage on an upsert dataset.
    • should-fix S3: must_contain still requires applyOn: 'create'. For the demo Opportunity seed it is now inert, so an answer that follows the corrected skill (no mode, no applyOn) fails the case. Either reword the prompt so stage sits on an upsert dataset, or accept it and track it on the card. Changing the assertion is inside the claim's evals clause, but it is not wording-only.
  • The applyOn example gains mode: 'upsert'. CORRECT. Under ignore, applyOn: 'create' changes nothing, so the old example relied on the old default.
  • The demo categorySeed drops mode: 'upsert'. CORRECT. Demo data takes the create-only default, which matches the mdx and the new best-practices row. The reference statusSeed keeps upsert, which is right.

② Semver level

'@objectstack/skills': patch is right. Only skill prose and an eval fixture ship. content/docs does not publish. The behaviour itself shipped in #30081 and #31083. Clause-②: no and Metadata-protocol: none are right, because no schema, contract or metadata meaning moves here. Both lines are line-initial in the changeset and in the PR body. No skip-changeset label is set (labels: documentation, size/s, tooling).

③ Boundary flags

  • Diff against the claim. The diff stays inside the claim: seeds.md, one eval in evals/*.json, seed-data.mdx and one changeset. SKILL.md and rules/*.md are untouched, and the sweep found no hit there. The ratchet script is unchanged against main. packages/spec is untouched, so item 3 is not in this PR.
  • Tier. check-governed-merges --pr 31097: GOVERNED, Tier H, skills/** ×2 (seeds.md and the eval). The size is 70 lines (+50 / −20), under 3000. Landing needs the maintainer's merge or an APPROVED review from GOVERNED_APPROVERS. The PR body says so. The PR is a draft, and mergeable_state is blocked.
  • PR body. It says Fixes #31090. It routes item 3 and gives the spec wording owed for seed.zod.ts :24, :27, :171-172, :195-196 and :116-117. That wording is accurate against main. The 维护者速读 section has six lines. The body also gives the code-site table, the token readings (which match mine), the sweep hits and the deleted-restatement table. The model waiver is stated without naming a model. No model identifier appears in the body, the diff or the commit (the trailer is model-free).
  • should-fix S4, item 3 has no card. The body says item 3 "is routed to the spec lane on a separate card". No such card is linked on docs + skills: teach that a deleted seed row stays deleted (#31021), and fix the stale seed-mode table in the objectstack-data skill #31090, and a search finds none. File it and link it from the card before merge. Put the diff.ts:81 CLI string on the same card or a sibling, so neither stale replace text gets lost.

④ Design judgment

  • One home per rule. Yes. The modes have one table in each surface. The landed-keys rule has its home in the mdx subsection, and seeds.md carries one sentence plus the reseed call. M1 and S1 only ask that home to name the doors that keep no record.
  • Token budget. Counted as ceil(bytes/4) with the ceilings from check-skills-token-ratchet.mjs on main. No row is raised.
File main head ceiling
references/seeds.md 1566 1555 1566
objectstack-data/SKILL.md 6078 6078 6078
evals/hooks-security-seeds-datasources.json 1753 1777 not ratcheted

The script exits 0, and the readings match the PR body.

  • should-fix S5, line length. seeds.md adds two lines over 120 B, the ignore and upsert table rows, at 123 B each. The longest line on main was 118 B. Trim each by a few words, for example "every replay writes declared columns back". That also pays toward S2. (The changeset paragraph and the mdx table row are also long, but those files are not skill files.)
  • nit N1. The mdx modes table links the landed-keys rule from the ignore row only. upsert withholds a removed row too, so link it there as well.
  • nit N2. seeds.md L48 says externalId is "used for upsert / update lookup". It is also the ignore default's match key, and the landed key. Optional to add.
  • Check-runs on this head, read 2026-11-07T07:26Z: 42 runs, 25 success, 12 skipped, 5 in progress, 0 failed.

Must-fix: M1. Should-fix: S1, S2, S3, S4, S5. Nit: N1, N2.

Implemented-by: claude/project-thread-ba8x76
Reviewed-by: session_01J8VVAj7CvjkGXvgXzd6RZ7

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

This branch has not been deployed

No deployments
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/s tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs + skills: teach that a deleted seed row stays deleted (#31021), and fix the stale seed-mode table in the objectstack-data skill

1 participant