Skip to content

fix(add-factor): post-insert FactorLookup ensure reconcile - #237

Open
orekav wants to merge 5 commits into
uaf/5-lookup-ensure-heal-followupsfrom
uaf/7-ensure-lookup-post-insert-reconcile
Open

fix(add-factor): post-insert FactorLookup ensure reconcile#237
orekav wants to merge 5 commits into
uaf/5-lookup-ensure-heal-followupsfrom
uaf/7-ensure-lookup-post-insert-reconcile

Conversation

@orekav

@orekav orekav commented Aug 6, 2026

Copy link
Copy Markdown

Summary

  • Follow-up to Codex on fix(add-factor): lookup ensure vs delete + heal retries #235: close the TOCTOU where /delete-factor can remove a factor (and its lookup) between the ensure pre-check and Dynamo insert, leaving a resurrected stale row.
  • After a successful ensure insert/adopt, re-read metadata; if the factor is absent and the lookup still maps to this backup, delete the lookup.

Stack

Base: #235 (uaf/5-lookup-ensure-heal-followups). Sibling to #236; rebase #236 onto updated #235 separately if needed.

Test plan

  • Existing add-factor / concurrent lookup tests still pass
  • Manual/reasoning: delete-factor between ensure presence check and insert → post-insert reconcile removes the resurrected row
  • Transient metadata Unknown after insert does not delete the lookup

… insert

Re-read metadata after a successful ensure insert/adopt and delete the
lookup if the factor was removed concurrently, closing the pre-check
TOCTOU resurrection window.
orekav and others added 2 commits August 6, 2026 16:46
Merge identical heal match arms and split ensure into helpers so
too_many_lines and match_same_arms pass under pedantic.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84387d654d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/routes/add_factor.rs Outdated
Comment on lines +896 to +898
factor_lookup
.delete(FactorScope::Main, factor_to_lookup)
.await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Recheck metadata after deleting the reconciled lookup

If another same-backup /add-factor request restores this factor after the Absent read but before this delete, it can adopt the existing lookup, commit metadata, complete its own ensure while the mapping still exists, and then have that valid mapping deleted here. That successful request leaves the restored factor untraceable, so this cleanup needs a post-delete metadata re-read and lookup heal (as the rollback path already performs), rather than ending immediately after the unconditional delete.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch — fixed in 49797e3.

After the reconcile delete we now call heal_main_factor_lookup_if_present, which re-reads metadata and re-inserts the lookup if a concurrent same-backup /add-factor restored the factor (and had adopted this mapping) between the Absent check and the delete.

After deleting a stale ensure mapping on Absent metadata, re-run heal so
a concurrent same-backup add-factor that restored the factor is not left
untraceable.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 49797e3c42

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/routes/add_factor.rs Outdated
Comment on lines +948 to +950
factor_lookup
.delete(FactorScope::Main, factor_to_lookup)
.await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Delete only the mapping whose owner was reconciled

If /delete-factor removes this backup's lookup after the consistent read and another backup then claims the same factor before this call, the unconditional delete removes that new backup's valid mapping. The subsequent heal checks only the original backup's now-absent metadata, so it cannot restore the new owner's row; use an owner-conditional delete rather than deleting solely by factor key.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in f48996e.

Reconcile now uses FactorLookup::delete_if_maps_to (#backup_id = :backup_id condition) so we cannot delete another backup's valid mapping if ownership changed after the consistent read.

Comment thread src/routes/add_factor.rs Outdated
Comment on lines +948 to +950
factor_lookup
.delete(FactorScope::Main, factor_to_lookup)
.await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Heal after an ambiguous delete response

When this DynamoDB delete applies but returns a timeout or dispatch error, .await? exits before the new heal call. If a concurrent same-backup /add-factor adopted the mapping and committed metadata before that delete, its successful request is left without a lookup. Fresh evidence after the earlier fix is that the added heal remains reachable only on Ok; run it after both successful and ambiguous delete outcomes, as the rollback path already does.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in f48996e.

Delete errors are logged and heal still runs (same pattern as lookup rollback), so an ambiguous Dynamo delete ACK cannot skip repair.

Comment thread src/routes/add_factor.rs Outdated
Comment on lines +953 to +960
heal_main_factor_lookup_if_present(
backup_storage,
factor_lookup,
factor_to_lookup,
backup_id,
new_factor_kind,
)
.await;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reconcile the lookup inserted by the heal path

If another /delete-factor removes the newly restored factor after heal_main_factor_lookup_if_present observes it in metadata but before that helper inserts the lookup, the helper returns immediately after the successful insert and recreates the stale row this post-insert reconciliation is intended to prevent. Fresh evidence beyond the earlier heal fix is that the reused helper performs only a pre-insert metadata check; its successful insert also needs a post-insert reconciliation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in f48996e.

Reconcile now runs up to two delete+heal rounds: after a heal insert that raced with /delete-factor, the next round re-checks metadata and cleans up / re-heals without async recursion between heal and reconcile.

…ners

Use owner-conditional FactorLookup deletes, heal after ambiguous delete
errors, and run a second delete+heal round so heal inserts are reconciled
without async recursion.
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.

1 participant