Skip to content

refactor(slm-frontend): derive the security, credential and auth-token contracts from the generated schema (#13138) - #13146

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

mrveiss merged 2 commits into
Dev_new_guifrom
issue-13138

Conversation

@mrveiss

@mrveiss mrveiss commented Jul 31, 2026 •

Copy link
Copy Markdown
Owner

Closes #13147 — the discrete, fully-delivered security/auth slice of #13138.

#13138 itself stays OPEN: ~77 shapes remain, enumerated below, and the credential write paths are blocked on #13145. Partial delivery never closes the parent.

Thinking Path

The count in the issue is wrong, and so was my first attempt at it. The issue says 114 collisions remain. Reproducing the scan on pristine Dev_new_gui with a name-level regex reproduced ~109 — but both figures are inflated. A pattern like ^\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 the true figure:

pristine Dev_new_gui (5a4c3a4)
generated schemas 309
hand-declared type/interface names 414
exact-name collisions 129
— already derived by #13137 35
— still hand-written 94

35, not 39, because four of #13137's shapes have no response_model and stayed hand-declared by design.

Not every collision is drift — three are worse than drift. usePrometheusMetrics.ts declares DashboardOverview and SystemMetrics, and two components declare LogEntry, whose names match a wire schema but whose contents are deliberate client-side view-models built by remapping that same endpoint's response — usePrometheusMetrics.ts:224-241 maps /monitoring/dashboard (whose response model really is DashboardOverview, autobot-slm-backend/api/monitoring.py:704) into a different shape, and LogViewer.vue:113-127 maps severity→level. Deriving those would be actively wrong; they need renaming. They are called out here so the remaining batches do not derive them blindly.

Prioritised by risk, not alphabetically. The groups, highest first:

  1. Credential write paths — NodeCreate, NodeUpdate, ConnectionTestRequest. Genuine disagreement, and it turned out to be a live defect. Filed as 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, deferred here (below).
  2. Auth token + security read models — TokenResponse, the eight /security/* shapes, VNC/TLS credential shapes. Delivered here.
  3. Shape-changing but non-security — envelope-vs-bare and enum widening across useCodeSync, useOrchestration, useRoles. Deferred.
  4. Monitoring/observability read models in types/api-responses.ts — the long tail. Deferred.

What Changed

18 shapes derived from components['schemas'][…] (94 → 77 by the same scan; SecurityView.vue:43's local re-alias of the now-derived ThreatSummary still counts against the total but satisfies the issue's "collapse to one exported alias").

File Types
types/slm.ts SecurityEventResponse, SecurityEventListResponse, SecurityOverviewResponse, SecurityPolicyResponse, SecurityPolicyListResponse, AuditLogResponse, AuditLogListResponse, ThreatSummary
types/api-responses.ts VNCCredentialCreate, VNCCredentialResponse, VNCEndpointResponse, VNCEndpointsResponse, VNCConnectionInfo, TLSCredentialCreate, TLSCredentialResponse, TLSEndpointResponse, TLSEndpointsResponse
stores/auth.ts TokenResponse, MfaChallengeResponse

Each alias carries the file:line of the backend endpoint whose response_model it is, so the mapping is checkable rather than assumed.

Double declarations collapsed. All eight /security/* shapes were hand-declared identically in both types/slm.ts:706-821 and types/api-responses.ts:538-653 — one backend change had two places to drift from, and only the types/slm.ts copy had any importer. types/slm.ts now owns the derivation and types/api-responses.ts re-exports it, so both import paths keep working from one definition.

Drift the derivation exposed and corrected:

  • TokenResponse omitted token. The SLM mirrors the JWT under both access_token and token so a client written against the core backend reads it (autobot-slm-backend/models/schemas.py:31-48); the hand-written copy documented only half the contract.
  • TLSCredentialResponse omitted ca_cert and server_cert — public certificate data the endpoint returns and the UI could never reach (autobot-slm-backend/models/schemas.py:1352-1368).
  • VNCCredentialCreate and TLSCredentialCreate both omitted extra_data.
  • SecurityOverviewResponse.recent_events was declared non-null but is default_factory=list server-side (autobot-slm-backend/models/schemas.py:1601), so the contract makes it optional. SecurityView.vue:643 read .length off it unguarded — a crash on any response that omits it. Now ?? [] / optional-chained.
  • TLSEndpointResponse.days_until_expiry is nullable and optional; formatExpiryStatus accepted only number | null and tested === null, so an absent value fell through to the "expired" branch. Now == null.
  • The same "declared non-null, actually optional" tail across ~30 timestamp/count fields in the security models — resolved by derivation, no consumer change needed.

/api/auth/login is a union, and was modelled as a flattened blob. Its response_model is TokenResponse | MfaChallengeResponse (autobot-slm-backend/api/auth.py:81), but stores/auth.ts declared one all-optional MFALoginResponse merging both. That made access_token optional on the success branch and forced two ! assertions on the live login path (stores/auth.ts:99-100). Now the real union plus an isMfaChallenge type guard; the assertions are gone. Both contract members carry an additionalProperties catch-all, so the guard checks the discriminator structurally rather than with in.

Inline envelopes replaced by their schemas. getNodeVncCredentials / getNodeTlsCredentials re-typed their { credentials, total } return inline; both are real schemas (VNCCredentialListResponse, TLSCredentialListResponse) — the exact shape of the #13137 scope-picker defect, avoided here.

Deferred, with reasons

Verification

Run in autobot-slm-frontend/.

Before — pristine origin/Dev_new_gui src/ swapped in with cp from git archive (never git stash):

npm run type-check  -> clean
npm run test:unit   -> Test Files 26 passed (26) | Tests 207 passed (207)
npm run lint        -> 16 problems (0 errors, 16 warnings)

After:

npm run type-check  -> clean
npm run test:unit   -> Test Files 26 passed (26) | Tests 211 passed (211)
npm run lint        -> 16 problems (0 errors, 16 warnings)

+4 tests: a new describe in stores/auth.test.ts pinning the login-union discrimination — token branch authenticates, challenge branch stores no token and makes no follow-up /api/auth/me call, access_token wins over the mirrored token, and requires_mfa: false is not mistaken for a challenge.

Lint findings were diffed line-for-line between the cp-swapped baseline tree and the final tree — identical 18-line finding set, so no pre-existing warning was masked and none was added.

verify-generated-types-slm still holds: git diff --exit-code src/types/generated/api.ts is clean. The generated file was read but never edited, and no regeneration was needed because no backend schema changed.

Branch rebased onto 163933b75 after origin/Dev_new_gui moved mid-work; type-check re-run green on the rebased tip.

Per the #13090 execution constraint no AutoBot application code was run — no backend, no SLM backend, no schema dump, no ad-hoc Postgres/Redis. Every contract claim above was read from the committed generated artefact and cross-checked against backend source at the cited file:line.

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!

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