Filed unassigned from #10063 (the REST publish route now states ?package=). Recording the half that card's fence could not reach — the file surface for #10063 was packages/rest/src/**, and this lands in packages/metadata-protocol.
Not a blocker for #10063: measured, the value flows correctly end-to-end today. This is about the declared contract and a comment that has gone stale.
📝 Body re-posted once to repair a sanitizer strip: the original wrote the parameter as ?package= followed by an angle-bracketed id placeholder, and the stored bytes lost the placeholder (the mutation AGENTS.md's "GitHub mutates body BYTES" clause describes). Spelled PKG_ID below instead. No other edit.
What is true after #10063
POST /meta/:type/:name/publish now accepts ?package=PKG_ID and puts packageId on the request it hands publishMetaItem. That value reaches the gate and repo.promoteDraft intact — verified on main + the #10063 branch:
publishMetaItem forwards its whole request object to promoteDraftForPublish, and the one transform in between, canonicalizeMetaRequestType, is return type === request.type ? request : { ...request, type } — a spread, so no key is dropped.promoteDraftForPublishdoes declare packageId?: string | null and threads it into both limbs.
So the runtime path is sound. Two things are not.
1. The declared request type does not carry the field
publishMetaItem's request type declares type / name / organizationId / actor / message / _skipSeedApply — no packageId. The REST door reaches it through await (p as any).publishMetaItem({...}), so the cast is what makes the call compile, not the contract.
That makes the binding invisible to every typed caller: a caller that wants to state a package cannot discover it from the type, and a future refactor of publishMetaItem that destructures its request instead of forwarding it wholesale would silently drop the key. The failure mode would be the one this whole card family is about — narrowing that looks like it works and never fires.
⚠️environmentId is in exactly the same position on this call site today, so the fix should cover the declared shape rather than one key.
Same class as #9741 (Meta-read request schemas still omit previewDrafts / state / environmentId, so the REST call-site casts survive the organizationId catch-up), reached from the write side.
2. The in-tree comment now says the opposite of what happens
promoteDraftForPublish, on the gate limb:
publishPackageDrafts states it (a package publish, which is exactly the write this card is about); bare publishMetaItemnames no package and so narrows nothing, which is the correct answer rather than a gap — a promotion whose package is unstated has no declared dependency set to bound it.
and on its packageId docblock:
undefined (the publishMetaItem path, which names no package) keeps the historical "match any package" resolution.
Both were true when written. After #10063 the publishMetaItem path can name a package, and does whenever the HTTP caller states one. A future reader taking these at face value would conclude the REST door's forwarding is dead code.
⛔ Note the comment is still right about the thing it was really defending — that the package must not be read off draftForGate, because rowToItem projects a MetadataItem that carries no package id. That warning should survive any rewrite; only the "names no package" claim is stale.
Explicitly NOT this card
Widening MetadataItem in packages/spec (option 1 on #10063) stays out — that is a spec-contract change needing its own ruling, and nothing here needs it. This is metadata-protocol's own request type plus two comments.
Re-check
sed -n '/async publishMetaItem(request: {/,/_skipSeedApply/p' packages/metadata-protocol/src/protocol.ts
grep -n "names no package" packages/metadata-protocol/src/protocol.ts
grep -n "publishMetaItem({" packages/rest/src/rest-server.ts
Unassigned, concrete, awaiting first-touch grading.
Filed unassigned from #10063 (the REST publish route now states
?package=). Recording the half that card's fence could not reach — the file surface for #10063 waspackages/rest/src/**, and this lands inpackages/metadata-protocol.Not a blocker for #10063: measured, the value flows correctly end-to-end today. This is about the declared contract and a comment that has gone stale.
📝 Body re-posted once to repair a sanitizer strip: the original wrote the parameter as
?package=followed by an angle-bracketedidplaceholder, and the stored bytes lost the placeholder (the mutation AGENTS.md's "GitHub mutates body BYTES" clause describes). SpelledPKG_IDbelow instead. No other edit.What is true after #10063
POST /meta/:type/:name/publishnow accepts?package=PKG_IDand putspackageIdon the request it handspublishMetaItem. That value reaches the gate andrepo.promoteDraftintact — verified onmain+ the #10063 branch:publishMetaItemforwards its whole request object topromoteDraftForPublish, and the one transform in between,canonicalizeMetaRequestType, isreturn type === request.type ? request : { ...request, type }— a spread, so no key is dropped.promoteDraftForPublishdoes declarepackageId?: string | nulland threads it into both limbs.So the runtime path is sound. Two things are not.
1. The declared request type does not carry the field
publishMetaItem's request type declarestype / name / organizationId / actor / message / _skipSeedApply— nopackageId. The REST door reaches it throughawait (p as any).publishMetaItem({...}), so the cast is what makes the call compile, not the contract.That makes the binding invisible to every typed caller: a caller that wants to state a package cannot discover it from the type, and a future refactor of
publishMetaItemthat destructures its request instead of forwarding it wholesale would silently drop the key. The failure mode would be the one this whole card family is about — narrowing that looks like it works and never fires.environmentIdis in exactly the same position on this call site today, so the fix should cover the declared shape rather than one key.Same class as #9741 (
Meta-read request schemas still omit previewDrafts / state / environmentId, so the REST call-site casts survive the organizationId catch-up), reached from the write side.2. The in-tree comment now says the opposite of what happens
promoteDraftForPublish, on the gate limb:and on its
packageIddocblock:Both were true when written. After #10063 the
publishMetaItempath can name a package, and does whenever the HTTP caller states one. A future reader taking these at face value would conclude the REST door's forwarding is dead code.⛔ Note the comment is still right about the thing it was really defending — that the package must not be read off
draftForGate, becauserowToItemprojects aMetadataItemthat carries no package id. That warning should survive any rewrite; only the "names no package" claim is stale.Explicitly NOT this card
Widening
MetadataIteminpackages/spec(option 1 on #10063) stays out — that is a spec-contract change needing its own ruling, and nothing here needs it. This ismetadata-protocol's own request type plus two comments.Re-check
Unassigned, concrete, awaiting first-touch grading.