Summary
OSH accepts samplingFeature@link on Observation POST requests (HTTP 201) but silently discards the field — it is not returned on subsequent GET. This prevents CSAPI-native linkage between Observations and their Feature of Interest (FOI).
This is the same "silent acceptance + silent discard" pattern documented in Issue #1 for deployedSystems@link and deployment@link.
| Operation |
Result |
POST /samplingFeatures (create FOI) |
201 Created — persists and round-trips correctly |
POST /datastreams/{id}/observations with samplingFeature@link |
201 Created — accepted without error |
GET /observations/{id} (read back) |
samplingFeature@link absent — silently dropped |
Environment
- Server: OSH (OpenSensorHub) CSAPI instance on Oracle Cloud
- API root:
https://os4csapi-osh.duckdns.org/sensorhub/api
- Date tested: 2026-03-03
- Encoding tested:
application/om+json
Context
The LOB Localizer system fuses Lines of Bearing from three acoustic sensor nodes to produce location-estimate Observations (rendered as gold ⊕ dots in the webapp). These observations need a featureOfInterest link to identify what they are about — in this case, a tracked UAS target.
Per SOSA/SSN, every sosa:Observation should reference a sosa:FeatureOfInterest. CSAPI Part 2 provides samplingFeature@link as the mechanism for this (OGC 23-002 §8.3.5).
Reproduction
1. Create SamplingFeature ✅
POST /sensorhub/api/samplingFeatures HTTP/1.1
Content-Type: application/json
{
"type": "Feature",
"properties": {
"uid": "urn:os4csapi:foi:uas-track-001",
"name": "UAS-Track-001",
"description": "Tracked UAS target identity for the AZ String Alpha sensor network.",
"featureType": "http://www.opengis.net/def/samplingFeatureType/OGC-OM/2.0/SF_SamplingPoint"
}
}
HTTP 201 Created → Location: /samplingFeatures/040g
Read-back returns the full resource correctly. SamplingFeature CRUD works.
2. POST Observation with samplingFeature@link ✅ (accepted)
POST /sensorhub/api/datastreams/04f0/observations HTTP/1.1
Content-Type: application/om+json
{
"phenomenonTime": "2026-03-04T03:13:18.886Z",
"resultTime": "2026-03-04T03:13:18.886Z",
"samplingFeature@link": {
"href": "/samplingFeatures/040g",
"uid": "urn:os4csapi:foi:uas-track-001",
"title": "UAS-Track-001"
},
"result": {
"timestamp": 1772593998.953,
"trackId": 1,
"estimatedLat": 31.658,
"estimatedLon": -110.270,
"cep50_m": 50.0,
"classification": "UAS",
"numContributingLobs": 3,
"contributingSensors": "AZ-MA-1,AZ-MA-2,AZ-MA-3",
"residual_m": 30.0
}
}
HTTP 201 Created → Location: /observations/041sthkupk339jq9g0
3. Read back ❌ (field dropped)
GET /sensorhub/api/observations/041sthkupk339jq9g0 HTTP/1.1
Accept: application/om+json
{
"id": "041sthkupk339jq9g0",
"datastream@id": "04f0",
"phenomenonTime": "2026-03-04T03:13:18.886Z",
"resultTime": "2026-03-04T03:13:18.886Z",
"result": { "..." }
}
samplingFeature@link is completely absent.
Cumulative Pattern
Third instance of the silent-accept/silent-discard behavior:
@link Field |
Resource Type |
Spec Reference |
Persists? |
deployedSystems@link |
Deployment |
OGC 23-001 §8.5 |
❌ Dropped (#1) |
deployment@link |
DataStream |
OGC 23-002 §7.3.2 |
❌ Dropped (#1) |
samplingFeature@link |
Observation |
OGC 23-002 §8.3.5 |
❌ Dropped (this issue) |
platform@link |
Deployment |
OGC 23-001 §8.5 |
✅ Works |
system@link |
DataStream |
OGC 23-002 §7.3.2 |
✅ Works (read-only) |
Impact
Without persistence, clients cannot:
- Query observations by FOI (
GET /observations?samplingFeature={id})
- Group observations by subject identity through the API
- Build track-grouped displays without embedding track identity in the result blob as a workaround
Suggested Resolution
Option A — Persist the field: Store samplingFeature@link on Observation write, return it on read, support filtering by samplingFeature query parameter.
Option B — Reject unsupported fields: Return HTTP 422 when samplingFeature@link is provided but not supported, making the gap visible at write time.
Evidence
Full probe report with HTTP transcripts:
Summary
OSH accepts
samplingFeature@linkon Observation POST requests (HTTP 201) but silently discards the field — it is not returned on subsequent GET. This prevents CSAPI-native linkage between Observations and their Feature of Interest (FOI).This is the same "silent acceptance + silent discard" pattern documented in Issue #1 for
deployedSystems@linkanddeployment@link.POST /samplingFeatures(create FOI)POST /datastreams/{id}/observationswithsamplingFeature@linkGET /observations/{id}(read back)samplingFeature@linkabsent — silently droppedEnvironment
https://os4csapi-osh.duckdns.org/sensorhub/apiapplication/om+jsonContext
The LOB Localizer system fuses Lines of Bearing from three acoustic sensor nodes to produce location-estimate Observations (rendered as gold ⊕ dots in the webapp). These observations need a
featureOfInterestlink to identify what they are about — in this case, a tracked UAS target.Per SOSA/SSN, every
sosa:Observationshould reference asosa:FeatureOfInterest. CSAPI Part 2 providessamplingFeature@linkas the mechanism for this (OGC 23-002 §8.3.5).Reproduction
1. Create SamplingFeature ✅
Read-back returns the full resource correctly. SamplingFeature CRUD works.
2. POST Observation with
samplingFeature@link✅ (accepted)3. Read back ❌ (field dropped)
{ "id": "041sthkupk339jq9g0", "datastream@id": "04f0", "phenomenonTime": "2026-03-04T03:13:18.886Z", "resultTime": "2026-03-04T03:13:18.886Z", "result": { "..." } }samplingFeature@linkis completely absent.Cumulative Pattern
Third instance of the silent-accept/silent-discard behavior:
@linkFielddeployedSystems@linkdeployment@linksamplingFeature@linkplatform@linksystem@linkImpact
Without persistence, clients cannot:
GET /observations?samplingFeature={id})Suggested Resolution
Option A — Persist the field: Store
samplingFeature@linkon Observation write, return it on read, support filtering bysamplingFeaturequery parameter.Option B — Reject unsupported fields: Return HTTP 422 when
samplingFeature@linkis provided but not supported, making the gap visible at write time.Evidence
Full probe report with HTTP transcripts:
OSH_SamplingFeature_Link_Persistence_Gap.md