diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index d2abd77..588d027 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -13,7 +13,7 @@ jobs: with: python-version: "3.11" - name: Install CivicCore release wheel - run: python -m pip install https://github.com/CivicSuite/civiccore/releases/download/v1.1.0/civiccore-1.1.0-py3-none-any.whl + run: python -m pip install https://github.com/CivicSuite/civiccore/releases/download/v1.2.0/civiccore-1.2.0-py3-none-any.whl - name: Install package run: python -m pip install -e ".[dev]" - name: Run release gate diff --git a/CHANGELOG.md b/CHANGELOG.md index a76c79d..3fbabbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,22 @@ The format follows Keep a Changelog, and this project follows Semantic Versionin ## [Unreleased] +## [0.3.0] - 2026-06-25 + +### Added + +- Added default local SQLite review persistence so installed CivicAccess is ready without hidden environment setup. +- Added `/civicaccess/staff` for saved review queue, readiness, integration contracts, and records-ready export operations. +- Added `GET /api/v1/civicaccess/reviews`, `POST /api/v1/civicaccess/reviews/{review_id}/records-export`, and `GET /api/v1/civicaccess/integration-contracts`. + +### Changed + +- Aligned CivicAccess to the published CivicCore v1.2.0 release wheel and SHA256. +- Wired the public `/civicaccess` review form to the accessibility review API. +- Added local schema status and `/ready` readiness gates for review-record persistence. +- Added bounded request models and actionable validation errors for public API payloads. +- Changed readiness from environment-gated optional persistence to default local persistence with explicit override support. + ## [0.2.0] - 2026-05-21 ### Corrected diff --git a/README.md b/README.md index fbb78f4..f898d1f 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,15 @@ CivicAccess is the CivicSuite module for accessibility, plain-language, multilingual, and ADA Title II review-support workflows. -Current state: **v0.2.0 corrective demotion state**. This repo contains a deterministic scaffold with a FastAPI package, health/root endpoints, WCAG-aligned review support, optional database-backed review records via `CIVICACCESS_REVIEW_DB_URL`, accessible form planning, accessible publishing workflow checks, plain-language rewrites, multilingual draft variants, ADA Title II review-support packages, tagged-PDF expectations, records-ready export checklists, a public UI at `/civicaccess`, and `civiccore==1.1.0` dependency alignment. The previous `v1.0.0` release was published in error and is superseded by this honest sub-1.0.0 label. +Current state: **v0.3.0 standalone readiness candidate**. This repo contains a FastAPI package aligned to the published CivicCore v1.2.0 release wheel, health/root endpoints, readiness gates, WCAG-aligned review support, local database-backed review records, accessible form planning, accessible publishing workflow checks, plain-language rewrites, multilingual draft variants, ADA Title II review-support packages, tagged-PDF expectations, records-ready export checklists, an API-backed public review UI at `/civicaccess`, and a staff review/export workspace at `/civicaccess/staff`. The previous `v1.0.0` release was published in error and remains historical evidence only. CivicAccess does **not** provide legal advice, certified ADA compliance, official translation certification, live LLM calls, or final publication approval. City staff, ADA coordinators, translators, and qualified reviewers remain responsible for publication decisions. ## What CivicAccess Does - Reviews public content for actionable WCAG-aligned issues. +- Reports local review-record persistence readiness through `/ready`. +- Provides a staff queue for saved accessibility reviews and records-ready exports. - Checks accessible form publication basics: labels, required fields, validation copy, and record context. - Builds a staff publication workflow with accessibility, plain-language, translation-review, export, and approval steps. - Rewrites common municipal jargon into plainer language while preserving source/rewrite provenance. @@ -16,17 +18,18 @@ CivicAccess does **not** provide legal advice, certified ADA compliance, officia - Produces ADA Title II review-support checklists without claiming certification. - Checks tagged-PDF heading expectations before publication. - Builds records-ready export checklists that preserve source/rewrite provenance. -- Provides a local accessibility support UI at `/civicaccess`. +- Provides a local API-backed accessibility review UI at `/civicaccess`. ## Release Integrity Correction -CivicAccess is not finished, shipping, city-ready, product-ready, or public-use ready. It has no real AI layer, no real municipal data/search layer, no production-grade frontend, and no independent Section 2 public-use gate sign-off. See [docs/release-integrity-correction-2026-05-21.md](docs/release-integrity-correction-2026-05-21.md). +CivicAccess was previously demoted after a false `v1.0.0` release. The current branch is rebuilding that truthfully: it now has automatic local persistence, a staff workspace, and integration contracts, but it still must pass the current module-completion audit, suite integration proof, and clean-machine evidence before any finished/public-use label can be promoted. See [docs/release-integrity-correction-2026-05-21.md](docs/release-integrity-correction-2026-05-21.md). ## Developer Quickstart ```powershell python -m venv .venv .\.venv\Scripts\Activate.ps1 +python -m pip install https://github.com/CivicSuite/civiccore/releases/download/v1.2.0/civiccore-1.2.0-py3-none-any.whl python -m pip install -e ".[dev]" python -m pytest -q bash scripts/verify-release.sh @@ -36,9 +39,14 @@ bash scripts/verify-release.sh - `GET /` returns current module status and boundaries. - `GET /health` returns package and CivicCore version information. -- `GET /civicaccess` returns the public UI. +- `GET /ready` and `GET /api/v1/civicaccess/readiness` report whether local review persistence is configured and schema-ready. +- `GET /civicaccess` returns the API-backed public accessibility review UI. +- `GET /civicaccess/staff` returns the staff review queue and records-export workspace. - `POST /api/v1/civicaccess/review` returns accessibility findings and next steps. -- `GET /api/v1/civicaccess/reviews/{review_id}` retrieves persisted review records when `CIVICACCESS_REVIEW_DB_URL` is configured. +- `GET /api/v1/civicaccess/reviews` lists saved review records. +- `GET /api/v1/civicaccess/reviews/{review_id}` retrieves persisted review records. +- `POST /api/v1/civicaccess/reviews/{review_id}/records-export` builds a CivicRecords-ready retention export. +- `GET /api/v1/civicaccess/integration-contracts` publishes upstream/downstream integration contracts. - `POST /api/v1/civicaccess/forms` returns accessible form publication checks. - `POST /api/v1/civicaccess/publishing-workflow` returns staff publication workflow blockers and steps. - `POST /api/v1/civicaccess/plain-language` returns a deterministic plain-language rewrite. @@ -47,7 +55,9 @@ bash scripts/verify-release.sh - `POST /api/v1/civicaccess/tagged-pdf` returns tagged-PDF heading expectations. - `POST /api/v1/civicaccess/export` returns a records-ready accessibility export checklist. -Set `CIVICACCESS_REVIEW_DB_URL` to enable persistent accessibility review records. When unset, CivicAccess continues to use deterministic in-memory review behavior for local examples. +By default, CivicAccess stores review records in `data/civicaccess-reviews.db` under the process working directory. Set `CIVICACCESS_DATA_DIR` to choose a different local data directory, or set `CIVICACCESS_REVIEW_DB_URL` to use an explicit SQLAlchemy database URL. `/ready` is expected to be ready when the local schema can be created and verified. + +Use the `civicaccess-db-status` console script with an explicit SQLAlchemy URL when an operator wants to preflight a non-default review database. ## License diff --git a/README.txt b/README.txt index f77bf12..38fec7c 100644 --- a/README.txt +++ b/README.txt @@ -3,7 +3,7 @@ CivicAccess CivicAccess is the CivicSuite module for accessibility, plain-language, multilingual, and ADA Title II review-support workflows. -Current state: v0.2.0 corrective demotion state. This repo contains a deterministic scaffold with a FastAPI package, health/root endpoints, WCAG-aligned review support, optional database-backed review records via CIVICACCESS_REVIEW_DB_URL, accessible form planning, accessible publishing workflow checks, plain-language rewrites, multilingual draft variants, ADA Title II review-support packages, tagged-PDF expectations, records-ready export checklists, a public UI at /civicaccess, and civiccore==1.1.0 dependency alignment. The previous v1.0.0 release was published in error and is superseded by this honest sub-1.0.0 label. +Current state: v0.3.0 corrective demotion state. This repo contains a deterministic scaffold with a FastAPI package aligned to the published CivicCore v1.2.0 release wheel, health/root endpoints, readiness gates, WCAG-aligned review support, optional database-backed review records via CIVICACCESS_REVIEW_DB_URL, accessible form planning, accessible publishing workflow checks, plain-language rewrites, multilingual draft variants, ADA Title II review-support packages, tagged-PDF expectations, records-ready export checklists, and an API-backed public review UI at /civicaccess. The previous v1.0.0 release was published in error and is superseded by this honest sub-1.0.0 label. CivicAccess does not provide legal advice, certified ADA compliance, official translation certification, live LLM calls, or final publication approval. City staff, ADA coordinators, translators, and qualified reviewers remain responsible for publication decisions. diff --git a/SECURITY.md b/SECURITY.md index 49141b2..9ee7fd5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ # Security -CivicAccess version: `0.2.0`. +CivicAccess version: `0.3.0`. CivicAccess is self-hosted municipal software. It provides advisory accessibility, plain-language, multilingual draft, and ADA Title II review-support workflows; it does not make legal, certification, translation, or publication decisions. diff --git a/USER-MANUAL.md b/USER-MANUAL.md index 701486a..a2d6826 100644 --- a/USER-MANUAL.md +++ b/USER-MANUAL.md @@ -4,17 +4,23 @@ CivicAccess helps cities make public information easier to read, reach, translate, review, and preserve. It supports accessibility review, accessible forms, public publishing workflows, plain-language rewrites, multilingual draft variants, ADA Title II review support, tagged-PDF expectations, and records-ready export checklists. -Current state: `0.2.0` corrective demotion state. CivicAccess includes deterministic checks, optional database-backed review records, a public UI at `/civicaccess`, and `civiccore==1.1.0` dependency alignment. The previous `v1.0.0` release was published in error and is superseded by this honest sub-1.0.0 label. CivicAccess does not provide legal advice, certified ADA compliance, official translation certification, live LLM calls, or final publication approval. +Current state: `0.3.0` standalone readiness candidate. CivicAccess includes deterministic checks, local database-backed review records, readiness gates, an API-backed public review UI at `/civicaccess`, a staff review/export workspace at `/civicaccess/staff`, and CivicCore v1.2.0 release-wheel alignment. The previous `v1.0.0` release was published in error and remains historical evidence only. CivicAccess does not provide legal advice, certified ADA compliance, official translation certification, live LLM calls, or final publication approval. ## For IT And Technical Staff -CivicAccess is a FastAPI Python package pinned to `civiccore==1.1.0`. The current runtime exposes: +CivicAccess is a FastAPI Python package pinned to the published `CivicCore v1.2.0` release wheel. The current runtime exposes: - `GET /` - `GET /health` +- `GET /ready` - `GET /civicaccess` +- `GET /civicaccess/staff` +- `GET /api/v1/civicaccess/readiness` - `POST /api/v1/civicaccess/review` -- `GET /api/v1/civicaccess/reviews/{review_id}` when `CIVICACCESS_REVIEW_DB_URL` is configured +- `GET /api/v1/civicaccess/reviews` +- `GET /api/v1/civicaccess/reviews/{review_id}` +- `POST /api/v1/civicaccess/reviews/{review_id}/records-export` +- `GET /api/v1/civicaccess/integration-contracts` - `POST /api/v1/civicaccess/forms` - `POST /api/v1/civicaccess/publishing-workflow` - `POST /api/v1/civicaccess/plain-language` @@ -23,11 +29,14 @@ CivicAccess is a FastAPI Python package pinned to `civiccore==1.1.0`. The curren - `POST /api/v1/civicaccess/tagged-pdf` - `POST /api/v1/civicaccess/export` -Set `CIVICACCESS_REVIEW_DB_URL` to persist review requests, findings, WCAG references, disclaimers, and next steps. Leave it unset for deterministic sample behavior. +By default, CivicAccess persists review requests, findings, WCAG references, disclaimers, and next steps in `data/civicaccess-reviews.db` under the process working directory. Set `CIVICACCESS_DATA_DIR` to choose a different local data directory, or set `CIVICACCESS_REVIEW_DB_URL` for an explicit SQLAlchemy database URL. Use `civicaccess-db-status` with the same explicit database URL when preflighting a non-default database. + +Before public use, check `/ready` or `/api/v1/civicaccess/readiness`. The readiness gate is `ready` when the local review database schema can be created and verified. Run local verification with: ```powershell +python -m pip install https://github.com/CivicSuite/civiccore/releases/download/v1.2.0/civiccore-1.2.0-py3-none-any.whl python -m pip install -e ".[dev]" python -m pytest -q bash scripts/verify-release.sh @@ -38,9 +47,11 @@ bash scripts/verify-release.sh ```mermaid flowchart LR PublicUser["Resident or staff publisher"] --> CivicAccess["CivicAccess"] - CivicAccess --> CivicCore["CivicCore v1.1.0"] + CivicAccess --> CivicCore["CivicCore v1.2.0"] + CivicAccess --> CivicRecords["CivicRecords AI records export"] CivicClerk["CivicClerk"] -. uses accessibility support .-> CivicAccess CivicCode["CivicCode"] -. uses accessibility support .-> CivicAccess + CivicAccess -. downstream contracts .-> LandUse["Zone / Plan / Permit / Inspect / Grants / Procure"] ``` CivicAccess depends on CivicCore. CivicCore does not depend on CivicAccess. diff --git a/USER-MANUAL.txt b/USER-MANUAL.txt index 3b2f3a3..365fb62 100644 --- a/USER-MANUAL.txt +++ b/USER-MANUAL.txt @@ -3,13 +3,15 @@ CivicAccess User Manual CivicAccess helps cities make public information easier to read, reach, translate, review, and preserve. It supports accessibility review, accessible forms, public publishing workflows, plain-language rewrites, multilingual draft variants, ADA Title II review support, tagged-PDF expectations, and records-ready export checklists. -Current state: 0.2.0 corrective demotion state. CivicAccess includes deterministic checks, optional database-backed review records, a public UI at /civicaccess, and civiccore==1.1.0 dependency alignment. The previous v1.0.0 release was published in error and is superseded by this honest sub-1.0.0 label. It does not provide legal advice, certified ADA compliance, official translation certification, live LLM calls, or final publication approval. +Current state: 0.3.0 corrective demotion state. CivicAccess includes deterministic checks, optional database-backed review records, readiness gates, an API-backed public review UI at /civicaccess, and CivicCore v1.2.0 release-wheel alignment. The previous v1.0.0 release was published in error and is superseded by this honest sub-1.0.0 label. It does not provide legal advice, certified ADA compliance, official translation certification, live LLM calls, or final publication approval. Runtime routes: - GET / - GET /health +- GET /ready - GET /civicaccess +- GET /api/v1/civicaccess/readiness - POST /api/v1/civicaccess/review - GET /api/v1/civicaccess/reviews/{review_id} when CIVICACCESS_REVIEW_DB_URL is configured - POST /api/v1/civicaccess/forms @@ -20,4 +22,6 @@ Runtime routes: - POST /api/v1/civicaccess/tagged-pdf - POST /api/v1/civicaccess/export -Set CIVICACCESS_REVIEW_DB_URL to persist review requests, findings, WCAG references, disclaimers, and next steps. Leave it unset for deterministic sample behavior. +Set CIVICACCESS_REVIEW_DB_URL to persist review requests, findings, WCAG references, disclaimers, and next steps. Use civicaccess-db-status with the same database URL to initialize and verify schema. Leave persistence unset for deterministic sample behavior. + +Before public use, check /ready or /api/v1/civicaccess/readiness. The readiness gate is not-ready until local review persistence is configured and schema-ready. diff --git a/civicaccess/__init__.py b/civicaccess/__init__.py index 4b32bb0..3f3c522 100644 --- a/civicaccess/__init__.py +++ b/civicaccess/__init__.py @@ -1,3 +1,3 @@ """civicaccess package.""" -__version__ = "0.2.0" +__version__ = "0.3.0" diff --git a/civicaccess/access_review.py b/civicaccess/access_review.py index 0593309..c30ce3a 100644 --- a/civicaccess/access_review.py +++ b/civicaccess/access_review.py @@ -49,6 +49,8 @@ class StoredAccessibilityReview: metadata = sa.MetaData() +SCHEMA_VERSION = "2026-06-05-001" + accessibility_review_records = sa.Table( "accessibility_review_records", metadata, @@ -64,6 +66,23 @@ class StoredAccessibilityReview: schema="civicaccess", ) +schema_migrations = sa.Table( + "schema_migrations", + metadata, + sa.Column("schema_version", sa.String(40), primary_key=True), + sa.Column("applied_at", sa.DateTime(timezone=True), nullable=False), + schema="civicaccess", +) + + +@dataclass(frozen=True) +class SchemaStatus: + schema_version: str | None + expected_schema_version: str + ready: bool + missing_tables: tuple[str, ...] + dialect: str + class AccessibilityReviewRepository: """SQLAlchemy-backed accessibility review records for local publication workflows.""" @@ -76,7 +95,52 @@ def __init__(self, *, db_url: str | None = None, engine: Engine | None = None) - self.engine = base_engine with self.engine.begin() as connection: connection.execute(sa.text("CREATE SCHEMA IF NOT EXISTS civicaccess")) + self.migrate() + + def migrate(self) -> SchemaStatus: + """Apply non-destructive local schema setup and return the resulting status.""" + metadata.create_all(self.engine) + with self.engine.begin() as connection: + exists = connection.execute( + sa.select(schema_migrations.c.schema_version).where( + schema_migrations.c.schema_version == SCHEMA_VERSION + ) + ).first() + if exists is None: + connection.execute( + schema_migrations.insert().values( + schema_version=SCHEMA_VERSION, + applied_at=datetime.now(UTC), + ) + ) + return self.schema_status() + + def schema_status(self) -> SchemaStatus: + inspector = sa.inspect(self.engine) + translated_schema = None if self.engine.dialect.name == "sqlite" else "civicaccess" + available_tables = set(inspector.get_table_names(schema=translated_schema)) + expected_tables = {"accessibility_review_records", "schema_migrations"} + missing_tables = tuple(sorted(expected_tables - available_tables)) + schema_version = None + if "schema_migrations" not in missing_tables: + with self.engine.begin() as connection: + schema_version = connection.execute( + sa.select(schema_migrations.c.schema_version) + .order_by(schema_migrations.c.applied_at.desc()) + .limit(1) + ).scalar_one_or_none() + return SchemaStatus( + schema_version=schema_version, + expected_schema_version=SCHEMA_VERSION, + ready=schema_version == SCHEMA_VERSION and not missing_tables, + missing_tables=missing_tables, + dialect=self.engine.dialect.name, + ) + + def review_count(self) -> int: + with self.engine.begin() as connection: + return connection.execute(sa.select(sa.func.count()).select_from(accessibility_review_records)).scalar_one() def create_review( self, *, title: str, body: str, has_alt_text: bool, language: str @@ -125,6 +189,16 @@ def get_review(self, review_id: str) -> StoredAccessibilityReview | None: return None return _row_to_stored_review(row) + def list_reviews(self, *, limit: int = 25) -> tuple[StoredAccessibilityReview, ...]: + bounded_limit = max(1, min(limit, 100)) + with self.engine.begin() as connection: + rows = connection.execute( + sa.select(accessibility_review_records) + .order_by(accessibility_review_records.c.created_at.desc()) + .limit(bounded_limit) + ).mappings().all() + return tuple(_row_to_stored_review(row) for row in rows) + def review_accessibility(*, title: str, body: str, has_alt_text: bool, language: str) -> AccessibilityReview: """Return deterministic sample accessibility findings without live LLM calls.""" diff --git a/civicaccess/db_admin.py b/civicaccess/db_admin.py new file mode 100644 index 0000000..41f048c --- /dev/null +++ b/civicaccess/db_admin.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +import argparse + +from civicaccess.access_review import AccessibilityReviewRepository + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Check and initialize the local CivicAccess review database schema." + ) + parser.add_argument( + "--db-url", + required=True, + help="SQLAlchemy database URL used by CIVICACCESS_REVIEW_DB_URL.", + ) + args = parser.parse_args() + + repository = AccessibilityReviewRepository(db_url=args.db_url) + try: + status = repository.schema_status() + finally: + repository.engine.dispose() + + ready = "ready" if status.ready else "not ready" + missing = ", ".join(status.missing_tables) if status.missing_tables else "none" + version = status.schema_version or "none" + print( + "CivicAccess schema " + f"{ready}: version={version}; expected={status.expected_schema_version}; " + f"dialect={status.dialect}; missing_tables={missing}." + ) + + +if __name__ == "__main__": + main() diff --git a/civicaccess/main.py b/civicaccess/main.py index d8c0e3d..0dd8393 100644 --- a/civicaccess/main.py +++ b/civicaccess/main.py @@ -1,18 +1,20 @@ """FastAPI runtime foundation for CivicAccess.""" import os +from pathlib import Path from civiccore import __version__ as CIVICCORE_VERSION -from fastapi import FastAPI, HTTPException -from fastapi.responses import HTMLResponse -from pydantic import BaseModel +from fastapi import FastAPI, HTTPException, Request +from fastapi.exceptions import RequestValidationError +from fastapi.responses import HTMLResponse, JSONResponse +from pydantic import BaseModel, Field from civicaccess import __version__ -from civicaccess.access_review import AccessibilityReviewRepository, StoredAccessibilityReview, review_accessibility +from civicaccess.access_review import AccessibilityReviewRepository, StoredAccessibilityReview from civicaccess.exports import build_accessible_export from civicaccess.multilingual import create_language_variant from civicaccess.plain_language import rewrite_plain_language -from civicaccess.public_ui import render_public_lookup_page +from civicaccess.public_ui import render_public_lookup_page, render_staff_page from civicaccess.workflows import ( build_accessible_form_plan, build_ada_title_ii_review_plan, @@ -32,45 +34,45 @@ class AccessibilityReviewRequest(BaseModel): - title: str = "" - body: str + title: str = Field(default="", max_length=500) + body: str = Field(max_length=5000) has_alt_text: bool = False - language: str = "en" + language: str = Field(default="en", min_length=1, max_length=80) class PlainLanguageRequest(BaseModel): - text: str + text: str = Field(min_length=1, max_length=5000) class LanguageVariantRequest(BaseModel): - text: str - language: str + text: str = Field(min_length=1, max_length=5000) + language: str = Field(min_length=1, max_length=80) class AccessibleExportRequest(BaseModel): - title: str - format: str = "html" + title: str = Field(min_length=1, max_length=500) + format: str = Field(default="html", min_length=1, max_length=40) class AccessibleFormRequest(BaseModel): - form_name: str = "" - fields: list[str] = [] + form_name: str = Field(default="", max_length=500) + fields: list[str] = Field(default_factory=list, max_length=100) class PublishingWorkflowRequest(BaseModel): - title: str = "" + title: str = Field(default="", max_length=500) has_review: bool = False has_plain_language: bool = False has_translation_review: bool = False class AdaTitleIiReviewRequest(BaseModel): - service_area: str = "" + service_area: str = Field(default="", max_length=500) has_coordinator_review: bool = False class TaggedPdfExpectationRequest(BaseModel): - heading_levels: list[int] = [] + heading_levels: list[int] = Field(default_factory=list, max_length=200) @app.get("/") @@ -80,12 +82,12 @@ def root() -> dict[str, str]: return { "name": "CivicAccess", "version": __version__, - "status": "corrective demotion state", + "status": "standalone readiness candidate", "message": ( - "CivicAccess is an honest v0.2.0 deterministic scaffold with accessible-form planning, publishing workflow checks, WCAG-aligned review support, optional database-backed review records, plain-language rewrites, multilingual sample variants, ADA Title II review-support packages, tagged-PDF expectations, records-ready export checklists, and a public UI. " + "CivicAccess provides local accessibility review support with accessible-form planning, publishing workflow checks, WCAG-aligned review records, plain-language rewrites, multilingual sample variants, ADA Title II review-support packages, tagged-PDF expectations, records-ready export checklists, and API-backed public and staff interfaces. " "It does not provide legal advice, certified ADA compliance, official translation certification, live LLM calls, or final publication approval." ), - "next_step": "Use CivicAccess for local review support only; do not treat it as finished or public-use ready until a future independent audit signs off against the full CivicSuite Section 2 gate.", + "next_step": "Open /civicaccess/staff to review saved publication work and export records-ready packages.", } @@ -101,6 +103,47 @@ def health() -> dict[str, str]: } +@app.exception_handler(RequestValidationError) +async def validation_exception_handler( + _request: Request, exc: RequestValidationError +) -> JSONResponse: + fields = sorted( + { + ".".join(str(part) for part in error.get("loc", [])[1:]) + for error in exc.errors() + if len(error.get("loc", [])) > 1 + } + ) + field_list = ", ".join(fields) if fields else "request body" + return JSONResponse( + status_code=422, + content={ + "detail": { + "message": f"CivicAccess could not validate: {field_list}.", + "fix": ( + "Send a JSON body that includes the required field names listed in " + "the fields array, using strings for text inputs and booleans for yes/no inputs." + ), + "fields": fields, + } + }, + ) + + +@app.get("/ready") +def ready() -> dict[str, object]: + """Return public-use readiness for local review-record persistence.""" + + return _readiness_payload() + + +@app.get("/api/v1/civicaccess/readiness") +def readiness() -> dict[str, object]: + """Return detailed CivicAccess local persistence readiness for operators.""" + + return _readiness_payload() + + @app.get("/civicaccess", response_class=HTMLResponse) def public_civicaccess_page() -> str: """Return the accessible public sample UI.""" @@ -108,42 +151,50 @@ def public_civicaccess_page() -> str: return render_public_lookup_page() +@app.get("/civicaccess/staff", response_class=HTMLResponse) +def staff_civicaccess_page() -> str: + """Return the staff publication review workspace.""" + + return render_staff_page() + + @app.post("/api/v1/civicaccess/review") def accessibility_review(request: AccessibilityReviewRequest) -> dict[str, object]: - if _review_database_url() is not None: - stored = _get_review_repository().create_review( - title=request.title, - body=request.body, - has_alt_text=request.has_alt_text, - language=request.language, - ) - return _stored_review_response(stored) - - result = review_accessibility( + stored = _get_review_repository().create_review( title=request.title, body=request.body, has_alt_text=request.has_alt_text, language=request.language, ) + return _stored_review_response(stored) + + +@app.get("/api/v1/civicaccess/reviews") +def list_accessibility_reviews(limit: int = 25) -> dict[str, object]: + reviews = _get_review_repository().list_reviews(limit=limit) return { - "status": result.status, - "findings": [finding.__dict__ for finding in result.findings], - "disclaimer": result.disclaimer, - "next_steps": list(result.next_steps), - "review_id": None, + "status": "ok", + "count": len(reviews), + "reviews": [_stored_review_summary(review) for review in reviews], } @app.get("/api/v1/civicaccess/reviews/{review_id}") def get_accessibility_review(review_id: str) -> dict[str, object]: - if _review_database_url() is None: + stored = _get_review_repository().get_review(review_id) + if stored is None: raise HTTPException( - status_code=503, + status_code=404, detail={ - "message": "CivicAccess review persistence is not configured.", - "fix": "Set CIVICACCESS_REVIEW_DB_URL to retrieve persisted accessibility review records.", + "message": "Accessibility review record not found.", + "fix": "Use a review_id returned by POST /api/v1/civicaccess/review.", }, ) + return _stored_review_response(stored) + + +@app.post("/api/v1/civicaccess/reviews/{review_id}/records-export") +def export_accessibility_review_record(review_id: str) -> dict[str, object]: stored = _get_review_repository().get_review(review_id) if stored is None: raise HTTPException( @@ -153,7 +204,56 @@ def get_accessibility_review(review_id: str) -> dict[str, object]: "fix": "Use a review_id returned by POST /api/v1/civicaccess/review.", }, ) - return _stored_review_response(stored) + export = build_accessible_export(title=stored.title or "Untitled accessible publication") + return { + "status": "records-export-ready", + "module": "civicaccess", + "target_module": "civicrecords-ai", + "review": _stored_review_summary(stored), + "export": { + "title": export.title, + "format": export.format, + "checklist": list(export.checklist), + "retention_note": export.retention_note, + "status": export.status, + "fix": export.fix, + }, + "provenance": { + "source_text_preserved": True, + "findings_preserved": True, + "disclaimer_preserved": True, + "created_at": stored.created_at.isoformat(), + }, + } + + +@app.get("/api/v1/civicaccess/integration-contracts") +def integration_contracts() -> dict[str, object]: + return { + "status": "ok", + "module": "civicaccess", + "provides": [ + { + "contract": "civicaccess.publication_accessibility_review.v1", + "endpoint": "/api/v1/civicaccess/review", + "purpose": "Create a persisted accessibility review before publication.", + }, + { + "contract": "civicaccess.records_export.v1", + "endpoint": "/api/v1/civicaccess/reviews/{review_id}/records-export", + "target_module": "civicrecords-ai", + "purpose": "Export review provenance and checklist data for records retention.", + }, + ], + "downstream_ready_for": [ + "civiczone public notice text", + "civicplan policy summaries", + "civicpermit applicant forms", + "civicinspect notices", + "civicgrants public opportunity notices", + "civicprocure RFP and award packets", + ], + } @app.post("/api/v1/civicaccess/plain-language") @@ -237,14 +337,17 @@ def accessible_export(request: AccessibleExportRequest) -> dict[str, object]: def _review_database_url() -> str | None: - return os.environ.get("CIVICACCESS_REVIEW_DB_URL") + configured = os.environ.get("CIVICACCESS_REVIEW_DB_URL") + if configured: + return configured + data_dir = Path(os.environ.get("CIVICACCESS_DATA_DIR", Path.cwd() / "data")).resolve() + data_dir.mkdir(parents=True, exist_ok=True) + return f"sqlite:///{data_dir / 'civicaccess-reviews.db'}" def _get_review_repository() -> AccessibilityReviewRepository: global _review_db_url, _review_repository db_url = _review_database_url() - if db_url is None: - raise RuntimeError("CIVICACCESS_REVIEW_DB_URL is not configured.") if _review_repository is None or db_url != _review_db_url: _dispose_review_repository() _review_db_url = db_url @@ -252,6 +355,27 @@ def _get_review_repository() -> AccessibilityReviewRepository: return _review_repository +def _readiness_payload() -> dict[str, object]: + db_url = _review_database_url() + repository = _get_review_repository() + schema_status = repository.schema_status() + blockers: list[str] = [] + if not schema_status.ready: + blockers.append("Run the local CivicAccess schema status/migration check.") + ready_for_public_use = not blockers + return { + "status": "ready" if ready_for_public_use else "not-ready", + "ready": ready_for_public_use, + "review_database_configured": True, + "review_database_url": db_url, + "schema_ready": schema_status.ready, + "schema_version": schema_status.schema_version, + "expected_schema_version": schema_status.expected_schema_version, + "review_count": repository.review_count(), + "blockers": blockers, + } + + def _dispose_review_repository() -> None: global _review_repository if _review_repository is not None: @@ -274,3 +398,14 @@ def _stored_review_response(stored: StoredAccessibilityReview) -> dict[str, obje "language": stored.language, "created_at": stored.created_at.isoformat(), } + + +def _stored_review_summary(stored: StoredAccessibilityReview) -> dict[str, object]: + return { + "review_id": stored.review_id, + "title": stored.title, + "language": stored.language, + "status": stored.status, + "finding_count": len(stored.findings), + "created_at": stored.created_at.isoformat(), + } diff --git a/civicaccess/public_ui.py b/civicaccess/public_ui.py index 8ce2d5b..f301410 100644 --- a/civicaccess/public_ui.py +++ b/civicaccess/public_ui.py @@ -1,10 +1,10 @@ -"""Static public UI for CivicAccess v0.2.0.""" +"""Public UI for CivicAccess v0.3.0.""" from __future__ import annotations def render_public_lookup_page() -> str: - """Render the accessible public-facing CivicAccess page.""" + """Render the accessible public-facing CivicAccess review page.""" return """ @@ -53,7 +53,7 @@ def render_public_lookup_page() -> str:

