Skip to content

Temporal query parameters (resultTime, phenomenonTime, datetime) silently ignored — all values discarded, including latest #11

@Sam-Bolling

Description

@Sam-Bolling

Issue evolution notice. This issue was originally filed as "resultTime=latest temporal filter not supported" based on the assumption the server rejected or errored on the latest keyword specifically. A subsequent empirical re-test against the live Go server (see the comment thread and below) showed the defect is broader and more silent than originally described: the entire temporal parameter parser is a no-op, not just the latest keyword. The body below has been rewritten to reflect the corrected diagnosis. The original filing is preserved verbatim in the collapsible section at the bottom for historical transparency; nothing is being hidden or retracted.

Finding

The Go CSAPI server accepts resultTime, phenomenonTime, and datetime query parameters on observation/datastream endpoints without error and without effect. Every value — valid ISO 8601 intervals, the latest keyword, far-future dates, and even obvious garbage — is silently discarded. The server returns its default unfiltered page in all cases. No 400, no 422, no warning, no log entry.

This is a conformance failure against OGC API — Connected Systems — Part 2: Dynamic Data (OGC 23-002r0), Clause 13.3.2, Requirement /req/advanced-filtering/obs-by-resulttime — not merely a missing latest keyword handler.

Review source: Live integration testing against the Go CSAPI server during the ISS map investigation, documented in CSAPI Go Server Integration Report §12–§13. Empirical re-test documented in the comments on this issue.

Severity: P2-Important (elevated from original P3-Minor — silent filter ignorance is a correctness hazard for any client relying on temporal slicing, not just latest).
Category: Standards Conformance.
Ownership: Upstream (SomethingCreativeStudios/connected-systems-go).

Normative requirement being violated

OGC 23-002r0, Clause 13.3.2 — Requirement /req/advanced-filtering/obs-by-resulttime:

A The HTTP GET operation at an Observation resources endpoint SHALL support a parameter resultTime.
B The parameter SHALL fulfill the same requirements as the parameter datetime defined in Clause 7.15.4 of OGC API — Features — Part 1: Core.
C Only the resultTime property of Observation resources SHALL be used to determine the temporal extent evaluated against the parameter.
D In addition to the possible parameter values defined in OGC API — Features — Part 1: Core, the parameter SHALL also support the special value latest. When this special value is used, only observations with the latest result time SHALL be included in the result set.

Parallel requirement for phenomenonTime at Clause 13.3.1 (/req/advanced-filtering/obs-by-phenomenontime) has the same A/B/C structure. Go fails A, B, C, and D.

  • Conformance test IDs: /conf/advanced-filtering/obs-by-resulttime, /conf/advanced-filtering/obs-by-phenomenontime (both would fail against the current implementation).
  • Draft URL: https://docs.ogc.org/DRAFTS/23-002r0.html (Clause 13.3.2)

Empirical evidence

Tested against production endpoint https://129-80-248-53.sslip.io/csapi-go on datastream db6756eb-e4ed-47e9-94c2-02eff5edf8d4 (ISS Position, 1000+ observations):

