Files
zeekay e7471084cb feat(tax): G-15 IRS IRIS + FIRE + CFSF + TCC e-file adapters
G-15 — IRS FIRE + IRIS e-file adapter for 1099 series, with CFSF state
routing and per-issuer TCC management.

pkg/tax/iris — IRIS A2A adapter (mandatory for TY 2024+):
- Client with JWT bearer auth (OAuth2 password-grant exchange against
  IRS Identity), env switch (Production / AATS sandbox), per-call
  re-auth on 401, exponential-backoff + jitter retry with Retry-After
  honoring, X-IRS-TCC header on every submission
- SubmitForm / GetSubmissionStatus / ListSubmissions /
  CorrectSubmission cover all 8 form types: 1099-DIV / B / INT / MISC /
  NEC / OID / K / R
- Typed Form1099*Data per form covering every IRS-published box
- IRIS schema XML marshaling (header + per-payee Form1099* element with
  per-form payload element name; FormTypeCd discriminator; correction
  envelope with PaymentYearTypeCd=G and OriginalReceiptID)
- Validation: TCC format, EIN 9-digit, TIN 9-digit per payee, payee
  data type compatible with FormType, max 100,000 payees per
  submission (Pub 5717 §3.1), original_receipt_id required for
  corrections
- Acknowledgment + Status parsing for both XML and JSON wire formats

pkg/tax/fire — FIRE legacy adapter (pre-TY-2024 corrections):
- Client with multipart upload, env switch (Production / Test)
- Fixed-width 750-byte records per Publication 1220 Part C —
  T (Transmitter), A (Payer), B (Payee), C (End-of-A), F (End-of-File),
  K (State Totals) with positional layout: numeric right-justified
  zero-padded, alphanumeric left-justified blank-padded, dollar
  amounts in cents
- Per-payee amount codes mapped to positions 55..187 (12-position
  fields, codes '1'..'9' / 'A'..'I' = 18 codes total) and per-amount-
  code totals carried on the C and K records
- All 8 form codes: 1099-DIV (1), 1099-INT (6), 1099-B (B),
  1099-MISC (A), 1099-NEC (NE), 1099-OID (D), 1099-K (MC), 1099-R (9)
- CFSF state-totals block on B records (positions 663-748) and per-
  state K records with CombinedFedStateCode

pkg/tax/cfsf — Combined Federal/State Filing routing:
- StateProfile registry covering 32 CFSF participants + DC + WV with
  their IRS-assigned numeric codes (CA=06, NY=n/a, MA=25, etc.)
  plus 19 non-CFSF states with the per-state DOR portal references
  (PortalDORWebsite / PortalFSET / PortalNone for no-income-tax
  states)
- RouteToStates(submission, []State) -> []*StateFiling distinguishing
  cfsf (federal filing auto-routes), direct (operator hand-off to
  named portal), and none (no broad state income tax)
- IRISWrapper adapts *iris.FormSubmission to FormSubmissionForRouting
  by reading StateCd / StateTaxWithheld / StateIncome from each
  payee's typed Form1099*Data

pkg/tax/tcc — Transmitter Control Code registry:
- Per-(issuer, system, env) scoping: same issuer may hold separate
  IRIS / FIRE TCCs and separate Sandbox / Production TCCs
- Agency split: AgencyIssuer (issuer files own returns) vs
  AgencyTransmitter (Lux files as agent under transmitter TCC)
- Validation: TCC 5-char, EIN 9-digit, agency requires entity id when
  transmitter

pkg/tax — integration hook (submit.go):
- tax.SubmitForm + tax.SubmitForm1099B route by default to IRIS for
  TY >= 2024 (IRISMandatoryTaxYear per IRS final regs), fall back to
  FIRE only when ForceLegacyFIRE is set or TaxYear < 2024
- SubmissionOptions binds the chosen IRIS / FIRE clients plus a
  TCCRegistry for per-issuer TCC resolution
- Builders translate captable Form1099DIV / Form1099B into the
  transport's typed submission shape

Coverage: go build / vet / -race clean across the full module. 68
tests in the four new packages (16 IRIS happy paths × form types +
validation suite + correction round trip + 429 retry + re-auth + XML
ack parsing; 8 FIRE happy paths × form codes + record-layout tests +
CFSF K-record test + rejection path; 9 CFSF routing tests covering
the spec-required CA/MA/OH/OK/IN CFSF participants + NY/IL direct +
TX/FL no-tax; 9 TCC scoping + validation tests), all passing.

Source-of-design: Public-Spec
Source-ref: https://www.irs.gov/e-file-providers/iris-online-portal
Source-ref: IRS Publication 5717 (IRIS A2A Specifications)
Source-ref: IRS Publication 5718 (IRIS Electronic Filing Application Guide)
Source-ref: IRS Publication 1220 (FIRE Specifications)
Source-ref: https://www.irs.gov/pub/irs-pdf/p1220.pdf
Clean-Team-Member: dev-agent (P0 G-15 implementation)
Signed-off-by: dev <[email protected]>
2026-05-26 18:24:44 -07:00

201 lines
5.9 KiB
Go

// Package tcc — Transmitter Control Code registry. Each issuer that
// files its own information returns has its own IRS-issued TCC; Lux
// may also act as agent under its own TCC. The registry resolves
// (issuer, env) -> TCC and tracks per-TCC scoping rules.
//
// Source-of-design: Public-Spec
// Source-ref: IRS Publication 5717 §4 — Transmitter Control Code
// Source-ref: IRS Publication 1220 Part B §2 — TCC Application Process
package tcc
import (
"errors"
"fmt"
"sync"
)
// Env identifies the IRS environment a TCC is registered against.
// Sandboxes (IRIS AATS, FIRE Test) require their own TCC distinct
// from the production TCC.
type Env string
const (
// EnvProduction is the production TCC scope.
EnvProduction Env = "production"
// EnvSandbox is the sandbox TCC scope (IRIS AATS, FIRE Test).
EnvSandbox Env = "sandbox"
)
// System identifies the IRS e-file system the TCC is registered
// against — IRIS or FIRE. The same legal entity may hold separate
// IRIS and FIRE TCCs.
type System string
const (
// SystemIRIS is the IRIS A2A scope.
SystemIRIS System = "iris"
// SystemFIRE is the FIRE scope.
SystemFIRE System = "fire"
)
// Agency identifies whether the TCC is used by the issuer directly or
// by a third-party transmitter acting on the issuer's behalf.
type Agency string
const (
// AgencyIssuer = the TCC belongs to the issuer; the issuer
// transmits its own returns.
AgencyIssuer Agency = "issuer"
// AgencyTransmitter = the TCC belongs to a third-party
// transmitter (e.g., Lux Industries Inc. transmitting on behalf
// of a captable customer).
AgencyTransmitter Agency = "transmitter"
)
// Entry is one registry row — a single (issuer, system, env) -> TCC
// mapping.
type Entry struct {
// IssuerID is the captable-level issuer identifier (the company
// id under which returns are filed).
IssuerID string
// System is the e-file system the TCC is registered against.
System System
// Env is the IRS environment the TCC is registered for.
Env Env
// TCC is the IRS-assigned 5-character Transmitter Control Code.
// Loaded from KMS, never logged.
TCC string
// EIN is the EIN bound to the TCC on the IRIS / FIRE
// Application. The IRS rejects submissions where the
// transmitter EIN on the wire does not match the EIN of record
// for the TCC.
EIN string
// Agency identifies whether this TCC is the issuer's own or a
// transmitter's.
Agency Agency
// TransmitterEntityID identifies the transmitter entity when
// Agency == AgencyTransmitter (e.g., "lux-industries-inc").
// Empty for AgencyIssuer.
TransmitterEntityID string
}
// Registry is the in-memory TCC registry. The production registry is
// backed by KMS-encrypted storage outside the captable library; this
// in-memory implementation is the canonical contract that consumers
// (broker/treasury) instantiate.
type Registry struct {
mu sync.RWMutex
entries map[registryKey]*Entry
}
// registryKey scopes a registry lookup to (issuer, system, env).
type registryKey struct {
IssuerID string
System System
Env Env
}
// NewRegistry constructs an empty Registry. Callers Register
// entries from KMS at process boot.
func NewRegistry() *Registry {
return &Registry{entries: make(map[registryKey]*Entry)}
}
// Errors surfaced by the registry.
var (
// ErrNotRegistered is returned by Resolve when no TCC is registered
// for the (issuer, system, env) tuple.
ErrNotRegistered = errors.New("tcc: no TCC registered for the (issuer, system, env) tuple")
// ErrInvalidEntry is returned by Register on validation failure.
ErrInvalidEntry = errors.New("tcc: invalid entry")
)
// Register stores a new (issuer, system, env) -> TCC entry. Returns
// ErrInvalidEntry on validation failure.
func (r *Registry) Register(e *Entry) error {
if err := validateEntry(e); err != nil {
return fmt.Errorf("%w: %v", ErrInvalidEntry, err)
}
r.mu.Lock()
defer r.mu.Unlock()
r.entries[registryKey{e.IssuerID, e.System, e.Env}] = e
return nil
}
// Resolve returns the TCC entry for the (issuer, system, env) tuple.
// Returns ErrNotRegistered if no entry is present.
func (r *Registry) Resolve(issuerID string, system System, env Env) (*Entry, error) {
r.mu.RLock()
defer r.mu.RUnlock()
e, ok := r.entries[registryKey{issuerID, system, env}]
if !ok {
return nil, ErrNotRegistered
}
cp := *e
return &cp, nil
}
// List returns a copy of all registered entries (no particular order).
func (r *Registry) List() []*Entry {
r.mu.RLock()
defer r.mu.RUnlock()
out := make([]*Entry, 0, len(r.entries))
for _, e := range r.entries {
cp := *e
out = append(out, &cp)
}
return out
}
// Unregister removes the (issuer, system, env) entry. Returns
// ErrNotRegistered if no entry was present.
func (r *Registry) Unregister(issuerID string, system System, env Env) error {
r.mu.Lock()
defer r.mu.Unlock()
key := registryKey{issuerID, system, env}
if _, ok := r.entries[key]; !ok {
return ErrNotRegistered
}
delete(r.entries, key)
return nil
}
// validateEntry runs field-level validation on a registry entry.
func validateEntry(e *Entry) error {
if e == nil {
return fmt.Errorf("entry is nil")
}
if e.IssuerID == "" {
return fmt.Errorf("issuer_id is required")
}
if e.System != SystemIRIS && e.System != SystemFIRE {
return fmt.Errorf("system must be one of iris/fire, got %q", e.System)
}
if e.Env != EnvProduction && e.Env != EnvSandbox {
return fmt.Errorf("env must be one of production/sandbox, got %q", e.Env)
}
if len(e.TCC) != 5 {
return fmt.Errorf("tcc must be 5 chars, got %d", len(e.TCC))
}
if len(e.EIN) != 9 {
return fmt.Errorf("ein must be 9 digits, got %d", len(e.EIN))
}
if e.Agency != AgencyIssuer && e.Agency != AgencyTransmitter {
return fmt.Errorf("agency must be one of issuer/transmitter, got %q", e.Agency)
}
if e.Agency == AgencyTransmitter && e.TransmitterEntityID == "" {
return fmt.Errorf("transmitter_entity_id is required when agency=transmitter")
}
return nil
}