Files
Lux Dev d27ece3b99 feat(edgar,bluesky): add Form D + Blue-Sky state filing adapters
G-03 — SEC EDGAR Form D adapter (pkg/edgar):
- FormDFiling typed model covering primary issuer, related persons,
  industry, federal exemptions, sales/offering amounts, investor count,
  sales commissions, use of proceeds, and signature blocks
- marshalFormD emits the SEC Form D XML primaryDocument shape;
  buildSGMLSubmission wraps it in the EDGARLink Online SUBMISSION SGML
  envelope (TYPE / CIK / CCC / CONTACT / DOCUMENT)
- Filer with FileFormD / AmendFormD / GetFilingStatus, CIK normalization,
  exponential-backoff + jitter retry with Retry-After honoring,
  REJECTED-status → ErrSubmissionRejected, 4xx → *APIError
- 32 conformance tests against httptest.Server fixtures

G-04 — Blue-Sky state notice adapter (pkg/bluesky):
- Registrar dispatches to per-state StateAdapter implementations
- EFDAdapter (NASAA Electronic Filing Depository) covers 49 states + DC
  with bearer-token auth, ACH-payment metadata, and renewal support
- Real state-portal adapters for FL, TX, NY, CA, MA — five highest-
  volume non-EFD / hybrid-portal states
- Per-state fee calculator with flat + scaled fee schedules
  (CA / NY / WA scale with offering size); EFD system fee layered on
- 30 conformance tests against httptest.Server fixtures

compliance bridge (pkg/compliance/filings.go):
- FormDFiler / BlueSkyFiler interfaces let the compliance Service
  delegate filings without importing the wire packages directly
- compliance.FileFormD / AmendFormD / FileBlueSkyNotice update the
  in-domain record with the regulator-assigned identifier, status,
  filing date, and fee
- 6 bridge tests with in-memory fakes

Coverage: go build / vet / -race clean across the full repo. 62 tests
in the two new packages + 6 in the compliance bridge, all passing.

Source-of-design: Public-Spec
Source-ref: https://www.sec.gov/info/edgar/specifications/formdxml.pdf
Source-ref: https://www.sec.gov/edgar/filer-information/current-edgar-technical-specifications
Source-ref: http://nasaaefd.org
Source-ref: http://nasaaefd.org/About/FormDStates
Clean-Team-Member: dev-agent (P0 G-03 / G-04 implementation)
Signed-off-by: dev <[email protected]>
2026-05-26 18:14:15 -07:00

164 lines
5.9 KiB
Go