CivicSuite / CivicAccess

Make public information easier to read, reach, and preserve.

CivicAccess gives staff a deterministic review path for accessible forms, public notices, plain-language rewrites, multilingual samples, ADA Title II review support, tagged-PDF expectations, and municipal-record exports.

-

v0.2.0 corrective demotion state

+

v0.3.0 standalone readiness candidate

@@ -119,37 +119,230 @@ def render_public_lookup_page() -> str: if (initialParams.has("notice")) notice.value = initialParams.get("notice"); if (initialParams.get("alt") === "1") altText.checked = true; + function appendText(tagName, text) { + const node = document.createElement(tagName); + node.textContent = text; + result.appendChild(node); + return node; + } + function setResult(kind, heading, body, items) { result.className = "result" + (kind ? " " + kind : ""); - const list = items && items.length ? "" : ""; - result.innerHTML = "

" + heading + "

" + body + "

" + list; + result.replaceChildren(); + appendText("h3", heading); + appendText("p", body); + if (items && items.length) { + const list = document.createElement("ul"); + for (const item of items) { + const listItem = document.createElement("li"); + listItem.textContent = item; + list.appendChild(listItem); + } + result.appendChild(list); + } } - runReview.addEventListener("click", () => { + runReview.addEventListener("click", async () => { setResult("pending", "Loading review", "Checking the notice text and publication fields.", []); - window.setTimeout(() => { - try { - const params = new URLSearchParams(window.location.search); - if (params.get("force_error") === "1") { - throw new Error("forced qa error"); - } - const fixes = []; - if (!title.value.trim()) fixes.push("Add a short title that names the service, deadline, or public action."); - if (!notice.value.trim()) fixes.push("Add the resident-facing notice text before publication review."); - if (!altText.checked) fixes.push("Add alt text for non-decorative images or mark decorative images as decorative."); - if (fixes.length === 0) { - fixes.push("Add a plain-language summary and preserve the source text with the record."); - setResult("", "Sample checks passed", "Staff review is still required before publication.", fixes); - } else if (!title.value.trim() && !notice.value.trim()) { - setResult("warning", "No notice text yet", "Add a title, resident-facing notice text, and image context before running the review.", fixes); - } else { - fixes.push("Add a plain-language summary and preserve the source text with the record."); - setResult("warning", "Needs fixes", "Resolve these items before staff publication approval.", fixes); - } - } catch (error) { - setResult("warning", "Review could not finish", "Check that the notice text is present, then run the review again. If this happens in the installed stack, staff should check the CivicAccess service health endpoint.", []); + runReview.disabled = true; + try { + const response = await fetch("/api/v1/civicaccess/review", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + title: title.value, + body: notice.value, + has_alt_text: altText.checked, + language: "en", + }), + }); + const payload = await response.json(); + if (!response.ok) { + const detail = payload.detail || {}; + throw new Error([detail.message, detail.fix].filter(Boolean).join(" ")); } - }, 250); + const fixes = (payload.findings || []).map((finding) => finding.fix); + if (payload.status === "passes-sample-checks") { + setResult("", "Sample checks passed", "Staff review is still required before publication.", payload.next_steps || []); + } else if (fixes.length) { + setResult("warning", "Needs fixes", "Resolve these items before staff publication approval.", fixes); + } else { + setResult("warning", "Review needs staff attention", payload.disclaimer || "Staff review is required before publication.", []); + } + } catch (error) { + setResult("warning", "Review could not finish", error.message || "Check that the notice text is present, then run the review again. If this happens in the installed stack, staff should check the CivicAccess service health endpoint.", []); + } finally { + runReview.disabled = false; + } + }); + + + +""" + + +def render_staff_page() -> str: + """Render the staff review workspace for saved CivicAccess work.""" + + return """ + + + + +CivicAccess Staff Workspace + + + + +
+

CivicSuite / CivicAccess staff

+

Review, preserve, and export accessible publication work.

+

This workspace gives staff a saved local queue for accessibility reviews, records-ready exports, and downstream publication contracts used by other CivicSuite modules.

+
+
+
+
+

Create saved review

+ + + + + + +
+

Saved reviews appear in the staff queue and can be exported for records retention.

+
+
+
+

System readiness

+

Loading readiness...

+

Downstream contracts

+

Loading integration contracts...

+
+
+

Saved review queue

+

Loading saved reviews...

+

Select a saved review to export a records-ready package.

+
+
+
+

CivicAccess staff outputs are advisory. Staff approval, qualified translation review, and ADA coordinator review remain required for official publication decisions.

+ diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md index d0051be..dba6956 100644 --- a/docs/IMPLEMENTATION_PLAN.md +++ b/docs/IMPLEMENTATION_PLAN.md @@ -1,6 +1,6 @@ # CivicAccess Implementation Plan -CivicAccess v0.2.0 is a corrective demotion after the mistaken v1.0.0 publication. It contains deterministic accessible forms, accessible publishing workflows, WCAG review support, plain-language rewrites, multilingual draft variants, ADA Title II review support, tagged-PDF expectations, records-ready exports, persistence for review records, and a public UI, but it is not finished, shipping, city-ready, product-ready, or public-use ready. +CivicAccess v0.2.0 is a corrective demotion after the mistaken v1.0.0 publication. It contains deterministic accessible forms, accessible publishing workflows, WCAG review support, plain-language rewrites, multilingual draft variants, ADA Title II review support, tagged-PDF expectations, records-ready exports, persistence for review records, readiness gates, CivicCore v1.2.0 release-wheel alignment, and an API-backed public review UI, but it is not finished, shipping, city-ready, product-ready, or public-use ready. ## Verification Bar diff --git a/docs/audit-lite-api-validation-2026-06-05.md b/docs/audit-lite-api-validation-2026-06-05.md new file mode 100644 index 0000000..963f434 --- /dev/null +++ b/docs/audit-lite-api-validation-2026-06-05.md @@ -0,0 +1,39 @@ +# Audit Lite - CivicAccess API Validation + +**Date:** 2026-06-05 +**Scope:** Reviewed bounded request models, actionable FastAPI validation errors, domain-preserved empty review behavior, and regression tests. +**Reviewer:** Codex (audit-lite) + +## TL;DR + +Ship this slice. CivicAccess now bounds public API payloads and returns actionable validation errors without breaking the domain-level empty-content review finding. + +## Severity rollup + +- Blocker: 0 +- Critical: 0 +- Major: 0 +- Minor: 0 +- Nit: 0 + +## Findings + +None. + +## What's working + +- `civicaccess/main.py` adds `Field` limits for public request models and avoids mutable list defaults with `default_factory`. +- The `RequestValidationError` handler returns `message`, `fix`, and `fields` instead of FastAPI's default raw validation list. +- The review `body` remains required and bounded, while empty strings still reach the accessibility domain logic and return `missing-body`. +- Regression tests cover missing required body, oversized body, successful review, and empty-content advisory findings. + +## Verification + +- Focused validation/API tests - 3 passed. +- `python -m pytest -q` - 23 passed. +- `python -m ruff check .` - passed. +- `bash scripts/verify-release.sh` - PASSED; 23 passed, 1 pytest-asyncio deprecation warning, ruff passed, artifacts built. + +## Escalation recommendation + +No escalation needed for this slice. diff --git a/docs/audit-lite-civiccore-1-2-alignment-2026-06-05.md b/docs/audit-lite-civiccore-1-2-alignment-2026-06-05.md new file mode 100644 index 0000000..73c9ab1 --- /dev/null +++ b/docs/audit-lite-civiccore-1-2-alignment-2026-06-05.md @@ -0,0 +1,40 @@ +# Audit Lite - CivicAccess CivicCore 1.2 Alignment + +**Date:** 2026-06-05 +**Scope:** Reviewed the CivicAccess dependency, CI, docs, and runtime tests changed to align with the published CivicCore v1.2.0 release wheel. +**Reviewer:** Codex (audit-lite) + +## TL;DR + +Ship this slice. CivicAccess now installs CivicCore from the pinned v1.2.0 release wheel with SHA256, the runtime health contract expects 1.2.0, and current-facing docs no longer describe the stale 1.1.0 dependency posture. + +## Severity rollup + +- Blocker: 0 +- Critical: 0 +- Major: 0 +- Minor: 0 +- Nit: 0 + +## Findings + +None. + +## What's working + +- `pyproject.toml` uses the published CivicCore v1.2.0 release wheel and enables Hatch direct references. +- `.github/workflows/verify.yml` installs the same CivicCore v1.2.0 wheel before the package. +- `tests/test_runtime_foundation.py` asserts both the direct-reference dependency and the `/health` CivicCore version. +- README, user manual, docs landing, implementation plan, and placeholder-import diagnostics all reflect the 1.2.0 platform alignment. + +## Verification + +- `python -m pytest tests\test_runtime_foundation.py -q` - 4 passed. +- `python -m pip install -e ".[dev]"` - installed CivicAccess with CivicCore v1.2.0 wheel. +- `python -m ruff check .` - passed. +- `python -m pytest -q` - 18 passed. +- `bash scripts/verify-release.sh` - PASSED; 18 passed, 1 pytest-asyncio deprecation warning, ruff passed, artifacts built. + +## Escalation recommendation + +No escalation needed for this slice. diff --git a/docs/audit-lite-public-review-ui-2026-06-05.md b/docs/audit-lite-public-review-ui-2026-06-05.md new file mode 100644 index 0000000..d946787 --- /dev/null +++ b/docs/audit-lite-public-review-ui-2026-06-05.md @@ -0,0 +1,41 @@ +# Audit Lite - CivicAccess Public Review UI + +**Date:** 2026-06-05 +**Scope:** Reviewed the `/civicaccess` public review UI wiring, result rendering path, current-facing docs, route tests, and browser behavior. +**Reviewer:** Codex (audit-lite) + +## TL;DR + +Ship this slice. The public page now calls the real accessibility review API, renders response data without `innerHTML`, and preserves the advisory/non-certification boundary. + +## Severity rollup + +- Blocker: 0 +- Critical: 0 +- Major: 0 +- Minor: 0 +- Nit: 0 + +## Findings + +None. + +## What's working + +- `civicaccess/public_ui.py` submits to `/api/v1/civicaccess/review` and renders API findings or next steps. +- Result rendering uses DOM construction and `textContent`, not `innerHTML`. +- `tests/test_accessibility_foundation.py` asserts API wiring and guards against reintroducing `result.innerHTML`. +- README, user manual, docs landing, implementation plan, and root endpoint copy describe the public surface as API-backed review UI. +- Playwright checks at 1440x1000 and 390x844 submitted the review form successfully with no console messages, request failures, or horizontal overflow. + +## Verification + +- `python -m pytest tests\test_accessibility_foundation.py::test_public_ui_route_is_accessible_and_honest tests\test_accessibility_foundation.py::test_api_review_success_shape -q` - 2 passed. +- `python -m pytest tests\test_accessibility_foundation.py -q` - 10 passed. +- Playwright live check against `http://127.0.0.1:18165/civicaccess` - desktop and mobile rendered `Needs fixes`, 1 finding, no overflow, no console messages, no request failures. +- `python -m pytest -q` - 18 passed. +- `bash scripts/verify-release.sh` - PASSED; 18 passed, 1 pytest-asyncio deprecation warning, ruff passed, artifacts built. + +## Escalation recommendation + +No escalation needed for this slice. diff --git a/docs/audit-lite-schema-readiness-2026-06-05.md b/docs/audit-lite-schema-readiness-2026-06-05.md new file mode 100644 index 0000000..393b42d --- /dev/null +++ b/docs/audit-lite-schema-readiness-2026-06-05.md @@ -0,0 +1,40 @@ +# Audit Lite - CivicAccess Schema And Readiness Gates + +**Date:** 2026-06-05 +**Scope:** Reviewed CivicAccess schema migration status, `civicaccess-db-status`, `/ready` and `/api/v1/civicaccess/readiness`, docs, and regression tests. +**Reviewer:** Codex (audit-lite) + +## TL;DR + +Ship this slice. CivicAccess now has a non-destructive schema status path and readiness endpoints that require configured review-record persistence before public-use readiness. + +## Severity rollup + +- Blocker: 0 +- Critical: 0 +- Major: 0 +- Minor: 0 +- Nit: 0 + +## Findings + +None. + +## What's working + +- `AccessibilityReviewRepository.migrate()` records `SCHEMA_VERSION` in `schema_migrations` and reports missing tables, dialect, and readiness. +- `civicaccess-db-status` initializes and reports schema readiness using the same URL as `CIVICACCESS_REVIEW_DB_URL`. +- `/ready` and `/api/v1/civicaccess/readiness` return `not-ready` when review persistence is not configured and `ready` when schema is initialized. +- README, user manual, docs landing, and implementation plan document the readiness behavior. + +## Verification + +- Focused readiness tests - 4 passed. +- `python -m civicaccess.db_admin --db-url sqlite:///:memory:` - reported `CivicAccess schema ready`. +- `python -m pytest tests\test_runtime_foundation.py::test_root_endpoint_states_runtime_boundary -q` - 1 passed. +- `python -m pytest -q` - 22 passed. +- `bash scripts/verify-release.sh` - PASSED; 22 passed, 1 pytest-asyncio deprecation warning, ruff passed, artifacts built. + +## Escalation recommendation + +No escalation needed for this slice. diff --git a/docs/audit-lite-standalone-persistence-staff-workspace-2026-06-06.md b/docs/audit-lite-standalone-persistence-staff-workspace-2026-06-06.md new file mode 100644 index 0000000..517fcb6 --- /dev/null +++ b/docs/audit-lite-standalone-persistence-staff-workspace-2026-06-06.md @@ -0,0 +1,37 @@ +# Audit Lite - CivicAccess Standalone Persistence And Staff Workspace + +**Date:** 2026-06-06 +**Scope:** Reviewed the slice that changes CivicAccess from optional review persistence to default local persistence, adds staff review/list/export UI, and publishes upstream/downstream integration contracts. +**Reviewer:** Codex (audit-lite) + +## TL;DR + +Ship this slice. CivicAccess now creates local review persistence by default, `/ready` can pass without hidden environment setup, staff can create/list/export saved review records, and the module publishes contracts for CivicRecords retention plus downstream publication workflows. No findings remain in this lite pass. + +## Severity Rollup + +- Blocker: 0 +- Critical: 0 +- Major: 0 +- Minor: 0 +- Nit: 0 + +## Findings + +None. + +## What's Working + +- Correctness: `civicaccess.main._review_database_url()` now resolves a local SQLite database under `CIVICACCESS_DATA_DIR` or `data/`, and tests prove default readiness is `ready`. +- UX: `/civicaccess/staff` provides a staff-facing queue, readiness panel, contract panel, and records-export action without unsafe HTML injection. +- Integration: `GET /api/v1/civicaccess/integration-contracts` declares the CivicRecords export contract and downstream publication hooks for Zone, Plan, Permit, Inspect, Grants, and Procure. +- Tests: `python -m pytest -q` passed with 25 tests, including default persistence, review list, records export, contracts, and staff UI wiring. +- Runtime/release: `bash scripts/verify-release.sh` passed, including docs verification, placeholder import scan, Ruff, tests, and package builds. + +## Watch Items + +Historical audit and QA docs still accurately describe the prior 2026-06-05 stage scope, where readiness stayed not-ready until persistence was configured. Current-facing README, user manual, changelog, code, and tests now describe the new default-persistence behavior. + +## Escalation Recommendation + +No escalation needed for this slice. CivicAccess still needs the later full module gate: full audit, Playwright walkthrough of the new staff workspace, suite installer refresh, clean-machine proof, and integration proof before final module completion. diff --git a/docs/audits/audit-civicaccess-stage-2026-06-05/00-executive-audit.md b/docs/audits/audit-civicaccess-stage-2026-06-05/00-executive-audit.md new file mode 100644 index 0000000..399ae86 --- /dev/null +++ b/docs/audits/audit-civicaccess-stage-2026-06-05/00-executive-audit.md @@ -0,0 +1,50 @@ +# CivicAccess Stage Gate Audit + +**Date:** 2026-06-05 +**Branch:** `stage-civicaccess-release-readiness-2026-06-05` +**Head reviewed:** `ed9ef77` +**Scope:** Full CivicAccess stage gate after CivicCore 1.2.0 alignment, API-backed public review UI, schema/readiness gates, and API validation guardrails. + +## Executive Summary + +CivicAccess passes this stage gate. The module remains honest about its corrective-demotion status while adding the release-readiness basics expected for a local-first accessibility review service: current CivicCore runtime alignment, API-backed public review, bounded validation errors, schema status, and readiness checks. Tests, docs, and browser walkthrough evidence agree; no Blocker, Critical, Major, Minor, or Nit findings remain in this audit pass. + +## Severity Rollup + +- Blocker: 0 +- Critical: 0 +- Major: 0 +- Minor: 0 +- Nit: 0 + +## Top Findings + +None. + +## What's Working Well + +- Runtime truth: `/health` reports CivicCore 1.2.0, and docs no longer imply unsupported v1 completion. +- Public UI wiring: `/civicaccess` submits to `/api/v1/civicaccess/review` and renders API findings without unsafe HTML injection. +- Readiness gate: `/ready` and `/api/v1/civicaccess/readiness` stay not-ready until a local review-record database is configured. +- Validation signal: malformed API requests return actionable field-specific errors instead of opaque framework responses. +- Release gate: `bash scripts/verify-release.sh` passed with tests, docs, placeholder import scan, ruff, and build artifacts. + +## This-Sprint Punch List + +No required fixes remain for this CivicAccess stage gate. + +## Next-Sprint Watchlist + +- Wire `civicaccess-db-status` and `/ready` into the suite-level installer checks once the city-core installer stage resumes. +- Add external clean-machine evidence only when the suite-level stage calls for module-by-module installer validation. + +## Blast-Radius Notes + +No active findings require blast-radius handling. The highest-risk changes were API validation and readiness semantics; regression coverage now asserts required-field validation, bounded payload handling, schema status, and unconfigured-runtime not-ready behavior. + +## Verification + +- `python -m pytest -q` - 23 passed. +- `bash scripts/verify-release.sh` - PASSED; 23 passed, 1 pytest-asyncio deprecation warning, ruff passed, artifacts built. +- Playwright walkthrough against `http://127.0.0.1:18166/civicaccess` - desktop and mobile no overflow, no console messages, no request failures. +- Residue check - no banned workspace path matches and no skipped-test markers in the release surface. diff --git a/docs/audits/audit-civicaccess-stage-2026-06-05/01-engineering-deepdive.md b/docs/audits/audit-civicaccess-stage-2026-06-05/01-engineering-deepdive.md new file mode 100644 index 0000000..cf0bd00 --- /dev/null +++ b/docs/audits/audit-civicaccess-stage-2026-06-05/01-engineering-deepdive.md @@ -0,0 +1,26 @@ +# Engineering Deep Dive + +## Verdict + +Pass. CivicAccess has the expected local-first runtime controls for this module stage and no engineering findings remain. + +## Reviewed Areas + +- Dependency alignment with CivicCore 1.2.0. +- FastAPI routes, validation handler, review endpoints, readiness endpoints, and public page mounting. +- SQLite persistence schema migration, schema status reporting, and configured-runtime behavior. +- Release scripts, packaging metadata, console entry points, and test coverage. + +## Findings + +None. + +## Notes + +The module now exposes independent runtime health and readiness signals. `/health` confirms service and CivicCore versions, while `/ready` refuses to represent an unconfigured local review database as production-ready. The review API keeps deterministic advisory behavior and stores records only when a configured database is supplied. + +## Evidence + +- 23 pytest tests pass. +- Release verification passes through docs, placeholder import checks, ruff, and build. +- `civicaccess-db-status` is covered by tests and required by documentation verification. diff --git a/docs/audits/audit-civicaccess-stage-2026-06-05/02-uiux-deepdive.md b/docs/audits/audit-civicaccess-stage-2026-06-05/02-uiux-deepdive.md new file mode 100644 index 0000000..51fda96 --- /dev/null +++ b/docs/audits/audit-civicaccess-stage-2026-06-05/02-uiux-deepdive.md @@ -0,0 +1,27 @@ +# UI/UX Deep Dive + +## Verdict + +Pass. The public review surface is usable at desktop and mobile widths and no UI findings remain. + +## Reviewed Areas + +- `/civicaccess` form layout and copy. +- Submit, loading, success, and error states. +- API-backed findings rendering. +- Keyboard focus entry through the skip link. +- Desktop and mobile overflow behavior. + +## Findings + +None. + +## Notes + +The interface presents a real review workflow rather than a static demo. It keeps the advisory boundary visible, renders returned findings as structured DOM nodes, and avoids horizontal overflow in the checked desktop and mobile viewports. + +## Evidence + +- Screenshot: `docs/qa/civicaccess-stage-2026-06-05/public-desktop.png` +- Screenshot: `docs/qa/civicaccess-stage-2026-06-05/public-mobile.png` +- Evidence JSON: `docs/qa/civicaccess-stage-2026-06-05/walkthrough-evidence.json` diff --git a/docs/audits/audit-civicaccess-stage-2026-06-05/03-documentation-deepdive.md b/docs/audits/audit-civicaccess-stage-2026-06-05/03-documentation-deepdive.md new file mode 100644 index 0000000..9577373 --- /dev/null +++ b/docs/audits/audit-civicaccess-stage-2026-06-05/03-documentation-deepdive.md @@ -0,0 +1,25 @@ +# Documentation Deep Dive + +## Verdict + +Pass. Documentation matches the module's current behavior and no documentation findings remain. + +## Reviewed Areas + +- README and plain-text README. +- User manual and plain-text user manual. +- Documentation index and implementation plan. +- Changelog and release verification scripts. + +## Findings + +None. + +## Notes + +The docs now describe CivicAccess as an honest v0.2.0 deterministic scaffold with API-backed public review, optional review-record persistence, and readiness checks. They do not claim certified compliance, official translation, legal advice, or completed suite installer readiness. + +## Evidence + +- `scripts/verify-docs.sh` passes as part of the release gate. +- Current docs mention `civicaccess-db-status`, `/ready`, and `/api/v1/civicaccess/readiness`. diff --git a/docs/audits/audit-civicaccess-stage-2026-06-05/04-test-deepdive.md b/docs/audits/audit-civicaccess-stage-2026-06-05/04-test-deepdive.md new file mode 100644 index 0000000..f0afbd2 --- /dev/null +++ b/docs/audits/audit-civicaccess-stage-2026-06-05/04-test-deepdive.md @@ -0,0 +1,27 @@ +# Test Deep Dive + +## Verdict + +Pass. The test suite covers the stage's behavior changes and no test findings remain. + +## Reviewed Areas + +- Runtime foundation tests. +- Public UI source wiring tests. +- API validation tests. +- Persistence and readiness tests. +- Release gate script. + +## Findings + +None. + +## Notes + +Tests assert the direct CivicCore 1.2.0 dependency, public UI API fetch wiring, safe result rendering, missing/oversized request validation, configured schema readiness, and unconfigured runtime not-ready behavior. The suite avoids disabled-test markers in the checked release surface. + +## Evidence + +- `python -m pytest -q` - 23 passed. +- `bash scripts/verify-release.sh` - PASSED. +- Residue grep found no skipped-test markers or placeholder assertions. diff --git a/docs/audits/audit-civicaccess-stage-2026-06-05/05-qa-deepdive.md b/docs/audits/audit-civicaccess-stage-2026-06-05/05-qa-deepdive.md new file mode 100644 index 0000000..5800505 --- /dev/null +++ b/docs/audits/audit-civicaccess-stage-2026-06-05/05-qa-deepdive.md @@ -0,0 +1,28 @@ +# QA Deep Dive + +## Verdict + +Pass. Runtime walkthrough evidence matches the documented and tested behavior, with no QA findings remaining. + +## Reviewed Areas + +- Public review page in Chromium desktop and mobile. +- Root, health, readiness, and review API responses. +- Invalid request handling. +- Console and network failure capture. + +## Findings + +None. + +## Notes + +The walkthrough exercised the public UI and API endpoints on a local server. The public form rendered a real returned finding, invalid API input produced a 422 with field detail, and readiness correctly stayed not-ready without a configured review database. + +## Evidence + +- Desktop result heading: `Needs fixes`. +- Mobile result heading: `Needs fixes`. +- Console messages: none. +- Request failures: none. +- Overflow: none in checked desktop or mobile viewport. diff --git a/docs/audits/audit-civicaccess-stage-2026-06-05/next-sprint-watchlist.md b/docs/audits/audit-civicaccess-stage-2026-06-05/next-sprint-watchlist.md new file mode 100644 index 0000000..ef41cdb --- /dev/null +++ b/docs/audits/audit-civicaccess-stage-2026-06-05/next-sprint-watchlist.md @@ -0,0 +1,5 @@ +# CivicAccess Next-Sprint Watchlist + +- Connect CivicAccess readiness checks to the suite-level city-core installer when that stage resumes. +- Include `civicaccess-db-status` in operator runbooks that cover module-by-module local configuration. +- Re-run clean-machine evidence only when the suite-level installer stage requires external VM validation. diff --git a/docs/audits/audit-civicaccess-stage-2026-06-05/sprint-punchlist.md b/docs/audits/audit-civicaccess-stage-2026-06-05/sprint-punchlist.md new file mode 100644 index 0000000..ee627de --- /dev/null +++ b/docs/audits/audit-civicaccess-stage-2026-06-05/sprint-punchlist.md @@ -0,0 +1,11 @@ +# CivicAccess This-Sprint Punch List + +No required fixes remain for this CivicAccess stage gate. + +## Closed In This Stage + +- Aligned CivicAccess to the CivicCore 1.2.0 runtime artifact. +- Replaced static public review behavior with an API-backed UI. +- Added schema status and readiness gates for local review-record storage. +- Added bounded, actionable API validation guardrails. +- Captured stage audit and Playwright walkthrough evidence. diff --git a/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/00-executive-audit.md b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/00-executive-audit.md new file mode 100644 index 0000000..2eb6980 --- /dev/null +++ b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/00-executive-audit.md @@ -0,0 +1,56 @@ +# CivicAccess Standalone Gate Audit - Executive Report + +Date: 2026-06-06 +Scope: CivicAccess standalone persistence, staff workspace, records-export contract, tests, docs, release build, and runtime UI/API behavior. + +## Executive Summary + +CivicAccess is ready for the current standalone module gate. The module now starts with default local SQLite persistence, reports ready without hidden environment setup, exposes public and staff UIs, persists review records, exports records-ready packages for CivicRecords AI, and publishes downstream integration contracts for the remaining modules. Tests, Ruff, docs gate, placeholder import gate, package build, and browser walkthrough all passed. + +## Severity Roll-Up + +- Blocker: 0 +- Critical: 0 +- Major: 0 +- Minor: 0 +- Nit: 0 + +## Evidence + +- Commit under audit: `d8871b88ce1e255d0e7ac9842e23d237f985717b` +- Tests: `python -m pytest -q` -> 25 passed +- Lint: `python -m ruff check civicaccess tests` -> passed +- Release gate: `bash scripts/verify-release.sh` -> passed +- Browser walkthrough: `docs/qa/civicaccess-standalone-gate-2026-06-06/walkthrough.md` +- Screenshots: `docs/qa/civicaccess-standalone-gate-2026-06-06/*.png` +- Evidence JSON: `docs/qa/civicaccess-standalone-gate-2026-06-06/walkthrough-evidence.json` + +## Top Findings + +No findings. + +## What Works + +- Default persistence makes `/ready` and `/api/v1/civicaccess/readiness` usable on a fresh local install. +- Public review UI is API-backed and creates persisted review records. +- Staff workspace supports readiness, contract visibility, saved queue review, and records export. +- Records export publishes `target_module=civicrecords-ai`, preserving source, findings, disclaimer, and retention metadata. +- Integration contracts explicitly prepare CivicZone, CivicPlan, CivicPermit, CivicInspect, CivicGrants, and CivicProcure. + +## This-Sprint Punch List + +No CivicAccess standalone gate fixes remain. + +## Next-Sprint Watchlist + +- Keep CivicAccess contract names stable as downstream modules begin consuming them. +- Add end-to-end suite tests when CivicZone and later modules start linking publication notices into CivicAccess. +- Replace the `standalone readiness candidate` wording only after the integrated suite gate proves CivicAccess in the installed stack. + +## Deep Dives + +- Engineering: `01-engineering-deepdive.md` +- UI/UX: `02-uiux-deepdive.md` +- Documentation: `03-documentation-deepdive.md` +- Test Engineering: `04-test-deepdive.md` +- QA: `05-qa-deepdive.md` diff --git a/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/01-engineering-deepdive.md b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/01-engineering-deepdive.md new file mode 100644 index 0000000..16cdc7c --- /dev/null +++ b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/01-engineering-deepdive.md @@ -0,0 +1,44 @@ +# Engineering Deep Dive + +Scope: runtime architecture, persistence, API contracts, downstream blast radius, and package/build readiness. + +## Findings + +Rollup: 0 Blocker / 0 Critical / 0 Major / 0 Minor / 0 Nit. + +No engineering findings. + +## Evidence Reviewed + +- `civicaccess/main.py` +- `civicaccess/access_review.py` +- `civicaccess/public_ui.py` +- `tests/test_production_depth_review_persistence.py` +- `tests/test_accessibility_foundation.py` +- `README.md` +- `USER-MANUAL.md` +- `CHANGELOG.md` +- `docs/qa/civicaccess-standalone-gate-2026-06-06/walkthrough-evidence.json` + +## Assessment + +The persistence contract is conservative and appropriate for standalone use. `CIVICACCESS_REVIEW_DB_URL` remains the explicit override, while default local storage derives from `CIVICACCESS_DATA_DIR` or `data/civicaccess-reviews.db`. Repository creation verifies schema readiness, and the readiness endpoint reports the configured URL, schema status, schema version, and review count. + +The API surface supports the current city-employee workflow: +- create persisted accessibility review +- list saved reviews +- retrieve saved review +- export records-ready package +- publish integration contracts + +The integration-contract endpoint is a good downstream stabilizer. CivicZone and later modules can depend on contract names instead of scraping UI or assuming internal database details. + +## Blast Radius + +Downstream modules should consume only the contract endpoints and review/export APIs. They should not assume SQLite file layout, internal table names, or UI text. + +## What Works + +- No optional persistence trap remains. +- Error responses for missing review records remain actionable. +- The records-export endpoint preserves provenance and points to CivicRecords AI without coupling to its internals. diff --git a/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/02-uiux-deepdive.md b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/02-uiux-deepdive.md new file mode 100644 index 0000000..ad2f19c --- /dev/null +++ b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/02-uiux-deepdive.md @@ -0,0 +1,30 @@ +# UI/UX Deep Dive + +Scope: public `/civicaccess` UI, staff `/civicaccess/staff` UI, desktop/mobile screenshots, interaction feedback, and accessibility-adjacent usability. + +## Findings + +Rollup: 0 Blocker / 0 Critical / 0 Major / 0 Minor / 0 Nit. + +No UI/UX findings. + +## Evidence Reviewed + +- `docs/qa/civicaccess-standalone-gate-2026-06-06/public-desktop.png` +- `docs/qa/civicaccess-standalone-gate-2026-06-06/staff-initial-desktop.png` +- `docs/qa/civicaccess-standalone-gate-2026-06-06/staff-after-export-desktop.png` +- `docs/qa/civicaccess-standalone-gate-2026-06-06/staff-mobile.png` +- `civicaccess/public_ui.py` + +## Assessment + +The public UI clearly frames CivicAccess as advisory support, not certified compliance or legal advice. The main workflow is visible in the first page body and reaches a clear final state after API review. + +The staff workspace exposes the operational controls a clerk would expect for this slice: readiness, downstream contracts, saved review creation, saved queue, and records export. The desktop layout is scannable and the mobile layout stacks without overlap or clipped controls. + +## What Works + +- Buttons are tied to real API behavior. +- Status cards use live data, not static claims. +- The export action displays a final visible result with the target module and retention note. +- Advisory boundaries are visible in both public and staff contexts. diff --git a/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/03-documentation-deepdive.md b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/03-documentation-deepdive.md new file mode 100644 index 0000000..ca409e0 --- /dev/null +++ b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/03-documentation-deepdive.md @@ -0,0 +1,28 @@ +# Documentation Deep Dive + +Scope: README, user manual, changelog, release evidence, and truthfulness against runtime behavior. + +## Findings + +Rollup: 0 Blocker / 0 Critical / 0 Major / 0 Minor / 0 Nit. + +No documentation findings. + +## Evidence Reviewed + +- `README.md` +- `USER-MANUAL.md` +- `CHANGELOG.md` +- `docs/audit-lite-standalone-persistence-staff-workspace-2026-06-06.md` +- `docs/qa/civicaccess-standalone-gate-2026-06-06/walkthrough.md` + +## Assessment + +The current-facing docs accurately describe CivicAccess as a v0.2.0 standalone readiness candidate. They state the operational endpoints, default persistence behavior, environment overrides, advisory boundary, and staff responsibilities. The docs do not overclaim legal, accessibility-certification, translation-certification, or final approval capabilities. + +## What Works + +- Default database behavior is documented. +- Public and staff routes are documented. +- Records export and downstream module preparation are documented. +- The changelog captures the runtime behavior change from optional persistence to default local persistence. diff --git a/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/04-test-deepdive.md b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/04-test-deepdive.md new file mode 100644 index 0000000..6499a1e --- /dev/null +++ b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/04-test-deepdive.md @@ -0,0 +1,36 @@ +# Test Engineering Deep Dive + +Scope: unit/API tests, persistence tests, documentation gates, placeholder import gate, lint, release build, and mutation-risk review. + +## Findings + +Rollup: 0 Blocker / 0 Critical / 0 Major / 0 Minor / 0 Nit. + +No test findings. + +## Evidence Reviewed + +- `tests/test_accessibility_foundation.py` +- `tests/test_production_depth_review_persistence.py` +- `tests/test_runtime_foundation.py` +- `tests/conftest.py` +- `scripts/verify-release.sh` +- `scripts/verify-docs.sh` +- `scripts/check-civiccore-placeholder-imports.py` + +## Verification Run + +- `python -m pytest -q`: 25 passed +- `python -m ruff check civicaccess tests`: passed +- `bash scripts/verify-release.sh`: passed, including docs, placeholder import scan, Ruff, tests, and package build + +## Assessment + +The tests cover the behavior that previously blocked standalone readiness: default local persistence, readiness, schema status, persisted review creation/retrieval, review listing, records export, staff UI wiring, public UI wiring, validation, and advisory boundary text. The autouse test fixture isolates default data directories and disposes the repository between tests, reducing cross-test leakage risk. + +## What Works + +- Behavioral tests assert readiness is true by default. +- API tests assert records export and review-list behavior. +- UI source tests assert fetch wiring for public and staff routes. +- Release script exercises the packaging path, not only unit tests. diff --git a/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/05-qa-deepdive.md b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/05-qa-deepdive.md new file mode 100644 index 0000000..1dfe066 --- /dev/null +++ b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/05-qa-deepdive.md @@ -0,0 +1,38 @@ +# QA Deep Dive + +Scope: runtime behavior, browser walkthrough, API cross-checks, local persistence, screenshots, and console/runtime errors. + +## Findings + +Rollup: 0 Blocker / 0 Critical / 0 Major / 0 Minor / 0 Nit. + +No QA findings. + +## Runtime Evidence + +- Local service started on `127.0.0.1:18101` +- Fresh local SQLite data directory +- `/api/v1/civicaccess/readiness`: `ready=true`, `schema_ready=true` +- `/api/v1/civicaccess/integration-contracts`: required contracts present +- `/civicaccess`: browser status 200 +- `/civicaccess/staff`: browser status 200 +- Browser console: no errors captured + +## Workflow Evidence + +Public workflow: +- Filled public notice. +- Ran review. +- Final UI state: `Sample checks passed`. + +Staff workflow: +- Readiness card loaded live ready state. +- Contract card loaded published contracts. +- Saved a review. +- Review appeared in saved queue. +- Exported the review for records. +- Final UI state: `Records export ready` with `civicrecords-ai: checklist-created`. + +## Assessment + +The runtime path works as a real clerk-facing slice. The UI is not merely decorative: visible controls call live endpoints, persist review records, refresh the queue, and export records packages. diff --git a/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/next-sprint-watchlist.md b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/next-sprint-watchlist.md new file mode 100644 index 0000000..170a025 --- /dev/null +++ b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/next-sprint-watchlist.md @@ -0,0 +1,5 @@ +# CivicAccess Standalone Gate Next-Sprint Watchlist + +- Preserve the published contract names while downstream modules start consuming CivicAccess. +- Add suite-level consumer tests as CivicZone, CivicPlan, CivicPermit, CivicInspect, CivicGrants, and CivicProcure link to CivicAccess outputs. +- Promote wording from `standalone readiness candidate` only after the installed integrated-suite gate proves the module in the full launcher environment. diff --git a/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/sprint-punchlist.md b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/sprint-punchlist.md new file mode 100644 index 0000000..7c3b817 --- /dev/null +++ b/docs/audits/audit-civicaccess-standalone-gate-2026-06-06/sprint-punchlist.md @@ -0,0 +1,5 @@ +# CivicAccess Standalone Gate Sprint Punch List + +No fixes remain for this gate. + +Severity rollup: 0 Blocker / 0 Critical / 0 Major / 0 Minor / 0 Nit. diff --git a/docs/index.html b/docs/index.html index 6d10c29..980d221 100644 --- a/docs/index.html +++ b/docs/index.html @@ -22,9 +22,9 @@

Accessibility support that keeps humans responsible.

CivicAccess is the accessibility, plain-language, multilingual, and ADA Title II review-support module for CivicSuite.

- v0.2.0 corrective demotion state + v0.3.0 corrective demotion state

Current State

-

The package contains deterministic accessibility review support, optional database-backed review records via CIVICACCESS_REVIEW_DB_URL, accessible form planning, accessible publishing workflow checks, plain-language rewrite, multilingual draft variants, ADA Title II review-support packages, tagged-PDF expectations, records-ready export checklists, and a public UI. The previous v1.0.0 release was published in error and is superseded by this honest sub-1.0.0 label.

+

The package contains deterministic accessibility review support, CivicCore v1.2.0 release-wheel alignment, readiness gates, optional database-backed review records via CIVICACCESS_REVIEW_DB_URL, accessible form planning, accessible publishing workflow checks, plain-language rewrite, multilingual draft variants, ADA Title II review-support packages, tagged-PDF expectations, records-ready export checklists, and an API-backed public review UI. The previous v1.0.0 release was published in error and is superseded by this honest sub-1.0.0 label.

CivicAccess does not provide legal advice, certified ADA compliance, official translation certification, live LLM calls, or final publication approval.

diff --git a/docs/qa/civicaccess-stage-2026-06-05/public-desktop.png b/docs/qa/civicaccess-stage-2026-06-05/public-desktop.png new file mode 100644 index 0000000..42ea17a Binary files /dev/null and b/docs/qa/civicaccess-stage-2026-06-05/public-desktop.png differ diff --git a/docs/qa/civicaccess-stage-2026-06-05/public-mobile.png b/docs/qa/civicaccess-stage-2026-06-05/public-mobile.png new file mode 100644 index 0000000..ee47add Binary files /dev/null and b/docs/qa/civicaccess-stage-2026-06-05/public-mobile.png differ diff --git a/docs/qa/civicaccess-stage-2026-06-05/walkthrough-evidence.json b/docs/qa/civicaccess-stage-2026-06-05/walkthrough-evidence.json new file mode 100644 index 0000000..3ddaec3 --- /dev/null +++ b/docs/qa/civicaccess-stage-2026-06-05/walkthrough-evidence.json @@ -0,0 +1,108 @@ +{ + "generated_at": "2026-06-05T22:12:04.872Z", + "base": "http://127.0.0.1:18166", + "checks": [ + { + "name": "public-desktop", + "status": 200, + "viewport": { + "width": 1440, + "height": 1000 + }, + "focused": "Skip to main content", + "heading": "Needs fixes", + "itemCount": 1, + "resultText": "Needs fixes\n\nResolve these items before staff publication approval.\n\nAdd concise alt text that explains the purpose of the visual, or mark decorative images as decorative.", + "overflow": false, + "consoleMessages": [], + "requestFailures": [], + "screenshot": "C:/dev/Claude/civicaccess/docs/qa/civicaccess-stage-2026-06-05/public-desktop.png" + }, + { + "name": "public-mobile", + "status": 200, + "viewport": { + "width": 390, + "height": 844 + }, + "focused": "Skip to main content", + "heading": "Needs fixes", + "itemCount": 1, + "resultText": "Needs fixes\n\nResolve these items before staff publication approval.\n\nAdd concise alt text that explains the purpose of the visual, or mark decorative images as decorative.", + "overflow": false, + "consoleMessages": [], + "requestFailures": [], + "screenshot": "C:/dev/Claude/civicaccess/docs/qa/civicaccess-stage-2026-06-05/public-mobile.png" + }, + { + "name": "api-/", + "status": 200, + "body": "{\"name\":\"CivicAccess\",\"version\":\"0.2.0\",\"status\":\"corrective demotion state\",\"message\":\"CivicAccess is an honest v0.2.0 deterministic scaffold with accessible-form planning, publishing workflow checks, WCAG-aligned review support, optional database-backed review records, plain-language rewrites, multilingual sample variants, ADA Title II review-support packages, tagged-PDF expectations, records-ready export checklists, and an API-backed public review UI. It does not provide legal advice, certified ADA compliance, official translation certification, live LLM calls, or final publication approval.\",\"next_step\":\"Configure CIVICACCESS_REVIEW_DB_URL and verify /ready before public use.\"}" + }, + { + "name": "api-/health", + "status": 200, + "body": "{\"status\":\"ok\",\"service\":\"civicaccess\",\"version\":\"0.2.0\",\"civiccore_version\":\"1.2.0\"}" + }, + { + "name": "api-/ready", + "status": 200, + "body": "{\"status\":\"not-ready\",\"ready\":false,\"review_database_configured\":false,\"schema_ready\":false,\"schema_version\":null,\"expected_schema_version\":null,\"review_count\":0,\"blockers\":[\"Set CIVICACCESS_REVIEW_DB_URL to a local review-record database.\"]}" + }, + { + "name": "api-/api/v1/civicaccess/readiness", + "status": 200, + "body": "{\"status\":\"not-ready\",\"ready\":false,\"review_database_configured\":false,\"schema_ready\":false,\"schema_version\":null,\"expected_schema_version\":null,\"review_count\":0,\"blockers\":[\"Set CIVICACCESS_REVIEW_DB_URL to a local review-record database.\"]}" + }, + { + "name": "api-review-empty-content", + "status": 200, + "body": { + "status": "needs-fixes", + "findings": [ + { + "code": "missing-body", + "severity": "high", + "message": "The public text is empty.", + "fix": "Add the resident-facing text before running an accessibility review.", + "wcag_reference": "WCAG 3.1.5 Reading Level" + }, + { + "code": "missing-title", + "severity": "high", + "message": "The document needs a descriptive title.", + "fix": "Add a short title that names the service, deadline, or public action.", + "wcag_reference": "WCAG 2.4.2 Page Titled" + }, + { + "code": "missing-alt-text", + "severity": "high", + "message": "At least one image or visual element is missing alternative text.", + "fix": "Add concise alt text that explains the purpose of the visual, or mark decorative images as decorative.", + "wcag_reference": "WCAG 1.1.1 Non-text Content" + } + ], + "disclaimer": "Accessibility review is advisory support only; it does not replace legal review, a certified accessibility audit, or an ADA coordinator's decision.", + "next_steps": [ + "Resolve each high-severity finding before publication.", + "Have staff or an ADA coordinator review the final publication decision.", + "Preserve the review record with the source content and publication package." + ], + "review_id": null + } + }, + { + "name": "api-review-invalid", + "status": 422, + "body": { + "detail": { + "message": "CivicAccess could not validate: body.", + "fix": "Send a JSON body that includes the required field names listed in the fields array, using strings for text inputs and booleans for yes/no inputs.", + "fields": [ + "body" + ] + } + } + } + ] +} \ No newline at end of file diff --git a/docs/qa/civicaccess-stage-2026-06-05/walkthrough.md b/docs/qa/civicaccess-stage-2026-06-05/walkthrough.md new file mode 100644 index 0000000..730e8fb --- /dev/null +++ b/docs/qa/civicaccess-stage-2026-06-05/walkthrough.md @@ -0,0 +1,62 @@ +# CivicAccess Stage Walkthrough + +## Executive Summary + +The `/civicaccess` interface is wired to the accessibility review API and works in desktop and mobile Chromium checks. The page renders a real review form, submits to `/api/v1/civicaccess/review`, displays returned findings, and keeps the advisory-only boundary visible. No interface wiring findings remain. + +## Methodology + +- Reviewed README, user manual, route definitions, public UI source, persistence code, readiness code, and tests. +- Launched `civicaccess.main:app` locally on `127.0.0.1:18166`. +- Used Playwright Chromium at 1440x1000 and 390x844. +- Captured screenshots and network/console evidence. +- Exercised `/`, `/health`, `/ready`, `/api/v1/civicaccess/readiness`, valid review submission, and invalid review submission. + +## Project Gestalt + +CivicAccess is a local-first accessibility review support module. Its public UI exposes advisory content review; API and persistence paths support optional local review records; readiness gates prevent an unconfigured review database from being treated as customer-ready. + +## Findings By Severity + +None. + +## Missing Or Partial Features + +No missing UI wiring was found within the current CivicAccess stage scope. The broader suite still needs installer-level clean-machine validation outside this module stage. + +## Backend Or System Capabilities Not Surfaced + +The public UI surfaces advisory accessibility review. Review-record persistence, schema status, and readiness are documented operator surfaces rather than public controls, which matches the module boundary. + +## Confusing Or Misleading UI + +None found. The UI labels the flow as accessibility review support and states that it does not replace legal review, a certified audit, or an ADA coordinator decision. + +## Broken Or Suspicious Wiring Map + +| UI element or workflow | Expected system connection | Actual connection | Status | Evidence | +| --- | --- | --- | --- | --- | +| Review form | POST review API | `fetch("/api/v1/civicaccess/review")` | Pass | Finding rendered as `Needs fixes` | +| Loading/success status | Human-readable state update | `#reviewStatus` updates during and after fetch | Pass | Playwright captured final result text | +| Invalid review API | 422 actionable validation | Missing body returned 422 with `fields: ["body"]` | Pass | `walkthrough-evidence.json` | +| Mobile layout | No horizontal overflow | `document.body.scrollWidth <= window.innerWidth` | Pass | desktop/mobile evidence | + +## Test Assessment + +The current tests prove the public UI is API-wired, returned content is rendered without unsafe HTML injection, review API validation is actionable, schema status works, and readiness remains not-ready until local review storage is configured. The stage walkthrough adds runtime browser evidence on top of the unit/API suite. + +## Recommended Repair Plan + +No immediate repairs required for CivicAccess stage scope. + +## Confidence And Gaps + +High confidence for the local CivicAccess module gate. This walkthrough does not claim suite-level bare-metal installer readiness or cross-module end-to-end packaging readiness. + +## Appendix + +- Screenshot: `docs/qa/civicaccess-stage-2026-06-05/public-desktop.png` +- Screenshot: `docs/qa/civicaccess-stage-2026-06-05/public-mobile.png` +- Evidence JSON: `docs/qa/civicaccess-stage-2026-06-05/walkthrough-evidence.json` +- `python -m pytest -q` - 23 passed. +- `bash scripts/verify-release.sh` - PASSED. diff --git a/docs/qa/civicaccess-standalone-gate-2026-06-06/public-desktop.png b/docs/qa/civicaccess-standalone-gate-2026-06-06/public-desktop.png new file mode 100644 index 0000000..5cfcf1a Binary files /dev/null and b/docs/qa/civicaccess-standalone-gate-2026-06-06/public-desktop.png differ diff --git a/docs/qa/civicaccess-standalone-gate-2026-06-06/staff-after-export-desktop.png b/docs/qa/civicaccess-standalone-gate-2026-06-06/staff-after-export-desktop.png new file mode 100644 index 0000000..cd9dcc0 Binary files /dev/null and b/docs/qa/civicaccess-standalone-gate-2026-06-06/staff-after-export-desktop.png differ diff --git a/docs/qa/civicaccess-standalone-gate-2026-06-06/staff-initial-desktop.png b/docs/qa/civicaccess-standalone-gate-2026-06-06/staff-initial-desktop.png new file mode 100644 index 0000000..7274dfb Binary files /dev/null and b/docs/qa/civicaccess-standalone-gate-2026-06-06/staff-initial-desktop.png differ diff --git a/docs/qa/civicaccess-standalone-gate-2026-06-06/staff-mobile.png b/docs/qa/civicaccess-standalone-gate-2026-06-06/staff-mobile.png new file mode 100644 index 0000000..ea39ac7 Binary files /dev/null and b/docs/qa/civicaccess-standalone-gate-2026-06-06/staff-mobile.png differ diff --git a/docs/qa/civicaccess-standalone-gate-2026-06-06/walkthrough-evidence.json b/docs/qa/civicaccess-standalone-gate-2026-06-06/walkthrough-evidence.json new file mode 100644 index 0000000..ec4d72e --- /dev/null +++ b/docs/qa/civicaccess-standalone-gate-2026-06-06/walkthrough-evidence.json @@ -0,0 +1,85 @@ +{ + "startedAt": "2026-06-06T16:11:11.561Z", + "routes": [ + { + "name": "public-ui", + "route": "/civicaccess", + "status": 200, + "title": "CivicAccess Public Accessibility Support" + }, + { + "name": "staff-ui", + "route": "/civicaccess/staff", + "status": 200, + "title": "CivicAccess Staff Workspace" + }, + { + "name": "staff-mobile", + "route": "/civicaccess/staff", + "status": 200 + } + ], + "actions": [ + { + "workflow": "public-review", + "result": "Sample checks passed\n\nStaff review is still required before publication.\n\nResolve each high-severity finding before publication.\nHave staff or an ADA coordinator review the final publication decision.\nPreserve the review record with the source content and publication package." + }, + { + "workflow": "staff-save-export", + "readiness": "Ready\n\nReview database ready. Saved reviews: 1.", + "contracts": "Contracts published\n\ncivicaccess.publication_accessibility_review.v1, civicaccess.records_export.v1", + "staffStatus": "Review saved\n\n8302bf61-887f-4116-b084-eeec0c6cf7f4: passes-sample-checks", + "queueText": "Council agenda accessibility package\n\npasses-sample-checks; 0 finding(s); en\n\nExport for records\nMain Street water notice\n\npasses-sample-checks; 0 finding(s); en\n\nExport for records", + "exportText": "Records export ready\n\ncivicrecords-ai: checklist-created. Keep the source, rewritten text, reviewer, and publication timestamp with the municipal record." + } + ], + "console": [], + "api": { + "ready": { + "status": "ready", + "ready": true, + "review_database_configured": true, + "review_database_url": "sqlite:///C:\\dev\\Claude\\civicaccess\\.tmp-runtime-data\\civicaccess-reviews.db", + "schema_ready": true, + "schema_version": "2026-06-05-001", + "expected_schema_version": "2026-06-05-001", + "review_count": 2, + "blockers": [] + }, + "contracts": { + "status": "ok", + "module": "civicaccess", + "provides": [ + { + "contract": "civicaccess.publication_accessibility_review.v1", + "endpoint": "/api/v1/civicaccess/review", + "purpose": "Create a persisted accessibility review before publication." + }, + { + "contract": "civicaccess.records_export.v1", + "endpoint": "/api/v1/civicaccess/reviews/{review_id}/records-export", + "target_module": "civicrecords-ai", + "purpose": "Export review provenance and checklist data for records retention." + } + ], + "downstream_ready_for": [ + "civiczone public notice text", + "civicplan policy summaries", + "civicpermit applicant forms", + "civicinspect notices", + "civicgrants public opportunity notices", + "civicprocure RFP and award packets" + ] + }, + "reviewsCount": 2, + "firstReview": { + "review_id": "8302bf61-887f-4116-b084-eeec0c6cf7f4", + "title": "Council agenda accessibility package", + "language": "en", + "status": "passes-sample-checks", + "finding_count": 0, + "created_at": "2026-06-06T16:11:13.117155" + } + }, + "finishedAt": "2026-06-06T16:11:13.914Z" +} \ No newline at end of file diff --git a/docs/qa/civicaccess-standalone-gate-2026-06-06/walkthrough.md b/docs/qa/civicaccess-standalone-gate-2026-06-06/walkthrough.md new file mode 100644 index 0000000..73ed24f --- /dev/null +++ b/docs/qa/civicaccess-standalone-gate-2026-06-06/walkthrough.md @@ -0,0 +1,71 @@ +# CivicAccess Standalone Gate Walkthrough - 2026-06-06 + +Scope: CivicAccess v0.2.0 standalone readiness candidate after local persistence, staff workspace, records-export contract, and CivicSuite installer pin work. + +Runtime: +- Service: `uvicorn civicaccess.main:app --host 127.0.0.1 --port 18101` +- Data: fresh local SQLite database through `CIVICACCESS_DATA_DIR` +- Browser: Playwright Chromium, desktop 1440x1000 and mobile 390x900 +- Evidence file: `docs/qa/civicaccess-standalone-gate-2026-06-06/walkthrough-evidence.json` + +## Routes + +- `/civicaccess`: 200, title `CivicAccess Public Accessibility Support` +- `/civicaccess/staff`: 200, title `CivicAccess Staff Workspace` +- `/civicaccess/staff` mobile viewport: 200 + +## Workflows + +### Public Accessibility Review + +Action: Filled notice title/text, marked images as having alt text, clicked `Run review`. + +Result: UI reached final state `Sample checks passed`, with staff-review and preservation next steps displayed. API-backed review persisted to the local review database. + +### Staff Review Queue + +Action: Opened staff workspace. + +Result: Readiness card displayed `Ready`; contracts card displayed: +- `civicaccess.publication_accessibility_review.v1` +- `civicaccess.records_export.v1` + +### Save Review + +Action: Filled staff publication title/text, marked images as having alt text, clicked `Save review`. + +Result: UI displayed `Review saved` with the generated review id and `passes-sample-checks`; review appeared in the saved review queue. + +### Records Export + +Action: Clicked `Export for records` on a saved review. + +Result: UI displayed `Records export ready` with `civicrecords-ai: checklist-created` and the retention note. API evidence confirms endpoint-level `status=records-export-ready`, `target_module=civicrecords-ai`. + +## API Cross-Check + +`/api/v1/civicaccess/readiness` returned: +- `ready=true` +- `schema_ready=true` +- `review_database_configured=true` +- expected schema version `2026-06-05-001` + +`/api/v1/civicaccess/integration-contracts` returned both required contracts and downstream preparation for CivicZone, CivicPlan, CivicPermit, CivicInspect, CivicGrants, and CivicProcure. + +`/api/v1/civicaccess/reviews` returned the saved reviews created by the browser flow. + +## Visual Check + +Captured: +- `public-desktop.png` +- `staff-initial-desktop.png` +- `staff-after-export-desktop.png` +- `staff-mobile.png` + +No console errors were captured. Desktop and mobile layouts are usable; controls remain visible and readable, and the mobile layout stacks panels without overlap. + +## Findings + +Rollup: 0 Blocker / 0 Critical / 0 Major / 0 Minor / 0 Nit. + +No walkthrough findings remain for this CivicAccess standalone slice. diff --git a/pyproject.toml b/pyproject.toml index 635369a..5ed6d49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "civicaccess" -version = "0.2.0" +version = "0.3.0" description = "civicaccess runtime foundation for accessibility, plain-language, multilingual, and ADA review support." readme = "README.md" requires-python = ">=3.11" @@ -13,12 +13,18 @@ authors = [ { name = "CivicSuite contributors" } ] dependencies = [ - "civiccore==1.1.0", + "civiccore @ https://github.com/CivicSuite/civiccore/releases/download/v1.2.0/civiccore-1.2.0-py3-none-any.whl#sha256=a94ce958e36fb03c8d961e4db4672ce5bcfa25765c57d75886e999cf15703ec7", "fastapi>=0.115.0,<1.0.0", "sqlalchemy>=2.0.0,<3.0.0", "uvicorn[standard]>=0.30.0,<1.0.0", ] +[project.scripts] +civicaccess-db-status = "civicaccess.db_admin:main" + +[tool.hatch.metadata] +allow-direct-references = true + [project.optional-dependencies] dev = [ "build>=1.2.0,<2.0.0", diff --git a/scripts/check-civiccore-placeholder-imports.py b/scripts/check-civiccore-placeholder-imports.py index dc1f6a7..230560b 100644 --- a/scripts/check-civiccore-placeholder-imports.py +++ b/scripts/check-civiccore-placeholder-imports.py @@ -32,7 +32,7 @@ def main() -> int: package = match.group(1) if package in PLACEHOLDERS: failures.append( - f"{path}: civiccore.{package} is a placeholder package in v0.2.0. " + f"{path}: civiccore.{package} is a placeholder package in CivicCore v1.2.0. " "See AGENTS.md section 3.1." ) @@ -49,4 +49,3 @@ def main() -> int: if __name__ == "__main__": sys.exit(main()) - diff --git a/scripts/verify-docs.sh b/scripts/verify-docs.sh index 62fe344..946ed0d 100644 --- a/scripts/verify-docs.sh +++ b/scripts/verify-docs.sh @@ -29,6 +29,7 @@ required=( "civicaccess/__init__.py" "civicaccess/main.py" "civicaccess/access_review.py" + "civicaccess/db_admin.py" "civicaccess/plain_language.py" "civicaccess/multilingual.py" "civicaccess/exports.py" diff --git a/scripts/verify-release.sh b/scripts/verify-release.sh index 4d8b65b..49bb823 100644 --- a/scripts/verify-release.sh +++ b/scripts/verify-release.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -VERSION="0.2.0" +VERSION="0.3.0" find_python() { local candidates=() @@ -40,7 +40,7 @@ ${PYTHON_BIN} - <<'PY' from pathlib import Path import tomllib -version = "0.2.0" +version = "0.3.0" root = Path(".") pyproject = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8")) assert pyproject["project"]["version"] == version, pyproject["project"]["version"] @@ -56,7 +56,7 @@ for path in [ "SECURITY.md", ]: text = (root / path).read_text(encoding="utf-8") - assert "0.2.0" in text, f"missing release version in {path}" + assert "0.3.0" in text, f"missing release version in {path}" assert "0.1.0.dev0" not in text, f"stale dev version in {path}" print("PASS: version surfaces synchronized") PY @@ -81,8 +81,8 @@ from pathlib import Path import hashlib dist = Path("dist") -wheel = dist / "civicaccess-0.2.0-py3-none-any.whl" -sdist = dist / "civicaccess-0.2.0.tar.gz" +wheel = dist / "civicaccess-0.3.0-py3-none-any.whl" +sdist = dist / "civicaccess-0.3.0.tar.gz" assert wheel.exists(), f"missing {wheel}" assert sdist.exists(), f"missing {sdist}" lines = [] diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..bca4dab --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,11 @@ +import pytest + +import civicaccess.main as main_module + + +@pytest.fixture(autouse=True) +def isolated_default_data_dir(monkeypatch, tmp_path): + monkeypatch.setenv("CIVICACCESS_DATA_DIR", str(tmp_path / "civicaccess-data")) + yield + main_module._dispose_review_repository() + main_module._review_db_url = None diff --git a/tests/test_accessibility_foundation.py b/tests/test_accessibility_foundation.py index 32cfc0a..fc2a713 100644 --- a/tests/test_accessibility_foundation.py +++ b/tests/test_accessibility_foundation.py @@ -82,6 +82,22 @@ def test_api_review_success_shape() -> None: assert payload["status"] == "needs-fixes" assert payload["findings"][0]["fix"] assert payload["next_steps"] + assert payload["review_id"] + + +def test_api_review_validation_is_actionable() -> None: + missing = client.post("/api/v1/civicaccess/review", json={"title": "Notice"}) + oversized = client.post( + "/api/v1/civicaccess/review", + json={"title": "Notice", "body": "x" * 5001, "has_alt_text": True, "language": "en"}, + ) + + assert missing.status_code == 422 + missing_detail = missing.json()["detail"] + assert "body" in missing_detail["fields"] + assert "fields array" in missing_detail["fix"] + assert oversized.status_code == 422 + assert "body" in oversized.json()["detail"]["fields"] def test_api_plain_language_and_language_variant() -> None: @@ -164,8 +180,11 @@ def test_public_ui_route_is_accessible_and_honest() -> None: text = response.text assert '' in text assert '
' in text - assert "v0.2.0 corrective demotion state" in text + assert "v0.3.0 standalone readiness candidate" in text assert 'id="runReview"' in text + assert 'fetch("/api/v1/civicaccess/review"' in text + assert "result.replaceChildren()" in text + assert "result.innerHTML" not in text assert "Show empty state" not in text assert "Show error state" not in text assert "Partial review pending" in text @@ -173,3 +192,18 @@ def test_public_ui_route_is_accessible_and_honest() -> None: assert "does not provide legal advice" in text assert "official translation certification" in text assert "zoning" not in text.casefold() + + +def test_staff_ui_route_is_api_wired_and_contract_aware() -> None: + response = client.get("/civicaccess/staff") + + assert response.status_code == 200 + assert "text/html" in response.headers["content-type"] + text = response.text + assert '' in text + assert "Saved review queue" in text + assert 'fetch("/api/v1/civicaccess/readiness")' in text + assert 'fetch("/api/v1/civicaccess/integration-contracts")' in text + assert 'fetch("/api/v1/civicaccess/reviews")' in text + assert 'records-export' in text + assert "result.innerHTML" not in text diff --git a/tests/test_production_depth_review_persistence.py b/tests/test_production_depth_review_persistence.py index c47149d..6a2ec4c 100644 --- a/tests/test_production_depth_review_persistence.py +++ b/tests/test_production_depth_review_persistence.py @@ -1,7 +1,10 @@ +import subprocess +import sys + from fastapi.testclient import TestClient import civicaccess.main as main_module -from civicaccess.access_review import AccessibilityReviewRepository +from civicaccess.access_review import SCHEMA_VERSION, AccessibilityReviewRepository from civicaccess.main import app @@ -32,6 +35,42 @@ def test_accessibility_review_records_persist_findings(tmp_path) -> None: db_path.unlink() +def test_review_repository_records_schema_status(tmp_path) -> None: + db_path = tmp_path / "schema-status.db" + repository = AccessibilityReviewRepository(db_url=f"sqlite:///{db_path}") + try: + status = repository.schema_status() + finally: + repository.engine.dispose() + + assert status.ready is True + assert status.schema_version == SCHEMA_VERSION + assert status.expected_schema_version == SCHEMA_VERSION + assert status.missing_tables == () + assert status.dialect == "sqlite" + + +def test_db_status_cli_reports_ready_schema(tmp_path) -> None: + db_path = tmp_path / "schema-cli.db" + + result = subprocess.run( + [ + sys.executable, + "-m", + "civicaccess.db_admin", + "--db-url", + f"sqlite:///{db_path}", + ], + check=True, + capture_output=True, + text=True, + ) + + assert "CivicAccess schema ready" in result.stdout + assert f"version={SCHEMA_VERSION}" in result.stdout + assert "missing_tables=none" in result.stdout + + def test_api_persists_and_retrieves_review_records(monkeypatch, tmp_path) -> None: db_path = tmp_path / "api-reviews.db" monkeypatch.setenv("CIVICACCESS_REVIEW_DB_URL", f"sqlite:///{db_path}") @@ -57,6 +96,48 @@ def test_api_persists_and_retrieves_review_records(monkeypatch, tmp_path) -> Non db_path.unlink() +def test_default_local_database_makes_readiness_ready(monkeypatch, tmp_path) -> None: + monkeypatch.delenv("CIVICACCESS_REVIEW_DB_URL", raising=False) + monkeypatch.setenv("CIVICACCESS_DATA_DIR", str(tmp_path / "civicaccess-data")) + + try: + response = client.get("/api/v1/civicaccess/readiness") + finally: + main_module._dispose_review_repository() + main_module._review_db_url = None + + assert response.status_code == 200 + payload = response.json() + assert payload["status"] == "ready" + assert payload["ready"] is True + assert payload["review_database_configured"] is True + assert payload["schema_ready"] is True + assert payload["blockers"] == [] + assert "civicaccess-reviews.db" in payload["review_database_url"] + + +def test_readiness_passes_with_configured_schema(monkeypatch, tmp_path) -> None: + db_path = tmp_path / "ready-runtime.db" + monkeypatch.setenv("CIVICACCESS_REVIEW_DB_URL", f"sqlite:///{db_path}") + + try: + response = client.get("/ready") + repository = main_module._get_review_repository() + finally: + main_module._dispose_review_repository() + main_module._review_db_url = None + + assert response.status_code == 200 + payload = response.json() + assert payload["status"] == "ready" + assert payload["ready"] is True + assert payload["review_database_configured"] is True + assert payload["schema_ready"] is True + assert payload["review_count"] == 0 + assert payload["blockers"] == [] + repository.engine.dispose() + + def test_review_record_lookup_reports_missing_record(monkeypatch, tmp_path) -> None: db_path = tmp_path / "missing-review.db" monkeypatch.setenv("CIVICACCESS_REVIEW_DB_URL", f"sqlite:///{db_path}") @@ -72,8 +153,51 @@ def test_review_record_lookup_reports_missing_record(monkeypatch, tmp_path) -> N db_path.unlink() -def test_review_record_lookup_requires_configured_database() -> None: - response = client.get("/api/v1/civicaccess/reviews/not-configured") +def test_review_record_lookup_uses_default_database(monkeypatch, tmp_path) -> None: + monkeypatch.delenv("CIVICACCESS_REVIEW_DB_URL", raising=False) + monkeypatch.setenv("CIVICACCESS_DATA_DIR", str(tmp_path / "default-data")) + + try: + response = client.get("/api/v1/civicaccess/reviews/not-configured") + finally: + main_module._dispose_review_repository() + main_module._review_db_url = None + + assert response.status_code == 404 + assert "Use a review_id returned by POST" in response.json()["detail"]["fix"] + + +def test_review_list_and_records_export_contract(monkeypatch, tmp_path) -> None: + db_path = tmp_path / "api-review-list.db" + monkeypatch.setenv("CIVICACCESS_REVIEW_DB_URL", f"sqlite:///{db_path}") + + try: + create_response = client.post( + "/api/v1/civicaccess/review", + json={ + "title": "Budget hearing notice", + "body": "Residents may ask for help before the hearing.", + "has_alt_text": True, + "language": "en", + }, + ) + review_id = create_response.json()["review_id"] + list_response = client.get("/api/v1/civicaccess/reviews") + export_response = client.post(f"/api/v1/civicaccess/reviews/{review_id}/records-export") + contracts_response = client.get("/api/v1/civicaccess/integration-contracts") + finally: + main_module._dispose_review_repository() + main_module._review_db_url = None - assert response.status_code == 503 - assert "Set CIVICACCESS_REVIEW_DB_URL" in response.json()["detail"]["fix"] + assert create_response.status_code == 200 + assert list_response.status_code == 200 + assert list_response.json()["reviews"][0]["review_id"] == review_id + assert export_response.status_code == 200 + export_payload = export_response.json() + assert export_payload["status"] == "records-export-ready" + assert export_payload["target_module"] == "civicrecords-ai" + assert export_payload["provenance"]["source_text_preserved"] is True + contracts = contracts_response.json() + assert contracts["status"] == "ok" + assert "civicpermit applicant forms" in contracts["downstream_ready_for"] + db_path.unlink() diff --git a/tests/test_runtime_foundation.py b/tests/test_runtime_foundation.py index 61553b4..79ec33b 100644 --- a/tests/test_runtime_foundation.py +++ b/tests/test_runtime_foundation.py @@ -1,14 +1,30 @@ from fastapi.testclient import TestClient +import tomllib +from pathlib import Path import civicaccess from civicaccess.main import app client = TestClient(app) +ROOT = Path(__file__).resolve().parents[1] def test_package_version_is_020() -> None: - assert civicaccess.__version__ == "0.2.0" + assert civicaccess.__version__ == "0.3.0" + + +def test_pyproject_uses_published_civiccore_release_wheel() -> None: + data = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8")) + dependencies = data["project"]["dependencies"] + + assert data["tool"]["hatch"]["metadata"]["allow-direct-references"] is True + assert ( + "civiccore @ https://github.com/CivicSuite/civiccore/releases/download/" + "v1.2.0/civiccore-1.2.0-py3-none-any.whl#sha256=a94ce958e36fb03c8d961e4db4672ce5bcfa25765c57d75886e999cf15703ec7" + ) in dependencies + assert "civiccore==1.1.0" not in dependencies + assert "civiccore==1.0.0" not in dependencies def test_root_endpoint_states_runtime_boundary() -> None: @@ -17,11 +33,11 @@ def test_root_endpoint_states_runtime_boundary() -> None: payload = response.json() assert payload["name"] == "CivicAccess" - assert payload["version"] == "0.2.0" - assert payload["status"] == "corrective demotion state" - assert "database-backed review records" in payload["message"] + assert payload["version"] == "0.3.0" + assert payload["status"] == "standalone readiness candidate" + assert "staff interfaces" in payload["message"] assert "does not provide legal advice" in payload["message"] - assert payload["next_step"].startswith("Use CivicAccess for local review support only") + assert "/civicaccess/staff" in payload["next_step"] def test_health_endpoint_reports_versions() -> None: @@ -31,5 +47,5 @@ def test_health_endpoint_reports_versions() -> None: assert payload["status"] == "ok" assert payload["service"] == "civicaccess" - assert payload["version"] == "0.2.0" - assert payload["civiccore_version"] == "1.1.0" + assert payload["version"] == "0.3.0" + assert payload["civiccore_version"] == "1.2.0"