Generated by an AI assistant on behalf of @rpbritton.
Problem statement
JujuModelHandle serves two roles at once, and owner is the seam:
- Identity key — used as a dict/set key (
applications_by_model, _known_models, _applications_cache, the resource registry) and as the basis for resource_id / path_segment, all of which are owner-free.
- Addressing descriptor —
uri renders controller:owner/model and is passed to the Juju CLI as --model, which requires the owner.
Because owner must be ignored for identity but included for addressing, it is declared owner: str | None = field(default=None, compare=False). The consequence is that JujuModelHandle(controller="c", model="m", owner="alice") == JujuModelHandle(controller="c", model="m", owner="bob") is True — a frozen dataclass whose equality silently excludes a field. Callers reading a == b or handle in some_set must know that owner is excluded; the exclusion is invisible at the call site.
There are also three inconsistent representations of "the model": resource_id (owner-free), path_segment (owner-free), and uri (owner-qualified). Nothing keeps them in sync.
Separately, owner=None silently falls back to the authenticated user when addressing, which is the failure mode behind #1034.
Enhancement Proposal
Give each role its own type so equality is honest and the identity/address distinction is enforced by the type system rather than a field annotation:
- A bare
ModelKey(controller, model) with all fields compared, used for dict/set keys, resource_id, and path_segment.
- The handle (carrying
owner) used only for addressing.
compare=False then disappears, because neither type needs an equality override.
What needs to get done?
- Introduce the identity type and migrate dict/set/registry keying to it.
- Reconcile
resource_id / path_segment with the new identity type.
- Update the ~25 non-test
JujuModelHandle(...) construction sites and the model-ref fixtures.
- Decide whether
owner=None should remain a silent fallback or become an explicit error when addressing.
Not urgent, and out of scope for #1034 — filing so the compromise is recorded rather than silently normalized.
Generated by an AI assistant on behalf of @rpbritton.
Problem statement
JujuModelHandleserves two roles at once, andowneris the seam:applications_by_model,_known_models,_applications_cache, the resource registry) and as the basis forresource_id/path_segment, all of which are owner-free.urirenderscontroller:owner/modeland is passed to the Juju CLI as--model, which requires the owner.Because owner must be ignored for identity but included for addressing, it is declared
owner: str | None = field(default=None, compare=False). The consequence is thatJujuModelHandle(controller="c", model="m", owner="alice") == JujuModelHandle(controller="c", model="m", owner="bob")isTrue— a frozen dataclass whose equality silently excludes a field. Callers readinga == borhandle in some_setmust know that owner is excluded; the exclusion is invisible at the call site.There are also three inconsistent representations of "the model":
resource_id(owner-free),path_segment(owner-free), anduri(owner-qualified). Nothing keeps them in sync.Separately,
owner=Nonesilently falls back to the authenticated user when addressing, which is the failure mode behind #1034.Enhancement Proposal
Give each role its own type so equality is honest and the identity/address distinction is enforced by the type system rather than a field annotation:
ModelKey(controller, model)with all fields compared, used for dict/set keys,resource_id, andpath_segment.owner) used only for addressing.compare=Falsethen disappears, because neither type needs an equality override.What needs to get done?
resource_id/path_segmentwith the new identity type.JujuModelHandle(...)construction sites and the model-ref fixtures.owner=Noneshould remain a silent fallback or become an explicit error when addressing.Not urgent, and out of scope for #1034 — filing so the compromise is recorded rather than silently normalized.