Skip to content

feat(mcp): add the Approvals page for step-up requests - #126

Merged
Bugs5382 merged 6 commits into
mainfrom
feat/125-approvals-page
Sep 30, 2026
Merged

Bugs5382 merged 6 commits into
mainfrom
feat/125-approvals-page

Conversation

@Bugs5382

@Bugs5382 Bugs5382 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

What and why

This adds the Approvals page, where a person decides the step-up requests an MCP agent raises before a tool that changes the fleet runs. The agent holds only a bearer key, so the decision has to come from someone presenting an operator certificate in this UI.

  • Approvals page at /approvals (nav entry right after Agent keys). It lists approvals through ListApprovals, pending by default, and a status filter switches to approved, denied, expired, used or all. The columns are tool, summary, requested by (CN), key, required level, created, expires and status (with the decider's CN once decided).
  • Approve / Deny open a confirmation that repeats the tool, requester, required level, summary and the full request digest, then call DecideApproval. A refusal from the manager (expired, already decided, level too low) shows inline and the dialog stays open.
  • Gating: both buttons are disabled unless the approval is pending and the operator's WhoAmI level is at least required_level. The tooltip says why. An unknown required_level is treated as undecidable.
  • Deep link: the manager's approve_url opens /approvals?id=<approval_id>. That view starts on all statuses, because the request may already be decided or expired by the time it is opened, and it highlights (aria-current) and scrolls to that row. If the id is not in the list, the page says so.
  • Pending badge: the nav entry shows the pending count. It recounts on navigation, every 30 seconds (new requests arrive without any action in the tab), and when decideApproval dispatches a window event after a decision. A failed count hides the badge.
  • Mock mode: src/lib/approvals.ts has seeded fixtures (two pending at different levels, one used, one expired) and applies the same pending-only rule on decide.
  • Stubs: src/gen/fleet is copied from the api repo's gen/ts at api main ad5d169, which carries the approval RPCs from CryptOS-PKI/api#103. The copy also brings in the scep_pb.ts and tsa_pb.ts stubs and the other fields that landed on api main since the previous copy. DecideApproval returns DecideApprovalResponse{ approval }, and a response without an approval is reported as an error. wire-fields.test.ts pins every Approval field.
  • License headers: the branch is rebased onto the licensed main with every commit signed off. The new files (the approvals page, dialog and lib, and their tests) carry the Apache 2.0 header in its own commit, chore(license): stamp the Approvals files with the Apache 2.0 header.
  • README and AGENTS.md describe the page, the deep link and the badge refresh.
  • What the approver checks: the banner and the confirmation now ask the approver to match the approval ID and summary against what the agent showed, because the manager's pending_approval response gives the agent those and not the digest. The confirmation shows the approval ID, and keeps the request digest as a detail recorded in the audit log. This is an intentional text change: the dialog test is renamed to repeats the approval ID and summary to check, and the digest as an audit detail, and the page test tells the approver to check the approval ID and summary, not the digest is new.
  • Docs: the companion docs PR is docs(fleet-manager): document approving agent step-up requests docs#48 (Approving agent requests).

Closes #125

Merge order: CryptOS-PKI/api#103 and the CryptOS-PKI/cryptos-manager#103 phase-2 work are on main, so nothing else has to land first. Merge together with CryptOS-PKI/docs#48. The stubs match api main ad5d169, so no stub refresh is needed before merging unless api main moves first. If it does, re-copy gen/ts from api main into src/gen/fleet.

Verification

  • Lint clean
  • Tests pass
  • Build succeeds
  • Documentation updated (if behavior or API changed)

How this was verified

  • npm run lint: eslint and prettier are clean.
  • npm test: 79 files and 373 tests pass. The new tests were written first and failed before the implementation:
    • approvals.test.tsx (page): default pending list with every column, the filter switching to all, approve and deny opening the confirmation for the right row, gating below the required level, for a viewer and for a non-pending row, the deep link choosing all statuses and highlighting only the named row, the not-found note, and the empty and error states.
    • approval-decide-dialog.test.tsx: the approval ID, summary and check wording on screen before approving, with the digest labelled as recorded in the audit log, approve and deny calling DecideApproval with the right body, cancel, and an inline refusal.
    • lib/approvals.test.ts: canDecide, the live ListApprovals and DecideApproval calls (including the response wrapper and the change event), and the mock filter and single-decision rule.
    • top-nav.test.tsx: Approvals right after Agent keys, the pending badge, no badge on zero or error, and a recount after a decision.
    • The new wire-field pin fails against the previous stub copy.
  • npm run build: tsc and vite build succeed.
  • License check (golic inject --dry -x, as task license runs it) exits 0. No file still carries the old header.

Before merging: add a closing comment summarizing what was actually done in this PR
(not just the checked boxes).

@Bugs5382 Bugs5382 self-assigned this Sep 29, 2026
@Bugs5382
Bugs5382 marked this pull request as ready for review September 29, 2026 18:18
@github-actions github-actions Bot added the enhancement New feature (feat). Minor version bump. label Sep 29, 2026
Copy the api repo's gen/ts at 742f05f into src/gen/fleet. It adds the
ListApprovals and DecideApproval RPCs with the Approval message and the
DecideApprovalResponse wrapper.

The wire round-trip test now pins every Approval field, so a stale copy
that would drop one on decode fails the suite.

Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Approvals, next to Agent keys, lists the step-up requests agents raise
through ListApprovals, pending by default, with a status filter for the
rest. Approve and Deny call DecideApproval after a confirmation that
repeats the summary and the full request digest, so the operator can
match them against what the agent reported.

The buttons are disabled unless the request is pending and the
operator's level is at least its required level, the same rule the
manager enforces.

The manager's approval link opens /approvals?id=<id>. That view shows
every status, because the request may already be decided or expired,
and highlights and scrolls to the named request, or says it was not
found.

The nav entry carries a pending count that refreshes on navigation,
every 30 seconds, and after a decision.

Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
…e digest

Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
@Bugs5382

Copy link
Copy Markdown
Contributor Author

Closing summary: adds the Approvals page for MCP step-up requests: list with a status filter (pending by default), approve and deny through DecideApproval with a confirmation that asks the approver to match the approval ID and summary the agent showed (the request digest is shown as an audit detail), level gating from WhoAmI, a ?id= deep link from the agent's approve_url, and a pending-count badge in the nav. Stubs refreshed to api main, license headers stamped, commits signed off. Companion docs: CryptOS-PKI/docs#48.

@Bugs5382
Bugs5382 merged commit 8b6bc84 into main Sep 30, 2026
16 checks passed
@Bugs5382
Bugs5382 deleted the feat/125-approvals-page branch September 30, 2026 14:07
@Bugs5382 Bugs5382 added this to the v0.1.0 milestone Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature (feat). Minor version bump.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(mcp): add the Approvals page for MCP step-up requests

1 participant