diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md index 16847b4..ef1f047 100644 --- a/docs/IMPLEMENTATION_PLAN.md +++ b/docs/IMPLEMENTATION_PLAN.md @@ -3652,6 +3652,7 @@ Verifiziert 2026-09-09 (Phase 7): Bereichswechsel beschleunigt und Performance-V Verifiziert 2026-09-08 (Phase 7/13B): GPO-Quellenkorrektur fuer Work und Client Info. Aktuelle Evaluationen aus dem vorhandenen autoritativen Readmodell statt ergebnisabhaengiger Backupgruppierung; Import- und Auswertungszeit getrennt, Source-Scan-Provenienz auch auf GPO-only-Blaettern. 1.014 Tests bestanden, acht optionale PostgreSQL-Tests uebersprungen; Local-only-/Designpruefung und beide Builds erfolgreich. Dummy lesend bestaetigt: eine statt drei GPO-Quellenoptionen, 3.805 aktuelle Zeilen, elf historische Evaluationen unveraendert. Keine Persistenzmutation oder Neuanalyse. | Datum | Änderung | +| 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 }); });