You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
metadata-protocol: when one seed in a publish-drafts batch fails, the seedApplied receipt reports inserted 0 / updated 0 although earlier seeds wrote rows, and later seeds are published but never applied #27393
QA-source: #27392 · platform-core.seed-mode-matrix · observed beside the clauses
What happens
POST /api/v1/packages/ID/publish-drafts applies every published seed body in one loader call. If one seed in the batch fails, for example because it names an object that does not exist, two things go wrong:
The receipt under-reports what was written.seedApplied answers {"success":false,"inserted":0,"updated":0,"error":"Object 'seedrepro_nope' not found"}, but the rows of the seeds ahead of the failing one are already in the table.
The batch is neither applied nor rolled back. Seeds after the failing one are published (publishedCount counts them and failed is []) but never applied. The broken seed itself stays published.
The response's probes block does carry {"code":"seed_not_applied","severity":"error"}. But the counters an author or Studio reads say that nothing was written, and that is false.
Reproduction (fresh showcase boot, admin bearer)
POST /api/v1/packages with {"manifest":{"id":"com.objectstack.qa.seedrepro","name":"R","version":"1.0.0","scope":"project","type":"app","engines":{"protocol":"^18"}}} → 201.
PUT /api/v1/meta/object/seedrepro_item?mode=draft&package=com.objectstack.qa.seedrepro with {"name":"seedrepro_item","label":"R","sharingModel":"public_read_write","fields":{"name":{"type":"text","label":"Name"}}}, then POST /api/v1/packages/com.objectstack.qa.seedrepro/publish-drafts with {} → outcome published.
PUT /api/v1/meta/seed/r_good?mode=draft&package=com.objectstack.qa.seedrepro with {"object":"seedrepro_item","mode":"upsert","records":[{"name":"R1"},{"name":"R2"}]} → 200.
PUT /api/v1/meta/seed/r_missing_object?mode=draft&package=com.objectstack.qa.seedrepro with {"object":"seedrepro_nope","mode":"upsert","records":[{"name":"Z"}]} → 200.
POST /api/v1/packages/com.objectstack.qa.seedrepro/publish-drafts with {}.
Expected:seedApplied counts what landed (inserted 2), or the batch's seed writes roll back together.
Actual: outcome published, publishedCount 2, failed [], and seedApplied{success:false, inserted:0, updated:0, error:"Object 'seedrepro_nope' not found"}. Meanwhile GET /api/v1/data/seedrepro_item returns R1 and R2, and GET /api/v1/meta/seed/r_missing_object answers 200.
With three seeds in the order [good, bad, good], the first seed's rows are written and the third seed's are not. Published on its own, the third seed applies.
This was reproduced on two independent boots and databases at c4c515ad.
When it throws, the catch returns a hard-coded { success: false, inserted: 0, updated: 0, error }. This discards the counts of the datasets that had already been written, and nothing undoes those writes.
The loader stops at the failing dataset, so later datasets never run.
Where a fix would go
Either make the counts truthful on failure (carry the loader's partial summary, or the per-dataset results, into the receipt), or make the batch's seed writes atomic. Separately, it should be decided whether a seed that fails to apply should stay published.
Read fresh. In a publish-drafts batch where one seed fails, the seedApplied receipt reports inserted 0 / updated 0, although earlier seeds wrote rows. Later seeds are published but never applied, and the failing seed stays published. A receipt that says nothing was written when rows were written is wrong data, so p2.
The fix picks one semantics and states it: all or none, or per-seed counts with the failing seed named. ⛔ The counters always describe what was written.
It is extracted from the 18.3.0 RC run, so it is listed in wave anchor [sweep] v18.3.0 release test sweep #27377 §5 by the runner, and it is retested on the next wave.
Claim: PM loop round 4
Session: session_01L6bhgUCizoYWQVJWjhdR6F
Account: os-sales
Branch: claude/issue-27393-seed-receipt-counts
Worktree: objectstack-issue-27393
Domain: domain:server
Seat: domain:server#2
File surface: packages/metadata-protocol/src/protocol-publish-batch.ts (the seed-apply receipt), the seed loader's partial summary only if it does not already expose one (seed-loader.ts, read first), and their tests (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: default (dispatch-gates --tier: no path-derived mandate), effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: metadata-protocol publish-drafts seed receipt (this lane) | the receipt's counters must describe what was written (triage) | every Studio/CLI author who publishes a batch with seeds
Thread-read: 6489203467
Premise: holds (read on main 81d00a4e 2026-10-29T22:05Z: the catch in protocol-publish-batch.ts (~L123-140) returns hard-coded inserted: 0, updated: 0 after a partial load; the success arm (~L119) reads r.summary)
Semantics (PM, to be measured): truthful aggregate counts of what was written before the failure, and the failing seed named in error, inside the existing seedApplied shape (success/inserted/updated/error/errors). A per-seed breakdown would be a spec edit → stop and report.
Serial constraints cleared: none (no open PR edits these files; #27444 is protocol-authoring-gate.ts / draft-verdict wiring).
{
"issue": 27393,
"status": "done",
"branch": "claude/issue-27393-seed-receipt-counts",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27802",
"session": "session_01L6bhgUCizoYWQVJWjhdR6F",
"premise_still_valid": true,
"summary": "Semantics: truthful aggregate counts inside the existing seedApplied shape. SeedLoaderService.load() now records on the error it throws (module-private WeakMap, read via seedLoadPartialWrites, not re-exported from the index) the rows written so far (finished datasets plus the in-flight dataset's counters) and the in-flight dataset's object; applySeedBodies' catch reads it instead of hard-coding 0/0 and prefixes error with \"seed apply stopped at the seed for object 'OBJECT': \" before the unchanged sentence. Publishing, stop-at-failure and no-rollback are unchanged; no spec edit. Premise measured: red on the unfixed tree (expected +0 to be 2). The seed is named by object, not by metadata name: the bodies applySeedBodies receives carry no name, and passing names in would mean editing protocol.ts. A container restart interrupted the run once; resumed from pushed state.",
"tests": "Red first: objectql publish-package-drafts-seed-receipt-counts.test.ts on the unfixed tree: Tests 2 failed | 2 passed (expected +0 to be 2). At head 1c64cb68: that file 5 passed (real publishPackageDrafts, real ObjectQL engine and SeedLoaderService, in-memory driver, since objectql has no sqlite driver; counts read with engine.find): good+missing gives inserted 2; update+missing gives updated 2; control gives {success:true,inserted:3,updated:0}; first-fails gives 0/0 with 0 rows at the engine; only-fails gives 0/0. metadata-protocol seed-loader-seed-files.test.ts 22 passed (3 new: in-flight throw after a written row gives {inserted:3,updated:0,failedObject:'pw_doc'}; pre-dataset throw gives 0/0; foreign error gives undefined). Full metadata-protocol suite at 86b1fe73 (post-merge): Test Files 281 passed | 4 skipped, Tests 21805 passed | 26 skipped. Ablation 1 (scripts/ablation-replace.mjs, hard-coded zeros restored in protocol-publish-batch.ts, metadata-protocol rebuilt, ablation-dist-preflight marker present in 2 built files): Tests 2 failed | 3 passed (expected +0 to be 2). Restored blob 838c1b9a1d2c == HEAD, git diff HEAD empty, rebuilt, preflight --absent ok, 5 passed. Ablation 2 (in-flight term removed from seed-loader.ts, src-resolved): 1 failed (inserted 2 vs 3), restored blob 6557a0c4b948 == HEAD.",
"gates": "Local: dispatch-gates --commands found 59 families. Run with exit codes captured before any pipe; --ran reconciliation gave 58 run and 1 NOT MEASURED: pnpm check:dual-build-cjs-loads exited 3, PREREQUISITE NOT MET because it needs every package's dist/; left to CI. First pass: check:engine-double-contract, check:objectql-double-limit and check:query-options-erasure were red because of the new test doubles. Fixed in 1c64cb68 (pins moved into the existing seed-files test so they reuse its pinned double; the driver now honours limit; the find options are typed). All three re-ran exit 0 on 1c64cb68, as did type-check-debt, undeclared-dep-imports, watch-hint-literal, where-matcher, nul-bytes, cross-package-test-inputs, test-source-alias and slot-lookup. The other families ran exit 0 on 86b1fe73; 1c64cb68 changed test files only. typecheck exit 0 for metadata-protocol and objectql; eslint --no-inline-config on both packages exit 0. CI on 1c64cb68 at report time: 21 check-runs, 18 in_progress, 3 skipped. Not waited on.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 — fleet-write relay pr_create (workflow_dispatch to the relay, which created PR #27802 as a draft with no assignees or reviewers); post-stamped comment on #27393 (this report). Plus 3 git pushes, which are not REST writes.",
"open_questions": [],
"out_of_scope_findings": [
"class: none (observation, not filed) · reach: none measured, because the stock metadata-protocol always returns seedApplied for a batch with seeds, so this fallback is not reached · packages/runtime/src/domains/packages.ts handlePackages fallback (applyPublishedSeeds, own ../seed-loader.js copy) reports seedApplied with no inserted/updated on a throw · carrier: none · noted in PR Acceptance notes",
"carrier: none · the seed is named by object rather than seed name, because bodies carry no name; naming by seed name would mean threading names through protocol.ts publishPackageDrafts · noted in PR Acceptance notes"
],
"line_budget": "diff vs merge base: 5 files changed, about +560/-16, under the 3000-line human-merge threshold; no skills/** touched",
"deviations": "Door test lives in packages/objectql: metadata-protocol cannot depend on objectql's real engine. It uses an in-memory driver, not sqlite, because objectql has no sqlite devDependency. The loader-level pins were added to the existing seed-loader-seed-files.test.ts so they reuse its pinned engine double and leave the ledger unchanged. The commit carries the model-free trailer pair from AGENTS.md, not the harness's model-named Co-Authored-By. The first gate run straddled a container restart; families affected by the last commit were re-run on 1c64cb68. The report comment has the seat signature on line 1 and os-dev-report alone on line 2: post-stamped refuses any first line that is not the signature.",
"files_changed": [
".changeset/27393-seed-receipt-partial-counts.md",
"packages/metadata-protocol/src/protocol-publish-batch.ts",
"packages/metadata-protocol/src/seed-loader.ts",
"packages/metadata-protocol/src/seed-loader-seed-files.test.ts",
"packages/objectql/src/publish-package-drafts-seed-receipt-counts.test.ts"
]
}
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 一条命令把应用跑起来 | platform-core.seed-mode-matrix | P②
Triage: p2 ·
domain:server·area:devpath· 2026-10-29T05:56Z⛔ Not a claim.
publish-draftsbatch where one seed fails, theseedAppliedreceipt reportsinserted 0 / updated 0, although earlier seeds wrote rows. Later seeds are published but never applied, and the failing seed stays published. A receipt that says nothing was written when rows were written is wrong data, so p2.bug,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Claim: PM loop round 4
Session:
session_01L6bhgUCizoYWQVJWjhdR6FAccount:
os-salesBranch:
claude/issue-27393-seed-receipt-countsWorktree:
objectstack-issue-27393Domain:
domain:serverSeat:
domain:server#2File surface:
packages/metadata-protocol/src/protocol-publish-batch.ts(the seed-apply receipt), the seed loader's partial summary only if it does not already expose one (seed-loader.ts, read first), and their tests (stop on breach; explain in the report)Container & model:
S,mode:subagent,model: default (dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
metadata-protocol publish-drafts seed receipt (this lane) | the receipt's counters must describe what was written (triage) | every Studio/CLI author who publishes a batch with seedsThread-read: 6489203467
Premise: holds (read on main
81d00a4e2026-10-29T22:05Z: the catch inprotocol-publish-batch.ts(~L123-140) returns hard-codedinserted: 0, updated: 0after a partial load; the success arm (~L119) readsr.summary)Semantics (PM, to be measured): truthful aggregate counts of what was written before the failure, and the failing seed named in
error, inside the existingseedAppliedshape (success/inserted/updated/error/errors). A per-seed breakdown would be a spec edit → stop and report.Serial constraints cleared: none (no open PR edits these files; #27444 is
protocol-authoring-gate.ts/ draft-verdict wiring).objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
os-dev-report
Posted 2026-10-29T23:45Z.
{ "issue": 27393, "status": "done", "branch": "claude/issue-27393-seed-receipt-counts", "pr": "https://github.com/objectstack-ai/objectstack/pull/27802", "session": "session_01L6bhgUCizoYWQVJWjhdR6F", "premise_still_valid": true, "summary": "Semantics: truthful aggregate counts inside the existing seedApplied shape. SeedLoaderService.load() now records on the error it throws (module-private WeakMap, read via seedLoadPartialWrites, not re-exported from the index) the rows written so far (finished datasets plus the in-flight dataset's counters) and the in-flight dataset's object; applySeedBodies' catch reads it instead of hard-coding 0/0 and prefixes error with \"seed apply stopped at the seed for object 'OBJECT': \" before the unchanged sentence. Publishing, stop-at-failure and no-rollback are unchanged; no spec edit. Premise measured: red on the unfixed tree (expected +0 to be 2). The seed is named by object, not by metadata name: the bodies applySeedBodies receives carry no name, and passing names in would mean editing protocol.ts. A container restart interrupted the run once; resumed from pushed state.", "tests": "Red first: objectql publish-package-drafts-seed-receipt-counts.test.ts on the unfixed tree: Tests 2 failed | 2 passed (expected +0 to be 2). At head 1c64cb68: that file 5 passed (real publishPackageDrafts, real ObjectQL engine and SeedLoaderService, in-memory driver, since objectql has no sqlite driver; counts read with engine.find): good+missing gives inserted 2; update+missing gives updated 2; control gives {success:true,inserted:3,updated:0}; first-fails gives 0/0 with 0 rows at the engine; only-fails gives 0/0. metadata-protocol seed-loader-seed-files.test.ts 22 passed (3 new: in-flight throw after a written row gives {inserted:3,updated:0,failedObject:'pw_doc'}; pre-dataset throw gives 0/0; foreign error gives undefined). Full metadata-protocol suite at 86b1fe73 (post-merge): Test Files 281 passed | 4 skipped, Tests 21805 passed | 26 skipped. Ablation 1 (scripts/ablation-replace.mjs, hard-coded zeros restored in protocol-publish-batch.ts, metadata-protocol rebuilt, ablation-dist-preflight marker present in 2 built files): Tests 2 failed | 3 passed (expected +0 to be 2). Restored blob 838c1b9a1d2c == HEAD, git diff HEAD empty, rebuilt, preflight --absent ok, 5 passed. Ablation 2 (in-flight term removed from seed-loader.ts, src-resolved): 1 failed (inserted 2 vs 3), restored blob 6557a0c4b948 == HEAD.", "gates": "Local: dispatch-gates --commands found 59 families. Run with exit codes captured before any pipe; --ran reconciliation gave 58 run and 1 NOT MEASURED: pnpm check:dual-build-cjs-loads exited 3, PREREQUISITE NOT MET because it needs every package's dist/; left to CI. First pass: check:engine-double-contract, check:objectql-double-limit and check:query-options-erasure were red because of the new test doubles. Fixed in 1c64cb68 (pins moved into the existing seed-files test so they reuse its pinned double; the driver now honours limit; the find options are typed). All three re-ran exit 0 on 1c64cb68, as did type-check-debt, undeclared-dep-imports, watch-hint-literal, where-matcher, nul-bytes, cross-package-test-inputs, test-source-alias and slot-lookup. The other families ran exit 0 on 86b1fe73; 1c64cb68 changed test files only. typecheck exit 0 for metadata-protocol and objectql; eslint --no-inline-config on both packages exit 0. CI on 1c64cb68 at report time: 21 check-runs, 18 in_progress, 3 skipped. Not waited on.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 — fleet-write relay pr_create (workflow_dispatch to the relay, which created PR #27802 as a draft with no assignees or reviewers); post-stamped comment on #27393 (this report). Plus 3 git pushes, which are not REST writes.", "open_questions": [], "out_of_scope_findings": [ "class: none (observation, not filed) · reach: none measured, because the stock metadata-protocol always returns seedApplied for a batch with seeds, so this fallback is not reached · packages/runtime/src/domains/packages.ts handlePackages fallback (applyPublishedSeeds, own ../seed-loader.js copy) reports seedApplied with no inserted/updated on a throw · carrier: none · noted in PR Acceptance notes", "carrier: none · the seed is named by object rather than seed name, because bodies carry no name; naming by seed name would mean threading names through protocol.ts publishPackageDrafts · noted in PR Acceptance notes" ], "line_budget": "diff vs merge base: 5 files changed, about +560/-16, under the 3000-line human-merge threshold; no skills/** touched", "deviations": "Door test lives in packages/objectql: metadata-protocol cannot depend on objectql's real engine. It uses an in-memory driver, not sqlite, because objectql has no sqlite devDependency. The loader-level pins were added to the existing seed-loader-seed-files.test.ts so they reuse its pinned engine double and leave the ledger unchanged. The commit carries the model-free trailer pair from AGENTS.md, not the harness's model-named Co-Authored-By. The first gate run straddled a container restart; families affected by the last commit were re-run on 1c64cb68. The report comment has the seat signature on line 1 and os-dev-report alone on line 2: post-stamped refuses any first line that is not the signature.", "files_changed": [ ".changeset/27393-seed-receipt-partial-counts.md", "packages/metadata-protocol/src/protocol-publish-batch.ts", "packages/metadata-protocol/src/seed-loader.ts", "packages/metadata-protocol/src/seed-loader-seed-files.test.ts", "packages/objectql/src/publish-package-drafts-seed-receipt-counts.test.ts" ] }objectstack-fleet commentedon Oct 30, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
ACCEPT → PR #27802 · head
1c64cb68· 2026-10-30T00:11Z · landing sent.