@classytic/ca-tax 0.0.18 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,108 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.20] - 2026-09-09
4
+
5
+ ### Added — every shipped rate table declares its own provenance, and the declaration is enforced
6
+
7
+ A new `@statutory-data` doc block on every module exporting a `RateBook` (and the two
8
+ effective-dated rate tables) states its coverage years, sourcing status
9
+ (`primary-sourced`/`partially-sourced`/`reference-only`), last-verified date, cited sources, and
10
+ known gaps. `tests/statutory-data-provenance.test.ts` enforces four things a comment alone
11
+ cannot: a new rate book cannot ship without the block; a cited vendored document must actually
12
+ exist on disk; the declared `covers:` years must match the years the book actually ships (this
13
+ one would have caught a real defect — every reference book in this package stops at 2024 while
14
+ a host may already have registered 2026); and the roster of unsourced/unverified files is
15
+ pinned, so provenance debt can shrink but never grow silently.
16
+
17
+ Result of running the check against this package's own five rate books: general corporate rates,
18
+ CCA rates, and the Québec CO-17 rate table are `partially-sourced` or `reference-only` and have
19
+ never been checked against a primary document (`verified: never`); the 22-figure
20
+ `PROVINCE_RATE_BOOK` (11 provinces × general + small-business) has zero sourced figures at all —
21
+ the single largest provenance gap in the package. Alberta's rate stack and the GST/HST rate table
22
+ are `primary-sourced` and dated. None of this changes any shipped rate value; it makes the
23
+ package's real sourcing position visible and machine-checked rather than asserted in a comment
24
+ that could go stale unnoticed.
25
+
26
+ ### Fixed — AT1 Schedule 21 line 059 cited no federal source at all
27
+
28
+ Line 059's own printed caption reads "as reported on Federal Schedule 4 line 220" — the ABILs
29
+ expired as non-capital losses — but the field carried no `from` reference, while line 057 right
30
+ above it (current-year capital loss, from Schedule 4 line 210) did. The two are genuinely
31
+ different federal lines; 059 now cites S4 220, pinned by a test asserting both citations point
32
+ at their own line rather than one having been generalized onto the other.
33
+
34
+ ### Changed — Schedule 21's capital-loss pool columns now say GROSS in their own heading
35
+
36
+ The column already carried a note warning that Schedule 12 wants the allowable half and that
37
+ carrying the raw figure over-deducts by roughly two — the heading and all ten row captions in
38
+ the pool now say "gross" up front, so a preparer reading the grid knows which figure the column
39
+ holds before reaching that note.
40
+
41
+ ## [0.0.19] - 2026-09-09
42
+
43
+ ### Fixed — AT1 Schedule 12 was missing three mandatory computed lines entirely
44
+
45
+ Lines 050, 090 and 091 are all marked `M` (mandatory) in §3.2.3.13 and none was ever filed. The
46
+ schedule went out with net income at 054 but neither the federal total that produces it (050)
47
+ nor the taxable income it leads to (090/091) — and 090 is what AT1 page 2 line 062 is taken
48
+ from, so its absence left the jacket's own taxable income undisclosed on the schedule that
49
+ derives it.
50
+
51
+ - **050** — the federal/Alberta discretionary-difference total, signed per the spec's own
52
+ formula (`−005 +007 −009 −011 +013 +015 −017 +019 −021 −023 −027 −029 −031 −033 +037 −039
53
+ +041`). Filed as an explicit zero, not omitted, when nothing diverges — the spec states so
54
+ verbatim.
55
+ - **090/091** — taxable income, Alberta and federal: `054 − (Area B Alberta) + 082` and
56
+ `002 − (Area B federal) + 083`, with the printed qualification that a corporation whose
57
+ deductions exhaust its income reports its s.110.5 additions as the figure rather than a
58
+ negative. New `Schedule12FilingInput.taxableIncome?: { alberta, federal }` lets a caller
59
+ supply the real computed figure instead — the line-derived sum is only as complete as the
60
+ deduction lines this engine actually models (a central credit union allocation, prospector's
61
+ shares, the non-qualified-securities deduction, if uncarried, would silently overstate it).
62
+ Line 052 (the printed subtotal en route to 054) and Area B's own 080/081 subtotals are
63
+ confirmed print-only — no line code, nothing to file — same as the jacket's line 088.
64
+
65
+ ### Added — five previously-unfiled Schedule 12 Area B pairs
66
+
67
+ Taxable dividends deductible (060/061), Part VI.1 tax deduction (062/063), central credit union
68
+ allocation (074/075), prospector's and grubstaker's shares (078/079), non-qualified-securities
69
+ employer deduction (140/141) — all mandatory pairs with no prior emitter. Each now transmits
70
+ both sides whenever the caller supplies it; absent data stays unfiled rather than defaulting to
71
+ zero, since filing 0 would assert a deduction the corporation is not known to lack.
72
+
73
+ ### Fixed — Schedule 12's s.110.5 additions line traced a typo on TRA's own printed form
74
+
75
+ 083 is annotated on the page as "T2 line 335" — that line is limited partnership losses, a
76
+ *deduction*, already this schedule's 072/073 pair, and cannot be what an *additions* line reads.
77
+ §3.2.3.13's own rule says "must equal fed 200355", and CRA line 355 is the s.110.5/115(1)(a)(vii)
78
+ additions line. The engine now follows the spec and CRA over the form's own annotation, per
79
+ `section110_5Additions`.
80
+
81
+ ### Fixed — Schedule 12 label and note corrections
82
+
83
+ "Taxable dividends deductible under ITA s.112, 113 or **186**(6)" corrected to **138**(6). Four
84
+ Area B notes claiming "no Alberta divergence path" or "both sides read the same federal line"
85
+ corrected — each box in fact accepts a preparer-entered Alberta-side override; there was simply
86
+ no modelled path to supply one until this release.
87
+
88
+ ### Fixed — Schedule 18's ABIL rows filed the name and shares/debt flag but none of the money
89
+
90
+ Every printed column on the small-business-corporation row is its own filed box — 086 (date of
91
+ acquisition), 088/090/092 (proceeds, adjusted cost base, outlays) — and the emitter previously
92
+ wrote only 082 and 084. The 094 total was correct; nothing underneath it showed TRA how the loss
93
+ was arrived at. 086 is genuinely optional on the form and is omitted (not filed as an empty
94
+ string) when no acquisition date is recorded.
95
+
96
+ ### Fixed — Schedule 18's `FormDefinition` invented a page section that isn't on the form
97
+
98
+ The definition split dispositions into two sections, "Dispositions by category" and "Adjustments
99
+ to the total gain" (lines 062-099). The form prints exactly two block headings total — capital
100
+ property dispositions (page 1) and the ABIL block (page 2) — and every line the definition had
101
+ called "adjustments" sits under the same page-1 heading as the six-category grid above it. A
102
+ preparer holding the paper form would look for that second heading and not find it. Merged back
103
+ into one `dispositions` section per the actual page; both sections now carry their printed page
104
+ number.
105
+
3
106
  ## [0.0.18] - 2026-09-08
