Skip to content

refactor(slm-frontend): derive the monitoring long tail from the generated schema (#13138) - #13159

Merged
mrveiss merged 3 commits into
Dev_new_guifrom
issue-13138-monitoring
Jul 31, 2026
Merged

mrveiss merged 3 commits into
Dev_new_guifrom
issue-13138-monitoring

Conversation

@mrveiss

@mrveiss mrveiss commented Jul 31, 2026 •

Copy link
Copy Markdown
Owner

Closes #13161 — the monitoring long-tail slice of #13138. #13138 itself stays OPEN: 4 shapes remain, 3 of them still blocked on #13145 and the 4th an alias-of-derived counting artifact. Partial delivery never closes the parent.

Thinking Path

Fourth slice of #13138, after #13137 (39), #13146 (18) and #13152 (23). Target was the monitoring long tail — types/api-responses.ts (~30) and the rest of types/slm.ts (~20).

Measured on the tip with the three rules the prior slices established (require a declaration body · exclude import { type X } · a name counts as remaining if any declaration lacks a components['schemas'][…] body): 309 schemas, 422 hand-declared names, 132 collisions, 78 derived, 54 still hand-written. One unit above the 53 reported by slice 3: ThreatSummary at SecurityView.vue:43 is type ThreatSummary = ThreatSummaryType, an alias of the derived import — remaining by the strict per-name rule, already correct in substance. Effective 53, so the count reproduces.

Treated every disagreement as a finding and established which side was wrong before making it compile. Four defects fell out, all with the server right.

Two more view-models found, exactly where the brief predicted. The four already known (DashboardOverview/SystemMetrics in usePrometheusMetrics.ts, LogEntry ×2) plus a fifth: RoleInfo at SetupWizardView.vue:594 declares a display_name the contract does not have — loadRoles builds it from description, because the backend already folds the registry's display_name into description (api/deployments.py:157-160). Deriving it would have swapped a working local shape for an unrelated wire shape. All five are renamed, not derived, so the collision cannot come back. A sixth candidate, RoleInfo at DeploymentWizard.vue:12, is a straight projection rather than a remap, so it drops its copy for one with derived member types.

What Changed

53 → 4 remaining. 50 shapes derived; 3 of the 4 left are blocked on #13145, the 4th is the ThreatSummary alias.

Defects found (server right in every case)

1. A blue-green deployment in post-deploy monitoring showed no state at all. BlueGreenStatus omitted 'monitoring' — the health-watch state set at services/blue_green.py:780 and held for up to post_deploy_monitor_duration seconds (default 1800). It matched none of the four bgStats buckets (DeploymentsView.vue:202-205) and fell through getStatusClass to the neutral gray default, so a deployment being actively health-watched vanished from every tile and its badge claimed nothing was happening. The hand-written type also had no health_failures, health_failure_threshold, monitoring_started_at or post_deploy_monitor_duration, so there was nothing to render even had it been counted. 4 new tests, confirmed failing against the pre-fix view.

2. NodeRole was missing 'docker'. constants/node-roles.ts states "Source of truth: role_registry.py:DEFAULT_ROLES" and "Keep in sync", but the registry has 21 roles and the union had 20 — docker (_INFRA_ROLES, role_registry.py:372) has always been returned by GET /deployments/roles. Record<NodeRole, RoleMetadata> forced the matching metadata entry once the union was corrected.

3. PlaybookInfo was declared identically twice (InfrastructureWizard.vue:19, InfrastructureView.vue:20) — one backend change, two places to drift from. Same class as the /security/* duplicate in #13146. Both omitted tags and typed category as a bare string where the contract has the PlaybookCategory enum.

4. BlueGreenDeployment / BlueGreenCreate / BlueGreenListResponse were each declared twice for the same wire models, in types/slm.ts and types/api-responses.ts, with DeploymentsView.vue:335 bridging them via as BlueGreenDeployment[] — so a divergence would have been silently cast away. Now one derivation plus aliases, and the cast is gone.

Fields the frontend type could not reach

RoleInfo.ansible_role · NodeService.endpoint_path / port / protocol / is_discoverable · BlueGreenCreate.health_failure_threshold / post_deploy_monitor_duration (so the New Blue-Green modal could never configure post-deploy monitoring) · PlaybookInfo.tags.

Optional-and-nullable, declared required

temperature_celsius (NPUPerformanceMetrics.vue:54,61 tested only === null, so an absent reading rendered undefined°C in the "cool" colour), memory_bytes (NodeServicesPanel.vue:145 — the body already handled undefined, only the signature disagreed; useNodeServices.ts:56 used !== null), the blue-green timestamps (DeploymentsView.vue:575), RecentError.resolved_at / resolved_by, MetricsSummary's three nullables, FleetMetricsResponse.timestamp, SystemHealthResponse.last_check, AlertItem.node_id / hostname, PlaybookExecution.output. NPUWorkerConfig.assigned_models is default_factory=list and so omittable — toggleModel indexed it unguarded, now normalised at the load site.

Widening, resolved per type

Case Types Resolution
Constrained in a way the schema cannot express RestartAllServicesRequest.category (pattern="^(autobot|system|all)$"), NodeService.category derive, keep the union
Unconstrained but exhaustively enumerable ErrorStatistics.trend (3 branches, errors.py:245-273), ErrorHealthResponse.status (3 branches, :509-518), BlueGreenStatus, NPULoadBalancingConfig.strategy (enum at schemas.py:2078-2085), RoleInfo.name/category derive, keep the union, cite the sites
Frontend-side guarantee NPUWorkerConfig.failure_action — sole construction site is the four-option <select> at NPUDetailsPanel.vue:383-386 derive, keep the union
Frontend claim unverifiable NPUCapabilities.deviceType — copied verbatim out of an external worker's /health payload (api/npu.py:83); all three renderers already fall back to the raw string widened
Frontend claim describes a field the endpoint never populates RoleInfo.dependencies (deployments.py:166 passes [] unconditionally) widened

Bare dict server-side

RestartAllServicesResponse.results is List[Dict] (schemas.py:920), so the contract can only say { [key: string]: unknown }[]. Derived for the scalars and intersected to pin RestartServiceResult, whose keys are built at api/services.py:892-897 and always include the is_slm_agent the UI reads.

A third trap for the next slice

Omit/Pick collapse (#13152) and required-means-defaulted (#13152) both held. A new one: A[] & B[] keeps both map signatures and TypeScript resolves the callback against the first, so base & { items: Narrow[] } silently types the callback parameter as the WIDE element — that is exactly how useNodeServices.ts:52 ended up mapping category as string with no error at the definition site. Writing { items: Narrow[] } & base picks the narrowed element. All list envelopes here use that order, and the rule is recorded on RoleListResponse.

Deferred, with reasons

Verification

All measured in a worktree synced to Dev_new_gui, rebased onto a1fc379d7 mid-work after another session pushed #13153, then re-verified.

  • npm run type-check — clean (vue-tsc --noEmit -p tsconfig.json, no output).
  • npx vitest run — 27 files / 219 tests passed, up from 26 / 215 (+4 blue-green tests). Baseline taken against a cp-swapped pristine src/ restored from git archive, never git stash.
  • The 4 new tests fail against the pre-fix view, confirmed by swapping the pristine DeploymentsView.vue back in: expected '0' to be '1' for the active tile, and expected … to contain 'bg-blue-100 text-blue-800' for the badge. The two regression tests (pre-existing in-flight statuses, terminal status) pass both before and after.
  • npm run lint — 16 problems, 0 errors, 16 warnings, identical to the pristine baseline apart from one line-number shift in DeploymentWizard.vue (107 → 123) caused by an added doc comment: same file, same rule, same finding.
  • git diff --exit-code src/types/generated/api.ts — clean, so verify-generated-types-slm holds.
  • Remaining count re-measured with the same scan: 309 schemas, 428 hand-declared names, 132 collisions, 128 derived, 4 still hand-written.
  • No AutoBot application code was run (agents: forbid privilege escalation — SLM backend run as root in a worktree left 147 root-owned files (#12662) #13090) — frontend tooling only, no backend, no schema dump, no Postgres/Redis, no sudo.

Model Used

claude-opus-5

@github-actions

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No hardcoded values detected that have SSOT config equivalents!

mrveiss added 3 commits July 31, 2026 23:35
…odels from the generated schema (#13138)

Derives 47 hand-declared SLM frontend types from components['schemas'][...]:
the whole monitoring/observability long tail in types/api-responses.ts and the
NPU, maintenance, service, role, blue-green and external-agent shapes in
types/slm.ts.

Drift the derivation exposed and corrected:

- BlueGreenStatus omitted 'monitoring' (models/database.py:581), the
  post-deploy health-watch state set at services/blue_green.py:780.
- NodeRole omitted 'docker' (services/role_registry.py:372), a role
  GET /deployments/roles has always returned; NODE_ROLE_METADATA gains the
  matching entry it is documented to mirror.
- RoleInfo omitted ansible_role; NodeService omitted endpoint_path, port,
  protocol and is_discoverable; BlueGreenDeployment omitted
  health_failure_threshold, health_failures, monitoring_started_at and
  post_deploy_monitor_duration.
- temperature_celsius, memory_bytes and the blue-green timestamps are optional
  AND nullable; three helpers tested only for null.
- NPUCapabilities.deviceType is copied verbatim out of an external worker's
  /health payload (api/npu.py:83), so the four-value union was unverifiable;
  all three renderers already fall back to the raw string.
- BlueGreen shapes were declared twice (types/slm.ts and types/api-responses.ts)
  for the same wire models; now one derivation plus aliases.
…ame the monitoring view-models (#13138)

A blue-green deployment in the 'monitoring' status (services/blue_green.py:780,
held for up to post_deploy_monitor_duration seconds) matched none of the four
bgStats buckets and fell through getStatusClass to the neutral gray default, so
it vanished from the dashboard entirely. Adds it to the in-flight set and the
in-flight badge, backed by 4 tests that fail against the pre-fix view.

Also drops the 'as BlueGreenDeployment[]' cast that bridged the two independent
hand-written copies of the same wire model, now that both derive from one
schema.

View-models that shadow a generated schema while deliberately remapping it are
renamed rather than derived, so the collision cannot come back:
LogEntry -> LogRow (LogViewer.vue, maps severity->level),
LogEntry -> DeploymentLogMessage (DeploymentLogViewer.vue, a WebSocket
envelope), SystemMetrics -> SystemMetricsViewModel and
DashboardOverview -> DashboardViewModel (usePrometheusMetrics.ts, synthesised
from fleet_metrics), RoleInfo -> WizardRoleOption (SetupWizardView.vue, builds
a display_name the contract does not have).

DeploymentWizard.vue drops its own RoleInfo copy for a projection with derived
member types, and normalises the optional 'dependencies' its template indexes.
…collapsing the duplicate PlaybookInfo (#13138)

PlaybookInfo was declared identically in InfrastructureWizard.vue and
InfrastructureView.vue; both now import one derived definition in types/slm.ts.
Both copies omitted 'tags' and typed 'category' as a bare string where the
contract has the PlaybookCategory enum. PlaybookExecution gains the
PlaybookStatus enum and the correct optionality of 'output'. TimeConfig
(GeneralSettings.vue) derives from the /settings/time model.
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