fix(profiles): nested refusal block_reason and explain move-client hint (Spec fix-nested-refusal) - #1468
Merged
Conversation
…son (Spec 108 T166) A call_tool refused by the caller's profile inside code_execution wrote its blocked tool_call child without block_reason, so activity could not tell a tier refusal from a rule or unannotated one. The sandbox gate now reports the typed reason through a second optional method, and the child record persists it as both the first-class field and the metadata key.
…profile (Spec 108 T167) The move-client fix printed 'mcpproxy client set-profile <client> <profile>' beside a label that named a concrete profile. Fix gains an additive, move_client-only profile field and the CLI prints the real command, keeping the placeholder for a daemon that predates the field.
…explain hint (Spec 108 T168)
Deploying mcpproxy-docs with
|
| Latest commit: |
4e1e404
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://2ff3e22c.mcpproxy-docs.pages.dev |
| Branch Preview URL: | https://fix-nested-refusal-block-rea.mcpproxy-docs.pages.dev |
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Contributor
📦 Build ArtifactsWorkflow Run: View Run Available Artifacts
How to DownloadOption 1: GitHub Web UI (easiest)
Option 2: GitHub CLI gh run download 37068217635 --repo smart-mcp-proxy/mcpproxy-go
|
…f6b-1 # Conflicts: # ROADMAP.md
11 tasks
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.
Summary
Closes the two gaps the Spec 108 done-check found.
Nested profile refusals now carry
block_reason(US1-5, FR-029). Acall_toolorcall_toolselement that the caller's profile refuses insidecode_executionalready wrote a blockedtool_callchild withparent_id, but the record had noblock_reason. The child now carriesprofile_tier,profile_ruleorprofile_unannotated, the same value the top-levelpolicy_decisioncarries, next to the sameprofileandprofile_source. Every existing surface already readsEffectiveBlockReason(), so REST/activity,mcpproxy activity list|show, the Web UI drawer and the macOS Activity window show it with no change of their own. Refusals that are not profile tool-policy ones (server outside the profile, token scope or permission, quarantine, approval) are unchanged and carry noblock_reason.access explainmove-client hint names the destination (FR-035). The fix readMove Cursor to work-fullbut the command printedmcpproxy client set-profile cursor <profile>. The fix now carries the destination slug in a new additiveprofilefield (move_client only), and the CLI printsmcpproxy client set-profile cursor work-full. A CLI talking to a daemon that predates the field keeps the placeholder.Decisions
tool_callwithstatus=blocked, like every other nested refusal, so?parent_id=drill-down and usage aggregation keep working.ProfilePolicyBlockReason().ProfilePolicyRefusal()is untouched on purpose: the sandbox finds it by an anonymous interface assertion, so changing its signature would silently stop nested profile refusals from refusing. The method is pinned at compile time on the sandbox gate, and a jsruntime test proves a gate that only implementsProfilePolicyRefusalstill refuses.handleToolCallCompletedwrites bothrecord.BlockReasonandMetadata["block_reason"], the same one-release rule aspolicy_decision. When there is no reason,Metadatais not allocated, so legacy records are byte-identical.EmitActivityToolCallCompletedAttributedgainsblockReasonjust beforeattr; the server side addsemitActivityToolCallCompletedWithBlockReasonand keepsemitActivityToolCallCompletedas a delegate, andstatusstays at argument index 6.profile,profile_source, token, client) of the child was already correct through the sandbox's execution context; this PR adds tests that pin it.authz denyaudit line still reports the Spec 107 reason, not the top-levelother. Aligning them touches the Spec 107 audit contract and is left as a follow-up.profile. The Web UI navigates bytarget, so this is harmless; follow-up.Spec
specs/108-profiles-v3(tasks T166 to T168, Phase 17):contracts/refusals.md,contracts/mcp-tools.md,contracts/rest-api.md,contracts/cli.md,data-model.md,plan.md,tasks.md. User docs:docs/features/activity-log.md,docs/features/profiles.md,docs/cli/profile-commands.md.oas/swagger.yamlandoas/docs.gogain onlyruntime.Fix.profile.Tests added
internal/server:TestCodeExecution_ProfileV3NestedCallBlockedBeforeUpstream(extended),TestCodeExecution_ProfileV3NestedRefusalReasons(tier, rule, unannotated, batch element, and the no-reason server-outside case, each with zero upstream dispatch),TestCodeExecution_ProfileV3NestedRefusalMatchesTopLevel,TestExplain_FixesOrderedByPreferenceandTestExplain_DanglingClientBindingMoveFixNamesDestination(extended and new), and thestatusArgIndexguard for the new emit funnel.internal/jsruntime:TestAuthzObserver_ProfileRefusalReportsBlockReason.internal/runtime:TestActivityService_ToolCallCompletedPersistsBlockReason.cmd/mcpproxy:TestAccessExplain_MoveClientFixNamesDestinationProfile.internal/httpapiandinternal/profile: the sharedexplain_blocked.jsoncontract fixture now carriesprofileon the move_client fix.