Skip to content

feat(ogc): filter ogc_* views to public records (BDMS-971 A1)#782

Open
ksmuczynski wants to merge 7 commits into
stagingfrom
kas-bdms-971-apply-public-filter-to-all-OGC-views
Open

feat(ogc): filter ogc_* views to public records (BDMS-971 A1)#782
ksmuczynski wants to merge 7 commits into
stagingfrom
kas-bdms-971-apply-public-filter-to-all-OGC-views

Conversation

@ksmuczynski

Copy link
Copy Markdown
Contributor

Summary

Restricts every ogc_* view/materialized view behind the public,
unauthenticated /ogcapi mount to release_status = 'public', and flips the 56 project_areas rows (currently all draft) to public, so the newly-filtered ogc_project_areas view isn't left returning zero rows.

Why

This PR addresses the following problem / context:
None of the 22 ogc_* relations filtered on release_status. They selected it, but never gated on it. Confirmed against live data: water_wells alone was serving 1,145 private + 140 draft rows alongside 8,678 public ones, and project_areas was 100% draft (56/56).

Changes

Implementation summary - the following was changed / added / removed:

  • Public release_status filter: new Alembic migration adds AND release_status = 'public' to all 21 existing ogc_* relations, and introduces ogc_locations (previously the locations collection pointed straight at the raw location table with no filter at all). ogc_actively_monitored_wells gets no predicate of its own — it already inherits public-only rows transitively through its join to ogc_water_well_summary. Fully reversible: downgrade() restores the byte-identical unfiltered SQL that was in production (ogc_locations is the one exception. I didn't exist pre-migration, so downgrade drops it instead).
  • project_areas publish migration: companion data migration (run via oco data-migrations run, not Alembic) flips the 56 project_areas group rows from draft to public. Forward-only by design, gated on this PR's schema revision having landed first.
  • Shared test fixtures: required prerequisite: flips the shared location/water_well_thing/group fixtures from draft to public, since 9 existing tests assert those rows are visible through ogc_* responses.
  • Behave coverage: new scenarios for the @A1-tagged cases in ogc-cleanup-sprint1.feature (migration application, downgrade reversibility, already-consistent layers, project_areas count).
  • data_migrations packaging: pyproject.toml's packages list was missing data_migrations, so the installed oco CLI couldn't import it at all. Pre-existing gap, unrelated to this ticket's logic, found while verifying oco data-migrations run.
  • Hardcoded test group id: test_get_project_area assumed its fixture's Group would always get database id 1, which only held because nothing else in the suite created a Group first. Surfaced by this PR's new data-migration test.

Verification

  • Full pytest suite: 716 passed, 0 failed.
  • alembic downgrade -1 / upgrade head cycle on a local test DB, with psql inspection confirming the filter predicate lands in the right place (including the innermost CTE for materialized views, not the outer query, per the DDL-ordering constraint for ogc_actively_monitored_wellsogc_water_well_summary).
  • oco data-migrations run/status round-trip for the project_areas migration.
  • behave --tags=@A1 — 5/5 scenarios passing.
  • Manual smoke test against /ogcapi/collections/*/items.

Notes

Any special considerations, workarounds, or follow-up work to note?

  • Layer visibility (which collections are published) is unchanged. That's A16/A17/A18, separate tickets.
  • Before merging: confirm the 56 project_areas rows this PR publishes are actually appropriate for public release — the data migration trusts that this review is where that confirmation happens.
  • Deploy-time check (not verifiable locally): confirm SELECT release_status, count(*) FROM "group" WHERE project_area IS NOT NULL GROUP BY release_status reads draft: 56 before and public: 56 after, on the real target database.
  • A11 (authenticated /ogcapi-internal mount for staff who need unfiltered access) is a separate ticket/PR/branch that depends on this one merging first.

test_get_project_area assumed its "Test Group Foo" fixture would always get database id 1, which only held because nothing else in the suite created a Group row first. Any earlier-running test that inserts a Group (e.g. a data-migration test) shifts the sequence and breaks this test with an unrelated 404.

Use the fixture's actual group.id instead of the literal 1.
pyproject.toml's explicit setuptools packages list omitted data_migrations, so the installed `oco` CLI couldn't import it at all -- `oco data-migrations status/run` failed with ModuleNotFoundError for every migration, not just new ones. Running via `uv run pytest`/`python -c` masked this because pytest prepends the repo root to sys.path; the installed console-script entry point does not.
The upcoming ogc_* view filter excludes non-public records, and nine currently-passing tests in test_ogc.py assert that rows backed by the shared location/water_well_thing/group fixtures ARE present in ogc_* responses. Their "draft" default was an arbitrary safe value, not a deliberate test input -- confirmed by grepping the suite for anything that depends on it being specifically "draft".

Flips the three fixtures plus one inline Group(...)construction in test_ogc.py that bypassed the fixture.
Every ogc_* view/materialized view selected release_status but never filtered on it, so the unauthenticated /ogcapi endpoints have been serving private and draft records alongside public ones (e.g. water_wells: 1,145 private + 140 draft rows next to 8,678 public).

Adds "AND release_status = 'public'" to all 21 existing ogc_* relations and introduces ogc_locations (previously the locations collection pointed straight at the raw location table, which has no filter at all). ogc_actively_monitored_wells gets no predicate of its own -- it already inherits public-only rows transitively through its join to ogc_water_well_summary; adding a redundant filter on status_history.release_status would repeat a mistake already made and reverted once (see w1x2y3z4a5b6), since that column is never populated by the transfer scripts.

Fully reversible: downgrade() rebuilds the same relations with the byte-identical unfiltered SQL that was in production before this migration (ogc_locations is the exception, since it didn't exist pre-migration -- downgrade drops it rather than recreating it unfiltered).

Repoints core/pygeoapi-config.yml's locations provider at the new view in the same commit, since shipping the view without the config change (or vice versa) leaves the collection either unfiltered or broken.

Layer visibility (which collections are published)is unchanged. That's separate, out-of-scope work.
ogc_project_areas is now filtered to public records, but all 56 current project_area-bearing group rows are release_status=draft (confirmed in docs/ogc-layer-audit.md), which would make the layer return zero rows until someone flips them.

Uses the data_migrations framework (not Alembic, which is schema-only in this repo) so the change is tracked, skip-if-applied, and gated on this ticket's schema revision having landed first. Runs independently via `oco data-migrations run 20260714_0001_publish_project_areas` -- deliberately not swept in via run-all, which could also apply other unrelated pending migrations. No downgrade: this framework is forward-only, and a blanket revert couldn't tell rows this migration published apart from ones published independently afterward -- undoing it later means writing a new, deliberate migration instead.

This is a genuine publication decision, not a mechanical schema change -- confirm the 56 rows are actually appropriate for public release before merging.
Covers the migration-application, downgrade-reversibility, already-consistent-layers, and project_areas scenarios in ogc-cleanup-sprint1.feature tagged @A1. The other ~10 tickets sharing that feature file have no steps yet and stay undefined, per plan.

Tags @production only on these specific scenarios (the feature file has no feature-level tag since ~20 other tickets' scenarios share it) and @migration-mutates-schema on the three that call alembic downgrade, wiring a before/after_scenario hook in environment.py that unconditionally restores head so a downgrade left mid-scenario can't leak into later scenarios sharing this database.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants