Skip to content

fix: rules.Merge no longer panics when the base declares no domains (iss-187) - #205

Merged
REPPL merged 1 commit into
mainfrom
bugfix/iss-187-rules-merge-nil-domains-panic
Aug 6, 2026
Merged

fix: rules.Merge no longer panics when the base declares no domains (iss-187)#205
REPPL merged 1 commit into
mainfrom
bugfix/iss-187-rules-merge-nil-domains-panic

Conversation

@REPPL

@REPPL REPPL commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Fixes iss-187 (minor), surfaced during a bug-hunt loop sweep (round 1) and verified with a failing test.

The bug

rules.Merge(base, over RuleSet) RuleSet (internal/core/rules/rules.go) wrote override domain keys straight into out.Domains without ever allocating that map. cloneRuleSet deliberately preserves a nil Domains map when the source has none, so a base such as RuleSet{SchemaVersion: 1} — a Validate-accepted, valid RuleSet — panicked with assignment to entry in nil map the moment the overlay carried a domain key, contradicting Merge's own doc comment ("New domain keys are added"), which promises no such precondition.

Not reachable in production today: the sole call site, Load, always calls Merge(Defaults(), over), and Defaults() always has a populated Domains map. This is a latent defect in an exported API contract — it goes live the moment any caller merges onto a non-Defaults() base (e.g. a future multi-tier overlay starting from an empty RuleSet).

The fix

Merge now allocates out.Domains (via make) before the override loop when it's nil and the overlay has at least one key — matching the idiom the sibling loader guard.Merge (internal/core/guard/config.go) already uses for out.Entries. cloneRuleSet is untouched, so its nil-preserving semantics hold for every other current and future caller.

Evidence

  • internal/core/rules/rules.go — the allocation guard in Merge
  • internal/core/rules/rules_test.goTestMergeNilBaseDomainsAddsNewKeys, watched failing on pre-fix code for the claimed reason (panic: assignment to entry in nil map) and passing after
  • .abcd/work/issues/resolved/iss-187-rules-merge-nil-domains-panic.md — resolved via abcd capture resolve
  • CHANGELOG.md, .abcd/work/DECISIONS.md — updated

An independent adversarial correctness review of the full diff (fresh subagent, no context from the fix) walked all four base/overlay nil-combinations, reverted the fix locally to confirm the test fails for the exact claimed reason, checked the sole call site and cloneRuleSet for regressions, and checked for concurrency issues. Verdict: MERGE. No blocking findings.

make preflight green; gofmt -l . clean; go run ./cmd/record-lint 0 blockers.

rules.Merge wrote override domain keys straight into out.Domains
without ever allocating that map, because cloneRuleSet deliberately
preserves a nil Domains map when the source has none. A base such as
RuleSet{SchemaVersion: 1} — which Validate accepts — panicked with
"assignment to entry in nil map" the moment the overlay carried a
domain, contradicting Merge's own doc comment ("New domain keys are
added"). Not reachable today (RuleSet.Load always merges onto
Defaults(), which always has a populated map), but a latent defect in
an exported API contract.

Allocate out.Domains before the override loop when it's nil and the
overlay has at least one key, matching the idiom guard.Merge already
uses for out.Entries. cloneRuleSet keeps its nil-preserving semantics
for every other caller.

Resolves iss-187.

Assisted-by: Claude:claude-opus-4-8
@REPPL
REPPL merged commit c133c05 into main Aug 6, 2026
12 checks passed
@REPPL
REPPL deleted the bugfix/iss-187-rules-merge-nil-domains-panic branch August 6, 2026 13:29
@REPPL REPPL mentioned this pull request Aug 6, 2026
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.

2 participants