daybook/issues/013-handover-to-llm-test-round-2.md
peter 292c386209 Add round-2 handover doc for the LLM test project
Covers everything shipped since the #007 handoff: the json/csv kinds,
.xlsx conversion, public sharing, syntax highlighting/table styling, and
the #008-#012 fixes. Prioritizes round-3 probe suggestions by risk, with
sharing (the new unauthenticated surface) and the #010 quota race (no
automated coverage) called out as the highest-value targets.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 20:53:12 +01:00

6 KiB
Raw Permalink Blame History

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/<token>? 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 unshared 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/<token> 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 </script> 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.