You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
POST /api/v1/packages is a publish-vs-install shape collision — REST registers first and shadows the dispatcher (#3587 finding) #3610
Found by the #3587 tranche-2 audit; ledgered as mismatch in packages/rest/src/rest-route-ledger.ts (PR #3609). This is the only route in the monorepo where the same verb+path carries two different semantics on two surfaces.
The collision
REST (packages/rest/src/package-routes.ts:49): POST /api/v1/packages = registry publish. Requires {manifest, metadata} in the body; 400 Missing required fields: manifest, metadata otherwise.
Dispatcher (packages/runtime/src/domains/packages.ts): POST /packages = install — different body, different semantics; client.packages.install targets this shape (dispatcher route ledger row POST /packages → packages.install).
Registration order decides: the production stack (packages/cli/src/commands/serve.ts:1748-1780) registers the REST plugin FIRST, the dispatcher second, and the underlying routers are first-match-wins with no specificity sorting (documented at rest-server.ts:1866-1877).
Consequence
In any stack where the package service resolves (REST's package routes are service-gated, rest-api-plugin.ts:300-321), client.packages.install's POST lands on the publish handler and should 400 — the SDK's install path is broken exactly in the fullest deployments. In stacks without service-package, the dispatcher serves install fine, which is why nothing obviously screams. (Not yet reproduced end-to-end — the audit is static; a boot-and-call repro should be the first step.)
The other three REST package routes (GET /packages, GET /packages/:id, DELETE /packages/:id) shadow their dispatcher twins compatibly (list additionally merges registry + database), so only POST is semantically wrong.
Options
A (recommended): consolidate on the dispatcher domain. Port what's unique in the REST handlers (registry+db merge on list; protocol.deletePackage full uninstall, Package uninstall does not clean up data-plane permission rows by package_id (ADR-0086 D3 wiring gap) #2747) into domains/packages.ts, then delete package-routes.ts and its plugin wiring. Kills a RouteManager-bypassing registrar too (the ledger's direct-mount source shrinks to external-datasource only). The dispatcher domain is the audited, 17-route, ledgered surface — one owner for /packages.
B: rename REST's publish to POST /packages/publish and keep both surfaces. Smaller, but leaves two owners for one prefix and the shadowing hazard for future routes.
Acceptance
One surface owns POST /api/v1/packages; a boot-level test proves client.packages.install works in a full stack (package service present).
Both route ledgers updated (mismatch row resolved; conformance suites green).
Activity
docs(api): metadata-api packages section — bare POST installs, publis…
fix(rest,client)!: reconcile the two REST↔client mismatches the audit…