Found while sweeping consumers for #3983. Not fixed there because deleting it needs a cloud-side check that is out of that PR's scope (Prime Directive #10).
The shim
packages/runtime/src/domains/share-links.ts answers create and list with the payload under two keys:
// POST /share-links → createreturn{handled: true,response: {status: 201,body: {success: true,data: link, link }}};// GET /share-links?object&recordId → listreturn{handled: true,response: {status: 200,body: {success: true,data: links, links }}};Note these bypass deps.success() — the domain's other three routes (revoke, resolve, messages) go through it and emit a clean { success: true, data }. Only these two hand-build a body, and the only reason to is the duplicate key.
That is a producer-side compatibility shim: the legacy link / links keys are there for readers written before the envelope existed. Prime Directive #12 is about deleting consumer-side sniffing by fixing the producer; this is the mirror image — a producer emitting two dialects at once so consumers never have to choose.
Why it can go now (and why not yet)
#3983 removes the reason it exists. Before that PR, the sharing plugin's routes answered bare { link } / { links } while this domain answered enveloped, so every consumer had to tolerate both:
// objectui packages/components/src/share/ShareDialog.tsxsetLinks(body.links??body.data??[]);constnewLink=created.link??created.data;
That ?? is reading the plugin's shape first and this domain's second. Once #3983 lands, both surfaces answer data, the fallback is dead code, and the duplicate key has no reader left in either repo.
What still needs checking before deletion: this domain is the designed primary surface for cloud's per-environment kernels (registerShareLinkRoutes: false, per the #2462 step-① note in the file header). Anything in cloud reading body.link / body.links off these two routes would break. I can't see that repo from this session, so the sweep has to happen there.
Suggested shape of the fix
- Sweep
cloud for body.link / body.links reads against POST|GET /share-links. - Drop the duplicate keys and route both through
deps.success(...) like the domain's other three routes — which also removes the two hand-built bodies. - Delete the now-dead
?? body.data / ?? created.data fallbacks in objectui's ShareDialog (they become unreachable, and leaving them implies a shape that no longer exists). - Add the two routes to the dispatcher's envelope coverage so a hand-built body can't come back.
Step 3 is the part worth doing even if 1–2 are deferred: a tolerance kept after its reason is gone reads as "both shapes are still live" to the next person, which is how #3833 happened.
Scope note
scripts/check-route-envelope.mjs does not cover this file — it scans route modules that write via res.json(...), and the dispatcher domains return { status, body } objects for a central sender instead. So this drift is invisible to that guard by construction. Whether the guard should grow a second mode for the domain handlers is a bigger question than this issue; worth raising separately if the dispatcher turns out to have more of these.
Found while sweeping consumers for #3983. Not fixed there because deleting it needs a cloud-side check that is out of that PR's scope (Prime Directive #10).
The shim
packages/runtime/src/domains/share-links.tsanswers create and list with the payload under two keys:Note these bypass
deps.success()— the domain's other three routes (revoke,resolve,messages) go through it and emit a clean{ success: true, data }. Only these two hand-build a body, and the only reason to is the duplicate key.That is a producer-side compatibility shim: the legacy
link/linkskeys are there for readers written before the envelope existed. Prime Directive #12 is about deleting consumer-side sniffing by fixing the producer; this is the mirror image — a producer emitting two dialects at once so consumers never have to choose.Why it can go now (and why not yet)
#3983 removes the reason it exists. Before that PR, the sharing plugin's routes answered bare
{ link }/{ links }while this domain answered enveloped, so every consumer had to tolerate both:That
??is reading the plugin's shape first and this domain's second. Once #3983 lands, both surfaces answerdata, the fallback is dead code, and the duplicate key has no reader left in either repo.What still needs checking before deletion: this domain is the designed primary surface for cloud's per-environment kernels (
registerShareLinkRoutes: false, per the #2462 step-① note in the file header). Anything incloudreadingbody.link/body.linksoff these two routes would break. I can't see that repo from this session, so the sweep has to happen there.Suggested shape of the fix
cloudforbody.link/body.linksreads againstPOST|GET /share-links.deps.success(...)like the domain's other three routes — which also removes the two hand-built bodies.?? body.data/?? created.datafallbacks in objectui'sShareDialog(they become unreachable, and leaving them implies a shape that no longer exists).Step 3 is the part worth doing even if 1–2 are deferred: a tolerance kept after its reason is gone reads as "both shapes are still live" to the next person, which is how #3833 happened.
Scope note
scripts/check-route-envelope.mjsdoes not cover this file — it scans route modules that write viares.json(...), and the dispatcher domains return{ status, body }objects for a central sender instead. So this drift is invisible to that guard by construction. Whether the guard should grow a second mode for the domain handlers is a bigger question than this issue; worth raising separately if the dispatcher turns out to have more of these.