feat!: make property tax a list of match/formula rules - #28
Merged
Conversation
The tax was a global `default-formula` plus `exempt-plot-threshold`, with `rules` as an optional per-property override. Scoping the tax to a subset of plots was not expressible: rule predicates have no negation, and a rule match removes a plot from the aggregate count, so the plots you want counted cannot be shielded from a catch-all. The tax is now the `rules` list and nothing else. Each rule pairs a tag predicate with the formula charged to the plots it claims: - every plot falls to the FIRST rule matching its tags; a plot matching no rule is untaxed and counted nowhere - each rule's formula is evaluated ONCE per owner, with <plots> bound to that owner's plot count in THAT rule, not their total holdings - `exempt-threshold` moves onto the rule, so each bracket carries its own - charges are summed and floored to the cent A server-wide tax is a rule with no `match`. Rules are aggregate rather than per-property, which loses nothing: a flat per-plot rate is `10 * <plots>`. A rule whose formula fails to parse is dropped and its plots fall through to the next match, so a typo under-charges rather than charging something unintended. BREAKING CHANGE: `default-formula`, `exempt-plot-threshold` and TaxSettings.DEFAULT_FORMULA are removed. An un-migrated taxes.yml has its `rules` key filled from the shipped defaults on startup, switching that server to the packaged rule. `rules: []` does not disable collection — Configurate treats an empty list as absent, so the default returns on the next restart; `enabled: false` is the off-switch. Tests: 18 policy tests cover scoping, per-rule counting and exemption, rule ordering and drop-on-parse-failure. A new TaxesConfigTest loads the packaged taxes.yml as the plugin does and pins that it deserializes, that its formulas compile, that it charges the figures its own comments quote, that every tag it names exists in region-tags.yml, and both startup-merge behaviours above. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The tax was a global
default-formulaplusexempt-plot-threshold, withrulesas an optional per-property override. Scoping the tax to a subset of plots was not expressible: rule predicates have no negation, and a rule match removes a plot from the aggregate count, so the plots you want counted cannot be shielded from a catch-all.The tax is now the
ruleslist and nothing else. Each rule pairs a tag predicate with the formula charged to the plots it claims:exempt-thresholdmoves onto the rule, so each bracket carries its ownA server-wide tax is a rule with no
match. Rules are aggregate rather than per-property, which loses nothing: a flat per-plot rate is10 * <plots>. A rule whose formula fails to parse is dropped and its plots fall through to the next match, so a typo under-charges rather than charging something unintended.BREAKING CHANGE:
default-formula,exempt-plot-thresholdand TaxSettings.DEFAULT_FORMULA are removed. An un-migrated taxes.yml has itsruleskey filled from the shipped defaults on startup, switching that server to the packaged rule.rules: []does not disable collection — Configurate treats an empty list as absent, so the default returns on the next restart;enabled: falseis the off-switch.Tests: 18 policy tests cover scoping, per-rule counting and exemption, rule ordering and drop-on-parse-failure. A new TaxesConfigTest loads the packaged taxes.yml as the plugin does and pins that it deserializes, that its formulas compile, that it charges the figures its own comments quote, that every tag it names exists in region-tags.yml, and both startup-merge behaviours above.