S3 · Writing
Code builds the structure; the LLM writes only sentences. Those sentences carry several prohibitions.
Code deterministically builds a row skeleton from the design document, chunks of 25 rows get their sentences filled in parallel, and code merges the result back.
Why structure and sentences are split
Row count, numbering, column layout, group boundaries — all decided by code. The LLM writes only the sentence in each row.
Split that way, an LLM dropping a row or shifting the order gets caught at the merge, because the merger cross-checks index, source echo, hash, and count. It isn’t checking the sentence. It’s checking that the sentence is in the right place.
Chunks know nothing about each other. So when one chunk drifts, only that chunk re-runs, and every other result stays valid. That property is what made unattended completion possible — if one wobbling chunk in a 200-row run meant re-running the whole thing, a run nobody is watching would not be a workable idea.
An “or” means it isn’t one case
The most frequently tripped of the prohibitions on the expected-result column.
One case = one precondition + one action + one expected result. The moment an “or” appears, it isn’t one case.
- Branching result — “a loading screen or an error notice appears”
- Branching precondition — “with none owned, or one owned”
- Branching action — “when a tab is selected or a material is registered”
Two ways out. If the spec settles which one it is, split into two cases. If it doesn’t, don’t split — leave it marked as needing spec confirmation. Pick one arbitrarily and that decision is recorded nowhere.
There’s exactly one exception: a single result that enumerates permitted states — “exists as either confirmed or pending, and nothing else.” The result there is one thing (“consistency holds”) and the states are its condition. But only when that enumeration is settled in the spec.
It has to read like a sentence a person wrote
The second prohibition. Table names, enum type names, and internal decision variables never appear as the subject or verb of the sentence.
Functional QA does not go digging through table structures. Put an identifier in the subject position and the reader cannot tell what behaviour is being described.
Verify visibility condition type enums 0–6 each apply correctly and the decision is processed properly per type
Verify that when the visibility condition is 'level reached', it shows once the level is met and stays hidden until then (condition = level reached)
Enumerated values don’t get bundled into one case either. 0–6 each is not one case — it’s seven, or a few representative ones.
Abstract phrasing is caught by a banned-word list: works normally, correctly, naturally, without issue, appropriately. An expected result carrying one of those carries no guarantee that two people would judge it the same way. It has to be a screen change, a numeric change, or a state change.
The notes column is not a notepad
Third. The notes column accepts exactly five values: empty, to-be-implemented, low implementation priority, needs spec confirmation, and a confirmed spec-bug note.
Leave it unlocked and design-stage tags leak straight through — boundary-value markers, concurrency, session. Those mean something to the person who built it and are noise to everyone reading the sheet.
Each value also fixes the result columns automatically. To-be-implemented forces N/A; low priority stays not-run. Mixing those two skews the statistics — absent from the build and deferred by choice are not the same thing.
No pretending when the tool is missing
This stage and the next require a multi-agent orchestration tool. There is no fallback path.
So when the tool isn’t there, it stops immediately and reports that fact. Earlier outputs are preserved, so once the tool is available it resumes from here. Carrying on while imitating a capability you don’t have is the worst option available.