# 008 — `source_name`/`source_id`/`source_prompt` accept any JSON type and get silently mangled into `repr()` strings **Status:** Fixed Follow-up to the #007 handover, which specifically asked to probe other wrong-typed `PATCH` fields beyond `tags` now that #005 is fixed. ## Description Unlike `tags`, `metadata`, and `tag_colors` (all now type-checked, `tags` since #005), `source_name`, `source_id`, and `source_prompt` accept any JSON type — object, array, number — on both `POST` (`source_fields()` in `views.py`) and `PATCH` (`ArtifactDetailView.patch`). Nothing validates they're strings before assignment. `source_name`/`source_id` are `CharField`, `source_prompt` is `TextField`. Django's `Field.get_prep_value()` silently coerces non-string input via plain `str(value)` at save time — which for a `dict`/`list` produces **Python `repr()` syntax, not JSON**: `{"a": 1}` becomes the *string* `"{'a': 1}"` (single quotes, unparseable as JSON). This is lossy and irreversible — there's no way to recover the original structured value. **Worse: the API response is internally inconsistent.** Immediately after a `POST`/`PATCH` with a non-string value, the response echoes back the *raw, pre-save Python object* (still a real JSON object/array in the response body) — but a subsequent `GET` of the same artifact returns the mangled `repr()` string instead. Same request, two different apparent types for the same field depending only on *when* you read it. Reproduced: ```bash # PATCH source_name with an int, source_id with a list, source_prompt with a dict curl -X PATCH .../api/artifacts/a74de286 -d '{"source_name": 12345}' ... # -> 200, response shows "source_name": 12345 (int) curl .../api/artifacts/a74de286 # fresh GET, session auth # -> "source_name": "12345" (string) # Actual DB values (confirmed via shell): # source_name = '12345' # source_id = "['a', 'b']" <- repr() of the list, not JSON # source_prompt = "{'nested': 'object'}" <- repr() of the dict, not JSON ``` Also reproduced identically on `POST` (create), not just `PATCH` — a freshly created artifact's immediate response shows `"source_name": {"a": 1}` (real object) even though what's stored (and what any later `GET` returns) is the mangled string form. ## Suggested Fix Add a type check for `source_name`, `source_id`, and `source_prompt` (must be `str` or absent/null) alongside the existing `tags`/`metadata`/ `tag_colors` checks in `validate_source_fields` (create path) and `ArtifactDetailView.patch` (update path) — return `400` naming the field, consistent with #005's approach for `tags`. This also removes the response/DB inconsistency, since invalid types would never reach `save()` in the first place. ## Resolution Added `validate_string_fields(data, fields)` in `artifacts/views.py`, rejecting any non-`None`, non-`str` value with `400`. Called from `validate_source_fields` (covers `POST`, both text and binary paths) and directly in `ArtifactDetailView.patch` for `source_name`, `source_id`, `source_prompt` — matching #005's approach for `tags`. Invalid values never reach `save()`, so the response/DB inconsistency the issue described can no longer occur. Verified live against the running dev instance and with new tests in `tests/api/test_artifacts.py`.