Filed unassigned from #9612 (PR #10058), which implements package-closure narrowing at the runtime publish gate. Recording the half that card's fence could not reach.
Blocked-by: #9612
What is true after #9612
The gate narrows objects to the written item's package closure when the caller can state a packageId. Two write doors reach it:
| door |
states a package? |
narrows? |
saveMetaItem (direct active save) |
yes — request.packageId |
yes |
publishPackageDrafts (batch package publish) |
yes — the batch names the package |
yes |
publishMetaItem (single-item draft→active) |
no |
no |
The third row is the one worth a card. Studio's designer saves ?mode=draft and POSTs /publish on every edit, so the promotion door is the hot one for the surface the gate exists to protect — and it is the one that keeps receiving the whole tenant.
Why it could not simply be wired
The package binding is genuinely stored: sys_metadata.package_id, and SysMetadataRepository.listDrafts already projects it. But the gate reads the draft through repo.get(ref, { state: 'draft' }), whose rowToItem builds a MetadataItem — { ref, body, hash, parentHash, authoredBy, authoredAt, message, seq }. There is no package id on that shape, so draftForGate.packageId is undefined on every path.
That is the trap worth recording: reading it there would have compiled, typed cleanly, and produced narrowing that never fires while looking exactly like narrowing that does. #10058 deliberately does not do it, and says so in a comment at the call site.
MetadataItem is a packages/spec contract, so widening it — or adding a package-aware read seam beside it — is a decision outside #9612's fence.
Options, for triage rather than as a recommendation
- Add the owning package to
MetadataItem (spec change; every repository implementation has to answer for it).
- Give
publishMetaItem an optional packageId in its request, as promoteDraftForPublish already has — pushes the question to the caller, which is where saveMetaItem already answers it.
- Resolve it inside
promoteDraftForPublish from the draft row it is about to promote, without widening the returned item shape.
Route 2 is the smallest and matches the existing saveMetaItem shape; route 3 costs a read on a path that is already reading. Not a dev's pick — option 1 changes a contract.
Re-check command
grep -n "rowToItem(ref: Pick" packages/metadata-protocol/src/sys-metadata-repository.ts
grep -n "packageId" packages/metadata-protocol/src/protocol.ts | grep -i promotedraftforpublish
Unassigned, observation-adjacent but concrete, awaiting first-touch grading.
Filed unassigned from #9612 (PR #10058), which implements package-closure narrowing at the runtime publish gate. Recording the half that card's fence could not reach.
Blocked-by: #9612What is true after #9612
The gate narrows
objectsto the written item's package closure when the caller can state apackageId. Two write doors reach it:saveMetaItem(directactivesave)request.packageIdpublishPackageDrafts(batch package publish)publishMetaItem(single-item draft→active)The third row is the one worth a card. Studio's designer saves
?mode=draftand POSTs/publishon every edit, so the promotion door is the hot one for the surface the gate exists to protect — and it is the one that keeps receiving the whole tenant.Why it could not simply be wired
The package binding is genuinely stored:
sys_metadata.package_id, andSysMetadataRepository.listDraftsalready projects it. But the gate reads the draft throughrepo.get(ref, { state: 'draft' }), whoserowToItembuilds aMetadataItem—{ ref, body, hash, parentHash, authoredBy, authoredAt, message, seq }. There is no package id on that shape, sodraftForGate.packageIdisundefinedon every path.That is the trap worth recording: reading it there would have compiled, typed cleanly, and produced narrowing that never fires while looking exactly like narrowing that does. #10058 deliberately does not do it, and says so in a comment at the call site.
MetadataItemis apackages/speccontract, so widening it — or adding a package-aware read seam beside it — is a decision outside #9612's fence.Options, for triage rather than as a recommendation
MetadataItem(spec change; every repository implementation has to answer for it).publishMetaIteman optionalpackageIdin its request, aspromoteDraftForPublishalready has — pushes the question to the caller, which is wheresaveMetaItemalready answers it.promoteDraftForPublishfrom the draft row it is about to promote, without widening the returned item shape.Route 2 is the smallest and matches the existing
saveMetaItemshape; route 3 costs a read on a path that is already reading. Not a dev's pick — option 1 changes a contract.Re-check command
Unassigned, observation-adjacent but concrete, awaiting first-touch grading.