Skip to content

resolveDraftOrgScopeForPublish probes without the package dimension, so a package-stating per-item publish can resolve the wrong org scope and answer no_draft #11003

Description

@os-elon

Filed unassigned from #10350, which was scoped to publishMetaItem's declared request type plus three stale comments. This is the behavioural half that the third of those comments turned out to be sitting on. Not fixed there — it needs its own fixture and its own ruling on precedence.

What is true today

publishMetaItem resolves WHICH ORG SCOPE's draft it means before promoting, via resolveDraftOrgScopeForPublish (#10219, packages/metadata-protocol/src/protocol.ts). Both of that helper's probes read sys_metadatawithout the package_id dimension:

constinOrg=awaitthis.engine.findOne('sys_metadata',{where: {organization_id: requestOrgId,type: singularType, name,state: 'draft'},});if(inOrg)returnrequestOrgId;constinEnv=awaitthis.engine.findOne('sys_metadata',{where: {organization_id: null,type: singularType, name,state: 'draft'},});returninEnv ? null : requestOrgId;

That was correct, and its in-tree comment said so, for the reason it gave: the per-item door named no package, so promoteDraft resolved with "match any package" and these reads asked the same question it would.

#10063 changed the premise.POST /meta/:type/:name/publish?package=PKG_ID now states a package, publishMetaItem forwards it, and promoteDraftForPublish threads it into repo.promoteDraft — whose whereFordoes constrain package_id. So the scope probe and the promote can now ask different questions.

The consequence

With two packages holding drafts for one (type, name) in different org scopes — the ADR-0048 coexistence protocol-publish-drafts-package-scope.test.ts exists for — publishing the one that is env-wide fails:

  • app.other holds a draft for (object, shared_ticket) in org1
  • app.demo holds a draft for the same (object, shared_ticket) env-wide (organization_id IS NULL)
  • caller: POST /meta/object/shared_ticket/publish?package=app.demo, session org org1

Probe 1 is package-agnostic, finds app.other'sorg1 row, and returns org1. The promote then looks in org1withpackage_id = 'app.demo', finds nothing, and the door answers 404 [no_draft] — while app.demo's draft is sitting env-wide, publishable, and was the row the caller named.

The inverse shape also exists: a probe that lands env-wide because the caller's own org row belongs to another package.

Why it was not fixed in #10350

#10350's fence was the declared type and the comments; changing which scope a publish resolves is a behaviour change needing its own pins, and the correct precedence between the ADR-0005 overlay order and the ADR-0048 package key is a real decision rather than a mechanical repair. The stale claim in that comment was corrected in #10350 (it no longer tells the reader the door cannot name a package) and the comment now points here; the behaviour is untouched.

The decision this needs

Threading packageId into both probes is the obvious move, but it is not obviously right on its own:

  1. Probe with the package — the probes then ask exactly the promote's question. Cost: a caller stating a package can no longer discover a draft of that (type, name) authored with no package binding, which today it can.
  2. Probe with the package, falling back to package-agnostic — preserves today's reach, but reintroduces a two-question resolution, which is the shape publishPackageDrafts can promote (and drain) ANOTHER package's draft row — the promote resolves the draft without the ADR-0048 package dimension #8907 was about.
  3. Leave it and refuse loudly — detect the disagreement and answer something better than a bare no_draft.

Option 1 is the contract-first reading and the one I would start from, but it is a narrowing of a live door, so it wants the maintainer's call rather than an agent's.

Re-check

sed -n '/private async resolveDraftOrgScopeForPublish/,/return inEnv ? null : requestOrgId;/p' packages/metadata-protocol/src/protocol.ts

Unassigned, concrete, awaiting first-touch grading.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions