All notable changes to this project will be documented in this file. The format follows Keep a Changelog and releases follow Semantic Versioning.
-
The corpus now holds the CEQA Guidelines: every section and appendix of 14 CCR Title 14, Division 6, Chapter 3 (§ 15000 et seq.), retrieved section by section from the official online California Code of Regulations that the Office of Administrative Law contracts for, with the site's own currency statement recorded as each document's
edition(OAL publishes no snapshot or PDF; this is a dated retrieval of the weekly official edition and may lag the live code). Rule packs gained aguidelinesfield that wires the NOD and NOE rules to the sections governing their forms, soai explainandai draft-fixcan quote the regulation verbatim; the rule engine does not read it.scripts/build_corpus.pywalks the official table of contents and accepts--ccr-cacheto reuse a prior crawl. -
Recorded the CCR Guidelines retrieval-compliance review in
docs/audits/ccr-guidelines-retrieval-review-2026-08-21.md, linked fromcorpus/README.md: OAL publishes no snapshot in any digital form (confirmed against its own CCR and historical-versions pages), norobots.txtdisallow or terms language forbidding automated retrieval or retention was found on the official online CCR host, and no unofficial reprint was substituted. Re-ran the citation-grounding eval live on Bedrockclaude-sonnet-4-6against the Guidelines-wired commit: 20 previously-withheld claims across the two Guidelines-citing findings (NOE-003,NOD-003) now verify against the retained text;findings_with_nothing_showndropped from 7 to 3, and the remaining 3 are unrelated to the Guidelines. -
Added the real-filing extraction eval and the citation-grounding eval under
evals/, withscripts/fetch_ceqanet_sample.pyto fetch a small, varied sample of real CEQAnet filings (15 committed as identifiers, hashes, and the metadata CEQAnet publishes; PDFs stay in a gitignored cache; contact phone, email, and address are never written). Recorded the first live results for all three suites on Bedrockclaude-sonnet-4-6(the defaultclaude-sonnet-5was not reachable): refusal 109/109 end to end with 0 missed; extraction 88.4% match where both the form and the metadata hold a value, every shown value quote-verified; grounding 308/313 claims shown with 0 uncited and 0 determination-language claims. Seeevals/README.md. -
The quote verifiers now fold typography (curly quotes, dashes, non-breaking spaces, ligatures) before verbatim comparison; a quote that differs in a word still fails.
-
Added
ai explain,ai draft-fix, andai ask(ADR 0002). Explanations and correction drafts are grounded incorpus/: every claim must cite a passage of the official source the rule cites and quote it verbatim, and a verifier checks each quote against the corpus and each sentence for determination language before display, withholding and counting what fails.ai askanswers questions about a report's findings behind a deterministic legal-sufficiency guard that refuses every phrasing of "is this sufficient / will it be accepted / is the exemption valid / did the agency comply" before any model call; the model is instructed to refuse as a second layer and the verifier is a third.evals/now holds the refusal suite (109 refuse phrasings, 30 technical questions, English and Spanish), its two-layer harness, and the results contract: a recorded result must carry provider, model, prompt version, commit, and time, or saynot_run. -
Added
ceqa-preflight ai extract, the first command of the opt-inaigroup (ADR 0002). It reads each PDF's text layer through a bounded, process-isolated extractor, asks the configured model (Anthropic API or Amazon Bedrock via the publicanthropicSDK;claude-sonnet-5by default) to copy out manifest facts, and verifies every proposed value against a verbatim quote from the document before it is shown. Values whose quote does not verify are withheld and counted; fields the text does not state areunknown; image-only PDFs are reported as having no text layer and never sent. The output is a draft manifest for a person to review;checkis unchanged and never invokes it. The provider SDK is an optional extra (ceqa-preflight[ai],[ai-bedrock]) imported only insideaicommands; a test proves the default path never imports it. Every output carries provenance (provider, model, prompt version, tool version, time). -
Added
make audit-sources/scripts/check_rule_sources.py, a maintainer-run link-rot check that confirms every rule catalog source citation URL still resolves. It is deliberately excluded frommake verifyand CI: the product and its test suite make no real network calls, and this stays a manual, periodic companion to the existing rule-source review audits. -
Added
corpus/, the committed, hashed, dated plain text of every official source the rule catalog cites (plus the LCI document-submission page and the project's own source-review addendum), split into addressable passages, withscripts/build_corpus.pyto rebuild it and a loader that refuses text that does not match its manifest. It is the only text the opt-in AI explanation layer (ADR 0002) may quote. The corpus ships inside the wheel. -
Source citations now carry a
kind(official,technical_reference, orproject_advisory) and the HTML report labels each citation link with it instead of a bare "Source".FILE-004andFILE-005, which are self-cited because no official guidance states a file-size limit or a filename character set, now link to the 2026-07-27 source-review addendum that explains their thresholds rather than to the repository root, and are labeled "Project advisory rule — not an official source" (closes #38). Report schema1.1gains the optionalsource.kindfield; existing consumers are unaffected. -
Accepted ADR 0002, an owner-directed change of direction: an opt-in
aicommand group will draft manifest fields from document text, explain findings against the committed official-source corpus, draft corrections, and refuse legal-sufficiency questions. The defaultcheckpath is unchanged and still makes no network requests. The README, contributing guide, data card, threat model, roadmap, and audit log now scope the "no network at runtime" claim to the default path and describe the new data flow. -
Every report now names the checks that did not run. A rule that applies to the filing type but was skipped — experimental without
--include-experimental, removed by--rulesor--exclude-rules, or withdrawn — is listed with its identifier, source citation, and the reason, in the console, JSON, HTML, and checklist formats. Previously a default NOE or NOD run silently omitted the six filing-specific rules (six of the twenty that apply), two of which can produce a failure, and--exclude-rulescould turn a report with warnings into0 failure(s), 0 warning(s)with no trace of the removal; the printable sign-off checklist, whose next step is submission, was identical either way. Exit codes are unchanged: a skipped check still exits0, which the README now states explicitly. -
Report schema version
1.1adds thenot_runarray. The addition is backwards compatible; existing consumers offindingsandmanual_revieware unaffected. -
The release workflow now installs with
uv sync --all-groups --lockedinstead of--frozen.ci.ymlandsecurity.ymlalready made this substitution and said why;release.ymlwas the one job left installing a lockfile it never compared againstpyproject.toml, which is the job where it matters most. -
Fixed checks that reported a clean result for documents they never read. A PDF that timed out, failed to parse, or is encrypted still produced a
PdfInspectionwith every absence signal at its default, so the searchable text, active content, fillable form, and structure tag checks passed on it. A package whose PDFs all timed out therefore produced the same passing lines as a package that was genuinely clean. Those checks now examine only completed inspections, state the number of documents each pass covers, and report anything they could not examine as a manual-review item. -
PdfInspectionnow carriesform_fields_readable, so a form dictionary that could not be parsed is distinguishable from a document with no form fields rather than both reporting a field count of zero. -
CI and the security workflow install with
uv sync --lockedinstead of--frozen;--frozenexits 0 on a lockfile that has drifted frompyproject.toml, so lockfile drift previously passed unnoticed. -
Enabled
pytest-socketthrough--disable-socket. It was a declared development dependency that was never activated, so nothing enforced the documented "no network requests at runtime" boundary; a test now fails if the guard is ever switched off. -
Corrected workflow and script comments that described this public repository as private.
-
Named every portfolio standard explicitly in the README conformance table with reasoned N/A rows, added a Quality & Metrics ledger and AI-development-measurement declaration to the roadmap, and disclosed AI-assisted development in the README.
-
Release publication now authorizes an existing SSH-signed stable tag from reviewed
main, verifies and builds the exact selected commit without a shared cache, and hands artifacts to a checkout-free publisher that rechecks the immutable tag object. -
Pin the contributor, CI, and release interpreter to Python 3.12 through
.python-version, matchingrequires-pythonand eliminating ambient runtime drift in local setup tools. -
Raise the minimum supported mypy version to 1.18 so strict type checks use the portfolio baseline instead of silently accepting older analyzer behavior.
-
Tighten the cyclomatic-complexity budget from 15 to 10 and split archive, PDF, and pilot analysis into focused, independently testable helpers.
-
Correct the internationalization scope from N/A to Applies and document the gettext migration boundary, EN/ES review requirements, and public-release gate without overstating current conformance.
-
Established the local-first CLI foundation, quality gates, governance, project boundaries, and source-cited rule catalog.
-
Added Portfolio Standards conformance, pinned standards submodule, security automation, and release-readiness documentation.
-
Added a privacy-preserving, controlled-label evidence kit for the permissioned practitioner pilot.
-
Gated NOD/NOE-specific filing rules as opt-in experimental rules pending the pilot and qualified-practitioner review; documented the current official guidance source review.
-
Added a pilot partner kit with a non-legal authorization template, private reviewer rubric, outreach email, coordinator checklist, and stop conditions.
-
Added a
synthcommand that generates plainly fictional synthetic packages with seedable, objective defects for demos, regression tests, and pilot reviewer calibration, plus a committed example package and HTML report. -
Added common rules for flattened form fields (PDF-007), screen-reader structure tags (PDF-008), convertible non-PDF documents (FILE-003), advisory file size (FILE-004), and portable filenames (FILE-005); narrowed PDF-006 to security-relevant active content and taught PDF-003 to recommend OCR for scanned documents (rule catalog 1.2.0).
-
Sped up inspection: one text-extraction parse per PDF instead of one per sampled page, and bounded concurrent per-PDF worker processes with content-free progress events.
-
Extended the CLI: batch
checkover multiple packages with a roll-up summary,--rules/--exclude-rulesselection, a printable--format checklistsign-off report,init --from-packagemanifest prepopulation,rules list --format json, and report summaries with a print-friendly HTML stylesheet.