Request Expected Actual Verdict
?resultTime=latest 1 observation (per spec D) 10 items (default page, unfiltered) ❌ silently ignored
?resultTime=latest&limit=1 1 observation (the latest) 1 item, the newest (works by accident — Go's descending default sort + limit=1 happens to yield the correct answer) ⚠️ coincidental correctness
?resultTime=2099-01-01T00:00:00Z/2099-12-31T23:59:59Z 0 items (no future data) 10 items from today (unfiltered default page) ❌ silently ignored
?resultTime=frobnicate 400 Bad Request (per OGC Features §7.15.4 / OAS validation) 200 OK, 10 items (unfiltered) ❌ garbage accepted
?resultTime=2024-01-01T00:00:00Z/2024-01-02T00:00:00Z observations inside window 10 items from today (window ignored) ❌ silently ignored
?phenomenonTime=now observations at now (per spec §13.3.1 example) 10 items from today (unfiltered) ❌ silently ignored

The server NEVER errors and NEVER returns an empty list. It always returns the default unfiltered page, regardless of parameter value. This is what makes the defect dangerous: downstream clients have no signal that their filter was ineffective.

Impact on consumers

Because the server silently discards all temporal filter values, any consumer relying on temporal filtering for correctness (not just for "latest") will:

  • Process observations that are outside the requested time window, without knowing.
  • Miss windowing/retention logic (e.g., "only process the last 5 minutes").
  • Get correct-looking results from resultTime=latest&limit=1 today only because Go defaults to descending sort; a future change to the default sort order, or an addition of ascending support, would silently break every such consumer.

The CSAPI Explorer's MapViewPage.vue originally contained a 7-site fallback pattern (see ogc-client-CSAPI_2#168) that happened to mask this bug for the "latest observation" use case. That workaround is dead code today — the primary URL already returns data (because Go ignores latest but limit=1 survives) — and it does not protect against the range-filter case at all. The server bug is the actionable root cause.

Files to Modify

File Change Est. lines Notes
internal/model/query_params/query_params.go (or temporal parser) Modify ~40 Implement parsing for ISO 8601 instant, closed interval, half-open intervals (../, /..), and the special keywords latest and now. Reject unparseable values with 400.
internal/repository/observation_repository.go Modify ~20 Apply parsed temporal bounds to SQL WHERE result_time … / WHERE phenomenon_time … clauses. For latest, emit ORDER BY result_time DESC LIMIT 1.
internal/handler/ (or equivalent) Modify ~5 Return 400 with an OGC-style error body for malformed temporal values.
e2e/ Modify ~60 Add E2E tests per acceptance criteria below.

Proposed Solutions

Option A — Implement the full temporal parser correctly (Recommended)

Parse resultTime, phenomenonTime, and datetime values per OGC Features Part 1 §7.15.4, extended with the latest keyword for observations endpoints per OGC CS Part 2 §13.3.2 D. Apply as SQL range predicates; special-case latest as ORDER BY <timeCol> DESC LIMIT 1 merged with any other predicates/filters.

Pros: Fixes all four statements (A–D) of the requirement at once; matches SensorHub behavior; removes the need for any client-side workaround on any consumer.
Cons: Touches parser, validator, and repository.
Effort: Medium. Risk: Low (orthogonal to existing code paths; unparseable values currently produce no output anyway).

Option B — Keyword-only patch (NOT recommended)

Add a special case for the latest string only, leaving range parsing still broken.

Pros: Smallest possible diff.
Cons: Leaves statements A–C of /req/advanced-filtering/obs-by-resulttime unsatisfied; leaves phenomenonTime ranges and datetime on systems/deployments broken; leaves silent-discard-of-garbage intact. Will not pass /conf/advanced-filtering/obs-by-resulttime.
Effort: Small. Risk: High (false sense of compliance).

Scope — What NOT to Touch

  • ❌ Do NOT limit the fix to the latest keyword — statements A–C require the full range parser to work.
  • ❌ Do NOT change how other non-temporal query parameters are parsed unless they share the temporal parser path.
  • ❌ Do NOT implement latest for non-observation resource types; it is defined only for observations endpoints per §13.3.2 D.
  • ❌ Do NOT widen scope to uid=, subdeployments, or other unrelated filter bugs; they have their own issues.

Acceptance Criteria

  1. GET /datastreams/{id}/observations?resultTime=latestexactly the observation(s) with the maximum resultTime (spec §13.3.2 D).
  2. GET /datastreams/{id}/observations?resultTime=2024-01-01T00:00:00Z/2024-01-02T00:00:00Z → only observations whose resultTime intersects that closed interval.
  3. GET /datastreams/{id}/observations?resultTime=2024-01-01T00:00:00Z/.. → only observations at or after the instant (half-open interval).
  4. GET /datastreams/{id}/observations?resultTime=../2024-01-01T00:00:00Z → only observations at or before the instant.
  5. GET /datastreams/{id}/observations?resultTime=frobnicateHTTP 400 with an OGC-style error body; no 200-with-default-page.
  6. Equivalent behavior for phenomenonTime on observations endpoints per §13.3.1 (A/B/C; no latest keyword required for phenomenonTime).
  7. Equivalent behavior for datetime on systems/deployments/samplingFeatures endpoints per OGC 23-001r0 §8.7.
  8. The abstract test /conf/advanced-filtering/obs-by-resulttime passes (the test issues a bounded resultTime query and verifies every returned observation's resultTime falls inside the window).
  9. Existing tests still pass (make test).
  10. E2E test added for each of items 1–5.

Dependencies

  • Client-side companion (now a compatibility shim, not a bug): ogc-client-CSAPI_2#168. When this server issue ships and a new Go release is cut, the shim there can be deprecated and eventually removed.
  • Related server strictness patterns: #5, #7.

Operational Constraints

⚠️ MANDATORY: Before starting work on this issue, review docs/governance/AI_OPERATIONAL_CONSTRAINTS.md if available.

  • Precedence: OGC specifications → AI Collaboration Agreement → This issue description → Existing code → Conversational context.
  • No scope expansion; minimal diffs; ask when unclear.
  • This code is upstream — coordinate with the maintainer if contributing back.

References

# Reference Role
1 OGC 23-002r0 §13.3.2, /req/advanced-filtering/obs-by-resulttime (A–D) Normative requirement being violated
2 OGC 23-002r0 §13.3.1, /req/advanced-filtering/obs-by-phenomenontime Parallel requirement for phenomenonTime
3 OGC 23-001r0 §8.7, /req/api-common/datetime datetime query parameter (systems/deployments/samplingFeatures)
4 OGC 23-002r0 Annex A.6 — /conf/advanced-filtering/obs-by-resulttime Abstract test that currently fails
5 internal/model/query_params/query_params.go Root cause — temporal parser no-op
6 CSAPI Go Integration Report §12–§13 Full investigation context and empirical test log
7 ogc-client-CSAPI_2#168 Downstream client-side compatibility shim (now scoped as such)

Original filing (superseded — kept verbatim for historical transparency)

Finding (original)

resultTime=latest temporal query parameter is not implemented — the server either rejects or ignores the latest keyword, forcing clients to implement a multi-step fallback pattern at every call site.

Review Source: ISS map investigation during live integration testing against the Go CSAPI server (documented in CSAPI Go Server Integration Report §12).
Severity: P3-Minor
Category: API Design
Ownership: Upstream (SomethingCreativeStudios/connected-systems-go)

Problem Statement (original)

The OGC Connected Systems API specification defines resultTime=latest as a temporal filter shorthand to retrieve the most recent observation(s) from a datastream. The Go server does not implement this keyword — requests with resultTime=latest return empty results or an error instead of the latest observation.

Affected code — temporal parameter handling in internal/model/query_params/:

The BuildFromRequest() method parses resultTime as a time range but does not recognize the latest keyword as a special case. When latest is passed, it likely fails to parse as an ISO 8601 timestamp and is silently discarded, resulting in no temporal filter being applied or an empty result set.

Scenario:

# Datastream has 500+ observations spanning several days
GET /datastreams/{id}/observations?resultTime=latest

# Expected: 1 observation — the most recent by resultTime
# Actual: empty result set or parse error

# Workaround — client must use limit+sort:
GET /datastreams/{id}/observations?limit=1
# Returns the most recent observation (assuming default sort is newest-first)

Impact: Every client that uses resultTime=latest to fetch the current/most-recent observation must implement a fallback pattern. In the CSAPI Explorer (MapViewPage.vue), this required a retry block at 7 separate call sites:

// Fallback pattern repeated 7 times in MapViewPage.vue
let obs = await fetchObservations(dsId, { resultTime: 'latest', limit: 1 });
if (!obs || obs.length === 0) {
  obs = await fetchObservations(dsId, { limit: 1 });
}

SensorHub supports resultTime=latest natively, so this is a Go-server-specific gap that causes divergent client behavior when targeting different CSAPI backends.

Ownership Verification (original)

This is a missing feature in the upstream SomethingCreativeStudios/connected-systems-go repository. The OS4CSAPI fork is currently synced with upstream (main branch is up to date).

Conclusion: This is upstream — the temporal query parameter parsing has never included latest keyword support.

Files to Modify (original)

File Change Est. lines Notes
internal/model/query_params/query_params.go (or temporal parameter handler) Modify ~15 Add latest keyword recognition in temporal parameter parsing
internal/repository/observation_repository.go Modify ~10 Translate latest into ORDER BY result_time DESC LIMIT 1 query
e2e/ Modify ~20 Add E2E test for resultTime=latest

Proposed Solutions (original)

Option A: Translate latest to ORDER BY ... DESC LIMIT 1 (Recommended)

When the temporal parameter parser encounters latest as the value, instead of trying to parse it as an ISO 8601 timestamp, set a flag or special value that the repository layer translates into ORDER BY result_time DESC LIMIT 1.

Pros: Minimal change; matches SensorHub behavior; spec-aligned; no client workaround needed.
Cons: Requires touching both the query param parser and the repository layer.
Effort: Small. Risk: Low.

Option B: Rewrite latest to time range in middleware

Intercept resultTime=latest at the handler level and rewrite it to resultTime=<now-1s>/<now> (or a very recent time window), letting the existing temporal range logic handle it.

Pros: No repository changes needed.
Cons: Fragile — defining "latest" as a fixed time window is semantically wrong; may miss the actual latest observation if there's a gap.
Effort: Small. Risk: Medium.

Scope — What NOT to Touch (original)

  • ❌ Do NOT modify files outside the "Files to Modify" table above.
  • ❌ Do NOT refactor adjacent code that isn't part of this finding.
  • ❌ Do NOT change how other temporal parameters (phenomenonTime, validTime) are parsed unless they share the same parser path.
  • ❌ Do NOT implement latest for non-observation resource types (it only applies to temporal result sets).

Acceptance Criteria (original)

  • GET /datastreams/{id}/observations?resultTime=latest returns the single most recent observation by resultTime.
  • GET /datastreams/{id}/observations?resultTime=2024-01-01/2024-12-31 still works unchanged (explicit time ranges unaffected).
  • Existing tests still pass (make test).
  • E2E test added verifying resultTime=latest returns the most recent observation.

Dependencies (original)

Blocked by: Nothing. Blocks: Nothing.
Related: ogc-client-CSAPI_2#168 — Client library resultTime=latest fallback (client-side companion); #5 — Strict schema validation (related pattern of server strictness).

References (original)

# Reference Role
1 internal/model/query_params/query_params.go — temporal parsing Root cause — latest keyword not recognized
2 OGC 23-001 §9.4 — Temporal filtering Spec defines resultTime parameter including keyword values
3 CSAPI Explorer MapViewPage.vue — 7 fallback sites Real-world workaround demonstrating impact
4 ogc-client-CSAPI_2#168 Client-side companion issue
5 Integration Report §12 Investigation that discovered this gap

Why this framing was superseded

Empirical re-test showed the server does not reject or return empty for resultTime=latest; it silently returns its default unfiltered page, and does the same for every value of resultTime, phenomenonTime, and datetime. The defect is broader than the latest keyword. The spec reference was also sharpened from OGC 23-001 §9.4 (which covers systems/deployments temporal filtering) to OGC 23-002r0 §13.3.2 (which is the observations-specific requirement including the latest keyword mandate at statement D). Both corrections are reflected in the rewritten body above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions