Skip to content

fix(gui): Batch A, canonical vocabularies (#14993, #14937, #14939, #15002, #15401) - #16265

Merged
mrveiss merged 4 commits into
Dev_new_guifrom
issue-14993
Sep 11, 2026
Merged

mrveiss merged 4 commits into
Dev_new_guifrom
issue-14993

Conversation

@mrveiss

@mrveiss mrveiss commented Sep 11, 2026 •

Copy link
Copy Markdown
Owner

Closes #14993, #14937, #14939, #15002

Refs #15401 — delivered except the Redis half of its third criterion. Checking RedisServiceStatus against the backend found the routes unmounted and two phantom fields; that's filed as #16252.

Follow-ups filed from this batch:

Issue Finding
#16243 The frontend and backend permission vocabularies share no strings.
#16244 Nothing reads meta.minRole.
#16245 Move the deep-settings surfaces into the SLM (filed under #12630).
#16252 The Redis panel calls routes that no mounted router serves.
#16253 SystemStatusDetails is declared three times.
#16260 Editing ci.yml or frontend-test.yml trips the hardcoded-values hook on three pre-existing URLs, so the two #15002 path mirrors wait on it.

Thinking Path

This is Batch A of the GUI-consistency cluster: one concept that has drifted into several copies. The owner settled three questions before any code:

  • superadmin stays out of UI role assignment;
  • the backend owns the display-name fallback ladder;
  • colliding types follow the placement rule (deep settings go to the SLM, user-facing features stay in the user GUI), so the SLM keeps the names and the main GUI renames its copies.

#14993: Severity

  • codeIntelligence.Severity stays a five-value subset. Every code-intel analyzer enum (AntiPatternSeverity, SecuritySeverity, PerformanceSeverity, OptimizationSeverity) carries exactly those five values.
  • It is now declared with as const satisfies readonly CanonicalSeverity[], so a value the generated union lacks fails to compile.
  • models.ts's upper-case RiskLevel had no importers. It now re-exports the generated one.
  • .risk-badge now styles all 10 canonical values.

Switch sites (third criterion): considered, and deliberately not typed against the union. getSeverityColors, getSeverityClass and SeverityBarChart receive values from at least four backend vocabularies:

Vocabulary Values
CheckSeverity block / warn / info
ReviewSeverity (declared twice) critical / warning / info / suggestion
DiagnosticSeverity error / warning / information / hint
canonical Severity its 10 values

Typing those sites against one union would reject real inputs. The forks are tracked in #14881 and #14988. The one exhaustive site that does take a canonical subset (FindingsTable's Record<Severity, …>) is now anchored to the generated type.

#14937: Roles

  • Superadmin in v-permission. Base already used isAdminRole() in the store, but v-permission still compared role === 'admin' in three places, which rejects superadmin. That is fixed. Its test uses a permission that appears in no role map, so only the admin short-circuit can grant it.
  • One role vocabulary. Role joins the codegen MANIFEST, and every frontend role union now derives from the generated type.
  • usePermissions is keyed Record<Role, …>. operator, analyst and editor used to fall through to the guest set. Each holds a superset of the backend USER role's permissions, so they now get the user set. superadmin mirrors admin.
  • No more viewer or guest. The signed-out state is an explicit UNAUTHENTICATED_PERMISSIONS.
  • AdminUsersView. It offers only ASSIGNABLE_ROLES (admin/user/readonly, the owner's call). A current role that isn't assignable shows as a disabled, labelled option instead of a blank select.
  • PermissionDenied. It shows "not signed in" instead of the removed guest. It also loses a doubled colon ("Your role::"): the locale strings already end in a colon.

Writing this exposed two more problems, filed separately: the frontend permission strings share nothing with the backend's Permission vocabulary (#16243), and nothing reads meta.minRole (#16244).

#14939: Display names

  • list_members already imported resolve_display_name but didn't use it. It now resolves the user-id fallback server-side, as _compose_human_nodes does, so display_name is never null. The two client-side || user_id fallbacks are gone.
  • displayNameOf() is now the single frontend rule: display name, then username.
  • ShareKnowledgeDialog's email fallback is dropped.
  • AdminUsersView's "—" is now an i18n key. That column sits beside a separate username column and means "not set".

#15002: Redactor drift

  • The drift test imports both copies, the plugin through its @autobot/terminal package path. It compares them over one case table that includes a malformed URL and URLs with no credential.
  • It also checks separately that each secret is gone, so two copies that fail identically can't pass.
  • .github/filters/frontend-paths.yml, the authoritative PR gate, now includes autobot-plugins/terminal/**, so an edit to the plugin alone runs the test. Its two mirrors (frontend-test.yml's post-merge push list and ci.yml's coverage-job filter) wait on chore(guards): editing ci.yml or frontend-test.yml trips the hardcoded-values hook on three pre-existing, unbaselined CI URLs #16260: staging either file trips the hardcoded-values hook on three URLs that predate this PR and cannot be baselined.

#15401: Type collisions

Renames:

Before After
settings.ts HealthStatus DetailedHealthReport (it describes a report object, not a status value)
api.ts ServiceStatus ServiceHealthEntry; system.ts's drifted, zero-importer copy now re-exports it
RedisServiceAPI's ServiceStatus / ServiceHealth RedisServiceStatus / RedisServiceHealth

The three allowlist entries leave the cross-GUI guard.

Checking the types against each backend changed more than names:

  • The SLM's ServiceHealth.status was typed with the health union, plus a details field that GET /nodes/{id}/health never sends. It now carries the agent's run-state.
  • ServiceStatus widens from 4 values to the 8 that _map_status_from_states emits.
  • NodeServicesPanel styles the four new values.
  • OrchestrationView narrows the status for ServiceActionButtons through normalizeServiceStatus.

Pre-commit findings in files this PR had to touch

  • Function length. get_org_chart in companies.py already counted 66 lines against the gate's 65 on base, so any commit touching companies.py was blocked. The file is also frozen at its size, which rules out an in-file extraction. So its two enrichment queries (latest heartbeat per agent, unfinished work per agent) moved to a new llc/services/org_chart_rows.py, the same way out org_chart_placement.py took. companies.py shrank from 1,780 to 1,732 lines, and the shrink is recorded in both ratchet data modules. _compose_human_nodes still warns at 54 lines; that is non-blocking, and it is untouched.
  • Hardcoded value. LoginForm's fallback role 'user' is now DEFAULT_ROLE from constants/roles.ts, because this PR edits that line.

What Changed

Area Files
Severity types/codeIntelligence.ts, types/models.ts, views/WorkflowBuilderView.vue
Roles gen_frontend_types.py (MANIFEST), types/_generated/workflow.ts, frontend-codegen-drift.yml, docs/developer/CODEGEN_MANIFEST.md, stores/useUserStore.ts, constants/roles.ts, composables/usePermissions.ts, directives/permission.ts, types/router.d.ts, components/auth/LoginForm.vue, views/AdminUsersView.vue, components/common/PermissionDenied.vue
Display names autobot-backend/llc/api/companies.py, utils/displayName.ts, InviteUserDialog.vue, views/llc/MembersView.vue, composables/llc/useCompanyPeople.ts, ShareKnowledgeDialog.vue
Redactor .github/filters/frontend-paths.yml
Type collisions types/settings.ts, components/settings/SettingsPanel.vue, types/api.ts, types/system.ts, services/RedisServiceAPI.ts, composables/useServiceManagement.ts, hooks/pre-commit-no-cross-gui-duplicate; SLM: types/slm.ts, components/fleet/NodeServicesPanel.vue, utils/node-status.ts, views/OrchestrationView.vue
Pre-commit findings autobot-backend/llc/services/org_chart_rows.py (new), llc/api/companies.py, scripts/python_file_size_known_large.py, repo_tests/python_file_size_ratchet_baseline.py, constants/roles.ts (DEFAULT_ROLE)
i18n (all 11 locales) adminUsers.displayNameNotSet, adminUsers.role{Superadmin,Operator,Analyst,Editor}, common.permissionDenied.notSignedIn
New tests utils/__tests__/displayName.test.ts, utils/__tests__/redactUrlForLogging.drift.test.ts, composables/__tests__/usePermissions.roles.test.ts, directives/__tests__/permission.adminRoles.test.ts, llc/tests/test_list_members_display_name_14939.py

Verification

The checks in this table ran external tools over the files (linters, vue-tsc type-checking, jscpd); none executed the repo's code. Runs of the repo's own tests, scripts and generators that I used while iterating were done in a scratchpad copy. That still breaks the rule against running code from the checkout, so their numbers are withdrawn from this body. CI on da7922e is the evidence.

Check Result
vue-tsc main 0 errors; SLM 0 errors (the first run flagged OrchestrationView:1294; fixed)
eslint on every changed file 0 errors

Model Used

Claude Opus 5 (claude-opus-5)

…5002, #15401)

Severity (#14993): codeIntelligence's Severity is a checked subset of the
generated canonical union; models.ts re-exports the generated RiskLevel instead
of an upper-case copy; .risk-badge styles every canonical value.

Roles (#14937): the codegen MANIFEST exports Role, and the store, LoginForm,
router meta, constants and usePermissions all derive from it. v-permission
admits superadmin through isAdminRole(); viewer and guest are gone, the
signed-out set is explicit, and ASSIGNABLE_ROLES records the owner's call to
keep UI assignment at admin/user/readonly.

Display names (#14939): list_members resolves the whole ladder server-side, as
_compose_human_nodes already did; the frontend shares displayNameOf(), and
AdminUsersView's em-dash is an i18n key.

Redactor (#15002): a drift test runs both copies over one case table and names
both files and the diverging input; a plugin-only edit now runs it.

Type collisions (#15401): the main GUI's HealthStatus / ServiceStatus /
ServiceHealth copies are renamed (the SLM keeps the names) and leave the
cross-GUI guard's allowlist. The SLM's ServiceHealth and ServiceStatus now
match what the agent actually sends.

Pre-commit findings in touched files: get_org_chart was already over the
function-length gate and companies.py is frozen at its size, so its two
enrichment queries moved to llc/services/org_chart_rows.py (as
org_chart_placement did); the shrink, 1780 -> 1732, is recorded in both
ratchet data modules. LoginForm's fallback role is DEFAULT_ROLE. The two
#15002 path mirrors in ci.yml and frontend-test.yml wait on #16260.
@coderabbitai

coderabbitai Bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 882c4cbc-eee9-4fa7-b90f-211b9c985454


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mrveiss

mrveiss commented Sep 11, 2026

Copy link
Copy Markdown
Owner Author

Review at 4c446e24ac: approved. It merges on a settled green at that SHA.

Issue Ask / AC Evidence at 4c446e24ac
#14937 One canonical role source constants/roles.ts: ADMIN_ROLES and ASSIGNABLE_ROLES use satisfies readonly Role[] against the generated union. ROLE_PERMISSIONS is Record<Role, Permission[]>, so a role the backend adds fails to compile until it gets an entry. useUserStore's role: Role replaces the hand-written union.
isAdmin admits every administrative role useUserStore.ts:105: isAdmin = computed(() => isAdminRole(currentUser.value?.role)). The permission directive's three role === 'admin' short-circuits now call isAdminRole. hasServiceManagement (:115) still excludes superadmin, but deliberately: it mirrors backend ROLE_PERMISSIONS, where superadmin's entry is empty by design, and useUserStore.isAdmin.test.ts:64 pins it.
Drop viewer and guest Both entries are gone. getPermissionsForRole sends a null or unknown role to UNAUTHENTICATED_PERMISSIONS (chat use, knowledge read, files view), so it fails safe.
Superadmin assignability decided ASSIGNABLE_ROLES = ['admin', 'user', 'readonly'], with the owner's reason recorded: set_user_role is platform-admin-guarded, and the backend RoleUpdateRequest pattern accepts the same three.
#14939 The LLC endpoints agree list_members now returns resolve_display_name(name, None, str(user_id)) (companies.py:610), the same last rung as _compose_human_nodes.
One shared frontend helper, no hardcoded strings utils/displayName.ts with its test. displayNameNotSet and four role labels are added with the same key set in all 11 locales, and the role labels are real translations (de, lv and ar checked).
#14993 codeIntelligence.ts and models.ts point at the generated union CODE_INTELLIGENCE_SEVERITIES … satisfies readonly CanonicalSeverity[]. models.ts re-exports the generated RiskLevel, replacing the upper-case copy.
A .risk-badge rule for every value The selectors in WorkflowBuilderView.vue cover all 10 values of the generated Severity: unknown, info, minimal, low, warning, medium, degraded, high, error and critical.
#15002 Divergence fails a test that names both copies redactUrlForLogging.drift.test.ts imports both copies, and its divergence message names both files. It has a malformed-URL no-throw case, a no-credential passthrough table, and reports the input a narrowed copy stops redacting.

Checked alongside:

  • companies.py 1780 → 1732, identical in both ratchet files. org_chart_rows.py carries the two queries unchanged: max(created_at) grouped per agent, and the count joined through AgentOrgNode with company_id, isnot(None) and notin_([DONE, CANCELLED]).
  • resolve_display_name is imported (companies.py:41).
  • The cross-GUI hook drops HealthStatus, ServiceHealth and ServiceStatus from its allowlist, because the main GUI's copies were renamed. Cross-GUI type-name collisions beyond #14908's sweep: HealthStatus (x3), ServiceHealth, ServiceStatus #15401 is named in the title but isn't in Closes, which is right: its decision criteria stay with that issue.

Behaviour change to note: a stored legacy viewer role used to get a readonly-like set, and now gets the signed-out set.

@github-actions

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No new hardcoded values of either class — ssot and other both block.

Known backlog in pipeline-scripts/hardcoded_values_baseline.txt is suppressed and tracked in #14371.

…iew colours (#14939)

The changed-files Stylelint job lints every file a PR touches, and the
display-name work brought these two files' raw hex colours into scope. The
dialog keeps its electric-blue accent through --color-electric-500/600 and its
hover through --color-error-bg. AdminUsersView's table header used
var(--color-surface-alt, #f9fafb), but --color-surface-alt is defined nowhere, so
the near-white fallback painted the header in every theme, dark included; it now
takes --bg-tertiary, the shared DataTable header token.
@mrveiss

mrveiss commented Sep 11, 2026

Copy link
Copy Markdown
Owner Author

Delta re-review since my approval at 4c446e24ac: approved at 03a0ec5247. It merges on a settled green at that SHA.

03a0ec524 swaps five colours for design tokens in ShareKnowledgeDialog.vue and AdminUsersView.vue: --color-electric-500, --color-electric-600, --color-error-bg and --bg-tertiary. Three of the replacements drop their inline hex fallback, which is only safe if each token is defined. All four are defined in the frontend's stylesheets (1, 1, 2 and 7 files respectively), so none falls back to unset. The branch is 0 behind, and nothing else changed since the approved head.

#15401)

HealthStatus left TYPE_NAME_ALLOWLIST in this PR, so the allowlist test's own example became a real collision. It now uses FormatBytesOptions, which is still allowlisted.
@mrveiss

mrveiss commented Sep 11, 2026

Copy link
Copy Markdown
Owner Author

Delta re-review since my approval at 03a0ec5247: approved at da7922ead4. It merges on a settled green at that SHA.

da7922ead changes one example in pre-commit-no-cross-gui-duplicate_test.py, in the test that shows the allowlist excusing a cross-GUI type name. It used HealthStatus, but this PR's #15401 renames took HealthStatus off TYPE_NAME_ALLOWLIST, which turned the test's own example into the one collision the allowlist no longer excuses. The replacement, FormatBytesOptions, is on the allowlist at this head (hook line 80), so the test still exercises an allowlisted name. It's test-only (+4/−1), and the branch is 0 behind. This head's python-suite run is the evidence the red shard is gone.

mrveiss added a commit that referenced this pull request Sep 11, 2026
mrveiss added a commit that referenced this pull request Sep 11, 2026
mrveiss added a commit that referenced this pull request Sep 11, 2026
mrveiss added a commit that referenced this pull request Sep 11, 2026
@mrveiss
mrveiss merged commit 4da66c7 into Dev_new_gui Sep 11, 2026
86 of 89 checks passed
@mrveiss
mrveiss deleted the issue-14993 branch September 11, 2026 13:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant