Skip to content

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

Closed

Description

@objectstack-fleet

domain:devx#1 · sunhaolin · session_017xRBcTp5wXse9Zwgq8aFWV

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:

  1. 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.
  2. 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)

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

Mechanism

packages/metadata-protocol/src/protocol-publish-batch.ts, applySeedBodies:

  • One loader.load(...) call covers every seed body.
  • 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.

Full evidence chain: QA run #27392.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    • 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.
    • bug, pm:queue.

    Generated by Claude Code

  2. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Oct 29, 2026
  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F

    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).

  4. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    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"
      ]
    }
  5. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F

    ACCEPT → PR #27802 · head 1c64cb68 · 2026-10-30T00:11Z · landing sent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions