feat(citycore): Phase A hardening — Postgres default, write authz, audit, backup/restore (v0.4.0) - #9
Merged
Merged
Conversation
…dit, backup/restore (v0.4.0) Bring CivicAccess to city-core readiness in its own repo by mirroring the shipped CivicNotice no-AI pattern. Closes probe gaps #2 (authz), #3 (audit), #4 (backup/restore) and defaults persistence to the shared CivicCore PostgreSQL. Cuts v0.4.0. Persistence (#Postgres default): - Read the supervisor's DATABASE_URL and derive a sync psycopg2 URL (_sync_database_url); CIVICACCESS_REVIEW_DB_URL overrides; SQLite is now an explicit dev fallback, not the default. - Move psycopg2-binary to a runtime dependency. - Rename the migration id to civicaccess-windows-local-state-v1. - Mandatory Postgres release gate: verify-release.sh + CI require CIVICACCESS_POSTGRES_TEST_URL (postgres:16 service); tests/test_postgres_persistence.py. Authz (#2): - Trusted-write guard on POST /review and POST /reviews/{id}/records-export (CIVICACCESS_TRUSTED_WRITE_TOKEN + X-CivicAccess-Write-Token): 403 missing/invalid, 503 fail-closed. - New stateless public POST /analyze (no persistence, no token); public page uses it so the public surface can no longer write city records. - Staff page carries the server-rendered token and sends it on save/export. Audit (#3): - audit_events table + record_audit_event; review.create written in the same transaction as the review, review.records_export on export. Backup/restore (#4): - Round-trip test proving review + audit data survive a Data-directory backup and restore. Version bumped 0.3.0 -> 0.4.0 across code, docs, and the release gate. PROBE-PROGRESS.md added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…obustness, coverage, honesty) Adversarial verification panel (4 lenses) found 0 Blocker / 1 Critical / 2 Major / 6 Minor. All resolved: Critical — trusted-write secret was embedded in the staff page served over an unauthenticated GET: - Removed server-rendered token from render_staff_page; the operator now pastes the token into a field kept in sessionStorage and sent only as X-CivicAccess-Write-Token on save/export. - Added test_staff_page_never_leaks_the_write_token (asserts the env secret never appears in the page). Major — Postgres-default selection and the async->sync conversion had zero coverage: - Added tests/test_database_url_selection.py: _sync_database_url scheme flip + credential/marker preservation + sqlite passthrough; _review_database_url precedence (override > DATABASE_URL > SQLite). Major — backup/restore proof only covered the SQLite dev fallback, not the Postgres default store: - Added test_postgres_review_and_audit_survive_reconnect (default-store durability across a fresh engine); scoped/reworded the SQLite test (backup taken while live) and the gap #4 evidence. Minor: - Constant-time token comparison (hmac.compare_digest). - _sync_database_url now rewrites only the URL scheme via sqlalchemy make_url (robust to passwords/ db names containing scheme-marker substrings). - CHANGELOG no longer overclaims the records_export audit is in the review transaction. - Removed the discredited "v1.0.0" label from four module docstrings; verify-release.sh now greps civicaccess/*.py to keep it out. Full verify-release.sh PASSED against real Postgres: 45 passed, docs/placeholder/ruff gates, 0.4.0 wheel+sdist build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase A of the CivicAccess → city-core integration plan (CivicSuite/civicsuite
docs/roadmap/civicaccess-citycore-integration/). Module-repo work only — no umbrella/desktop/installer changes (those are Phases B–D). Mirrors the shipped CivicNotice no-AI city-core pattern.Closes probe gaps (see
PROBE-PROGRESS.md)CIVICACCESS_TRUSTED_WRITE_TOKEN+X-CivicAccess-Write-Token) onPOST /reviewandPOST /reviews/{id}/records-export: 403 missing/invalid, 503 fail-closed when unconfigured. New stateless publicPOST /analyze(no persistence, no token); the public page uses it so the public surface can no longer write city records.audit_eventstable;review.createwritten atomically with the review,review.records_exporton export.Persistence
DATABASE_URL(asyncpg) → derive sync psycopg2 URL.CIVICACCESS_REVIEW_DB_URLoverrides; SQLite is now an explicit dev fallback.psycopg2-binarymoved to a runtime dependency. Migration id →civicaccess-windows-local-state-v1.verify-release.sh+ CI requireCIVICACCESS_POSTGRES_TEST_URL(postgres:16service) so PG coverage can't be skipped.Verification
verify-release.shPASSED locally against a real PostgreSQL (34 passed incl. the PG persistence test; docs gate, placeholder gate, ruff, 0.4.0 wheel+sdist build).🤖 Generated with Claude Code