Outcome
Observation rows carry their own tenant, enforced by the same machinery as every other tenant-scoped
table, and the provenance pin that ties an observation to the practice revision that produced it cannot be
destroyed.
Problem
Two defects on today's schema, independent of any catalogue restructure.
1. observation has no tenancy column of its own. It is scoped transitively through
Practice.workspace, and ObservationRepository carries a type-level @WorkspaceAgnostic. That
opens the bypass for every method on the repository: WorkspaceStatementInspector never inspects
observation SQL and the architecture tests skip filter validation. Isolation on the most
tenant-sensitive table in the schema rests on the convention that each hand-written query remembers to
join practice and predicate on its workspace_id. feedback already carries a plain workspace_id
scalar — the delivery side does the right thing while the observation side does not.
2. The provenance pin is destructible. observation.practice_revision_id is nullable with
ON DELETE SET NULL, and observation.practice_id is ON DELETE CASCADE. Deleting a practice destroys
the measurement record it produced. A pin you can lose is not a pin.
What
- Add
observation.workspace_id, backfill it from practice.workspace_id, and delete the type-level
@WorkspaceAgnostic on ObservationRepository so the existing statement inspector and architecture
tests cover it.
- Make
practice_revision_id NOT NULL with RESTRICT, and drop the practice → observation cascade.
- Keep workspace purge and erasure working: they must still remove every observation for the workspace,
now through the explicit column.
Acceptance criteria
Out of scope
- Catalogue-scoped practice identity, adoption rows, and the reconciler. Those stay in the parent epic;
this issue is a security and integrity fix that depends on none of them.
Split out of #1445 so it can ship independently. Related: #1372.
Outcome
Observation rows carry their own tenant, enforced by the same machinery as every other tenant-scoped
table, and the provenance pin that ties an observation to the practice revision that produced it cannot be
destroyed.
Problem
Two defects on today's schema, independent of any catalogue restructure.
1.
observationhas no tenancy column of its own. It is scoped transitively throughPractice.workspace, andObservationRepositorycarries a type-level@WorkspaceAgnostic. Thatopens the bypass for every method on the repository:
WorkspaceStatementInspectornever inspectsobservation SQL and the architecture tests skip filter validation. Isolation on the most
tenant-sensitive table in the schema rests on the convention that each hand-written query remembers to
join
practiceand predicate on itsworkspace_id.feedbackalready carries a plainworkspace_idscalar — the delivery side does the right thing while the observation side does not.
2. The provenance pin is destructible.
observation.practice_revision_idis nullable withON DELETE SET NULL, andobservation.practice_idisON DELETE CASCADE. Deleting a practice destroysthe measurement record it produced. A pin you can lose is not a pin.
What
observation.workspace_id, backfill it frompractice.workspace_id, and delete the type-level@WorkspaceAgnosticonObservationRepositoryso the existing statement inspector and architecturetests cover it.
practice_revision_idNOT NULLwithRESTRICT, and drop the practice → observation cascade.now through the explicit column.
Acceptance criteria
ObservationRepositoryruns without a bound workspace parameter, enforced by testrather than by an annotation bypass.
ON DELETE CASCADE,asserted over
information_schema.Out of scope
this issue is a security and integrity fix that depends on none of them.
Split out of #1445 so it can ship independently. Related: #1372.