Schema and Read-Model Contract Lock
Status
Implemented in OP-081.
Related Requirements
- Phase 1 journey hardening requires schema drift to fail before deploy.
- Agent onboarding-to-asset visibility depends on
assets,approved_inventory,agent_enrollment_tokens, andagent_identitiesstaying aligned across SQLite local tests and Postgres migrations. - Console-visible read models must preserve API shapes and aliases consumed by the frontend.
Problem Statement
Recent deployed validation found bugs where a route or component looked correct in isolation but the production-shaped read model failed because SQLite schema, Postgres migrations, and console expectations had drifted. This caused production-only surprises such as missing columns or aliases in asset reads.
Architectural Intent
OP-081 adds a cheap, always-on guardrail before broader deployed smoke probes. It does not replace Postgres runtime validation or persona journeys; it catches the earliest class of table/column and alias drift that should never reach AWS dev.
What Was Implemented
- Added
scripts/validate_schema_read_models.py. - Introspects SQLite after
init_db(). - Statically parses
db/postgres/*.sqlforCREATE TABLEand additiveALTER TABLE ... ADD COLUMN IF NOT EXISTSmigrations. - Fails when the initial critical tenant/asset/agent table set drifts by column name between SQLite and Postgres migrations.
- Fails closed when critical-table migrations use unsupported shape-changing DDL such as column rename, drop, or type-change operations until the parser is extended to model them.
- Reports type-class warnings such as SQLite text timestamps versus Postgres
timestamptz. - Pins whitespace/case-tolerant asset read-model tokens including
approved_by AS owner,mac_addresses_json,network_interfaces_json,open_ports_json,agent_id,asset_type,source_confidence, andapproved_inventory. - Added
make validate-read-model-contracts. - Wired the guardrail into CI
backend_validateand full validation. - Added
tests/test_schema_read_model_contracts.pysomake test-sqlitealso exercises the guardrail.
Components Involved
scripts/validate_schema_read_models.pytests/test_schema_read_model_contracts.pypoc/ingest_api/database.pydb/postgres/*.sqlpoc/ingest_api/asset_read_model.pyMakefile.gitlab-ci.ymlscripts/ci/validate.sh
APIs / Events / Schemas
No API, event, OpenAPI, or AsyncAPI contracts changed. This is a validation guardrail for runtime persistence and console-facing read models.
Security / Tenant Isolation
The first locked table set covers tenant, role, asset, approved inventory, agent enrollment token, and agent identity tables that form the tenant-scoped agent onboarding-to-asset journey. The guardrail helps prevent tenant-scoped read models from silently diverging between local tests and Postgres-backed AWS dev.
Validation Steps
make validate-read-model-contracts
make test-sqlite
The full branch validation also runs:
make validate-contracts
make typecheck-python
make lint
make docs-build
git diff --cached --check
Known Limitations
- The first OP-081 table set is intentionally narrow: tenant, asset, approved inventory, enrollment token, and agent identity tables.
- Static Postgres DDL parsing is not a substitute for a live Postgres
information_schemacomparison. It is the always-on gate that runs in local and CI contexts without requiring a live database. - Alerts, tickets, compliance, delivery, SCIM, Intune, and discovery read models should be added as their persona journeys are hardened under OP-083, OP-084, and OP-085.
Acceptance Criteria Mapping
| Acceptance criterion | Evidence |
|---|---|
| Critical SQLite/Postgres tables are checked | scripts/validate_schema_read_models.py compares SQLite init_db() against parsed Postgres migrations |
| Schema drift fails before deploy | make validate-read-model-contracts runs in backend_validate and full validation |
| Asset read-model aliases are pinned | The validator checks poc/ingest_api/asset_read_model.py for critical tokens including approved_by AS owner |
| Guardrail is test-backed | tests/test_schema_read_model_contracts.py runs under make test-sqlite |