diff --git a/.claude/commands b/.claude/commands
index 95b417d3..315ad0e7 160000
--- a/.claude/commands
+++ b/.claude/commands
@@ -1 +1 @@
-Subproject commit 95b417d3c70f7ec4cdf372e9432382f428ff71f7
+Subproject commit 315ad0e73645e212ec0b38a61a15bf4c73a707f6
diff --git a/internal b/internal
index 148e99f2..7cb78f47 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 148e99f2e69bab60f39213a280f10b0f355ed4d9
+Subproject commit 7cb78f47ead7a9f36a61c5d43acb2d0fdbc9d1e6
diff --git a/src/Web/autopilot-monitor-web/app/analyze-rules/page.tsx b/src/Web/autopilot-monitor-web/app/analyze-rules/page.tsx
index 764b94bf..327fd6d6 100644
--- a/src/Web/autopilot-monitor-web/app/analyze-rules/page.tsx
+++ b/src/Web/autopilot-monitor-web/app/analyze-rules/page.tsx
@@ -112,6 +112,9 @@ export default function AnalyzeRulesPage() {
// A read-only Global Reader — and an own-tenant admin viewing a FOREIGN tenant (cross-tenant override) —
// is read-only. Backend also enforces (rules write is TenantAdminOrGA, cross-tenant blocked for non-GA).
const isReadOnly = !(user?.isGlobalAdmin || (user?.isTenantAdmin && !isGlobalOverride));
+ // Community submissions are TenantAdminOrGA on the own tenant; only a global-scope override reads them through
+ // the operator list. Everyone else (Operator, Viewer) gets a hint instead of a 403 on the list call.
+ const submissionsAdminOnly = isReadOnly && !isGlobalOverride;
// Cross-tenant write routing: in a Global Admin override the JWT-scoped rules/analyze/{id} route
// resolves the tenant from the caller's token and would silently upsert into the GA's OWN tenant
@@ -610,15 +613,17 @@ export default function AnalyzeRulesPage() {
) : (
{/* Community contribution: submit own custom rules for the shared pool; the list shows their status. */}
-
setShowSubmitModal(true)} />
-
+ setShowSubmitModal(true)} adminOnly={submissionsAdminOnly} />
+ {!submissionsAdminOnly && (
+
+ )}
{/* Summary Stats */}
diff --git a/src/Web/autopilot-monitor-web/app/gather-rules/page.tsx b/src/Web/autopilot-monitor-web/app/gather-rules/page.tsx
index b8f7d2f6..179f0d32 100644
--- a/src/Web/autopilot-monitor-web/app/gather-rules/page.tsx
+++ b/src/Web/autopilot-monitor-web/app/gather-rules/page.tsx
@@ -83,6 +83,9 @@ export default function GatherRulesPage() {
// A read-only Global Reader — and an own-tenant admin viewing a FOREIGN tenant (cross-tenant override) —
// is read-only. Backend also enforces (rules write is TenantAdminOrGA, cross-tenant blocked for non-GA).
const isReadOnly = !(user?.isGlobalAdmin || (user?.isTenantAdmin && !isGlobalOverride));
+ // Community submissions are TenantAdminOrGA on the own tenant; only a global-scope override reads them through
+ // the operator list. Everyone else (Operator, Viewer) gets a hint instead of a 403 on the list call.
+ const submissionsAdminOnly = isReadOnly && !isGlobalOverride;
// Cross-tenant write routing: in a Global Admin override the JWT-scoped rules/gather/{id} route
// resolves the tenant from the caller's token and would silently upsert into the GA's OWN tenant
@@ -444,15 +447,17 @@ export default function GatherRulesPage() {
)}
{/* Community contribution: submit own custom rules for the shared pool; the list shows their status. */}
-
setShowSubmitModal(true)} />
-
+ setShowSubmitModal(true)} adminOnly={submissionsAdminOnly} />
+ {!submissionsAdminOnly && (
+
+ )}
{/* Summary Stats */}
diff --git a/src/Web/autopilot-monitor-web/components/rules/CommunityContributionBox.tsx b/src/Web/autopilot-monitor-web/components/rules/CommunityContributionBox.tsx
index 9d8b19d7..5f67944c 100644
--- a/src/Web/autopilot-monitor-web/components/rules/CommunityContributionBox.tsx
+++ b/src/Web/autopilot-monitor-web/components/rules/CommunityContributionBox.tsx
@@ -6,10 +6,12 @@ import { DOCS_PATHS } from "@/lib/docsPaths";
interface CommunityContributionBoxProps {
/** Undefined hides the button (read-only roles, no custom rules yet). */
onContribute?: () => void;
+ /** The caller's role cannot contribute at all (contributing is tenant-admin only): say so instead of just hiding the button. */
+ adminOnly?: boolean;
}
/** The community call-to-action on the rule pages: contribute a rule here, report bugs on GitHub. */
-export function CommunityContributionBox({ onContribute }: CommunityContributionBoxProps) {
+export function CommunityContributionBox({ onContribute, adminOnly }: CommunityContributionBoxProps) {
return (
@@ -22,6 +24,9 @@ export function CommunityContributionBox({ onContribute }: CommunityContribution
open a GitHub issue
.
+ {adminOnly && (
+
Only tenant admins can contribute rules and follow the tenant's submissions — ask an admin of your tenant to submit it.
+ )}
{onContribute && (