Symptom
POST /api/v1/packages/publish on a live showcase boot answers 405 with Allowed: DELETE, GET, HEAD, PATCH.
That method list belongs to a different route. POST is the only verb the publish surface ever had, so a 405 naming four other verbs tells the caller "this path exists, just use another method" — when in truth no publish surface is served on this host at all, and every verb in that list would be operating on a package whose id is the literal string publish.
Expected: either the real publish handler (the route is ledgered — POST /api/v1/packages/publish in packages/rest/src/rest-route-ledger.ts, source: direct-mount, disposition: server-only), or an honest 404 for a surface that is not mounted here. A 405 whose Allowed set describes another route's methods is the one answer that misinforms.
Root cause
As far as the run locates it: the REST package registrar is not mounted on showcase. POST /packages/publish is owned by packages/rest/src/package-routes.ts (which is where marketplace publish landed — the file says so explicitly: "Marketplace publish lives at /packages/publish, NOT at the bare …"), and those REST package routes are service-gated, so on a stack without the package service they never register.
With the registrar absent, the path is still matched — by /packages/:id with id = "publish" — and the router derives its Allowed header from that route's method set, which is exactly DELETE, GET, HEAD, PATCH. The path is therefore "reachable" only by accident of the id-parameter route, and the 405 is generated by the wrong owner.
Related, closed: #3610 (POST /api/v1/packages publish-vs-install shape collision, closed by PR #3621) is the reconciliation that moved publish onto /packages/publish in the first place. This is not a regression of that fix's stated acceptance — it is the residue it did not cover: what the /packages prefix answers on a host where only one of the two owners is mounted.
Still live on origin/main as of 2026-08-11 (route present in package-routes.ts and in the REST ledger; gating unchanged).
Reproduction
- Boot showcase on a fresh isolated file DB (
SqlDriver / better-sqlite3); authenticate as admin@objectos.ai.
POST /api/v1/packages/publish with any body.
- Observe
405 with Allowed: DELETE, GET, HEAD, PATCH — not a handler answer, and not the 404 an unmounted surface should give.
- Cross-check the ledger:
POST /api/v1/packages/publish is a declared route, so the 405 is not "this route does not exist" either.
Source
Extracted from the QA run #7463 (framework a86db17).
Symptom
POST /api/v1/packages/publishon a live showcase boot answers 405 withAllowed: DELETE, GET, HEAD, PATCH.That method list belongs to a different route.
POSTis the only verb the publish surface ever had, so a 405 naming four other verbs tells the caller "this path exists, just use another method" — when in truth no publish surface is served on this host at all, and every verb in that list would be operating on a package whose id is the literal stringpublish.Expected: either the real publish handler (the route is ledgered —
POST /api/v1/packages/publishinpackages/rest/src/rest-route-ledger.ts,source: direct-mount,disposition: server-only), or an honest 404 for a surface that is not mounted here. A 405 whoseAllowedset describes another route's methods is the one answer that misinforms.Root cause
As far as the run locates it: the REST package registrar is not mounted on showcase.
POST /packages/publishis owned bypackages/rest/src/package-routes.ts(which is where marketplace publish landed — the file says so explicitly: "Marketplace publish lives at/packages/publish, NOT at the bare …"), and those REST package routes are service-gated, so on a stack without the package service they never register.With the registrar absent, the path is still matched — by
/packages/:idwithid = "publish"— and the router derives itsAllowedheader from that route's method set, which is exactlyDELETE, GET, HEAD, PATCH. The path is therefore "reachable" only by accident of the id-parameter route, and the 405 is generated by the wrong owner.Related, closed: #3610 (
POST /api/v1/packagespublish-vs-install shape collision, closed by PR #3621) is the reconciliation that moved publish onto/packages/publishin the first place. This is not a regression of that fix's stated acceptance — it is the residue it did not cover: what the/packagesprefix answers on a host where only one of the two owners is mounted.Still live on
origin/mainas of 2026-08-11 (route present inpackage-routes.tsand in the REST ledger; gating unchanged).Reproduction
SqlDriver/ better-sqlite3); authenticate asadmin@objectos.ai.POST /api/v1/packages/publishwith any body.405withAllowed: DELETE, GET, HEAD, PATCH— not a handler answer, and not the 404 an unmounted surface should give.POST /api/v1/packages/publishis a declared route, so the 405 is not "this route does not exist" either.Source
Extracted from the QA run #7463 (framework a86db17).