Skip to content

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

Merged
Mygod merged 3 commits into
WatWowMap:mainfrom
jfberry:feat/golbat-capabilities
Oct 20, 2026
Merged

Mygod merged 3 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

@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.

@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/.

jfberry and others added 3 commits October 12, 2026 21:32
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
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
- Keep every distinct credential set configured for a shared endpoint url
  and fall through to the next on 401/403, remembering the set that works.
- Read the status body outside the JSON parse guard so a stalled or reset
  body keeps the last good capabilities instead of recording a legacy result.
- Force at most one status refresh per availability payload when showcase
  focus is unsupported, so failing session inits no longer re-read the
  status on every page load.
- Resolve rechecks against the registered endpoint url instead of splitting
  at the first /api/, so endpoints configured with a path still recheck.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jfberry

jfberry commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

@Mygod all four points are confirmed and fixed in fe537cb. The branch is also rebased onto current main.

  • [P2] Credential isolation: discover now keeps every distinct secret and basic-auth pair configured for a shared URL. When Golbat answers 401 or 403, the status fetch tries the next pair, and the pair that works is tried first on later refreshes. Consumers still look instances up by URL alone.
  • [P2] Body read failures: the status body is read with response.text() outside the parse guard, and only JSON.parse is inside it. A stalled or reset body now reaches the outer handler and keeps the last good capabilities. Invalid JSON is still recorded as an older Golbat.
  • [P2] Retry throttling: Pokestop.getAvailable now forces at most one status refresh per availability payload, tracked in a WeakMap keyed by the payload object. The combined fort cache returns the same object for its whole window, so failing session inits no longer re-read the status on every page load. A new snapshot gets one fresh refresh, and concurrent callers share it.
  • [P3] Endpoint path prefixes: evalScannerQuery no longer splits at the first /api/. It passes the request URL to recheck, which resolves it with the new memFor(url). That picks the longest registered endpoint the URL starts with, so https://host/api/golbat resolves correctly.

Testing: yarn test passes 156 of 156, including a new test for each point, and each new test fails against the previous code. ESLint and Prettier are clean on the changed files, yarn build succeeds, and the tsc error count is unchanged. I have not run it against a live Golbat.

Copilot AI 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.

🟢 Approval recommended

No blocking issues were identified in the reviewed implementation or regression tests; tests were not executed during this review.

No open issues 🎉

What changed in this PR

Adds per-instance Golbat capability discovery so ReactMap can use advertised showcase-focus support while remaining compatible with older builds.

Changes:

  • Adds authenticated status discovery, periodic refreshes, and cached capabilities.
  • Integrates discovery with startup, reloads, and scanner rejection handling.
  • Updates showcase availability checks, shared types, logging, and regression tests.
File Description
server/​test/​showcaseEndpointAvailability.test.js Tests capability-based availability and legacy compatibility.
server/​test/​scannerCapabilityRecheck.test.js Tests rechecks after scanner validation failures.
server/​test/​golbatCapabilities.test.js Tests discovery, caching, authentication, and recovery.
server/​src/​utils/​scannerHeaders.js Extracts shared request headers.
server/​src/​utils/​evalScannerQuery.js Triggers capability rechecks on 400/422 responses.
server/​src/​services/​GolbatCapabilities.js Implements the capability registry.
server/​src/​services/​DbManager.js Awaits discovery during context initialization.
server/​src/​models/​pokestopAvailableMapper.js Documents the optional legacy flag.
server/​src/​models/​Pokestop.js Uses discovered showcase-focus capabilities.
packages/​types/​lib/​server.d.ts Adds registry types and deprecates the legacy flag.
packages/​logger/​lib/​tags.js Adds the Golbat logging tag.

Review effort: Balanced

@Mygod
Mygod merged commit c83f93f into WatWowMap:main Oct 20, 2026
2 checks passed
github-actions Bot pushed a commit that referenced this pull request Oct 20, 2026
# [1.54.0](v1.53.2...v1.54.0) (2026-10-20)

### Features

* **golbat:** discover instance capabilities from /api/status ([#1258](#1258)) ([c83f93f](c83f93f))
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.54.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

skelgaard added a commit to skelgaard/ReactMap that referenced this pull request Oct 21, 2026
…port

The own /api/status fetch every 5 minutes is replaced by the capability
registry of WatWowMap#1258 (`supportsFilter(mem, 'annotate_for')`). A Golbat that
refuses personal scans (e.g. its rates aren't loaded) is still skipped for a
minute, and its status is now re-read right away in case it was downgraded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants