Skip to content

The /share-links dispatcher domain emits data AND a legacy link/links beside it — a producer-side shim that outlived its readers #4038

Description

@os-zhuang

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

  1. Sweep cloud for body.link / body.links reads against POST|GET /share-links.
  2. 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.
  3. 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).
  4. 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

  1. self-assigned this
    on Jul 30, 2026
  2. os-zhuang commented on Jul 30, 2026

    @os-zhuang
    ContributorAuthor

    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. cloud touches share-links in exactly two places (objectos-runtime/src/artifact-kernel-factory.ts, service-cloud/src/default-environment-plugins.ts), and both only register SharingServicePlugin into a per-environment kernel with registerShareLinkRoutes: false — precisely so this dispatcher serves the paths. No calls to the endpoints, no body reads, no body.link / body.links in 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.success hardcodes status 200, and POST /share-links is a 201 — which is the same reason the /keys domain 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.data was "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/components publishes 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions