Skip to content

publishPackageDrafts answers success:false for a publish that refused nothing, and leaves no trace of that exit #10462

Description

@os-zhuang

What

publishPackageDrafts (packages/metadata-protocol/src/protocol.ts) ends with

return{success: failed.length===0&&published.length>0,publishedCount: published.length,
...
};

so a publish that had nothing to promote — no pending drafts for the requested packageId — resolves

{ "success": false, "publishedCount": 0, "failedCount": 0, "published": [], "failed": [] }

That is the same success:false a genuine ADR-0067 D2 refusal answers with. One boolean is carrying two facts ("nothing was refused" and "nothing landed") and the caller cannot tell them apart from success alone.

Why it matters

The no-op path is also the only publishPackageDrafts exit that leaves no trace at all. Both refusal returns (the pre-flight block and the Phase-1 unwind) write recordMetadataAudit refusal rows; this one writes nothing. So a caller that reads success:false as a refusal reports a rollback, and an operator who then goes looking for the rollback in the audit trail and the server log finds neither.

Measured on the cloud side (objectstack-ai/cloud#1488, fixed consumer-side in objectstack-ai/cloud#1492): the AI Studio publish tools graded that shape as a rollback and told users "发布被拒绝并整体回滚" over turns whose metadata was entirely state='active', then burned two automatic repair rounds re-authoring artifacts that were already correct. The cloud fix discriminates on failed.length > 0 — which works only because today both refusal returns happen to build a non-empty failed[]. That is an invariant no test states and nothing pins.

Suggested shape (not a ruling)

Make the answer say which of the two it is, rather than making every caller infer it from failed[]:

  • a distinct field on the response (e.g. publishedCount === 0 && failedCount === 0 given a first-class name), or
  • a reason / discriminant on the envelope, or
  • pin the current invariant explicitly ("every non-success return carries at least one failed[] entry") with a test, so consumers may rely on it.

Any of the three is fine; the thing worth deciding is that the distinction becomes part of the contract instead of a shape consumers reverse-engineer. Worth pairing with a log line on the no-op path so the exit is not invisible.

Filed from cloud#1488 by the dev agent that fixed the consumer half. No assignee.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions