fix(consent): order durable outbox tail by insert time - #123
cursor[bot] wants to merge 25 commits into
Conversation
Reject a grant-only snapshot after a stored revocation by locking the participant ledger and requiring the durable event tail to match the envelope before enqueue. Record the Active PR composition in traceability, changelog, ERD, UML, and as-built schema. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Exercise missing ledger or event rows, time and identity mismatch, timestamp overflow, and dropped relations so the tail lock stays at full statement and branch coverage. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Same-millisecond grant/revoke pairs are legal. Ordering the durable tail by event_ref DESC let a lexicographically later grant hide a stored research revocation. Require the submitted ledger to contain every durable event and order the tail by occurrence time, created_at, then event_ref. Do not merge #70 or #112. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Record GDPR, NIST Privacy Framework, Helsinki, outbox, and clock_timestamp references. #120 orders the tail by created_at but does not require the submitted ledger to contain every durable event. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
There was a problem hiding this comment.
Stale comment
Review — #123
6643041Durable-set completeness is the right successor to #120: after
FOR UPDATE, every storedconsent_eventidentity must appear in the submitted ledger, so a grant-only snapshot cannot enqueue beside a later revoke even whenevent_ref DESCwould pick the grant.Residual hole remains: the tail query still ends with
event_ref DESC. A complete ledger plus stale grant envelope still succeeds when two rows shareoccurred_at_unix_msandcreated_atand the grant identity sorts last. Fail closed when more than one row ties for max(occurred_at_unix_ms, created_at)instead of using opaque identity as a correctness key.Do not merge #70, #112, or #120. Do not self-approve. Prefer a successor that keeps this completeness check and rejects an ambiguous physical tail.
Sent by Cursor Automation: Fix Issues
There was a problem hiding this comment.
Review — #123 6643041
Durable-set completeness is correct: after consent_ledger FOR UPDATE, every stored consent_event identity must appear in the submitted ledger, so a grant-only snapshot cannot enqueue beside a later same-millisecond revoke.
Residual hole remains on this head: the tail still ends with event_ref DESC. When two rows share occurred_at_unix_ms and created_at, a complete ledger plus grant envelope can still bind the grant. That fail-closed unique-tail contract is #134 (729a2bd), not this SHA.
Do not merge #70 (3180620), #112 (040bcf7), #120 (3f72446), or this #123 head while #134 is the tighter successor. Independent non-author approval and green required checks are still required on the landing SHA. Do not self-approve.
Next after the landing consent/outbox head: make persist_consent_ledger_with_outbox the sole product write path (authorize ManageOwnConsent before any durable write), then purpose-specific POST /v1/consents.
Sent by Cursor Automation: Fix Issues
Protected-main #81 requires claim_deadline_at on processing integration_consumption rows. Recovery fixtures that omit the column fail constraint integration_consumption_claim_deadline_shape.
|
Closing this Draft as superseded by the current #142 consent/outbox landing. #123's own current contract still orders a tied durable tail by |


Why
PR #112 binds the consent outbox to a durable ledger tail, but that tail is
ORDER BY occurred_at_unix_ms DESC, event_ref DESC.ConsentLedger::recordallows equal timestamps. A same-millisecond research revocation whoseevent_refsorts before the grant is hidden, so a grant-only snapshot can still enqueue grant propagation beside stored revocation. A complete same-millisecond revoke envelope is also rejected when the grant identity sorts last.#70 at
3180620only checks the in-memory last event. Do not merge #70 or #112.What
Successor to #70/#112 on current main (
a763735) plus the #112 composition:consent_ledgerFOR UPDATE.consent_eventidentity to appear in the submitted ledger.occurred_at_unix_ms DESC, created_at DESC, event_ref DESC(created_atalready exists; no new physical objects).InvalidPropagationEnvelope, outbox count unchanged.This does not change consent purposes, infer consent, duplicate identity credentials, or weaken research opt-in separation.
Verification
cargo test --test postgres_consent_outbox_latest_event --test postgres_consent_outbox_transaction --test postgres_consent_outbox_error_boundarycargo test --lib postgres_consent_propagationcargo clippy --all-targets -- -D warningsNext after this lands: make
persist_consent_ledger_with_outboxthe sole consent write path, then purpose-specific consent HTTP.