diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md index c4f0b56..3d1056e 100644 --- a/docs/IMPLEMENTATION_PLAN.md +++ b/docs/IMPLEMENTATION_PLAN.md @@ -4231,6 +4231,7 @@ Verifiziert 2026-09-08 (Phase 7/13B): GPO-Quellenkorrektur fuer Work und Client | 2026-10-06 | Phase 7: Data Hub Deviation/Passed now use the same assessed-percentage helper as Client Info; all six donuts state their population basis. 29 focused tests and synthetic Chrome Dark/Light checks passed; absolute counts, other population shares and assessment semantics retained. Client build, design and UI/API version checks passed. Nine out-of-scope report test failures, a report Local-only failure and report server type errors prevent full release verification; attempted 2.16.9 reverted to 2.16.8 pending successful production builds. | | 2026-10-06 | Phase 7: Client Info Deviations percentage now divides by Passed + Deviations and labels Of assessed checks; zero assessed shows a dash while preserving absolute zero. Permanent UI/click invariance tests in both themes and synthetic Chrome presentation verified; unchanged counts, verdicts, scores and other donuts. Patch 2.16.8: both builds and version contracts passed; full run 1,936 passed/15 skipped with one stale API-version assertion corrected and passed in the 43-test focused rerun; Local-only/design checks passed. | | 2026-10-06 | Phase 13B: GPO folder read diagnostics retain only safe error names, stages and counts, distinguish empty/denied/unreadable selections, preserve cancellation and wrap complete timeslot messages. 27 focused GPO UI tests and synthetic Chrome Dark/Light checks passed; both builds and version contracts verified for patch 2.16.7. Full run: 1,932 passed, 15 skipped, one general import timeout passed on separate rerun; Local-only/design checks passed. Customer-specific read failure remains unconfirmed; no customer path/data used, Phase 13B remains in progress. | +| 2026-10-05 | Phasen 6/7/13A, Produktversion 2.17.0: `merge/defender-for-iot` auf `250c213` (Michael, Business Impact + Management Report, 2.16.5) rebased; Michaels Commit ist die Basis, die 25 Branch-Commits liegen darauf. Die Minor-Erhoehung der Defender-Serie (zuvor 2.11.2 -> 2.12.0, Eintrag unten historisch) wird auf dieser Basis 2.16.5 -> 2.17.0; `package.json`, Root-Eintraege in `package-lock.json` und die Versionsvertragstests sind synchron. Korrektur aus `2b5fdf3`: `isDeviationRecord` uebersprang Maester `Result` immer als Ergebnisnachricht; nach AGENTS.md ist `Result` nur neben einer eigenen Statusspalte Evidenz, sonst der native Status. Michaels neuer Sidebar-Test deckte das auf; synthetische Regression in `tests/domain/deviation-predicate.test.ts`. Michaels Result-DB-Leerzustandstest erwartet nun das mit `55da786` eingefuehrte `audience: 'result-db'`. Kein Reimport, keine Migration, nicht im Browser geprueft. Verifikation: Gesamtlauf mit 2.049 bestandenen und 18 uebersprungenen Tests, beide Produktionsbuilds erfolgreich. | | 2026-10-02 | Phase 13A, Disclosure Policy 3.5.0: Defender-Teil von `21a6266` uebernommen; `48a1888` (PingCastle-Aggregate, steht gegen die Zaehlwert-Grenze der Policy 2.0.0) und `78ea9a6` auf Nutzerentscheidung ausgelassen. Die Result DB kennt Microsoft Defender for IoT als Werkzeug statt `Security assessment`; freigegeben wird nur eine formal gueltige oeffentliche CVE-ID als Rule ID und zugleich als Titel. Geraete-, Site-, Sensor- und Zaehlwerte bleiben ausgeschlossen, `affectedAsset` bleibt zurueckgehalten; das Alerts-Profil bleibt generisch. Die zurueckgestellte `securityToolId`-Zeile aus `c056c21` bildet beide Defender-Namen auf den Schluessel `defender-for-iot` ab. Regression: Work und Result DB benennen ein Defender-Finding ueber die Projektion mit seiner CVE. `docs/RESULT_CONTRACT.md` beschreibt 3.5.0; die Versionsaufzaehlung der Result-DB-Regel in `AGENTS.md` ist nicht angepasst (Governance nicht freigegeben). Die materialisierte Result DB wird nach dem naechsten Backendstart mit der neuen Policy neu aufgebaut. Verifikation: beide Typechecks, Gesamtlauf mit 1.921 bestandenen und 18 uebersprungenen Tests sowie dem bekannten lastbedingten Ausfall von `sheet-selection-cost.test.tsx` (allein bestanden), Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | | 2026-10-02 | Phase 13A: Geraeteteil von `95ec322` nachgezogen. `defenderRiskTargetsForOccurrences` ordnet Devices ueber die Quellzeilen der jeweiligen Occurrence zu statt ueber die CVE allein; fuehren zwei Occurrences dieselbe CVE mit unterschiedlichen Devices, bleibt jedes Device bei seiner eigenen Occurrence, und ein `device-finding` fuer ein Device ausserhalb der gewaehlten Occurrence wird abgelehnt. Das stuetzt die Regel, dass eine geraetebezogene Entscheidung nur ihre erfassten Occurrences deckt. Zwei Regressionen (Domain, lokales Repository) uebernommen. Keine API-, SQL- oder Persistenzaenderung. Verifikation: beide Typechecks, Gesamtlauf mit 1.922 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | | 2026-10-02 | Phase 7: Defender-Teil von `3f6259d` (Stile und Dashboardtest) nachgezogen. Kennzahlbeschriftungen und Tabellenkoepfe des Defender-Panels nutzen die 14px-Tabellenrolle statt der 12px-Labelrolle; die Kennzahlen stehen als kompakte Kacheln (`--tile-compact-width`) in einem Segment statt in sechs festen Spalten; der Zeilenmarker nutzt `--marker-width`. Beibehalten gegenueber dem Original: Kachelkontur `--tile-border`, gemeinsame Textrollen und die blaue Tabellenzeilen-Markierung. Nur CSS und Testassertions; nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.920 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | diff --git a/src/importers/status-normalization.ts b/src/importers/status-normalization.ts index 444548a..26137c7 100644 --- a/src/importers/status-normalization.ts +++ b/src/importers/status-normalization.ts @@ -126,12 +126,17 @@ export function toolIdForRecord(record: FindingRecord, fallbackToolId?: string): function statusValueForRecord(record: FindingRecord, toolId: string | undefined): string { const presentation = toolId ? definitionsByToolId.get(toolId)?.presentation : undefined; const headers = presentation?.statusHeaders ?? [/^(?:status|evaluation|result)$/i]; - for (const header of headers) { - const field = record.fields.find(candidate => - candidate.value - && header.test(candidate.label.trim()) - && !presentation?.resultMessageHeaders?.some(pattern => pattern.test(candidate.label.trim()))); - if (field) return field.value; + const isMessage = (label: string) => presentation?.resultMessageHeaders?.some(pattern => pattern.test(label)) ?? false; + // Eine Ergebnisnachricht (Maester `Result`) ist nur neben einer eigenen + // Statusspalte Evidenz; ohne sie ist sie der native Status. + for (const skipMessages of [true, false]) { + for (const header of headers) { + const field = record.fields.find(candidate => + candidate.value + && header.test(candidate.label.trim()) + && !(skipMessages && isMessage(candidate.label.trim()))); + if (field) return field.value; + } } return ''; } diff --git a/tests/domain/deviation-predicate.test.ts b/tests/domain/deviation-predicate.test.ts index ad6094a..4381552 100644 --- a/tests/domain/deviation-predicate.test.ts +++ b/tests/domain/deviation-predicate.test.ts @@ -75,6 +75,13 @@ describe('Outcome eines rohen Statusfeldes fuer die Arbeitsansichten', () => { expect(isDeviationRecord(recordWith([['Result', 'Passed']]), 'cis-cat')).toBe(false); }); + it('liest Maester `Result` ohne eigene Statusspalte als Status und daneben als Evidenz', () => { + expect(isDeviationRecord(recordWith([['Result', 'Failed']]), 'maester')).toBe(true); + expect(isDeviationRecord(recordWith([['Result', 'Passed']]), 'maester')).toBe(false); + expect(isDeviationRecord(recordWith([['Status', 'Passed'], ['Result', 'Failed']]), 'maester')).toBe(false); + expect(isDeviationRecord(recordWith([['Status', 'Failed'], ['Result', 'Passed']]), 'maester')).toBe(true); + }); + it('unterscheidet die drei Maester-Ergebnisse voneinander', () => { expect(outcomeForStatusValue('maester', 'Passed')).toBe('pass'); expect(outcomeForStatusValue('maester', 'Failed')).toBe('fail'); diff --git a/tests/features/filter-empty-state.test.tsx b/tests/features/filter-empty-state.test.tsx index 01b933e..e12efc8 100644 --- a/tests/features/filter-empty-state.test.tsx +++ b/tests/features/filter-empty-state.test.tsx @@ -94,5 +94,5 @@ it.each(['Reset filters', 'Search all Findings'])('Result DB action %s preserves expect(host.querySelector('.filter-empty-state')).toBeNull(); expect(host.querySelectorAll('.results-browser-row[role="row"]')).toHaveLength(2); expect(item.title).toBe('Synthetic result'); // Disclosure controls presentation; the source remains intact. - expect(query.mock.calls.at(-1)![0]).toEqual({ ...(action === 'Reset filters' ? { deviation: true } : {}), projectId: 'synthetic', scanRunIds: ['scan'], assessmentIds: ['slot'], offset: 0, limit: 50 }); + expect(query.mock.calls.at(-1)![0]).toEqual({ ...(action === 'Reset filters' ? { deviation: true } : {}), audience: 'result-db', projectId: 'synthetic', scanRunIds: ['scan'], assessmentIds: ['slot'], offset: 0, limit: 50 }); });