Skip to content

Commit 0fbc3a3

Browse files
committed
Document OSH datastream schema constraints
1 parent 3e307ec commit 0fbc3a3

1 file changed

Lines changed: 93 additions & 0 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# OSH Strict SWE Observation Field Order and Datastream Immutability
2+
3+
**Date:** 2026-06-03
4+
**Context:** Storebaelt webcam publisher Phase 1 freshness heartbeat deployment on the Oracle OSH/CSAPI server.
5+
6+
## Summary
7+
8+
The Storebaelt freshness-heartbeat deployment exposed two important OSH/OGC CSAPI implementation constraints:
9+
10+
1. Existing datastream record structures cannot be updated after observations exist.
11+
2. Observation result JSON must follow the datastream SWE `DataRecord` field order exactly.
12+
13+
Both behaviors are important for publisher authors because they affect schema evolution, bootstrap strategy, and runtime payload construction.
14+
15+
## Finding 1: Datastream Record Structures Are Immutable After Observations
16+
17+
Attempting to update the Storebaelt datastream schema in place failed after the datastream already had observations.
18+
19+
Observed server response:
20+
21+
```text
22+
HTTP 400 PUT /datastreams/{id}
23+
Cannot update the record structure or encoding of a datastream if it already has observations
24+
```
25+
26+
This means publisher developers should treat a datastream's `schema.resultSchema.fields` as effectively immutable once observations have been written.
27+
28+
### Practical Implications
29+
30+
- Add result fields before first production publishing whenever possible.
31+
- For new publishers, run schema dry-runs and local observation serialization checks before deploying the service.
32+
- For existing datastreams with observations, schema changes require one of these migration paths:
33+
- create a new datastream/output name/version;
34+
- delete and recreate the datastream if historical observations can be discarded;
35+
- create a companion status datastream for new fields;
36+
- keep old schema and encode optional status in existing fields only if semantically acceptable.
37+
38+
For Storebaelt, the datastreams were brand new and only contained initial observations, so we deleted and recreated just the two Storebaelt datastreams while preserving systems, deployments, and procedures.
39+
40+
## Finding 2: SWE Observation Result Field Order Is Strict
41+
42+
After the Storebaelt datastreams were recreated with the updated schema, the first heartbeat payload still failed because the JSON object emitted `sourceUrl` before the newly added freshness fields.
43+
44+
Observed server response:
45+
46+
```text
47+
HTTP 400 POST /datastreams/{id}/observations
48+
Invalid payload: Invalid JSON: Expected field 'imageChanged' but was 'sourceUrl'
49+
```
50+
51+
The result payload contained all required fields, but not in the exact order declared by the SWE `DataRecord` schema. OSH's SWE JSON parser treated that as invalid.
52+
53+
### Practical Implications
54+
55+
- Runtime observation payloads must preserve insertion order matching the datastream schema.
56+
- In Python publishers, build `result` dictionaries in schema order and avoid appending new fields after a trailing field that already appears later in the schema.
57+
- If enriching a result after initial construction, move any trailing fields as needed so the final insertion order matches the schema.
58+
- Add tests that check result key order for strict SWE datastreams.
59+
60+
For Storebaelt, the fix was to temporarily pop `sourceUrl`, add the freshness fields, and then reinsert `sourceUrl` so the runtime payload matched schema order.
61+
62+
## Recommended Publisher Pattern
63+
64+
When designing or changing a datastream schema:
65+
66+
1. Declare the SWE `DataRecord` fields in the exact desired runtime order.
67+
2. Construct result objects in the same order.
68+
3. Add regression tests asserting key order for any publisher that mutates/enriches result dictionaries.
69+
4. Deploy schema changes before starting the service.
70+
5. If observations already exist, do not assume `PUT /datastreams/{id}` can alter the schema.
71+
6. Prefer a new datastream or companion status stream for non-disposable production histories.
72+
73+
## Storebaelt-Specific Resolution
74+
75+
The Storebaelt Phase 1 freshness heartbeat deployment used this migration path:
76+
77+
1. Stop `storebaelt-webcams-publisher.service`.
78+
2. Pull and compile the updated publisher code on Oracle.
79+
3. Attempt in-place datastream schema update; observe OSH immutability rejection.
80+
4. Delete and recreate only the two Storebaelt webcam datastreams.
81+
5. Restart the service.
82+
6. Observe strict field-order rejection.
83+
7. Patch runtime payload ordering and add a regression test.
84+
8. Redeploy and verify both changed and unchanged heartbeat observations are accepted.
85+
86+
Final verified behavior:
87+
88+
- First post after datastream recreation: two `changed/fresh` observations accepted.
89+
- Manual unchanged one-shot cycle: two `unchanged/unchanged` observations accepted.
90+
91+
## Broader CSAPI Lesson
92+
93+
CSAPI publisher implementations should not treat SWE result schemas as loose JSON contracts. In OSH, they are typed, ordered record structures. That is good for interoperability, but it means publisher code must be deliberate about schema versioning and JSON object field order.

0 commit comments

Comments
 (0)