Skip to content

Commit c588935

Browse files
committed
docs: add server quirks research report 2026-05-10
Covers 5 issues found during map integration debugging: - Q1: Go v2 SML responses wrapped as GeoJSON Feature (fields in .properties) - Q2: NIMS bootstrap was silently incomplete (cameras never created) - Q3: POST /deployments/{id}/subdeployments 400 on full body - Q4: OSH system@link.href includes ?f=json query suffix - Q5: NIMS publisher had 0 observations on both servers Also documents ISS system documentation gap and updated behavioral comparison table between OSH and Go v2.
1 parent a47f1e1 commit c588935

1 file changed

Lines changed: 380 additions & 0 deletions

File tree

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
# CSAPI Server Quirks — Map Integration Debugging Session
2+
3+
**Date:** 2026-05-10
4+
**Status:** Complete
5+
**Scope:** Map integration defects across Go v2 and OSH servers — NIMS cameras, DSC card thumbnails, ISS photo, station images, timelapse links
6+
**Follows:** `CSAPI_Go_Server_Integration_Report_2026-04-17.md`
7+
8+
---
9+
10+
## 1 Executive Summary
11+
12+
During a map integration debugging session, five distinct server/client behavioral issues were uncovered. Three are server-side quirks in `connected-systems-go` (Go v2). One is a data completeness issue caused by a partial bootstrap. One is a documentation gap in the ISS system definition. All issues are now resolved with client-side workarounds and/or data patches.
13+
14+
| # | Issue | Severity | Servers | Resolved |
15+
|---|-------|----------|---------|---------|
16+
| Q1 | Go v2 SML responses are GeoJSON Features, not raw SML objects | P2 | Go v2 | Client workaround |
17+
| Q2 | NIMS bootstrap silently incomplete — cameras never created | P2 | Go v2 | Bootstrap re-run |
18+
| Q3 | `POST /deployments/{id}/subdeployments` 400 on full body | P2 | Go v2 | Minimal-stub retry |
19+
| Q4 | OSH `system@link.href` includes `?f=json` query suffix | P3 | OSH | Strip in client |
20+
| Q5 | NIMS observation queue empty on both servers (0 published) | P2 | Both | Publisher cycle run |
21+
22+
---
23+
24+
## 2 Q1 — Go v2 SML Responses Are GeoJSON Features
25+
26+
### Symptom
27+
The Deployed System Card (DSC) showed no thumbnail, no station photo, and no SML-derived metadata (keywords, classifiers, contacts, documents) for any system on Go v2. The same systems on OSH displayed correctly.
28+
29+
### Root Cause
30+
When fetching `GET /systems/{id}?f=application/sml+json`:
31+
32+
- **OSH** returns a raw SML object with fields at the top level:
33+
```json
34+
{
35+
"type": "PhysicalSystem",
36+
"label": "NDBC 44025 — Long Island, NY",
37+
"keywords": [...],
38+
"identifiers": [...],
39+
"contacts": [...],
40+
"documents": [...]
41+
}
42+
```
43+
44+
- **Go v2** returns a GeoJSON Feature wrapper with SML fields inside `.properties`:
45+
```json
46+
{
47+
"type": "Feature",
48+
"id": "3b1bc3bc-...",
49+
"geometry": { "type": "Point", "coordinates": [...] },
50+
"properties": {
51+
"uid": "urn:os4csapi:system:...",
52+
"name": "CO-OPS 8518750 — The Battery, NY",
53+
"keywords": [...],
54+
"identifiers": [...],
55+
"contacts": [...],
56+
"documentation": [...]
57+
},
58+
"links": [...]
59+
}
60+
```
61+
62+
All `extractSml*` functions in `useDeployedSystemCard.ts` read from `sml.keywords`, `sml.documentation`, etc. These were all `undefined` on Go v2 because the actual data lives in `sml.properties.*`.
63+
64+
Additional difference: OSH uses `documents` as the key for the documentation array. Go v2 uses `documentation`. The extractors already handled both (`sml?.documents || sml?.documentation`) — only the Feature wrapper was missed.
65+
66+
### Fix
67+
Added a single normalization step before all SML extraction:
68+
69+
```typescript
70+
// Normalize: Go v2 wraps SML as GeoJSON Feature (fields in .properties)
71+
// OSH returns SML with fields at top level
72+
const smlSource = systemSml?.type === 'Feature'
73+
? (systemSml?.properties || {})
74+
: (systemSml || {})
75+
```
76+
77+
All extractors now receive `smlSource` instead of `systemSml` directly.
78+
79+
**Commit:** `8eb8fc5` on `ogc-csapi-explorer` main
80+
**Affected systems:** Every system on Go v2 — CO-OPS stations, USGS water stations, NDBC buoys, ISS, NIMS cameras, all others.
81+
82+
### Systems Affected by This Quirk
83+
- `card.thumbnail` — station photo (first `documentation` entry with `image/*` MIME)
84+
- `card.summarySentence` — depends on SML description
85+
- `card.kindBadge` / `card.roleBadge` — from classifiers/identifiers
86+
- `card.docsLinks` — the documentation links panel
87+
- `card.ownerMaintainer` — from contacts
88+
- `card.methodSummary` — from procedure links
89+
- Keywords, capabilities, identifiers — all derived from SML
90+
91+
---
92+
93+
## 3 Q2 — NIMS Bootstrap Silently Incomplete
94+
95+
### Symptom
96+
NIMS cameras did not appear on the Go v2 map despite USGS water station systems being present on Go v2 and a "USGS NIMS Camera Stations" group deployment existing.
97+
98+
### Root Cause
99+
The NIMS bootstrap (`bootstrap_usgs_nims.py`) was previously run against Go v2 and completed without errors. However it only created:
100+
- ✅ Root deployment `urn:os4csapi:deployment:usgs-nims-demo:v1`
101+
- ✅ Group deployment `urn:os4csapi:deployment:usgs-nims-cameras:v1`
102+
- ✅ Procedure `urn:os4csapi:procedure:usgs-nims-imagery:v1`
103+
104+
It did **not** create:
105+
- ❌ 8 individual camera sub-deployments (`usgs-nims-{siteId}:v1`)
106+
- ❌ 8 `usgsNimsImage` datastreams on the water station systems
107+
108+
**Why silently?** The map renders deployment leaf nodes based on whether the deployment has a `platform@link`. The group deployment (`usgs-nims-cameras`) has 0 sub-deployments, so nothing renders — no error, just empty.
109+
110+
**Diagnosis commands:**
111+
```powershell
112+
# Confirmed 0 sub-deployments under the NIMS camera group
113+
GET /deployments/81bb4aa3-a428-4325-9b1d-52d7b4a84412/subdeployments
114+
# → { items: [] }
115+
116+
# Confirmed no usgsNimsImage datastream on water station system 09380000
117+
GET /systems/c9d8de34-52a7-43a6-a08f-f85490d4baf5/datastreams
118+
# → 2 items: Discharge, Gage Height (no image DS)
119+
```
120+
121+
A dry-run confirmed what would be created:
122+
```
123+
[DRY] Would create datastream 'usgsNimsImage' on system c9d8de34... (09380000)
124+
[DRY] Would create datastream 'usgsNimsImage' on system 9d06bc30... (09019850)
125+
... (8 total)
126+
[DRY] Would create deployment: urn:os4csapi:deployment:usgs-nims-09380000:v1
127+
... (8 total)
128+
```
129+
130+
### Fix
131+
Re-ran `python -m publishers.usgs_nims.bootstrap_usgs_nims` (no `--dry-run`). All 8 datastreams and 8 camera sub-deployments created successfully.
132+
133+
Then ran `usgs_nims_publisher --once` against Go v2 to seed initial observations (all 8 images published).
134+
135+
### NIMS Structure on Go v2 (Post-Fix)
136+
137+
```
138+
53458c4f USGS NIMS Imagery Demo [root deployment]
139+
└─ 81bb4aa3 USGS NIMS Camera Stations [group]
140+
├─ fbd0f765 NIMS Camera 09380000 [leaf, platform@link → c9d8de34]
141+
├─ 73761560 NIMS Camera 09019850 [leaf]
142+
├─ e0e71e45 NIMS Camera 11313433 [leaf]
143+
├─ bd396835 NIMS Camera 08171000 [leaf]
144+
├─ 353abfa8 NIMS Camera 01650800 [leaf]
145+
├─ 62b191c1 NIMS Camera 05051300 [leaf]
146+
├─ 9fffa737 NIMS Camera 12439500 [leaf]
147+
└─ 5bdf3fe4 NIMS Camera 02135000 [leaf]
148+
```
149+
150+
---
151+
152+
## 4 Q3 — `POST /deployments/{id}/subdeployments` Returns 400 with Full Body
153+
154+
### Symptom
155+
Every `POST /deployments/{id}/subdeployments` returned HTTP 400 on the first attempt during the NIMS bootstrap.
156+
157+
### Root Cause
158+
Go v2 rejects some fields in the deployment body when POSTing to the subdeployments endpoint. The exact offending fields are not identified in the error response (body is empty / generic 400).
159+
160+
Bootstrap helper already implements a minimal-stub retry:
161+
```python
162+
[WARN] POST deployments/.../subdeployments failed (HTTP 400); retrying with minimal stub
163+
[OK] Created deployment urn:os4csapi:deployment:usgs-nims-09380000:v1 → id=...
164+
```
165+
166+
All 8 camera sub-deployments were created via the minimal stub on retry.
167+
168+
### Behavior Details
169+
- Initial POST: full deployment body (uid, name, description, validTime, platform@link, ...) → **400**
170+
- Retry POST: minimal stub (uid, name only) → **201 Created**, empty body
171+
- After creation: full metadata PATCHed separately (when applicable)
172+
173+
### Note
174+
This is distinct from the previously documented "201 with empty body" behavior (section 13.4 of the April report). This is a 400 on the initial POST that only affects the `/subdeployments` sub-endpoint, not the top-level `/deployments` endpoint.
175+
176+
**Recommendation for Go server issue:** File as separate issue — "POST /deployments/{id}/subdeployments rejects valid full deployment body with 400; top-level POST /deployments accepts same body."
177+
178+
---
179+
180+
## 5 Q4 — OSH `system@link.href` Includes `?f=json` Query String Suffix
181+
182+
### Symptom
183+
When following a `system@link.href` from an OSH datastream resource to fetch the associated system, the href includes a query string:
184+
185+
```json
186+
"system@link": {
187+
"href": "https://os4csapi-osh.duckdns.org/sensorhub/api/systems/045g?f=json"
188+
}
189+
```
190+
191+
Appending additional parameters (e.g., `?f=application/sml+json`) to this URL results in a malformed request:
192+
```
193+
GET /systems/045g?f=json?f=application%2Fsml%2Bjson → 400 Bad Request
194+
```
195+
196+
### Root Cause
197+
OSH embeds its own internal format parameter in outbound hrefs. This is non-standard; RFC 3986 links should be canonical URLs without self-referential format hints.
198+
199+
### Workaround
200+
Strip query string from hrefs before using them:
201+
```typescript
202+
systemId = platformLink.href.replace(/\/+$/, '').split('/').pop() || ''
203+
// Extracts ID from path, discarding any ?f=... suffix
204+
```
205+
206+
The Explorer already does this via the `split('/').pop()` pattern when extracting system IDs from hrefs. This works correctly.
207+
208+
**Note:** This quirk only manifests if you try to use the full href directly rather than extracting the ID. Clients that compose their own request URLs (using the extracted ID) are not affected.
209+
210+
---
211+
212+
## 6 Q5 — NIMS Observations Missing on Both Servers (0 Published)
213+
214+
### Symptom
215+
NIMS image datastreams existed on both Go v2 and OSH but had 0 observations. The map marker and metadata card showed no camera image, no timelapse link.
216+
217+
### Root Cause
218+
The `usgs-nims-publisher.service` systemd service targets OSH by default (via the `.env` file). On Go v2, a separate `usgs-nims-publisher-go.service` should exist (see Appendix A of April report). The Go v2 publisher was not running or had failed silently.
219+
220+
On OSH, the publisher had also failed to post new observations — likely a crash after the server migration to the new URL/auth config or a transient network issue.
221+
222+
### Immediate Fix
223+
Ran one-shot publishes manually:
224+
225+
```powershell
226+
# Go v2
227+
$env:OSH_BASE_URL = "https://129-80-248-53.sslip.io/csapi-go-v2"
228+
python -m publishers.usgs_nims.usgs_nims_publisher --once
229+
# → Published: 8
230+
231+
# OSH
232+
$env:OSH_BASE_URL = "https://129-80-248-53.sslip.io/sensorhub/api"
233+
python -m publishers.usgs_nims.usgs_nims_publisher --once
234+
# → Published: 8
235+
```
236+
237+
### NIMS Observation Result Schema
238+
239+
For reference, a NIMS observation result from this publisher:
240+
```json
241+
{
242+
"camId": "AZ_Colorado_River_at_Lees_Ferry_Upstream",
243+
"filename": "AZ_Colorado_River_at_Lees_Ferry_Upstream___2026-05-10T23-00-05Z.jpg",
244+
"imageUrl": "https://usgs-nims-images.s3.amazonaws.com/overlay/AZ_Colorado_River_at_Lees_Ferry_Upstream/...jpg",
245+
"smallUrl": "https://usgs-nims-images.s3.amazonaws.com/720/AZ_Colorado_River_at_Lees_Ferry_Upstream/...jpg",
246+
"thumbUrl": "https://usgs-nims-images.s3.amazonaws.com/thumbnail/AZ_Colorado_River_at_Lees_Ferry_Upstream/...jpg",
247+
"mediaType": "image/jpeg",
248+
"stationId": "09380000",
249+
"timestamp": "2026-05-10T23:00:05Z",
250+
"timeLapseUrl": "https://usgs-nims-images.s3.amazonaws.com/timelapse/AZ_Colorado_River_at_Lees_Ferry_Upstream/...mp4"
251+
}
252+
```
253+
254+
The DSC card (`useDeployedSystemCard.ts`) reads these fields:
255+
- `result.imageUrl``card.cameraImageUrl`
256+
- `result.thumbUrl``card.cameraThumbUrl`
257+
- `result.timeLapseUrl``card.cameraTimeLapseUrl`
258+
- `result.camId``card.cameraCamId`
259+
- `result.mediaType` → gate condition (must start with `image/`)
260+
261+
**NDBC BuoyCAM result schema** (for comparison):
262+
```json
263+
{
264+
"stationId": "46013",
265+
"imageUrl": "https://os4csapi-osh.duckdns.org/buoycam/46013/2026/04/17/...jpg",
266+
"mediaType": "image/jpeg",
267+
"cameraStatus": "ok",
268+
"sha256": "...",
269+
"contentLength": 59812.0,
270+
"latestImageUrl": "https://www.ndbc.noaa.gov/buoycam.php?station=46013"
271+
}
272+
```
273+
274+
BuoyCAM does **not** have `timeLapseUrl`. The timelapse link only appears for NIMS cameras.
275+
276+
---
277+
278+
## 7 Supplementary: ISS System Missing Documentation/Photo
279+
280+
Not a server quirk per se — a data gap in the ISS bootstrap.
281+
282+
### Symptom
283+
The ISS DSC card showed no thumbnail. After Q1 was fixed (SML Feature normalization), the card could theoretically show a photo if one existed in the system's `documentation` array. The ISS position system had none.
284+
285+
### Fix
286+
Added `documentation` array to `_system_position()` in `bootstrap_iss.py`:
287+
```python
288+
"documentation": [
289+
{
290+
"role": "http://dbpedia.org/resource/Photograph",
291+
"name": "ISS Photograph",
292+
"link": {
293+
"href": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/International_Space_Station_after_undocking_of_STS-132.jpg/640px-International_Space_Station_after_undocking_of_STS-132.jpg",
294+
"type": "image/jpeg",
295+
},
296+
},
297+
{
298+
"role": "http://dbpedia.org/resource/Web_page",
299+
"name": "ISS Tracking Page",
300+
"link": {
301+
"href": "https://spotthestation.nasa.gov/",
302+
"type": "text/html",
303+
},
304+
},
305+
]
306+
```
307+
308+
**Patched live** on both servers:
309+
- Go v2 ISS position system: `8f06cdeb-d50e-4aca-baeb-5aa5601323ff` → PUT 204
310+
- OSH ISS position system: `04i0` → PUT 204
311+
312+
---
313+
314+
## 8 Updated Behavioral Comparison Table
315+
316+
Extends the table from the April 2026 report (section 13.7).
317+
318+
| Behavior | SensorHub (OSH) | connected-systems-go (Go v2) |
319+
|---|---|---|
320+
| SML response format (`?f=application/sml+json`) | Raw SML object (top-level fields) | **GeoJSON Feature** (SML fields in `.properties`) |
321+
| SML response key for documentation | `documents` | `documentation` |
322+
| `system@link.href` format | Contains `?f=json` suffix | Clean path, no suffix |
323+
| POST `/deployments/{id}/subdeployments` with full body | Accepts | **Returns 400**; minimal stub retry succeeds |
324+
| `?f=` parameter for SML format | `?f=sml3` | `?f=application/sml%2Bjson` |
325+
| `outputName` filter on `/datastreams` | **Not reliable** (returns wrong items) | Works correctly |
326+
| Observation default sort | Ascending | Descending (newest-first) |
327+
| `resultTime=latest` param | Honored | **Silently ignored** |
328+
| `/deployments/{id}/systems` endpoint | Supported | **404 Not Found** |
329+
| POST 201 response body | Full resource JSON | Empty; `Location` header only |
330+
331+
---
332+
333+
## 9 Server State After This Session
334+
335+
### Go v2 (`https://129-80-248-53.sslip.io/csapi-go-v2`)
336+
337+
| Resource Type | Count | Notes |
338+
|---|---|---|
339+
| Systems | ~37 | Unchanged |
340+
| Datastreams | +8 | Added `usgsNimsImage` for 8 NIMS camera stations |
341+
| Deployments | +10 | Added 8 NIMS camera leaf deployments (+ 2 already existed) |
342+
| Observations | +8 | Initial NIMS image observations published |
343+
344+
### OSH (`https://129-80-248-53.sslip.io/sensorhub/api`)
345+
346+
| Resource Type | Count | Notes |
347+
|---|---|---|
348+
| Systems | ~44 | Unchanged |
349+
| Datastreams | Unchanged | NIMS datastreams already existed |
350+
| Observations | +8 | NIMS image observations published (were stale/empty) |
351+
352+
### System Patches Applied (Both Servers)
353+
354+
| System | Server | Change |
355+
|---|---|---|
356+
| ISS Position Publisher (`iss-position-publisher:v1`) | Go v2 | Added `documentation` with photo + tracking page |
357+
| ISS Position Publisher (`iss-position-publisher:v1`) | OSH | Added `documentation` with photo + tracking page |
358+
359+
---
360+
361+
## 10 Client-Side Fixes Committed
362+
363+
| File | Change |
364+
|---|---|
365+
| `demo/src/composables/useDeployedSystemCard.ts` | Normalize SML source: detect Go v2 Feature wrapper, extract `.properties` before all `extractSml*` calls |
366+
| `publishers/iss/bootstrap_iss.py` | Add `documentation` array (photo + tracking page) to `_system_position()` |
367+
368+
**Commit:** `8eb8fc5``ogc-csapi-explorer` main — deployed via Cloudflare auto-build.
369+
370+
---
371+
372+
## 11 Recommended Go Server Issues to File
373+
374+
These are new findings beyond the April report's outstanding list:
375+
376+
1. **`POST /deployments/{id}/subdeployments` rejects full body with 400** — body content vs. top-level endpoint inconsistency. Minimal stub workaround exists but forces a separate PATCH round-trip.
377+
378+
2. **SML responses from `GET /systems/{id}?f=application/sml+json` return GeoJSON Feature wrapper instead of raw SML** — breaks any client expecting SML field names at the top level of the response. OSH returns correct raw SML format.
379+
380+
3. **`usgs-nims-publisher-go.service` may not be running** — verify systemd service status on the VM; the publisher had 0 observations on Go v2 before manual re-run.

0 commit comments

Comments
 (0)