Repository navigation
Conversation
Mount the HTTP boundary of the per-process GFS watch registry:
- GET opens the NDJSON stream: token, key parsing, external session
authentication (V1 only: 409 for V2, 426 for upgrade_required), listener
readiness and the open limiter, then hands off to registry.open.
- PUT mutates a live connection's subscription set: body validation (keys
capped at 64 before any per-element work), authentication, its own
limiter bucket before the authority lookup, and a gfs_watch_cmd NOTIFY
built by serializeWatchCmd; replies 202 {seq}.
- HEAD and every other method get 405 with Allow: GET, PUT, charged once on
the shared per-IP ingress limiter so OPTIONS no longer bypasses it.
- Every handler is total: one response writer, step/classify around each
await, x-watch-error on every non-2xx, 503 watch_reopen instead of 500.
- The router mounts ahead of the GFS router and reuses its ingress limiter
instance and request-id middleware; the ingress 429 on the watch route is
tagged by req.route so path spelling cannot drop the code.
- GFS_WATCH_HEARTBEAT_MS (default 20 s, range 5-25 s), registry start at
boot, stopRegistry inside stopOnce before server.close, and a
watch-database-pool shutdown step before the core pool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s-event-subscriptions-api-http Brings origin/dev (22dd421) through the synced parent branch. No textual conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s-event-subscriptions-api-http
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
Mounts the HTTP boundary of the GFS watch registry (#1499) in control-api.
GET /external/gfs/watch?k=<key>&k=...opens an NDJSON stream. Steps: token, key parsing, external session authentication, listener readiness, the per-user open limiter, thenregistry.open.PUT /external/gfs/watchchanges a live connection's subscription set. Body validation (keys capped at 64 before any per-element work), authentication, its own limiter bucket before the authority lookup, and agfs_watch_cmdNOTIFY built byserializeWatchCmd. Replies202 {seq}; the decision arrives on the stream.405withAllow: GET, PUT, charged once on the shared per-IP ingress limiter, soOPTIONSdoes not bypass it.GFS_WATCH_HEARTBEAT_MS(default 20 s, range 5 to 25 s).stopGfsWatchRegistryinsidestopOncebeforeserver.close; awatch-database-poolshutdown step before the core pool.Why
This is the only entry point to the watch. Its contract is what ERA (#1501) relays and what the desktop client (#1503) keys its retry decisions on, so every non-2xx carries a machine-readable reason.
How
409 session_contract_unsupported; an outdated client gets426. V2 sessions cannot be issued today; supporting them needs its own design for the session binding and its revocation hints (GFS watch: support V2 sessions before enabling V2 issuance #1504).x-watch-erroron every non-2xx the handler originates, and503 watch_reopeninstead of a 500.req.route, so path spelling (trailing slash, case) cannot drop the code.x-watch-error. The client treats them by status, with the same effect.Testing
gfsWatch.routes.realPostgres(real registry, real Postgres):503 watch_reopen, never a 500;OPTIONSandHEADanswered 405 through it, the 429 tagged on every path spelling;server_shutdownbefore ending the watch pool.config.gfsWatchHeartbeat: default, range and invalid values.server.gfsWatchShutdownandshutdown: registry stop and pool drain ordering.tsc --noEmitclean;npm test636 files / 8956 tests passed (real-Postgres suites skip in this lane); real-Postgres run of the watch suites (gfsWatch*,gfs.externalGfsRead*): 18 files / 238 tests passed.db.realPostgresMigrationfails 41 of 75 cases on the test host because its production access verifier shells out topsql, which is not installed there; the same 41 fail ondev.Risks / follow-ups
429 admission_full,Retry-After: 5). Accepted as a design limit for now; if it shows up in operation the fix is a parameter (per-team or per-source-IP cap), not a redesign.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.