Skip to content

fix(manifest): parse JSON pod manifests instead of silently no-opping (issue #74) - #93

Merged
dndungu merged 5 commits into
mainfrom
task/t3-1-issue74-json-ingestion
Aug 28, 2026
Merged

fix(manifest): parse JSON pod manifests instead of silently no-opping (issue #74)#93
dndungu merged 5 commits into
mainfrom
task/t3-1-issue74-json-ingestion

Conversation

@dndungu

@dndungu dndungu commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

POST /api/v1/pods with Content-Type: application/json returned 201 {"pods":null} and created nothing (issue #74: POST /api/v1/pods silently no-ops on a JSON body), because the endpoint always fed the raw body to a hand-rolled, YAML-only, line-based parser regardless of content type. This makes JSON manifests actually parse (JSON is documented as an accepted body format), and makes any manifest document that resolves to nothing a 400, never a silent 201.

Changes

  • internal/manifest/parse.go: Parse now sniffs the body's first non-whitespace byte; a { or [ routes the whole body through encoding/json instead of the YAML line-walker (JSON has no --- multi-document convention, so a JSON body is always exactly one document, or an array of documents). The existing kind-dispatch switch (Pod/Job/Deployment/StatefulSet/CronJob) is factored out into a shared parseDocument helper so both paths use identical logic — no changes were needed anywhere else in the manifest package, since encoding/json's native decode (map[string]interface{}, []interface{}, string, float64, bool) already satisfies every existing map-walking helper (getString's fallback already does fmt.Sprintf("%v", v) for non-string values, matching what call sites already expect from bare YAML scalars).
  • internal/manifest/parse.go: the YAML per-document loop used to silently continue past a document that parsed to an empty root map, returning a zero-value ParseResult with no error. Since splitDocuments already strips whitespace/comment-only chunks before a document reaches this point, a real-content document that yields zero fields is always a parser defect or an unsupported top-level shape (e.g. a bare list instead of a map) — never a legitimate no-op. This now returns an error instead. Same defect class as issue Flow-style YAML maps (resources: { cpu: "1", memory: 512Mi }) are silently dropped — pod admitted with zero requests #66 (docs/devlog.md's 2026-07-09 entry, "flow-style YAML maps silently dropped"): "every silent-default in the input path eventually admitted something dangerous. Any new parser branch must reject what it does not understand." This closes the YAML-side half of that standing lesson; the JSON path enforces the same rule for an empty/kind-less JSON document.
  • internal/manifest/parse_test.go: TestParse_ValidJSONPod (a pretty-printed JSON Pod manifest parses identically to the YAML equivalent), TestParse_EmptyResultDocumentIsError (an empty JSON {} document errors), TestParse_YAMLEmptyResultDocumentIsError (a bare top-level YAML list, which has no map, now errors instead of silently vanishing).
  • internal/api/pods_mutate_test.go: TestApplyPod_JSON (a real HTTP POST with a JSON body creates the pod and it's findable in the store — the exact symptom from the issue, now green) and TestApplyPod_MalformedJSON (an unparseable JSON body gets 400, never 201).
  • README.md: the "Apply Pod" section now documents Content-Type: application/json as an accepted alternative to YAML, with a matching curl example, and states the empty/no-kind document is a 400.
  • .gitignore: ignore local .kazi/ and .mcp.json tooling artifacts (unrelated housekeeping from the authoring session, kept as its own commit).

Out of scope, deliberately untouched: internal/manifest/yaml.go's hand-rolled YAML parser internals (JSON is routed around it, not into it), and internal/api/pods_mutate.go (the handler already content-sniffs by always calling manifest.Parse; it never read Content-Type and still doesn't — the fix lives entirely in the parser).

Testing

Run from a clean worktree at this branch's tip:

  • go build ./... — passes.
  • go vet ./... — passes.
  • staticcheck ./... — passes.
  • go test ./... -race -timeout 120s -count=1 — passes, all 13 packages, ~40s wall clock (internal/watcher is the slow package at ~37s; everything else is 1-4s).
  • go test ./internal/manifest/... -race -v and go test ./internal/api/... -race -v — the new tests observed red before each fix and green after (verified by hand for the YAML backstop commit; verified via kazi's t0/t-final predicate vector for the JSON-routing commits).

Not yet done (intentionally out of scope for this PR — the coordinator handles it centrally after merge): a live curl against the DGX host reproducing the exact issue #74 repro steps. Recipe for that:

# before merge/deploy: reproduces the bug (201, {"pods":null}, GET 404s)
# after: 201 with the pod listed, GET finds it
curl -i -X POST $SPARK/api/v1/pods \
  -H "Content-Type: application/json" \
  -d '{"apiVersion":"v1","kind":"Pod","metadata":{"name":"issue74-live-check"},"spec":{"containers":[{"name":"main","image":"alpine:latest","command":["sleep","60"]}]}}'
curl -i $SPARK/api/v1/pods/issue74-live-check
# cleanup:
curl -i -X DELETE $SPARK/api/v1/pods/issue74-live-check

Linked issues

Part of #74 (POST /api/v1/pods with a JSON body returns 201 {"pods":null} and creates nothing) — not closing it here; the coordinator closes it after live-verifying on the DGX per the recipe above.

Parse's YAML per-document loop silently skipped (continue) a document
that parsed to an empty root map, producing a zero-value ParseResult
with no error. splitDocuments already strips whitespace/comment-only
chunks before a document reaches this point, so a real-content
document that yields zero fields is always a parser defect or an
unsupported top-level shape (e.g. a bare list instead of a map) --
never a legitimate no-op.

Same defect class as issue #66 (docs/devlog.md, 2026-07-09): every
silent-default in the input path eventually admits something
dangerous. Closes the YAML-side half of the standing lesson; the JSON
path (issue #74) already rejects an empty/kind-less document.
Apply Pod now accepts application/json in addition to application/yaml
(issue #74).
@dndungu
dndungu merged commit d0504a7 into main Aug 28, 2026
1 check passed
@dndungu
dndungu deleted the task/t3-1-issue74-json-ingestion branch August 28, 2026 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant