ADR-0011 — the manager dashboard writes a member's meter, role and area with optional registry scopes
Date: 2026-09-27 Status: accepted
Context
A community manager needs to attach a member's meter and correct their role and area. Those
are registry data, so the manager dashboard (svc-community) writes them to the registry
directly; routing them through ../onboarding would add a hop and make onboarding a general
member-administration service for data it does not own (requester, 2026-09-27).
Three things in this repository bear on that:
svc-communityholdsrec-registry.readas a default scope and nothing that writes. Its one optional scope,onboarding.members.invite, is optional because the Digital Twin forwards this client's default-scope token to dataset-api, and a write capability must not travel there.- Every registry grant is registry-wide. The registry authorises by scope; nothing in a
scope names a community.
rec-registry.members.writewould also let the dashboard rewrite a member'suser_id, DID and status. rec-registry.assets.writeis declared and held by nobody, and there is no scope for "role and area only". The registry adds a dedicated profile route and amembers.profile.writeaction for exactly that, whichrec-registry.members.writeandrec-registry.adminalso satisfy.
The dashboard's declaration carries a promise, in clients.yaml and in
scopes-and-permissions.md: "no email, user id,
DID or supply point leaves the BFF process". A meter write puts a sensor id on the wire from
the BFF to the registry.
Decision
Declare rec-registry.members.profile.write — change one member's role and area, nothing
else — in the registry's scope family.
svc-community gains two optional scopes, and no default one:
rec-registry.assets.write for attaching and detaching a meter, and
rec-registry.members.profile.write for role and area. The BFF requests each only for the one
write that needs it, as it already does for onboarding.members.invite, so the token the
Digital Twin forwards carries neither.
rec-registry.members.write stays refused to svc-community. The narrow scope exists so
that it is never needed.
Who may press is not decided here. The registry grants reach every community; what keeps a
manager to their own REC is the dashboard's policy, and the dashboard's audit row is the only
record of who pressed. rec-registry.read, which the dashboard already holds, makes the same
trade for reads.
The promise about what leaves the BFF is narrowed, not dropped. The meter's sensor id is
entered as free text by the manager, in one dialog; nothing reads a list of candidate meters
for it, because an unattached meter belongs to no community and any candidate list would
disclose or mis-assign another community's meter. The id travels from the BFF to the
registry and nowhere else; the members list shows only whether a meter is attached; audit
rows and logs carry no sensor id. Email, user id, DID and supply point still never leave the
BFF. The sentence in clients.yaml and in the scope reference was rewritten in the change that
granted the scopes (REQ-0005), so that neither described a grant the realm did not have yet.
svc-onboarding gains four scopes, with the changes that use them, by the rule
svc-community already follows: a token the Digital Twin forwards carries no write
capability.
- Optional, requested per call:
provisioning.reconcile(setting up a community, see ADR-0010) andrec-registry.community.write(writing a community's areas and topology from its onboarding template). Onboarding requests each only for the one call that needs it, inside a registry sync a realm admin started. - Default:
digital-twin.values.read(resolving a supply address to a primary-substation boundary, and validating a template's boundary ids), andrec-registry.read(reading a community's areas, topology and member counts back for the sync's dry run, its prune count and the console's drift check). The registry's communityGETroutes derive the actionread, which onlyrec-registry.readorrec-registry.adminsatisfies, and onboarding holds neither today. Both are reads;svc-communityholds the same two by default. No new scope is declared for the drift check.
Consequences
- The dashboard can write any community's meters, roles and areas if its policy is wrong. That is inherent in registry-wide grants, and is the reason the dashboard's per-REC policy and audit row are tested there, not here.
tests/test_provisioning_scope_holders.pypinssvc-community's optional scopes to exactlyonboarding.members.invite,rec-registry.assets.writeandrec-registry.members.profile.write, changed in the change that granted the two, and still asserts noprovisioning.*scope.tests/test_community_registry_scopes.pypins the rest of the shape (REQ-0005): both writes optional, none default, nevermembers.writeor.admin. The test that onboarding holdsparticipants.write"and nothing wider" gainsprovisioning.reconcileas an optional scope in the change that grants it (REQ-0004).- Onboarding's default token can read every community's registry data, as the dashboard's already can. The writes stay out of it.
- The tempting shortcut is
rec-registry.members.write, because it already exists and covers role and area. It also coversuser_id, DID and status, which the dashboard must not be able to rewrite. - The scope descriptions and the registry's policy must agree. The superset
(
members.writeand.adminsatisfymembers.profile.write) is the registry's; this repository only declares the name and who holds it.