feat: add approval request intake and bypass-request promotion - #5074
Open
daviddanialy wants to merge 4 commits into
Open
Conversation
Contributor
🦋 Changeset detectedLatest commit: faf4073 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
daviddanialy
force-pushed
the
daviddanialy/ais-468-feat-org-exposure-signals
branch
from
August 7, 2026 20:36
27e91de to
5f6ccfd
Compare
daviddanialy
force-pushed
the
daviddanialy/ais-469-feat-approval-request-intake
branch
from
August 7, 2026 20:38
a501aad to
be1c01e
Compare
Contributor
There was a problem hiding this comment.
cubic analysis
All reported issues were addressed
Linked issue analysis
Linked issue: AIS-469: feat: approval request intake for employees without dashboard access
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Proactive intake endpoint to let a non-admin member request review (mcpApproval.createRequest) that accepts a target and a free-text justification (note) and intentionally carries no new RBAC scope | Handler, API surface, SDK and CLI were added for createRequest; payload includes target, targetKind, and note; tests exercise createRequest and scope/feature-gate boundaries. |
| ✅ | Promote a shadow-MCP bypass request into the approval queue and carry the blocked employee as the requester with their justification (mcpApproval.promote), rejecting whole-policy bypasses and allowing unattributed requests | Promote endpoint and plumbing implemented; promote links risk_policy_bypass_request_id and preserves requester/note handling; tests include cross-project safety and unattributed-requester behavior. |
| ✅ | Dedupe: proactive asks, promoted blocks, and observed traffic converge on one review per canonicalized server reference | Target canonicalization used when creating/upserting requests so repeated asks attach to the same review; DB upsert preserves existing rows and re-opens denied reviews per description and tests. |
| ✅ | Identity resolution runs at intake so evidence fields (artifact_ref / version_pinned) are populated or explicitly marked unresolved before an admin looks | Intake invokes identity resolution and stores resolved references or explicit unresolved markers; tests exercise identity/resolve semantics and repeat-ask note behavior. |
| ✅ | Every intake is audited as mcp_approval_request:create in the same transaction | Audit action and logger added; tests include audit delta assertions. |
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
daviddanialy
force-pushed
the
daviddanialy/ais-468-feat-org-exposure-signals
branch
from
August 7, 2026 20:52
5f6ccfd to
bd6eee1
Compare
daviddanialy
force-pushed
the
daviddanialy/ais-469-feat-approval-request-intake
branch
from
August 7, 2026 20:52
5a7eac3 to
0c8e5be
Compare
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
The two entry points from AIS-469 that need no placement decision: a proactive createRequest for members, and promote for admins bringing a risk-policy bypass request into the review queue. createRequest deliberately carries no RBAC scope — the people asking typically cannot reach the dashboard, and a scope for it would either be ungranted for everyone who needs it or granted so universally it means nothing, the same posture as the block and bypass surfaces. Authentication, project membership, and the product-feature gate still apply. promote resolves the caller-supplied bypass-request id under the caller's project before anything is written. There is deliberately no database-level pin for this pair, so that read is the primary control against promoting another project's bypass request into this project's queue — the sharpest IDOR surface in the workflow, and the reason the cross-project case has its own test asserting nothing is written. Server URLs canonicalize with the same function that keys the shadow-MCP inventory, so a proactive ask, a promoted block, and the org's own traffic converge on one review per server. One row per person per request: a repeat ask keeps the freshest justification without erasing an earlier one when the new ask carries none. Identity resolution runs at intake, and every admission audits as a create against the request subject. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Server URLs are persisted and audited in redacted form — scheme, host and path only — reusing the identity package's redaction. A token pasted into a request URL must reach neither the review queue nor the audit feed; the test that previously demonstrated the leak now pins the redaction. - Only http and https references are admitted, on both intake paths: the MCP backend can reach nothing else, and a review for an unreachable reference wastes an admin's attention. - A proactive ask requires a non-blank justification at the payload boundary, per the ticket; a promoted bypass keeps its note optional since one cannot be conjured, and an empty bypass note no longer erases a justification the same person gave proactively — now tested. - target_kind is an enum in the published contract, so invalid namespaces fail at the API boundary. - The audit outbox event's contract wording covers creates as well as decisions, matching the one-event-per-subject convention. - The reopen guard is now exercised through createRequest for the approved-stays and pending-stays cases, with history asserted intact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Composes the deterministic signals — resolved identity, registry metadata for package references, and this org's own traffic for remote ones — into the versioned current_evidence document, gathered at admission so evidence is on the request by the time an admin looks. The shape holds three properties deliberately: found, not-published, and could-not-look are distinct (a failed lookup lands in the document's gaps list, never reading as checked-and-clean); everything gathered is a declaration or an observation of this org's own traffic; and gathering is best-effort per source under a bounded timeout, so an admission is never lost to a flaky registry. The research agent's output deliberately never enters this document — web-sourced claims keep their own trust tier, lifecycle, and caveats in mcp_research_reports. This document is instead the agent's briefing: identity is where its research starts, gaps list what deterministic gathering could not get, and DecodeDocument gives the future runner and any frozen-snapshot reader the document typed, version-dispatched so a version-1 snapshot stays decodable after the shape moves on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
daviddanialy
force-pushed
the
daviddanialy/ais-468-feat-org-exposure-signals
branch
from
August 7, 2026 21:15
bd6eee1 to
3cff2b2
Compare
…ctions - Each source inside the assembler now carries its own deadline, so an unreachable registry costs its own budget and lands in the document's gaps rather than holding the admission for the whole gather window. The handler's outer timeout remains as a backstop only. - The package-metadata client uses the pooled guardian client: it is a long-lived client making repeated requests to the same two registry hosts, which is exactly what PooledClient documents itself for — per-request connection churn was the cost of the non-pooled default. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
daviddanialy
force-pushed
the
daviddanialy/ais-469-feat-approval-request-intake
branch
from
August 7, 2026 21:18
911491b to
faf4073
Compare
This was referenced Aug 7, 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.
Adds approval-request intake (AIS-469) — the two entry points that need no surface-placement decision. Stacked on the org-exposure-signals branch; review that stack bottom-up.
What it does
mcpApproval.createRequest— the proactive path. A member names a server (URL or stdio launch command) and says why. Deliberately carries no RBAC scope, per the ticket's decision: the people asking typically cannot reach the dashboard, and a scope for it would either be ungranted for everyone who needs it or granted so universally it means nothing — the same posture as the block and bypass surfaces. Authentication, project membership, and the product-feature gate still apply, each tested.mcpApproval.promote—decide-scoped. Brings a risk-policy bypass request (minted by a shadow-MCP block) into the review queue, carrying the blocked employee as requester with their justification, and linking the source onrisk_policy_bypass_request_id. Whole-policy bypasses are rejected (they name no server), and a bypass whose hook couldn't resolve a user still promotes — the ask isn't lost, it just carries no requester attribution.The promote path is the sharpest IDOR surface in this workflow
The bypass-request id is caller-supplied, and there is deliberately no database-level pin between bypass requests and projects for this pair (adding one would have made the schema migration non-transactional). So the handler's project-scoped resolve is the primary control: the id is read under the caller's project before anything is written, and the cross-project test asserts both the 404 and that nothing entered the queue.
Dedupe converges across entry points
Server URLs canonicalize with the same function that keys the shadow-MCP inventory, so a proactive ask, a promoted block, and the org's own observed traffic land on one review per server —
https://MCP.Example.com:443/sse#xandhttps://mcp.example.com/sseare the same request, whichever door it came through. One row per person per request: a repeat ask keeps the freshest justification without erasing an earlier one when the new ask carries none. A re-request reopens a denied review with its decision history intact.Also
artifact_ref/version_pinnedare populated before an admin looks; an unidentifiable reference is stored as explicitly unresolved, never as empty.mcp_approval_request:createin the same transaction.Evidence assembly at intake
Every admission now gathers the deterministic evidence into the versioned
current_evidencedocument — resolved identity (always present, unknown shown as unknown), registry metadata for package references (withpackage_not_publishedas a distinct clean outcome), and the org's own traffic exposure for remote ones (fed the resolved URL, somcp-remotestdio commands hit the inventory). Per-source failures land in agapslist inside the document rather than reading as checked-and-clean, gathering runs before the transaction under a bounded timeout, and a flaky registry can delay evidence but never lose an admission.The optional research agent deliberately stays outside this document: web-sourced findings keep their own trust tier and lifecycle in
mcp_research_reports, and this document doubles as the agent's briefing when an admin later triggers a run —DecodeDocumentis the version-dispatched typed read the runner (and any frozen-snapshot reader) will use.Not in this PR
Where the employee-facing proactive surface lives (block-page family vs device-agent) — the ticket defers that to the design partner. End-to-end verification of the non-admin block-page auth path is also outstanding.
Testing
13 new tests (44 total in the package): both happy paths, cross-project promote, dedupe across users and entry points, repeat-ask note semantics, unattributed requester, feature gate, scope boundary, audit delta.
mise run lint:server,mise run gen:sdk, andpnpm -F dashboard type-checkall pass.AIS-469
🤖 Generated with Claude Code
Summary by cubic
Adds employee approval-request intake and admin bypass-request promotion for MCP approvals. Also assembles a versioned evidence document at intake so admins have identity, registry, and traffic signals ready (AIS-469).
New Features
mcpApproval.createRequest: members ask by server URL or stdio command; no RBAC scope; auth, project membership, and feature gate enforced.mcpApproval.promote(decide-scoped): promotes a bypass into the queue, linksrisk_policy_bypass_request_id, validates the id within the caller’s project, rejects whole-policy bypasses, supports unattributed users.current_evidencewith identity, package-registry metadata, and org traffic exposure; records gaps; bounded timeout with per-source deadlines; pooled registry client to reduce churn; research agent stays separate.mcp_approval_request:create; OpenAPI updated; new client methods and React Query hooks (createMcpApprovalRequest,promoteMcpApprovalRequest); CLI addscreate-requestandpromote.Bug Fixes
http/httpstargets allowed;target_kindenforced as an enum at the API boundary.createRequestrequires a non-blank note;promotenote stays optional and won’t erase an existing justification; reopen guards verified; audit event wording now covers creates.Written for commit faf4073. Summary will update on new commits.