Skip to content

fix(consent): fail closed on reordered stored history - #156

Open
cursor[bot] wants to merge 7 commits into
mainfrom
cursor/bc-d83b661e-5d9c-4fb0-b796-c41f5a0ee573-b36d
Open

fix(consent): fail closed on reordered stored history#156
cursor[bot] wants to merge 7 commits into
mainfrom
cursor/bc-d83b661e-5d9c-4fb0-b796-c41f5a0ee573-b36d

Conversation

@cursor

@cursor cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Prefer this head over #140 for consent-ledger reload.

#140 reconstructed stored events by occurred_at then created_at then event_ref. That silently reorders a grant@20000 / revoke@19000 fixture into revoke-then-grant, so ConsentLedger::record accepts both and the latest decision becomes a grant. The same identity tail can also invent grant-latest when two rows share created_at.

This successor keeps the reload slice (no outbox, no HTTP, no new tables) and:

  • reconstructs by physical created_at insertion order
  • fails closed on a created_at collision instead of ordering by event identity
  • lets non-monotonic insertion-order occurred_at become CorruptHistory
  • rejects whitespace-padded participant aliases instead of trimming them into another identity
  • documents that FOR SHARE does not serialize with persist-side header inserts

Local evidence: cargo test --lib -- postgres_consent, postgres_consent_reload, postgres_consent_persistence, and postgres_consent_error_contract are green against PostgreSQL 16.

Do not fold #142 outbox composition or consent HTTP into this PR. Do not self-approve. Independent last-push review and exact-head required checks still gate merge.

Open in Web View Automation 

cursoragent and others added 5 commits August 16, 2026 15:50
A same-millisecond research revocation must remain the latest purpose
decision after the runtime reloads PostgreSQL consent evidence. Missing
participants stay absent, out-of-order stored history fails closed, and
stronger isolation is rejected.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Reconstruct consent events by occurrence time, then insertion time, then
event identity so a later-inserted same-millisecond revoke is not hidden
behind a lexicographically later grant after process restart.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Traceability, ERD, UML, as-built schema, ADR-0015, and doctoring now
distinguish persist-on-main from this reload slice and cite PostgreSQL
READ COMMITTED isolation.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Traceability and architecture views now cite #140 so agents do not
duplicate consent-ledger reload or fold it into the #120 outbox stack.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Reconstruct persisted consent events by physical created_at so a
later-inserted same-millisecond revoke stays latest, and reject
created_at collisions instead of inventing order from event identity.
Out-of-order stored occurred_at now fails closed instead of being
sorted into a newer grant.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
@cursor
cursor Bot requested a review from seonghobae August 16, 2026 16:01
@seonghobae
seonghobae marked this pull request as ready for review August 16, 2026 16:01
@cursor

cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Review (COMMENT, not approval)

This head closes the #140 fail-open. Reconstruct by physical created_at, not occurred_at plus event_ref. A later-inserted same-millisecond research revoke stays latest. A created_at collision or non-monotonic insertion-order occurred_at returns CorruptHistory instead of inventing a grant. Whitespace-padded participant aliases fail closed. Outbox and HTTP consent stay out of this slice.

Do not merge #140. Do not self-approve this head. Independent last-push review and exact-head required checks still gate merge.

Residual risk (not a reconstruct defect)

created_at defaults to clock_timestamp(). Two inserts in one persist transaction can theoretically share a microsecond and become unloadable after restart. That is fail-closed, not a silent grant. A monotonic insertion key belongs in a later persist-lock/outbox slice, not a third reload PR.

There is still no PostgreSQL test that participant B’s events are absent when loading participant A. The WHERE participant_ref = $1 predicate is correct; add that isolation fixture when extending persist/outbox, not here.

Next buyer-visible gap after this lands

Keep consent outbox on #142, then make persist_consent_ledger_with_outbox the sole write path. Consent HTTP stays later. This run continues with item-delivery restart reload on protected main: persist exists, but a restarted runtime cannot reconstruct already-shown items, so a mid-assessment restart can re-present or skip items.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

This is the correct consent-reload landing over #140. Reconstruct by physical created_at insertion order, fail closed on a created_at collision instead of sorting by event_ref, and reject whitespace-padded participant aliases at the persist boundary. No HTTP or outbox leaked into this slice.

Keep outbox composition on #142. Consent write-path landing is #170. Do not open another reload PR.

One later evidence gap, not a land blocker: persist participant A and B with different research decisions, load A, and prove B's events and research scope never appear. The WHERE participant_ref = $1 predicate already looks correct.

Do not self-approve. Independent last-push review and exact-head required checks still gate merge.

View PR

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review (7fa917c)

SOUND. Prefer this head over closed #140 for consent-ledger reload. Do not open another consent-reload successor.

Verified

  • load_consent_ledger reconstructs by physical created_at, not occurred_at / event_ref. Same-millisecond grant-then-revoke stays revoke-latest (same_millisecond_research_revoke_remains_the_latest_decision_after_reload).
  • Shared created_at fails closed as CorruptHistory instead of ordering by event identity.
  • Non-monotonic insertion-order occurred_at fails closed as CorruptHistory rather than being reordered into a newer grant.
  • Missing ledger is None, not an empty grant.
  • Caller aliases must already be canonical: required_reference rejects whitespace-padded participant refs instead of trimming them into another identity.
  • Rustdoc states the FOR SHARE header lock does not serialize with persist-side header inserts. That honesty is correct for this slice.

Residual (later, not this PR)

Persist still inserts the ledger header without FOR UPDATE. A concurrent persist append can race the share lock. Do not fold #142 outbox composition or consent HTTP here.

Keep this ready head until exact-head required checks and an independent last-push review land. Never self-approve.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Processing consumption rows now require claim_deadline_at after #81.
Seed the wall-clock deadline and prove binary restore preserves it.
@seonghobae

Copy link
Copy Markdown
Contributor

Pushed the #81 recovery fixture: processing restore rows now seed claim_deadline_at, and COPY must keep the exact source deadline. This unblocks integration_consumption_claim_deadline_shape. Do not merge until exact-head checks and independent last-push approval succeed. Never self-approve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants