Skip to content

Enforce persisted curtailment authorization envelopes - #957

Merged
negarn merged 2 commits into
mainfrom
negar/gh-909-enforce-persisted-curtailment-authorization-envelopes
Aug 24, 2026
Merged

Enforce persisted curtailment authorization envelopes#957
negarn merged 2 commits into
mainfrom
negar/gh-909-enforce-persisted-curtailment-authorization-envelopes

Conversation

@negarn

@negarn negarn commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Reviewable diff: +299/-448 across 8 files (excludes generated, test, and story files).

Summary

Curtailment events and response profiles now authorize reads, mutations, and recovery operations from the durable authorization envelope captured when each record is written. This lets site-scoped operators retain access to the records they were authorized to create even if topology membership changes later, while malformed or incomplete persisted envelopes fail closed. This is the next backend slice of issue #909; topology execution and the remaining lifecycle work stay tracked there.

How it works

Handlers first require that the caller has the relevant curtailment capability at some supported scope, load the organization-owned event or profile, and strictly parse its persisted authorization envelope. Miner and facility-fan coverage are converted into site resource contexts; unbounded coverage requires an organization-wide grant, and facility-fan sites additionally require site:read.

Single-record operations enforce every required context before returning or mutating the record. List operations apply the same checks with per-request permission caches, filter unauthorized records, and only then hydrate display-oriented target coverage. Response-profile deletion also includes the authorized envelope in its SQL optimistic predicate, so a concurrent envelope change cannot invalidate the permission decision before deletion.

Diagrams

flowchart TD
    A["Event or profile RPC"] --> B["Require scoped curtailment capability"]
    B --> C["Load organization-owned record"]
    C --> D["Strictly parse persisted authorization envelope"]
    D --> E["Check curtailment permission for miner and fan coverage"]
    E --> F["Check site:read for facility-fan coverage"]
    F --> G["Filter or continue the lifecycle operation"]
    G --> H["Hydrate display-only target coverage"]
Loading
sequenceDiagram
    participant H as Handler
    participant S as Response profile service
    participant DB as PostgreSQL
    H->>S: Delete with authorized selector, fan, and envelope snapshots
    S->>DB: DELETE where every expected snapshot still matches
    DB-->>S: One row or zero rows
    S-->>H: Success or FailedPrecondition
Loading

Areas of the code involved

Area / package / file What changed Why it matters for review
server/internal/domain/curtailment/ Added strict authorization-envelope parsing and validation Establishes the fail-closed trust boundary for persisted coverage
server/internal/handlers/curtailment/handler.go Replaced live topology reconstruction with envelope-based event authorization and cached list filtering Controls event visibility and lifecycle/recovery authority
server/internal/handlers/curtailment/response_profiles.go Applies the same persisted checks to profile get/list/update/delete flows Keeps profile behavior aligned with event authorization
server/internal/domain/stores/ and server/sqlc/queries/ Threads the expected envelope through response-profile deletion Makes authorization and deletion atomic with respect to concurrent changes
server/generated/sqlc/ Regenerated query bindings Generated — skip
Curtailment domain, handler, and SQL-store tests Covers strict parsing, site-scoped access, filtering, recovery, and stale-envelope deletion Verifies the authorization boundary and regression scenarios

Key technical decisions & trade-offs

  • Persisted envelopes are the authority for record reads and recovery; current topology is used only for display hydration, avoiding authorization drift after moves or deletions.
  • The parser requires the exact supported schema and fields; invalid stored data surfaces as an internal error instead of widening access.
  • Facility fans require both the curtailment grant and site:read for every captured fan site.
  • List endpoints cache organization-wide and per-site decisions for the request rather than repeating middleware checks per record.
  • Automation entry points remain organization-scoped until immutable automation bindings have equivalent persisted authorization coverage.
  • Response-profile deletion compares the envelope alongside the selector and fan snapshots, preferring a failed precondition over a stale authorization decision.

Testing & validation

  • just gen
  • cd server && just lint
  • cd server && go test ./internal/domain/curtailment ./internal/handlers/curtailment -count=1
  • cd server && go test ./internal/domain/stores/sqlstores -run '^(TestBuildAuthorizationEnvelopeJSON|TestLockTopologyScopeCoverage)' -count=1
  • git diff --check origin/main...HEAD
  • The full database-backed SQL-store suite was not runnable locally because the configured local Postgres rejected the fleet password; focused in-memory SQL-store authorization tests passed, and CI remains responsible for the database integration run.

Refs #909

