ADR-0003 — The boundary fetchers live in the energy-community domain, and source is a closed enum
Date: 2026-09-27 Status: accepted
Context
A renewable energy community's areas are being redefined as references to published
reference boundaries — for Italy, the GSE conventional primary-substation areas, one shape
per substation code, already loaded into the gold layer and exposed by dataset-api as an
open dataset. Two consumers need to ask about them:
- onboarding, at eligibility: which boundary contains this supply address's point? It asks with its own service token, before the person is a member of anything;
- the community dashboard's backend, for a read-only map: the shapes of this community's boundaries, by id.
Hence two value fetchers, boundary_at_point(source, lat, lon) and
boundary_shape(source, ids). Every value fetcher here is reached under a domain's entity
route, so they have to live in some domain. Three were candidates:
it-gridalready serves geometry, but it models a distribution network's resilience: its entity is anetwork_id, and neither consumer has one. Hosting boundaries there would make both callers invent an entity for a question that is not about a network.- A new reference-geography domain would be the purest home, and it would cost a new
domain registration, a new route prefix, a new generated client surface in
celine-sdkand new consumer wiring, for two read-only fetchers. Nothing else would live in it yet. - The energy-community domain is where the question comes from: areas exist to model the
community incentive, both consumers already address the Digital Twin by community, and the
base class
EnergyCommunityDomainis inherited by every locale variant.
The shapes are country-specific; the question is not. Another country's communities will want the same two answers from their own boundary tables (a NUTS or LAU table, say), and the extension point has to be somewhere that does not require a new fetcher per country.
Decision
Declare both fetchers in the base energy-community domain, in a module the locale variants
share, so it-energy-community registers them today and any later locale variant inherits
them unchanged. They are reached as
/communities/it/{community_id}/values/boundary_at_point and …/boundary_shape.
A point on a boundary's edge is inside it, and a point in more than one shape resolves to the
lowest id. boundary_at_point tests that the shape covers the point, not that it contains
it, so a supply address on a line two boundaries share is never "outside every boundary"; of
several covering shapes it answers the one with the lowest id in lexical order, so the wizard
and the approval that re-checks it always get the same id. The requester settled both, and the
base-domain placement, on 2026-09-27 (REQ-1153, REQ-1155).
The community in the path does not scope the answer. A reference boundary belongs to no
community; the entity is the address the runtime needs, not a filter. Both fetchers query
dataset-api with the Digital Twin's own service token, not the caller's forwarded one
(identity="service", REQ-1125, REQ-1160; the requester, 2026-09-28): a caller needs
digital-twin.values.read (the svc-digital-twin audience the route still requires) and no
dataset.query. Every other fetcher keeps forwarding the caller's token.
source is a closed enum in both fetchers' payload_schema, initially
gse_cabine_primarie alone. Each value selects one gold table through a Jinja branch written
into the template; the value itself never reaches the statement. A new country's boundaries
are a new enum value and a new branch, reviewed like any other change to a statement — not a
new fetcher, and never a table name taken from the request. An unknown value is refused at
payload validation.
The requirements are docs/specifications/values.md (reference boundaries) and
docs/specifications/query-templates.md (table selection), marked planned under ADR-0002.
Consequences
dataset-apisees the Digital Twin, not the caller, on these two statements: its audit and any row filter it applies name the Digital Twin's client. That is acceptable for the same reason as the next point, and it is what lets onboarding ask before the person is a member, without adataset-apigrant of its own.- A caller must name some community to ask a geography question. Onboarding knows the community a template binds, and the dashboard asks for its own, so neither is burdened today; a caller with no community would be, and that is the point at which a reference-geography domain earns its cost.
- Because the path does not scope the answer, any authenticated caller that can reach one community route can read every boundary of an enabled source. That is acceptable only because the sources are open reference data; a source holding anything else does not belong in this enum.
- The enum is the whole extension surface. Loosening it to a free string, or to a table name, would make the request choose what the statement reads — the interpolation REQ-1210 forbids, one level up.
- What will tempt someone to undo it: the second country. Its boundaries will look like they belong in its own locale domain. They can live there as an override if its shapes need different columns; while they answer the same two questions from a table of the same shape, one more enum value is the smaller change.