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
⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
The two telemetry-ingest routes are near-identical handlers — the AMS one's own comment says it mirrors the ORB one ("same optional bearer-token gate, same hard body ceiling", src/api/routes.ts:4605-4607) — but their published operations disagree with the handlers and with each other.
POST /v1/ams/ingest (src/api/routes.ts:4609-4617) returns, in order: 401 unauthorized (:4610), 413 payload_too_large (:4612), 400 invalid_request (:4613), 400 on a handler error (:4615), and 200 on success (:4616). Its spec entry (src/openapi/internal-and-public-route-specs.ts:298-307) declares 202, 400, 401. The declared 202 is unreachable and the actual 200 is undeclared, and 413 is missing.
POST /v1/orb/ingest (src/api/routes.ts:4593-4603) returns 401 (:4594), 413 (:4596), 400 (:4597), 403 instance_unauthenticated (:4602), 400 (:4602), 200 (:4603). It is still a legacy registry.registerPath block (src/openapi/spec.ts:1961) declaring only 200 and 400, with no security key at all — because requiresApiToken exempts it (src/auth/route-auth.ts:77) precisely because it carries its own ORB_INGEST_TOKEN bearer. So the published document describes an unauthenticated write endpoint that cannot fail with 401.
Both are public write surfaces for third-party self-host instances, so their published contract is the only thing an integrator has.
Requirements
Move POST /v1/orb/ingest out of src/openapi/spec.ts:1961 and declare it as a SpecEntry in src/openapi/internal-and-public-route-specs.ts, next to the existing /v1/ams/ingest entry.
/v1/orb/ingest declares auth: "orb" (its bearer is ORB_INGEST_TOKEN, not a LoopOver API token), and responses 200, 400, 401, 403, 413.
/v1/ams/ingest's success response becomes 200 (its handler's real status). The 202 must be removed — it is unreachable.
/v1/ams/ingest additionally declares 413.
Both entries' 413 description names the body ceiling enforced by readOrbIngestBody.
Regenerate and commit apps/loopover-ui/public/openapi.json.
⚠️ Required pattern: mirror the existing /v1/ams/ingest entry in src/openapi/internal-and-public-route-specs.ts:298-307 (a SpecEntry with auth: "orb" and its own comment explaining the shared-secret header). It does NOT satisfy this issue to change the AMS handler to return 202 instead of correcting the spec (the 200 is the shipped, client-observed status and callers depend on it); to leave /v1/orb/ingest as a registerPath block in spec.ts and hand-add a security field; or to declare both 200 and 202 for the AMS route.
Deliverables
POST /v1/ams/ingest declares 200 (not 202), 400, 401, 413.
POST /v1/orb/ingest is declared in src/openapi/internal-and-public-route-specs.ts with auth: "orb" and responses 200, 400, 401, 403, 413; its registry.registerPath block is deleted from src/openapi/spec.ts.
In the regenerated apps/loopover-ui/public/openapi.json, POST /v1/orb/ingest has security: [{ "OrbBearer": [] }].
A new test file (e.g. test/unit/openapi-ingest-status-parity.test.ts) drives createApp() with a test env and asserts, for BOTH ingest routes, that a request with no bearer answers 401, that an oversized content-length answers 413, and that a valid authenticated batch answers 200 — and that each of those statuses is declared in buildOpenApiSpec() for that operation.
apps/loopover-ui/public/openapi.json regenerated and committed; test/unit/route-spec-ratchet.test.ts still passes in both directions.
All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example correcting the AMS 202-to-200 without moving /v1/orb/ingest off the legacy registration, or adding the status-parity test without the spec changes — does not resolve this issue.
Test Coverage Requirements
src/api/** and src/openapi/** are inside Codecov's src/** include; the 99% branch-counted patch gate applies. The new test must exercise both arms of each ingest handler branch it touches (authorized vs not, over-ceiling vs not). The 200-vs-202 assertion is the named regression test for this fix.
Expected Outcome
Both ingest operations publish the exact status set their handlers produce, and /v1/orb/ingest publishes the bearer it actually requires. A self-host integrator generating a client from the document gets a success-status check that matches production.
Context
The two telemetry-ingest routes are near-identical handlers — the AMS one's own comment says it mirrors the ORB one ("same optional bearer-token gate, same hard body ceiling",
src/api/routes.ts:4605-4607) — but their published operations disagree with the handlers and with each other.POST /v1/ams/ingest(src/api/routes.ts:4609-4617) returns, in order:401unauthorized (:4610),413payload_too_large (:4612),400invalid_request (:4613),400on a handler error (:4615), and200on success (:4616). Its spec entry (src/openapi/internal-and-public-route-specs.ts:298-307) declares202,400,401. The declared 202 is unreachable and the actual 200 is undeclared, and 413 is missing.POST /v1/orb/ingest(src/api/routes.ts:4593-4603) returns401(:4594),413(:4596),400(:4597),403instance_unauthenticated (:4602),400(:4602),200(:4603). It is still a legacyregistry.registerPathblock (src/openapi/spec.ts:1961) declaring only200and400, with nosecuritykey at all — becauserequiresApiTokenexempts it (src/auth/route-auth.ts:77) precisely because it carries its ownORB_INGEST_TOKENbearer. So the published document describes an unauthenticated write endpoint that cannot fail with 401.Both are public write surfaces for third-party self-host instances, so their published contract is the only thing an integrator has.
Requirements
POST /v1/orb/ingestout ofsrc/openapi/spec.ts:1961and declare it as aSpecEntryinsrc/openapi/internal-and-public-route-specs.ts, next to the existing/v1/ams/ingestentry./v1/orb/ingestdeclaresauth: "orb"(its bearer isORB_INGEST_TOKEN, not a LoopOver API token), and responses200,400,401,403,413./v1/ams/ingest's success response becomes200(its handler's real status). The202must be removed — it is unreachable./v1/ams/ingestadditionally declares413.readOrbIngestBody.apps/loopover-ui/public/openapi.json.Deliverables
POST /v1/ams/ingestdeclares200(not202),400,401,413.POST /v1/orb/ingestis declared insrc/openapi/internal-and-public-route-specs.tswithauth: "orb"and responses200,400,401,403,413; itsregistry.registerPathblock is deleted fromsrc/openapi/spec.ts.apps/loopover-ui/public/openapi.json,POST /v1/orb/ingesthassecurity: [{ "OrbBearer": [] }].test/unit/openapi-ingest-status-parity.test.ts) drivescreateApp()with a test env and asserts, for BOTH ingest routes, that a request with no bearer answers401, that an oversizedcontent-lengthanswers413, and that a valid authenticated batch answers200— and that each of those statuses is declared inbuildOpenApiSpec()for that operation.apps/loopover-ui/public/openapi.jsonregenerated and committed;test/unit/route-spec-ratchet.test.tsstill passes in both directions.All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example correcting the AMS 202-to-200 without moving
/v1/orb/ingestoff the legacy registration, or adding the status-parity test without the spec changes — does not resolve this issue.Test Coverage Requirements
src/api/**andsrc/openapi/**are inside Codecov'ssrc/**include; the 99% branch-counted patch gate applies. The new test must exercise both arms of each ingest handler branch it touches (authorized vs not, over-ceiling vs not). The 200-vs-202 assertion is the named regression test for this fix.Expected Outcome
Both ingest operations publish the exact status set their handlers produce, and
/v1/orb/ingestpublishes the bearer it actually requires. A self-host integrator generating a client from the document gets a success-status check that matches production.Links & Resources
src/api/routes.ts:4589-4617;src/openapi/internal-and-public-route-specs.ts:298-307;src/openapi/spec.ts:1961;src/auth/route-auth.ts:77-78.