UAT parser & merge robustness: phantom items, undefined-named gaps, silently-dropped merges #46

Closed
opened 2026-07-23 14:50:25 +00:00 by crenshawdev · 0 comments
crenshawdev commented 2026-07-23 14:50:25 +00:00 (Migrated from github.com)

Severity: low · Area: uat · surfaced in the post-v1.2.0 cross-model review sweep (bundled: three UAT parse/merge robustness gaps). Related to #35 (the high-severity newline-injection case in the same subsystem).

1. A numbered list in a non-item ### section becomes a phantom item

cadence-core/bin/lib/planning-files.mjs:266-277: parseUat splits on ### and, for each chunk, matches the item head with /^(\d+)\.\s+(.+?)\s*$/m — the /m flag lets it match a numbered line anywhere in the chunk, not just its first line. A hand-added ### Manual notes section containing 1. check the logs yields a phantom item (possibly with a duplicate k), which has no status so the checklist can never complete; the next writeUat then deletes the notes section and materializes the phantom as a real item.

2. A gap with neither k nor name becomes an item titled "undefined"

cadence-core/bin/planning.mjs:366: on uat merge, a gap that matches no existing item is pushed as { k: ++k, name: g.name, ... }. With no name, the item renders ### 2. undefined, status fail — a permanently-failing nameless item that blocks completion until deleted by hand. init/refresh validate their payloads; merge does not.

3. uat merge silently drops findings for non-pending items

cadence-core/bin/planning.mjs:346-372: the merge only fills items whose status is pending. A verifier pass or gap for an item already recorded (by a prior verifier or the user) is dropped with no count and no "N skipped, already recorded" note in the result. The "verifier never overwrites user results" invariant is correct, but the silent drop hides a real discrepancy between the verifier and the recorded state.

Fix direction

Anchor the item-head match to the first line of each ### chunk; validate merge gap payloads (require a k or name) as init/refresh already do; and surface a count of merge findings skipped because the item was non-pending.

**Severity:** low · **Area:** uat · surfaced in the post-v1.2.0 cross-model review sweep (bundled: three UAT parse/merge robustness gaps). Related to #35 (the high-severity newline-injection case in the same subsystem). ## 1. A numbered list in a non-item `###` section becomes a phantom item `cadence-core/bin/lib/planning-files.mjs:266-277`: `parseUat` splits on `### ` and, for each chunk, matches the item head with `/^(\d+)\.\s+(.+?)\s*$/m` — the `/m` flag lets it match a numbered line **anywhere** in the chunk, not just its first line. A hand-added `### Manual notes` section containing `1. check the logs` yields a phantom item (possibly with a duplicate `k`), which has no status so the checklist can never complete; the next `writeUat` then deletes the notes section and materializes the phantom as a real item. ## 2. A gap with neither `k` nor `name` becomes an item titled "undefined" `cadence-core/bin/planning.mjs:366`: on `uat merge`, a gap that matches no existing item is pushed as `{ k: ++k, name: g.name, ... }`. With no `name`, the item renders `### 2. undefined`, status `fail` — a permanently-failing nameless item that blocks completion until deleted by hand. `init`/`refresh` validate their payloads; `merge` does not. ## 3. `uat merge` silently drops findings for non-pending items `cadence-core/bin/planning.mjs:346-372`: the merge only fills items whose status is `pending`. A verifier pass or gap for an item already recorded (by a prior verifier or the user) is dropped with no count and no "N skipped, already recorded" note in the result. The "verifier never overwrites user results" invariant is correct, but the silent drop hides a real discrepancy between the verifier and the recorded state. ## Fix direction Anchor the item-head match to the first line of each `### ` chunk; validate merge gap payloads (require a `k` or `name`) as `init`/`refresh` already do; and surface a count of merge findings skipped because the item was non-pending.
Sign in to join this conversation.
No description provided.