Repository navigation
fix(wiki): refuse a draft refinement whose value is empty (#585) - #586
Conversation
The ADR-1068 refusal tested `is None`, so an empty value passed it and
update_draft wrote it over the draft: `lead: ""` blanks the lead, which
draft_curator then reads as a placeholder and scores 0.25 lower;
`frontmatter: {}` drops the synthesizer's `updated` stamp; `title: " "`
writes a blank title; `sections: []` would replace the sections of a kind
with no required section. Each also inserted a refined_llm memo.
_content_refusal now names every field whose value is empty (blank text,
empty list, empty mapping) and the call writes neither the draft nor a
memo (ADR-1069). An omitted field is untouched as before.
Five parametrized cases on SQLite cover blank lead, whitespace title,
empty sections, empty frontmatter, and a real lead paired with an empty
title; all five fail on the previous handler. Full suite: 8680 passed,
8 skipped.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cz7A6goxVzfKL9NT7k4zDH
|
ZETETIC-REVIEW: APPROVE SummaryFixes exactly the gap issue #585 describes. Stakes: Medium (bug fix in a handler with 1 non-test file changed plus docs; not touching auth/billing/schema; changed function is called from one place).1. Is
|
Summary
The ADR-1068 refusal in
handler_refine(mcp_server/handlers/wiki_refine.py) testedis None, so an empty value passed it andupdate_draftwrote it over the draft:lead: ""blanks the lead, whichdraft_curator._lead_is_placeholderthen reads as a placeholder, subtracting 0.25 from the draft's score.frontmatter: {}drops theupdatedstampcore/draft_synthesizer.pywrites.title: " "writes a blank title, scored at −0.3 byevaluate_draft.sections: []would replace the sections of a kind with no required section.Each also inserted a
refined_llmmemo recording a refinement that carried nothing._content_refusalnow names every field whose value is empty (blank text, empty list, empty mapping) and the call writes neither the draft nor a memo (ADR-1069). An omitted field stays untouched, as before; refusing by name rather than dropping the field keeps the caller's mistake visible.Found by the review of PR #584, which drove
lead: ""andfrontmatter: {}against a real SQLite draft.Closes #585
Type of change
Test plan
test_a_refine_carrying_an_empty_value_writes_nothingis parametrized over five shapes on SQLite: blank lead, whitespace title, empty sections list, empty frontmatter, and a real lead paired with an empty title. Each asserts the four content columns,synth_modeland the draft's memo count are unchanged, and that the response names the offending field. All five fail on the previous handler and pass here.draft_idalone) and the ADR-1065 confidence tests still pass.ruff check,ruff format --check: clean.pyright mcp_server/: 0 errors.scripts/check_craftsmanship.py --base origin/main: OK.scripts/check_doc_claims.py: OK.Audit notes
_is_emptytreats a string bystrip()and everything else by truthiness, so[]and{}are empty while0never reaches it: the four content fields are typed string, array and object in the tool schema.Coding-standards compliance
Breaking changes
A
wiki_refine_draftcall carrying an empty value used to returnupdated: trueafter blanking that field; it now returns an error. The tool has been registered only since #581, and the store holds onerefined_llmmemo, from a test runner in April 2026.Reviewer checklist
wiki_refine_drafttool description).🤖 Generated with Claude Code
https://claude.ai/code/session_01Cz7A6goxVzfKL9NT7k4zDH