Skip to content

fix(config): say what is wrong when a path is keyed and replaced - #16

Merged
b2m9 merged 1 commit into
mainfrom
fix/equal-path-policy-diagnostic
Aug 13, 2026
Merged

fix(config): say what is wrong when a path is keyed and replaced#16
b2m9 merged 1 commit into
mainfrom
fix/equal-path-policy-diagnostic

Conversation

@b2m9

@b2m9 b2m9 commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Item 3 of the original four flagged design choices. The verdict there was keep the validation, fix the diagnostic.

The defect

keyBy: {"order.items": "id"} with replace: ["order.items"] threw:

replace path 'order.items' makes keyBy path 'order.items' unreachable

Nothing is unreachable. Both policies land on the same node — one says enter the list and reconcile it, the other says the list is opaque. They contradict rather than nest, so the wording sent the reader hunting for a nesting problem that doesn't exist. It also withheld the spelling that works, replace: ["order.items[]"], which isn't guessable.

Now:

path 'order.items' cannot be both keyed and replaced: keyBy enters the list, replace treats it as opaque; replacing matched items instead is spelled 'order.items[]'

Genuinely nested collisions keep the "unreachable" wording, which is accurate for them.

Why it names a spelling instead of prescribing a fix

My first attempt told the caller what to do — "use items[], or drop the keyBy". Both reviewers independently found configurations where that prescription throws again:

  • a keyBy nested below the list is stranded under either spelling
  • a second colliding replace path defeats the advice even with no nested policy

My second attempt special-cased the nested policy and claimed it "has to go either way" — also false, since dropping the replace keeps it, and that config is the README's own showcase example.

Two wrong fixes in a row showed the shape was wrong, not the wording. Guaranteeing a suggested edit compiles means solving the whole policy graph inside one error message. So the message reports the collision it found and names a spelling — both true whatever else is configured — and stops. The special-case detection was removed entirely, which is also less code than either failed attempt.

Verification

  • Message-only. Codex compared 43,510 option combinations against main, Fable 22,765: zero accept/reject differences, zero error-class differences, 2,882 message changes all of the equal-path shape.
  • Discriminator proven. Equal segment length is reached only after isPrefix succeeds, so it means identical paths — confirmed against unicode, embedded whitespace, control characters, quotes, and 200-segment paths.
  • Tests non-vacuous under mutation, including that not.toThrow(/regex/) genuinely inspects the thrown message rather than passing vacuously.
  • README documented neither this rejection nor its remedy; it does now.

125 tests, vp run check green.


Summary by cubic

Clarifies config validation when keyBy and replace target the same path. Old behavior mislabeled the collision as “unreachable”; new behavior reports a contradiction and names the item-swap spelling (path[]). Nested collisions still use “unreachable.”

  • In src/paths.ts, equal-path collisions now throw: “path '

    ' cannot be both keyed and replaced…; replacing matched items instead is spelled '

    []'”. Prefix collisions keep the “unreachable” error.

  • Tests add assertions for the new message and remove the equal-path case from the “unreachable” set.
  • README documents that keying and replacing the same path is rejected and explains the item-swap idiom.
  • Message-only change. Error class and merge semantics are unchanged. No migration required.

Written for commit 471fcd8. Summary will update on new commits.

Review in cubic

Configuring keyBy and replace on the same path reported that the
replace path "makes the keyBy path unreachable". Nothing is unreachable:
the two policies sit on the same node, one saying to enter the list and
the other saying it is opaque. They contradict rather than nest, and the
old wording sent the reader looking for a nesting problem that is not
there. It also withheld the spelling that works, which is not guessable.

The message now states the contradiction and names the item-swap
spelling. It deliberately stops there rather than prescribing an edit. A
first attempt did prescribe one, and both reviewers found configurations
where the prescription throws again: a keyBy nested below the list
strands under either replace spelling, and a second colliding replace
path defeats the advice even with no nested policy. Guaranteeing that a
suggested edit compiles would mean solving the whole policy graph inside
one error, so the message reports the collision it found and names a
spelling, both of which stay true whatever else is configured.

Genuinely nested collisions keep the unreachable wording, which is
accurate for them.

The README documented neither case at the same path, so it does now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 3 files

Re-trigger cubic

@b2m9
b2m9 merged commit 927d1eb into main Aug 13, 2026
3 checks passed
@b2m9
b2m9 deleted the fix/equal-path-policy-diagnostic branch August 13, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant