Validate connector fetch boundaries with Zod and brand label types
- Dominant language
- TypeScript
- Stars
- 481
- Forks
- 108
- Avg merge
- 6d 8h
- Merged PRs (30d)
- 5
Description
## Description
Surfaced during the code review of the openCypher schema-sync batching change (see #2076). The connector fetch boundaries encode uncertainty in ways AGENTS.md discourages, and the fixes are cross query-language (Gremlin, openCypher, SPARQL), so they were deliberately kept out of that focused change. Capturing them here as tech debt.
## Improvements
### 1. Validate fetch responses at the boundary with Zod
`OpenCypherFetch` (and the Gremlin/SPARQL equivalents) return an unchecked cast — the caller asserts a `Raw*Response` shape that is never validated. The schema-sync response types then encode absence as `results?: …` plus a `?? []` fallback at each use. AGENTS.md: "Prefer Zod at boundaries to enforce contract and strong typing" and "Do not encode uncertainty as adapters, defaults, optionals, spreads, or catch blocks. Resolve it into the owned contract."
Resolve by schematizing the entity shapes (`OCVertex`/`OCEdge`, and Gremlin/SPARQL analogues) and parsing responses at the boundary: `results` defaults to `[]` (removing the optional and the `?? []`), and each row is shape-validated via `safeParse` (preserving skip-malformed behavior while replacing hand-rolled truthiness guards).
### 2. Dishonest element types vs. runtime guards
Response element types declare fully-formed entities (e.g. `object: OCVertex`) while the consuming loops still guard `if (!ocVertex || !ocVertex["~labels"])`. Either the type over-promises or the guard is dead. Boundary validation (item 1) makes this honest — a parsed row really is the entity, and malformed rows are filtered by the schema, not by ad-hoc key checks.
### 3. Primitive Obsession — labels as raw `string[]`
Vertex/edge labels flow as bare `string`/`string[]` and are used as lookup keys into `countsByLabel` and interpolated into query fragments. AGENTS.md: "Prefer a branded type over a raw `string`/`number` whenever the value is used for a lookup or passed to a function expecting a value that represents a specific concept — an ID, a node/edge label, a type name, etc." Introduce a branded label type constructed via its creator function and thread it through the schema-sync label maps and templates across connectors.
## Scope
Cross query-language and connector-wide (Gremlin, openCypher, SPARQL). The openCypher schema-sync path (#2076) is the natural first slice; the branded-label change in particular touches both the openCypher and Gremlin template/label code and should land consistently.
## Related Issues
- Surfaced from #2076
- Related to #2077
> [!IMPORTANT]
> Internal only — this issue is maintained by the core team and is not accepting external contributions.
Contributor guide
Research direction
Start with AGENTS.md and the openCypher schema-sync path from #2076, then trace the Gremlin and SPARQL connector fetch boundaries and their label maps and templates. Done means boundary responses are Zod-validated, malformed rows are skipped, optional results fall back at the boundary, and branded labels are used consistently across the connector paths.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- databases
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 15/100