Skip to content

finding(runtime/docs): projectResolution: 'required' is documented as registering ONLY scoped routes for package handlers, but the dispatcher mounts /packages* unscoped unconditionally #17432

Description

@os-justin

Found while landing #16781. Pre-existing, unchanged by that PR, and filed rather than fixed there: removing a currently-mounted route surface is a different change with a different blast radius than adding a missing door.

The contract, verbatim

content/docs/api/environment-routing.mdx, the projectResolution table:

| required | Registers only environment-scoped routes for data/meta/AI/automation/package handlers. | Hardened clients that always pass an environment id. |

"only environment-scoped" and "package handlers" are both explicit.

What the code does

packages/runtime/src/dispatcher-plugin.ts honours that for three of the four families named — registerAutomationRoutes, registerActionRoutes, registerAIRoutes all sit inside the required branch and drop their unscoped mounts:

if (enableProjectScoping && projectResolution === 'required') {
    registerAutomationRoutes(`${prefix}/environments/:environmentId`);
    registerActionRoutes(`${prefix}/environments/:environmentId`);
    registerAIRoutes(`${prefix}/environments/:environmentId`);
} else {
    
}

The package routes are not in that branch at all. registerPackageRoutes(prefix) is called unconditionally, several hundred lines earlier, so GET/POST/PATCH/DELETE /api/v1/packages* stay mounted on a required host regardless.

Why it matters rather than being a doc nit

required is the posture a host picks to guarantee every caller names an environment. A host that selected it to close the unscoped surface still serves the whole unscoped /packages family — including DELETE /api/v1/packages/:id and the destructive lifecycle verbs — with no environment segment in the URL. The operator's mental model and the served surface disagree, and the doc is what set the model.

State after #16781

#16781 added the scoped package mount, so on a required host the scoped routes the doc promises now exist. The unscoped ones the doc says should be absent still do too. The asymmetry is recorded in a comment at the mount site rather than silently tidied into a removal.

Two options, both a decision rather than a repair

  1. Move the code to the doc — put registerPackageRoutes in the same three-way branch as its siblings. This REMOVES a mounted route surface on required hosts; it needs a ruling on whether any host relies on it, since it can 404 a caller that works today.
  2. Move the doc to the code — state that package routes are always mounted unscoped and that required narrows only data/meta/AI/automation.

⛔ Not choosing here; option 1 is a wire removal and belongs to whoever owns that call.

Related: #16781 · #14503 · content/docs/api/environment-routing.mdx.

Activity

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions