Skip to content

tech-debt(slm-frontend): 114 hand-declared types still shadow a generated OpenAPI schema (from #12420) #13138

Description

@mrveiss

114 hand-declared SLM frontend types still shadow a generated OpenAPI schema

#13075 stood up the SLM frontend's openapi-typescript pipeline and #13137 (from #12420) wired 39 of the generated schemas into the seven composables already routed through the canonical slmApiClient. This issue tracks the remainder.

Measurement

Reproduce with a name scan: collect every export interface X / export type X under autobot-slm-frontend/src (excluding src/types/generated/), collect every key of the schemas block in src/types/generated/api.ts, and intersect.

At the time of filing (post-#13137):

generated schemas:                       309
hand-declared type/interface names:      411
exact-name collisions with a schema:     120   <- 6 handled by #13137, 114 remain

A name collision is a strong drift signal, not proof — but every pair checked so far had real drift (see below).

Where they cluster

File Colliding names (sample)
autobot-slm-frontend/src/types/api-responses.ts AlertItem:259, AlertsResponse:270, AppLogEntry:314, AppLogsResponse:320, DashboardOverview:286, ErrorStatistics:406, MetricsSummary:469, TLSCredentialResponse:135, TLSEndpointsResponse:160, VNCCredentialResponse:76, VNCEndpointsResponse:105, TimelineResponse:486, TopErrorsResponse:501 (~45 names)
autobot-slm-frontend/src/types/slm.ts NodeCreate:129, NodeUpdate:145, RoleInfo:223, RoleListResponse:237, ServiceListResponse:448, SecurityEventResponse:705, SecurityPolicyResponse:792, AuditLogResponse:743, NPUWorkerConfig:641, MaintenanceWindowCreate:369 (~30 names)
autobot-slm-frontend/src/composables/useCodeSync.ts PendingNodesResponse:55, FleetSyncResponse:68, FleetSyncJobStatus:85, DriftedFile:165, FileDriftReport:171, DriftResolveResponse:182, UpdateAllStage:214, UpdateAllJob:225
autobot-slm-frontend/src/composables/useRoles.ts PortInfo:40, PostSyncAction:85, NodeActionsResponse:93, DecommissionRoleInfo:108
autobot-slm-frontend/src/composables/useOrchestration.ts ServiceActionRequest:38, ServiceMigrateRequest:43, FleetStatusResponse:64, BulkActionRequest:71, BulkActionResponse:84
autobot-slm-frontend/src/composables/usePrometheusMetrics.ts SystemMetrics:25, DashboardOverview:96

Several names are declared in three places at once (RoleInfo in types/slm.ts:223, DeploymentWizard.vue:11 and SetupWizardView.vue:593; LogEntry in types/api-responses.ts:294, LogViewer.vue:22 and DeploymentLogViewer.vue:20), so one backend change has three independent places to drift from.

Why it matters — drift already found

Every hand/generated pair inspected during #13137 had at least one mismatch, and two were user-visible defects (both fixed there):

  • the API-key scope picker rendered a single bogus entry because the response envelope was not modelled;
  • SLM MFA login returned 422 on every attempt because a query parameter was sent in the body.

Lower-severity drift found in the same pass and corrected by derivation: SecretResponse was missing warning; SSOProviderHealthResponse was missing secret_staleness; SSOProviderUpdate was missing is_social and org_id, so the UI could set fields on create that it could never edit; plus a long tail of "declared non-null, actually optional" timestamps.

Definition of done

  1. Each remaining collision is either derived from components['schemas'][...] or carries a comment stating why it cannot be (endpoint has no response_model).
  2. Triple declarations collapse to one exported alias; the .vue-local copies import it.
  3. npm run type-check green, SLM vitest suite green, and any drift the derivation exposes is fixed rather than cast away.
  4. No edit to src/types/generated/api.ts — verify-generated-types-slm diffs it.

Suggested batching

useCodeSync.ts -> useRoles.ts / useOrchestration.ts -> types/slm.ts -> types/api-responses.ts (largest, mostly monitoring/observability read models).

Parent: #12420 (umbrella #12645). Do not merge the SLM client with the main frontend's — ADR-008 keeps one client per (app, backend) pair.

Activity

  1. mrveiss commented on Jul 31, 2026

    @mrveiss
    OwnerAuthor

    Measurement correction: 94 remain, not 114

    The scan described in the issue over-counts. ^\s*(?:export\s+)?(interface|type)\s+(\w+) also matches the members of a type-only import:

    import {
      useMfaApi,
      type MFASetupResponse,   // <- counted as a declaration
    } from '@/composables/useMfaApi'

    Requiring a real declaration body (interface X … { or type X … =) gives, on pristine Dev_new_gui (5a4c3a4ae):

    generated schemas:                   309
    hand-declared type/interface names:  414
    exact-name collisions:               129
      already derived by #13137:          35   (39 minus the 4 with no response_model)
      still hand-written:                 94
    

    Most of the entries the issue lists as third declarations — MFASetupResponse in SecuritySettings.vue, SSOProviderResponse in SSOSettings.vue, SecretCreate in SecretsSettings.vue, etc. — are already import type of the derived composable alias, i.e. already correct. The genuine multi-declaration cases are RoleInfo (3), LogEntry (3), PlaybookInfo (2), ThreatSummary (3) and the eight /security/* shapes (2 each).

    Not every collision is drift — 4 need renaming, not derivation

    Name Declared at Why deriving it would be wrong
    DashboardOverview composables/usePrometheusMetrics.ts:97 A client-side view-model built by remapping /monitoring/dashboard (usePrometheusMetrics.ts:224-241). The endpoint's real response model is DashboardOverview (autobot-slm-backend/api/monitoring.py:704) and is separately declared at types/api-responses.ts:287.
    SystemMetrics composables/usePrometheusMetrics.ts:26 Same — synthesised from fleet_metrics. The schema belongs to /health/metrics (autobot-slm-backend/api/health.py:59).
    LogEntry views/monitoring/LogViewer.vue:23 View-model; LogViewer.vue:113-127 explicitly maps severity→level.
    LogEntry components/DeploymentLogViewer.vue:21 A WebSocket message envelope (type/log_type, timestamp: Date), not an HTTP response at all.

    These four should be renamed so the collision disappears. Deriving them would replace a working local shape with an unrelated wire shape.

    Genuine disagreement found: the credential write paths (filed as #13145)

    NodeUpdate, NodeCreate and ConnectionTestRequest declare fields the contract does not have. Confirmed against backend source: the surplus fields are silently dropped by Pydantic's default extra="ignore", the request 200s, and the UI reports success. Worst case: the Edit-Node modal's "Deploy PKI certificates" and "Re-run enrollment tasks" checkboxes are no-ops, and validation forces an SSH password to enable them.

    Which side is wrong is a product decision, so #13145 records it with file:line evidence rather than changing the server contract. These three are blocked on #13145 and cannot be derived until it is resolved — deriving them turns the surplus fields into compile errors whose only silent fix is deleting UI controls.

    Delivered: PR #13146 — 18 shapes, security/auth surface (94 -> 77)

    TokenResponse + MfaChallengeResponse (stores/auth.ts), the eight /security/* shapes (collapsed from two identical declarations to one derivation), and the nine VNC/TLS credential shapes. Drift corrected along the way: TokenResponse missing token, TLSCredentialResponse missing ca_cert/server_cert, both *CredentialCreate missing extra_data, an unguarded .length on the optional SecurityOverviewResponse.recent_events, and /api/auth/login modelled as a flattened blob instead of its declared union.

    Remaining 77, grouped by risk for whoever picks this up

    1. Blocked on bug(slm): Edit Node silently discards all SSH/PKI/enrollment fields — NodeUpdate accepts 5 of 14; ssh_key dropped on register and connection-test too #13145 — NodeCreate, NodeUpdate, ConnectionTestRequest.
    2. Rename, do not derive — DashboardOverview, SystemMetrics, LogEntry ×2 (table above).
    3. Shape-changing — useCodeSync.ts (10), useOrchestration.ts (5), useRoles.ts (4), useOrchestrationManagement.ts (2). Mostly literal-union status fields that the contract widens to string, plus FleetSyncJobStatus missing failure_reason and UpdateAllJob missing skipped_fleet_nodes. Note the widening direction: the hand-written unions are narrower than the contract, so plain derivation loses a real UI-side guarantee — check whether the backend actually constrains the value before widening.
    4. Long tail — types/api-responses.ts monitoring/observability read models (~30) and the remaining types/slm.ts shapes (~20: NPU, maintenance windows, services, external agents, roles).

    RoleInfo (3 declarations, types/slm.ts:223 + DeploymentWizard.vue:12 + SetupWizardView.vue:594) is the highest-value item left in group 4: all three disagree with each other and with the contract, and types/slm.ts:223 types name/category/dependencies as the local NodeRole/RoleCategory unions where the contract says string.

  2. mrveiss commented on Jul 31, 2026

    @mrveiss
    OwnerAuthor

    Closed by PR #13146, merged to Dev_new_gui as c0a45b77a. 18 shapes derived; the shadowed-type count drops 94 → 77. #13138 stays open for the remainder.

    The count in #13138 was wrong, and so is its measurement method

    94, not 114. The scan regex ^\s*(?:export\s+)?(interface|type)\s+(\w+) also matches members of a type-only import:

    import { useMfaApi, type MFASetupResponse } from '@/composables/useMfaApi'

    So most entries listed as "declared in three places" (MFASetupResponse in SecuritySettings.vue, SSOProviderResponse in SSOSettings.vue, SecretCreate in SecretsSettings.vue) were already correct — they are import type of the derived alias. Requiring a real declaration body on pristine Dev_new_gui: 309 schemas, 414 hand-declared names, 129 collisions, 35 already derived by #13137 → 94 remaining. The method needs fixing in #13138, or every future pass re-derives the same wrong number.

    Four entries must NOT be derived

    DashboardOverview and SystemMetrics (usePrometheusMetrics.ts:97,:26) and LogEntry (LogViewer.vue:23, DeploymentLogViewer.vue:21) are local view-models that deliberately remap the very endpoint whose schema shares their name — LogViewer.vue:113-127 maps severity→level, usePrometheusMetrics.ts:224-241 reshapes the response. Deriving them would be actively harmful; they need renaming instead. A blanket "derive everything" sweep would have broken all four.

    Frontend-side mismatches found and fixed

    • SecurityOverviewResponse.recent_events is default_factory=list (schemas.py:1601) → optional in the contract, but SecurityView.vue:643 read .length unguarded.
    • TLSEndpointResponse.days_until_expiry is optional and nullable; formatExpiryStatus tested only === null, so an absent value fell through to the "expired" branch.
    • /api/auth/login returns the union TokenResponse | MfaChallengeResponse (api/auth.py:81), modelled as one flattened all-optional blob — producing two ! assertions on the live login path. Now a real union, with 4 new tests.
    • TokenResponse was missing token; TLSCredentialResponse missing ca_cert/server_cert; both *CredentialCreate missing extra_data.

    A duplicate nothing used

    The eight /security/* shapes were declared identically twice — types/slm.ts:706-821 and types/api-responses.ts:538-653 — and the api-responses.ts copy had zero importers. Every consumer used the slm.ts copy. So had the two diverged, nothing would have surfaced it. Now one definition plus a re-export.

    Inline { credentials, total } returns replaced with the real VNCCredentialListResponse / TLSCredentialListResponse.

    Deferred with reasons

    Credential write paths (NodeCreate, NodeUpdate, ConnectionTestRequest) are blocked on #13145 — a genuine server-side disagreement where the server is wrong, and which side to change is a product decision. Envelope/enum-widening groups (useCodeSync ×10, useOrchestration ×5, useRoles ×4, useOrchestrationManagement ×2) and the ~50-type monitoring long tail remain in #13138.

    Verification

    type-check clean both sides; vitest 207 → 211 (+4 login-union tests); lint finding set byte-identical at 18 lines — all measured against a cp-swapped pristine src/ from git archive, never git stash. git diff --exit-code src/types/generated/api.ts clean, so verify-generated-types-slm holds. No AutoBot application code was run (#13090). Rebased onto 163933b75 mid-work after another session pushed #13144, then re-verified.

  3. mrveiss commented on Jul 31, 2026

    @mrveiss
    OwnerAuthor

    Slice 3 delivered: PR #13152, merged as 66279b115 — 23 shapes, the envelope / enum-widening group (76 → 53)

    Closes the discrete slice issue #13155. #13138 stays open with 53 remaining.

    The count, a third time — and the rule that settles it

    Measured on c0a45b77a (post-#13146): 76 still hand-written, not 77.

    generated schemas:      309
    hand-declared names:    414
    exact-name collisions:  131
      already fully derived: 55
      still hand-written:    76
    

    The one-unit gap against the previous comment is ThreatSummary, and it is a methodology gap rather than an arithmetic one. It is derived at types/slm.ts:727, but SecurityView.vue:43 re-declares it locally. Counting per name puts it in the hand-written bucket; counting per declaration puts it in the derived one.

    So the scan needs a third rule on top of the two already recorded (require a declaration body; exclude import { type X }):

    A name counts as remaining if any of its declarations lacks a components['schemas'][…] body — a body-requiring regex still matches type X = components['schemas']['X'], so derived aliases must be excluded explicitly or the collision count never falls.

    With all three rules the number is reproducible and moves monotonically. On the merged tip: 309 schemas, 419 hand-declared names (up 5 — the new non-colliding status unions), 131 collisions, 78 derived, 53 remaining.

    Defect found: a partial update-all reports no outcome at all

    The pattern held again — the disagreement was the finding, and the server was right.

    UpdateAllJob.status was declared 'pending' | 'running' | 'completed' | 'failed' | 'already_current'. Since #11511 the backend has a sixth terminal status:

    # autobot-slm-backend/api/code_sync.py:4940
    job.status = "partial" if skipped else "completed"

    set whenever the fleet stage skips a non-operational node. 'partial' matched neither banner — failure tests status === 'failed' (CodeSyncView.vue:913), success tests 'completed' | 'already_current' (:921). The pipeline stopped, the CTA re-armed, and the user got no outcome whatsoever. The backend's own explanation — "Updated 2/3 nodes (1 skipped — not operational)" (code_sync.py:4933-4935) — goes into stage.message, which the template never renders anywhere, so it had no route to the screen either.

    skipped_fleet_nodes (code_sync.py:4270) was absent from the frontend type entirely, so there was nothing to count even in a hand-written fix. Deriving the type restored the field and immediately broke both test fixtures that build an UpdateAllJob — they had been mocking the truncated shape, the same failure mode as #13137's scope picker. Confirmed the 4 new tests fail against the pre-fix view before fixing it.

    Second instance of the same class: UpdateAllStage.status omitted 'current' (_StageStatus.CURRENT, code_sync.py:4245) even though CodeSyncView.vue:127 and :145 already map it — the type contradicted the view's own rendering, and current was silently displayed as "done".

    The widening direction, resolved per type

    Every status here is str server-side with the value set living only in a trailing # comment. Blanket derivation would have discarded a real guarantee; blanket retention would have preserved two provably false claims. Resolved against the backend's assignment sites instead:

    Case Types Resolution
    Unconstrained but exhaustively enumerable PostSyncAction.category (4 construction sites), FleetSyncNodeStatus.status, FleetSyncJobStatus.status, ComponentSyncJobStatus.status derive the shape, keep the union, cite the sites
    Constrained in a way the schema cannot express ServiceCategoryUpdate.category (pattern="^(autobot|system)$") derive, keep the union — anything else is a guaranteed 422
    Frontend union simply wrong UpdateAllJob.status, UpdateAllStage.status the defect above

    Fields the frontend type could not reach

    FleetSyncJobStatus.failure_reason (schemas.py:1795, populated on every failure at code_sync.py:401-402); DriftResolveResponse.deps_changed / post_steps (schemas.py:1689-1690); PortInfo.address (schemas.py:106, GH#11224's bind interface). Also PendingNodeResponse.current_version is optional and nullable, but formatVersion (CodeSyncView.vue:248) narrowed its parameter to string | null — narrower than the getCommitHashDisplay it delegates to.

    Two traps for whoever takes the long tail

    1. The generated types are intersected with an additionalProperties index signature, so keyof T is string | number and Omit/Pick silently collapse every named member into the index signature — Omit<ServiceActionRequest, 'force'>.node_id types as unknown, with no error at the definition site. Partial is homomorphic and preserves them; plain intersection narrows a widened string correctly.
    2. openapi-typescript emits a field with a server-side default as required — right for a response, backwards for a request body. ServiceActionRequest.force and BulkActionRequest.exclude are optional to send.

    Also: FleetStatusResponse.services and BulkActionResponse.results are bare dict server-side (api/orchestration.py:79, :97), so the contract can only say { [key: string]: unknown } while every consumer reads .status/.host/.port. Derived for the scalar fields and intersected to pin the element shape — the long tail in types/api-responses.ts will hit this shape repeatedly.

    No new view-models in this group

    Re-checked all 23 against the #13146 "rename, do not derive" list. Every one is a straight wire model; none remaps its endpoint. The four known view-models (DashboardOverview, SystemMetrics, LogEntry ×2) are untouched and still need renaming.

    Remaining 53

    1. Blocked on bug(slm): Edit Node silently discards all SSH/PKI/enrollment fields — NodeUpdate accepts 5 of 14; ssh_key dropped on register and connection-test too #13145 — NodeCreate, NodeUpdate, ConnectionTestRequest. Unchanged.
    2. Rename, do not derive — DashboardOverview, SystemMetrics, LogEntry ×2.
    3. Long tail — types/api-responses.ts monitoring/observability read models (~30, expect the bare-dict narrowing question repeatedly) and the rest of types/slm.ts (~20: NPU, maintenance windows, services, external agents). RoleInfo (3 declarations) is still the highest-value item.

    Recorded, not acted on

    stage.message is never rendered by the update-all template even though the backend writes a useful per-stage summary into it on every stage, and getJobStatus / getRecentJobs are exported from useCodeSync with no consumer outside tests — so failure_reason has no route to the screen regardless of the type. Both left alone to keep the diff to contract derivation; say the word and I will file them.

    Note for future slices: the SLM admin app bootstraps a single en locale (src/i18n/index.ts:13; src/locales/ contains only en.json) — it is not the 11-locale main frontend.

  4. mrveiss commented on Jul 31, 2026

    @mrveiss
    OwnerAuthor

    Slice 4 open: PR #13159 — 50 shapes, the monitoring long tail (53 → 4)

    #13138 stays open with 4 remaining, 3 of which are blocked on #13145.

    The count, a fourth time — the three rules hold

    Measured on 66279b115 (post-#13152) with the rules recorded in the previous comments (require a declaration body · exclude import { type X } · a name counts as remaining if any of its declarations lacks a components['schemas'][…] body):

    generated schemas:      309
    hand-declared names:    422
    exact-name collisions:  132
      already derived:       78
      still hand-written:    54
    

    54, one above slice 3's 53. The unit is ThreatSummary: SecurityView.vue:43 is type ThreatSummary = ThreatSummaryType, an alias of the derived import, which the strict per-name rule counts as remaining while it is correct in substance. So the rules reproduce and the residual gap is a known artifact, not arithmetic. On the branch tip: 309 schemas, 428 names, 132 collisions, 128 derived, 4 remaining.

    The pattern held: four defects, the server right every time

    1. A blue-green deployment in post-deploy monitoring reported nothing at all.

    BlueGreenStatus (types/slm.ts) was pending | borrowing | deploying | verifying | switching | active | rolling_back | rolled_back | completed | failed. The backend has an eleventh state:

    # autobot-slm-backend/services/blue_green.py:780
    deployment.status = BlueGreenStatus.MONITORING.value
    deployment.monitoring_started_at = datetime.now(timezone.utc)

    set after the traffic switch whenever post_deploy_monitor_duration > 0 and auto_rollback (:766), and held for that many seconds — 1800 by default. 'monitoring' matched none of the four bgStats buckets (DeploymentsView.vue:202-205) and fell through getStatusClass to the neutral gray default, so for up to half an hour a deployment under active health-watch was absent from the Total-adjacent Active/Completed/Failed/Rolled-back tiles and its badge asserted that nothing was happening.

    Worse, the fields the health-watch actually reports — health_failures (written every poll, blue_green.py:1055), health_failure_threshold, monitoring_started_at, post_deploy_monitor_duration — were absent from the frontend type entirely, so there was nothing to render even in a hand-written fix. Deriving BlueGreenResponse restored all four. Confirmed the 4 new tests fail against the pre-fix view (expected '0' to be '1'; badge class absent).

    2. NodeRole was missing 'docker'. constants/node-roles.ts states, in its own header, "Source of truth: autobot-slm-backend/services/role_registry.py:DEFAULT_ROLES" and "Keep in sync". The registry carries 21 roles; the union carried 20. docker (_INFRA_ROLES, role_registry.py:372) has always been returned by GET /deployments/roles and has always rendered with no label and no description. Correcting the union made Record<NodeRole, RoleMetadata> demand the missing metadata entry — the mirror now type-checks itself.

    3. PlaybookInfo was declared identically twice — InfrastructureWizard.vue:19 and InfrastructureView.vue:20. Same class as the /security/* duplicate #13146 found. Both omitted tags, and typed category as a bare string where the contract has the PlaybookCategory enum.

    4. The blue-green shapes were declared twice as well, in types/slm.ts and types/api-responses.ts, and DeploymentsView.vue:335 bridged the two with response.deployments as BlueGreenDeployment[] — a cast that would have swallowed any divergence between them. One derivation plus aliases now; the cast is gone.

    Two more view-models — and one that looked like one but was not

    The four already on the "rename, do not derive" list (DashboardOverview, SystemMetrics, LogEntry ×2) are joined by a fifth found in this group:

    Name Declared at Why deriving it would be wrong
    RoleInfo views/SetupWizardView.vue:594 Declares a display_name the contract does not have. loadRoles (:818-832) builds it from description, because the backend already folds the registry's display_name into description (api/deployments.py:157-160).

    All five are now renamed — LogRow, DeploymentLogMessage, SystemMetricsViewModel, DashboardViewModel, WizardRoleOption — each carrying a comment naming the schema it used to collide with, so the collision cannot silently return.

    RoleInfo at DeploymentWizard.vue:12 looked like a sixth but is a straight projection, not a remap. It keeps a local four-field type because the offline NODE_ROLE_METADATA fallback cannot supply ansible_role or required — a full RoleInfo there would be a lie — but the member types are now derived by indexed access, so a contract rename still breaks the build. Its dependencies is normalised at the fetch site: the field is default_factory=list and the template reads .length unguarded.

    Widening, resolved per type

    Case Types Resolution
    Constrained in a way the schema cannot express RestartAllServicesRequest.category (pattern="^(autobot|system|all)$", schemas.py:900), NodeService.category derive, keep the union
    Unconstrained but exhaustively enumerable ErrorStatistics.trend (3 returns, errors.py:245-273), ErrorHealthResponse.status (3 branches, :509-518), BlueGreenStatus, NPULoadBalancingConfig.strategy 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), and all three renderers already fall back to the raw string widened
    Frontend claim describes a field never populated RoleInfo.dependencies — deployments.py:166 passes [] unconditionally widened

    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 at all) · PlaybookInfo.tags · plus the long tail of optional-and-nullable timestamps and gauges declared as required, of which two had live consequences: temperature_celsius tested only === null so an absent reading rendered undefined°C in the "cool" colour, and NPUWorkerConfig.assigned_models is default_factory=list yet toggleModel indexed it unguarded.

    A third trap for whoever takes the remainder

    The two recorded in #13152 both held. A new one, and it is the quiet kind:

    A[] & B[] keeps both map signatures and TypeScript resolves the callback against the first, so base & { items: Narrow[] } types the callback parameter as the wide element, with no error at the definition site. That is exactly how useNodeServices.ts:52 ended up mapping category as string. Writing { items: Narrow[] } & base picks the narrowed element.

    Every list envelope in this slice uses that order, and the rule is recorded on RoleListResponse in types/slm.ts.

    Remaining 4

    1. Blocked on bug(slm): Edit Node silently discards all SSH/PKI/enrollment fields — NodeUpdate accepts 5 of 14; ssh_key dropped on register and connection-test too #13145 — NodeCreate, NodeUpdate, ConnectionTestRequest. Unchanged: a genuine server-side disagreement whose resolution is a product decision.
    2. ThreatSummary (SecurityView.vue:43) — an alias of the derived import, inside a block of five such aliases. Correct in substance; renaming it churns the file to satisfy a counting rule.

    That is the end of the derivable surface. Once #13145 lands, #13138 closes.

    Verification

    type-check clean; vitest 215 → 219 (+4 blue-green tests, confirmed failing against the pre-fix view); lint finding set identical at 16 problems / 0 errors — all measured against a cp-swapped pristine src/ from git archive, never git stash. git diff --exit-code src/types/generated/api.ts clean, so verify-generated-types-slm holds. Rebased onto a1fc379d7 mid-work after another session pushed #13153, then re-verified. No AutoBot application code was run (#13090).

  5. modified the milestones: Backlog, v0.12.0 on Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions