Skip to content

Harden Austrian swap-link markers#4

Merged
tobomobo merged 1 commit into
mainfrom
harden-at-swap-markers
Apr 22, 2026
Merged

Harden Austrian swap-link markers#4
tobomobo merged 1 commit into
mainfrom
harden-at-swap-markers

Conversation

@tobomobo
Copy link
Copy Markdown

Summary

Two classes of silent-underreporting bugs surfaced by an adversarial review of the recent Austrian swap-neutrality work are addressed here.

  • Tokenized marker parsing. notes marker detection switched from substring matching to exact-token matching on the documented separators ( \t\n,). Unrelated text like prefixed_at_swap_link=foo no longer flips the regime or triggers Neu swap neutrality. Duplicate/conflicting markers raise RP2ValueError instead of silently resolving to the first match.
  • Cross-asset pairing validator. New AbstractCountry.validate_input_data hook (default no-op; upstream-friendly) called from rp2_main before per-asset accounting. AT overrides to run validate_at_swap_link_pairing, which asserts every at_swap_link=<id> appears on exactly one outgoing and one incoming leg across two different assets — catching the class of bug Kassiber structurally cannot catch (a leg that never made it into its input at all, e.g. partial wallet export). Events tagged at_regime=alt are skipped (Alt swaps are regime-breaking per § 27b EStG; the marker is a no-op there). rp2_main now parses every configured asset for validation, then restricts accounting/reporting to --asset if set, so narrowed diagnostic runs don't misreport valid paired swaps as orphaned.
  • Same-event kind check. at_swap_link= on a non-SELL Neu disposal (GIFT/DONATE/FEE/LOST/STAKING) now raises — swap neutrality is the § 27b Abs 3 Z 2 EStG sale carveout and has no pairable incoming leg for non-sale dispositions.

AGENTS.md is updated to document tokenized matching, duplicate/conflict rejection, the SELL-only constraint, and the new pairing guarantee. The Known Gaps entry now correctly identifies basis-value verification (still trusted to Kassiber) as the remaining gap rather than basis-pairing (now enforced on RP2's side).

Test plan

  • Unit tests: 237 pass (20 new, covering tokenized matching, duplicate/conflict rejection, separator coverage, non-SELL swap-link rejection, orphan/same-asset/duplicate pairing rejection, and Alt-skip semantics).
  • mypy src tests clean.
  • pylint -r n rated 10.00/10 on changed files.
  • bandit -r src clean.
  • pre-commit run passes on all changed files.
  • End-to-end smoke test: rp2_at -o /tmp/... config/crypto_example.ini input/crypto_example.ods with and without -a BTC.
  • Review whether the AGENTS.md update reflects the intended long-term contract before merging.

🤖 Generated with Claude Code

Addresses two classes of silent-underreporting bugs flagged in an
adversarial review of the recent AT swap-neutrality work:

1. Loose `notes` parsing. Marker detection used substring matching, so
   unrelated free-form text (e.g. `prefixed_at_swap_link=foo`) could flip
   the regime or trigger Neu swap neutrality by accident. Now tokenizes
   on the documented separators (` \t\n,`) and matches exact tokens
   only. Duplicate/conflicting markers raise instead of resolving to
   the first match.

2. No cross-asset validation. A missing incoming leg on a crypto-to-crypto
   swap (partial wallet export, Kassiber emission bug) would silently
   produce a zero-gain disposal with no basis carry anywhere. Adds an
   `AbstractCountry.validate_input_data` hook (default no-op, upstream-
   friendly) invoked from rp2_main before per-asset accounting. `AT`
   overrides to run `validate_at_swap_link_pairing`, which asserts every
   `at_swap_link=<id>` appears on exactly one outgoing and one incoming
   leg on two different assets. Events tagged `at_regime=alt` are
   skipped (Alt swaps are regime-breaking per § 27b EStG — the marker
   is a no-op there). `rp2_main` now parses every configured asset for
   validation, then restricts accounting/reporting to `--asset` if set,
   so narrowed diagnostic runs don't misreport valid paired swaps as
   orphaned.

Also adds a same-event kind check: `at_swap_link=` on a non-SELL Neu
disposal (GIFT/DONATE/FEE/LOST/STAKING) raises, since swap neutrality
is the § 27b Abs 3 Z 2 EStG *sale* carveout and has no pairable
incoming leg for non-sale dispositions.

AGENTS.md updated to document tokenized matching, duplicate/conflict
rejection, the SELL-only constraint, and the new pairing guarantee.
The "RP2 does not perform cross-asset validation" language is
replaced with the new contract; the Known Gaps entry now correctly
identifies basis-*value* verification (still trusted) as the remaining
gap, not basis-*pairing* (now enforced).

237 tests pass, mypy clean, pylint 10/10, bandit clean, pre-commit
passes. Smoke-tested with and without `--asset` on the sample dataset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tobomobo tobomobo merged commit 23c9449 into main Apr 22, 2026
35 checks passed
@tobomobo tobomobo deleted the harden-at-swap-markers branch April 22, 2026 11:19
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