Skip to content

metadata-protocol: a refused package publish half-lands — every draft promoted before the refused item goes live, despite "没有内容生效 / nothing took effect" #32396

Closed

Description

@os-bob

Edition: ObjectOS Cloud (os-test environment, reached over the MCP connection)
Date seen: 2026-11-11

What happened

Package app.nyd2 (app leave_management 「请假管理」) had 11 pending drafts, in this draft order:

1–5. view nyd2_leave_request.leave_ledger / leave_month_ledger / leave_pending / leave_status_board / leave_calendar
6. dataset nyd2_leave_request_ds
7. dashboard leave_stats
8. app leave_management
9. object nyd2_leave_request ← refused
10. permission app_nyd2_member_access
11. seed nyd2_leave_request_sample

Studio › 变更 › 全部发布 refused item 9:

object 'nyd2_leave_request' · field 'leave_no' (autonumber "QJ{YYYYMM}-{0000}") — format interpolates {YYYYMM}, but object 'nyd2_leave_request' has no field named 'YYYYMM' — generation will always throw.

and the toast said:

未发布:1 项需要修改。… 没有内容生效:软件包作为一个整体发布,其余草稿会等待这项修复。

But items 1–8 were published anyway. Right after the refusal:

  • the package's pending drafts dropped from 11 to 3 (object, permission, seed: exactly item 9 and everything after it);
  • describe_metadata(app, leave_management) returns _unpublished: false;
  • verify_build reports appsLive: [{ name: "leave_management", live: true }], while every view probe fails with OBJECT_NOT_FOUND: Object 'nyd2_leave_request' not found.

So the launcher now serves an app whose every nav entry opens a view on a table that does not exist.

Expected

ADR-0067 D2 / the publish dialog's own text ("发布将一次性发布此包全部 11 个待发布草稿:要么全部生效,要么都不生效"): a refusal of any item rolls back every promotion in the batch, and nothing goes live.

Why this looks like a rollback that covered nothing

publishPackageDrafts (packages/metadata-protocol/src/protocol-history-and-publish.ts, Phase 1) promotes ordered drafts one by one inside inTxn(...) and rethrows on the first failure, expecting the engine transaction to roll back earlier promotions. The landed set is exactly the prefix of ordered before the failing item. That fits either:

The outcome text is also wrong: the refusal path reports "nothing took effect" without checking that the rollback did anything.

Related, closed: #3066 (package-draft publish not turn-atomic: a commit CAN half-land), #7880 (ambient txStore rollback covers nothing).

Secondary: the refusal was not visible before publish

The bad autonumber format was staged through MCP create_metadata(type: object) → status: drafted with no warning, and MCP verify_build then answered errors: 0 ("structurally clean"). The lint-autonumber-formats rule is only enforced at publish. That is the save-vs-publish verdict parity #26707 / #26856 closed, but verify_build (and the create_metadata result) still do not surface it. After the half-landed publish, verify_build also said "every built app is live and reachable from the launcher — safe to tell the user the app is ready to open", although the app's only object is missing.

