Skip to content
0004 - Jurisdiction Service

0004 - Jurisdiction Service

Andi Lamprecht Andi Lamprecht ·· 8 min read· Draft

PRD: Jurisdiction Service (MVP)

FieldValue
StatusDraft
OwnerTBD
ContributorsTBD
Date2026-04-01

1. Executive Summary

Problem Statement: DroneUp has no authoritative service defining who is responsible for managing a given volume of airspace, nor what operational rules apply within it. Without this, downstream services (authorization, flight planning) have no structured source of truth for airspace governance and zone-based rules.

Proposed Solution: A standalone Go microservice exposing a REST API for CRUD management of named Jurisdictions and their sub-Zones. Each Jurisdiction owns one or more 2.5D volumetric boundaries (polygon + AGL floor/ceiling). Each Zone is a typed operational polygon constrained within a parent jurisdiction’s volume, stored in PostGIS 16+.

Success Criteria:

  1. A jurisdiction with multiple volumes can be created, retrieved, updated, and deleted via REST API running locally.
  2. A zone can be created within a jurisdiction and is validated to fall within a jurisdiction volume’s 2D footprint and altitude band.
  3. JurisdictionVolume and Zone geometry is stored and returned as valid GeoJSON polygons.
  4. Floor/ceiling values are stored in feet AGL and returned accurately.
  5. Service starts cleanly with docker compose up against a local PostGIS instance.
  6. API returns appropriate HTTP status codes and error bodies for invalid inputs.

Scope Clarity: A — requirements are clear and bounded for MVP.


2. User Experience & Functionality

User Personas

  • *Authority — Define airspace jurisdictions and zones.
  • Authorization service (future, machine consumer) — will query jurisdiction/zone data to make airspace access decisions.

User Stories & Acceptance Criteria

US-01: Create a jurisdiction

As an authority operator, I want to create a named jurisdiction with one or more volumetric polygons so that I can define who manages a region of airspace.

  • POST /v1/jurisdictions accepts name (string, required), is_active (boolean, optional, defaults true), and volumes (array, min 1, required).
  • Each volume contains a GeoJSON polygon, floor_value (float, ≥ 0), floor_unit (ft), floor_ref (AGL), ceiling_value (float, > floor_value), ceiling_unit (ft), ceiling_ref (AGL).
  • Returns 201 Created with full resource including generated id fields.
  • Returns 400 Bad Request if name missing, volumes empty, polygon invalid GeoJSON, ceiling ≤ floor, or unit/ref values are not in allowed enum.

US-02: Retrieve a jurisdiction

As an authority operator, I want to fetch a jurisdiction by ID so that I can review its configuration.

  • GET /v1/jurisdictions/{id} returns the jurisdiction with all its volumes.
  • Returns 404 Not Found if ID does not exist.

US-03: List all jurisdictions

As a droneup operator, I want to list all jurisdictions so that I can see what is currently defined.

  • GET /v1/jurisdictions returns an array of all jurisdictions with their volumes.
  • Returns empty array (not 404) when none exist.

US-04: Update a jurisdiction

As an authority operator, I want to update a jurisdiction’s name, active status, or volumes.

  • PUT /v1/jurisdictions/{id} replaces name, is_active, and volumes array entirely.
  • Returns 200 OK with updated resource.
  • Returns 404 Not Found if ID does not exist. Same validation rules as creation apply.

US-05: Delete a jurisdiction

As an authority operator, I want to delete a jurisdiction and all its volumes and zones.

  • DELETE /v1/jurisdictions/{id} removes the jurisdiction, all its volumes, and all its zones (cascade).
  • Returns 204 No Content on success.
  • Returns 404 Not Found if ID does not exist.

US-06: Create a zone

As an authority operator, I want to create a typed zone within a jurisdiction so that I can define operational rules for a sub-region of airspace.

  • POST /v1/zones accepts jurisdiction_id (UUID, required), type (no-fly | autoapprove, required), polygon (GeoJSON, required), and optionally floor_value / floor_unit / floor_ref / ceiling_value / ceiling_unit / ceiling_ref.
  • If altitude fields are omitted, they default to the floor/ceiling of the matching parent jurisdiction volume.
  • Validation on insert:
    1. Zone polygon must be geometrically contained within (ST_CoveredBy) the 2D footprint of at least one of the jurisdiction’s volumes.
    2. Zone floor_value must be ≥ matched volume’s floor_value (same unit/ref).
    3. Zone ceiling_value must be ≤ matched volume’s ceiling_value (same unit/ref).
  • Returns 201 Created with full resource.
  • Returns 400 Bad Request if jurisdiction not found, polygon falls outside all volumes, altitude band violates parent volume constraints, or unit/ref values are not in allowed enum.

US-07: Retrieve / list zones

As a platform operator, I want to view zones for a jurisdiction.

  • GET /v1/zones?jurisdiction_id={id} returns all zones for a jurisdiction.
  • GET /v1/zones/{id} returns a single zone.
  • Returns empty array when no zones exist for the jurisdiction.

US-08: Update a zone

As an authority operator, I want to update a zone’s geometry, altitude, or type.

  • PUT /v1/zones/{id} replaces the zone. Same validation rules as creation apply.
  • Returns 200 OK with updated resource.
  • Returns 404 Not Found if ID does not exist.

US-09: Delete a zone

As an authority operator, I want to delete a zone.

  • DELETE /v1/zones/{id} removes the zone.
  • Returns 204 No Content on success.
  • Returns 404 Not Found if ID does not exist.

Non-Goals (MVP)

Out of ScopeRationale
Authentication / authorization on the APIMVP — deferred
Spatial intersection / query endpointsNot needed until authorization service is built
Overlap enforcement between zones or volumesDeferred
Bulk import / exportNot required for MVP
Audit trail / change historyDeferred
Effective date rangesDeferred
Additional jurisdiction/zone metadata fieldsDeferred — name + type only for MVP
Pagination on list endpointsDeferred — low volume at MVP
MVT tile endpoint (/v1/zones/{z}/{x}/{y})v1.1 — deferred
Server-side enforcement of is_active flagConsumer responsibility — not enforced by this service in MVP
Altitude unit/ref normalization across mismatched valuesv1.1 — MVP enforces ft / AGL only

3. Technical Specifications

Architecture Overview

Apollo Frontend
      │
      ▼  REST (HTTP/JSON)
jurisdiction-service  (Go)
      │  ├─ /v1/jurisdictions  (CRUD)
      │  └─ /v1/zones          (CRUD + future MVT)
      ▼
PostGIS 16+ (PostgreSQL)
      │
 [future]
      ▼
authorization-service  (reads jurisdiction + zone data)

Tech Stack

  • Language: Go
  • Database: PostgreSQL 16 + PostGIS extension
  • Geometry storage: PostGIS geometry(Polygon, 4326) — WGS84
  • API format: REST / JSON, GeoJSON for polygon representation
  • Local dev: Docker Compose (service + PostGIS container)

API Surface

MethodPathDescription
POST/v1/jurisdictionsCreate jurisdiction + volumes
GET/v1/jurisdictionsList all jurisdictions
GET/v1/jurisdictions/{id}Get jurisdiction by ID
PUT/v1/jurisdictions/{id}Replace jurisdiction + volumes
DELETE/v1/jurisdictions/{id}Delete jurisdiction, volumes, and zones
POST/v1/zonesCreate zone (validated against parent volume)
GET/v1/zonesList zones (filter by jurisdiction_id)
GET/v1/zones/{id}Get zone by ID
PUT/v1/zones/{id}Replace zone
DELETE/v1/zones/{id}Delete zone
GET/healthzHealth check

Data Model

jurisdictions

ColumnTypeNotes
idUUIDPK, generated
nameTEXTNOT NULL
is_activeBOOLEANNOT NULL, default true
created_atTIMESTAMPTZNOT NULL, default now()
updated_atTIMESTAMPTZNOT NULL, default now()

jurisdiction_volumes

ColumnTypeNotes
idUUIDPK, generated
jurisdiction_idUUIDFK → jurisdictions(id) ON DELETE CASCADE
polygongeometry(Polygon, 4326)NOT NULL
floor_valueFLOAT8NOT NULL, ≥ 0
floor_unitTEXTNOT NULL, default ft — enum: ft
floor_refTEXTNOT NULL, default AGL — enum: AGL
ceiling_valueFLOAT8NOT NULL, > floor_value
ceiling_unitTEXTNOT NULL, default ft — enum: ft
ceiling_refTEXTNOT NULL, default AGL — enum: AGL
created_atTIMESTAMPTZNOT NULL, default now()

zones

ColumnTypeNotes
idUUIDPK, generated
jurisdiction_idUUIDFK → jurisdictions(id) ON DELETE CASCADE
polygongeometry(Polygon, 4326)NOT NULL
floor_valueFLOAT8NOT NULL, ≥ 0
floor_unitTEXTNOT NULL, default ft — enum: ft
floor_refTEXTNOT NULL, default AGL — enum: AGL
ceiling_valueFLOAT8NOT NULL, > floor_value
ceiling_unitTEXTNOT NULL, default ft — enum: ft
ceiling_refTEXTNOT NULL, default AGL — enum: AGL
typeTEXTNOT NULL — enum: no-fly | autoapprove
created_atTIMESTAMPTZNOT NULL, default now()
updated_atTIMESTAMPTZNOT NULL, default now()

Indexes:

  • jurisdiction_volumes(jurisdiction_id) — FK lookup
  • jurisdiction_volumes GIST index on polygon — spatial validation + future queries
  • zones(jurisdiction_id) — FK lookup + filter
  • zones GIST index on polygon — spatial validation + future MVT queries

Zone Altitude Defaulting Logic

On POST /v1/zones, if altitude fields are omitted:

  1. Find the jurisdiction volume whose 2D polygon contains (ST_CoveredBy) the zone polygon.
  2. Copy all 6 altitude columns (floor_value, floor_unit, floor_ref, ceiling_value, ceiling_unit, ceiling_ref) from that volume as the zone’s defaults.
  3. If no single volume contains the zone polygon, return 400 — zone must fall within one volume.

Altitude Validation Rules (all endpoints)

  • floor_unit and ceiling_unit must be ft — return 400 otherwise
  • floor_ref and ceiling_ref must be AGL — return 400 otherwise
  • ceiling_value > floor_value — return 400 otherwise
  • floor_value ≥ 0 — return 400 otherwise

v1.1 note: additional enum values (m, MSL, etc.) will be added when cross-unit normalization logic is implemented.

Integration Points

  • Apollo frontend: REST consumer — UI for managing jurisdictions and zones
  • authorization-service (future): will read jurisdiction + zone data; interface TBD at that service’s design time

Security & Privacy

  • No auth on API for MVP — must be addressed before any non-local deployment
  • No PII in data model
  • PostGIS must not be exposed outside the local Docker network

5. Risks & Phased Rollout

Technical Risks

RiskLikelihoodImpactMitigation
GeoJSON ↔ PostGIS edge cases (invalid rings, winding order)MediumMediumValidate with ST_IsValid server-side; return descriptive errors
Zone altitude defaulting selects wrong volume if jurisdiction has overlapping volumesLowMediumDocument tie-breaking rule (first matched volume by insertion order); revisit with overlap enforcement in v1.1
No auth — accidental API exposureLow (local MVP)HighDocument prominently; enforce NetworkPolicy when deployed to cluster
PUT full-replace on jurisdiction cascades to zones — caller must re-submit zonesMediumMediumDocument clearly; PATCH support added in v1.1

Phased Rollout

MVP (current)

  • CRUD for jurisdictions, volumes, and zones
  • Zone validation against parent volume (2D containment + altitude band)
  • Altitude represented as floor_value / floor_unit / floor_ref — validated enum (ft / AGL)
  • No auth, local PostGIS, Docker Compose
  • Zone types: no-fly, autoapprove

Dependencies

  • PostGIS 16+ Docker image for local dev
  • authorization-service design will determine query interface into this service

6. Estimation Input

prd_sizing_input:
  feature: "jurisdiction-service MVP"
  scope_clarity: "A"
  key_terms:
    - "jurisdiction"
    - "airspace volume"
    - "zone"
    - "postgis"
    - "geospatial"
    - "no-fly"
  risk_flags:
    - "new-service"
    - "geospatial-data"
    - "spatial-validation"
  affected_repos:
    - "jurisdiction-service"
  domains:
    - "backend-go"
    - "infrastructure-docker"
  regulatory: false
  discovery_needed: false
Last updated on