Design doc (read in full first): docs/design/redaction_rationale_reporting.md — "Report Schema Changes" → HTML report, and mitigation items 1 & 2 in "Mitigations required before shipping".
This is the HTML/report presentation layer for Redaction Rationale Reporting. The JSON data layer (schema, origin enum, generation) lands in #68 and is a prerequisite. This issue only renders what that issue produces.
Scope
Render the rationale object in the HTML audit report, visually distinguishing AI-inferred rationale from deterministic rule rationale, in a way that survives print/export.
Extend the existing .source-badge CSS pattern (report.py:734–751, classes .source-badge.rule / .source-badge.llm) inside _generate_html_audit_report() (report.py:163); the per-span row is rendered around report.py:351.
Non-negotiable requirements (from the design doc)
- Persistent "AI-INFERRED, NOT VERIFIED" treatment on every
llm_*-origin rationale, placed directly adjacent to the explanation text itself — not only in a one-time legend elsewhere on the page. The caveat must travel with the text if a single row is copied, screenshotted, or printed.
- A distinct, neutral "Rule match" treatment for
rule_deterministic — the two must not be merged into one undifferentiated "AI explanation" bucket. A reader must be able to tell at a glance whether an explanation is unverified model prose or a deterministic rule description.
unavailable renders as an explicit visible state ("No rationale recorded for this entity"), never as an omitted/blank cell — so its absence is never ambiguous with "the redaction needed no justification".
- The distinction must survive the print/export path. The report already supports print/export (
report.py print handling around the HTML generator); styling-only cues that vanish in print are not acceptable — verify the labeling text is present in printed/exported output, not just on screen.
- Copy/UI language must make clear that inferred explanations are unverified prose, not extracted facts, even when correctly labeled AI-generated (the risk analysis is explicit that prose reads as more authoritative than a confidence float).
Acceptance criteria
Verification gate (all PRs in this set)
PYTHONPATH=src/python python3 -m pytest -q
swift build --package-path src/swift/MarcutApp
swift test --package-path src/swift/MarcutApp
bash scripts/release_preflight.sh
Dependencies
Blocked by #68 (needs the rationale JSON fields to render).
Land as its own PR; do not combine with other issues in this set. Do not reopen design-spike #32.
Design doc (read in full first):
docs/design/redaction_rationale_reporting.md— "Report Schema Changes" → HTML report, and mitigation items 1 & 2 in "Mitigations required before shipping".This is the HTML/report presentation layer for Redaction Rationale Reporting. The JSON data layer (schema,
originenum, generation) lands in#68and is a prerequisite. This issue only renders what that issue produces.Scope
Render the
rationaleobject in the HTML audit report, visually distinguishing AI-inferred rationale from deterministic rule rationale, in a way that survives print/export.Extend the existing
.source-badgeCSS pattern (report.py:734–751, classes.source-badge.rule/.source-badge.llm) inside_generate_html_audit_report()(report.py:163); the per-span row is rendered aroundreport.py:351.Non-negotiable requirements (from the design doc)
llm_*-origin rationale, placed directly adjacent to the explanation text itself — not only in a one-time legend elsewhere on the page. The caveat must travel with the text if a single row is copied, screenshotted, or printed.rule_deterministic— the two must not be merged into one undifferentiated "AI explanation" bucket. A reader must be able to tell at a glance whether an explanation is unverified model prose or a deterministic rule description.unavailablerenders as an explicit visible state ("No rationale recorded for this entity"), never as an omitted/blank cell — so its absence is never ambiguous with "the redaction needed no justification".report.pyprint handling around the HTML generator); styling-only cues that vanish in print are not acceptable — verify the labeling text is present in printed/exported output, not just on screen.Acceptance criteria
.source-badgepattern.llm_validationrows carry a persistent, adjacent "AI-inferred, not verified" label;rule_deterministicrows carry a neutral "rule match" label;unavailableshows the explicit "no rationale recorded" state.llm_*row (so it survives copy/print, which is text-level, not style-level).rationalefield (no crash, no misleading blank).Verification gate (all PRs in this set)
PYTHONPATH=src/python python3 -m pytest -qswift build --package-path src/swift/MarcutAppswift test --package-path src/swift/MarcutAppbash scripts/release_preflight.shDependencies
Blocked by
#68(needs therationaleJSON fields to render).Land as its own PR; do not combine with other issues in this set. Do not reopen design-spike #32.