Steps

  1. Over MCP, create an app package with a few views/app drafts plus an object draft that has an autonumber field with autonumberFormat: "QJ{YYYYMM}-{0000}" (any unsupported {TOKEN}), with the object drafted after the views/app.
  2. verify_build → 0 errors.
  3. Studio › 变更 › 全部发布 → refused on the object, toast says nothing took effect.
  4. describe_metadata(app, …) → _unpublished: false; the pending-draft count dropped to the object plus the drafts after it.

Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 11, 2026

    @objectstack-fleet
    Contributor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first touch → domain:server · p1 · pm:queue · 2026-11-11T06:55Z

    ⛔ Not a claim.

    • Lane: domain:server (packages/metadata-protocol, the package publish door). It was measured on ObjectOS Cloud over MCP, but the door is the framework's.
    • Grade: p1. The door promises a whole-package publish ("没有内容生效"), yet a refusal leaves every draft before the refused item live. Here that made an app, a dashboard and a dataset go live over an object that did not. It reaches every package publish that hits a refusal.
    • Ask: a refusal at any item leaves the environment exactly as before the publish. Pin the measured eleven-draft order, refusing at item 9.

    Generated by Claude Code

  2. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    priority:p1High: required for production / M2
    on Nov 11, 2026
  3. objectstack-fleet commented on Nov 11, 2026

    @objectstack-fleet
    Contributor

    domain:server#6 · huangyiirene · session_01J8VVAnTcb4cZVkPdhkPicW

    Claim: PM loop round 3
    Session: session_01J8VVAnTcb4cZVkPdhkPicW
    Account: huangyiirene
    Branch: claude/issue-32396-publish-all-or-nothing
    Worktree: objectstack-issue-32396
    Domain: domain:server
    Seat: domain:server#6
    File surface: packages/metadata-protocol/src/protocol-history-and-publish.ts (publishPackageDrafts, the package publish door) and the draft-promotion and rollback helpers it calls inside packages/metadata-protocol/src/**; their tests; root .changeset/. Stop and report if the fix needs packages/metadata (the store) or packages/spec. (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default tier (dispatch-gates --tier: "floor sonnet · default opus · ceiling fable"; PM judgment: default), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Responsibility: metadata-protocol package publish door in objectstack (platform card) | ADR-0067 D2 promises all-or-nothing; the rollback covered nothing in the reported run | every package publish that hits a refusal (measured on ObjectOS Cloud over MCP, 2026-11-11)
    Thread-read: 6789258148
    Premise: re-measure owed (the card's reading is on cloud; step 0 reproduces it in-repo with a package whose later draft is refused)
    Serial constraints cleared: no open PR touches protocol-history-and-publish.ts or the publish path (every open PR's file list read at claim; this seat's PR #32327 touches only a packages/metadata test).
    Maintainer scope order: p1 and p1-blockers only. #32396 is p1.

    Claimed at 2026-11-11T07:01Z.

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 11, 2026
  5. objectstack-fleet commented on Nov 11, 2026

    @objectstack-fleet
    Contributor

    domain:server#6 · huangyiirene · session_01J8VVAnTcb4cZVkPdhkPicW
    os-dev-report

    {
    "issue": 32396,
    "status": "done",
    "branch": "claude/issue-32396-publish-all-or-nothing",
    "pr": "#32454",
    "session": "session_01J8VVAnTcb4cZVkPdhkPicW",
    "premise_still_valid": true,
    "summary": "Reproduced in-repo (cd9f896). The card's eleven drafts, refused at item 9 by the real publish-time autonumber lint, on an engine modelled on ObjectQL.transaction() over a driver declaring transactionsUnsupported (the remote Turso transport): items 1-8 stayed active while the response said refused with publishedCount 0. Cause: publishPackageDrafts chose atomicity by typeof engine.transaction === 'function', and the engine degrades to no rollback (ADR-0119 D1). The writes-not-joining hypothesis does not produce the prefix shape in-repo, because a cross-datasource business write is refused at item 1. Which driver the reported cloud environment runs on was not measured from here. Fix, only where engineCanRollBack(engine) is false: Phase 0 judges every draft through promoteDraftForPublish({judgeOnly:true}) before any write; a Phase-1 failure undoes every landed promotion through undoLandedPromotions (created rows removed, edited rows restored to their prior version, consumed drafts put back byte-identical, each write routed through restoreArtifactRegistryView); an undo that fails is named in failed[] without BATCH_ABORTED, in one console.error, and in the audit note. Runtimes that can roll back are unchanged. Surface widened beyond the claim, both test or ledger only: the objectql orchestration test's promote count, and scripts/engine-double-contract.pinned.json (written by the gate). packages/metadata and packages/spec are untouched. Deviations: single-file vitest runs, the ablation and the final-head build plus test subset ran unlocked, because the shared verify lock was held by a sibling's verify-c.sh (pid 2930) for more than 30 minutes; the rest of the metadata-protocol suite and the runtime suites are narrowed out and left to CI Test Core.",
    "tests": "Head 4831a9b: metadata-protocol typecheck exit 0; metadata-protocol build exit 0; 61 publish/draft/batch/commit/revert test files of metadata-protocol 'Test Files 61 passed (61) / Tests 1542 passed (1542)', including the new protocol-publish-drafts-all-or-nothing.test.ts (9 pins) and both stored-row censuses; 11 objectql publishPackageDrafts files via rebuilt dist 'Test Files 11 passed (11) / Tests 169 passed (169)'. Before the fix (cd9f896): the store pin was red with exactly app/dashboard/dataset/5 views active. Ablation at ed1ed88 via scripts/ablation-replace.mjs: anchor 'const canRollBack = engineCanRollBack(this.engine);' replaced by the pre-fix probe, anchor x1->x0, blob 92a0e25b57a3->9d513072b99e, suite imports src (no build in between) -> 'Tests 5 failed | 4 passed (9)' (store, read-face/side-effects, judge-first and both undo pins red); restored: blob == HEAD 92a0e25b57a3, git diff HEAD empty.",
    "gates": "dispatch-gates --commands at 4831a9b: 80 derived; --ran 'accounted for 80'. 73 source-reading families exit 0 at 4831a9b; check:dts-closure, check:sourcemap-no-sources-content and check:published-files exit 0 after the build; check:type-check-debt and check:type-check-coverage exit 0 at ec95718 (later commits typecheck clean); check:dual-build-cjs-loads and check:lean-entry-closure exit 3 PREREQUISITE NOT MET -> NOT MEASURED locally (CI). Red at the first run, fixed on the branch: engine-double-contract (ledger --write), error-code-casing (D6b markers), objectql-double-limit (find applies limit), where-matcher (double refuses unimplemented combinators). Changed-file eslint --no-inline-config exit 0. PR check-runs at 4831a9b when reported: 40 total; Governed Surface Queue Guard success; Build Core, Check Changeset and Dogfood Regression Gate in_progress, 20 in_progress overall (in_progress, not awaited).",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "0 — no MCP GitHub calls",
    "api_writes": "3 — through the fleet-write relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (draft create), POST /repos//issues/32454/assignees (claim Account huangyiirene), POST /repos//issues/32396/comments (this report); git pushes not counted",
    "open_questions": [],
    "security_findings": "none",
    "out_of_scope_findings": [
    "class: b · reach: named producer, code read, not measured through the door: the POST /packages/:id/publish-drafts handler (packages/runtime/src/domains/packages.ts, ADR-0045 visibility flip block) runs whatever publishPackageDrafts' outcome, so a refused publish still sets _unpublished false on any app already ACTIVE for the package · evidence: ADR-0067 D2 'no partial visibility flip'; no outcome guard before the flip at 767f110 · Seam: spec:ADR-0067 D2 visibility flip -> runtime:packages.ts publish-drafts flip · dedupe words: publish-drafts, visibility flip, _unpublished, refused publish, unhiddenApps",
    "carrier: maintainer · ADR-0067 D2 driver caveat ('atomicity is a SQL-driver guarantee. Production is SQL') does not match the remote Turso production transport; the door now keeps D2 without the driver; whether the caveat line is amended is a Tier H edit · noted, not filed",
    "carrier: seat · the card's Secondary (verify_build and the create_metadata result not surfacing the autonumber refusal before publish) is not addressed by this PR · noted, not filed"
    ]
    }

  6. objectstack-fleet commented on Nov 11, 2026

    @objectstack-fleet
    Contributor

    domain:server#6 · huangyiirene · session_01J8VVAnTcb4cZVkPdhkPicW

    Close-out · #32396 · 2026-11-11T17:37Z

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions