Skip to content

feat(frontend): admin UI for outbound mutation reconciliation - #786

Merged
pedrosakuma merged 3 commits into
mainfrom
feat/785-outbound-mutations-admin-ui
Aug 8, 2026
Merged

pedrosakuma merged 3 commits into
mainfrom
feat/785-outbound-mutations-admin-ui

Conversation

@pedrosakuma

@pedrosakuma pedrosakuma commented Aug 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds an admin frontend screen for outbound mutation reconciliation, so an operator no longer needs raw curl + a manually-extracted admin JWT (docs/RUNBOOK.md §0.1) to resolve a stuck outbound mutation.

Closes #785

What changed

  • protocol.js: 5 new wrappers mirroring AdminOutboundMutationEndpoints.cs exactly — listOutboundMutations, getOutboundMutation, registerOutboundMutationEvidence, resolveOutboundMutation, approveOutboundMutationResolution.
  • state.js: outboundMutations (list) and outboundMutationDetail (expanded row) slices, following the existing set*/notify convention.
  • adminUi.js + index.html: new "Outbound mutation reconciliation" admin panel — filterable list (state / requiresReconciliation), expandable per-mutation detail (raw JSON of attempts/evidence/proposals/resolution), an evidence-registration form, and a propose-resolution form.
  • app.js: handlers wired via setAdminHandlers, refreshOutboundMutations hooked into refreshAdminData(), status-code-specific feedback (401 → logout, 422 → invalid evidence, 409 → conflict, 503 → unavailable).