4
107
 
5
108
  ### Removed — AT1 Schedules 5, 6, 7, 8 and 9 (BREAKING)
package/dist/forms.mjs CHANGED
@@ -1810,12 +1810,12 @@ const AT1_SCHEDULE_12_PAIRS = [
1810
1810
  note: "If Schedule 20 exists, Alberta = 020076; otherwise Alberta = federal."
1811
1811
  },
1812
1812
  {
1813
- label: "Taxable dividends deductible under ITA s.112, 113 or 186(6)",
1813
+ label: "Taxable dividends deductible under ITA s.112, 113 or 138(6)",
1814
1814
  federal: "061",
1815
1815
  alberta: "060",
1816
1816
  section: "area-b",
1817
1817
  emitOnlyWhenDifferent: false,
1818
- note: "No Alberta divergence pathalways equals federal."
1818
+ note: "Alberta = federal unless the preparer enters a different amount (ri.albertaSchedule12.albertaTaxableDividendsDeductible) no Alberta schedule computes this independently, but the box is not fixed to federal by rule."
1819
1819
  },
1820
1820
  {
1821
1821
  label: "Part VI.1 tax deduction",
@@ -1879,7 +1879,7 @@ const AT1_SCHEDULE_12_PAIRS = [
1879
1879
  alberta: "074",
1880
1880
  section: "area-b",
1881
1881
  emitOnlyWhenDifferent: false,
1882
- note: "No Alberta divergence pathalways equals federal."
1882
+ note: "Alberta = federal unless the preparer enters a different amount (ri.albertaSchedule12.albertaCentralCreditUnionAllocation) no Alberta schedule computes this independently, but the box is not fixed to federal by rule."
1883
1883
  },
1884
1884
  {
1885
1885
  label: "Prospector's and grubstaker's shares",
@@ -1887,7 +1887,7 @@ const AT1_SCHEDULE_12_PAIRS = [
1887
1887
  alberta: "078",
1888
1888
  section: "area-b",
1889
1889
  emitOnlyWhenDifferent: false,
1890
- note: "Both sides read T2 line 350 the form prints that source over each box."
1890
+ note: "The federal side reads T2 line 350, which the form prints over the box. Alberta follows it unless the preparer enters a different amount (ri.albertaSchedule12.albertaProspectorsShares)."
1891
1891
  },
1892
1892
  {
1893
1893
  label: "Employer deduction for non-qualified securities",
@@ -1895,7 +1895,7 @@ const AT1_SCHEDULE_12_PAIRS = [
1895
1895
  alberta: "140",
1896
1896
  section: "area-b",
1897
1897
  emitOnlyWhenDifferent: false,
1898
- note: "Both sides read T2 line 352."
1898
+ note: "The federal side reads T2 line 352. Alberta follows it unless the preparer enters a different amount (ri.albertaSchedule12.albertaNonQualifiedSecuritiesDeduction)."
1899
1899
  },
1900
1900
  {
1901
1901
  label: "ITA section 110.5 and/or subparagraph 115(1)(a)(vii) additions",
@@ -2774,24 +2774,29 @@ const AT1_SCHEDULE_18_CATEGORIES = [
2774
2774
  lossRestricted: true
2775
2775
  }
2776
2776
  ];
2777
- const SECTIONS$25 = [
2778
- {
2779
- id: "dispositions",
2780
- title: "Dispositions by category",
2781
- description: "Proceeds, cost base and outlays for each of six categories. Losses on the last two are restricted and do not net against ordinary gains."
2782
- },
2783
- {
2784
- id: "adjustments",
2785
- title: "Adjustments to the total gain",
2786
- description: "Reserves, donated securities and the flow-through share threshold, before the section 34.2 partnership amounts."
2787
- },
2788
- {
2789
- id: "abil",
2790
- title: "Allowable business investment loss",
2791
- secondary: true,
2792
- description: "A capital loss on a small business corporation, deductible against any income."
2793
- }
2794
- ];
2777
+ /**
2778
+ * The form prints exactly TWO block headings, and these are them verbatim —
2779
+ * "CAPITAL PROPERTY DISPOSITIONS" on page 1 and the ABIL block on page 2.
2780
+ *
2781
+ * There used to be a third section here, "Adjustments to the total gain",
2782
+ * grouping lines 062-099. No such heading exists on the page: every one of
2783
+ * those lines sits inside the page-1 dispositions block, under the same
2784
+ * printed heading as the six-category grid above them. Splitting them out
2785
+ * read as a separate part of the form that a preparer holding the paper
2786
+ * would look for and not find, so they are back where the page puts them.
2787
+ */
2788
+ const SECTIONS$25 = [{
2789
+ id: "dispositions",
2790
+ title: "Capital property dispositions",
2791
+ description: "Proceeds, cost base and outlays for each of six categories, then the reserves, donated securities, flow-through share threshold and section 34.2 partnership amounts that adjust the total. Losses on personal-use and listed personal property are restricted and do not net against ordinary gains.",
2792
+ page: 1
2793
+ }, {
2794
+ id: "abil",
2795
+ title: "Property qualifying for and resulting in an allowable business investment loss",
2796
+ secondary: true,
2797
+ description: "A capital loss on a small business corporation. Unlike an ordinary capital loss it is deductible against ANY income, which is why the form gives it its own page.",
2798
+ page: 2
2799
+ }];
2795
2800
  /**
2796
2801
  * The four printed columns, and the prefix §3.2.3.19 puts on each column's own
2797
2802
  * Line Name — "Adjusted cost base: Total of all shares", not "Shares — B".
@@ -2857,7 +2862,7 @@ const REST = [
2857
2862
  caption: "Total of Column D",
2858
2863
  kind: "money",
2859
2864
  role: "computed",
2860
- section: "adjustments",
2865
+ section: "dispositions",
2861
2866
  note: "EXCLUDES lines 059 and 060 where the difference is a net loss — restricted losses are dropped, not netted. Summing column D over-deducts."
2862
2867
  },
2863
2868
  {
@@ -2865,28 +2870,28 @@ const REST = [
2865
2870
  caption: "Capital gains dividends",
2866
2871
  kind: "money",
2867
2872
  role: "input",
2868
- section: "adjustments"
2873
+ section: "dispositions"
2869
2874
  },
2870
2875
  {
2871
2876
  field: "066",
2872
2877
  caption: "Add: capital gain reserve opening balance, if any",
2873
2878
  kind: "money",
2874
2879
  role: "input",
2875
- section: "adjustments"
2880
+ section: "dispositions"
2876
2881
  },
2877
2882
  {
2878
2883
  field: "068",
2879
2884
  caption: "Deduct: capital gain reserve closing balance, if any",
2880
2885
  kind: "money",
2881
2886
  role: "input",
2882
- section: "adjustments"
2887
+ section: "dispositions"
2883
2888
  },
2884
2889
  {
2885
2890
  field: "070",
2886
2891
  caption: "Capital gain or (loss): Line 062 + line 064 + line 066 - line 068",
2887
2892
  kind: "money",
2888
2893
  role: "computed",
2889
- section: "adjustments",
2894
+ section: "dispositions",
2890
2895
  note: "Line 062 plus 064 plus 066 minus 068."
2891
2896
  },
2892
2897
  {
@@ -2894,21 +2899,21 @@ const REST = [
2894
2899
  caption: "Deduct: Gain on the donation to a qualified donee of a share, debt obligation, or right listed on a designated stock exchange and other securities under paragraphs 38(a.1)(i) and (iii) of the federal Act",
2895
2900
  kind: "money",
2896
2901
  role: "input",
2897
- section: "adjustments"
2902
+ section: "dispositions"
2898
2903
  },
2899
2904
  {
2900
2905
  field: "073",
2901
2906
  caption: "Deduct: Gain on the donation to a qualified donee of ecologically sensitive land under paragraph 38(a.2) of the federal Act",
2902
2907
  kind: "money",
2903
2908
  role: "input",
2904
- section: "adjustments"
2909
+ section: "dispositions"
2905
2910
  },
2906
2911
  {
2907
2912
  field: "075",
2908
2913
  caption: "Line 070 minus (line 071 + 073)",
2909
2914
  kind: "money",
2910
2915
  role: "computed",
2911
- section: "adjustments",
2916
+ section: "dispositions",
2912
2917
  note: "Line 070 minus lines 071 and 073."
2913
2918
  },
2914
2919
  {
@@ -2916,7 +2921,7 @@ const REST = [
2916
2921
  caption: "Add: Exemption threshold at time of disposal",
2917
2922
  kind: "money",
2918
2923
  role: "input",
2919
- section: "adjustments",
2924
+ section: "dispositions",
2920
2925
  note: "Flow-through share class of property — ITA subsection 40(12)."
2921
2926
  },
2922
2927
  {
@@ -2924,14 +2929,14 @@ const REST = [
2924
2929
  caption: "Add: Total of all capital gains from the disposition of the actual property",
2925
2930
  kind: "money",
2926
2931
  role: "input",
2927
- section: "adjustments"
2932
+ section: "dispositions"
2928
2933
  },
2929
2934
  {
2930
2935
  field: "079",
2931
2936
  caption: "Lesser of lines 077 or 078",
2932
2937
  kind: "money",
2933
2938
  role: "computed",
2934
- section: "adjustments",
2939
+ section: "dispositions",
2935
2940
  note: "The LESSER of lines 077 and 078 — not their sum. Adding them inflates the gain."
2936
2941
  },
2937
2942
  {
@@ -2939,7 +2944,7 @@ const REST = [
2939
2944
  caption: "Taxable capital gains under section 34.2 of the federal Act (line 275 of federal Schedule 73, Income Inclusion Summary for Corporations that are members of Partnerships) X 2 =",
2940
2945
  kind: "money",
2941
2946
  role: "carried-in",
2942
- section: "adjustments",
2947
+ section: "dispositions",
2943
2948
  from: {
2944
2949
  form: "T2SCH73",
2945
2950
  line: "270"
@@ -2951,7 +2956,7 @@ const REST = [
2951
2956
  caption: "Subtotal: Line 075 + line 079 + line 096",
2952
2957
  kind: "money",
2953
2958
  role: "computed",
2954
- section: "adjustments",
2959
+ section: "dispositions",
2955
2960
  note: "Lines 075 plus 079 plus 096."
2956
2961
  },
2957
2962
  {
@@ -2959,7 +2964,7 @@ const REST = [
2959
2964
  caption: "Deduct: Allowable capital losses under section 34.2 of the federal Act (line 285 of federal Schedule 73, Income Inclusion Summary for Corporations that are Members of Partnerships) X 2 =",
2960
2965
  kind: "money",
2961
2966
  role: "carried-in",
2962
- section: "adjustments",
2967
+ section: "dispositions",
2963
2968
  from: {
2964
2969
  form: "T2SCH73",
2965
2970
  line: "285"
@@ -2971,7 +2976,7 @@ const REST = [
2971
2976
  caption: "Total capital gains or losses: Line 097 - line 098",
2972
2977
  kind: "money",
2973
2978
  role: "computed",
2974
- section: "adjustments",
2979
+ section: "dispositions",
2975
2980
  note: "If line 099 is NEGATIVE, carry the capital loss forward to Schedule 21, line 057, and no taxable capital gain arises at line 076.",
2976
2981
  to: {
2977
2982
  form: "AT1SCH21",
@@ -2984,7 +2989,7 @@ const REST = [
2984
2989
  caption: "Taxable capital gain: Line 099 X 50%",
2985
2990
  kind: "money",
2986
2991
  role: "computed",
2987
- section: "adjustments",
2992
+ section: "dispositions",
2988
2993
  note: "Line 099 at the inclusion rate — the only place the halving happens. If line 099 is positive, carry this forward to Schedule 12, line 040. The 50% printed here is the current rate; a year whose dispositions straddle an inclusion-rate change needs supporting documentation with the RSI (§3.2.3.19 filing-requirement exception).",
2989
2994
  to: {
2990
2995
  form: "AT1SCH12",
@@ -3438,7 +3443,7 @@ const AT1_SCHEDULE_21_POOLS = [
3438
3443
  },
3439
3444
  {
3440
3445
  key: "capital",
3441
- label: "Net capital losses",
3446
+ label: "Net capital losses (Gross amount)",
3442
3447
  carriedForward: "051",
3443
3448
  windUpTransfer: "055",
3444
3449
  currentYearLoss: "057",
@@ -3786,6 +3791,7 @@ const continuityFields = AT1_SCHEDULE_21_POOLS.flatMap((pool) => CONTINUITY_ORDE
3786
3791
  const isAppliedLine = k === "appliedAgainstIncome";
3787
3792
  const isOpening = k === "opening";
3788
3793
  const isCurrentYearLoss = k === "currentYearLoss";
3794
+ const isAbilExpired = k === "abilExpired";
3789
3795
  let role = "input";
3790
3796
  let from;
3791
3797
  const closingFootnotes = {
@@ -3809,6 +3815,11 @@ const continuityFields = AT1_SCHEDULE_21_POOLS.flatMap((pool) => CONTINUITY_ORDE
3809
3815
  line: pool.key === "farm" ? "310" : "410",
3810
3816
  note: "Defaults to this federal figure — override only where Alberta genuinely diverges."
3811
3817
  };
3818
+ else if (isAbilExpired) from = {
3819
+ form: "T2SCH4",
3820
+ line: "220",
3821
+ note: "Printed on the form: \"as reported on Federal Schedule 4 line 220\" — federal line 220 is \"ABILs expired as non-capital losses (line 215 multiplied by 2)\". An expiring ABIL leaves the non-capital pool and arrives here as a net capital loss."
3822
+ };
3812
3823
  else if (isCarryBack) role = "computed";
3813
3824
  const footnoteMarks = isClosing ? closingFootnotes[pool.key] : void 0;
3814
3825
  return {
package/dist/index3.d.mts CHANGED
@@ -92,6 +92,23 @@ declare function resolveAlbertaTaxRates(taxYear: number, book?: RateBook<Alberta
92
92
  * after 2020-06-30 8.0%
93
93
  *
94
94
  * Pure, no I/O. Whole dollars.
95
+ *
96
+ * @statutory-data
97
+ * covers: 2006-04-01 onward (effective-dated bands, not year-versioned)
98
+ * status: primary-sourced
99
+ * verified: 2026-09-09
100
+ * sources:
101
+ * - research/sources/tra-spec/AT1-Chapter3-2026.4-full.txt §3.2.3.1 line 068
102
+ * ("Basic Alberta Tax Payable"). The spec defines the day counts B–F and
103
+ * their multipliers H–L; every band above is a transcription of that pair:
104
+ * B after 2006-03-31, before 2015-07-01 H = A x .100 x B/H
105
+ * C after 2015-06-30, before 2019-07-01 I = A x .120 x C/H
106
+ * D after 2019-06-30, before 2020-01-01 J = A x .110 x D/H
107
+ * E after 2019-12-31, before 2020-07-01 K = A x .100 x E/H
108
+ * F after 2020-06-30 L = A x .080 x F/H
109
+ * "Value = H + I + J + K + L. Round up at $.50 to the nearest dollar. If
110
+ * the value is negative, default = zero." — the rounding and floor this
111
+ * module implements.
95
112
  */
96
113
  interface GeneralRateBand {
97
114
  /** Inclusive start of the band, ISO `YYYY-MM-DD`. */
@@ -1424,9 +1441,19 @@ interface At1CategoryResult {
1424
1441
  differsFromFederal: boolean;
1425
1442
  }
1426
1443
  interface At1AbilResult {
1444
+ /** 018082 — name of the small business corporation. */
1427
1445
  name: string;
1446
+ /** 018084 — shares (1) or debt (2). */
1428
1447
  kind: 'shares' | 'debt';
1429
- /** 088 − (090 + 092) the business investment loss before the inclusion rate. */
1448
+ /** 018086date of acquisition, ISO `YYYY-MM-DD`, when supplied. */
1449
+ dateOfAcquisition?: string;
1450
+ /** 018088 — A, proceeds of disposition. */
1451
+ proceeds: number;
1452
+ /** 018090 — B, adjusted cost base. */
1453
+ acb: number;
1454
+ /** 018092 — C, outlays and expenses. */
1455
+ outlays: number;
1456
+ /** 088 − (090 + 092) — the business investment loss before the inclusion rate. Not itself a printed line; feeds 018094's total. */
1430
1457
  businessInvestmentLoss: number;
1431
1458
  }
1432
1459
  interface AlbertaSchedule18Result {
@@ -2602,6 +2629,108 @@ interface Schedule12FilingInput {
2602
2629
  alberta: number;
2603
2630
  federal: number;
2604
2631
  };
2632
+ /**
2633
+ * 012040 / 012041 / 012048 — "Other for Alberta / federal purposes — specify",
2634
+ * and the explanation line 048 that TRA requires alongside it.
2635
+ *
2636
+ * A CATCH-ALL, not a single reconciling item: §3.2.3.13 defines 040 as "the
2637
+ * total amount of any other discretionary account items not specifically
2638
+ * listed on form 012", plus two named inclusions. Callers accumulate their
2639
+ * components into one figure per side.
2640
+ *
2641
+ * The two named inclusions, verbatim:
2642
+ *
2643
+ * 1. If form 018 exists, 040 must include `018076 + 018094` as an addition
2644
+ * — the taxable capital gain and the allowable business investment loss.
2645
+ * The matching federal side of 041 is `fed 001113 minus 001406`, which
2646
+ * is federal Schedule 1's "Taxable capital gains from Schedule 6" less
2647
+ * its "Allowable business investment loss from Schedule 6".
2648
+ *
2649
+ * The asymmetry is the spec's, not a transcription slip, and it is
2650
+ * consistent once the signs are seen: an ABIL reaches 018094 already
2651
+ * NEGATIVE (see `albertaAbilDifference` — "the inputs arrive negative
2652
+ * per the form"), while federal line 406 is a deduction line reported
2653
+ * positive. Both expressions therefore net the loss out of the gain.
2654
+ * Do not "correct" either into matching the other.
2655
+ *
2656
+ * 2. If form 015 exists, 040 must also include the Schedule 15 Area C, D,
2657
+ * F, G and H expense computations — but only where a computation comes
2658
+ * out NEGATIVE, and then entered as a positive amount. That component is
2659
+ * NOT modelled here; a caller with Schedule 15 resource pools has to add
2660
+ * it to `alberta` itself. Named rather than silently omitted.
2661
+ *
2662
+ * Line 048 is not optional decoration: "If 012040 exists, then 012048 must
2663
+ * exist." Supply `explanation` whenever the pair is non-nil.
2664
+ */
2665
+ other?: {
2666
+ alberta: number;
2667
+ federal: number;
2668
+ explanation?: string;
2669
+ };
2670
+ /** 012060 / 012061 — taxable dividends deductible under ITA s.112, 113 or 138(6). Both sides are federal T2 line 320. */
2671
+ taxableDividendsDeductible?: {
2672
+ alberta: number;
2673
+ federal: number;
2674
+ };
2675
+ /** 012062 / 012063 — Part VI.1 tax deduction. Both sides are federal T2 line 325. */
2676
+ partVI1Deduction?: {
2677
+ alberta: number;
2678
+ federal: number;
2679
+ };
2680
+ /** 012074 / 012075 — taxable capital gains or taxable dividends allocated from a central credit union. Both sides are federal T2 line 340. */
2681
+ centralCreditUnion?: {
2682
+ alberta: number;
2683
+ federal: number;
2684
+ };
2685
+ /** 012078 / 012079 — prospector's and grubstaker's shares. Both sides are federal T2 line 350. */
2686
+ prospectorsShares?: {
2687
+ alberta: number;
2688
+ federal: number;
2689
+ };
2690
+ /** 012140 / 012141 — employer deduction for non-qualified securities. Both sides are federal T2 line 352. */
2691
+ nonQualifiedSecurities?: {
2692
+ alberta: number;
2693
+ federal: number;
2694
+ };
2695
+ /**
2696
+ * 012082 / 012083 — ITA s.110.5 and/or subparagraph 115(1)(a)(vii) ADDITIONS,
2697
+ * the one Area B item that adds to taxable income rather than deducting.
2698
+ *
2699
+ * Alberta's side comes from AT1 Schedule 21 line 017; the federal side is T2
2700
+ * line 355, "Section 110.5 additions or subparagraph 115(1)(a)(vii)
2701
+ * additions".
2702
+ *
2703
+ * The printed AT1 Schedule 12 annotates 083 as "T2 line 335" — that is a
2704
+ * TYPO on TRA's form. T2 line 335 is "Limited partnership losses of previous
2705
+ * tax years from Schedule 4", which is already this schedule's 072/073 pair
2706
+ * and is a deduction, not an addition. §3.2.3.13's own rule for 083 says
2707
+ * "Must equal fed 200355", and CRA's line 355 is the s.110.5 additions line.
2708
+ * Follow the spec and CRA, not the annotation.
2709
+ */
2710
+ section110_5Additions?: {
2711
+ alberta: number;
2712
+ federal: number;
2713
+ };
2714
+ /**
2715
+ * 012090 / 012091 — taxable income, when the caller already knows it.
2716
+ *
2717
+ * Prefer supplying these. This builder can derive them from the Area B lines
2718
+ * it emits, and does when they are absent, but that derivation is only as
2719
+ * complete as the deduction lines actually modelled: any Division C item this
2720
+ * engine does not carry (a central credit union allocation, prospector's
2721
+ * shares, the non-qualified-securities deduction) is missing from the sum and
2722
+ * OVERSTATES taxable income — and 090 is what AT1 page 2 line 062 is taken
2723
+ * from, so an overstatement there is Alberta tax on income the corporation
2724
+ * does not have.
2725
+ *
2726
+ * A caller that ran the engine has the real figure. Passing it makes the
2727
+ * filed line agree with the tax actually computed, and leaves the derived
2728
+ * value as a cross-check rather than the source.
2729
+ */
2730
+ taxableIncome?: {
2731
+ alberta: number;
2732
+ federal: number;
2733
+ };
2605
2734
  /**
2606
2735
  * Area B — losses of preceding taxation years, deducted in arriving at Alberta
2607
2736
  * taxable income. Same federal/Alberta pairing as Area A, but NOT the same
package/dist/t2.mjs CHANGED
@@ -57,6 +57,23 @@ function extendRateBook(base, overrides) {
57
57
  * Never mutate a shipped year — add a new {@link RateBookEntry} to the host book.
58
58
  * A host wires future Alberta years / TRA-published values via the same
59
59
  * effective-dated seam the federal engine uses (see rates/rate-book.ts).
60
+ *
61
+ * @statutory-data
62
+ * book: AB_TAX_RATE_BOOK
63
+ * covers: 2024
64
+ * status: partially-sourced
65
+ * verified: 2026-09-09
66
+ * sources:
67
+ * - research/sources/tra-spec/AT1-Chapter3-2026.4-full.txt §3.2.3.1 line 068 —
68
+ * GENERAL_RATE 8%, as band F of the day-weighted table (`L = A x .080 x F/H`,
69
+ * days after 2020-06-30). See rates/general-rate-bands.ts for the full table.
70
+ * - research/sources/tra-spec/AT1-Chapter3-2026.4-full.txt §3.2.3.2 —
71
+ * BUSINESS_LIMIT $500,000, in effect since 2009-04-01.
72
+ * gaps:
73
+ * - SMALL_BUSINESS_RATE (2%) appears in NO vendored source. The AT1 spec states
74
+ * no small-business rate at all: its only rate multipliers are the five
75
+ * general-rate bands above. Verify against the Alberta Corporate Tax Act /
76
+ * TRA's published rate page before relying on it to file.
60
77
  */
61
78
  const AB_TAX_2024 = Object.freeze({
62
79
  GENERAL_RATE: .08,
@@ -251,11 +268,6 @@ const AT1_SCHEDULES_WITH_BUILDERS = Object.freeze([
251
268
  "002",
252
269
  "003",
253
270
  "004",
254
- "005",
255
- "006",
256
- "007",
257
- "008",
258
- "009",
259
271
  "010",
260
272
  "012",
261
273
  "013",
@@ -424,14 +436,16 @@ function schedule18Values(result) {
424
436
  put("076", result.taxableCapitalGain);
425
437
  result.abil.forEach((entry, i) => {
426
438
  const n = i + 1;
427
- values.push({
428
- lineItemId: at1LineItemId("018", "082", n),
429
- value: entry.name
430
- });
431
- values.push({
432
- lineItemId: at1LineItemId("018", "084", n),
433
- value: entry.kind === "shares" ? 1 : 2
439
+ const row = (fieldId, value) => values.push({
440
+ lineItemId: at1LineItemId("018", fieldId, n),
441
+ value
434
442
  });
443
+ row("082", entry.name);
444
+ row("084", entry.kind === "shares" ? 1 : 2);
445
+ if (entry.dateOfAcquisition) row("086", entry.dateOfAcquisition);
446
+ row("088", entry.proceeds);
447
+ row("090", entry.acb);
448
+ row("092", entry.outlays);
435
449
  });
436
450
  put("094", result.allowableBusinessInvestmentLoss);
437
451
  return {
@@ -679,6 +693,10 @@ function schedule12Values(input) {
679
693
  pair("032", "033", input.cogpe);
680
694
  pair("036", "037", input.reservesDeductedPriorYear);
681
695
  pair("038", "039", input.reservesClaimedCurrentYear);
696
+ if (input.other && input.other.alberta !== input.other.federal) {
697
+ pair("040", "041", input.other);
698
+ if (input.other.explanation) put("048", input.other.explanation);
699
+ }
682
700
  const alwaysPair = (albertaField, federalField, p) => {
683
701
  if (!p) return;
684
702
  put(albertaField, p.alberta);
@@ -693,7 +711,99 @@ function schedule12Values(input) {
693
711
  alwaysPair("056", "057", input.donations?.charitable);
694
712
  alwaysPair("058", "059", input.donations?.gifts);
695
713
  alwaysPair("130", "131", input.restrictedInterestAndFinancing);
714
+ alwaysPair("060", "061", input.taxableDividendsDeductible);
715
+ alwaysPair("062", "063", input.partVI1Deduction);
716
+ alwaysPair("074", "075", input.centralCreditUnion);
717
+ alwaysPair("078", "079", input.prospectorsShares);
718
+ alwaysPair("140", "141", input.nonQualifiedSecurities);
719
+ alwaysPair("082", "083", input.section110_5Additions);
696
720
  put("054", input.albertaNetIncomeForTax);
721
+ const filed = /* @__PURE__ */ new Map();
722
+ for (const v of values) if (typeof v.value === "number") filed.set(v.lineItemId.slice(3, 6), v.value);
723
+ const n = (field) => filed.get(field) ?? 0;
724
+ const sum = (fields) => fields.reduce((t, f) => t + n(f), 0);
725
+ put("050", sum([
726
+ "007",
727
+ "013",
728
+ "015",
729
+ "019",
730
+ "037",
731
+ "041"
732
+ ]) - sum([
733
+ "005",
734
+ "009",
735
+ "011",
736
+ "017",
737
+ "021",
738
+ "023",
739
+ "027",
740
+ "029",
741
+ "031",
742
+ "033",
743
+ "039"
744
+ ]));
745
+ /**
746
+ * 090 / 091 — taxable income, Alberta and federal. Both mandatory, and
747
+ * neither was filed; 090 is also what AT1 page 2 line 062 is taken from
748
+ * ("Enter this amount on AT1 page 2, line 062"), so its absence left the
749
+ * jacket's own taxable income undisclosed on the schedule that derives it.
750
+ *
751
+ * The printed form numbers the two Area B subtotals 080 (Alberta) and 081
752
+ * (federal) and strikes:
753
+ *
754
+ * 090 = 054 − 080 + 082 091 = 002 − 081 + 083
755
+ *
756
+ * with the qualification, printed under each: "If there is an amount at line
757
+ * 082 and line 054 − line 080 is negative, then line 090 must equal line
758
+ * 082" (and the same for 083/081/091). That is not rounding defensiveness —
759
+ * a corporation whose deductions exhaust its income reports its ITA s.110.5
760
+ * additions as the figure, not a negative.
761
+ *
762
+ * 080 and 081 are NOT transmitted — they carry no line-number marker on the
763
+ * page, and §3.2.3.13 defines no row for either, expanding both inline
764
+ * instead. So they are summed here rather than emitted, exactly as 052 is on
765
+ * page 1. The members below are the spec's own expansion, which matches the
766
+ * Alberta and federal deduction columns of Area B row for row.
767
+ */
768
+ const AREA_B_ALBERTA = [
769
+ "056",
770
+ "058",
771
+ "060",
772
+ "062",
773
+ "064",
774
+ "066",
775
+ "068",
776
+ "070",
777
+ "072",
778
+ "074",
779
+ "078",
780
+ "130",
781
+ "140"
782
+ ];
783
+ const AREA_B_FEDERAL = [
784
+ "057",
785
+ "059",
786
+ "061",
787
+ "063",
788
+ "065",
789
+ "067",
790
+ "069",
791
+ "071",
792
+ "073",
793
+ "075",
794
+ "079",
795
+ "131",
796
+ "141"
797
+ ];
798
+ /**
799
+ * The formula, applied to the lines this builder emitted. Used only when the
800
+ * caller did not supply the real figure — see `taxableIncome` on the input
801
+ * for why supplying it matters: this sum is only as complete as the deduction
802
+ * lines actually modelled, and a missing one overstates the result.
803
+ */
804
+ const derive = (income, deductions, additions) => income - deductions < 0 && additions > 0 ? additions : income - deductions + additions;
805
+ put("090", input.taxableIncome?.alberta ?? derive(n("054"), sum(AREA_B_ALBERTA), n("082")));
806
+ put("091", input.taxableIncome?.federal ?? derive(n("002"), sum(AREA_B_FEDERAL), n("083")));
697
807
  return {
698
808
  scheduleId: "012",
699
809
  values
@@ -4349,6 +4459,23 @@ function albertaCurrentYearLoss(result) {
4349
4459
  *
4350
4460
  * Rates per CRA T4012 / ITR Schedule II. T2 CCA amounts are whole dollars (GIFI
4351
4461
  * convention), so the CCA calculator works in integer dollars — NOT cents.
4462
+ *
4463
+ * @statutory-data
4464
+ * book: CCA_RATE_BOOK
4465
+ * covers: 2024
4466
+ * status: partially-sourced
4467
+ * verified: never
4468
+ * sources:
4469
+ * - research/sources/legislation/ITR-section-1100-cca-deductions.txt —
4470
+ * the Reg 1100(1) allowance rates these classes are drawn from.
4471
+ * - research/sources/legislation/ITR-schedule-III-class-13-leasehold.txt —
4472
+ * class 13, which is straight-line and deliberately NOT in this table.
4473
+ * gaps:
4474
+ * - The per-class rates are transcribed from ITR Schedule II, which is not
4475
+ * vendored; ITR s.1100 above is the deduction mechanism, not the class list.
4476
+ * - No class rate has been checked against a primary document on a recorded
4477
+ * date. Class rates move rarely, so staleness is a lower risk here than in
4478
+ * the year-tagged tax tables — but it is unmeasured, not measured-as-fine.
4352
4479
  */
4353
4480
  const CCA_DECLINING_BALANCE_RATES_2024 = Object.freeze({
4354
4481
  "1": .04,
@@ -5340,6 +5467,10 @@ function computeAlbertaSchedule18(input) {
5340
5467
  const abil = (input.abilEntries ?? []).map((e) => ({
5341
5468
  name: e.name,
5342
5469
  kind: e.kind,
5470
+ ...e.dateOfAcquisition ? { dateOfAcquisition: e.dateOfAcquisition } : {},
5471
+ proceeds: nn$23(e.proceeds),
5472
+ acb: nn$23(e.acb),
5473
+ outlays: nn$23(e.outlays),
5343
5474
  businessInvestmentLoss: nn$23(e.proceeds) - (nn$23(e.acb) + nn$23(e.outlays))
5344
5475
  }));
5345
5476
  const totalBil = abil.reduce((s, e) => s + Math.min(0, e.businessInvestmentLoss), 0);
@@ -6099,6 +6230,28 @@ function federalSchedulePayloads(r) {
6099
6230
  *
6100
6231
  * Never mutate a shipped year — add a new {@link RateBookEntry} for the new
6101
6232
  * year (in the host book) so prior-year returns stay reproducible.
6233
+ *
6234
+ * @statutory-data
6235
+ * book: CORP_TAX_RATE_BOOK
6236
+ * covers: 2024
6237
+ * status: partially-sourced
6238
+ * verified: never
6239
+ * sources:
6240
+ * - research/sources/legislation/ITA-section-123.4-general-rate-reduction.txt —
6241
+ * GENERAL_RATE_REDUCTION (13%).
6242
+ * - research/sources/legislation/T4012-lines-638-639-general-tax-reduction.txt —
6243
+ * the general tax reduction as CRA states it on the return.
6244
+ * - research/sources/legislation/ITA-section-191.1-part-vi-1-tax.txt —
6245
+ * the PART_VI_1_* rates.
6246
+ * gaps:
6247
+ * - The rate stack itself (BASIC_RATE 38%, FEDERAL_ABATEMENT 10%, SBD_RATE 19%)
6248
+ * is transcribed from ITA s.123/124/125, which are NOT vendored.
6249
+ * - The thresholds (BUSINESS_LIMIT, TC_GRIND_*, AAII_*, SRED_*) and the
6250
+ * refundable fractions have no vendored source.
6251
+ * - No value in this file has been checked against a primary document on a
6252
+ * recorded date, which is what `verified: never` means. It is a REFERENCE
6253
+ * table: a host filing a real return supplies CRA-published figures through
6254
+ * CORP_TAX_RATE_BOOK.
6102
6255
  */
6103
6256
  const CORP_TAX_2024 = Object.freeze({
6104
6257
  BASIC_RATE: .38,
@@ -6381,6 +6534,21 @@ function blendProvinceRateTable(baseTable, changes, periodStart, periodEnd) {
6381
6534
  * ⚠ REFERENCE VALUES for tax year 2024 — verify against each province's current
6382
6535
  * schedule before filing. They are year-versioned in {@link PROVINCE_RATE_BOOK}
6383
6536
  * so a host wires the authoritative figures without an engine change.
6537
+ *
6538
+ * @statutory-data
6539
+ * book: PROVINCE_RATE_BOOK
6540
+ * covers: 2024
6541
+ * status: reference-only
6542
+ * verified: never
6543
+ * sources: none
6544
+ * gaps:
6545
+ * - All 22 figures (11 provinces × general + small-business) are unsourced.
6546
+ * Each province legislates its own rate and publishes its own schedule;
6547
+ * none of those are vendored, and several provinces have changed rates
6548
+ * since 2024 — so these values are a plausible starting shape, not a
6549
+ * filing authority, and the header above says so.
6550
+ * - A host MUST supply its own PROVINCE_RATE_BOOK before filing a T2 with a
6551
+ * Schedule 5. This is the single largest provenance gap in the package.
6384
6552
  */
6385
6553
  /** 2024 reference rates (general / small-business). Host overrides via the book. */
6386
6554
  const PROVINCE_RATES_2024 = Object.freeze({
@@ -9158,6 +9326,21 @@ function formatConformanceReport(summary) {
9158
9326
  * 2026-04-29 — wire that year via the rate book.
9159
9327
  *
9160
9328
  * Never mutate a shipped year — add a host rate-book entry for a new year.
9329
+ *
9330
+ * @statutory-data
9331
+ * book: QC_TAX_RATE_BOOK
9332
+ * covers: 2024
9333
+ * status: reference-only
9334
+ * verified: never
9335
+ * sources: none
9336
+ * gaps:
9337
+ * - GENERAL_RATE (11.5%), SMALL_BUSINESS_RATE (3.2%), BUSINESS_LIMIT and
9338
+ * SBD_HOURS_THRESHOLD (5,500) are transcribed from the Taxation Act (Québec)
9339
+ * and Revenu Québec guidance, neither of which is vendored. The CO-17 forms
9340
+ * under research/sources/rq-forms carry line captions, not rates.
9341
+ * - The doc comment above notes the SBD rate rises (small-business rate 2.2%)
9342
+ * for tax years beginning after 2026-04-29. That year is NOT in this book —
9343
+ * resolving 2026 returns the 2024 entry. A host must wire it.
9161
9344
  */
9162
9345
  const QC_TAX_2024 = Object.freeze({
9163
9346
  name: "Québec",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/ca-tax",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "Canadian corporate tax engine for @classytic/tax-core — federal T2, Alberta AT1 and Québec CO-17 with form definitions traced to the published forms, plus GST/HST/PST/QST with dated provincial regimes.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -64,7 +64,9 @@
64
64
  "lint:fix": "biome check src/ tests/ --write",
65
65
  "check": "biome ci src/ tests/ --diagnostic-level=error",
66
66
  "prepublishOnly": "npm run check && npm run build && npm run typecheck && npm test",
67
- "push": "classytic-push"
67
+ "push": "classytic-push",
68
+ "format": "biome check --write --staged",
69
+ "format:baseline": "biome check --write src/ tests/"
68
70
  },
69
71
  "peerDependencies": {
70
72
  "@classytic/primitives": ">=0.26.4",