@@ -26,9 +26,18 @@ have an expiry date.
2626- Net negative on ** metadata fidelity over the wire** (lossy round-trip vs. the
2727 legacy OSH server) and ** maintenance surface area** (mechanical workarounds
2828 copy-pasted into each publisher).
29- - ** Four upstream defects** identified in ` csapi-go-v2 ` . Two are clear bugs,
30- one is an asymmetric/incomplete fix, one is a server-side internal error.
29+ - ** Five upstream defects** identified in ` csapi-go-v2 ` . Two are clear bugs,
30+ one is an asymmetric/incomplete fix, one is a server-side internal error,
31+ and one is a malformed-redirect bug that breaks the OGC-API
32+ ` /collections/{id}/items ` access pattern for any browser-side client.
3133 Each is described in §4 with reproducer probes.
34+ - ** Runtime observation pipeline confirmed end-to-end** on 2026-05-10: all
35+ four migrated publishers (aviation-wx, NDBC met, NDBC buoycam, CO-OPS)
36+ successfully POST live observations and image payloads to
37+ ` /csapi-go-v2/datastreams/{id}/observations ` with ** 20/20 success and
38+ zero errors** in a single cycle. Strict-parsing migration was sufficient at
39+ bootstrap time; no publisher-side code changes were needed for runtime
40+ posting against the strict server (see §4.9).
3241
3342---
3443
@@ -568,6 +577,84 @@ curl -s -H "Accept: application/sml+json" \
568577
569578---
570579
580+ ### 4.8 Issue #8 — ` /collections/{id}/items ` 307 redirect drops path prefix
581+
582+ Discovered 2026-05-10 while debugging 404s in the ` ogc-csapi-explorer `
583+ web UI. The OGC-API standard items access path,
584+ ` GET /csapi-go-v2/collections/{id}/items ` , returns a 307 redirect whose
585+ ` Location ` header is missing the ` /csapi-go-v2/ ` route prefix. Any client
586+ that follows the redirect (browsers, ` curl -L ` , OGC client libraries) lands
587+ on a 404.
588+
589+ ** Reproducer:**
590+
591+ ``` bash
592+ curl -sS -i -k " https://129-80-248-53.sslip.io/csapi-go-v2/collections/systems/items?f=json" | head -5
593+ # HTTP/2 307
594+ # location: https://129-80-248-53.sslip.io/systems?f=json <- prefix dropped
595+ #
596+ curl -sS -i -k " https://129-80-248-53.sslip.io/systems?f=json" | head -1
597+ # HTTP/2 404
598+ ```
599+
600+ ** Impact:** This is the most user-visible defect found so far. It breaks
601+ the entire ` /collections/{id}/items ` access pattern for browser clients
602+ because the OGC API client library inside ` ogc-csapi-explorer ` treats this
603+ path as canonical. Server-side scripts that hit resource roots directly
604+ (` /systems ` , ` /datastreams ` , ` /observations ` ) are unaffected — which is
605+ why our publishers and bootstraps never noticed it.
606+
607+ ** Workaround landed:** Proxy-side ` Location ` rewrite in both the
608+ Cloudflare Pages Function and the Vite dev proxy of the
609+ ` ogc-csapi-explorer ` repository (commit
610+ [ ` f1b23de ` ] ( https://github.com/OS4CSAPI/ogc-csapi-explorer/commit/f1b23de ) ).
611+ The proxies intercept any 3xx response, detect same-host redirects, and
612+ re-attach the ` /api/csapi-go-v2 ` prefix mapped to the upstream
613+ ` /csapi-go-v2 ` route. Verified end-to-end: a follow-through to
614+ ` /collections/systems/items ` now returns the full FeatureCollection (16
615+ systems including the entire NDBC and CO-OPS fleet).
616+
617+ ** Upstream fix:** the redirect target should be the same
618+ route-prefix-relative path the request arrived on. Either preserve the
619+ prefix in the redirect, or omit the redirect entirely and serve the
620+ FeatureCollection directly at ` /collections/{id}/items ` .
621+
622+ ---
623+
624+ ### 4.9 Runtime observation pipeline confirmation (2026-05-10)
625+
626+ After the bootstraps completed, an end-to-end runtime test was run against
627+ ` /csapi-go-v2/ ` to confirm that the strict-parsing migration applied at
628+ bootstrap was sufficient for the publishers' POST loop. All four migrated
629+ publishers were invoked with ` --once ` and ` OSH_BASE_URL ` pointed directly
630+ at ` https://129-80-248-53.sslip.io/csapi-go-v2 ` .
631+
632+ | Publisher | Stations | Published | Errors | Notes |
633+ | ---| ---| ---| ---| ---|
634+ | ` aviation_wx_publisher ` | 5 | 5 | 0 | METAR observations |
635+ | ` ndbc_publisher ` | 5 | 5 | 0 | Met observations (wind/wave/temp) |
636+ | ` ndbc_buoycam_publisher ` | 5 | 5 | 0 | JPEG image payloads |
637+ | ` coops_publisher ` | 5 | 5 | 0 | Water level + met |
638+
639+ ** Total:** 20/20 successful POSTs in a single cycle, zero errors. Sample
640+ GET against ` /datastreams/{id}/observations?limit=1 ` round-tripped the
641+ full ` result ` payload (lat/lon, met fields, timestamp) intact.
642+
643+ ** Why this works without further code changes:** the publishers'
644+ pre-existing ` _is_go_server ` branch in ` _post_observation() ` already
645+ performs the two known runtime workarounds for strict parsing — replacing
646+ string ` "NaN" ` with ` 0.0 ` (Go decoder rejects non-finite JSON numbers) and
647+ ensuring ` result.timestamp ` is a string when present. No SWE-Common-level
648+ rejections were observed at the observation tier; the strict parser appears
649+ to be lenient on the ` result ` body as long as the datastream's declared
650+ schema accepts the field set, which the bootstrap stage already negotiated.
651+
652+ ** Implication for §3.2 (test coverage):** until automated contract tests
653+ exist, this manual ` --once ` cycle is the de-facto smoke test. Recommend
654+ adding it to the runbook and running it after every publisher migration.
655+
656+ ---
657+
571658## 5. Summary Table of Upstream Issues
572659
573660| # | Endpoint | Field | Status | Severity | Defect type | Workaround |
@@ -579,6 +666,7 @@ curl -s -H "Accept: application/sml+json" \
579666| 5 | POST /datastreams | top-level ` uid ` | Rejected | P3 | Possibly intentional; undocumented | Let server assign ID |
580667| 6 | POST /deployments | ` parent@link ` in properties | Rejected | Informational | Not a defect | Use ` parent_id= ` helper param |
581668| 7 | PUT /systems | ` contacts[*].role=qualityControl ` , ` contactInfo.phone.voice ` , 3-role contact arrays | ** Accepted** | Confirmation | None — works as specified | None needed (CO-OPS pilot, commit ` 3ac7c88 ` ) |
669+ | 8 | GET /collections/{id}/items | ` Location ` header on 307 redirect | Drops ` /csapi-go-v2/ ` path prefix → 404 on follow | P1 (browser clients) | Malformed redirect target | Proxy-side ` Location ` rewrite (ogc-csapi-explorer commit ` f1b23de ` ) |
582670
583671---
584672
@@ -664,12 +752,17 @@ Option (c) is fine if documented. Right now it's implicit.
664752
665753** Improvements (durable):**
666754
667- 1 . Three publishers (aviation-wx, NDBC, CO-OPS) are portable across both
668- server families.
755+ 1 . Three publishers (aviation-wx, NDBC, CO-OPS) plus the NDBC buoycam image
756+ publisher are portable across both server families.
6697572 . Stub/SML separation matches OGC 23-001.
6707583 . Empirical schema is captured in three companion documents.
6717594 . Helper infrastructure (` bootstrap_helpers.ensure_* ` ) is validated.
672- 5 . Four upstream defects identified with reproducers.
760+ 5 . ** Five upstream defects identified with reproducers** , including a
761+ browser-breaking redirect bug (Issue #8 ) that was patched at the proxy
762+ layer in the ` ogc-csapi-explorer ` repository.
763+ 6 . ** Runtime observation pipeline verified end-to-end** against the strict
764+ server — 20/20 successful POSTs with zero errors across all migrated
765+ publishers (§4.9).
673766
674767** Costs (mitigatable but real):**
675768
0 commit comments