Skip to content

feat: Person UUID, related OIDC claims, and apis - #11415

Merged
jennifer-richards merged 18 commits into
ietf-tools:feat/uuidfrom
rjsparks:person-identifier
Aug 14, 2026
Merged

feat: Person UUID, related OIDC claims, and apis#11415
jennifer-richards merged 18 commits into
ietf-tools:feat/uuidfrom
rjsparks:person-identifier

Conversation

@rjsparks

@rjsparks rjsparks commented Aug 7, 2026

Copy link
Copy Markdown
Member

No description provided.

@rjsparks
rjsparks marked this pull request as ready for review August 10, 2026 15:36

@jennifer-richards jennifer-richards left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A few high level things I think we should fix (details are inline):

  • fix / simplify the DRF serializers to get rid of polymorphic complications and @extend_schema` misuse
  • the oidc claims should just be in a single scope
  • guard person creation with transactions
  • avoid registering the custom path converter on import
  • consider allowing retries on the uuid push task

We should manually test that the schema that comes out of this by running spectacular is correct (if you haven't done that already) and can be used to generate a client.

There are a bunch of other nit-level comments. The ones related to using the Person uuid @property accessors in tests could be ignored if it's a lot of manual work, but I'm assuming it's simple to generate code incorporating that comment (if you agree).

Comment thread ietf/utils/converters.py Outdated
Comment thread ietf/ietfauth/views.py Outdated
Comment thread ietf/ietfauth/tests.py Outdated
Comment thread ietf/ietfauth/utils.py Outdated
Comment thread ietf/person/api_uuid.py
Comment thread ietf/person/tests.py Outdated
Comment thread ietf/person/tests.py Outdated
Comment thread ietf/person/tests.py
Comment thread ietf/utils/test_data.py
Comment thread ietf/person/utils.py Outdated
Reformatting the import into a parenthesized block moved the ignore comment to
the closing paren. mypy reports the simple_history HistoricalPerson and
HistoricalEmail attribute errors against the 'from ... import (' line, so the
comment has to sit there to suppress them.
Registering it in ietf/utils/converters.py made importing that module a side
effect, and Django refuses to register a converter twice, so naming the
converter from a second URLconf was a latent error. Define it there, register it
once in ietf/urls.py before urlpatterns names it.
A Person with no primary UUID cannot be named to any external system, so the
create and the assign_primary_uuid() that follows it have to succeed or fail
together. Covers all three production creation sites, including the draft
submission one, and wraps the surrounding aliases and nominee email so a
failure part way leaves nothing half-built.
The resolved/unknown split needed a PolymorphicProxySerializer, which is an
annotation helper rather than a real serializer, so the endpoints hand-built
dicts and told consumers not to infer the outcome from which fields were
present. Use one entry serializer per endpoint instead, discriminated on status,
with the identifier fields nullable and always present, and actually serialize
responses through it so the schema cannot drift from what is returned.

Drops the ResolvedStatusEnum/UnknownStatusEnum overrides that existed only to
keep the two single-valued status enums apart - there is now one StatusEnum. The
entry fields are not read_only because read_only implies required=False, which
left a generated client treating even status as optional.

Also annotates retrieve with @extend_schema_view rather than overriding it just
to call super().
Routing this lookup through a GenericViewSet forced the handler to be named
create, because that is what SimpleRouter maps POST to on a collection route.
Nothing is created: the view returned 200 while drf-spectacular inferred 201
from the action name, so the schema advertised a status code the endpoint never
sends and a generated client would treat the real response as unexpected.

An APIView.post() returns 200 with no annotation gymnastics. The viewset was
buying nothing else - no retrieve, no mixins, and an empty queryset. api_key
auth is unaffected, since HasApiKey just reads api_key_endpoint off the view.

The URL is unchanged. Its name loses the router's -list suffix, and the schema
test now checks the declared success codes so this cannot drift again.
Splitting the current identifier and the superseded ones across two scopes was
finer-grained than any consumer needs - there is no case for granting one and
not the other, and the prior list is far too short for response size to matter.

Also corrects the scope description, which claimed the prior list included the
identifier in use now. It does not, and datatracker_uuid is where that lives.
The job logged that every Person has exactly one primary while only looking for
Persons with none. The partial unique constraint should make more than one
impossible, so finding one means the data is grossly inconsistent and worth
reporting - and ensure_primary_uuid() cannot repair that case, since it would be
picking a survivor arbitrarily, so it is reported and skipped rather than
silently 'fixed'. Same change in the base-test-data check.
Celery's default is three attempts over well under a second, which is cheap
enough on the request path that changed the UUID set and is the difference
between riding out a broker blip or failover and dropping the push on the floor.
The broker-error catch still keeps an outright outage from failing the
datatracker operation.
Neither endpoint had an APP_API_TOKENS entry in the container config, so every
call to them from a dev environment got a 403.
… accessors

PersonFactory now makes its Person's primary UUID with PersonUUIDFactory instead
of calling assign_primary_uuid() itself, so all UUID handling in tests goes
through the factories. PersonFactory(primary_uuid=False) covers the
no-UUIDs-at-all case, which no production path can reach, replacing the tests
that created a Person and then deleted its UUID rows.

Tests now assert through Person.primary_uuid and Person.prior_uuids rather than
querying uuids directly, so the accessors are the example to copy. Direct
queries remain only where they are the point: the test proving the accessors
agree with the rows, and setup that deliberately builds inconsistent state.

Also drops the retry kwarg assertion that went with the old retry=False.
A merge stamps every UUID it moves with the same time, so ordering the prior list
on time alone left the order undefined in exactly the case where there is more
than one prior. Break ties on the UUID, which also makes the claim that
uuid_sets_for() matches this accessor true - it was already ordering on both.
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.21429% with 4 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (feat/uuid@385568f). Learn more about missing BASE report.

Files with missing lines Patch % Lines
ietf/person/models.py 91.30% 2 Missing ⚠️
ietf/person/tasks.py 92.85% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             feat/uuid   #11415   +/-   ##
============================================
  Coverage             ?   88.67%           
============================================
  Files                ?      335           
  Lines                ?    44994           
  Branches             ?        0           
============================================
  Hits                 ?    39897           
  Misses               ?     5097           
  Partials             ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
Comment thread ietf/person/tests.py Outdated

@jennifer-richards jennifer-richards left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the updates. One question/suggestion to clarify a comment, plus a nit just to point at (in case it bothers you more than it bothers me and you want to adjust the logging, I think it's ok as-is)

Comment thread ietf/person/tasks.py
Comment thread ietf/person/models.py Outdated
The previous comment justified the tie-break by claiming a merge gives every
UUID it moves the same timestamp. It does not: merge_persons() moves them with a
queryset update that names only person and primary, and PersonUUID.time is a
per-row default with no auto_now, so each keeps its original timestamp.

The tie-break stands on narrower ground - it makes the order total instead of
leaving equal timestamps to the database, and matches the ordering uuid_sets_for()
already used - so only the comment changes.
@jennifer-richards
jennifer-richards changed the base branch from main to feat/uuid August 14, 2026 16:55
@jennifer-richards
jennifer-richards merged commit 7cfbfcc into ietf-tools:feat/uuid Aug 14, 2026
9 checks passed
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Aug 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

2 participants