feat(features,service,web): automations on the hosted oRPC router

Merged
CH
1d ago
+598-500

W4 of the oRPC migration plan: moves automations, automations/automation, automations/run and automations/runs onto the hosted oRPC router, server and client together. automations/webhook stays exactly as it was: its route, its segment rewrite and its address, which the client still only displays.

What changed

  • Contract (packages/features/src/contract/automations.ts): seven procedures on today's public paths: list, create (201), read, change, remove, run and runs. Each output is its wire schema flipped.
    • JSON bodies use exactInput, and path params plus a body use exactBodyInput with inputStructure: "detailed". Both are added to contract/base.ts with the shared names. They refuse an excess key, as the old readEither did, and the existing "body names a key it should not" test still holds.
    • The list's query stays lenient, as it was.
    • run uses outputStructure: "detailed" to keep answering 201 for a new run and 200 for a run its key already made.
  • Server (automations-procedures.ts, which replaces automations-app.ts):
    • The handler bodies are the old ones.
    • accountMiddleware and bodyLimit(MAXIMUM_BODY_BYTES) sit on the resource (hosted.automations.use(…)), so they run before input validation, in the old order. An unsigned caller gets 401 and an oversized body gets 413, whatever they sent. run adds its own tighter bodyLimit.
    • bodyLimit(maxBytes) is added to orpc/base.ts with the shared name and signature, ahead of W5.
    • A path id that is not a UUID is still not-found.
    • The old HttpRouter group and its package export are deleted.
  • Web: the four route files now export default orpcRoute. The three parameterized SEGMENT_REWRITES rows are marked orpc: true, and api-rewrites.json and vercel.json were regenerated. HostedRouterSeams is now an intersection, and DEPLOYMENT_SEAMS gains eveOrigin.
  • Client (automation-client.ts): every call goes through askHosted(hostedClient.automations.*). HostedRefused and HostedUnanswered map to the same AUTOMATION_CALL_FAILUREs, unreached included. The public interface and the callers in packages/host are unchanged. The accountCall plumbing, readAnswer and jsonOf are deleted.

Tests

  • automations-app.test.ts becomes automations-procedures.test.ts and runs through hostedFetch({ automations: automationsProcedures(seams) }).
    • Its assertions are unchanged. The only fixture change is that the installations list grows from 16 to 20 for one more test.
    • Its request helper now sends content-length, which the platform hands every function.
  • New test: an unsigned caller with a malformed body gets 401 invalid-token; a list filter named twice gets 400 invalid-request; an oversized body gets 413 request-too-large.
  • apps/web/tests/orpc-route.test.ts: /api/automations/{id}/runs and PATCH /api/automations/{id} go end to end through the committed rewrite table, the dispatcher and orpcRoute. An unsigned call answers the old bytes: {"error":"invalid-token"}, application/json, and no cache-control.
  • automation-client.test.ts passes unchanged over fakeCloudApi.
  • models-procedures.test.ts now goes through hostedFetch({ models: modelsProcedures(…) }), the shared convention.
  • automation-webhook-app.test.ts makes its automations through the new procedures.

Accepted gaps (shared across W3–W17)

  • A body that is not JSON at all, or one sent without content-type: application/json, is refused 400 before any procedure runs, even for an unsigned caller. Every client of ours sends JSON.

  • The body is checked against its schema before the handler looks the automation up. A malformed PATCH or run on an automation the caller cannot reach now gets 400, where it used to get 404.

  • bodyLimit reads only Content-Length. Until W5 adds the handler-level RequestLimitHandlerPlugin, a chunked body with no declared length is read whole before validation.

Checks run locally

Typecheck passes for features, service, host and web. Vitest passes for the touched suites and the whole features suite. pnpm lint (oxlint), pnpm knip, pnpm discarded-effect, scripts/repository-checks.sh and function-rewrites.ts --check all pass.

Made with Luke


Devin Review