Repository navigation
Conversation
…edge Add createGfsWatchRouter for GET/PUT /api/v1/me/gfs/watch, mounted before the GFS router so it does not inherit authenticated-ip or aggregate-ip. - Chain: request id -> own per-IP limiter -> requireAuth -> per-user bucket (240/min), outside aggregate-ip. - buildUrl appends each value of a string[] so every repeated `k` reaches control-api in order; a non-list `k` is a 400 at the edge. - GET and PUT forward x-request-id and X-Forwarded-For = req.ip, and call control-api with redirect: 'manual'. - Propagate 400/401/403/409/426/429 and 5xx with body and allowed headers; any other ControlApiError and every transport failure become 503 watch_reopen with Retry-After: 2. x-watch-error is forwarded. - HEAD and other methods answer 405 with Allow: GET, PUT and never reach control-api; a safe() wrapper turns unexpected throws into 503 (or destroys the stream once headers are out), never next(err). Tests run ERA's real app against control-api's real watch router on an ephemeral port, comparing each response direct and through the edge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent-subscriptions-era Brings origin/dev (22dd421) through the synced parent branch. Conflict: external-rest-api releaseManifest.ts. Took the v0.12.1 release coordinates from dev and kept this branch's externalRestApiVersion, which tracks external-rest-api/package.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent-subscriptions-era
This was referenced Nov 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
createGfsWatchRouterin external-rest-api:GETandPUT /api/v1/me/gfs/watch, relayed to control-api's/external/gfs/watch(#1500).requireAuth, then a per-user bucket (240/min). Mounted before the GFS router, so it does not inherit theauthenticated-iporaggregate-ipbuckets.buildUrlappends each value of astring[], so every repeatedkreaches control-api in order. Akthat is not a string or a list of strings is a 400 at the edge.x-request-idandX-Forwarded-For = req.ip, and call control-api withredirect: 'manual'.ControlApiErrorand every transport failure become503 watch_reopenwithRetry-After: 2.x-watch-erroris forwarded.Allow: GET, PUTand never reach control-api.safe()wrapper turns unexpected throws into a 503 (or destroys the stream once headers are out); the router never callsnext(err).Why
The desktop reaches control-api only through ERA. The watch needs a route that streams, keeps the repeated
kparameters intact, and does not spend the aggregate GFS budget the regular GFS routes share: one long-lived stream per window plus small PUTs has a different shape from file reads.How
attachGfsRequestId,withGfsRequestId, acreateGfsEdgeLimiterfactory (each call has its own counter, which keeps the watch budgets separate), andrelayControlApiErrorextracted fromforwardControlApiError.TypeError('fetch failed')(or one with acause) counts as transport; any otherTypeErroris a defect: logged, then the same 503.Testing
routes.gfsWatchruns ERA's real app against control-api's real watch router on an ephemeral port (fakes only behind control-api), comparing each response direct and through the edge:kin order with the client address and request id, 64 keys open, 65 keys relayed as control-api's 400, a non-listkrejected at the edge;503 watch_reopenand is never followed (the non-JSON PUT redirect is the 502 above);routes.gfsWatch.defect: unexpected failure before headers answers 503, never 500; only fetch failures count as transport; a failure after headers cuts the stream.tsc --noEmitclean,npm test57 files / 582 tests passed.Risks / follow-ups
Stack
Live GFS subscriptions ship as a stack. The server half and the desktop half must reach
devtogether: the server PRs retire the route the current desktop uses, and the desktop PR needs the new watch route.devprincipal_deniedreasondevdevGET/PUT /external/gfs/watchGET/PUT /api/v1/me/gfs/watch1 and 2 are independent of the chain and safe to merge on their own (1 before 2, since the controller check in 2 mirrors the one in 1).
3 to 8 are one unit and should not be deployed partially:
gfs_resource_changedNOTIFY trigger, which nothing listens to yet.changedevents of a stream that is idle (no op in flight). Do not ship 5/6 without 7./api/v1/entity-changes/streamand control-api/external/entity-changes/stream), which the current desktop still uses. Do not ship 7 without 8.