@classytic/ca-tax 0.0.19 → 0.0.21

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/dist/t2.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { Mt as AT1_SCHEDULE_21_POOLS, ot as SCHEDULE_1_LINE_BY_NUMBER } from "./forms.mjs";
1
+ import { Ft as AT1_SCHEDULE_21_POOLS, ot as SCHEDULE_1_LINE_BY_NUMBER } from "./forms.mjs";
2
2
  //#region src/t2/rates/rate-book.ts
3
3
  /**
4
4
  * Resolve the table in effect for `taxYear` — the entry with the greatest
@@ -57,6 +57,46 @@ 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
+ * - research/sources/tra-forms/pdf/AT1SCH01-small-business-deduction-TRA11723.pdf
73
+ * page 1, "Calculation of the Alberta Small Business Deduction", column F
74
+ * ("SBD Rate") — SMALL_BUSINESS_RATE, by subtraction. See the gap note.
75
+ * gaps:
76
+ * - SMALL_BUSINESS_RATE (2%) is stated DIRECTLY by no vendored source. The AT1
77
+ * spec states no small-business rate at all: its only rate multipliers are
78
+ * the five general-rate bands above.
79
+ *
80
+ * It is now corroborated arithmetically, which is a real improvement on the
81
+ * "appears in NO vendored source" this note used to say. AT1 Schedule 1's
82
+ * own calculation table pre-prints an SBD rate per period, and that rate is
83
+ * the general rate MINUS the small-business rate:
84
+ *
85
+ * 2009-04-01 .070 = 10% − 3% 2019-07-01 .090 = 11% − 2%
86
+ * 2015-07-01 .090 = 12% − 3% 2020-01-01 .080 = 10% − 2%
87
+ * 2017-01-01 .100 = 12% − 2% 2020-07-01 .060 = 8% − 2%
88
+ *
89
+ * Six bands against the general table's five: the extra split at
90
+ * 2017-01-01 is the small-business rate itself moving from 3% to 2%, which
91
+ * the general table has no reason to record. So 2% is right for every year
92
+ * this book covers, and 3% would be needed for a pre-2017 year — which the
93
+ * book does not have an entry for and must not silently apply 2% to.
94
+ *
95
+ * `AT1_SCHEDULE_1_RATE_PERIODS` carries the printed table, and a test
96
+ * cross-checks it against these two constants in both directions, so a
97
+ * future edit to either cannot quietly break the agreement. Still worth
98
+ * confirming against the Alberta Corporate Tax Act before filing a
99
+ * pre-2017 year.
60
100
  */
