Skip to content

The compound-name PUT /meta/:type/:section/:name never threads ?mode=draft, while its single-segment twin does — the fourth divergence closed, a fifth left open #11712

Description

@os-zhuang

Measured while implementing #11391 (giving @objectstack/client's meta.saveItem the query-string options its route reads). Separate surface, filed rather than folded — it is a server-side route defect and #11391's ruling is client-side only.

What was measured

packages/rest/src/rest-server.ts registers two PUT doors onto one generic saveMetaItem:

  • single-segmentPUT /meta/:type/:name reads three query parameters and threads all three:
    if (refuseRepeatedQueryParams(req, res, ['force', 'package', 'mode'])) return;
    ...
    ...(force ? { force: true } : {}),
    ...(packageId ? { packageId } : {}),
    ...((typeof req.query?.mode === 'string'
    && req.query.mode.toLowerCase() === 'draft')
    ? { mode: 'draft' } : {}),
    
  • compound-namePUT /meta/:type/:section/:name reads two:
    if (refuseRepeatedQueryParams(req, res, ['force', 'package'])) return;
    ...
    ...(force ? { force: true } : {}),
    ...(packageId ? { packageId } : {}),
    
    There is no mode read and no mode in the request object it builds.

So PUT /api/v1/meta/object/views/all_leads?mode=draft is accepted and published live, while the byte-identical intent against a single-segment name stages a draft. The parameter is not refused — it is silently ignored, and the caller gets a 200 saying the save succeeded.

mode is also absent from the compound door's refuseRepeatedQueryParams list, so the #6877 repeated-parameter guard does not cover it there either.

Why this looks like the same class the twin pair keeps closing

The compound route's own comments describe it as "word for word the same operation" as its single-segment twin — one generic saveMetaItem reached by a name spelled in two segments — and record four divergences closed on that reason:

?mode is the same shape, one field along: one door reading a parameter and its literal twin ignoring it. #11095's comment explicitly scopes its own ruling ("⛔ NOT a licence for every door that reaches this gate") to the dispatcher, which has no query string at all — that carve-out does not describe this door, which has a query string and already reads two parameters off it.

Not asserted here

Whether the compound door should gain mode is a contract question, not folded into this report — the draft/publish door may have its own reason to be single-segment only. What is measured is only that the two doors disagree and that the disagreement is silent.

Where it would land

packages/rest/src/rest-server.ts (the compound PUT handler), plus the parity pins in packages/rest/src/meta-compound-save-force-parity.test.ts, which already pins the force half of exactly this parity.

Related: #11095 (threaded ?force on this same door), #11391 (the client-side half — that card is not addressed by this one), #7019 (the twin-parity ruling).

Measured on origin/main at 4ceae8ab0.


Generated by Claude Code


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions