Fix issues #002-#006 found by the LLM test harness
- #006 (high, data loss): make_room_for() no longer deletes unkept artifacts on a doomed request - checks whether eviction can possibly succeed before deleting anything. Also fixed a related latent bug found while testing: the function returned evicted Artifact objects after calling .delete() on them, so their .id was always None (Django clears pk on delete); now returns ids captured beforehand. - #003: added server-side field-length validation (source/title/ source_name/source_id/theme) on POST and PATCH, matching the model's own max_length limits, which were previously unenforced on the ingest path entirely. - #004: ported daybook_post.py's content-shape validation (html_fragment/html_full/deck/markdown) to the server, closing the gap for any client that doesn't go through the helper script. - #005: PATCH now rejects wrong-typed `tags` (previously silently wiped existing tags) and rejects immutable fields (content/source/kind/day) with 400 instead of silently no-op'ing them. - #002: deleted the misleading skills/daybook-posting/.env.example (a zero-dependency script that never reads .env files) and clarified SKILL.md/docs/llm_posting_helper.md that these must be real process/shell env vars. Adds tests/unit/ for storage-layer tests. 46 tests passing (was 25). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
c1ea5c7990
commit
07328a3412
13 changed files with 635 additions and 35 deletions
72
issues/005-patch-silently-mishandles-invalid-input.md
Normal file
72
issues/005-patch-silently-mishandles-invalid-input.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# 005 — `PATCH /api/artifacts/{id}` silently mishandles invalid/immutable input instead of rejecting it
|
||||
|
||||
**Status:** Fixed
|
||||
|
||||
## Description
|
||||
|
||||
`ArtifactDetailView.patch` (`artifacts/views.py`) applies whatever
|
||||
recognized keys are present in the request body without validating their
|
||||
type or rejecting fields it can't act on. Two confirmed effects:
|
||||
|
||||
**1. A wrong-typed `tags` value silently wipes existing tags (data loss).**
|
||||
|
||||
`normalize_tags()` returns `[]` for anything that isn't a `str` or
|
||||
`list`. `ArtifactDetailView.patch` calls it unconditionally whenever
|
||||
`"tags"` is a key in the request body, with no type check first.
|
||||
|
||||
Reproduced:
|
||||
|
||||
```bash
|
||||
# Artifact has tags ["important", "keep-me"]
|
||||
curl -X PATCH .../api/artifacts/a74de286 -d '{"tags": 12345}' ...
|
||||
# -> 200, tags silently become []
|
||||
```
|
||||
|
||||
An agent that sends a malformed `tags` value (e.g. a stray int from a bad
|
||||
template substitution) gets a `200` and loses the artifact's existing
|
||||
tags with no indication anything went wrong.
|
||||
|
||||
**2. Immutable fields (`content`, `source`, `kind`, `day`) are silently
|
||||
no-ops instead of rejected.**
|
||||
|
||||
`docs/api.md`'s "Not editable yet" table lists these fields and why, but
|
||||
doesn't say what happens if a client sends them anyway. In practice:
|
||||
`PATCH` with `{"content": "...", "source": "...", "kind": "...", "day":
|
||||
"..."}` returns `200` with the artifact **unchanged** — the fields are
|
||||
silently dropped, not rejected. An agent attempting to fix a typo in
|
||||
`content` via `PATCH` (a very natural thing to try, since `PATCH` is the
|
||||
only mutate-after-create endpoint) gets a success response that looks
|
||||
like it worked but did nothing.
|
||||
|
||||
Both are instances of the same root cause: `patch()` doesn't validate
|
||||
before applying, unlike `POST`'s `validate_source_fields()`.
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
- Type-check `tags` (must be `str` or `list`) before calling
|
||||
`normalize_tags`; return `400` on other types instead of silently
|
||||
emptying it. Same principle likely applies to `metadata` (already
|
||||
checked) and could apply to `source_prompt`/`source_name`/`source_id`
|
||||
(currently accept anything via implicit str coercion via Django, but
|
||||
worth a pass).
|
||||
- Either make sending an immutable field (`content`/`source`/`kind`/`day`)
|
||||
in a `PATCH` body a `400` ("field is not editable"), or explicitly
|
||||
document that unknown/immutable keys are ignored — right now the
|
||||
behavior is undocumented and easy to mistake for success.
|
||||
|
||||
## Resolution
|
||||
|
||||
Went with explicit rejection for both, over silent-ignore-and-document:
|
||||
|
||||
- `tags` is now type-checked (`str` or `list`) before `normalize_tags` runs;
|
||||
any other type returns `400` and leaves existing tags untouched.
|
||||
- Sending `content`, `source`, `kind`, or `day` in a `PATCH` body now returns
|
||||
`400` naming the offending field(s), via a new `IMMUTABLE_PATCH_FIELDS`
|
||||
check at the top of `ArtifactDetailView.patch`, before any other
|
||||
processing — chosen over documenting the no-op because a `200` that does
|
||||
nothing is easy to mistake for success.
|
||||
|
||||
`docs/api.md`'s "Not editable" table now says so explicitly instead of just
|
||||
listing reasons. Tests added: wrong-typed `tags` is rejected and leaves
|
||||
existing tags intact, and each of the four immutable fields is rejected
|
||||
individually.
|
||||
Loading…
Add table
Add a link
Reference in a new issue