Skip to content

fix(traits): resolve JSON trait key collisions deterministically - #67

Merged
wizzomafizzo merged 1 commit into
mainfrom
fix/deterministic-json-trait-merge
Aug 29, 2026
Merged

fix(traits): resolve JSON trait key collisions deterministically#67
wizzomafizzo merged 1 commit into
mainfrom
fix/deterministic-json-trait-merge

Conversation

@wizzomafizzo

Copy link
Copy Markdown
Member

Addresses both CodeRabbit findings on #66.

The bug

v0.17.0 started normalizing **traits JSON keys, which lets two keys differing only in case land on one trait. The merge wrote both through a map, so Go's randomized iteration order decided which value survived. Parsing **traits:{"Tap":true,"tap":false} 200 times returned false 172 times and true 28 times — same input, different result.

This is a regression from #66. Before it, the JSON path stored keys verbatim, so those stayed two distinct keys and the outcome was stable.

Why not preserve JSON member order

CodeRabbit suggested preserving member order and applying last-wins, to match the inline #key syntax. That isn't available: parseJSONArg (arguments.go:71-80) unmarshals every JSON argument into any and re-marshals it, which sorts object keys alphabetically. Member order is gone before the traits merge ever runs — {"Tap":true,"tap":false,"TAP":"last"} arrives as {"TAP":"last","Tap":true,"tap":false}.

So a contested trait is dropped rather than resolved. That is order-independent, which means it stays correct however that upstream normalization behaves, and it matches how the parser already treats contradictory input elsewhere. Uncontested keys in the same object are unaffected.

Also

AGENTS.md redeclared an existing err with := in the new example; it now uses a scoped if err := ...; err != nil.

Tests

  • TestParseTraitsJSONKeyCollisionIsDeterministic — 200 parses per input, asserting a stable result. Fails on v0.17.0.
  • TestParseTraitsJSONKeyCollisionDropsTrait — the contested trait is dropped, uncontested keys survive.
  • TestParseTraitsJSONDecoding — null, empty object, value types, and non-object JSON keep their existing behavior.

task lint-fix 0 issues, task test (race) passing, FuzzParseScript clean for 30s.

v0.17.0 started normalizing **traits JSON keys, which lets two keys
differing only in case land on one trait. The merge then wrote both
through a map, so Go's randomized iteration order decided the survivor:
parsing {"Tap":true,"tap":false} 200 times returned false 172 times and
true 28 times.

There is no member order to break the tie by. JSON arguments are
normalized through a map in parseJSONArg before the traits merge sees
them, so the object arrives with its keys already sorted. Drop a
contested trait instead of picking a winner, which stays correct however
that upstream normalization behaves.

Also fix the AGENTS.md example, which redeclared an existing err with :=.
@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 40 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5efcff37-8f96-471a-8ce6-29378752fae4

📥 Commits

Reviewing files that changed from the base of the PR and between 1837a8e and 3c16c66.

📒 Files selected for processing (3)
  • AGENTS.md
  • parser.go
  • validate_test.go

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@wizzomafizzo
wizzomafizzo merged commit 1a882e9 into main Aug 29, 2026
12 checks passed
@wizzomafizzo
wizzomafizzo deleted the fix/deterministic-json-trait-merge branch August 29, 2026 23:30
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