Symptom (live staging tenant, cloud#1593)
GET /meta/_drafts returns {"drafts":[]} while a sys_metadata row with state='draft', organization_id=NULL demonstrably exists — the single-read GET /meta/:type/:name?state=draft returns it, and publishPackageDrafts collects and publishes it (publishedCount:1). Every ambient "pending changes" surface (Home banner, the new composer bar) counts via the list, so users see 0 pending while the publisher would publish 1.
Mechanism
protocol.listDrafts (null-org caller — the rest route forwards no organizationId) → SysMetadataRepository.listDrafts builds where: { state:'draft', organization_id: null } and calls engine.find('sys_metadata', …). On a tenant kernel the SqlDriver auto-scopes any table carrying an organization_id column to the request's org — the injected organization_id='org_…' ANDs with the repo's own organization_id IS NULL → contradiction → zero rows, deterministically, for every request carrying an active org. The publisher's reads run scoping-exempt, hence the list/publisher split.
Same over-scope class as the earlier "global object reads empty" SqlDriver incident; this is that seam hitting the draft ledger.
Fix directions
- Run the repository's
sys_metadata reads under the scoping-exempt/system read context (the same posture publishPackageDrafts already has), or - exempt
sys_metadata from SqlDriver org auto-scoping (it manages its own organization_id semantics — the ADR-0005 overlay order IS an org-aware contract the driver's blanket injection corrupts).
Second, independent defect on the same surface (also cloud#1593): a console PUT /meta/:type/:name?mode=draft save does not inherit package_id from the active row it overlays — the draft lands package_id NULL, unpublishable by any package publish. The draft-save path should inherit the overlaid row's package (or the item's _packageId).
Cross-links: cloud#1593 (full evidence chain), objectstack#10219 (draft-ledger unification), objectui#5694 (the standing bar this blinds).
Symptom (live staging tenant, cloud#1593)
GET /meta/_draftsreturns{"drafts":[]}while asys_metadatarow withstate='draft',organization_id=NULLdemonstrably exists — the single-readGET /meta/:type/:name?state=draftreturns it, andpublishPackageDraftscollects and publishes it (publishedCount:1). Every ambient "pending changes" surface (Home banner, the new composer bar) counts via the list, so users see 0 pending while the publisher would publish 1.Mechanism
protocol.listDrafts(null-org caller — the rest route forwards no organizationId) →SysMetadataRepository.listDraftsbuildswhere: { state:'draft', organization_id: null }and callsengine.find('sys_metadata', …). On a tenant kernel the SqlDriver auto-scopes any table carrying anorganization_idcolumn to the request's org — the injectedorganization_id='org_…'ANDs with the repo's ownorganization_id IS NULL→ contradiction → zero rows, deterministically, for every request carrying an active org. The publisher's reads run scoping-exempt, hence the list/publisher split.Same over-scope class as the earlier "global object reads empty" SqlDriver incident; this is that seam hitting the draft ledger.
Fix directions
sys_metadatareads under the scoping-exempt/system read context (the same posturepublishPackageDraftsalready has), orsys_metadatafrom SqlDriver org auto-scoping (it manages its ownorganization_idsemantics — the ADR-0005 overlay order IS an org-aware contract the driver's blanket injection corrupts).Second, independent defect on the same surface (also cloud#1593): a console
PUT /meta/:type/:name?mode=draftsave does not inheritpackage_idfrom the active row it overlays — the draft landspackage_id NULL, unpublishable by any package publish. The draft-save path should inherit the overlaid row's package (or the item's_packageId).Cross-links: cloud#1593 (full evidence chain), objectstack#10219 (draft-ledger unification), objectui#5694 (the standing bar this blinds).