mirror of
https://github.com/luxfi/captable.git
synced 2026-10-30 17:01:02 +00:00
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]>
164 lines
5.9 KiB
Go
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
|
|
}
|