Skip to content

Commit a5638b6

Browse files
docs(qa): seed-mode-matrix scores replace as declared = implemented bare insert (#31203)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XCHuoPDSh1whAU4gbrqZ7YX)_ Fixes #31140 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:QA 检查清单 `platform-core.seed-mode-matrix` 一项(仅 `docs/qa/platform-checklist/areas/platform-core.json`),把 `replace` 的预期姿态从「声明≠实现(声明删全表再插入,实际裸插入)」改成「声明=实现:只插入、不删任何行」,来源引文改引 `seed.zod.ts` 现有原文;revision 6→7,补一条 history。 - 为什么改:#31099(PR #31135)已把 `SeedMode` 的 `replace` 注释改成加载器的真实行为,清单这一项的姿态和引文因此变假,测试员会按一个已不存在的缺口打分。 - 风险与回滚:纯文档,不动代码、不发布;探针与判定不变(F 存活、G 插入、不得勾选「删除限于本对象」)。回滚即还原本 PR。 - 先看:`docs/qa/platform-checklist/areas/platform-core.json` 中 `acceptance[4].clause` 与 `source[0]` 两处。 - 两分钟验证:`pnpm check:platform-checklist`,预期 `OK — 16 areas, 360 items`;再 `grep -c "Delete ALL" docs/qa/platform-checklist/areas/platform-core.json`,预期 0。 - 需要你做的:无,席位落地。 ## What changes One checklist item, `platform-core.seed-mode-matrix`, in `docs/qa/platform-checklist/areas/platform-core.json` (+12 / -6): - `acceptance[4].clause`: the expected posture is now **declared = implemented**: `replace` inserts every record with no match and deletes nothing. It quotes both declarations on main (SeedMode's replace member and SeedSchema's `mode` description) and the loader arm that does it. F's survival is the declared behaviour, not a gap to record. The guard stays: no "replace deletes only its own object's rows" tick, and a future clear makes the clause stale and owes a scoping proof. - `source[0]`: the SeedMode citation now quotes text that exists on main ("Insert every record, no match, deleting nothing: for a cache table the caller cleared") and adds SeedSchema's `mode` description, instead of the removed delete-all wording. - Same item, same now-false posture, so re-worded with it: `title`, `steps[5]` (records F's survival as the declared posture instead of "the declared-vs-implemented gap") and `variants[3]`. - `revision` 6 → 7 plus a `history` row, as the checklist README's Change rule requires for a semantic edit (run records pin the revision they ran against). Unchanged: the probe itself (add F by hand, publish a replace seed declaring only G, F survives, G inserted), `acceptance[4].verify` / `evidence`, and every other clause. ## Why — the ruling's branch, measured The order rules: if the spec now describes bare insert, the row states it as the implemented and declared posture. Read on `origin/main` 658d84f: - **Spec (declared):** `packages/spec/src/data/seed.zod.ts` L27, `'replace', // Insert every record, no match, deleting nothing: for a cache table the caller cleared`; L214-215 in SeedSchema's `mode` describe: "`replace` does the same and deletes nothing: it is for a table the caller cleared". `git grep "Delete ALL"` over `packages/`: 0 hits. - **Loader (implemented):** `packages/metadata-protocol/src/seed-loader.ts` `writeRecord`, `case 'replace'`: a bare `engine.insert` with the comment "caller should have cleared the table"; `decideWriteAction` routes `insert` / `replace` / default to `{ action: 'insert' }`. - **No clearing caller:** `git grep` for a `replace` mode test or arm over `packages/**/*.ts` (tests excluded) finds only those two arms and `packages/cli/src/commands/diff.ts` L81, which words a plan line ("a replay deletes nothing and inserts these again, so rows already in the table stay"). So the branch taken is **declared = implemented**: the spec no longer declares a clear. ## Tests None owed: a QA ledger JSON, no code or tested behaviour changed, so no unit test and no ablation (os-dev: docs are not ablated). JSON parses (`python3 -c "import json; json.load(...)"`); edits were exact-string replacements asserted to match once each, so the hand formatting is kept. ## Acceptance notes - **Surface beyond the clause and citation.** The order named `acceptance[4]` and `source[0]`; `title`, `steps[5]` and `variants[3]` of the same item stated the same "declared delete-all, implemented insert" posture, and the checklist README requires the revision bump and history row. All in the one file the order names; nothing else touched. - **Not touched, by rule:** `docs/qa/platform-checklist/FOLLOW-UPS.md` row K6 still describes replace as declared ≠ implemented ("Delete ALL records, then insert"). The checker counts FOLLOW-UPS.md as append-only text (its line-citation limb: "history rows and FOLLOW-UPS.md — counted, not judged"), so it is history, not a live row. Noted, not filed. - The history row of revision 1 (2026-08-30) also narrates the old gap; history rows are append-only and stay as written. - Bounded by the order: no edit to `packages/spec/**` or the loader. ## Verification Gate union derived by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (16 families for this diff), run on head `6c5fe906`, reconciled with `--ran`: "16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED". <details><summary>16 gates, exit codes captured before any pipe</summary> - `node scripts/check-adr-links.mjs` (and `--self-test`): exit 0 / 0 - `node scripts/check-closing-keyword-parity.mjs` (and `--self-test`): exit 0 / 0 - `node scripts/check-comment-mask-corpus.mjs`: exit 0 - `node scripts/ci/plan-test-core-shards.mjs --self-test`: exit 0 - `pnpm --filter @objectstack/lint run check:doc-formula-expressions`: first run exit 3 (PREREQUISITE NOT MET: formula and lint not built; nothing measured), then after `pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` under the verify lock (VERDICT command-exit 0), exit 0 - `pnpm check:ci-filter-parity`, `check:cross-package-test-inputs`, `check:doc-authoring`, `check:driver-memory-census`, `check:gitlink-declared`, `check:nul-bytes`, `check:refd-timer-probe`, `check:watch-hint-literal`: exit 0 each - `pnpm check:platform-checklist`: exit 0, "check-platform-checklist: OK — 16 areas, 360 items (354 active, 1 planned) … symbol anchors: 1145/1155 resolved … 10 on the named #16898 residual" </details> - `node scripts/check-platform-checklist.mjs --changed-from origin/main`: exit 0 (no symbol anchor targets the changed file). - `pnpm lint` scoped: no package touched; the root eslint config does not lint the checklist JSON, so there is nothing to scope it to. - CI families are read from this PR's check-runs, not re-run here. `skip-changeset`: `docs/qa/**` ships in no package's `files[]`. --- _Generated by [Claude Code](https://claude.ai/code/session_01FEkQ5mfyhgv2EebbRFAVKL)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent b48490e commit a5638b6

1 file changed

Lines changed: 12 additions & 6 deletions

File tree

‎docs/qa/platform-checklist/areas/platform-core.json‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3053,10 +3053,10 @@
30533053
},
30543054
{
30553055
"id": "platform-core.seed-mode-matrix",
3056-
"title": "Seed mode matrix (insert/update/upsert/replace/ignore): each mode does exactly its verb through the ONE SeedLoaderService, no-op replays skip instead of churning, ignore reports skips — and `replace` is DECLARED delete-all-then-insert but IMPLEMENTED as bare insert (no caller clears the table): the posture is recorded, never ticked as scoped deletion",
3056+
"title": "Seed mode matrix (insert/update/upsert/replace/ignore): each mode does exactly its verb through the ONE SeedLoaderService, no-op replays skip instead of churning, ignore reports skips — and `replace` inserts and deletes nothing, DECLARED and IMPLEMENTED alike (no caller clears the table): its posture is scored as bare insert, never ticked as scoped deletion",
30573057
"since": "v15",
30583058
"status": "active",
3059-
"revision": 6,
3059+
"revision": 7,
30603060
"priority": "P1",
30613061
"surface": "api",
30623062
"personas": ["seeded admin (admin@objectos.ai / admin123)"],
@@ -3078,7 +3078,7 @@
30783078
"UPDATE: seed draft (mode update) touching A (changed value) and a nonexistent D → A updates, D is SKIPPED (update never creates); row count unchanged",
30793079
"IGNORE: seed draft (mode ignore) with A (changed value) and new E → A untouched (still the update-mode value; existing rows are never rewritten), E inserted; the skip is visible as A's unchanged value + count",
30803080
"INSERT: seed draft (mode insert) re-declaring A → a SECOND A-named row appears (no matching in insert mode); record the duplication; if the object declares a unique key on the natural key instead, record the refusal — either shape is the mode's real contract",
3081-
"REPLACE (expected-posture probe): add one row F by hand through /api/v1/data (a row the seed does NOT declare); publish a seed draft (mode replace) declaring only G → capture what happens to F. At head: F SURVIVES and G is inserted — the loader's replace case is bare insert with the comment 'caller should have cleared the table', and NO caller in the tree clears anything. Record the declared-vs-implemented gap; do NOT tick 'replace scoped to its own object' (there is no deletion to scope) and do NOT file F's survival as data loss (it is the opposite)",
3081+
"REPLACE (expected-posture probe): add one row F by hand through /api/v1/data (a row the seed does NOT declare); publish a seed draft (mode replace) declaring only G → capture what happens to F. At head: F SURVIVES and G is inserted — the loader's replace case is bare insert with the comment 'caller should have cleared the table', and NO caller in the tree clears anything, which is what SeedMode now declares for replace ('deleting nothing: for a cache table the caller cleared'). Score F's survival as the declared posture; do NOT tick 'replace scoped to its own object' (there is no deletion to scope) and do NOT file F's survival as data loss (it is the opposite)",
30823082
"idempotence tie-in: re-publish the upsert draft unchanged TWICE. The first replay may legitimately write, because steps 2–4 rewrote A's rows (update mode set A to a2, insert mode added a second A-named row), so it re-converges them to the seed; the SECOND replay is the idempotence probe — zero inserts and zero updates (all no-op skips), every row's updated_at untouched (the churn-avoidance isNoOpReplay exists for exactly this)",
30833083
"teardown: the writable package's own delete, or discard the run's own DB"
30843084
],
@@ -3108,7 +3108,7 @@
31083108
"evidence": "the post-insert row listing (or the refusal body)"
31093109
},
31103110
{
3111-
"clause": "EXPECTED POSTURE (declared ≠ implemented): `replace` is documented 'Delete ALL records in object then insert (Dangerous)' but the implementation is bare insert — writeRecord's replace arm inserts with the comment 'caller should have cleared the table' and NO caller anywhere clears it, so pre-existing rows SURVIVE a replace-mode publish. The run records this gap (a checklist-accuracy/product finding routed through the sweep's anchor, not re-filed per run); it must NOT tick a 'replace deletes only its own object's rows' claim — no deletion exists to scope. If a future head implements the clear, this clause is stale: revise, and then ALSO prove the deletion is scoped to the seed's own object before trusting it",
3111+
"clause": "EXPECTED POSTURE (declared = implemented): `replace` inserts every record with no match and deletes nothing. SeedMode's replace member declares 'Insert every record, no match, deleting nothing: for a cache table the caller cleared', SeedSchema's mode description says replace 'does the same and deletes nothing: it is for a table the caller cleared', and writeRecord's replace arm is that bare insert (its comment 'caller should have cleared the table'; NO caller anywhere clears it), so pre-existing rows SURVIVE a replace-mode publish. F's survival is the declared behaviour: neither a gap to record nor data loss to file; the run must NOT tick a 'replace deletes only its own object's rows' claim — no deletion exists to scope. If a future head implements a clear (in the loader or in a caller), this clause is stale: revise, and then ALSO prove the deletion is scoped to the seed's own object before trusting it",
31123112
"oracle": "api",
31133113
"verify": "F (undeclared, hand-inserted) still present after the replace publish; G inserted; grep evidence: the only 'replace' write arm is the insert in seed-loader.ts's writeRecord, and decideWriteAction (the batched mirror) routes replace to insert — cite the symbols, never a line number",
31143114
"evidence": "F's surviving read + G's read + the source excerpt"
@@ -3128,7 +3128,7 @@
31283128
"mode insert — no matching, always writes (duplicates on replay unless the object's own unique constraint refuses)",
31293129
"mode update — matched rows only; unmatched skipped, never created",
31303130
"mode upsert — create-or-update on externalId with no-op-replay skip (the default)",
3131-
"mode replace — DECLARED clear-then-insert, IMPLEMENTED bare insert (the expected-posture clause)",
3131+
"mode replace — bare insert, no matching, nothing deleted: DECLARED and IMPLEMENTED alike (the expected-posture clause)",
31323132
"mode ignore — insert-if-absent; existing rows never rewritten"
31333133
],
31343134
"enumSource": {
@@ -3138,7 +3138,7 @@
31383138
},
31393139
"traps": ["destructive-in-place", "seed-data-thin", "silent-coercion"],
31403140
"source": [
3141-
"packages/spec/src/data/seed.zod.ts#SeedMode (SeedMode — the five modes with their declared semantics, incl. replace's 'Delete ALL records … Dangerous' claim)",
3141+
"packages/spec/src/data/seed.zod.ts#SeedMode (SeedMode — the five modes with their declared semantics, incl. replace's 'Insert every record, no match, deleting nothing: for a cache table the caller cleared'), (SeedSchema's mode description — replace 'does the same and deletes nothing: it is for a table the caller cleared')",
31423142
"packages/metadata-protocol/src/seed-loader.ts#writeRecord (mode = dataset.mode || config.defaultMode), (existing-records preload for upsert/update/ignore ONLY — insert/replace never match), (writeRecord per-mode switch; the replace arm's bare insert + 'caller should have cleared the table' comment), (decideWriteAction — the batched mirror, replace → insert),+ (isNoOpReplay — seed-declared fields only, conservative on doubt)",
31433143
"packages/spec/src/data/seed-loader.zod.ts#defaultMode (defaultMode default 'upsert')",
31443144
"packages/metadata-protocol/src/protocol.ts#applySeedBodies+ (applySeedBodies — published seed bodies materialize through the same loader; failures returned, never thrown, surfaced as seedApplied)",
@@ -3182,6 +3182,12 @@
31823182
"date": "2026-10-28",
31833183
"change": "fixtures.requires[0] names the earlier refusal. Checklist-accuracy finding of the 18.2.0 wave 2 (QA run #26476, manual api set, at 756220c4; wave anchor #25807, slice B): a body name that mismatches the URL answered 400 VALIDATION_ERROR before the strict schema's 422 unrecognized_keys could. Measured live by that run; the requirement (no body name) is unchanged. No clause changed",
31843184
"ref": "#25807"
3185+
},
3186+
{
3187+
"revision": 7,
3188+
"date": "2026-11-07",
3189+
"change": "title, steps[5], acceptance[4].clause, variants[3] and source[0] re-worded: replace is now declared = implemented. #31099 (PR #31135) rewrote SeedMode's replace member and SeedSchema's mode description to what the loader does (insert every record, no match, deleting nothing), so the clause's 'declared ≠ implemented' posture went false and source[0] quoted the old delete-all-then-insert wording, which seed.zod.ts no longer holds. Read at base 658d84f5: seed.zod.ts SeedMode and SeedSchema mode describe text (no delete-all wording left), seed-loader.ts writeRecord's replace arm (bare insert, comment 'caller should have cleared the table') and decideWriteAction (replace routes to insert), and no other replace arm on the seed path (packages/cli/src/commands/diff.ts only words a plan line). The probe and its verdicts are unchanged: F survives, G is inserted, nothing is ticked as scoped deletion",
3190+
"ref": "#31140"
31853191
}
31863192
]
31873193
},

0 commit comments

Comments
 (0)