Skip to content

feat(#119 F1): resource-server scope foundation + dev XML capture/sample#167

Merged
dfcoffin merged 1 commit into
mainfrom
feature/119-f1-base-resource-controller
Jun 6, 2026
Merged

feat(#119 F1): resource-server scope foundation + dev XML capture/sample#167
dfcoffin merged 1 commit into
mainfrom
feature/119-f1-base-resource-controller

Conversation

@dfcoffin
Copy link
Copy Markdown
Contributor

@dfcoffin dfcoffin commented Jun 6, 2026

First foundation slice of the canonical DC ESPI resource surface (#119) — the security-critical scope core every resource endpoint will enforce, so subscription scoping (the data-leakage fix) lives in one tested component instead of per controller. Reuses the existing tested Atom/DTO/IdentifiedObject marshalling — F1 adds none.

Scope core (web/api/scope)

  • ResourceScope — resolved per-request visibility: unfiltered() (admin/Bulk), or a granted energy UsagePoint-id set + PII RetailCustomer-id set; fail-closed denied(). UUID resources; Long RetailCustomer correlation key (RetailCustomer is not an ESPI resource).
  • ScopeResolver — token → ResourceScope: DataCustodian-admin authority or Batch/Bulk → admin; Batch/Subscription resourceURI → the subscription's granted UsagePoint ids (read from the DC's own store, never the token body) via the new lazy-safe SubscriptionRepository.findUsagePointIdsBySubscriptionId; Batch/RetailCustomer customerResourceURI → the PII RetailCustomer id; otherwise denied(). URIs parsed via the canonical EspiBatchUri.
  • EspiQueryOptions — REQ.21.6.2.8 feed query params, all optional: published/updated min/max are the required-to-support timestamp filters; max-results/start-index/start-after/depth are optional best-effort DB knobs. No limit/offset pagination (ESPI CMD/DMD are machine-to-machine).

Dev tooling (web/api/devtools)

  • EspiXmlCaptureFilter — dev-only (@Profile dev-mysql/dev-postgresql/local/dev) servlet filter that tees ESPI XML responses under /espi/1_1/resource/** into per-run sub-dirs under repo-root dev-xml-capture/ (git-ignored) so a developer can view/diff the marshalled XML before committing. Zero impact on test/prod/CI; body passes through unchanged.
  • DevXmlSampleGenerator — on-demand (not run by CI; -Dtest=DevXmlSampleGenerator) generator that marshals a sample resource via the real UsageExportService into dev-xml-capture/sample/ — view ESPI XML without standing up the full server.

Tests

ScopeResolverTest (admin / Bulk / subscription→usage-points / PII / fail-closed denied), ResourceScopeTest, EspiQueryOptionsTest, EspiXmlCaptureFilterTest. The new repo @Query is validated by the existing SubscriptionRepositoryTest (JPA/H2 bootstrap).

Next (E1, not this PR)

The delegation-based resource engine + entry assemblyself/up/related atom:links (related FB-gated, per REQ.21.6.2; type=espi-feed/… for collection hrefs, espi-entry/… for id hrefs; links on entries only) — lands in E1 with the first real resource (UsagePoint/MeterReading/ReadingType), exercised end-to-end via MockMvc. (One open decision for E1: default vs atom:-prefixed Atom namespace.)

🤖 Generated with Claude Code

Foundation for the canonical DC ESPI resource surface (#119): the security-critical scope
core every resource endpoint will enforce, so subscription scoping / the data-leakage fix
lives in one tested component rather than per controller.

Scope core (web/api/scope):
- ResourceScope: resolved per-request visibility — admin/unfiltered, or a granted energy
  UsagePoint-id set + PII RetailCustomer-id set; fail-closed denied(). UUID resources, Long
  RetailCustomer correlation key.
- ScopeResolver: token -> ResourceScope. DataCustodian-admin authority or Batch/Bulk -> admin;
  Batch/Subscription resourceURI -> the subscription's granted UsagePoint ids (read from the
  DC's own store via the new lazy-safe SubscriptionRepository.findUsagePointIdsBySubscriptionId,
  never from the token body); Batch/RetailCustomer customerResourceURI -> the PII RetailCustomer
  id; otherwise denied. URIs parsed via the canonical EspiBatchUri.
- EspiQueryOptions: REQ.21.6.2.8 feed query params — ALL optional. published/updated min/max are
  required-to-support timestamp filters; max-results/start-index/start-after/depth are optional
  best-effort DB knobs. No limit/offset pagination (ESPI CMD/DMD are machine-to-machine).

Dev tooling (web/api/devtools):
- EspiXmlCaptureFilter: dev-only (@Profile dev-*) servlet filter that tees ESPI XML responses
  under /espi/1_1/resource/** to per-run sub-dirs under repo-root dev-xml-capture/ (git-ignored)
  so a developer can view/diff the marshalled XML before committing. Zero impact on test/prod/CI.
- DevXmlSampleGenerator: on-demand generator (not run by CI) that marshals a sample resource via
  the real UsageExportService into dev-xml-capture/sample/ — view ESPI XML without a full server.

Reuses the existing tested Atom/DTO/IdentifiedObject marshalling — F1 adds none. The resource
engine + entry-assembly (self/up/related FB-gated atom:links, per REQ.21.6.2) lands in E1 with
the first real resource (UsagePoint/MeterReading/ReadingType), exercised end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dfcoffin dfcoffin merged commit db1a2e8 into main Jun 6, 2026
4 checks passed
@dfcoffin dfcoffin deleted the feature/119-f1-base-resource-controller branch June 6, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant