Repository navigation
fix(risks): working, all-or-nothing CSV import of risks (#755) - #859
Merged
alex-dembele merged 11 commits intoOct 2, 2026
Merged
alex-dembele merged 11 commits into
alex-dembele merged 11 commits into
Conversation
A risk with no linked asset was stored with a factor of 1.0 by the worker, the handler event and the demo seed, but the breakdown and score-working views computed it with 1.5. Every such risk therefore showed "stored score does not match the working". domain.RiskAssetCriticality is now the only derivation: the average ScoreFactor of the linked assets, or NoAssetCriticalityFactor (1.0) when none is linked. The worker's repository lookup and both read views use it. 1.0 keeps every score already stored valid; the choice is logged as D-062 for the owner. Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
Create and update wrote Impact x Probability and left asset criticality to the Redis worker. Until the worker ran, or forever if the event was lost, the register showed a score the formula cannot produce: P=0.5, I=2 on a critical asset was stored as 1.0 instead of 3.0. Both use cases now resolve the linked assets themselves, score through pkg/scoring's engine with that criticality, and write the risk and its risk_assets links in one transaction (GormRiskAssetStore). The handler no longer links assets after the fact; it passes asset_ids through and still publishes risk.updated for the worker's audit entry. A malformed asset id is now a 400 instead of silently dropping every link, and the update path looks assets up by tenant_id unconditionally. Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
Both handlers re-read the risk by id alone to build the response. The use case had already proved ownership, but the query still broke the rule that every read filters by tenant_id. It now does. Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
D-062: a risk with no linked asset scores with the neutral factor 1.0, as applied in this PR. D-063 (raised as a duplicate D-061): the owner ships the restrained 3D tilt, tracked in #856. Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
#755) POST /risks/import was never mounted and its parser returned zero rows. Every row is now validated first, with line and column for each error; a valid file is written in one transaction through CreateRiskUseCase. Legacy 1-5 scale files are refused with an explicit message, and the plan cap is checked against the whole file.
The page shows created, rejected and every row error as the server sent them, validated with Zod. No success toast unless created > 0. The template now uses the product scales (P 0-1, I 0-10).
Each import error now carries a code and params; the page renders them in French or English and falls back to the server's English message for an unknown code.
…ore-p-×-i-without-asset-criticality' into 755-fixrisks-csv-import-of-risks-and-assets-fails-silently
This was referenced Oct 1, 2026
The page body moves to shared/csvImport so the asset import can reuse it, and every string, error codes included, now comes from the FR/EN catalogue instead of inline ternaries, which the i18n ratchet refused. The rewrite had left components/shared unreachable; those five unused modules are deleted, as the orphan audit requires.
The rewritten page reads csvImport.*; these ten keys had no caller left.
alex-dembele
deleted the
755-fixrisks-csv-import-of-risks-and-assets-fails-silently
branch
October 2, 2026 10:05
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.
Closes #755
Depends on #855 (#792). This branch merges #792's branch to use its
CreateRiskUseCase.WithAssetsandGormRiskAssetStore. Merge #855 first; after that, this PR's diff is only the import.POST /risks/importwas never mounted, and the use case behind it parsed zero rows and reported success. This PR mounts it and makes it real:codeandparams. Write nothing if any row is invalid.CreateRiskUseCase, behindrisk:create+capRisks, with a whole-file plan-cap check.assetscolumn: asset names (case-insensitive) or ids, separated by;, resolved inside the caller's tenant only. An unknown or ambiguous name refuses the row. Links are written in the same transaction, so the stored score is the frozen formula with asset criticality.created > 0. The template uses P 0–1, I 0–10.Verification
New tests:
TestImportRisks_ErrorsCarryCodesForTranslation,TestImportRisks_AssetsColumnLinksAndScores,TestImportRisks_UnknownOrAmbiguousAssetImportsNothing,TestImportRisks_AssetsColumnWithoutResolverIsRefused,TestRiskImportHTTP_AssetsResolveOnlyInCallersTenant(another tenant's asset name refuses the file).Live (Postgres 18 + Vite, throwaway):
0.5 × 6 linked to a CRITICAL asset → score 9.0
critical; LOW asset → 1.5low; no asset → 3.0medium. Links checked inrisk_assets.Webshared by two assets →ambiguous_asset;Ghost→unknown_asset; nothing persisted.In French: « Valeur obligatoire. », « Doit être entre 0 et 1 (valeur : 1.4). », « Aucun actif « Ghost » dans l'inventaire. », and the legacy-scale message.
Earlier pass: bad file persists nothing, good file → 200
created: 2.The page body is shared (
frontend/src/shared/csvImport/) and every string comes from the FR/EN catalogue (csvImport.*). This keeps the i18n ratchet and the orphan audit green: the fivecomponents/shared/*modules that only the old page imported are deleted.Honest remainders