refactor(be): allocate application numbers from a monotonic cell - #4233
Open
sea-snake wants to merge 22 commits into
Open
refactor(be): allocate application numbers from a monotonic cell#4233sea-snake wants to merge 22 commits into
sea-snake wants to merge 22 commits into
Conversation
|
✅ No security or compliance issues detected. Reviewed everything up to d9034cf. Security Overview
Detected Code Changes
|
sea-snake
force-pushed
the
refactor/monotonic-application-numbers
branch
from
August 19, 2026 07:07
2d8007b to
9f9c380
Compare
sea-snake
force-pushed
the
refactor/monotonic-application-numbers
branch
from
August 19, 2026 08:37
9f9c380 to
233d32c
Compare
sea-snake
force-pushed
the
refactor/monotonic-application-numbers
branch
2 times, most recently
from
August 20, 2026 17:24
782f91d to
e9b957f
Compare
`lookup_application_with_origin_memory.len()` is only a safe source of new
application numbers while nothing is ever removed. Once an application can be
reaped, `len()` does not merely reuse a retired number, it collides with a live
one: with `{0, 1, 2}`, removing `1` leaves `len() == 2`, which is owned.
Numbers now come from a cell at memory index 33, seeded when storage is loaded
rather than at the first allocation. Existing numbers are dense from zero, so the
row count is the first free number at that moment; deferring the seed would let a
removal shrink the count first and hand out a live number.
Implements docs/ongoing/tracked-default-accounts.md §8.3 (D17).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sea-snake
force-pushed
the
refactor/monotonic-application-numbers
branch
from
August 22, 2026 18:42
e9b957f to
e06729b
Compare
…notonic-application-numbers
MRmarioruci
reviewed
Aug 31, 2026
…notonic-application-numbers
The row count is exact only for data written before this counter existed. Once retiring an application removes a row without reissuing its number the count undershoots, and the maximum is what keeps allocation monotonic across both.
aterga
reviewed
Sep 4, 2026
aterga
reviewed
Sep 4, 2026
aterga
reviewed
Sep 4, 2026
aterga
previously requested changes
Sep 4, 2026
aterga
left a comment
Collaborator
There was a problem hiding this comment.
PR description isn't self-contained.
If apps can be removed, why not store the index of the next-free app ID rather than try to derive from the length of a collection?
…notonic-application-numbers # Conflicts: # src/internet_identity/src/storage/tests.rs
…notonic-application-numbers
…notonic-application-numbers
MRmarioruci
reviewed
Sep 4, 2026
…notonic-application-numbers
…notonic-application-numbers
Seeding ran at load and took `max(stored_counter, stable_application_memory.len())`. The row count is only the first free number while numbering is dense from zero, so it needed three sentences of doc explaining when it could be trusted, and it had to be seeded eagerly — deferring would let a removal shrink the count first. The map is keyed by `ApplicationNumber`, so `last_key_value` gives the highest number outright. That is exact whether or not the rows have holes, which removes the density assumption rather than documenting around it, and with it the reason to seed eagerly: the allocator takes `max(counter, highest + 1)` where it allocates. `seed_application_number_allocator` is gone, and so is its constructor-time `expect` — there was nowhere for it to report to. The counter is still what guarantees a number is never reissued, because it only climbs; the highest stored number is a floor for the applications that predate it. Removing the highest row walks that floor backwards, which is why it cannot be the answer on its own. Both arithmetic steps are checked, refusing rather than saturating for the reason `allocate_account_number` refuses: the number keys the application row and the origin index, so reissuing one would put two origins on a single row and have them share its accounts and counters. `allocate_application_number` and `lookup_or_insert_application_number_with_origin` return `Result` for that. `a_gap_below_the_highest_number_is_not_handed_out_again` covers the state a row count gets wrong: a hole in the rows while the counter knows nothing. `a_removal_before_the_first_allocation_does_not_collide_with_a_live_number` now covers it too — it used to pass under a row count only because the removal happened after seeding, and there is no seeding step for it to follow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVi99RYo2jyi2kCurgovNJ
Both create paths allocated a number and stored the account, then asked for the application number — a fallible step after two writes, in functions whose whole shape is about not doing that. Nothing in between depends on the number. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVi99RYo2jyi2kCurgovNJ
…notonic-application-numbers
"Row" was this stack's own word: `origin/main` uses it for something else. The storage layer already names this thing — the account reference list — in its types and functions, so the comments now say what the code says. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVi99RYo2jyi2kCurgovNJ
…notonic-application-numbers
"Row" was this stack's own word: `origin/main` uses it for something else. The storage layer already names this thing — the account reference list — in its types and functions, so the comments now say what the code says. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVi99RYo2jyi2kCurgovNJ
…notonic-application-numbers
…notonic-application-numbers
MRmarioruci
approved these changes
Sep 7, 2026
…notonic-application-numbers
…notonic-application-numbers
…notonic-application-numbers
…notonic-application-numbers
…notonic-application-numbers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Design: #4222. Overview: #4230.
ApplicationNumberis derived fromlookup_application_with_origin_memory.len(), which is only safe while nothing is ever removed. Once an application can be removed,len()collides with a live number rather than merely reusing a retired one:Both origins would then share one account universe: every
(anchor, 2)reference list and config list would belong to both.Numbers now come from a monotonic
StableCell<u64>at memory index 33. The counter is what guarantees a number is never reissued, because it only climbs — a number it has passed is not offered again even after that application's list is retired.The counter cannot be the whole answer on its own, because it postdates the applications numbered before it existed. So allocation takes
max(counter, highest_stored + 1), and the map being keyed byApplicationNumbermeanslast_key_valuegives that highest number exactly, holes in the lists or not. Nor can the floor stand alone: removing the highest list walks it backwards, which is what the counter is there to prevent.Both steps are checked and refuse at the ceiling rather than saturating, for the reason
allocate_account_numberrefuses — the number keys both the application list and the origin index, so reissuing one puts two origins on a single list.allocate_application_numberandlookup_or_insert_application_number_with_originreturnResultfor that; all three production callers propagate.ApplicationNumbernever appears ininternet_identity.did, so this is invisible to clients.Rollback needs a release note. The cell at index 33 is exactly what a rollback discards, restoring
len()as the allocator over a map that removals have punched holes in. Rolling forward recovers a safe allocator, since the cell can only be raised, but a collision created during the rollback window has already happened.Tests:
application_number_allocator_tests(7). The two the design exists for area_gap_below_the_highest_number_is_not_handed_out_again— a hole in the lists while the counter is behind, the one state a list count gets wrong — anda_removal_before_the_first_allocation_does_not_collide_with_a_live_number. Both fail if the floor goes back tolen().🤖 Generated with Claude Code
https://claude.ai/code/session_01LVi99RYo2jyi2kCurgovNJ