61
101
  const AB_TAX_2024 = Object.freeze({
62
102
  GENERAL_RATE: .08,
@@ -240,6 +280,67 @@ function computeAlbertaTax(input, rates = AB_TAX_2024) {
240
280
  };
241
281
  }
242
282
  //#endregion
283
+ //#region src/t2/at1/schedules/schedule21-current-year-loss.ts
284
+ /** Lines that are subtotals on the printed form and have no NetFile line code. */
285
+ const SCHEDULE_21_PART_1_PRINT_ONLY = ["013", "015"];
286
+ /**
287
+ * Net capital losses deducted — the CAPITAL pool's applied amount times the
288
+ * inclusion rate.
289
+ *
290
+ * Shared, not duplicated: Schedule 21 line 003 and Schedule 12 line 066 must
291
+ * state the same figure ("Schedule 21 line 061 x Inclusion Rate" is printed over
292
+ * 066), and computing it in two places is how they would drift.
293
+ */
294
+ function allowableCapitalLossApplied(applied, inclusionRate = .5) {
295
+ return Math.round(inclusionRate * Math.max(0, Math.round(applied)));
296
+ }
297
+ /** A positive whole-dollar amount — every deduction in this part is "entered as a positive". */
298
+ const positive = (v) => Math.max(0, Math.round(v ?? 0));
299
+ /** Normalise −0, which `Math.min(0, …)` and negation both produce. */
300
+ const noNegativeZero = (v) => v + 0;
301
+ function computeCurrentYearNonCapitalLoss(input) {
302
+ const issues = [];
303
+ const netIncome = Math.round(input.netIncome);
304
+ const optional = (v) => v === void 0 ? void 0 : positive(v);
305
+ const l002 = optional(input.rifeDeducted);
306
+ const l003 = input.capitalLossApplied === void 0 ? void 0 : allowableCapitalLossApplied(input.capitalLossApplied, input.inclusionRate);
307
+ const l005 = optional(input.taxableDividendsDeductible);
308
+ const l007 = optional(input.partVI1TaxDeductible);
309
+ const l011 = optional(input.prospectorsShares);
310
+ const l012 = optional(input.nonQualifiedSecurities);
311
+ const l017 = positive(input.section110_5Additions);
312
+ const l019 = optional(input.farmLoss);
313
+ const l013 = [
314
+ l002,
315
+ l003,
316
+ l005,
317
+ l007,
318
+ l011,
319
+ l012
320
+ ].reduce((n, v) => n + (v ?? 0), 0);
321
+ const l015 = noNegativeZero(Math.min(0, netIncome - l013));
322
+ const l021 = noNegativeZero(Math.min(0, l015 - l017 + (l019 ?? 0)));
323
+ if (input.federalSection110_5Additions !== void 0 && l017 > positive(input.federalSection110_5Additions)) issues.push(`AT1 Schedule 21 line 017 (${l017}) exceeds federal T2 line 355 (${positive(input.federalSection110_5Additions)}). The specification allows this only to the extent it does not increase AT1 lines 070/071 — confirm before filing.`);
324
+ return {
325
+ lines: {
326
+ "001": netIncome,
327
+ ...l002 !== void 0 ? { "002": l002 } : {},
328
+ ...l003 !== void 0 ? { "003": l003 } : {},
329
+ ...l005 !== void 0 ? { "005": l005 } : {},
330
+ ...l007 !== void 0 ? { "007": l007 } : {},
331
+ ...l011 !== void 0 ? { "011": l011 } : {},
332
+ ...l012 !== void 0 ? { "012": l012 } : {},
333
+ "013": l013,
334
+ "015": l015,
335
+ "017": l017,
336
+ ...l019 !== void 0 ? { "019": l019 } : {},
337
+ "021": l021
338
+ },
339
+ currentYearLoss: noNegativeZero(-l021),
340
+ issues
341
+ };
342
+ }
343
+ //#endregion
243
344
  //#region src/t2/at1/filing/at1-schedule-line-items.ts
244
345
  /**
245
346
  * Schedules this module can put into a filing payload. Exported so callers and
@@ -360,7 +461,27 @@ function schedule17Values(result) {
360
461
  values
361
462
  };
362
463
  }
363
- /** Field ids per category, from the verified field map. */
464
+ /**
465
+ * Field ids per category, from the verified field map.
466
+ *
467
+ * ── KNOWN GAP: 054 is filed short by line 053 ───────────────────────────────
468
+ *
469
+ * Shares are not like the other five. The page prints TWO grids: the shares
470
+ * grid leaves its column D unnumbered, and the shares gain is struck below it
471
+ * at 054 *after* line 053 has added federal Schedule 6 line 160 in. See
472
+ * `../forms/schedule18.ts`, where 054 is now modelled as that sum.
473
+ *
474
+ * This builder still files the raw `category.gain` at 054, and files nothing
475
+ * at 053 at all, because `AlbertaSchedule18Input` has no field for the federal
476
+ * amount — so a corporation with a Schedule 6 line 160 figure transmits a
477
+ * shares gain missing it, and `computeAlbertaSchedule18` carries the same
478
+ * shortfall into line 062 and on to the taxable gain at 076.
479
+ *
480
+ * Closing it needs an optional `federalScheduleSixSharesAddition` on the input
481
+ * (defaulting to 0, so existing callers are unaffected), added to the shares
482
+ * gain in the compute and emitted here as 053. Left as a separate change
483
+ * rather than smuggled into a form-definition fix.
484
+ */
364
485
  const S18_CATEGORY_FIELDS = {
365
486
  shares: {
366
487
  proceeds: "002",
@@ -634,7 +755,7 @@ function schedule4970Values(result) {
634
755
  */
635
756
  function schedule12LossDeductions(alberta, federal = {}, inclusionRate = .5) {
636
757
  const applied = (c) => Math.max(0, Math.round(c?.appliedCurrentYear ?? 0));
637
- const allowable = (c) => Math.round(inclusionRate * applied(c));
758
+ const allowable = (c) => allowableCapitalLossApplied(c?.appliedCurrentYear ?? 0, inclusionRate);
638
759
  return {
639
760
  nonCapital: {
640
761
  alberta: applied(alberta.nonCapital),
@@ -819,7 +940,14 @@ function schedule21Values$1(input) {
819
940
  lineItemId: at1LineItemId("021", fieldId),
820
941
  value
821
942
  });
822
- if (input.currentYearNonCapitalLoss !== void 0) put("021", input.currentYearNonCapitalLoss);
943
+ if (input.currentYearLoss) {
944
+ const printOnly = new Set(SCHEDULE_21_PART_1_PRINT_ONLY);
945
+ for (const [line, value] of Object.entries(input.currentYearLoss.lines)) {
946
+ if (printOnly.has(line) || value === void 0) continue;
947
+ if (!(line === "001" || line === "017" || line === "021" || line === "003") && value === 0) continue;
948
+ put(line, value);
949
+ }
950
+ } else if (input.currentYearNonCapitalLoss !== void 0) put("021", -Math.abs(input.currentYearNonCapitalLoss) + 0);
823
951
  for (const [inputKey, poolKey] of Object.entries(POOL_KEY)) {
824
952
  const c = input[inputKey];
825
953
  if (!c) continue;
@@ -1094,32 +1222,20 @@ function schedule20Values(input) {
1094
1222
  put("046", input.maximum.gainsComponent);
1095
1223
  put("048", input.maximum.maximumDeduction);
1096
1224
  }
1097
- const cf = input.gifts?.carryforward;
1098
- if (cf?.yearOfOrigin !== void 0) {
1099
- values.push({
1100
- lineItemId: at1LineItemId("020", "090"),
1101
- value: cf.yearOfOrigin
1102
- });
1103
- if (cf.charitable !== void 0) values.push({
1104
- lineItemId: at1LineItemId("020", "092"),
1105
- value: cf.charitable
1106
- });
1107
- if (cf.toCanadaOrProvince !== void 0) values.push({
1108
- lineItemId: at1LineItemId("020", "094"),
1109
- value: cf.toCanadaOrProvince
1110
- });
1111
- if (cf.culturalProperty !== void 0) values.push({
1112
- lineItemId: at1LineItemId("020", "096"),
1113
- value: cf.culturalProperty
1114
- });
1115
- if (cf.ecologicalLand !== void 0) values.push({
1116
- lineItemId: at1LineItemId("020", "098"),
1117
- value: cf.ecologicalLand
1118
- });
1119
- if (cf.medicine !== void 0) values.push({
1120
- lineItemId: at1LineItemId("020", "100"),
1121
- value: cf.medicine
1225
+ let occurrence = 0;
1226
+ for (const cf of input.gifts?.carryforward ?? []) {
1227
+ if (cf.yearOfOrigin === void 0) continue;
1228
+ occurrence += 1;
1229
+ const row = (field, value) => values.push({
1230
+ lineItemId: at1LineItemId("020", field, occurrence),
1231
+ value
1122
1232
  });
1233
+ row("090", cf.yearOfOrigin);
1234
+ if (cf.charitable !== void 0) row("092", cf.charitable);
1235
+ if (cf.toCanadaOrProvince !== void 0) row("094", cf.toCanadaOrProvince);
1236
+ if (cf.culturalProperty !== void 0) row("096", cf.culturalProperty);
1237
+ if (cf.ecologicalLand !== void 0) row("098", cf.ecologicalLand);
1238
+ if (cf.medicine !== void 0) row("100", cf.medicine);
1123
1239
  }
1124
1240
  return {
1125
1241
  scheduleId: "020",
@@ -3032,7 +3148,15 @@ function computeAlbertaReturn(input) {
3032
3148
  federalTaxableIncome: input.federalTaxableIncome,
3033
3149
  allocationFactor,
3034
3150
  activeBusinessIncome: input.activeBusinessIncome,
3035
- ...input.businessLimit !== void 0 ? { businessLimit: input.businessLimit } : {}
3151
+ ...input.businessLimit !== void 0 ? { businessLimit: input.businessLimit } : {},
3152
+ ...input.status !== void 0 ? { status: input.status } : {},
3153
+ ...input.wasCcpcThroughoutYear !== void 0 ? { wasCcpcThroughoutYear: input.wasCcpcThroughoutYear } : {},
3154
+ ...input.isAssociated !== void 0 ? { isAssociated: input.isAssociated } : {},
3155
+ ...input.allocatedBusinessLimit !== void 0 ? { allocatedBusinessLimit: input.allocatedBusinessLimit } : {},
3156
+ ...input.periodStart && input.periodEnd ? {
3157
+ periodStart: input.periodStart,
3158
+ periodEnd: input.periodEnd
3159
+ } : {}
3036
3160
  }, rates);
3037
3161
  const issues = [];
3038
3162
  let iegGroup;
@@ -3088,14 +3212,25 @@ function computeAlbertaReturn(input) {
3088
3212
  }
3089
3213
  const schedulePayloads = [];
3090
3214
  const sched = input.schedules;
3215
+ const schedule4 = sched?.foreignInvestmentTaxCredit;
3216
+ const otherDeductionsCredits = sched?.otherDeductionsCredits ? computeSchedule3({
3217
+ ...sched.otherDeductionsCredits,
3218
+ mad: {
3219
+ taxPayableBeforeDeduction: albertaTax.basicTax,
3220
+ line070: albertaTax.smallBusinessDeduction,
3221
+ line071: input.manufacturingDeduction ?? 0,
3222
+ line072: schedule4?.totalAllowableCredit ?? 0,
3223
+ line074: input.politicalContributionsTaxCredit ?? 0
3224
+ }
3225
+ }) : void 0;
3091
3226
  if (sched?.smallBusinessDeduction) {
3092
3227
  schedulePayloads.push(schedule1Values$1(sched.smallBusinessDeduction));
3093
3228
  issues.push(...sched.smallBusinessDeduction.result.issues);
3094
3229
  }
3095
3230
  if (sched?.allocation) schedulePayloads.push(schedule2Values$1(sched.allocation));
3096
- if (sched?.otherDeductionsCredits) {
3097
- schedulePayloads.push(schedule3Values(sched.otherDeductionsCredits));
3098
- issues.push(...sched.otherDeductionsCredits.issues);
3231
+ if (otherDeductionsCredits) {
3232
+ schedulePayloads.push(schedule3Values(otherDeductionsCredits));
3233
+ issues.push(...otherDeductionsCredits.issues);
3099
3234
  }
3100
3235
  if (sched?.foreignInvestmentTaxCredit) {
3101
3236
  schedulePayloads.push(schedule4Values(sched.foreignInvestmentTaxCredit));
@@ -3161,9 +3296,12 @@ const at1Engine = {
3161
3296
  return raw;
3162
3297
  },
3163
3298
  compute(input) {
3299
+ const iso = (d) => d.toISOString().slice(0, 10);
3164
3300
  const result = computeAlbertaReturn({
3165
3301
  ...input,
3166
- taxYear: input.taxYear ?? input.period.end.getFullYear()
3302
+ taxYear: input.taxYear ?? input.period.end.getFullYear(),
3303
+ periodStart: input.periodStart ?? iso(input.period.start),
3304
+ periodEnd: input.periodEnd ?? iso(input.period.end)
3167
3305
  });
3168
3306
  return {
3169
3307
  jurisdiction: "CA-AB",
@@ -3709,6 +3847,23 @@ const AT1_EDI_LINE_ITEMS = [
3709
3847
  fmt: "text"
3710
3848
  }
3711
3849
  ];
3850
+ /**
3851
+ * Every Line-Item ID the EDI schedule can file.
3852
+ *
3853
+ * Derived from the table above rather than restated, so the two cannot
3854
+ * disagree. Exported because a consumer's INTERFACE needs it: the return
3855
+ * editor labels these boxes "Software Certification Code (line EDI001)", and
3856
+ * that number is a promise to the preparer that what they type lands there.
3857
+ *
3858
+ * Every other schedule's citations are checked against a `FormDefinition`.
3859
+ * The EDI schedule has none — it exists only in the Net File XML, and spec
3860
+ * §3.3.6.1 is not vendored into this package — so this list is what makes the
3861
+ * claim checkable at all. A wrong line number is worse than no line number:
3862
+ * it files a real figure against the wrong box and nothing looks broken.
3863
+ */
3864
+ const AT1_EDI_LINES = AT1_EDI_LINE_ITEMS.map((li) => li.id);
3865
+ /** The FIELD half of each EDI id — `EDI001001` → `001`, which is what a preparer sees. */
3866
+ const AT1_EDI_FIELDS = AT1_EDI_LINES.map((id) => id.slice(3, 6));
3712
3867
  //#endregion
3713
3868
  //#region src/t2/at1/filing/at1-netfile-renderer.ts
3714
3869
  /**
@@ -4442,6 +4597,23 @@ function albertaCurrentYearLoss(result) {
4442
4597
  *
4443
4598
  * Rates per CRA T4012 / ITR Schedule II. T2 CCA amounts are whole dollars (GIFI
4444
4599
  * convention), so the CCA calculator works in integer dollars — NOT cents.
4600
+ *
4601
+ * @statutory-data
4602
+ * book: CCA_RATE_BOOK
4603
+ * covers: 2024
4604
+ * status: partially-sourced
4605
+ * verified: never
4606
+ * sources:
4607
+ * - research/sources/legislation/ITR-section-1100-cca-deductions.txt —
4608
+ * the Reg 1100(1) allowance rates these classes are drawn from.
4609
+ * - research/sources/legislation/ITR-schedule-III-class-13-leasehold.txt —
4610
+ * class 13, which is straight-line and deliberately NOT in this table.
4611
+ * gaps:
4612
+ * - The per-class rates are transcribed from ITR Schedule II, which is not
4613
+ * vendored; ITR s.1100 above is the deduction mechanism, not the class list.
4614
+ * - No class rate has been checked against a primary document on a recorded
4615
+ * date. Class rates move rarely, so staleness is a lower risk here than in
4616
+ * the year-tagged tax tables — but it is unmeasured, not measured-as-fine.
4445
4617
  */
4446
4618
  const CCA_DECLINING_BALANCE_RATES_2024 = Object.freeze({
4447
4619
  "1": .04,
@@ -5474,7 +5646,7 @@ const nn$22 = (v) => Math.max(0, v ?? 0);
5474
5646
  * side is a real override and must not be swallowed by the default, so the
5475
5647
  * check is on `undefined` rather than falsiness.
5476
5648
  */
5477
- function resolveCarryforward(federal, alberta) {
5649
+ function resolveCarryforwardRow(federal, alberta) {
5478
5650
  const f = federal ?? {};
5479
5651
  const a = alberta ?? {};
5480
5652
  const pick = (k) => a[k] !== void 0 ? a[k] : f[k];
@@ -5492,6 +5664,30 @@ function resolveCarryforward(federal, alberta) {
5492
5664
  }
5493
5665
  return out;
5494
5666
  }
5667
+ /**
5668
+ * Resolve every carryforward row, and drop the ones that say nothing.
5669
+ *
5670
+ * The row count is the LONGER of the two sides: an Alberta-only year (a
5671
+ * carryforward federal does not recognise) must survive, and so must a federal
5672
+ * year with no Alberta override.
5673
+ *
5674
+ * A row that resolves to `{}` is discarded rather than filed. `schedule20Values`
5675
+ * gates each occurrence on `yearOfOrigin` being present — 090 is mandatory
5676
+ * whenever any of 092-100 has a value — so an empty row would either transmit
5677
+ * nothing and still consume an occurrence number, shifting every row after it,
5678
+ * or transmit figures with no year against them. Neither is a return TRA can
5679
+ * read.
5680
+ */
5681
+ function resolveCarryforward(federal, alberta) {
5682
+ const f = federal ?? [];
5683
+ const a = alberta ?? [];
5684
+ const rows = [];
5685
+ for (let i = 0; i < Math.max(f.length, a.length); i += 1) {
5686
+ const row = resolveCarryforwardRow(f[i], a[i]);
5687
+ if (Object.keys(row).length > 0) rows.push(row);
5688
+ }
5689
+ return rows;
5690
+ }
5495
5691
  function computeSchedule20(input) {
5496
5692
  const openingBalance = nn$22(input.openingBalance);
5497
5693
  const expired = nn$22(input.expired);
@@ -6196,6 +6392,28 @@ function federalSchedulePayloads(r) {
6196
6392
  *
6197
6393
  * Never mutate a shipped year — add a new {@link RateBookEntry} for the new
6198
6394
  * year (in the host book) so prior-year returns stay reproducible.
6395
+ *
6396
+ * @statutory-data
6397
+ * book: CORP_TAX_RATE_BOOK
6398
+ * covers: 2024
6399
+ * status: partially-sourced
6400
+ * verified: never
6401
+ * sources:
6402
+ * - research/sources/legislation/ITA-section-123.4-general-rate-reduction.txt —
6403
+ * GENERAL_RATE_REDUCTION (13%).
6404
+ * - research/sources/legislation/T4012-lines-638-639-general-tax-reduction.txt —
6405
+ * the general tax reduction as CRA states it on the return.
6406
+ * - research/sources/legislation/ITA-section-191.1-part-vi-1-tax.txt —
6407
+ * the PART_VI_1_* rates.
6408
+ * gaps:
6409
+ * - The rate stack itself (BASIC_RATE 38%, FEDERAL_ABATEMENT 10%, SBD_RATE 19%)
6410
+ * is transcribed from ITA s.123/124/125, which are NOT vendored.
6411
+ * - The thresholds (BUSINESS_LIMIT, TC_GRIND_*, AAII_*, SRED_*) and the
6412
+ * refundable fractions have no vendored source.
6413
+ * - No value in this file has been checked against a primary document on a
6414
+ * recorded date, which is what `verified: never` means. It is a REFERENCE
6415
+ * table: a host filing a real return supplies CRA-published figures through
6416
+ * CORP_TAX_RATE_BOOK.
6199
6417
  */
6200
6418
  const CORP_TAX_2024 = Object.freeze({
6201
6419
  BASIC_RATE: .38,
@@ -6478,6 +6696,21 @@ function blendProvinceRateTable(baseTable, changes, periodStart, periodEnd) {
6478
6696
  * ⚠ REFERENCE VALUES for tax year 2024 — verify against each province's current
6479
6697
  * schedule before filing. They are year-versioned in {@link PROVINCE_RATE_BOOK}
6480
6698
  * so a host wires the authoritative figures without an engine change.
6699
+ *
6700
+ * @statutory-data
6701
+ * book: PROVINCE_RATE_BOOK
6702
+ * covers: 2024
6703
+ * status: reference-only
6704
+ * verified: never
6705
+ * sources: none
6706
+ * gaps:
6707
+ * - All 22 figures (11 provinces × general + small-business) are unsourced.
6708
+ * Each province legislates its own rate and publishes its own schedule;
6709
+ * none of those are vendored, and several provinces have changed rates
6710
+ * since 2024 — so these values are a plausible starting shape, not a
6711
+ * filing authority, and the header above says so.
6712
+ * - A host MUST supply its own PROVINCE_RATE_BOOK before filing a T2 with a
6713
+ * Schedule 5. This is the single largest provenance gap in the package.
6481
6714
  */
6482
6715
  /** 2024 reference rates (general / small-business). Host overrides via the book. */
6483
6716
  const PROVINCE_RATES_2024 = Object.freeze({
@@ -9255,6 +9488,21 @@ function formatConformanceReport(summary) {
9255
9488
  * 2026-04-29 — wire that year via the rate book.
9256
9489
  *
9257
9490
  * Never mutate a shipped year — add a host rate-book entry for a new year.
9491
+ *
9492
+ * @statutory-data
9493
+ * book: QC_TAX_RATE_BOOK
9494
+ * covers: 2024
9495
+ * status: reference-only
9496
+ * verified: never
9497
+ * sources: none
9498
+ * gaps:
9499
+ * - GENERAL_RATE (11.5%), SMALL_BUSINESS_RATE (3.2%), BUSINESS_LIMIT and
9500
+ * SBD_HOURS_THRESHOLD (5,500) are transcribed from the Taxation Act (Québec)
9501
+ * and Revenu Québec guidance, neither of which is vendored. The CO-17 forms
9502
+ * under research/sources/rq-forms carry line captions, not rates.
9503
+ * - The doc comment above notes the SBD rate rises (small-business rate 2.2%)
9504
+ * for tax years beginning after 2026-04-29. That year is NOT in this book —
9505
+ * resolving 2026 returns the 2024 entry. A host must wire it.
9258
9506
  */
9259
9507
  const QC_TAX_2024 = Object.freeze({
9260
9508
  name: "Québec",
@@ -9982,4 +10230,4 @@ function computeMpDeduction(input, rates = MP_RATES_2024) {
9982
10230
  };
9983
10231
  }
9984
10232
  //#endregion
9985
- export { computeSchedule6 as $, resolveRates as $i, resolveCcaRates as $n, computeIegReductionFactor as $r, dividendsDeductibleS112 as $t, LRIP_INVESTMENT_CORPORATION_MULTIPLE as A, schedule12LossDeductions as Ai, AT1_RESERVE_LINES as An, renderRsiHeader as Ar, computeExcessIfe as At, computeZetm as B, schedule2Values$1 as Bi, CLASS_14_1_MINIMUM_DEDUCTION as Bn, assertCriticalFields as Br, assessEifel as Bt, runConformance as C, SINGLE_JURISDICTION_ALBERTA_FACTOR as Ci, AT1_DONATION_INCOME_RATE as Cn, RSI_NEGATIVE_PREFIX as Cr, EIFEL_STANDARD_RATIO_FROM as Ct, SCHEDULE_88_MAX_URLS as D, AT1_SCHEDULES_WITH_BUILDERS as Di, SECTION_34_2_GROSS_UP as Dn, formatRsiDate as Dr, computeBorrowings as Dt, computeSchedule101 as E, AT1_SCHEDULES_WITHOUT_BUILDERS as Ei, AT1_DISPOSITION_CATEGORIES as En, formatRsiAmount as Er, ratioOfPermissibleExpenses as Et, LARGE_CORPORATION_THRESHOLD as F, schedule18Values as Fi, computeAlbertaSchedule13 as Fn, At1MandatoryFieldMissingError as Fr, computeLossPortionFromIfe as Ft, computeCde as G, computeDayWeightedGeneralTax as Gi, computeClass13 as Gn, computeAlbertaReturn as Gr, PROVINCE_RATES_2024 as Gt, computeBusinessLimitAllocation as H, computeAlbertaTax as Hi, CLASS_14_1_TRANSITIONAL_RATE as Hn, at1YesNo as Hr, computeRifeUnderSubsection111_8 as Ht, computeTaxableCapital as I, schedule1Values$1 as Ii, UnsupportedCcaClassError as In, At1TaxPayableMismatchError as Ir, computePartnershipIfe as It, computeCumulativeForeignResource as J, resolveAlbertaTaxRates as Ji, computeClass141RecaptureReduction as Jn, computeIegAgreement as Jr, resolveProvinceRates as Jt, computeCee as K, AB_TAX_2024 as Ki, computeClass14 as Kn, allocateIegEvenly as Kr, PROVINCE_RATE_BOOK as Kt, computeSchedule31 as L, schedule20Values as Li, computeCcaClass as Ln, albertaBalanceUnpaid as Lr, computePartnershipIfeAddBack as Lt, computeSchedule54 as M, schedule13Values as Mi, computeAlbertaSchedule17 as Mn, renderAt1NetFile as Mr, computeInterestAndFinancingExpenses as Mt, computeGrip as N, schedule16Values as Ni, assistanceFrom as Nn, AT1_CRITICAL_MANDATORY_FIELDS as Nr, computeInterestAndFinancingRevenues as Nt, normalizeSchedule88 as O, at1LineItemId as Oi, computeAlbertaSchedule18 as On, formatRsiText as Or, computeCapitalizedIfe as Ot, computeSchedule43 as P, schedule17Values as Pi, computeAlbertaSchedule16 as Pn, At1CriticalFieldMissingError as Pr, computeLoans as Pt, computeSpecifiedForeignExploration as Q, latestRateYear as Qi, isDecliningBalanceClass as Qn, computeIegBaseAmount as Qr, computeTaxableIncome as Qt, ITC_RECAPTURE_PERIOD_YEARS as R, schedule21Values$1 as Ri, computeCcaSchedule as Rn, assertAt1MandatoryComplete as Rr, computeResourceIfe as Rt, formatConformanceReport as S, schedule3Values as Si, AT1_DONATION_GAIN_RATE as Sn, RSI_DELIMITER as Sr, EIFEL_STANDARD_RATIO as St, computeFederalT2 as T, computeAt4970 as Ti, computeSchedule20 as Tn, RsiLineItemError as Tr, computeEifelLimitation as Tt, computeSchedule21 as U, computeAlbertaSbd as Ui, MAX_LEASEHOLD_PERIODS as Un, xmlEscape as Ur, FOREIGN_TAX_CREDIT_GROSS_UP as Ut, allocateEvenly as V, schedule4970Values as Vi, CLASS_14_1_RECAPTURE_REDUCTION_RATE as Vn, at1TaxPayableDeductions as Vr, computeEifelCapacity as Vt, computeSchedule13 as W, AB_GENERAL_RATE_BANDS as Wi, MIN_LEASEHOLD_PERIODS as Wn, at1Engine as Wr, computeAdjustedTaxableIncome as Wt, computeForeignExploration as X, extendRateBook as Xi, CCA_DECLINING_BALANCE_RATES_2024 as Xn, IEG_2024 as Xr, dayWeightedRate as Xt, computeDepletion as Y, earliestRateYear as Yi, leaseholdPeriods as Yn, computeIegGroupFigures as Yr, blendProvinceRateTable as Yt, computeSchedule12ResourceDeductions as Z, hasExactRateYear as Zi, CCA_RATE_BOOK as Zn, computeIeg as Zr, charitableDonationsDeduction as Zt, QC_TAX_2024 as _, computeSfedeCountrySuccessor as _i, computeRifeContinuity as _n, toRsiHeader as _r, terminalLossDeduction as _t, computeCanadianMPProfits as a, computeCdeRegular as ai, computeAggregateInvestmentIncome as an, albertaDispositionAdjustments as ar, computePart4Rdtoh as at, T2_LINE_META as b, schedule4Values as bi, computeLimitedPartnershipLossRow as bn, toRsiSchedule as br, partVI1DeductionMultiple as bt, computeSmallManufacturerTest as c, computeCeeSuccessor as ci, CORP_TAX_2024 as cn, albertaResourceDeductionDifference as cr, amortizationAddBack as ct, t2Engine as d, computeCmedb as di, federalSchedulePayloads as dn, reconcileAlbertaNetIncome as dr, computeSchedule1 as dt, computeIegEligibleExpenditures as ei, netCapitalLossApplied as en, albertaAbilDifference as er, computeProvincialAllocation as et, renderCo17DraftReturn as f, computeEdaRegular as fi, parseT2LineItemId as fn, LossCarrybackError as fr, deferredIncomeTaxProvisionAddBack as ft, computeQuebecTax as g, computeSfedeCountryRegular as gi, computeOtherLossByYearOfOrigin as gn, validateAt1Transmitter as gr, recaptureAddBack as gt, computeQuebecAllocationFactor as h, computeFedeSuccessor as hi, computeNonCapitalLossByYearOfOrigin as hn, assertAt1TransmitterValid as hr, mealsAndEntertainmentAddBack as ht, SMALL_MANUFACTURER_INCOME_THRESHOLD as i, computeCcogpeSuccessor as ii, computeAdjustedAggregateInvestmentIncome as in, albertaCurrentYearLoss as ir, REFUNDABLE_PART_I_RATE as it, LRIP_INVESTMENT_INCOME_FACTOR as j, schedule12Values as ji, AT1_RESERVE_TOTAL_LINES as jn, renderRsiLineItem as jr, computeExemptIfe as jt, computeSchedule55 as k, schedule10Values as ki, AT1_RESERVE_KINDS as kn, renderAt1Rsi as kr, computeClause95Amounts as kt, computeT2Settlement as l, computeCfreRegular as li, CORP_TAX_RATE_BOOK as ln, albertaTerminalLossDifference as lr, assertSchedule1Fileable as lt, computeQuebecReturn as m, computeFedeRegular as mi, T2_CERTIFICATION_FIXTURES as mn, At1TransmitterInvalidError as mr, incomeTaxProvisionAddBack as mt, MP_GROSS_REVENUE_THRESHOLD as n, computeAlbertaSchedule15 as ni, computeCcpcActiveBusinessTax as nn, albertaCcaDifference as nr, computeSchedule4Losses as nt, computeMpDeduction as o, computeCdeSuccessor as oi, computeBusinessLimit as on, albertaRecaptureDifference as or, computeSchedule2 as ot, co17Engine as p, computeEdaSuccessor as pi, t2LineItemId as pn, computeLossCarryback as pr, findSchedule1LineDefects as pt, computeCogpe as q, AB_TAX_RATE_BOOK as qi, computeClass141AdditionalAllowance as qn, allocateIegExpenditureLimit as qr, isSchedule5Province as qt, MP_RATES_2024 as r, computeCcogpeRegular as ri, computePartITax as rn, albertaCcaScheduleAdjustments as rr, PART_IV_RATE as rt, computePart2MPProfits as s, computeCeeRegular as si, computeSBD as sn, albertaReserveDifference as sr, Schedule1NotFileableError as st, MP_EXCLUDED_ACTIVITIES as t, iegT661SourceLine as ti, nonCapitalLossApplied as tn, albertaCapitalGainDifference as tr, computeSchedule5 as tt, renderT2DraftReturn as u, computeCfreSuccessor as ui, resolveCorpTaxRates as un, computeSchedule12 as ur, ccaDeduction as ut, QC_TAX_RATE_BOOK as v, schedule15Values as vi, computeLossSchedule as vn, toRsiJacketSchedules as vr, PART_VI_1_DEDUCTION_BANDS as vt, runConformanceSuite as w, computeAllocationFactor as wi, computeDonationMaximum as wn, RSI_WORD_GAP as wr, EIFEL_TRANSITIONAL_RATIO as wt, foldT2Lines as x, computeSchedule3 as xi, computeLimitedPartnershipLosses as xn, RSI_COLUMN_GAP as xr, EIFEL_FIRST_YEAR_START as xt, resolveQuebecTaxRates as y, computeSchedule4 as yi, computeLossContinuity as yn, toRsiLineItems as yr, computePartVI1Deduction as yt, computeItcRecapture as z, schedule29Values as zi, computeSchedule8 as zn, assertAt1TaxPayableReconciles as zr, EIFEL_EFFECTIVE_FROM as zt };
10233
+ export { computeSchedule6 as $, resolveAlbertaTaxRates as $i, resolveCcaRates as $n, computeIeg as $r, dividendsDeductibleS112 as $t, LRIP_INVESTMENT_CORPORATION_MULTIPLE as A, at1LineItemId as Ai, AT1_RESERVE_LINES as An, renderRsiHeader as Ar, computeExcessIfe as At, computeZetm as B, schedule21Values$1 as Bi, CLASS_14_1_MINIMUM_DEDUCTION as Bn, assertAt1MandatoryComplete as Br, assessEifel as Bt, runConformance as C, computeSchedule3 as Ci, AT1_DONATION_INCOME_RATE as Cn, RSI_NEGATIVE_PREFIX as Cr, EIFEL_STANDARD_RATIO_FROM as Ct, SCHEDULE_88_MAX_URLS as D, computeAt4970 as Di, SECTION_34_2_GROSS_UP as Dn, formatRsiDate as Dr, computeBorrowings as Dt, computeSchedule101 as E, computeAllocationFactor as Ei, AT1_DISPOSITION_CATEGORIES as En, formatRsiAmount as Er, ratioOfPermissibleExpenses as Et, LARGE_CORPORATION_THRESHOLD as F, schedule16Values as Fi, computeAlbertaSchedule13 as Fn, AT1_EDI_LINES as Fr, computeLossPortionFromIfe as Ft, computeCde as G, allowableCapitalLossApplied as Gi, computeClass13 as Gn, xmlEscape as Gr, PROVINCE_RATES_2024 as Gt, computeBusinessLimitAllocation as H, schedule2Values$1 as Hi, CLASS_14_1_TRANSITIONAL_RATE as Hn, assertCriticalFields as Hr, computeRifeUnderSubsection111_8 as Ht, computeTaxableCapital as I, schedule17Values as Ii, UnsupportedCcaClassError as In, At1CriticalFieldMissingError as Ir, computePartnershipIfe as It, computeCumulativeForeignResource as J, computeAlbertaSbd as Ji, computeClass141RecaptureReduction as Jn, allocateIegEvenly as Jr, resolveProvinceRates as Jt, computeCee as K, computeCurrentYearNonCapitalLoss as Ki, computeClass14 as Kn, at1Engine as Kr, PROVINCE_RATE_BOOK as Kt, computeSchedule31 as L, schedule18Values as Li, computeCcaClass as Ln, At1MandatoryFieldMissingError as Lr, computePartnershipIfeAddBack as Lt, computeSchedule54 as M, schedule12LossDeductions as Mi, computeAlbertaSchedule17 as Mn, renderAt1NetFile as Mr, computeInterestAndFinancingExpenses as Mt, computeGrip as N, schedule12Values as Ni, assistanceFrom as Nn, AT1_CRITICAL_MANDATORY_FIELDS as Nr, computeInterestAndFinancingRevenues as Nt, normalizeSchedule88 as O, AT1_SCHEDULES_WITHOUT_BUILDERS as Oi, computeAlbertaSchedule18 as On, formatRsiText as Or, computeCapitalizedIfe as Ot, computeSchedule43 as P, schedule13Values as Pi, computeAlbertaSchedule16 as Pn, AT1_EDI_FIELDS as Pr, computeLoans as Pt, computeSpecifiedForeignExploration as Q, AB_TAX_RATE_BOOK as Qi, isDecliningBalanceClass as Qn, IEG_2024 as Qr, computeTaxableIncome as Qt, ITC_RECAPTURE_PERIOD_YEARS as R, schedule1Values$1 as Ri, computeCcaSchedule as Rn, At1TaxPayableMismatchError as Rr, computeResourceIfe as Rt, formatConformanceReport as S, schedule4Values as Si, AT1_DONATION_GAIN_RATE as Sn, RSI_DELIMITER as Sr, EIFEL_STANDARD_RATIO as St, computeFederalT2 as T, SINGLE_JURISDICTION_ALBERTA_FACTOR as Ti, computeSchedule20 as Tn, RsiLineItemError as Tr, computeEifelLimitation as Tt, computeSchedule21 as U, schedule4970Values as Ui, MAX_LEASEHOLD_PERIODS as Un, at1TaxPayableDeductions as Ur, FOREIGN_TAX_CREDIT_GROSS_UP as Ut, allocateEvenly as V, schedule29Values as Vi, CLASS_14_1_RECAPTURE_REDUCTION_RATE as Vn, assertAt1TaxPayableReconciles as Vr, computeEifelCapacity as Vt, computeSchedule13 as W, SCHEDULE_21_PART_1_PRINT_ONLY as Wi, MIN_LEASEHOLD_PERIODS as Wn, at1YesNo as Wr, computeAdjustedTaxableIncome as Wt, computeForeignExploration as X, computeDayWeightedGeneralTax as Xi, CCA_DECLINING_BALANCE_RATES_2024 as Xn, computeIegAgreement as Xr, dayWeightedRate as Xt, computeDepletion as Y, AB_GENERAL_RATE_BANDS as Yi, leaseholdPeriods as Yn, allocateIegExpenditureLimit as Yr, blendProvinceRateTable as Yt, computeSchedule12ResourceDeductions as Z, AB_TAX_2024 as Zi, CCA_RATE_BOOK as Zn, computeIegGroupFigures as Zr, charitableDonationsDeduction as Zt, QC_TAX_2024 as _, computeFedeSuccessor as _i, computeRifeContinuity as _n, toRsiHeader as _r, terminalLossDeduction as _t, computeCanadianMPProfits as a, computeCcogpeRegular as ai, computeAggregateInvestmentIncome as an, albertaDispositionAdjustments as ar, computePart4Rdtoh as at, T2_LINE_META as b, schedule15Values as bi, computeLimitedPartnershipLossRow as bn, toRsiSchedule as br, partVI1DeductionMultiple as bt, computeSmallManufacturerTest as c, computeCdeSuccessor as ci, CORP_TAX_2024 as cn, albertaResourceDeductionDifference as cr, amortizationAddBack as ct, t2Engine as d, computeCfreRegular as di, federalSchedulePayloads as dn, reconcileAlbertaNetIncome as dr, computeSchedule1 as dt, earliestRateYear as ea, computeIegBaseAmount as ei, netCapitalLossApplied as en, albertaAbilDifference as er, computeProvincialAllocation as et, renderCo17DraftReturn as f, computeCfreSuccessor as fi, parseT2LineItemId as fn, LossCarrybackError as fr, deferredIncomeTaxProvisionAddBack as ft, computeQuebecTax as g, computeFedeRegular as gi, computeOtherLossByYearOfOrigin as gn, validateAt1Transmitter as gr, recaptureAddBack as gt, computeQuebecAllocationFactor as h, computeEdaSuccessor as hi, computeNonCapitalLossByYearOfOrigin as hn, assertAt1TransmitterValid as hr, mealsAndEntertainmentAddBack as ht, SMALL_MANUFACTURER_INCOME_THRESHOLD as i, resolveRates as ia, computeAlbertaSchedule15 as ii, computeAdjustedAggregateInvestmentIncome as in, albertaCurrentYearLoss as ir, REFUNDABLE_PART_I_RATE as it, LRIP_INVESTMENT_INCOME_FACTOR as j, schedule10Values as ji, AT1_RESERVE_TOTAL_LINES as jn, renderRsiLineItem as jr, computeExemptIfe as jt, computeSchedule55 as k, AT1_SCHEDULES_WITH_BUILDERS as ki, AT1_RESERVE_KINDS as kn, renderAt1Rsi as kr, computeClause95Amounts as kt, computeT2Settlement as l, computeCeeRegular as li, CORP_TAX_RATE_BOOK as ln, albertaTerminalLossDifference as lr, assertSchedule1Fileable as lt, computeQuebecReturn as m, computeEdaRegular as mi, T2_CERTIFICATION_FIXTURES as mn, At1TransmitterInvalidError as mr, incomeTaxProvisionAddBack as mt, MP_GROSS_REVENUE_THRESHOLD as n, hasExactRateYear as na, computeIegEligibleExpenditures as ni, computeCcpcActiveBusinessTax as nn, albertaCcaDifference as nr, computeSchedule4Losses as nt, computeMpDeduction as o, computeCcogpeSuccessor as oi, computeBusinessLimit as on, albertaRecaptureDifference as or, computeSchedule2 as ot, co17Engine as p, computeCmedb as pi, t2LineItemId as pn, computeLossCarryback as pr, findSchedule1LineDefects as pt, computeCogpe as q, computeAlbertaTax as qi, computeClass141AdditionalAllowance as qn, computeAlbertaReturn as qr, isSchedule5Province as qt, MP_RATES_2024 as r, latestRateYear as ra, iegT661SourceLine as ri, computePartITax as rn, albertaCcaScheduleAdjustments as rr, PART_IV_RATE as rt, computePart2MPProfits as s, computeCdeRegular as si, computeSBD as sn, albertaReserveDifference as sr, Schedule1NotFileableError as st, MP_EXCLUDED_ACTIVITIES as t, extendRateBook as ta, computeIegReductionFactor as ti, nonCapitalLossApplied as tn, albertaCapitalGainDifference as tr, computeSchedule5 as tt, renderT2DraftReturn as u, computeCeeSuccessor as ui, resolveCorpTaxRates as un, computeSchedule12 as ur, ccaDeduction as ut, QC_TAX_RATE_BOOK as v, computeSfedeCountryRegular as vi, computeLossSchedule as vn, toRsiJacketSchedules as vr, PART_VI_1_DEDUCTION_BANDS as vt, runConformanceSuite as w, schedule3Values as wi, computeDonationMaximum as wn, RSI_WORD_GAP as wr, EIFEL_TRANSITIONAL_RATIO as wt, foldT2Lines as x, computeSchedule4 as xi, computeLimitedPartnershipLosses as xn, RSI_COLUMN_GAP as xr, EIFEL_FIRST_YEAR_START as xt, resolveQuebecTaxRates as y, computeSfedeCountrySuccessor as yi, computeLossContinuity as yn, toRsiLineItems as yr, computePartVI1Deduction as yt, computeItcRecapture as z, schedule20Values as zi, computeSchedule8 as zn, albertaBalanceUnpaid as zr, EIFEL_EFFECTIVE_FROM as zt };
@@ -0,0 +1,109 @@
1
+ # Statutory data — provenance and revision
2
+
3
+ Every tax figure this package ships has to say **where it came from** and **when it
4
+ was last checked**, in a form a machine can verify. This file is the convention;
5
+ `tests/statutory-data-provenance.test.ts` is the enforcement.
6
+
7
+ The convention is adapted from Equisoft's [`tax-ca`][tax-ca], which puts a
8
+ `Sources` / `Revised` header on each data file. That is a good idea and we took it.
9
+ We added enforcement because they have none — their own coverage is 15 of 34 files,
10
+ and the RESP grants, the CPI, and the pension increase factors all carry live
11
+ numbers with no header while the build stays green. A convention nothing checks is
12
+ documentation of intent, not provenance.
13
+
14
+ ## The block
15
+
16
+ Every module that ships statutory data carries an `@statutory-data` block at the
17
+ end of its leading JSDoc:
18
+
19
+ ```ts
20
+ /**
21
+ * …the module's ordinary doc comment…
22
+ *
23
+ * @statutory-data
24
+ * book: AB_TAX_RATE_BOOK
25
+ * covers: 2024
26
+ * status: partially-sourced
27
+ * verified: 2026-09-09
28
+ * sources:
29
+ * - research/sources/tra-spec/AT1-Chapter3-2026.4-full.txt §3.2.3.1 line 068 —
30
+ * GENERAL_RATE 8%, as band F of the day-weighted table.
31
+ * gaps:
32
+ * - SMALL_BUSINESS_RATE (2%) appears in NO vendored source.
33
+ */
34
+ ```
35
+
36
+ | Field | Meaning |
37
+ |---|---|
38
+ | `book` | The exported `RateBook` this describes. Omit for effective-dated tables. |
39
+ | `covers` | The years (or effective window) the shipped data holds. **Checked against the real book**, not trusted. |
40
+ | `status` | `primary-sourced` · `partially-sourced` · `reference-only` |
41
+ | `verified` | ISO date the values were last checked against a cited source, or the literal `never`. |
42
+ | `sources` | Cited documents. A `research/sources/...` path **must exist on disk**. |
43
+ | `gaps` | What is *not* sourced. Required unless `status: primary-sourced`. |
44
+
45
+ `verified: never` is a legitimate, honest value. It says nobody has checked these
46
+ figures against a primary document — which is true of most of this package — and it
47
+ is loud precisely because it is allowed rather than faked. Writing today's date to
48
+ make a header look finished is the one thing this convention exists to prevent.
49
+
50
+ ## What the test enforces
51
+
52
+ Four things, three of which `tax-ca`'s layout cannot check at all:
53
+
54
+ 1. **A `RateBook` export requires a block.** Statutory data is identified
55
+ *structurally*, not from a list, so a new rate table without provenance fails.
56
+ You cannot forget.
57
+ 2. **Cited vendored documents must exist.** `tax-ca` cites URLs — one of them
58
+ `taxtips.ca`, a secondary source; one a dead `cra-arc.gc.ca` link — and nothing
59
+ notices when they rot. Our primary documents are checked into
60
+ `research/sources/`, so a citation is a path that resolves or fails the build.
61
+ (Skipped when the research tree is absent, e.g. installed from npm.)
62
+ 3. **`covers:` must match the book's actual years,** read from the imported book.
63
+ 4. **The debt roster is pinned.** The set of `reference-only` and `verified: never`
64
+ files is asserted explicitly. It can shrink; it cannot grow without a
65
+ deliberate edit a reviewer sees.
66
+
67
+ It does **not** judge whether a rate is correct. It asserts that every rate
68
+ declares its authority and that the declaration is not stale in a way a machine can
69
+ see. Correctness is what the vendored documents and the engine suites are for.
70
+
71
+ ## Current position
72
+
73
+ > **Every reference rate book in this package stops at 2024.**
74
+
75
+ That is asserted by the test, not merely written here. The rate book is a *seam*:
76
+ a host supplies authoritative figures per year through `CORP_TAX_RATE_BOOK` and its
77
+ siblings, and `apps/server` does exactly that. But a fork that installs this package
78
+ and files a 2025+ return against the shipped values will file wrong numbers, so the
79
+ fact is pinned where it cannot be missed.
80
+
81
+ | File | Status | Verified |
82
+ |---|---|---|
83
+ | `t2/at1/rates/general-rate-bands.ts` | primary-sourced | 2026-09-09 |
84
+ | `gst-hst/rates.ts` | primary-sourced | 2026-07-04 |
85
+ | `t2/at1/rates/alberta-rates.ts` | partially-sourced | 2026-09-09 |
86
+ | `t2/rates/corporate-rates.ts` | partially-sourced | never |
87
+ | `t2/rates/cca-rates.ts` | partially-sourced | never |
88
+ | `t2/co17/rates/quebec-rates.ts` | **reference-only** | never |
89
+ | `t2/rates/province-rates.ts` | **reference-only** | never |
90
+
91
+ `province-rates.ts` is the largest gap: 22 figures (11 provinces × general +
92
+ small-business), none sourced, several stale since 2024. A host **must** supply its
93
+ own `PROVINCE_RATE_BOOK` before filing a T2 with a Schedule 5.
94
+
95
+ ## Revising a year
96
+
97
+ 1. Add a **new** `RateBookEntry` for the year. Never mutate a shipped year — a
98
+ reprint of a 2024 return must stay correct forever.
99
+ 2. Update `covers:` to match. The test fails until it does.
100
+ 3. Update `sources:` and set `verified:` to the date you actually checked. If you
101
+ did not check against a primary document, leave it `never` and record the gap.
102
+ 4. Prefer **vendoring** the document into `research/sources/` over citing a URL.
103
+ A checked-in document is the difference between a citation that can be verified
104
+ in five years and one that 404s.
105
+ 5. If the change moves a file between statuses, update the pinned rosters in
106
+ `tests/statutory-data-provenance.test.ts` in the same commit — that diff is the
107
+ reviewable record of provenance debt changing.
108
+
109
+ [tax-ca]: https://github.com/Equisoft/tax-ca
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/ca-tax",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
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",
@@ -69,13 +69,13 @@
69
69
  "format:baseline": "biome check --write src/ tests/"
70
70
  },
71
71
  "peerDependencies": {
72
- "@classytic/primitives": ">=0.26.4",
72
+ "@classytic/primitives": ">=0.27.0",
73
73
  "@classytic/tax-core": ">=0.3.0"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@biomejs/biome": "^2.4.10",
77
77
  "@classytic/dev-tools": "^0.2.1",
78
- "@classytic/primitives": "^0.26.4",
78
+ "@classytic/primitives": "^0.27.0",
79
79
  "@classytic/tax-core": "^0.3.0",
80
80
  "@types/node": "^25.6.0",
81
81
  "tsdown": "^0.22.3",