fix: align Defender branch with Michael's Business Impact base

Read Maester `Result` as the native status when no separate Status
column exists, so the Work deviation filter matches the documented
Maester rule again (regressed in the vulnerability result type commit).
Expect the Result DB audience in Michael's empty-state test and record
the rebase and product version 2.17.0 in the implementation plan.
This commit is contained in:
Binh 2026-10-05 09:15:23 +02:00
parent e4f8524580
commit 47c8a839d1
4 changed files with 20 additions and 7 deletions

View File

@ -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. |

View File

@ -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 '';
}

View File

@ -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');

View File

@ -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 });
});