Repository navigation
Description
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 → create
return { handled: true, response: { status: 201, body: { success: true, data: link, link } } };
// GET /share-links?object&recordId → list
return { 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.tsx
setLinks(body.links ?? body.data ?? []);
const newLink = 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
cloudforbody.link/body.linksreads againstPOST|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.datafallbacks in objectui'sShareDialog(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.
Activity
os-zhuang commentedon Jul 30, 2026
PR #4049 does steps 1, 2 and 4. Two corrections to what I wrote above.
The cloud sweep came back clean — the blocker was smaller than I assumed. I said it "has to happen there" and that I couldn't see the repo; I could, and it took one pass.
cloudtouches share-links in exactly two places (objectos-runtime/src/artifact-kernel-factory.ts,service-cloud/src/default-environment-plugins.ts), and both only registerSharingServicePlugininto a per-environment kernel withregisterShareLinkRoutes: false— precisely so this dispatcher serves the paths. No calls to the endpoints, no body reads, nobody.link/body.linksin the repo at all.Step 2 was wrong about the status. "Drop the duplicate keys and route both through
deps.success(...)" only works for list.deps.successhardcodes status 200, andPOST /share-linksis a 201 — which is the same reason the/keysdomain hand-builds its own 201. So create stays hand-built (minus the duplicate key, matching/keys' shape) and list moves onto the helper.Step 3 I'm declining, and it was my recommendation, so worth saying why. I wrote that deleting objectui's
body.links ?? body.datawas "worth doing even if 1–2 are deferred", because a tolerance kept past its reason reads as "both shapes are still live".That conflates two different things. Before #3983 this was drift — two live producers genuinely disagreeing, and the consumer had to sniff. After #3983 and this PR it is version skew — one producer changed, and the consumer still supports the older one. Those want opposite treatment: drift should be deleted at the producer (which is what these PRs did), skew should be kept until a version gate retires it.
Concretely:
@object-ui/componentspublishes standalone against peer deps, so a new console can meet an older framework. Dropping the??turns that case from working into a silently empty share list — no error, no empty-state, just no links. That's a poor trade for removing one??. It should go when there's a declared minimum framework version to hang it on, not before.Leaving this issue to close with #4049; step 3 is not part of it.
Generated by Claude Code
fix(runtime)!: the /share-links dispatcher domain stops emitting a du…
chore(guard): extend the route-envelope check to the dispatcher domai…
test(spec): envelopeViolations — the conformance check BaseResponseSc…
test(runtime): the dispatcher envelope check uses the shared predicate (