// Filing delegation — bridges the in-domain Form D / blue-sky models
// to the wire-level filer adapters in `pkg/edgar` and `pkg/bluesky`.
//
// The compliance package does not import the edgar / bluesky packages
// directly (those packages own the wire format and the HTTP transport
// against the SEC / NASAA endpoints, which is a separate concern from
// the in-memory compliance state managed here). Instead, callers wire
// a FormDFiler / BlueSkyFiler implementation in at startup via
// SetFormDFiler / SetBlueSkyFiler and then invoke FileFormD /
// FileBlueSkyNotice on the Service.
//
// Source-of-design: Public-Spec
// Source-ref: https://www.sec.gov/info/edgar/specifications/formdxml.pdf
// Source-ref: http://nasaaefd.org
package compliance
import (
"context"
"errors"
"fmt"
"time"
)
// ErrNoFiler is returned by FileFormD / FileBlueSkyNotice when no
// adapter has been wired via SetFormDFiler / SetBlueSkyFiler.
var ErrNoFiler = errors.New("compliance: no filer configured")
// FormDFiler is the minimal interface the compliance service requires
// from a Form D adapter. Implemented by *edgar.Filer.
type FormDFiler interface {
// FileFormD submits a new Form D filing and returns the
// EDGAR-assigned acknowledgment.
FileFormD(ctx context.Context, payload any) (accessionNumber string, status string, err error)
// AmendFormD submits an amendment to a previously filed Form D.
AmendFormD(ctx context.Context, payload any) (accessionNumber string, status string, err error)
}
// BlueSkyFiler is the minimal interface the compliance service
// requires from a state notice-of-sale adapter. Implemented by
// *bluesky.Registrar.
type BlueSkyFiler interface {
// FileNoticeOfSale submits a state notice-of-sale filing and
// returns the state-assigned acknowledgment.
FileNoticeOfSale(ctx context.Context, state string, payload any) (filingID string, status string, fee float64, err error)
// RenewNotice submits a renewal for a state notice filing.
RenewNotice(ctx context.Context, state string, payload any) (filingID string, status string, fee float64, err error)
// CalculateStateFee returns the per-state filing fee for a notice
// of sale, before the filing is submitted.
CalculateStateFee(state string, payload any) (float64, error)
}
// SetFormDFiler wires the Form D filer adapter. Idempotent.
func (s *Service) SetFormDFiler(f FormDFiler) { s.formDFiler = f }
// SetBlueSkyFiler wires the blue-sky filer adapter. Idempotent.
func (s *Service) SetBlueSkyFiler(f BlueSkyFiler) { s.blueSkyFiler = f }
// FileFormD submits the FormD record's underlying payload through the
// wired EDGAR adapter, updates the FormD record with the resulting
// accession + status + filing date, and persists.
//
// The payload argument is the wire-level FormDFiling struct from
// `pkg/edgar` (declared as any here to keep this package free of an
// edgar import — the caller passes *edgar.FormDFiling).
func (s *Service) FileFormD(ctx context.Context, formDID string, payload any) (*FormD, error) {
if s.formDFiler == nil {
return nil, ErrNoFiler
}
rec, err := s.repo.GetFormD(ctx, formDID)
if err != nil {
return nil, fmt.Errorf("compliance: load form D %s: %w", formDID, err)
}
accession, status, err := s.formDFiler.FileFormD(ctx, payload)
if err != nil {
return rec, err
}
now := time.Now().UTC()
rec.SECFileNumber = accession
rec.FilingDate = &now
if status == "ACCEPTED" || status == "RECEIVED" {
rec.Status = "filed"
} else if status == "REJECTED" || status == "SUSPENDED" {
rec.Status = "rejected"
}
rec.UpdatedAt = now
if err := s.repo.UpdateFormD(ctx, rec); err != nil {
return rec, fmt.Errorf("compliance: persist form D %s after filing: %w", formDID, err)
}
return rec, nil
}
// AmendFormD submits an amendment to a previously filed Form D via
// the wired adapter, updates the FormD record, and persists.
func (s *Service) AmendFormD(ctx context.Context, formDID string, payload any) (*FormD, error) {
if s.formDFiler == nil {
return nil, ErrNoFiler
}
rec, err := s.repo.GetFormD(ctx, formDID)
if err != nil {
return nil, fmt.Errorf("compliance: load form D %s: %w", formDID, err)
}
accession, status, err := s.formDFiler.AmendFormD(ctx, payload)
if err != nil {
return rec, err
}
now := time.Now().UTC()
rec.AmendmentDate = &now
rec.SECFileNumber = accession
if status == "ACCEPTED" || status == "RECEIVED" {
rec.Status = "amended"
} else if status == "REJECTED" || status == "SUSPENDED" {
rec.Status = "rejected"
}
rec.UpdatedAt = now
if err := s.repo.UpdateFormD(ctx, rec); err != nil {
return rec, fmt.Errorf("compliance: persist form D %s after amendment: %w", formDID, err)
}
return rec, nil
}
// FileBlueSkyNotice submits a state notice filing via the wired
// adapter, updates the BlueSkyFiling record with state acknowledgment,
// fee, and status, and persists.
func (s *Service) FileBlueSkyNotice(ctx context.Context, filingID string, payload any) (*BlueSkyFiling, error) {
if s.blueSkyFiler == nil {
return nil, ErrNoFiler
}
rec, err := s.repo.GetBlueSkyFiling(ctx, filingID)
if err != nil {
return nil, fmt.Errorf("compliance: load blue sky filing %s: %w", filingID, err)
}
stateID, status, fee, err := s.blueSkyFiler.FileNoticeOfSale(ctx, rec.State, payload)
if err != nil {
return rec, err
}
now := time.Now().UTC()
rec.FilingDate = &now
rec.Fee = fee
if status == "ACCEPTED" || status == "FILED" || status == "RECEIVED" {
rec.Status = "filed"
} else if status == "REJECTED" {
rec.Status = "rejected"
} else if status == "APPROVED" {
rec.Status = "approved"
rec.ApprovedDate = &now
}
if stateID != "" {
// Stash the state-assigned ID in Notes for traceability.
if rec.Notes == "" {
rec.Notes = "state_id=" + stateID
} else {
rec.Notes = rec.Notes + "; state_id=" + stateID
}
}
if err := s.repo.UpdateBlueSkyFiling(ctx, rec); err != nil {
return rec, fmt.Errorf("compliance: persist blue sky filing %s after submit: %w", filingID, err)
}
return rec, nil
}