Skip to content

feat(golbat): discover instance capabilities from /api/status - #1258

Open
jfberry wants to merge 2 commits into
WatWowMap:mainfrom
jfberry:feat/golbat-capabilities
Open

feat(golbat): discover instance capabilities from /api/status#1258
jfberry wants to merge 2 commits into
WatWowMap:mainfrom
jfberry:feat/golbat-capabilities

Conversation

@jfberry

@jfberry jfberry commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Scope

Adds a Golbat capability registry so ReactMap can learn what each configured Golbat instance supports from GET /api/status, with showcase focus as the first consumer. This lets Golbat drop the deprecated per-response showcase_focus_filter flag once builds without a filters block are gone (Golbat PR #404 adds the block).

  • server/src/services/GolbatCapabilities.js (new). Reads /api/status for every configured endpoint using the existing X-Golbat-Secret / basic-auth headers and the api.fetchTimeoutMs timeout. Stores { features, limits, filters } per instance, keyed by the endpoint base url (mem). A 404 or a non-JSON body records a legacy result (older Golbat, filters: null); any other failure (401, 5xx, network error, timeout) keeps the last good result. Refreshes every 5 minutes on an unreferenced timer, single-flight per instance. recheck(mem) re-reads immediately, at most once per 30 s per instance. Consumer API: get(mem), advertisesFilters(mem), supportsFilter(mem, key). Logs each instance's advertised filters/features/limits at info only when they change.
  • DbManager.getDbContext awaits discovery alongside the schema checks, so the registry is populated before the first availability refresh at startup and on config reload.
  • evalScannerQuery triggers a recheck of the calling instance when Golbat answers 400 or 422 (the first sign of a downgrade). The header builder it shared with the new service moves to server/src/utils/scannerHeaders.js.
  • Pokestop.getAvailable decides showcase focus support from supportsFilter(mem, 'showcase_focus') when the instance advertises filters, and falls back to the legacy showcase_focus_filter response key only when the status route reported no filters block. An unsupported verdict re-reads the status (single-flight refresh, not the debounced recheck, since this path is already throttled by the availability refresh window) before throwing, so an upgrade that drops the legacy flag recovers on the same pass even seconds after a periodic status fetch. The hard error (no SQL masquerade) is unchanged.
  • Types: GolbatStatus / GolbatInstance added to packages/types/lib/server.d.ts; showcase_focus_filter marked @deprecated and optional there and in the mapper's JSDoc. golbat logger tag added.

Out of scope, as briefed: no change to which filters ReactMap sends, no new consumer beyond showcase focus, no Golbat changes. limits are stored for a later clamp consumer but nothing reads them yet.

Testing

  • yarn test: 103 server tests pass (14 new for the service, 2 new for the evaluator fast path, showcase availability tests rewritten per Golbat generation).
  • yarn lint clean on all touched files; Prettier clean; tsc --noEmit -p jsconfig.json reports no new errors (the 21 pre-existing errors in Pokestop.js are unchanged).
  • Not exercised against a live Golbat in this environment (no config/local.json). Suggested manual checks on a dev stack: start ReactMap against a PR Restrict areas without auth #404 Golbat and confirm the [GOLBAT] info line lists showcase_focus; point it at a current-main Golbat and confirm the line says no filters block (older Golbat) while showcase availability still loads via the legacy flag; stop Golbat and confirm a [GOLBAT] … keeping the last known capabilities warning without the drawer changing.

🤖 Generated with Claude Code

https://claude.ai/code/session_016611GTrQ2W8WcznKrLQnMv

Add a GolbatCapabilities service that reads GET /api/status from every
configured Golbat endpoint and keeps { features, limits, filters } per
instance, keyed by the endpoint base url. Discovery runs from
DbManager.getDbContext (startup and config reload), refreshes every five
minutes, and rechecks an instance immediately, debounced to once per
30 s, when a scanner call answers 400/422 — the first sign of a
downgrade. A 404 or non-JSON body means an older Golbat with no
capabilities; any other failure keeps the last good result.

Pokestop.getAvailable now decides showcase focus support from
supportsFilter(mem, 'showcase_focus') when the status route advertises
filters, and only falls back to the deprecated per-response
showcase_focus_filter flag for a build that does not. The hard error on
an unsupported build is unchanged; only the source of truth moves. The
header builder shared by the evaluator and the service is extracted
into scannerHeaders.js.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016611GTrQ2W8WcznKrLQnMv

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12e3acca32

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +121 to +125
if (
this.now() - instance.lastFetchAt <
GolbatCapabilities.RECHECK_DEBOUNCE_MS
) {
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow the unsupported verdict to force a status recheck

When Golbat is upgraded within 30 seconds of the startup or periodic status fetch, Pokestop.getAvailable() receives the new payload without showcase_focus_filter and calls recheck(), but lastFetchAt was just set by the preceding fetch, so this branch returns without requesting the updated status. The availability call consequently throws instead of recovering on the same pass, leaving the previous or empty filter drawer until the next scheduled availability refresh. The unsupported-capability path needs a forced retry or debounce logic that still permits this first corrective recheck.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4e0dbae. Confirmed the scenario: recheck() shared its 30 s floor with the periodic status fetch, so an upgrade landing inside that window left the pass throwing until the next availability refresh. The availability path now calls refresh() (still single-flight, no debounce); it is already throttled by the availability refresh window, so it cannot hammer Golbat. The debounce stays on the hot-path recheck triggered by 4xx scanner responses. The test for this path now drives the real registry with a fake transport, with the upgrade landing 10 s after discovery.

The unsupported showcase-focus verdict in Pokestop.getAvailable called
the debounced recheck(), which shares its 30 s floor with the periodic
status fetch. A Golbat upgrade landing inside that window left the
availability pass throwing until the next one. Use refresh() there: it
is still single-flight, and the path is already throttled by the
availability refresh window. The debounce stays on the hot-path recheck
triggered by 4xx scanner responses.

The replaced test now drives the real registry with a fake transport so
it exercises the debounce instead of a mocked method.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016611GTrQ2W8WcznKrLQnMv
@Mygod

Mygod commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Focused tests and changed-file lint/format checks passed, but targeted reproductions confirmed credential-isolation, timeout-handling, retry-throttling, and endpoint-prefix defects in the new capability discovery paths.

Full review comments:

  • [P2] Preserve credential isolation during capability discovery — server/src/services/GolbatCapabilities.js:75-80
    When multiple schema entries share an endpoint URL, only the first entry's credentials are used. If that entry has an expired secret while the Pokestop entry has valid credentials, discovery and every subsequent refresh receive 401. On Golbat builds without the legacy flag, healthy Pokestop availability then fails capability validation because another source supplied the credentials. Key discovery by URL and credentials, as fortAvailable already does, or retry with another configured credential set.

  • [P2] Retain known capabilities when reading the body fails — server/src/services/GolbatCapabilities.js:174-179
    If /api/status returns 200 headers but its body stalls or the connection resets, response.json() rejects with an abort or transport error. This catch converts that failure into a legacy status and overwrites the last good capabilities. Subsequent unsuccessful refreshes can therefore reject otherwise usable Pokestop availability on builds without the legacy flag. Distinguish invalid JSON from transport failures and let the latter reach the outer handler that preserves the previous status.

  • [P2] Throttle capability retries after failed availability refreshes — server/src/models/Pokestop.js:1367-1369
    With api.queryOnSessionInit.quests enabled and the sole Pokestop source failing capability validation, DbManager.getAvailable() returns null and EventManager does not update its success-based TTL. Every subsequent session initialization therefore performs this unconditional status refresh, even while the same availability payload remains cached. Three sequential refreshes reproduced one availability request but three additional status requests within the window. Bound this forced retry per availability snapshot or generation rather than relying on the success-only throttle.

  • [P3] Preserve endpoint path prefixes when triggering rechecks — server/src/utils/evalScannerQuery.js:56-56
    For an endpoint configured as https://host/api/golbat, discovery registers that complete URL, but this first-occurrence search derives https://host from a scan URL. recheck() silently returns because that key is unregistered, so 400/422 responses never trigger immediate capability discovery and stale capabilities remain until the five-minute poll. Pass the configured endpoint identity through instead of splitting at the first /api/.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants