mirror of
https://github.com/luxfi/broker.git
synced 2026-09-24 00:25:18 +00:00
New pkg/pretrade implementing Gap G-37 (Stage 6.5): synchronous gate
at order-entry that fetches state from cap-table, transfer, AML, KYC,
and on-chain ERC-3643 compliance providers and decides allow / deny /
escalate. Each predicate is a composable internal function; the
Decision carries every actionable reason in a single round.
Wired into the broker order path via Wrap[Req, Resp] generic
middleware so existing router signatures stay untouched (additive,
forwards-only). GateError exposes the Decision to callers that surface
required actions to the investor.
Audit is delegated through AuditSink; canonical AuditLogSink bridges
to broker/pkg/audit. PII (ClientIP) is masked to /24 / /48 in the
audit row.
Test coverage: happy path, every hard-deny predicate (kyc_not_verified,
kyc_stale, not_accredited, accreditation_stale_{income,third_party},
suitability_{missing,stale}, aml_not_clean, open_sar,
ofac_screen_stale, lockup_active, rule144_holding_period,
country_restricted, max_holders, transfer_restricted,
chain_compliance_refused), every escalate predicate
(suitability_ambiguous, kyc_near_expiry, accreditation_near_expiry),
upstream-provider error propagation, input validation, max-holder
sell-side exemption, max-holder existing-holder exemption, PII
redaction. 41 tests passing under -race.
Source-of-design: Lux-Prior-IP
Source-ref: f7a8d7a
Clean-Team-Member: dev-agent (P0 G-37 implementation)
Signed-off-by: dev <[email protected]>
62 lines
2.8 KiB
Go
62 lines
2.8 KiB
Go
package pretrade
|
|
|
|
import "context"
|
|
|
|
// CapTableProvider returns accreditation, suitability, and offering-level
|
|
// restriction state for an investor / offering pair. Backed in production
|
|
// by github.com/luxfi/captable. Defined here so the gate can be
|
|
// constructed with mocks in tests and so the consumed surface is
|
|
// minimal and stable.
|
|
type CapTableProvider interface {
|
|
// Accreditation returns the investor's accreditation snapshot, or an
|
|
// error if the investor is unknown.
|
|
Accreditation(ctx context.Context, investorID string) (AccreditationStatus, error)
|
|
// Suitability returns the investor's suitability snapshot for the
|
|
// offering, or an error if the investor is unknown.
|
|
Suitability(ctx context.Context, investorID, offeringID string) (SuitabilityStatus, error)
|
|
// Restrictions returns the offering-level restriction snapshot for
|
|
// the investor.
|
|
Restrictions(ctx context.Context, investorID, offeringID string) (RestrictionStatus, error)
|
|
}
|
|
|
|
// TransferProvider exposes the canonical transfer-restriction Check for
|
|
// the cap-table-of-record. Backed in production by github.com/luxfi/
|
|
// transfer/pkg/restrictions. The gate uses this as a cross-check against
|
|
// the offering-level RestrictionStatus: any disagreement is a hard deny.
|
|
type TransferProvider interface {
|
|
// AllowTransfer answers whether the proposed quantity of an offering
|
|
// can be transferred to or from the investor (depending on side).
|
|
AllowTransfer(ctx context.Context, investorID, offeringID, side string, qty int64) (bool, []string, error)
|
|
}
|
|
|
|
// AMLProvider returns the AML / OFAC snapshot for an investor.
|
|
type AMLProvider interface {
|
|
// Status returns the AML snapshot or an error if the investor is
|
|
// unknown to the AML service.
|
|
Status(ctx context.Context, investorID string) (AMLStatus, error)
|
|
}
|
|
|
|
// KYCProvider returns the KYC snapshot for an investor.
|
|
type KYCProvider interface {
|
|
// Status returns the KYC snapshot or an error if the investor is
|
|
// unknown.
|
|
Status(ctx context.Context, investorID string) (KYCStatus, error)
|
|
}
|
|
|
|
// ChainComplianceProvider delegates the on-chain ERC-3643 modular
|
|
// compliance check. Backed in production by github.com/luxfi/securities/
|
|
// erc3643/compliance.go. The provider is queried only when the order
|
|
// carries a non-empty TokenAddress.
|
|
type ChainComplianceProvider interface {
|
|
// CanTransfer asks the on-chain compliance module whether the
|
|
// transfer is permitted under the token's attached modules.
|
|
CanTransfer(ctx context.Context, tokenAddr, fromInvestorID, toInvestorID string, qty int64) (ChainComplianceStatus, error)
|
|
}
|
|
|
|
// AuditSink receives a Decision for every Check evaluation. Implementations
|
|
// must be non-blocking and concurrency-safe. The broker wires this to the
|
|
// canonical broker/pkg/audit log; tests pass a slice-collector.
|
|
type AuditSink interface {
|
|
RecordPreTrade(decision *Decision, order *Order)
|
|
}
|