risk-graph: stacked layout, 5-col Sankey + client-side what-if#5
Open
velvetway wants to merge 30 commits into
Open
risk-graph: stacked layout, 5-col Sankey + client-side what-if#5velvetway wants to merge 30 commits into
velvetway wants to merge 30 commits into
Conversation
Stacked layout (hero / threat list / graph), 5-column Sankey with C(СЗИ), client-side what-if simulation mirroring backend QReactionFromVLs, plus new bulk endpoint /api/risk/asset/:id/attack-paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 tasks across 4 phases (backend types/aggregate/service/handler, frontend types/riskFlow lib, 6 components, page rewrite + cleanup) with TDD-style steps and per-task commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Types for the new bulk attack-paths endpoint that returns all threats of a single asset in one request. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure function that summarizes a slice of AttackPath into WMax, Level, ThreatCount and UncoveredCount for the asset-level hero block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hardens the ComputeAssetAggregate contract before it is exercised by the new bulk service method on real DB data: a non-empty input where every path has all VLs covered must return UncoveredCount=0, and a path with nil VulnerableLinks must not increment counts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Returns all relevant attack paths plus an aggregate (WMax, Level, counts) for a single asset in one call. Skips paths with no sources, vulnerable links and destructive actions to keep the response noise-free. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bulk endpoint returning all attack paths and aggregate metrics for an asset in one request, plus handler unit tests using a stubbed Service. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Frontend types matching the new backend bulk endpoint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure functions for the new RiskGraphPage:
- buildSankeyGraph: S→ST→VL→{C|DA} graph with normalized flow
- recomputeW: client-side what-if mirroring backend QReactionFromVLs
10 unit tests cover flow conservation, coverage clamping, disabled
controls, severity-weighted splits and edge cases.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The severity || 1 fallback only caught zero; negative values produced negative flow values that d3-sankey rejects. Replaced with a Math.max(1, ...) clamp and added a regression test that verifies all link values are non-negative for paths containing a negative-severity VL. Also added the missing delta assertion to the empty-VLs recomputeW test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top hero card showing asset id, name, aggregate W, level badge, threat count and uncovered count, with PDF / Параметры / Back action buttons. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sortable list of all threats for an asset with W bars, uncovered count and selection highlighting. Sortable by W (default), uncovered count or name. Keyboard-navigable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extracted formula breakdown panel with optional what-if simulation overrides (effective q_reaction and W from client-side recompute). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d3-sankey layout with custom React-rendered nodes for the 5-column S→ST→VL→C→DA graph. Hover highlights, control nodes are clickable (opens ControlPopover via onControlClick), disabled controls are visually dimmed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modal popover for a single control: shows id, name, coverage value, and a toggle button to disable/re-enable in client-side simulation. Click-outside and Escape close the popover. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sticky bottom banner that surfaces an active what-if simulation: chips for each disabled control (click to re-enable), baseline → simulated W with ΔW, plus Сбросить and Сохранить заметку actions. Hidden when no controls are disabled. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New layout: AssetRiskHero / ThreatList / (AttackFlowSankey + PtsziBreakdown). Auto-selects max-W threat when ?threat= absent, mirrors selection to the URL via replaceState, and keeps a what-if simulation in client state (disabledControls Set) — recompute mirrors backend QReactionFromVLs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaced by AttackFlowSankey on the rewritten RiskGraphPage; the legacy component was no longer referenced by any route or page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
react-scripts build emits TS2802 because the project's tsconfig has neither downlevelIteration nor an ES2015+ target. Map.forEach is supported on the existing target without flags and yields the same behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move invalid-?threat= toast out of useMemo into a dedicated effect (memos should not produce side effects). - Drop `params` from the URL-sync effect deps to avoid redundant runs. - Wire onPdf in AssetRiskHero to POST /api/risk/report/pdf and trigger a blob download; restores the missing "PDF сценария" hero action. - Guard AttackFlowSankey against zero-VL paths (d3-sankey crashes on graphs with no edges from VL); render a friendly empty state instead. - ControlPopover: auto-focus the toggle on open, restore focus to the invoking element on close (keyboard-only users could not escape it). - Drop unused nodeIndex map in AttackFlowSankey. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reflect what was actually shipped in V1 — the service method calls AssembleAttackPath in a loop, producing N+1 (~40–120 queries for typical assets). True bulk repo method (LoadAssetAttackPaths) and the no-N+1 contract test are deferred to V2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eslint array-callback-return wants every branch of a sort comparator to return a value. The compile-time exhaustive switch already covers all SortKey values; the default is dead but silences the linter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This resolves the react-scripts build error where sub-dependency @types/d3-dispatch (v3.0.7) required TypeScript 5.0+ syntax. Upgraded to 5.1.6 to stay within @typescript-eslint supported limits.
…tly in tests Remove non-existent `type`/`location` columns from assets seed, widen fstec/fsb_protection_class to VARCHAR(64), and reset id sequences after seeding. Drop the schema/seed split in testhelper — all migrations now run in numeric order and fail the test on error instead of being silently logged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire `useAuth().logout` to a new IconBtn in the TopBar so users can sign out from any page. Remove the old App.test.tsx scaffold which no longer matches the current routing and was failing to compile. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pin the model to the source spec in /Расчет идеи/: full S/ST/VL/DA
reference tables, methods of противодействия, the W formula with
Z ∈ {0.5, 1.0}, and an explicit list of asset fields actually used by W.
Drops every mention of the legacy 1-25 / impact×likelihood scale —
this is now the single source of truth for the risk engine and an
anchor for the upcoming legacy purge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Delete the parallel legacy risk stack: - internal/service/risk/calculator.go (Impact × Likelihood × RegulatoryFactor) - internal/service/risk/recommendations.go (rule-based regulatory advice) - internal/service/asset/cia_calculator.go (auto-derived C/I/A & criticality) - internal/report/pdf.go (legacy PDF report) plus their tests. risk.Service now exposes only PTSZI methods: AssembleAttackPath, AssembleAssetAttackPaths, Overview, ListThreatSources, ListDestructiveActions. OverviewPoint loses its 1-25 back-compat fields (impact/likelihood/score) and returns only W and its decomposition. ZFromAsset is collapsed to the strict thesis form: 0.5 if isolated, else 1.0 (no more prod/stage 0.75 hop). Tests updated accordingly. HTTP layer: drop POST /api/risk/preview, GET /api/risk/asset/:id, and POST /api/risk/report/pdf. Asset request/response no longer carry business_criticality, C/I/A, kii_category, data_category, protection_level, has_personal_data, personal_data_volume, has_internet_access, type, location — the PTSZI formula does not use them. The DB columns still exist; Stage 2 will migrate them away. Asset.Create writes neutral defaults (3) for the remaining NOT NULL CHECK columns until the migration lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add migration 020_drop_legacy_risk_fields:
- drop tables risk_scenarios, recommendation_templates,
risk_scenario_recommendations (the old impact×likelihood engine)
- drop columns from assets: type, location, business_criticality,
confidentiality, integrity, availability, data_category, protection_level,
kii_category, has_personal_data, personal_data_volume, has_internet_access
- drop columns from threats: base_likelihood, attack_vector,
impact_confidentiality, impact_integrity, impact_availability
- drop columns from controls: reduces_likelihood_by, reduces_impact_by
- drop column asset_controls.effectiveness
- drop unused enum types data_category_type, protection_level,
kii_category, risk_status, risk_level, recommendation_status
Older migrations (002, 005, 010) still reference these columns; they run
strictly before 020 so the chronological chain replays cleanly on a fresh DB.
Mirror the schema in domain/models.go and the asset/threat repositories,
services, and HTTP DTOs. Threat input now takes q_threat / q_severity (∈[0,1])
directly — base_likelihood is gone from the API.
Repo tests updated to the new struct shapes; full repo suite passes against
the migrated container.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ormula Delete the screens that were shaped by the old impact×likelihood engine: - RiskMapPage (5×5 heatmap) + .backup - RiskPreviewPage (что-если симулятор) + .backup - AssetRiskProfilePage - RiskHeatmapSingle component Drop the corresponding routes (/risk/map, /risk/preview, /assets/:id/risks), nav entries, command-palette actions, and the old Asset/Threat/Risk* type definitions. types.ts and the API client now mirror the W-only backend exactly — no impact, likelihood, score, regulatory_factor, RiskRecommendation, RegulatoryRecommendation, or преview endpoint. AssetFormPage rebuilt to the four PTSZI-relevant inputs: name, description, asset_type_id, owner, environment, is_isolated. КИИ / ПДн / УЗ-1..4 / data_category sections are gone — the form now states explicitly that only `is_isolated` (Z) and the asset's deployed controls (Q^reaction) feed the formula. AssetsPage and DashboardPage rewired to render W max per asset (0..1) and «Изолированный (Z=0.5)» / «Открытый (Z=1.0)» chips instead of the old 1..25 score and КИИ/ПДн tags. The dashboard quick-action now opens the Risk Graph for the first asset rather than the dead simulator route. `npm run build` is clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
internal/report/pdf.go is back, this time PTSZI-only:
- GenerateAttackPathPDF(*AttackPath) renders one (asset, threat) page
with the W decomposition (Q^threat / q^threat / Q^reaction / Z),
the S → ST → VL → DA chain, per-VL coverage status and a
«what raises Q^reaction» recommendation block.
- GenerateAssetReportPDF(*AssetAttackPathsResponse) prints the
asset-level aggregate plus one section per applicable threat.
Both APIs use the embedded NotoSans font (full Cyrillic), no disk
fallback needed.
Two new GET endpoints expose the PDFs:
- GET /api/risk/report/graph/:asset_id/:threat_id
- GET /api/risk/report/asset/:asset_id
Smoke tests exercise both generators and assert the output starts with
the %PDF- magic and is non-trivially sized.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
RiskGraphPageпод stacked layout: hero (W-вердикт) → sortable список угроз актива → 5-колонок SankeyS→ST→VL→C→DA+ панель формулы ПТСЗИ.recomputeWзеркалит бэкендныйQReactionFromVLs, sticky-бар показываетΔWи позволяет сохранить заметку вlocalStorage.GET /api/risk/asset/:id/attack-paths— возвращает все AttackPath актива + агрегаты (w_max,level, counts) одним запросом.ComputeAssetAggregate) + 2 handler-теста + 11 frontend Jest-тестов на pureriskFlow.ts(flow conservation, coverage clamping, disabled controls, severity-weighted splits, negative-severity guard).Spec:
docs/superpowers/specs/2026-05-02-risk-graph-visualization-design.mdPlan:
docs/superpowers/plans/2026-05-02-risk-graph-visualization.mdЧто меняется на UI
/risk/graph/:assetId— теперь страница на 1 актив × все угрозы.?threat=<id>опционален; без него авто-выбирается max-W.coverage, VL→DA —(1−cov).frontend/src/components/RiskGraphSankey.tsx(legacy).V2 (документировано в spec, не блокирует merge)
AssembleAssetAttackPathsсейчас вызываетAssembleAttackPathв цикле —N+1(~40–120 запросов на актив с 10–30 угрозами). V2: bulk repo-методLoadAssetAttackPaths+ контракт-тест.RiskGraphPage.test.tsx/ThreatList.test.tsxsnapshot-тесты.localStorage).onParamshero-кнопка (заглушка из spec, V1 без неё).Test plan
go test ./internal/service/... ./internal/transport/... ./internal/domain/...— PASS (backend)cd frontend && CI=true npm test -- --testPathPattern=riskFlow— 11 passedcd frontend && CI=true npm run build— Compiled successfully/risk/graph/<assetId>для актива с ≥3 угрозами и ≥1 контролем — проверить hero/список/Sankey/popover/what-if/PDF./risk/graph/<id>?threat=<id>корректно подсвечивает выбранную угрозу.?threat=<id>→ toast + fallback к max-W.🤖 Generated with Claude Code