Maker/checker design (per discussion on #785)

Only the venue_absent decision is capacity-releasing and requires a genuinely different admin to approve (OutboundReconciliationService.Approve, server-side self-approval rejection). Unlike the existing killswitch/halt panels (same-user confirm() double-click), this UI implements "Propose resolution" and "Approve" as two separate actions:

  • Approve is rendered per-pending-proposal.
  • It is hidden client-side (UX hint only, not the enforcement point) whenever the proposal's makerRef equals the current session's username (session.username is populated from the same JWT sub claim the server uses as makerRef — confirmed via JwtIssuer.cs).
  • The server remains the actual enforcement point regardless of what the UI shows.

Non-goals (unchanged from #785)

  • No backend or auth changes — this is purely a UI over the already-implemented, already-tested admin API.
  • No automated/bulk decision-making; every resolution requires an explicit operator-entered reason + evidence reference, mirroring the runbook.

Testing

  • New frontend/test/outbound-mutations-ui.test.mjs (+ fixture frontend/test/fixtures/outbound-mutations.json):
    • Protocol-layer HTTP contract test (routes, methods, Authorization header) for all 5 new functions.
    • DOM-stub-based UI tests (pattern from operations-ui-states.test.mjs): list rendering incl. pending-approval badge, row-expand → detail load, and — the key design requirement — the Approve button is verified hidden when the current session is the proposer and shown (and wired to the right mutationId/proposalId) when a different admin proposed it.
  • npm test (428/428) passes, including all pre-existing suites, in frontend/.

Not in this PR

Actually resolving FIRM01's 7 legacy stuck mutations (from the July 25 incident, root-caused in #773/fixed by #778) — that's an operational action for a real Entra-authenticated admin to perform via this new screen once merged/deployed, not something this PR does.

Protocol evidence gate

Does this PR add or change a dependency on B3MatchingPlatform / B3MarketDataPlatform wire behavior?

  • No, this PR does not change cross-repo wire assumptions.
  • Yes, and merged upstream evidence plus official schema details are listed below.

Checklist:

  • Upstream issues, RFCs, or proposals are not the only evidence for implementation, or this PR has no cross-repo wire dependency.
  • Participant-side / product requirements do not prescribe venue messages, templates, fields, or enums that do not exist in the official schema and merged upstream implementation.

PedroTravi and others added 2 commits August 7, 2026 22:28
Adds a screen-driven flow so an operator no longer needs raw curl and a
manually-extracted admin JWT (docs/RUNBOOK.md §0.1) to resolve a stuck
outbound mutation:

- protocol.js: list/get/register-evidence/resolve/approve wrappers for
  the existing AdminOutboundMutationEndpoints API.
- state.js: outboundMutations / outboundMutationDetail slices.
- adminUi.js + index.html: new "Outbound mutation reconciliation" panel
  — filterable list, expandable detail, evidence-registration form,
  propose-resolution form.
- Genuinely separate maker/checker actions: "Propose resolution" and
  "Approve" are two different UI actions, not a same-user double-click.
  The Approve button is hidden client-side (UX hint only) when the
  pending proposal's makerRef matches the current session's username;
  the server independently rejects self-approval regardless.
- app.js: wiring + refreshOutboundMutations hooked into
  refreshAdminData(), 401/422/409/503 feedback mapping.

No backend, auth, or automated-decision changes — purely a UI for the
already-implemented, already-tested admin API.

Closes #785

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pproval identity

Address code-review findings on PR #786:
- state filter options (conflicting/resolved) didn't exist in the
  backend OutboundMutationState enum; replaced with the actual
  wire-format values (ambiguous, recorded_pending_approval,
  venue_acknowledged, operator_resolved).
- the self-approval hint compared proposal.makerRef against
  session.username, but local-login overwrites username with the raw
  login-form input (possibly different casing than the JWT sub the
  server actually uses as makerRef). Now compares against
  claimsFromToken(token).sub directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@pedrosakuma
pedrosakuma marked this pull request as ready for review August 7, 2026 22:34
…ce gate)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@pedrosakuma
pedrosakuma merged commit 0ada51f into main Aug 8, 2026
19 checks passed
@pedrosakuma
pedrosakuma deleted the feat/785-outbound-mutations-admin-ui branch August 8, 2026 00:46
pedrosakuma added a commit that referenced this pull request Aug 11, 2026
…und mutations (#787)

* feat(frontend): guided one-click resolution for venue-confirmed outbound mutations

Refs #785, follow-up to #786.

The reconciliation panel shipped in #786 exposed the full admin API
surface (evidence registration, resolution decision, evidence type,
raw evidence reference, reason code) as a single generic form. For the
common case — mutations already in `venue_acknowledged` state with an
eligible terminal Execution Report already recorded by the platform —
this made operators manually locate and copy a 64-hex evidenceId out
of a raw JSON blob for every mutation, one at a time.

Add a guided batch action:

- The mutation list now surfaces a "Resolve N confirmed mutations"
  banner whenever any listed mutation is `venue_acknowledged`,
  requires reconciliation, and has no pending proposal.
- Clicking it loads each candidate's detail, selects its eligible
  terminal ER evidence automatically (`findTerminalExecutionReportEvidence`),
  and submits `venue_acknowledged` / `terminal_er` / `terminal_er_verified`
  for all of them behind a single confirmation dialog.
- Mutations without eligible evidence are left untouched and reported
  back to the operator for advanced review.
- The full evidence-registration/propose-resolution/approve forms
  move behind an "Advanced reconciliation" `<details>` disclosure —
  still available unchanged for `venue_absent`/`leave_ambiguous`/
  registered-evidence flows, which genuinely need the raw fields.

No backend/API changes. Purely a frontend UX layer on top of the
existing `/api/admin/outbound-mutations` endpoints from #785/#786.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: re-validate mutation state from fresh detail before guided batch resolve

Code review (gpt-5.5) flagged that handleResolveVenueConfirmedBatch only
checked for eligible terminal-ER evidence after fetching each mutations
detail, but never re-checked state/requiresReconciliation/pendingApproval
against the freshly-fetched detail payload. A mutation that flipped out
of venue_acknowledged (e.g. reopened ambiguous by a late contradictory
ER) between the list render and this call could still be auto-resolved
by the guided one-click path using a stale terminal ER.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: pedrotravi <pedrotravi@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

feat(frontend): admin UI for outbound mutation reconciliation

2 participants