@negarn
negarn requested a review from a team as a code owner August 21, 2026 15:24
Copilot AI lite review requested due to automatic review settings August 21, 2026 15:24
@github-actions github-actions Bot added the review-policy: needs-review Managed by the Review Policy workflow. label Aug 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR shifts curtailment event and response-profile authorization from “current topology-derived scope” to a strictly-validated, persisted authorization envelope captured at write-time, ensuring read/mutate/recovery checks remain anchored to the durable snapshot and fail closed on malformed persisted data.

Changes:

  • Add strict parsing/validation for persisted curtailment authorization envelopes and enforce envelope-based permission checks (including site:read for facility-fan coverage).
  • Update curtailment event/profile handlers to filter/list and gate single-record operations using persisted envelope-derived resource contexts with per-request permission caching.
  • Make response-profile deletion concurrency-safe with respect to authorization changes by including the expected authorization envelope in the SQL delete predicate (plus corresponding store/service/interface changes and tests).

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated no comments.

Show a summary per file
File Description
server/sqlc/queries/curtailment_response_profile.sql Adds authorization_envelope_jsonb to the optimistic delete predicate for response profiles.
server/internal/handlers/curtailment/response_profiles.go Enforces persisted-envelope authorization for response profile get/list/update/delete, with request-scoped permission caching.
server/internal/handlers/curtailment/handler.go Enforces persisted-envelope authorization for curtailment events (read/manage/recovery), adds envelope permission caches, and hydrates target-site coverage post-filtering.
server/internal/handlers/curtailment/automation.go Adapts to the updated permission-cache shape in list filtering logic.
server/internal/handlers/curtailment/handler_update_test.go Updates update-handler tests to include persisted authorization envelopes and new site-scoped permission expectations.
server/internal/handlers/curtailment/handler_test.go Adds helper utilities and unit tests for envelope requirement derivation and fail-closed behavior.
server/internal/handlers/curtailment/handler_stop_test.go Updates stop-handler tests to ensure events include persisted envelopes.
server/internal/handlers/curtailment/handler_start_test.go Updates start-handler replay path tests to ensure replay events include persisted envelopes.
server/internal/handlers/curtailment/handler_response_profiles_test.go Updates response-profile handler tests for persisted facility-fan site enforcement and delete-envelope snapshotting.
server/internal/handlers/curtailment/handler_list_test.go Updates list tests to filter by persisted envelope authorization (including whole-org behavior and facility-fan requirements).
server/internal/handlers/curtailment/handler_admin_terminate_test.go Updates admin terminate / force-release tests to rely on persisted envelopes and allow matching site-only manage.
server/internal/domain/stores/sqlstores/curtailment.go Threads expected authorization envelope through response-profile deletion and passes it into sqlc params.
server/internal/domain/stores/interfaces/curtailment.go Updates ResponseProfileStore.DeleteResponseProfile signature to include expected authorization envelope JSON.
server/internal/domain/curtailment/response_profile.go Updates service delete API to include expected authorization envelope JSON.
server/internal/domain/curtailment/response_profile_test.go Updates fake-store/service tests for the updated delete signature.
server/internal/domain/curtailment/authorization_envelope.go Introduces strict persisted-authorization-envelope parsing/validation (fail-closed).
server/internal/domain/curtailment/authorization_envelope_test.go Adds unit tests for strict parsing and rejection cases.
server/internal/domain/stores/sqlstores/response_profile_facility_fans_integration_test.go Extends integration coverage for stale-envelope delete behavior (FailedPrecondition).
server/generated/sqlc/curtailment_response_profile.sql.go Regenerated sqlc bindings for the updated delete query (generated — skip).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread server/internal/handlers/curtailment/handler.go Outdated
Comment thread server/internal/handlers/curtailment/handler_test.go
Comment thread server/internal/domain/curtailment/authorization_envelope.go
Comment thread server/internal/handlers/curtailment/handler.go
Comment thread server/internal/handlers/curtailment/handler.go
@github-actions github-actions Bot added review-policy: human-approved Managed by the Review Policy workflow. and removed review-policy: needs-review Managed by the Review Policy workflow. labels Aug 21, 2026
@github-actions github-actions Bot added review-policy: needs-review Managed by the Review Policy workflow. and removed review-policy: human-approved Managed by the Review Policy workflow. labels Aug 21, 2026
@negarn
negarn merged commit d4f4786 into main Aug 24, 2026
65 of 66 checks passed
@negarn
negarn deleted the negar/gh-909-enforce-persisted-curtailment-authorization-envelopes branch August 24, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-policy: needs-review Managed by the Review Policy workflow. server

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants