Repository navigation
feat(mcp): add the Approvals page for step-up requests - #126
Merged
Merged
Conversation
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>
Bugs5382
force-pushed
the
feat/125-approvals-page
branch
from
September 30, 2026 13:38
8ef4d69 to
2669472
Compare
2 tasks done
…e digest Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
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. |
This was referenced Sep 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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(nav entry right after Agent keys). It lists approvals throughListApprovals, 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).DecideApproval. A refusal from the manager (expired, already decided, level too low) shows inline and the dialog stays open.required_level. The tooltip says why. An unknownrequired_levelis treated as undecidable.approve_urlopens/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.decideApprovaldispatches a window event after a decision. A failed count hides the badge.src/lib/approvals.tshas seeded fixtures (two pending at different levels, one used, one expired) and applies the same pending-only rule on decide.src/gen/fleetis copied from the api repo'sgen/tsat api mainad5d169, which carries the approval RPCs from CryptOS-PKI/api#103. The copy also brings in thescep_pb.tsandtsa_pb.tsstubs and the other fields that landed on api main since the previous copy.DecideApprovalreturnsDecideApprovalResponse{ approval }, and a response without an approval is reported as an error.wire-fields.test.tspins everyApprovalfield.chore(license): stamp the Approvals files with the Apache 2.0 header.pending_approvalresponse 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 torepeats the approval ID and summary to check, and the digest as an audit detail, and the page testtells the approver to check the approval ID and summary, not the digestis new.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-copygen/tsfrom api main intosrc/gen/fleet.Verification
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 callingDecideApprovalwith the right body, cancel, and an inline refusal.lib/approvals.test.ts:canDecide, the liveListApprovalsandDecideApprovalcalls (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.npm run build: tsc and vite build succeed.golic inject --dry -x, astask licenseruns 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).