Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / db/migrations/0082_provenance_by_source.sql |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | db/migrations/0082_provenance_by_source.sql |
| Original SHA-256 | 5250f69e897783904ad4d0dae650873293c76ccf2ac72d9915f8b473bd1ca148 |
| Git blob SHA | 6d63e33582dc78502f7b3a6dd31bb7e5d0e886f2 |
| Reading-copy SHA-256 | 5250f69e897783904ad4d0dae650873293c76ccf2ac72d9915f8b473bd1ca148 |
Recovered source transcript
Download complete source text ↓134 lines · 7.4 KB
-- +goose Up
-- PROVENANCE FOR opportunity_documents IS BOUND FROM THE SOURCE, not from the table.
--
-- Migration 0081 bound provenance per TABLE, which was right for five of its six tables and wrong for this
-- one. opportunity_documents holds BOTH a customer's own uploaded package AND published federal attachments
-- pulled from sam.gov, and those are not the same kind of data:
--
-- an uploaded PWS is the customer's private work product and must never leave the local plane
-- a sam.gov attachment is a published US Government document, already public, and the external
-- normalization lane exists precisely for text like it
--
-- Binding every row to tenant_upload made the public ones unusable by the lane built for them. Binding every
-- row to public_cache would be catastrophic. So the binding derives from source_type, which is the column
-- that already states where the row came from.
--
-- THE PROPERTY THAT MUST SURVIVE, and this is the whole point of provenance at origin: A CALLER STILL
-- CANNOT DECLARE PROVENANCE. It is computed, never accepted. What a caller can now influence is
-- source_type, which is a different and much more visible lie: it is a documented column with a CHECK
-- constraint, it appears in every audit entry, and the binding sites test enumerates the code paths allowed
-- to set the public values. Fabricating provenance therefore still requires touching a named location, it is
-- simply a different named location than before.
--
-- THE HONEST RESIDUAL RISK, written down rather than discovered later: application code that inserted a
-- tenant document with source_type 'sam_attachment' would produce a public_cache row. Nothing in the
-- database can distinguish that from a real attachment. The mitigation is that exactly one binary writes
-- that source_type, cmd/sam-attachment-sync, and the test asserts it.
-- +goose StatementBegin
CREATE OR REPLACE FUNCTION provenance_from_document_source() RETURNS trigger AS $$
DECLARE
born text;
BEGIN
-- The mapping is EXHAUSTIVE and its default is the restrictive one. A source_type nobody has thought
-- about yet becomes tenant_upload, which fails closed: the worst outcome is that a public document is
-- treated as private and cannot be sent to an external model, which is an inconvenience. The reverse
-- default would be a disclosure.
born := CASE
WHEN NEW.source_type IN ('sam_attachment', 'url_import') THEN 'public_cache'
ELSE 'tenant_upload'
END;
IF TG_OP = 'INSERT' THEN
NEW.provenance := born;
RETURN NEW;
END IF;
-- Provenance remains IMMUTABLE, and so does the source it is derived from. Allowing source_type to be
-- updated would reintroduce exactly the hole this design closes: a row could be inserted as an upload
-- and later relabelled an attachment, laundering a customer's document into the public class.
IF NEW.provenance IS DISTINCT FROM OLD.provenance THEN
RAISE EXCEPTION 'provenance is immutable: % cannot become % on table %',
OLD.provenance, NEW.provenance, TG_TABLE_NAME;
END IF;
IF NEW.source_type IS DISTINCT FROM OLD.source_type THEN
RAISE EXCEPTION 'source_type is immutable on %: % cannot become %, because provenance derives from it',
TG_TABLE_NAME, OLD.source_type, NEW.source_type;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- +goose StatementEnd
-- The trigger name is the one 0081 ACTUALLY created, read from pg_trigger. Guessing it wrong left the old
-- trigger attached, and the reclassify UPDATE below was then refused by it with "provenance is immutable",
-- which rolled the whole migration back. A correct rollback, and a wasted attempt that a two second query
-- would have avoided.
DROP TRIGGER IF EXISTS trg_opportunity_documents_provenance ON opportunity_documents;
DROP TRIGGER IF EXISTS trg_provenance_opportunity_documents ON opportunity_documents;
-- THE ORDER BELOW IS LOAD BEARING. The one time reclassification runs while NO trigger is attached,
-- because the trigger's whole job is to refuse a provenance change, and the reclassify IS a provenance
-- change. Creating the trigger first made the migration refuse itself, which it did correctly and which
-- cost a second rolled back attempt. Trigger goes on AFTER the data is correct, and from that moment the
-- column can never move again.
-- The CHECK from 0081 pinned this table to a single value. Widen it to the two the mapping can produce, and
-- no further: the column still cannot hold curated_card or tenant_record.
ALTER TABLE opportunity_documents DROP CONSTRAINT IF EXISTS ck_opportunity_documents_provenance;
ALTER TABLE opportunity_documents ADD CONSTRAINT ck_opportunity_documents_provenance
CHECK (provenance IN ('tenant_upload', 'public_cache'));
-- Existing rows are RECLASSIFIED to what the mapping says they should always have been. This is a one time
-- correction of rows written before the mapping existed, not a licence to update provenance later; the
-- trigger above refuses any subsequent change.
UPDATE opportunity_documents SET provenance = 'public_cache'
WHERE source_type IN ('sam_attachment', 'url_import') AND provenance <> 'public_cache';
CREATE TRIGGER trg_opportunity_documents_provenance
BEFORE INSERT OR UPDATE ON opportunity_documents
FOR EACH ROW EXECUTE FUNCTION provenance_from_document_source();
-- CHUNKS INHERIT FROM THEIR DOCUMENT, for the same reason. A chunk of a published federal attachment is
-- public; a chunk of a customer's PWS is not. 0081 bound every chunk to tenant_upload, which would have
-- made extracted public text unusable by the lane that exists for it.
-- +goose StatementBegin
CREATE OR REPLACE FUNCTION provenance_from_parent_document() RETURNS trigger AS $$
DECLARE
parent text;
BEGIN
IF TG_OP = 'INSERT' THEN
SELECT provenance INTO parent FROM opportunity_documents WHERE id = NEW.document_id;
-- A chunk whose parent cannot be found fails CLOSED rather than defaulting to public.
NEW.provenance := COALESCE(parent, 'tenant_upload');
RETURN NEW;
END IF;
IF NEW.provenance IS DISTINCT FROM OLD.provenance THEN
RAISE EXCEPTION 'provenance is immutable: % cannot become % on table %',
OLD.provenance, NEW.provenance, TG_TABLE_NAME;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- +goose StatementEnd
DROP TRIGGER IF EXISTS trg_opportunity_document_chunks_provenance ON opportunity_document_chunks;
DROP TRIGGER IF EXISTS trg_provenance_opportunity_document_chunks ON opportunity_document_chunks;
ALTER TABLE opportunity_document_chunks DROP CONSTRAINT IF EXISTS ck_opportunity_document_chunks_provenance;
ALTER TABLE opportunity_document_chunks ADD CONSTRAINT ck_opportunity_document_chunks_provenance
CHECK (provenance IN ('tenant_upload', 'public_cache'));
UPDATE opportunity_document_chunks c SET provenance = d.provenance
FROM opportunity_documents d
WHERE d.id = c.document_id AND c.provenance <> d.provenance;
CREATE TRIGGER trg_opportunity_document_chunks_provenance
BEFORE INSERT OR UPDATE ON opportunity_document_chunks
FOR EACH ROW EXECUTE FUNCTION provenance_from_parent_document();
-- +goose Down
DROP TRIGGER IF EXISTS trg_opportunity_document_chunks_provenance ON opportunity_document_chunks;
DROP TRIGGER IF EXISTS trg_opportunity_documents_provenance ON opportunity_documents;
DROP FUNCTION IF EXISTS provenance_from_parent_document();
DROP FUNCTION IF EXISTS provenance_from_document_source();