docs backend evidence community safety CS09B_CONSENT_STORE - AlexandreZanata/Abastevo GitHub Wiki
CS09B — Training-consent persistence (migration + guarded store)
Date: 2026-10-11. Status: LOCAL_DONE. Scope: migration, sqlc
queries, pg store and real-PostGIS tests only — no HTTP/routes/
OpenAPI (CS09C), no worker/S3 purge (CS10), no VPS/APK/dependency
change. Head: maintained dev + this task.
Plan: phased plan
(CS09, B-BR-CS06), CS09A contract. Rules:
B-BR-CS06, BUC-CS05.
Validation: PASS
Schema (append-only 000056_privacy_training_consent.sql)
privacy_training_consents:contributor_idTEXT PK (singleton per owner),contributor_ref, purposeCHECK = 'TRAINING',terms_version/terms_digest(digest 1–128 chars), stateCHECK IN ('GRANTED','REVOKED'),revision BIGINT CHECK >= 1,granted_at/revoked_at/decided_at. No DELETE path, no sibling table touched; absent rows deny by default at the application layer.
Store (privacy/adapters/consent_store.go, sqlc consent.sql)
- Separate
ConsentStorestruct (own pool wiring, likeHelpfulnessStore): the sharedStorealready owns a conflictingGetsignature, so the singleton gets its own type. Compile-time assertsapplication.ConsentStore. GetTrainingConsent+UpsertTrainingConsent(single guardedON CONFLICT ... DO UPDATE ... WHERE excluded.revision > stored.revision). A no-row upsert resolves against the stored row: identical retries converge (replayed), divergent same-revision writes conflict, smaller revisions refuse stale — a racing stale offline ON can never undo a revocation. Owner reads share one not-found shape (no oracle).- Regenerated with pinned
sqlc v1.31.1(vetclean,generateidempotent); stanza lists000056explicitly per ownership rules.
Validation (actual, disposable local PostGIS on 127.0.0.1:5434)
TestConsentGrantPersistsAndReadsBack: exact round trip.TestConsentRevisionGuardConverges: identical replays, divergent conflicts, older is stale, greater revocation wins (state REVOKED rev 3).TestConsentConcurrentUpsertsAgree(10 revisions): surviving row keeps an exact revision/instant pair.TestConsentOwnerIsolation: absent/foreign read as not found.TestConsentApplicationFlowOnPostgres: real-store application flow — grant 1 → status granted → revoke 2 (purge signal) → stale grant at 2 refuses → selection denied.TestConsentUpgradeAppliesOnly000056: chain without 000056, seed one export request, then exactly["000056"]applies; table present, history intact; replay idempotent.TestConsentQueriesStayNonDestructive: one guarded upsert, zero DELETE/TRUNCATE/DROP/ALTER, no cross-table references, revision guard pinned.- All 7 pass with
-count=1under-race. Full./internal/modules/privacy/...unit + integration suites green (no regressions).go vet(both tag sets),gofmt -l,git diff --checkclean;scan-secrets.shpassed. Only disposable local test databases used.
Limits / owed (CS09C and later)
- Signed owner grant/revoke/read HTTP + OpenAPI/apicontract: CS09C.
- Promotion gate worker, 24-hour physical-deletion proof, restore drill, erasure fan-out: CS10. Collection stays OFF.
- Restricted DB roles: P08 provisioning (query-level owner scoping done here, same reservation as CS02A).
- Per-image inventory rows arrive with promotion (CS10); no stored image scope in this task by design.
Next microtask: CS09C — consent signed HTTP + OpenAPI.