Unify issue-family error envelopes and publish the route inventory #82
- AtlantisPleb opened this issue 23h ago
-
A Author 6h ago Shipped in
2cd0ed1onmain.The envelope
Every refusal from an issue-family
/api/v3route now carries the same six keys, built by the newOpenAgentsWeb.ApiError:{ "message": "Validation Failed", "code": "validation_failed", "status": 422, "documentation_url": "https://openagents.com/api/v3", "request_id": "GM5_fLaSSJDluDMAACUh", "errors": { "state": ["must be one of: open, closed, all"] } }errorsis always present and is{}when the failure is not field-level, so a client parses one shape. A code determines its status insideApiError, so a controller never picks a status for a failure the API has already named:Code Status unauthenticated401forbidden403agent_participation_forbidden403not_found404label_not_on_issue404dependency_not_found404validation_failed422delete_failed422What was inconsistent
Six mutually incompatible bodies for the same class of failure:
{"message": "Not Found"}for a missing resource,{"errors": {...}}for a rejected field,{"error": "forbidden"}for a refused person,{"error": {"code": ...}}for a refused agent,{"error": "invalid_api_token"}for a refused token, and the repository family'scode/message/request_idtriple.403alone had three renderings insideIssueController,CommentController, andProjectController. Reading one refusal taught a client nothing about the next.Backward compatibility
Nothing was renamed or removed.
messagekeeps its GitHub meaning and a missing resource still reads exactlyNot Found. Theerrorsmap keeps the field-to-messages shape local clients read. The two participation refusals and the three bearer-token pipelines keep theirerrorkey beside the envelope.priv/api-contracts/repositories-v1.jsonis untouched — the envelope is a superset of the three keys it describes, so its claim stays true and the CLI's vendored hash stays valid.Non-disclosure
Preserved by construction, not by care: a private resource and an absent one both refuse with
not_found, and no code distinguishes them.ApiErrorContractTestasserts the two bodies are byte-identical apart fromrequest_id.Route inventory
GET /api/v3now publishes the inventory beside the extension index from #100, rather than adding a second discovery surface. Each entry is derived fromRouter.__routes__/0throughApiRouteAuthority:{ "method": "GET", "path": "/api/v3/repos/{owner}/{repo}/issues/{issue_number}", "authority": "optional_bearer", "family": "issue", "errors": "envelope", "mutation": false }The document also carries
version,families, and anerrorssection publishing the envelope keys and the stable code table. Inventory entries went from one classification to three — principal, resource family, error contract — all mandatory.What fails CI
ApiRouteAuthorityTest— the router and the inventory disagree in either direction, or a classification does not match what the pipeline does to an anonymous request.ApiExtensionControllerTest— the published document omits a live route, or a route is missing an authority, family, or error classification.ApiErrorContractTest— dispatches every route classifiedenvelopeand fails on any body that is not the envelope. Verified by reverting one controller to%{message: "Not Found"}:GET /api/v3/repos/:owner/:repo/labels did not answer with the API error envelope. Keys: ["message"].
ConnCase.assert_api_error/4andapi_error_code/2replace the exact-equality assertions that previously pinned the old bespoke shapes, so a regression fails at the contract rather than at whichever test happened to name the shape it broke.Adoption
#80's project lifecycle and field validation routes are already converged — they route through
ProjectController'sunprocessable/2,unprocessable_changeset/2,forbidden/1, andnot_found/1, all of which now delegate toApiError. #81 adopts it by callingApiError.validation_failed(conn, %{field => [message]})orApiError.changeset(conn, changeset); a new code goes in the@codestable inlib/openagents_web/api_error.exwith the one status it always carries.Verification
POOL_SIZE=8 MIX_TEST_PARTITION=lane82 mix precommitpasses: 3175 tests, 0 failures, no warnings.Contract recorded as
FORGEAPI-001inINVARIANTS.md, with the envelope and route inventory documented indocs/github-api-issues-projects-assessment.mdanddocs/openagents-cli/api.md. - closed this as completed 6h ago
Outcome
Give every issue-family API route a stable error envelope and publish a complete route inventory derived from the Phoenix router.
Current behavior
The API routes are reachable through
openagents api, but validation and not-found shapes vary by controller, and the published contract can omit a route without failing CI.Source:
docs/2026-08-21-issues-projects-work-system-assessment.md, tracks A4 and A6.Contract
404non-disclosure for private resources.Acceptance criteria
Verification
Add golden contract tests, controller matrix tests, privacy tests, route-diff tests, and packaged CLI error checks. Run
mix precommit.Dependencies
Build on #7 and #9. Coordinate the CLI issue in
OpenAgentsInc/openagents#13.