Skip to content

feat(dashboard): reconcile local state against core snapshot after webhook gaps - #276

Merged
Lakes41 merged 2 commits into
Adamantine-guild:mainfrom
vreabernardo:feat/webhook-reconciliation
Jul 24, 2026
Merged

feat(dashboard): reconcile local state against core snapshot after webhook gaps#276
Lakes41 merged 2 commits into
Adamantine-guild:mainfrom
vreabernardo:feat/webhook-reconciliation

Conversation

@vreabernardo

Copy link
Copy Markdown
Contributor

Closes #262.

Webhooks being best-effort means a deploy or an idempotency-store outage can permanently drop events and local state drifts with no recovery path. This adds the reconciliation pass the issue asks for: pull core's authoritative state, diff, apply only the deltas, tag everything so admins can tell corrected data from live webhooks.

The core contract (the coordination the issue asked me to call out): the dashboard needs one new core endpoint, GET /v1/guilds/:guildId/snapshot, returning the complete member list, core-managed passes, and guild metadata at a point in time. guildpass-core is a separate repo and doesn't have it yet, so IntegrationClient.getGuildSnapshot() maps 404 to null and the job reports supported: false instead of failing. The dashboard side is complete and tested against stubbed snapshots; it lights up the moment core ships the endpoint. Full contract is in docs/core-reconciliation.md. I went with a state snapshot over an event-log range because it can't replay history incorrectly and stays useful even if core adds an event log later.

Design notes:

  • Members match on wallet (case-insensitive); snapshot members without a wallet are skipped rather than guessed at. Passes match on id, then exact name, because local pass ids are repo-generated and a pass created by an earlier reconciliation run wouldn't id-match core on the next run.
  • Local-only passes are never touched. Core can't tell a dashboard draft from a deleted pass, so the conservative read is to leave them. Members absent from the snapshot do get deactivated, since the snapshot's member list is explicitly complete.
  • Applied changes write through the tenant-scoped repositories (same path as everything else), and each correction records exactly one activity event with source: "reconciliation" through the webhook idempotent write path, with deterministic event ids. No drift means zero writes and zero activity entries; a second apply run is a no-op.
  • Dry-run mode returns the identical diff with no side effects. Trigger is manual for now: POST /api/integrations/reconcile (settings:write) or the new panel on the Integrations page. An interval trigger is a cron line away once core's endpoint exists.
  • The activity feed has a "Reconciliation" source filter and a distinct badge.
  • Drive-by: removed two pre-existing unused imports in lib/reconciliation/ so eslint passes on the touched directory.

Acceptance criteria: manual trigger with diff report (dry-run) ✓, reconciliation changes visually distinct in /activity ✓, no duplicate activity for unchanged state ✓, core dependency documented + stubbed partial implementation ✓, tests for no-drift / partial drift / full resync against a stubbed core ✓.

Verified: tsx --test over the full dashboard suite, 559/559 pass (7 new). integration-client 14/14 (4 new). eslint clean on all touched files. tsc shows only the pre-existing TS6 baseUrl config deprecation.

Stacked on #273 — main still has unresolved conflict markers in 7 files, so this won't build from main until the repair lands.

vreabernardo added 2 commits July 24, 2026 00:40
…bhook gaps

Adds the recovery path for missed webhook events (Adamantine-guild#262): a manual
reconciliation pass that pulls a point-in-time guild snapshot from
GuildPass core, diffs it against local state, and applies corrections
tagged source: "reconciliation" in the activity feed.

- integration-client: getGuildSnapshot() (GET /v1/guilds/:id/snapshot,
  404 -> null so unsupported cores degrade gracefully) + GuildSnapshot
  types + "reconciliation" activity source
- dashboard: lib/reconciliation/core-sync.ts (wallet-keyed member diff,
  id/name pass diff, conservative local-only pass handling, dry-run and
  apply modes, deterministic event ids through the webhook idempotent
  write path)
- POST /api/integrations/reconcile (settings:write) + Integrations page
  trigger panel with report rendering
- activity feed: reconciliation source filter + badge
- docs/core-reconciliation.md: the snapshot contract core needs to
  implement, matching rules, idempotency guarantees
- tests: no-drift no-op, partial drift (dry-run purity, apply, re-run
  idempotency), full resync, wallet-less members, unsupported cores;
  integration-client snapshot client tests
@Lakes41
Lakes41 merged commit 2ca275d into Adamantine-guild:main Jul 24, 2026
2 of 5 checks passed
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.

Design and implement webhook replay/backfill reconciliation after downtime

2 participants