Finding
QueryParams.BuildFromRequest() parses the id query parameter but never reads uid, so all collection list endpoints silently ignore ?uid= and return unfiltered results.
Review Source: Live integration testing against the Go CSAPI server during OSHConnect-Python publisher fleet migration to the Go server.
Severity: P1-Critical
Category: API Design
Ownership: Upstream (SomethingCreativeStudios/connected-systems-go)
Problem Statement
The OGC 23-001 specification (§7.3) defines uid as a standard query parameter for filtering resources by their globally unique identifier. The Go server's base QueryParams struct parses id and maps it to both id and unique_identifier columns in the database, but it never reads the uid query parameter at all.
Affected code — internal/model/query_params/query_params.go:
// QueryParams.BuildFromRequest() — only reads "id", never "uid"
func (QueryParams) BuildFromRequest(r *http.Request) *QueryParams {
params := &QueryParams{
Limit: 10,
Offset: 0,
}
// ...
if ids := r.URL.Query().Get("id"); ids != "" {
params.IDs = strings.Split(ids, ",")
}
// ← No handling of r.URL.Query().Get("uid")
return params
}
Affected code — internal/repository/deployment_repository.go (representative of all repos):
func (r *DeploymentRepository) applyFilters(query *gorm.DB, params *queryparams.DeploymentsQueryParams, parentId *string) *gorm.DB {
if len(params.IDs) > 0 {
query = query.Where("id IN ? OR unique_identifier IN ?", params.IDs, params.IDs)
}
// The IDs field is the only one used for identity filtering.
// Since "uid" is never parsed into IDs (or any other field),
// ?uid=urn:example:sensor:001 is silently discarded.
}
Scenario:
# Server has 37 systems registered
# Filter by UID — should return only one
GET /systems?uid=urn:os4csapi:system:nws:KDEN:v1
# Expected: 1 system returned
# Actual: all 37 systems returned (first 10 due to default limit)
Impact: Any client using ?uid= to look up resources by their globally unique identifier gets the entire (paginated) collection back instead of the matching resource. This is a spec-compliance failure that affects all resource types: systems, datastreams, deployments, procedures, sampling features, properties, control streams, observations, and commands.
The OSHConnect-Python publisher bootstrap helpers rely on ?uid= for idempotent resource creation (check-before-create). The workaround was to fetch all resources with ?limit=1000 and filter client-side:
# Workaround in bootstrap_helpers.py
def find_by_uid(session, url, target_uid):
resp = session.get(f"{url}?limit=1000") # Can't rely on ?uid= filter
for item in resp.json().get("items", resp.json().get("features", [])):
uid = item.get("properties", {}).get("uid") or item.get("uid", "")
if uid == target_uid:
return item
return None
Ownership Verification
This code is pre-existing in the upstream SomethingCreativeStudios/connected-systems-go repository. The OS4CSAPI fork is currently synced with upstream (main branch is up to date).
Conclusion: This code is upstream.
Files to Modify
| File |
Action |
Est. Lines |
Purpose |
internal/model/query_params/query_params.go |
Modify |
~10 |
Add UIDs []string field and parse uid query parameter in BuildFromRequest() |
internal/repository/system_repository.go |
Modify |
~5 |
Add unique_identifier IN ? clause for UIDs in applyFilters() |
internal/repository/deployment_repository.go |
Modify |
~5 |
Same — add UID filtering |
internal/repository/datastream_repository.go |
Modify |
~5 |
Same — add UID filtering |
All other *_repository.go files with applyFilters |
Modify |
~5 each |
Same pattern across all resource types |
e2e/ |
Modify |
~30 |
Add E2E test for ?uid= filtering |
Proposed Solutions
Option A: Add UIDs field to QueryParams (Recommended)
// internal/model/query_params/query_params.go
type QueryParams struct {
IDs []string
UIDs []string // NEW: parsed from ?uid= parameter
Q []string
Limit int
Offset int
}
func (QueryParams) BuildFromRequest(r *http.Request) *QueryParams {
params := &QueryParams{
Limit: 10,
Offset: 0,
}
// ... existing code ...
if ids := r.URL.Query().Get("id"); ids != "" {
params.IDs = strings.Split(ids, ",")
}
// NEW: parse uid parameter
if uids := r.URL.Query().Get("uid"); uids != "" {
params.UIDs = strings.Split(uids, ",")
}
return params
}
// In each repository's applyFilters():
if len(params.UIDs) > 0 {
query = query.Where("unique_identifier IN ?", params.UIDs)
}
Pros: Minimal diff; follows existing pattern for id; spec-compliant; backward compatible
Cons: Requires touching every repository's applyFilters()
Effort: Small | Risk: Low
Option B: Map uid into existing IDs field
Since applyFilters() already does WHERE id IN ? OR unique_identifier IN ? for the IDs field, simply also parse uid into IDs:
if uids := r.URL.Query().Get("uid"); uids != "" {
params.IDs = append(params.IDs, strings.Split(uids, ",")...)
}
Pros: Zero changes to repositories; single-line addition in BuildFromRequest()
Cons: Conflates two semantically different parameters; could return unexpected results if a uid value happens to match a local id
Effort: Small | Risk: Low
Scope — What NOT to Touch
- ❌ 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 public API signatures unless the finding specifically requires it
- ❌ Do NOT add
uid filtering to single-resource GET endpoints (they use path-based ID lookup)
- ❌ Do NOT implement
uid as a path parameter — the spec defines it as a query parameter on list endpoints
Acceptance Criteria
Dependencies
Blocked by: Nothing
Blocks: Nothing
Related: #9 — Default pagination limit of 10 (compounds this problem — even ?id= returns only first 10 matches); ogc-client-CSAPI_2#167 — Client library default limit (client-side companion fix)
Operational Constraints
⚠️ MANDATORY: Before starting work on this issue, review docs/governance/AI_OPERATIONAL_CONSTRAINTS.md if available.
Key constraints:
- Precedence: OGC specifications → AI Collaboration Agreement → This issue description → Existing code → Conversational context
- No scope expansion: Fix the finding, nothing more
- Minimal diffs: Prefer the smallest change that satisfies the acceptance criteria
- Ask when unclear: If intent is ambiguous, stop and ask for clarification
- Respect ownership: This code is upstream — coordinate with the maintainer if contributing back
Ownership-Specific Constraints
If Upstream:
- Track the issue for potential future contribution or discussion with the maintainer
- If the fix is trivial and clearly beneficial, note in the issue that it could be offered as a separate upstream PR
References
| # |
Document |
What It Provides |
| 1 |
internal/model/query_params/query_params.go — BuildFromRequest() |
Root cause — uid param never parsed |
| 2 |
internal/repository/deployment_repository.go — applyFilters() |
Representative filter logic showing IDs used but no UIDs |
| 3 |
OGC 23-001 §7.3 — Query parameters |
Spec defines uid as a standard list filter parameter |
| 4 |
OSHConnect-Python publishers/bootstrap_helpers.py — find_by_uid() |
Real-world workaround demonstrating impact |
Finding
QueryParams.BuildFromRequest()parses theidquery parameter but never readsuid, so all collection list endpoints silently ignore?uid=and return unfiltered results.Review Source: Live integration testing against the Go CSAPI server during OSHConnect-Python publisher fleet migration to the Go server.
Severity: P1-Critical
Category: API Design
Ownership: Upstream (SomethingCreativeStudios/connected-systems-go)
Problem Statement
The OGC 23-001 specification (§7.3) defines
uidas a standard query parameter for filtering resources by their globally unique identifier. The Go server's baseQueryParamsstruct parsesidand maps it to bothidandunique_identifiercolumns in the database, but it never reads theuidquery parameter at all.Affected code —
internal/model/query_params/query_params.go:Affected code —
internal/repository/deployment_repository.go(representative of all repos):Scenario:
Impact: Any client using
?uid=to look up resources by their globally unique identifier gets the entire (paginated) collection back instead of the matching resource. This is a spec-compliance failure that affects all resource types: systems, datastreams, deployments, procedures, sampling features, properties, control streams, observations, and commands.The OSHConnect-Python publisher bootstrap helpers rely on
?uid=for idempotent resource creation (check-before-create). The workaround was to fetch all resources with?limit=1000and filter client-side:Ownership Verification
This code is pre-existing in the upstream
SomethingCreativeStudios/connected-systems-gorepository. TheOS4CSAPIfork is currently synced with upstream (mainbranch is up to date).Conclusion: This code is upstream.
Files to Modify
internal/model/query_params/query_params.goUIDs []stringfield and parseuidquery parameter inBuildFromRequest()internal/repository/system_repository.gounique_identifier IN ?clause for UIDs inapplyFilters()internal/repository/deployment_repository.gointernal/repository/datastream_repository.go*_repository.gofiles withapplyFilterse2e/?uid=filteringProposed Solutions
Option A: Add
UIDsfield toQueryParams(Recommended)Pros: Minimal diff; follows existing pattern for
id; spec-compliant; backward compatibleCons: Requires touching every repository's
applyFilters()Effort: Small | Risk: Low
Option B: Map
uidinto existingIDsfieldSince
applyFilters()already doesWHERE id IN ? OR unique_identifier IN ?for theIDsfield, simply also parseuidintoIDs:Pros: Zero changes to repositories; single-line addition in
BuildFromRequest()Cons: Conflates two semantically different parameters; could return unexpected results if a
uidvalue happens to match a localidEffort: Small | Risk: Low
Scope — What NOT to Touch
uidfiltering to single-resource GET endpoints (they use path-based ID lookup)uidas a path parameter — the spec defines it as a query parameter on list endpointsAcceptance Criteria
GET /systems?uid=urn:test:system:001returns only the system with that UIDGET /datastreams?uid=urn:test:ds:001returns only the matching datastreamGET /deployments?uid=urn:test:dep:001returns only the matching deployment?uid=urn:a,urn:breturns both matching resources?id=filtering still works unchangedmake test)?uid=filteringDependencies
Blocked by: Nothing
Blocks: Nothing
Related: #9 — Default pagination limit of 10 (compounds this problem — even
?id=returns only first 10 matches); ogc-client-CSAPI_2#167 — Client library default limit (client-side companion fix)Operational Constraints
Key constraints:
Ownership-Specific Constraints
If Upstream:
References
internal/model/query_params/query_params.go—BuildFromRequest()uidparam never parsedinternal/repository/deployment_repository.go—applyFilters()IDsused but noUIDsuidas a standard list filter parameterpublishers/bootstrap_helpers.py—find_by_uid()