| Field | Value |
|---|---|
| Date | 2026-03-02 |
| Author | Claude (Opus 4.6) |
| Status | Design Pattern Recommendation |
| Scope | Using CSAPI Deployments as the primary user-facing "deployed system" abstraction |
| Related Reports | See §8 |
The OGC Connected Systems API defines Systems and Deployments as separate first-class resource types. Systems own all data (datastreams, observations, control streams, commands). Deployments provide operational context (where, when, why a system was fielded).
However, the user base — particularly military operators and program managers — thinks in terms of deployed systems: "What is MA-1 doing at Node 1 right now?" They don't naturally separate the hardware identity from its operational role.
The CSAPI standard does not define a resource called "Deployed System." But does it need to? This report argues that the existing Deployment resource, when paired 1:1 with an operationally significant system, is the "deployed system" concept — and that both the standard and the OSH implementation fully support this pattern.
For every operationally significant system, create a corresponding deployment (or subdeployment) that serves as its operational face:
USER-FACING LAYER (Deployments) DATA LAYER (Systems)
════════════════════════════════ ═══════════════════
Ft Huachuca ODAS Net (deployment)
└── String Alpha (sub-deployment)
│
├── "Node 1" (sub-deployment) AZ-MA-1 (system)
│ platform@link ──────────────→ ├── 7 datastreams
│ ↑ ├── 4 control streams
│ └── deployment@link on DS ──────┘
│
├── "Node 2" (sub-deployment) AZ-MA-2 (system)
│ platform@link ──────────────→ ├── 7 datastreams
│ ↑ ├── 4 control streams
│ └── deployment@link on DS ──────┘
│
└── "Node 3" (sub-deployment) AZ-MA-3 (system)
platform@link ──────────────→ ├── 7 datastreams
↑ ├── 4 control streams
└── deployment@link on DS ──────┘
Users interact with the deployment layer. The deployment scopes queries. The system layer owns the data behind the scenes.
OGC 23-001 (Connected Systems API — Part 1: Feature Resources) §7.5 defines a Deployment as:
"A Deployment describes the deployment of one or more systems for a particular purpose at a particular place and time."
The key properties a deployment carries:
| Property | What It Gives You |
|---|---|
name, description |
User-friendly operational labeling (can differ from system name) |
validTime |
When this deployment is/was active — temporal scoping |
location / geometry |
Where the system is deployed — spatial scoping |
platform@link |
Which system backs this deployment |
| Subdeployments | Organizational hierarchy (nets, fields, strings, nodes) |
A system's name is what it is: "AZ-MA-1 Monitoring Array."
A deployment's name is what role it fills: "Node 1, String Alpha, North Sector."
The deployment adds operational semantics that the system doesn't carry. This is by design — the standard separates physical identity from operational assignment.
OGC 23-002 (Connected Systems API — Part 2: Observation & Command Resources) defines deployment-scoped nested endpoints:
GET /deployments/{id}/datastreams ← "what is this deployed system producing?"
GET /deployments/{id}/observations ← "what has this deployed system observed?"
These endpoints filter by the deployment@link field on datastreams. They exist specifically so consumers can query data through the operational lens rather than the hardware lens.
This is the standard saying: deployments are how you present data to operational users.
The standard defines platform@link on deployments (Part 1, Table 11) as a link to the system that is deployed. On OSH, this is the only working mechanism for associating systems with deployments — the deployedSystems@link alternative was proven non-functional in the conformance probe.
This makes the 1:1 pairing natural: one deployment, one platform@link, one system.
| User Concern | Query via System | Query via Deployment |
|---|---|---|
| "What is MA-1 seeing right now?" | Works | Works |
| "What was at Node 1 position last month?" | Requires knowing MA-1 was there then | Direct query — deployment tracks the role |
| "Show me all of String Alpha's observations" | Must union 3 system queries manually | GET /deployments/{string}/observations |
| MA-1 is replaced by MA-4 at same position | Completely different system ID, new DS IDs | Same deployment, update platform@link — continuity preserved |
| "Which systems are currently deployed?" | No standard filter mechanism | GET /deployments?validTime=now |
| Organizational navigation (net → field → string → node) | Systems don't model operational structure | Deployment hierarchy = org chart |
| Per-node observation scoping | Must filter by system ID in query params | deployment@link gives free scoping |
Consider the lifecycle of a field position:
Node 1 on String Alpha is a persistent operational role. The hardware occupying it may change — MA-1 today, MA-4 next quarter, MA-7 next year.
With systems only: Every hardware swap creates a new system with new datastream IDs. Historical queries must know every system that ever occupied the role and union their results. There is no persistent identifier for "Node 1."
With deployment as the deployed system: The deployment is the persistent identifier for the role. When hardware swaps:
TIMELINE:
Jan 2026 ─────────────────── Jun 2026 ─────────────────── Dec 2026
│ │ │
│ Deployment: Node 1 │ Deployment: Node 1 │
│ platform@link → MA-1 │ platform@link → MA-4 │
│ validTime: Jan..Jun │ validTime: Jul.. │
│ │ │
│ DS.deployment@link → D │ DS.deployment@link → D │
│ (MA-1 observations) │ (MA-4 observations) │
Or, for cleaner temporal separation, two sequential sub-deployments:
String Alpha (deployment)
└── Node 1 — Phase 1 (sub-deployment)
│ platform@link → MA-1
│ validTime: Jan 2026 .. Jun 2026
│ DS.deployment@link references this
│
└── Node 1 — Phase 2 (sub-deployment)
platform@link → MA-4
validTime: Jul 2026 ..
DS.deployment@link references this
Either way, the deployment layer provides role continuity that the system layer cannot.
Not every system needs its own deployment. The pattern applies to operationally significant systems — the ones users would look for by name or role.
These are systems that users think of as "deployed things":
| System Type | Why It Needs a Deployment |
|---|---|
| AZ-MA-1, MA-2, MA-3 (monitoring arrays) | Each occupies a field position, users query per-node data |
| Relay nodes | Deployed at specific locations, users check relay health |
| Any system that generates observations users query directly | Deployment provides scoped access |
| Any system that occupies a named operational role | Deployment carries the role name |
These are internal components — subsystems that are part of a parent system's physical composition:
| System Type | Why It Doesn't Need a Deployment |
|---|---|
| Monitoring Site Node 1 (subsystem of MA-1) | Internal component, data flows through parent's datastreams |
| Relay (subsystem of MA-1) | Internal component |
| Acoustic Sensor (subsystem) | Physical sensor, not an operational role |
| Processing Chain (subsystem) | Algorithm, not a deployed thing |
| Actuator (subsystem) | Command target, accessed through parent system |
If a user would ask "What is [this thing] doing?" then it needs a deployment.
If a user would ask "What components does [this thing] have?" then it's a subsystem inside a system.
"What is MA-1 doing?" → needs a deployment. "What sensors does MA-1 have?" → those sensors are subsystems, no deployment needed.
The webapp already navigates by deployment hierarchy. If users' mental model is "deployed systems," the deployment tree in the Explorer is the deployed-systems view:
┌──────────────────────────────────────────────┐
│ CSAPI Explorer — Deployments │
│ │
│ ▾ Ft Huachuca ODAS Net │
│ ▾ String Alpha │
│ ► Node 1 (AZ-MA-1) ← click │
│ ► Node 2 (AZ-MA-2) │
│ ► Node 3 (AZ-MA-3) │
│ ▸ String Bravo │
│ ▸ String Charlie │
│ │
├──────────────────────────────────────────────┤
│ Node 1 (AZ-MA-1) │
│ │
│ Location: 31.64°N, 110.25°W │
│ Active since: 2026-01-15 │
│ System: AZ-MA-1 Monitoring Array │
│ │
│ Datastreams (7): │
│ ► Track Updates 1,247 obs │
│ ► SSL 834 obs │
│ ► Health Status 412 obs │
│ ► SENREP 56 obs │
│ ► ... │
│ │
│ [View on Map] [View System Details] │
└──────────────────────────────────────────────┘
Clicking a deployment shows its datastreams (filtered by deployment@link). The user sees "what this deployed system is producing" without needing to know about the system/deployment distinction.
The "View System Details" link navigates to the system resource for users who want to see physical composition (subsystems, procedures, sampling features). But for day-to-day operational use, the deployment view is sufficient.
# System already exists: AZ-MA-1 = system ID 040g
deployment_payload = {
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [-110.25, 31.64] },
"properties": {
"featureType": "sosa:Deployment",
"uid": "urn:os4csapi:deployment:node:ft-huachuca:alpha:001",
"name": "Node 1 — AZ-MA-1",
"description": "AZ-MA-1 Monitoring Array deployed as Node 1, String Alpha, Ft Huachuca",
"validTime": ["2026-01-15T00:00:00Z", ".."],
"platform@link": {
"href": "/sensorhub/api/systems/040g",
"uid": "urn:os4csapi:system:set:ft-huachuca:001",
"type": "application/geo+json"
}
}
}
# POST as sub-deployment under String Alpha
POST /deployments/{stringAlphaId}/subdeployments
→ 201 Created, Location: /deployments/057gEvery datastream on the system gets deployment@link pointing to its paired deployment:
datastream_payload = {
"name": "Track Updates",
"outputName": "trackUpdates",
"system@link": { "href": "/sensorhub/api/systems/040g" },
"deployment@link": { "href": "/sensorhub/api/deployments/057g" },
"procedure@link": { "href": "/sensorhub/api/procedures/04c0" },
"observedProperties": [...],
"schema": { ... }
}
POST /systems/040g/datastreams
→ 201 CreatedOnce wired, deployment-scoped queries work:
# What is Node 1 producing?
GET /deployments/057g/datastreams
→ 7 datastreams (only those with deployment@link → 057g)
# What has Node 1 observed?
GET /deployments/057g/observations
→ all observations from those datastreams
# What's happening across String Alpha?
GET /deployments/{stringAlpha}/subdeployments
→ [Node 1, Node 2, Node 3]
# What's the full org chart?
GET /deployments/{sensorNet}/subdeployments → strings
GET /deployments/{string}/subdeployments → nodes
When MA-1 is replaced by MA-4 at the same position:
# Option A: Update platform@link on existing deployment (keeps same deployment ID)
PUT /deployments/057g
Body: { ...same props..., "platform@link": { "href": "/sensorhub/api/systems/{ma4Id}" } }
# Option B: Close old deployment, create new one (cleaner temporal separation)
PUT /deployments/057g
Body: { ...same props..., "validTime": ["2026-01-15T00:00:00Z", "2026-06-30T23:59:59Z"] }
POST /deployments/{stringAlpha}/subdeployments
Body: { "name": "Node 1 — AZ-MA-4", "platform@link": → MA-4, "validTime": ["2026-07-01T00:00:00Z", ".."] }Either way, the historical data from MA-1's tenure is preserved and queryable through its deployment scope.
Systems:
AZ-MA-1
├── 7 datastreams, thousands of observations
└── subsystems
| Pros | Cons |
|---|---|
| Simpler — fewer resources to create | No organizational hierarchy |
| No per-role observation scoping | |
| No temporal scoping by deployment period | |
| Hardware swap breaks all queries | |
| "Which systems are deployed at Ft Huachuca?" has no standard answer | |
| System hierarchy models physical composition, not operational structure |
Systems:
Sensor Net (system)
└── String Alpha (subsystem)
└── AZ-MA-1 (subsystem)
| Pros | Cons |
|---|---|
| One hierarchy to think about | Reparenting costs thousands of API calls + data migration |
| Sensor Net and String Alpha aren't really "systems" | |
| Conflates physical composition with operational organization | |
| Documented in detail |
Deployments:
String Alpha (deployment)
deployedSystems@link: [MA-1, MA-2, MA-3]
| Pros | Cons |
|---|---|
| Minimal resource count | OSH silently drops deployedSystems@link — proven by probe |
| No per-node observation scoping | |
| No per-node temporal validity | |
| Doesn't survive contact with the implementation |
Deployments:
Sensor Net
└── String Alpha
├── Node 1 (platform@link → MA-1)
├── Node 2 (platform@link → MA-2)
└── Node 3 (platform@link → MA-3)
| Pros | Cons |
|---|---|
| Per-node observation scoping | More resources to create (one deployment per significant system) |
| Organizational hierarchy | deployment@link must be set on every datastream |
| Role continuity across hardware swaps | |
| Temporal scoping per deployment | |
| Cheap to rearrange (~12 API calls per move) — documented | |
| Standards-conformant | |
| Works on OSH today | |
Forward-compatible with deployedSystems if OSH adds it |
With this pattern, the question "show me deployed systems" has a direct API answer:
GET /deployments
The deployment collection is the deployed-systems collection. Each deployment's platform@link tells you which system is behind it. The deployment hierarchy tells you how they're organized.
For filtered queries:
| User Question | API Query |
|---|---|
| "All deployed systems" | GET /deployments |
| "Deployed systems at Ft Huachuca" | GET /deployments?bbox=-110.5,31.5,-110.0,31.8 |
| "Currently active deployed systems" | GET /deployments?validTime=now |
| "Deployed systems in String Alpha" | GET /deployments/{stringAlpha}/subdeployments |
| "What is Node 1 seeing?" | GET /deployments/{node1}/observations |
| "Node 1's health" | GET /deployments/{node1}/datastreams?name=Health |
Every one of these is a single standard-conformant API call. No client-side filtering, no multi-system unions, no ID lookups.
Users don't need to understand that:
- "Node 1" is a deployment, not a system
- The data lives on system 040g's datastreams
deployment@linkis the wiring mechanism- The system has subsystems (Monitoring Site Node, Relay, etc.)
They see a tree of deployed systems. They click one. They see its data. The deployment abstraction hides the system/deployment distinction entirely.
The "deployed system as a first-class resource" concept is not something that needs to be invented — it already exists in the CSAPI standard as the Deployment resource. The standard's design intent is exactly this separation:
- Systems = physical inventory (what exists)
- Deployments = operational context (what's fielded, where, when, in what role)
The 1:1 pairing pattern makes this explicit:
- Every operationally significant system gets a paired deployment
- The deployment carries the operational name, location, time window, and org hierarchy
- Datastreams are wired to deployments via
deployment@link - Users interact with the deployment layer — it's their "deployed systems" view
- The system layer holds the data and physical composition, accessed when needed
This pattern is:
- Standards-conformant — uses
platform@link, subdeployments, anddeployment@linkexactly as defined - Implementation-proven — works on OSH today, unlike
deployedSystems@linkwhich does not - Cheap to reorganize — deployment reparenting costs ~12 API calls, not thousands
- Forward-compatible — enrichable with
deployedSystemsif/when OSH supports it
Deployments are the "deployed systems" resource. The standard already built it. Use it.
| Report | Topic | Relevance |
|---|---|---|
| CSAPI Deployment Modeling Standards Conformance | deployedSystems vs platform@link standards analysis; flat vs subdeployment models |
Establishes that subdeployments with platform@link are the correct wiring approach |
| OSH DeployedSystems Conformance Probe | Empirical proof that OSH silently drops deployedSystems@link; deployment@link observation scoping analysis |
Proves the flat deployedSystems alternative doesn't work; documents per-node scoping via deployment@link |
| CSAPI Deployment Reparenting Feasibility | Cost analysis of reorganizing deployment vs system hierarchies; two-layer architecture guidance | Confirms deployments are cheap (~12 API calls) to rearrange, making the pattern low-risk |
| AZ-MA-2/MA-3 Migration Procedure Analysis | Migration strategy for the remaining two ODAS nodes; procedure modeling decisions | Documents the practical migration context where this pattern will be applied |