diff --git a/issues/013-handover-to-llm-test-round-2.md b/issues/013-handover-to-llm-test-round-2.md new file mode 100644 index 0000000..4cae7ac --- /dev/null +++ b/issues/013-handover-to-llm-test-round-2.md @@ -0,0 +1,121 @@ +# 013 — Handover to AGENT_DAYBOOK__LLM_TEST (round 2) + +**Status:** For information — a handback covering everything shipped since +the round-1 handoff (#007). + +Round 1 (#007) closed out issues #002–#006. Since then, your round-2 +testing filed #008–#012 (all now fixed), and several new capabilities +landed. This doc summarizes all of it and points at where the fresh +surface area is worth probing. + +All of this is committed on `main` (pushed to Forgejo) and live on the +local dev instance you test against (`http://127.0.0.1:8001`) — Django's +autoreloader has already picked it up, no restart needed on your end. + +## New Capabilities To Exercise + +These are new since #007 and largely untested by you yet: + +1. **`json` and `csv` artifact kinds** (commit `e5d2f19`). Text kinds like + markdown. `json` renders as a collapsible tree (renderjson), `csv` as a + sortable/searchable table (Grid.js). Both validate server-side (`json` + must parse; `csv` only needs non-blank content) and preserve original + text as a `?raw=1` sidecar. Both vendored libs are served from + `/static/vendor/…`. + +2. **`.xlsx` upload → `csv`** (commit `4b5ec09`). Uploading an `.xlsx` file + (multipart, like any file) is converted server-side via openpyxl into a + `csv` artifact rather than stored as `binary`. Active sheet only; cached + formula results only; `.xls` unsupported; corrupt file → `400`. + +3. **Per-artifact public sharing** (commit `91ea19d`). Owner toggles a + share link from the feed UI; `GET /s/{share_token}` then serves that one + artifact with **no authentication at all**. `is_shared`/`share_url` now + appear in every artifact API response. Embedded images in a shared + artifact are also served unauthenticated while it's shared. **This is + the highest-value target for adversarial testing** — see below. + +4. **Syntax-highlighted code + styled tables** (commits `af9ecf2`, + `696cfe7`). Markdown fenced code blocks get Pygments highlighting; + tables in markdown and `html_fragment` render styled. Cosmetic, low-risk. + +5. **Posting script supports `json`/`csv`/`.xlsx`** (commit `e137725`). + `daybook_post.py --kind json/csv`, positional auto-detect of + `.json`/`.csv`/`.xlsx`. + +6. **Light/dark UI toggle** (commit `9a3b381`). UI-chrome only, no API + surface — not relevant to API testing. + +## Fixes Since #007 (issues you filed) + +- **#008** — `source_name`/`source_id`/`source_prompt` now reject + non-string values with `400` instead of silently mangling them. +- **#009** — the `html_fragment` content-shape check is substring-based; + documented as a known limitation rather than switched to real HTML + parsing (deliberate — matches the client helper's own approach). +- **#010** — concurrent posts could race past quota; fixed with a + write-lock-before-read + atomic transaction. Verified live but **not** + covered by an automated test (SQLite shared-cache test DB behaves + differently — see the issue). Worth re-probing. +- **#011** — CDN snippets documented for `html_full`; `--theme` help typo + fixed. +- **#012** — posting script gained `json`/`csv` support. + +## Suggested Round-3 Probes + +Prioritized by where the new surface area is riskiest. + +### Sharing (#91ea19d) — security boundary, probe hardest + +- Can an **unshared** artifact ever be reached via `/s/`? Brute/ + guessed/empty/malformed tokens should all `404`. +- Does **unsharing immediately** kill the old link? (Confirmed manually, + but re-verify — and try racing an unshare against an in-flight request.) +- Is `share_token` ever leaked in a response for an artifact you *don't* + own, or in the list endpoint, or anywhere a non-owner could see it? +- Can a shared artifact's **embedded assets** be reached unauthenticated — + and, importantly, can an *un*shared artifact's assets be reached that way + (they must not)? Also: does an orphaned/unattached asset stay private + even when some unrelated artifact is shared? +- Does `?raw=1` on `/s/` correctly serve raw source for the kinds + that support it, and not leak anything for kinds that don't? +- Cross-user: user A shares an artifact; can user B toggle/​unshare it, or + only view it? + +### json / csv (#e5d2f19) + +- `json`: valid-but-not-an-object payloads (bare array, string, number, + `null`); deeply nested / very large; unicode; the `` injection + vector (confirmed handled for json — verify **csv** escapes its embedded + data block the same way, it's a separate code path). +- `csv`: ragged rows (fewer/more columns than the header), embedded commas/ + quotes/newlines, duplicate column names, header-only, empty, a single + giant cell, BOM/encoding oddities. +- Quota accounting: json/csv now write a sidecar file — confirm size + accounting and eviction include/delete the sidecar (this generalized the + markdown-only logic; worth verifying it didn't regress markdown either). + +### .xlsx conversion (#4b5ec09) + +- Multi-sheet workbook — verify only the active sheet survives and the + others are silently dropped (documented behavior, confirm it's true). +- Formula cells never calculated by a real spreadsheet app (openpyxl + doesn't evaluate) → should be blank, not error. +- A non-`.xlsx` file renamed to `.xlsx`; a real `.xlsx` sent with a wrong/ + generic content type; a huge sheet; empty workbook; `.xls` (must not + convert). +- Field mangling regression: this path rebuilds the payload dict manually + because multipart `QueryDict` + `{**data}` was silently list-wrapping + values (the bug that made this a multi-fix). Re-probe that title/tags/ + source_* all come through correctly on `.xlsx` posts specifically. + +### #010 quota race (no automated coverage) + +- Fire N truly-concurrent posts at a tight quota; confirm final usage never + exceeds quota and no artifact is left half-created (row without files or + files without row). + +As always: use the `test` / `testpwd` account, clean up what you create, +check `artifacts/views.py` before calling a surprise a bug (some, like +#009, are documented-by-design), and file anything real as a new numbered +issue (next is #014), linking back if it's a variant of a closed one.