Skip to content

fix(server): scope observations to their workspace and make provenance pins indestructible #1505

Description

@FelixTJDietrich

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

  • No query in ObservationRepository runs without a bound workspace parameter, enforced by test
    rather than by an annotation bypass.
  • No foreign key from a workspace-scoped table to a global table carries ON DELETE CASCADE,
    asserted over information_schema.
  • Retiring or deleting a practice leaves every existing observation readable, pinned and attributable.
  • Workspace purge and SCM/conversation erasure still remove all observations for the workspace.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    application-serverSpring Boot server: APIs, business logic, databasepriority:criticalDrop everything - Loss of functionality or datarefactorCode restructuring without changing behaviorsecurityAuthentication, authorization, vulnerability fixes

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions