Skip to content

feat: migrate 9 remaining guards to TaxDiagnosticResult (close #47)#48

Merged
Rahul Dass (rahuldass19) merged 3 commits into
mainfrom
feat/issue-47-migrate-9-guards-diagnostic
Jun 22, 2026
Merged

feat: migrate 9 remaining guards to TaxDiagnosticResult (close #47)#48
Rahul Dass (rahuldass19) merged 3 commits into
mainfrom
feat/issue-47-migrate-9-guards-diagnostic

Conversation

@rahuldass19

@rahuldass19 Rahul Dass (rahuldass19) commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Completes the TaxDiagnosticResult migration for all remaining 9 guards, achieving 12/12 to_diagnostic() coverage. This is the final v0.2.0 release blocker.

What changed

audit.py — 15 new RuleRef entries

  • Capital Gains: CG_EQUITY_LTCG_112A, CG_EQUITY_STCG_111A, CG_DEBT_FUND_50AA, CG_NO_RATE_CONFIGURED
  • Speculation: SPECULATIVE_43_5, SPECULATIVE_SETOFF_73
  • Inter-head Set-off: INTERHEAD_SETOFF_71, CAPITAL_GAINS_SETOFF_74
  • Crypto/VDA: VDA_115BBH, VDA_SETOFF_PROHIBITION
  • PoEM: POEM_SECTION_6_3, POEM_CBDT_6_2017
  • FEMA/LRS: LRS_LIMIT, FEMA_SCHEDULE_I, TCS_LRS_206CR
  • IRS (US): IRS_COMMON_LAW, W4_EXEMPT_PUB505
  • Valuation: SAFE_CONVERSION
  • Added JURISDICTION_US = "US" constant

9 guards migrated

# Guard File Key changes
1 CapitalGainsGuard guards/capital_gains_guard.py build_trace in verify_tax_rate, to_diagnostic()
2 ClassificationGuard (IRS) guards/classification_guard.py build_trace in verify_classification_claim, to_diagnostic()
3 SpeculationGuard guards/speculation_guard.py build_trace in verify_setoff, to_diagnostic()
4 InterHeadAdjustmentGuard india/guards/setoff_guard.py build_trace + _RULE_REFS map, to_diagnostic()
5 CryptoTaxGuard india/guards/crypto_guard.py audit_trace field added to TaxResult model, build_trace in both methods, to_diagnostic()
6 ValuationGuard guards/valuation_guard.py build_trace in verify_conversion, to_diagnostic()
7 RemittanceGuard guards/remittance_guard.py build_trace in verify_lrs_limit, to_diagnostic()
8 PoEMGuard guards/poem_guard.py build_trace in determine_residency, to_diagnostic()
9 WithholdingGuard us/withholding_guard.py build_trace in verify_exempt_status, to_diagnostic()

Pattern (same as #45)

  1. build_trace() called on every verification path with the relevant RuleRef
  2. audit_trace added to return dict (additive — existing callers unaffected)
  3. to_diagnostic() static method:
    • VERIFIED + audit_trace → TaxDiagnosticResult.verified() with proof_ref
    • BLOCKED → TaxDiagnosticResult.blocked() with fallback constraint_id
    • VERIFIED without audit_trace → raise ValueError (fail-closed)
  4. PoEMGuard uses unverifiable() instead of blocked() for invalid input paths

Tests — 40 new (tests/test_guard_diagnostics.py)

  • VERIFIED path: status, proof_ref, constraint_id
  • BLOCKED path: status, proof_ref is None, error message
  • UNVERIFIABLE path (PoEMGuard): status, proof_ref is None
  • Fail-closed: verified=True without audit_trace raises ValueError
  • Serialization round-trip (to_dict / from_dict) for 3 guards

Test results

  • 260 tests pass (220 existing + 40 new)
  • Zero behavioral changes — all existing tests pass unchanged

Migration status

to_diagnostic() coverage: 12 of 12 guards

Closes #47

Summary by CodeRabbit

  • New Features

    • Added comprehensive audit trail and evidence tracking across tax verification modules
    • Enhanced diagnostic reporting with detailed outcome tracking for capital gains, asset classification, residency, remittance, valuation, and cryptocurrency determinations
    • Expanded rule reference catalog to support US jurisdiction rules
  • Tests

    • Added test coverage validating diagnostic result conversion and reporting functionality

- Add RuleRef entries to audit.py for all 9 guards (CG, IRS, Speculation,
  InterHead, VDA, PoEM, FEMA/LRS, W4, SAFE)
- Add build_trace() calls + audit_trace to all verification paths
- Add to_diagnostic() static method to all 9 guards
- Fail-closed: raise ValueError when verified=True but audit_trace is None
- CryptoTaxGuard: add audit_trace field to TaxResult pydantic model
- 40 new tests covering VERIFIED, BLOCKED, UNVERIFIABLE paths
- 260 total tests pass (220 existing + 40 new)
- to_diagnostic() coverage: 12 of 12 guards
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rahuldass19, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 49 minutes and 46 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 733b8051-d76b-4b56-8efd-ec1c3e679cb0

📥 Commits

Reviewing files that changed from the base of the PR and between 82ca74d and 5211db2.

📒 Files selected for processing (6)
  • qwed_tax/guards/capital_gains_guard.py
  • qwed_tax/guards/classification_guard.py
  • qwed_tax/guards/speculation_guard.py
  • qwed_tax/jurisdictions/india/guards/crypto_guard.py
  • qwed_tax/jurisdictions/india/guards/setoff_guard.py
  • tests/test_guard_diagnostics.py
📝 Walkthrough

Walkthrough

This PR completes the TaxDiagnosticResult migration for 9 remaining guards. It adds JURISDICTION_US and new RuleRef constants to audit.py, then wires build_trace() into each guard's verification return paths and adds to_diagnostic() static methods, a ValueError fail-closed contract, and _UNVERIFIABLE_OUTCOMES sets. A 456-line test suite validates all conversion paths and serialization round-trips.

Changes

Guard Migration to TaxDiagnosticResult

Layer / File(s) Summary
Audit rule catalog
qwed_tax/audit.py
Adds JURISDICTION_US and 18 new RuleRef constants covering capital gains, speculation/set-off, VDA/crypto, PoEM, FEMA/LRS/TCS, IRS/W-4, and SAFE_CONVERSION. All guard layers depend on these.
India guards: capital gains, classification, speculation, valuation, remittance
qwed_tax/guards/capital_gains_guard.py, qwed_tax/guards/classification_guard.py, qwed_tax/guards/speculation_guard.py, qwed_tax/guards/valuation_guard.py, qwed_tax/guards/remittance_guard.py
Each guard's verification method now populates audit_trace via build_trace() on all return paths. Each gains _UNVERIFIABLE_OUTCOMES and to_diagnostic() that maps audit_trace.outcome to TaxDiagnosticResult.unverifiable/blocked/verified, raising ValueError when verified=True but audit_trace is absent.
PoEM guard
qwed_tax/guards/poem_guard.py
Attaches audit_trace to the domestic fast-path return, the final determine_residency payload, and _unverifiable() calls. Adds to_diagnostic() converting the residency dict to TaxDiagnosticResult.
India jurisdiction guards: crypto and inter-head set-off
qwed_tax/jurisdictions/india/guards/crypto_guard.py, qwed_tax/jurisdictions/india/guards/setoff_guard.py
Extends TaxResult with optional audit_trace. Threads rule-specific traces through verify_set_off and verify_flat_tax_rate in CryptoTaxGuard. Adds _RULE_REFS mapping TaxHead to rule constants in InterHeadAdjustmentGuard and populates audit_trace on all verify_setoff() branches. Both guards add to_diagnostic().
US withholding guard
qwed_tax/jurisdictions/us/withholding_guard.py
Adds explicit -> Dict[str, Any] return type to verify_exempt_status, includes audit_trace via W4_EXEMPT_PUB505 on Z3 SAT/UNSAT outcomes, and adds to_diagnostic() with the same fail-closed contract.
Test suite
tests/test_guard_diagnostics.py
Adds 9 guard-specific test classes covering VERIFIED/BLOCKED/UNVERIFIABLE outcomes, constraint_id and developer_fields assertions, ValueError on missing audit_trace, and to_dict()/from_dict() serialization round-trips.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Guard as Guard.verify_*()
  participant build_trace
  participant to_diagnostic as Guard.to_diagnostic()
  participant TaxDiagnosticResult

  Caller->>Guard: verify_*(inputs)
  Guard->>build_trace: build_trace(RuleRef, outcome, context_dict)
  build_trace-->>Guard: audit_trace {rule_id, statute, jurisdiction, outcome, ...}
  Guard-->>Caller: {verified, error?, audit_trace}

  Caller->>to_diagnostic: to_diagnostic(result)
  alt verified=True but audit_trace is None
    to_diagnostic-->>Caller: raises ValueError
  else outcome in _UNVERIFIABLE_OUTCOMES
    to_diagnostic->>TaxDiagnosticResult: .unverifiable(agent_msg, dev_fields)
    TaxDiagnosticResult-->>Caller: TaxDiagnosticResult(UNVERIFIABLE)
  else verified=False
    to_diagnostic->>TaxDiagnosticResult: .blocked(agent_msg, dev_fields)
    TaxDiagnosticResult-->>Caller: TaxDiagnosticResult(BLOCKED)
  else verified=True
    to_diagnostic->>TaxDiagnosticResult: .verified(constraint_id, statute, jurisdiction, evidence=audit_trace)
    TaxDiagnosticResult-->>Caller: TaxDiagnosticResult(VERIFIED)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • QWED-AI/qwed-tax#45: Introduced the TaxDiagnosticResult tri-state model and migrated the first 3 guards (TDS, ITC, GST) using the exact to_diagnostic() + build_trace() + fail-closed ValueError pattern that this PR extends to the remaining 9 guards.
  • QWED-AI/qwed-tax#37: Extended qwed_tax/audit.py with RuleRef/build_trace infrastructure and wired audit_trace into guard verdict payloads — directly the same audit.py catalog that this PR expands with new constants.
  • QWED-AI/qwed-tax#42: Modified the same verification methods (CapitalGainsGuard.verify_tax_rate, ClassificationGuard.verify_classification_claim, SpeculationGuard.verify_setoff, CryptoTaxGuard.verify_flat_tax_rate) that this PR now adds audit_trace and to_diagnostic() wiring to.

Poem

🐇 Hopping through the tax law maze,
Each guard now carries trace and proof,
audit_trace lights the verdict's blaze,
to_diagnostic lifts the roof!
No verified=True without a clue —
The rabbit checks, and ValueError's due. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.51% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: migrating 9 remaining guards to TaxDiagnosticResult model and closing issue #47.
Linked Issues check ✅ Passed The PR fully meets all acceptance criteria from issue #47: all 9 guards have to_diagnostic() methods, audit_trace is integrated via build_trace() calls, fail-closed validation is implemented, comprehensive tests are added covering VERIFIED/BLOCKED/UNVERIFIABLE paths, and full 12-guard coverage is achieved.
Out of Scope Changes check ✅ Passed All changes align directly with issue #47 objectives: RuleRef additions, build_trace() integration, to_diagnostic() implementations, fail-closed validation, TaxResult model enhancement, and test coverage for the 9 guards.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-47-migrate-9-guards-diagnostic

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 and usage tips.

Comment thread tests/test_guard_diagnostics.py Fixed
Comment thread qwed_tax/guards/poem_guard.py
Comment thread qwed_tax/jurisdictions/india/guards/setoff_guard.py
@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR completes the TaxDiagnosticResult migration for the 9 remaining guards, achieving 12/12 to_diagnostic() coverage. The consistent pattern — build_trace on every verification path, _UNVERIFIABLE_OUTCOMES frozenset for ambiguous/unknown cases, and a fail-closed ValueError for VERIFIED results missing an audit_trace — is applied correctly across most guards.

  • 15 new RuleRef constants added to audit.py covering capital gains, speculation, set-off, crypto, PoEM, FEMA/LRS, IRS, and valuation domains.
  • 9 guards migrated with build_trace instrumentation and to_diagnostic() static methods; 40 new tests added covering VERIFIED, BLOCKED, UNVERIFIABLE, fail-closed, and serialisation round-trip paths.
  • One incorrect statutory citation in InterHeadAdjustmentGuard._RULE_REFS: VDA losses map to INTERHEAD_SETOFF_71 (Section 71) but the governing provision is VDA_SETOFF_PROHIBITION (Section 115BBH(2)); the fix requires importing VDA_SETOFF_PROHIBITION in setoff_guard.py and updating the map entry.

Confidence Score: 4/5

Safe to merge after correcting the VDA statutory reference in InterHeadAdjustmentGuard; all other guards are correctly implemented.

The InterHeadAdjustmentGuard._RULE_REFS map cites Section 71 for VDA losses whose prohibition is actually governed by Section 115BBH(2). Every call to verify_setoff(TaxHead.VDA, ...) will embed the wrong rule_id and statute in the audit_trace, and the diagnostic constraint_id will say INTERHEAD_SETOFF_71 instead of VDA_SETOFF_PROHIBITION. Because audit traces feed the proof_ref hash and are surfaced to compliance reviewers, this is a factual error in the trail, not just a label. The test test_blocked_vda_lapses passes today because it never asserts on constraint_id, so the mismatch is invisible. The remaining eight guards and their tests are correctly implemented.

qwed_tax/jurisdictions/india/guards/setoff_guard.py — the _RULE_REFS entry for TaxHead.VDA and the missing VDA_SETOFF_PROHIBITION import.

Important Files Changed

Filename Overview
qwed_tax/jurisdictions/india/guards/setoff_guard.py Adds _RULE_REFS map and build_trace calls to verify_setoff, plus to_diagnostic(). The VDA entry incorrectly maps to INTERHEAD_SETOFF_71 (Section 71) instead of VDA_SETOFF_PROHIBITION (Section 115BBH(2)), producing a wrong statutory citation in audit traces for every VDA loss verdict.
qwed_tax/guards/poem_guard.py Adds build_trace to all residency paths and to_diagnostic(). The private _unverifiable() helper is misnamed — its results correctly map to blocked() per the spec, but the name implies unverifiable() semantics, which will mislead future maintainers.
qwed_tax/guards/capital_gains_guard.py Correctly migrates all four rate paths: SLAB_RATE and NO_RATE now map to unverifiable(), RATE_MISMATCH to blocked(), and RATE_VERIFIED to verified(). Addresses the SLAB_RATE issue from the previous review.
qwed_tax/audit.py 15 new RuleRef constants added across capital gains, speculation, set-off, crypto, PoEM, FEMA/LRS, IRS, and valuation domains. JURISDICTION_US constant added. Definitions look accurate against cited statutes.
tests/test_guard_diagnostics.py 40 new tests cover VERIFIED, BLOCKED, UNVERIFIABLE, fail-closed, and serialization round-trip paths for all 9 guards. Three test names are misleading relative to the status they actually assert. test_blocked_vda_lapses is missing a constraint_id assertion that would have caught the wrong statutory reference in setoff_guard.
qwed_tax/guards/classification_guard.py AMBIGUOUS outcome correctly maps to unverifiable(), INVALID_CLAIM and MISCLASSIFICATION to blocked(). Logic and statutes are correctly represented.
qwed_tax/jurisdictions/india/guards/crypto_guard.py Adds audit_trace field to TaxResult and build_trace calls to all verification paths. GAIN_SIDE_NOT_IMPLEMENTED maps to unverifiable() correctly; VDA loss prohibition correctly cites VDA_SETOFF_PROHIBITION.
qwed_tax/guards/speculation_guard.py UNKNOWN_LOSS_SOURCE and UNKNOWN_PROFIT_SOURCE correctly map to unverifiable(); INVALID_INPUT and ILLEGAL_SETOFF map to blocked(). All semantics are correct per the diagnostics spec.
qwed_tax/guards/remittance_guard.py All validation failure paths correctly emit blocked() with appropriate RuleRef citations (LRS_LIMIT vs FEMA_SCHEDULE_I).
qwed_tax/guards/valuation_guard.py All failure paths map to blocked(); successful conversion maps to verified() with SAFE_CONVERSION rule. Correct throughout.
qwed_tax/jurisdictions/us/withholding_guard.py Z3-verified exempt status correctly maps to verified() with W4_EXEMPT_PUB505; violation maps to blocked(). Return type annotation added to verify_exempt_status.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["guard.verify()"] -->|returns Dict| B{verified?}
    B -->|True| C{audit_trace present?}
    C -->|No| D["raise ValueError — fail-closed"]
    C -->|Yes| E["TaxDiagnosticResult.verified\nproof_ref = sha256 of audit_trace"]
    B -->|False| F{outcome in _UNVERIFIABLE_OUTCOMES?}
    F -->|Yes| G["TaxDiagnosticResult.unverifiable\nproof_ref = None"]
    F -->|No| H["TaxDiagnosticResult.blocked\nproof_ref = None"]

    subgraph Unverifiable outcomes per guard
        U1["CapitalGainsGuard: NO_RATE, SLAB_RATE"]
        U2["ClassificationGuard: AMBIGUOUS"]
        U3["SpeculationGuard: UNKNOWN_LOSS or PROFIT_SOURCE"]
        U4["CryptoTaxGuard: GAIN_SIDE_NOT_IMPLEMENTED"]
        U5["InterHeadAdjustmentGuard: UNKNOWN_HEAD"]
    end

    G -.-> U1
    G -.-> U2
    G -.-> U3
    G -.-> U4
    G -.-> U5
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["guard.verify()"] -->|returns Dict| B{verified?}
    B -->|True| C{audit_trace present?}
    C -->|No| D["raise ValueError — fail-closed"]
    C -->|Yes| E["TaxDiagnosticResult.verified\nproof_ref = sha256 of audit_trace"]
    B -->|False| F{outcome in _UNVERIFIABLE_OUTCOMES?}
    F -->|Yes| G["TaxDiagnosticResult.unverifiable\nproof_ref = None"]
    F -->|No| H["TaxDiagnosticResult.blocked\nproof_ref = None"]

    subgraph Unverifiable outcomes per guard
        U1["CapitalGainsGuard: NO_RATE, SLAB_RATE"]
        U2["ClassificationGuard: AMBIGUOUS"]
        U3["SpeculationGuard: UNKNOWN_LOSS or PROFIT_SOURCE"]
        U4["CryptoTaxGuard: GAIN_SIDE_NOT_IMPLEMENTED"]
        U5["InterHeadAdjustmentGuard: UNKNOWN_HEAD"]
    end

    G -.-> U1
    G -.-> U2
    G -.-> U3
    G -.-> U4
    G -.-> U5
Loading

Reviews (3): Last reviewed commit: "fix: use frozenset for _UNVERIFIABLE_OUT..." | Re-trigger Greptile

Comment thread qwed_tax/guards/capital_gains_guard.py
- Remove unused build_trace import from test file (CodeQL)
- Differentiate BLOCKED vs UNVERIFIABLE based on audit_trace outcome:
  - UNVERIFIABLE: NO_RATE, SLAB_RATE, AMBIGUOUS, UNKNOWN_LOSS_SOURCE,
    UNKNOWN_PROFIT_SOURCE, UNKNOWN_HEAD, GAIN_SIDE_NOT_IMPLEMENTED
  - BLOCKED: RATE_MISMATCH, MISCLASSIFICATION, ILLEGAL_SETOFF,
    INVALID_INPUT, PROHIBITED, LIMIT_EXCEEDED, etc.
- PoEMGuard: input validation failures now correctly map to blocked()
  instead of unverifiable(); added audit_trace to _unverifiable() helper
- Added 3 new tests: SLAB_RATE unverifiable, gain-side not implemented
  unverifiable, unknown head unverifiable
- 263 tests pass (220 existing + 43 new)

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (5)
qwed_tax/guards/classification_guard.py (1)

113-113: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Use frozenset for immutable class-level constant.

Same as CapitalGainsGuard — using frozenset silences RUF012 and makes immutability explicit.

Suggested fix
-    _UNVERIFIABLE_OUTCOMES = {"AMBIGUOUS"}
+    _UNVERIFIABLE_OUTCOMES: frozenset[str] = frozenset({"AMBIGUOUS"})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qwed_tax/guards/classification_guard.py` at line 113, The
_UNVERIFIABLE_OUTCOMES class-level constant is currently defined as a mutable
set using braces syntax. Convert this constant to use frozenset instead to make
its immutability explicit and silence the RUF012 linter warning. Replace the set
definition with a frozenset constructor wrapping the set literal for the
_UNVERIFIABLE_OUTCOMES variable in the classification_guard.py file.

Source: Linters/SAST tools

qwed_tax/guards/speculation_guard.py (1)

95-95: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Use frozenset for immutable class-level constant.

Same pattern as other guards — frozenset silences RUF012 and documents intent.

Suggested fix
-    _UNVERIFIABLE_OUTCOMES = {"UNKNOWN_LOSS_SOURCE", "UNKNOWN_PROFIT_SOURCE"}
+    _UNVERIFIABLE_OUTCOMES: frozenset[str] = frozenset({"UNKNOWN_LOSS_SOURCE", "UNKNOWN_PROFIT_SOURCE"})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qwed_tax/guards/speculation_guard.py` at line 95, The class-level constant
`_UNVERIFIABLE_OUTCOMES` is currently defined as a regular set but should be an
immutable frozenset to follow the established pattern in other guards and comply
with the RUF012 linting rule. Convert the set literal containing
"UNKNOWN_LOSS_SOURCE" and "UNKNOWN_PROFIT_SOURCE" into a frozenset by wrapping
it with frozenset() constructor.

Source: Linters/SAST tools

qwed_tax/guards/capital_gains_guard.py (1)

114-114: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Use frozenset for immutable class-level constant.

The static analysis warning (RUF012) is valid here. Since _UNVERIFIABLE_OUTCOMES is only used for membership testing and should never be mutated, using frozenset makes the intent explicit and prevents accidental modification.

Suggested fix
-    _UNVERIFIABLE_OUTCOMES = {"NO_RATE", "SLAB_RATE"}
+    _UNVERIFIABLE_OUTCOMES: frozenset[str] = frozenset({"NO_RATE", "SLAB_RATE"})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qwed_tax/guards/capital_gains_guard.py` at line 114, The
_UNVERIFIABLE_OUTCOMES class constant should be changed from a mutable set to an
immutable frozenset since it is only used for membership testing and should
never be modified. Replace the set literal syntax with the frozenset constructor
wrapping the same values to make the immutability explicit and prevent
accidental modifications.

Source: Linters/SAST tools

qwed_tax/jurisdictions/india/guards/crypto_guard.py (1)

106-106: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use frozenset for immutable class-level constant.

_UNVERIFIABLE_OUTCOMES is only read (membership tests), never mutated. Using frozenset makes immutability explicit and silences the Ruff RUF012 warning.

♻️ Suggested fix
-    _UNVERIFIABLE_OUTCOMES = {"GAIN_SIDE_NOT_IMPLEMENTED"}
+    _UNVERIFIABLE_OUTCOMES: frozenset[str] = frozenset({"GAIN_SIDE_NOT_IMPLEMENTED"})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qwed_tax/jurisdictions/india/guards/crypto_guard.py` at line 106, The
_UNVERIFIABLE_OUTCOMES constant is defined as a regular set but is only used for
membership tests and never modified. Change the set literal to a frozenset by
wrapping the set with the frozenset() constructor to make immutability explicit
and resolve the Ruff RUF012 warning. This clarifies the intent that this
collection is immutable and will not be mutated at runtime.

Source: Linters/SAST tools

qwed_tax/jurisdictions/india/guards/setoff_guard.py (1)

133-133: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use frozenset for immutable class-level constant.

_UNVERIFIABLE_OUTCOMES is only read (membership tests), never mutated. Using frozenset makes immutability explicit and silences the Ruff RUF012 warning.

♻️ Suggested fix
-    _UNVERIFIABLE_OUTCOMES = {"UNKNOWN_HEAD"}
+    _UNVERIFIABLE_OUTCOMES: frozenset[str] = frozenset({"UNKNOWN_HEAD"})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qwed_tax/jurisdictions/india/guards/setoff_guard.py` at line 133, The
`_UNVERIFIABLE_OUTCOMES` class-level constant is defined as a mutable set but is
only read for membership tests and never mutated. Convert it to a frozenset by
wrapping the set literal with the frozenset constructor to make its immutability
explicit and satisfy the Ruff RUF012 linting rule. Change the assignment to use
frozenset() around the set definition.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_guard_diagnostics.py`:
- Around line 421-456: The TestAllGuardsSerialization class claims to verify
round-trip serialization for all migrated guards but only implements test
methods for three guards: test_capital_gains_roundtrip, test_crypto_roundtrip,
and test_withholding_roundtrip, leaving six migrated guards untested. Add test
methods for each of the remaining six migrated guards following the same pattern
used in the existing tests, where each test method instantiates the guard, calls
its verification method, converts to a diagnostic using to_diagnostic, performs
a round-trip serialization via to_dict and from_dict, and asserts that critical
properties like status and proof_ref are preserved through the round-trip.
- Around line 186-237: Add a new test method to the TestCryptoTaxGuardDiagnostic
class that validates the fail-closed contract of CryptoTaxGuard.to_diagnostic().
The test should verify that a ValueError is raised when attempting to call
to_diagnostic() with a raw result containing verified=True but missing the
required audit_trace field. This ensures the guard properly validates its inputs
and maintains the fail-closed regression safety net across all migrated guard
tests.

---

Nitpick comments:
In `@qwed_tax/guards/capital_gains_guard.py`:
- Line 114: The _UNVERIFIABLE_OUTCOMES class constant should be changed from a
mutable set to an immutable frozenset since it is only used for membership
testing and should never be modified. Replace the set literal syntax with the
frozenset constructor wrapping the same values to make the immutability explicit
and prevent accidental modifications.

In `@qwed_tax/guards/classification_guard.py`:
- Line 113: The _UNVERIFIABLE_OUTCOMES class-level constant is currently defined
as a mutable set using braces syntax. Convert this constant to use frozenset
instead to make its immutability explicit and silence the RUF012 linter warning.
Replace the set definition with a frozenset constructor wrapping the set literal
for the _UNVERIFIABLE_OUTCOMES variable in the classification_guard.py file.

In `@qwed_tax/guards/speculation_guard.py`:
- Line 95: The class-level constant `_UNVERIFIABLE_OUTCOMES` is currently
defined as a regular set but should be an immutable frozenset to follow the
established pattern in other guards and comply with the RUF012 linting rule.
Convert the set literal containing "UNKNOWN_LOSS_SOURCE" and
"UNKNOWN_PROFIT_SOURCE" into a frozenset by wrapping it with frozenset()
constructor.

In `@qwed_tax/jurisdictions/india/guards/crypto_guard.py`:
- Line 106: The _UNVERIFIABLE_OUTCOMES constant is defined as a regular set but
is only used for membership tests and never modified. Change the set literal to
a frozenset by wrapping the set with the frozenset() constructor to make
immutability explicit and resolve the Ruff RUF012 warning. This clarifies the
intent that this collection is immutable and will not be mutated at runtime.

In `@qwed_tax/jurisdictions/india/guards/setoff_guard.py`:
- Line 133: The `_UNVERIFIABLE_OUTCOMES` class-level constant is defined as a
mutable set but is only read for membership tests and never mutated. Convert it
to a frozenset by wrapping the set literal with the frozenset constructor to
make its immutability explicit and satisfy the Ruff RUF012 linting rule. Change
the assignment to use frozenset() around the set definition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 73fb2a8f-baca-4434-b7f6-15ee77523165

📥 Commits

Reviewing files that changed from the base of the PR and between 5fab535 and 82ca74d.

📒 Files selected for processing (11)
  • qwed_tax/audit.py
  • qwed_tax/guards/capital_gains_guard.py
  • qwed_tax/guards/classification_guard.py
  • qwed_tax/guards/poem_guard.py
  • qwed_tax/guards/remittance_guard.py
  • qwed_tax/guards/speculation_guard.py
  • qwed_tax/guards/valuation_guard.py
  • qwed_tax/jurisdictions/india/guards/crypto_guard.py
  • qwed_tax/jurisdictions/india/guards/setoff_guard.py
  • qwed_tax/jurisdictions/us/withholding_guard.py
  • tests/test_guard_diagnostics.py

Comment thread tests/test_guard_diagnostics.py
Comment thread tests/test_guard_diagnostics.py
- Convert _UNVERIFIABLE_OUTCOMES to frozenset in 5 guards (CodeRabbit RUF012)
- Add CryptoTaxGuard fail-closed test (verified=True without audit_trace)
- Add 6 remaining serialization round-trip tests (classification, speculation,
  interhead, valuation, remittance, poem)
- 270 tests pass (220 existing + 50 new)
@sonarqubecloud

Copy link
Copy Markdown

@rahuldass19 Rahul Dass (rahuldass19) merged commit babcda8 into main Jun 22, 2026
23 checks passed
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.

Migrate remaining 9 guards to TaxDiagnosticResult model

1 participant