fitzee commented on code in PR #44094:
URL: https://github.com/apache/superset/pull/44094#discussion_r4001404605


##########
superset/mcp_service/dashboard/tool/manage_dashboard_certification.py:
##########
@@ -92,6 +92,25 @@ def manage_dashboard_certification(
             warnings=["No fields provided; dashboard unchanged."],
         )
 
+    # Externally managed dashboards refuse certification CHANGES (the
+    # no-field inspect path above still returns current values): their
+    # source of truth lives outside Superset, so a badge set here would be
+    # overwritten (or drift from the certifying system) on the next
+    # external sync. This tool writes by direct attribute assignment +
+    # commit rather than through a command, so no command-layer check can
+    # protect it — the refusal must live in the tool itself, after the
+    # editorship check, where a caller with no edit rights keeps getting
+    # the plain editorship denial.
+    if dashboard.is_managed_externally:

Review Comment:
   **Fix altitude: this guard is a per-tool special case, but the sibling 
governance tools share the exact same command-bypassing direct-commit write 
path and remain unguarded.** I verified none of them check 
`is_managed_externally` (it appears only here + schemas.py):
   - `manage_dashboard_owners.py:193` — `db.session.commit()` after mutating 
owners
   - `manage_dashboard_roles.py:287` — `db.session.commit()` after mutating 
roles
   - `update_dashboard.py:181-206` — direct assignment of `dashboard_title` / 
`slug` / `json_metadata` / `css`, commit at 360
   
   All three go through the same `find_and_authorize_dashboard` helper (which 
does editorship but not this check), and the comment here explicitly cites 
*"writes by direct attribute assignment + commit rather than through a command, 
so no command-layer check can protect it"* — which is equally true of those 
tools. So an LLM caller can still add/remove owners, change RBAC roles, or edit 
title/metadata/CSS on an externally-managed dashboard, and those writes 
drift/get overwritten on the next external sync — the same harm this PR guards 
against for certification. Consider hoisting the check into a shared helper 
(with care to preserve certification's no-field *inspect* path, which should 
still work on managed dashboards), so all mutating tools inherit it.



##########
superset/mcp_service/dashboard/tool/manage_dashboard_certification.py:
##########
@@ -92,6 +92,25 @@ def manage_dashboard_certification(
             warnings=["No fields provided; dashboard unchanged."],
         )
 
+    # Externally managed dashboards refuse certification CHANGES (the
+    # no-field inspect path above still returns current values): their
+    # source of truth lives outside Superset, so a badge set here would be
+    # overwritten (or drift from the certifying system) on the next
+    # external sync. This tool writes by direct attribute assignment +
+    # commit rather than through a command, so no command-layer check can
+    # protect it — the refusal must live in the tool itself, after the
+    # editorship check, where a caller with no edit rights keeps getting
+    # the plain editorship denial.
+    if dashboard.is_managed_externally:
+        return ManageDashboardCertificationResponse(
+            permission_denied=True,
+            error=(
+                f"Dashboard '{dashboard.dashboard_title}' (ID: {dashboard.id}) 
"
+                "is managed externally; its certification is owned by the "

Review Comment:
   **Doc nit:** the tool docstring (lines 62-73) still advertises unconditional 
set/clear and never mentions that externally-managed dashboards are refused — 
the new behavior is documented only in the response schema. An agent reading 
the tool description will attempt the mutation and be surprised by the 
`permission_denied` response. Worth a one-line note in the docstring (and 
ideally the example) so the tool's own description reflects the refusal.



##########
superset/mcp_service/dashboard/tool/manage_dashboard_certification.py:
##########
@@ -92,6 +92,25 @@ def manage_dashboard_certification(
             warnings=["No fields provided; dashboard unchanged."],
         )
 
+    # Externally managed dashboards refuse certification CHANGES (the
+    # no-field inspect path above still returns current values): their
+    # source of truth lives outside Superset, so a badge set here would be
+    # overwritten (or drift from the certifying system) on the next
+    # external sync. This tool writes by direct attribute assignment +
+    # commit rather than through a command, so no command-layer check can
+    # protect it — the refusal must live in the tool itself, after the
+    # editorship check, where a caller with no edit rights keeps getting
+    # the plain editorship denial.
+    if dashboard.is_managed_externally:
+        return ManageDashboardCertificationResponse(
+            permission_denied=True,

Review Comment:
   **Semantics: overloading `permission_denied` conflates a structural refusal 
with an ACL denial, and they need opposite remediation.** The same flag is set 
by `find_and_authorize_dashboard` for real access denials whose canonical 
guidance is *"Ask the user to grant access; do not retry."* A 
managed-externally refusal is structural — granting permissions can never 
resolve it — yet a downstream agent that branches on the boolean (rather than 
parsing the free-text `error`) will tell the user to request access and, worse, 
may retry. The schema doc update helps for clients that read it, but the 
boolean itself can't be disambiguated. Consider a distinct signal (e.g. a 
`managed_externally: true` field or a machine-readable reason code) so agents 
can route the two cases differently.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to