feat(assets): CI impact-graph traversal endpoint (PMS-475) #347
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
psa-systems/mokosh-server!347
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/PMS-475-ci-impact-traversal"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
PMS-456 phase 1 shipped the ITIL CI classification columns (
asset_types.itil_category+assets.itil_lifecycle_stage) but the existingasset_relationshipstable sat unused beyond the manual list view. An operator asking "if I retire this database, what services break?" had to walk the table by hand. PMS-475 closes that with a server-side recursive traversal so the SPA's forthcoming CI Map tab has something to render.What landed (server-side; the SPA force-directed graph is a separate ticket):
New endpoint
GET /api/v1/assets/{id}/impact?direction=upstream|downstream|both&depth=N. The handler acceptsdirection(defaultboth) anddepth(default 10 = the hard server ceiling). The service clamps the effective walk depth tomin(query_depth, tenant_setting, 10)so a caller cannot blow the query plan even when the tenant raised the cap to 99 by editing the row directly.compute_impact_graphinassets::serviceruns two single-directionWITH RECURSIVECTEs and unions the results in Rust whendirection=both. Each CTE seeds from rows where the root sits on the relevant side of the edge, then recurses by joining the seed's other side to the next layer's matching side. Thed.depth < $3predicate bounds the walk; cycles terminate at the cap rather than looping forever (visited-set memoisation rejected as overkill for the modest CMDB sizes this surface targets).Migration
074_ci_impact_max_depth.sqlseeds the well-known settings key(category='ci', key='impact_max_depth')for every existing tenant with the default value of 5. The validator insettings::modelsaccepts integers in 1..=10. The reader (read_ci_impact_max_depth) defaults to 5 when the row is absent (new tenants provisioned post-migration) and clamps to 1..=10 defensively so a row predating the validator cannot break the ceiling.DTOs in
assets::models:AssetImpactNodeflattens edges from both halves into a uniform shape withdirectiondiscriminator +parent_asset_id+child_asset_id+relationship_type+depth, so the SPA renders nodes with one card template.AssetImpactResponsecarriesroot_asset_id+ the effectivedepthafter clamping (the SPA can render "truncated at depth N" without re-doing the clamp math) + the requesteddirection.Both CTEs join
assetsso the result rows include the friendlynamefor each neighbour. Tenant-scoped throughout: the CTE's seed-row and the join both predicate ontenant_id = $1, so a cross-tenant edge in the table (none exist, but defence in depth) cannot leak a name across the boundary.Integration tests at
tests/asset_ci_impact.rs(4 cases, all run live):ci/impact_max_depthset to 2 truncates the chain at depth 2 (D not in the result), and the response'sdepthfield reflects the effective cap.direction=bothreturns nodes stamped with their respectiveupstream/downstreamdiscriminator.Phase-3 follow-ups deferred to their own tickets:
#PMS-475
cab31d50155cc093e5bd