All notable hardening rounds for this project. Each round corresponds to a
plan document under .local/planning/v{N}/ (older plans were tracked at
docs/integration-hardening-plan-v{N}.md before v18; the docs/* path is
now gitignored so plan drafts stay local).
Completes the deliverable-rendering trio (markdown / DOCX / HTML) started in v21. Browser-viewable HTML is the natural third format when a user wants to share an answer via email or intranet without attaching a Word file.
scripts/render-html.py— vendored verbatim fromlegal-research-agent(87 lines, no DPA-domain rewrites needed — the renderer is generic). Usesmarkofor markdown→HTML, wraps in a self-contained styled HTML document with inline CSS (max-width 760px, bordered tables, code-block styling).requirements.txtaddsmarko>=2.0.0..claude/commands/answer.mdaccepts--htmlflag (independent of--docx; both may be combined).tests/test_e2e_agent_pipeline.pyaddstest_packet_renders_htmlparametrised over all 5 fixtures (asserts HTML > 2 KB + DOCTYPE present +<title>tag). Test skips gracefully whenmarkois unavailable.docs/rendering-examples.md(new) — worked example walking through all 4 output forms (md / meta.json / docx / html) for the same 3-jurisdiction ADM analysis used in the v21 dogfood. Includes file sizes, structure introspection, when-to-use table.
Test counts:
- Before: CA 49 / KR 23 / EU 28 / cross 129 = 229
- After: CA 49 / KR 23 / EU 28 / cross 134 = 234 (+5 v22 HTML)
Adds the second axis to the output contract: output_mode is now orthogonal
to research_mode. The default canonical (the 9-section research memo
shipped in v19) is preserved for backward compatibility; legal_opinion
adds a polished DOCX deliverable suitable for client circulation.
Rather than reinventing rendering and formatting infrastructure, v21
vendors the proven assets from the sibling legal-research-agent:
scripts/render-docx.py— basic markdown→DOCX renderer (KO/EN page setups, Times New Roman + 맑은 고딕 fallback, source-table styling).scripts/render-legal-opinion-docx.py— polished legal-opinion renderer with cover page (title, recipient, date, classification banner), auto-numbered headings, indented italic statutory excerpts, endnote-style 각주 section, page-numbered footer with confidentiality classification on every page.knowledge/legal-writing/— 5 formatter profiles (en-formatter-profile,ko-formatter-profile,ko-legal-opinion-profile,docx-ready-markdown-profile,formatter-index) covering tone, heading hierarchy, citation style, and per-language typography defaults..claude/skills/legal-writing-formatter/— composition skill for the polished deliverable..claude/skills/output-mode-composition/— output_mode dispatcher skill.templates/modes/{black-letter-commentary,comparative-matrix, enforcement-case-law,executive-brief}.md— 4 additional output_mode templates (the existing single-jurisdiction / multi-jurisdiction / comparative-matrix / fallback templates remain).
DPA-domain patches applied to vendored files:
- All
legal-research-agent-result.md/legal-research-agent-meta.jsonreferences rewritten todata-protection-agent-result.md/data-protection-agent-meta.json. Legal Research Agent (legal-research-agent)author default rewritten toData Protection Agent (data-protection-agent).- Backtick references to
legal-research-agentrewritten todata-protection-agent.
Meta schema (additive, all optional):
output_mode— one ofcanonical | legal_opinion | executive_brief | comparative_matrix | enforcement_case_law | black_letter_commentary; defaultcanonical.output_mode_audience— free-text audience descriptor; defaultnull.output_mode_format—markdown | docx_ready_markdown; defaultmarkdown.
Validator:
- New
validate_output_mode_fields()checks the three new fields againstVALID_OUTPUT_MODES/VALID_OUTPUT_FORMATS. - Existing v19 metadata without the new fields validates unchanged (treated as implicit canonical defaults).
/answer command:
- Accepts
output_mode=legal_opinionand--docxarguments. - Auto-renders DOCX when
output_mode=legal_opinion. - Default Korean opinion-letter cover-page values (수신인, 기밀 분류, 날짜) are applied unless the user overrides.
requirements.txt (new): pins python-docx>=1.1.0 and pytest>=7.0.
Two related rounds driven by the v19 dogfood:
- v20-mini (false-positive cleanup):
fallback_uswas treated as non-fallback by the validator, producing spurious missing-namespaces / empty-sources errors.local_id_citations()matched authority-id-shaped tokens inside backtick-wrapped paths and prose hyphenations (US-California), spamming "Local KB id is not present in any index" warnings. Cross-jurisdiction labels check fired on fallback memos that legitimately mention multiple jurisdictions only to explain they are out of scope. All three fixed:FALLBACK_MODESset,_strip_inline_code()helper + lowercase-only id matching + namespace token exclusion (CA/KR/EU), and aResearch mode: fallback*preflight that skips the labels check. - v20-retrieval: Only CA had a topic index, so KR/EU retrieval fell
back to keyword scoring and surfaced wrong-but-valid authorities
(data-act-art33 instead of gdpr-art33; pipa-art28-6 instead of
pipa-art34). Added
kb/kr-pipa/index/kr-topic-index.jsonandkb/eu-gdpr/index/eu-topic-index.json(8 curated topics each — breach notification, lawful basis / consent, data-subject rights, sensitive data, cross-border transfer, DPIA, ADM, enforcement). Patchedimport_namespaced_kbs.pyto read per-namespace topic files; patchedphrase_in()to treat space and hyphen as interchangeable separators (sobreach notificationmatchesbreach-notification); expanded the CA primary-law boost beyond ca-ccpa-* to include ca-customer-records, ca-caloppa, ca-cipa, ca-cmia, ca-data-broker-delete-act, and ca-age-appropriate-design-code. Bonus: added 11 CCR § 7070/7071 to the CAminors_childrentopic (golden regression fix).
After v20, T2.1 dogfood (3-way breach-notification) surfaces gdpr-art33, pipa-art34 + decree art39/40, civ-1798.150 + civ-1798.82 in top-K.
Adds the LLM-facing answering layer on top of the v3-v18 KB and auditor stack. Five Codex-implemented commits behind a single plan:
- 7 new skills under
.claude/skills/(intake-and-routing, kb-retrieval, trust-boundary, claim-grounding, result-memo-composition, comparative-composition, quality-check) — each withdisable-model-invocation: trueso the LLM loads them only when explicitly told to viaCLAUDE.mdreferences. - 6 templates under
templates/(canonical 9-section result.md + meta.example.json + per-mode variants for single-jurisdiction, multi-jurisdiction, comparative-matrix, fallback). - New
scripts/validate-output.py(538 lines, stdlib only) — dual-mode: full v19 strict checks AND legacy_packet compatibility for the pre-v19 deterministic runner. /answerslash command + refreshed.claude/agents/data-protection-agent.mdCLAUDE.md§9 workflow section.
- 5 golden fixtures (
g_ca_001,g_kr_001,g_eu_001,g_multi_001,g_comp_001) + parametrised e2e tests covering validator and auditor.
Mirrors legal-research-agent's 8-stage workflow (intake → retrieval →
trust-boundary → claim-grounding → composition → quality-check → write).
LLM hallucination most commonly takes the form of a correct citation id
paired with a fabricated quote — citation-id checks alone don't catch
this. Adds check_quote_integrity() to all three sub-auditors. Pulls
the cited authority's body via load_authority_body(), normalises both
the body and the quoted text, and verifies via substring match plus a
token-overlap fallback (>=80% of >=3-letter tokens within a 1.5x window).
KR variant uses substring only (Hangul has no whitespace word
boundaries). +12 tests (CA 49, KR 23, EU 28).
Mirrors v11's CA future-effective check for KR and EU. Both KBs use
YYYYMMDD-format effective_date in their article indexes. KR uses
bilingual present-tense triggers (English currently/requires plus
Korean 현재/지금/시행 중/반드시/해야 한다); EU uses English-only.
Today (2026-05-07) has 0 future-effective articles in either KB; the
mechanism is forward-looking. +6 tests.
docs/examples.md (245 lines) — 7 worked auditor examples with real
inputs and the actual JSON output the unified auditor produces, covering
each of the major check categories. Useful for onboarding new
contributors and for explaining auditor behaviour to non-implementer
users.
Wires the v13-v14 schema validator, KB snapshot diff, and coverage reports into the GitHub Actions CI as separate steps so each gets its own pass/fail signal in the PR check log. Graceful skip when KR/EU sibling repos are unavailable on the CI runner.
scripts/kb-diff.py — set-diff between two snapshots of the unified
authority index, with per-namespace breakdowns. Surfaces additions,
removals, and id renames between any two commits. Used in CI to
flag unexpected churn.
Five small tools rounded into one round:
scripts/validate-kb-schema.py— checks 14 indexes (2,414 items total) against required-field expectations.scripts/who-is.py— pretty-prints all metadata for a single authority id, parsing markdown frontmatter directly.scripts/who-cites.py— reverse citation lookup ("which authorities reference X?").scripts/coverage-report-all.py— unified CA+KR+EU coverage dashboard.tests/test_e2e_unified_auditor.py— integration test piping a curated multi-juris answer through the full auditor stack.
- KR PIPA coverage report (
scripts/coverage-report-kr.py) using cross-reference-graph (1,309 edges from 428 unique articles) and external-law-candidates (154 entries). - EU GDPR coverage report (
scripts/coverage-report-eu.py) using case-index + edpb-document-indexgdpr_articlesreferences; 62.6% GDPR coverage (62 of 99 articles cited). - This CHANGELOG.
- CA
check_future_effective_cited_as_current(warn when authority with futureeffective_dateis cited with present-tense language). Today (2026-05-07) has 0 future-effective authorities; check is forward-looking. - Opt-in pre-commit hook (
.githooks/pre-commit) running unified auditor on staged .md files (skips meta-docs to avoid false positives). - CA authority coverage report (
scripts/coverage-report.py) — first measurement: 40.5% coverage (94 of 232) across 5 families. Strongest: case (89.7%); weakest: statute (21.7%), adjacent_statute (31%).
- README.md expanded from 57 → 183 lines with sub-KB table, architecture ASCII tree, test coverage breakdown.
docs/auditors.md— consolidated catalog of ~27 checks across 4 layers..github/workflows/ci.yml— push/PR CI: Python 3.12, KB build, per-sub-KB tests with KR/EU graceful skip on CI runners (no sibling repos available).
unified_auditor/run.py— single-call runner over all 4 auditors via importlib (avoids package-name collision). SKILL.md simplified from 7-step → 5-step.STRICT_JURISDICTION_LABELSenv toggle — strict mode requires every signalled jurisdiction to have an explicit label.- 4 vocab terms (right to object/opt-out, 위탁, joint controllers).
- 3 vague patterns (depending on jurisdiction, in certain cases, may apply).
check_jurisdiction_labels— multi-juris answer with no labels → warn.- Vocab expansion: right to erasure / be forgotten / access / delete / know / DPO / 수탁자.
- 4 vague law reference patterns with proximity-aware skip (±200 chars).
- New
cross_jurisdiction_auditor/package above the 3 sub-auditors. check_citation_routing— authority cited from non-signalled jurisdiction.check_vocabulary— jurisdiction-specific terms in wrong context (10 initial terms across GDPR/CCPA/PIPA).- ASCII-letter boundary
(?<![A-Za-z])for vocab regex (Python\bfails in Korean+English mixed contexts).
- EU
check_edpb_doc_as_binding— non-binding EDPB doc cited with binding language (onlyBinding Decisionexempt bya-binding-prefix). - KR 시행규칙 (network-act-enforcement-rule) pattern.
- KR external-law-candidates info-warn for 154 external Korean laws.
- EU case_number / ECLI / EDPB document_number lookup-based matching (51 cases, 535 recitals, 120 EDPB docs).
- KR sub-KB sanitize.py (mirror CA pattern, 13 patterns).
- EU sub-KB sanitize.py.
- KR PIPA sub-auditor with article + guideline + PIPC-as-binding checks.
- EU GDPR sub-auditor with article + recital + recital-as-binding.
- Wire up SKILL.md.
cross_jurisdiction_auditor/skeleton.unified_auditor/skeleton.- README + AGENTS.md.
See docs/california-local-kb-implementation.md and
docs/california-local-kb-hardening-plan.md for the original CA-only
build (CCPA + adjacent statutes + cases) and the hardening plan that
introduced the unified-KB pattern.
| Round | Total tests | Delta |
|---|---|---|
| v3 | 11 (CA only) | seed |
| v4 | 48 | + KR sub 6, EU sub 6 |
| v5 | 72 | + EU case lookup, KR/EU sanitize |
| v6 | 79 | + EDPB binding, KR enforcement-rule, external-law |
| v7 | 94 | + cross-jurisdiction 15 |
| v8 | 106 | + labels, vocab, vague |
| v9 | 119 | + unified, STRICT |
| v10 | 125 | docs + CI only |
| v11 | 133 | + future-effective 3, coverage 5 |
| v12 | 144 | + KR coverage 4, EU coverage 8 |
| v13 | 162 | + schema 6, who-is/cites 4, coverage-all 4, e2e 4 |
| v14-v16 | 195 | + diff/CI/cookbook validation |
| v17 | 201 | + KR/EU future-effective 6 |
| v18 | 213 | + quote integrity 12 (CA 4, KR 4, EU 4) |
| v19 | 223 | + golden-set e2e 10 (5 fixtures × 2) |
| v20 | 223 | retrieval/auditor fixes; no new tests |
| v21 | 229 | DOCX + legal-opinion vendored; +6 e2e (5 fixtures × DOCX + 1 legal-opinion smoke) |
| v22 | 234 | HTML renderer vendored; +5 e2e (5 fixtures × HTML smoke) |