Documentation
¶
Overview ¶
Package company mounts /v1/company — Hanzo Company, the Stripe-Atlas-class incorporation + fundraising product. It runs ONE formation state machine per org: choose a structure (C-Corp / LLC / DAO-LLC) → add founders + KYC → pay the one-time $999 fee → generate formation documents → e-sign them → record the cap table's equity genesis on-chain → upgrade the org to a "company". An already-incorporated org SKIPS straight to the import path (corporate docs → dataroom, cap-table spreadsheet → captable) and lands at the same "company" terminal.
This file is the machine: the domain model (Formation, Founder, Genesis) and the PURE transition logic. It performs no I/O — every guard is a total function of a *Formation, so the whole lifecycle (legal transitions, the payment gate, the skip path) is unit-testable without a store, a clock, or a network. company.go layers the per-org SQLite store, the provider seams (KYC, billing, esign, dataroom, captable, on-chain anchor, state filing), and the HTTP surface on top.
Index ¶
- Constants
- func Advance(f *Formation, to Stage) error
- func Mount(app cloud.Router, deps cloud.Deps) error
- func Shutdown(context.Context) error
- type CapTable
- type Charger
- type DocumentSink
- type DriveFile
- type EquityAnchor
- type Esign
- type Filing
- type FilingProvider
- type Filter
- type Formation
- type Founder
- type Genesis
- type GoogleReader
- type Jurisdiction
- type KYCProvider
- type OrgUpgrader
- type RoundInput
- type Signer
- type Stage
- type Stakeholder
- type Store
- func (s *Store) Close() error
- func (s *Store) Count(ctx context.Context) (map[Stage]int, error)
- func (s *Store) Delete(ctx context.Context, org string) (bool, error)
- func (s *Store) Get(ctx context.Context, org string) (*Formation, error)
- func (s *Store) List(ctx context.Context, f Filter) ([]Summary, error)
- func (s *Store) Pending(ctx context.Context, limit int) ([]*Formation, error)
- func (s *Store) Put(ctx context.Context, f *Formation) error
- type Structure
- type Summary
Constants ¶
const ( KYCPending = "pending" KYCVerified = "verified" // a real idv provider reported a pass KYCReviewerConfirmed = "reviewer_confirmed" // a privileged reviewer confirmed the founder (not provider-reported) KYCFailed = "failed" )
KYC statuses for a founder. A founder reaches a PASSING status by exactly two paths, never a client assertion: a real idv provider reports a pass (KYCVerified), or a privileged reviewer confirms the founder out-of-band (KYCReviewerConfirmed). The two are distinct so a manual confirmation is never dressed up as a provider decision. The payment step cannot be reached until every founder passes (kycPass).
Variables ¶
This section is empty.
Functions ¶
func Advance ¶
Advance moves f to the target stage. It refuses any edge not in the transition table (errIllegalTransition) and any edge whose guard is unsatisfied (the guard's own error). On success it sets f.Stage = to and returns nil. It is PURE: it reads and writes only f, never a store or clock, so the caller persists f afterward.
Types ¶
type CapTable ¶
type CapTable interface {
SetIncorporation(ctx context.Context, org, companyName, incType, country, state string) error
SeedFounders(ctx context.Context, org, companyName string, founders []Founder) error
AddStakeholders(ctx context.Context, org string, holders []Stakeholder) (inserted int, err error)
RecordRound(ctx context.Context, org string, r RoundInput) (roundID string, err error)
}
CapTable is the cap-table seam. SetIncorporation records the entity kind on the canonical captable company row (the "org upgraded to company" fact at the cap table layer); SeedFounders writes the founding allocation; RecordRound records a fundraising round. All are org-scoped.
type Charger ¶
type Charger interface {
Charge(ctx context.Context, org string, amountCents int64, memo string) (ref string, err error)
}
Charger is the one-time billing seam: it authorizes and records the $999 formation fee against the org's ledger. It returns a payment reference on success, or a metering error (mapped by the handler to 402/503) when funds are insufficient or billing is unavailable.
type DocumentSink ¶
type DocumentSink interface {
Ingest(ctx context.Context, org, name, contentType string, data []byte) (docID string, err error)
}
DocumentSink stores a document for an org and returns its dataroom document id. It is how both generated formation docs and imported corporate docs land in the tenant's data room.
type DriveFile ¶
type DriveFile struct {
ID string `json:"id"`
Name string `json:"name"`
MimeType string `json:"mimeType"`
}
DriveFile is one Google Drive file.
type EquityAnchor ¶
type EquityAnchor interface {
Anchor(ctx context.Context, f *Formation) (*Genesis, error)
Configured() bool
}
EquityAnchor commits the cap-table equity genesis on-chain. Anchor computes a deterministic root of the founding allocation and, when the L1 wiring is present, commits it via a KMS-signed transaction (chain is the source of truth; the indexer projects it for reads). Root is always returned; TxHash/Block are set only when Configured().
type Esign ¶
type Esign interface {
Request(ctx context.Context, org string, docIDs []string, signers []Signer) (ref string, err error)
Status(ctx context.Context, org, ref string) (complete bool, err error)
Name() string
}
Esign is the e-signature seam. Request creates a signature request over the named documents for the given signers and returns a provider reference; Status reports completion. Formation docs and fundraising SAFEs/notes both ride this seam.
type Filing ¶
type Filing struct {
Provider string `json:"provider"`
Ref string `json:"ref,omitempty"`
Status string `json:"status"` // manual | submitted | filed | rejected
Note string `json:"note,omitempty"`
At int64 `json:"at,omitempty"`
}
Filing is the state-of-incorporation filing record. A real filing is performed by a Delaware/Wyoming filing partner (see providers.go FilingProvider); until one is wired the status is honest ("manual"/"pending") and NO fabricated filing id is recorded.
type FilingProvider ¶
type FilingProvider interface {
Submit(ctx context.Context, f *Formation) (*Filing, error)
Status(ctx context.Context, ref string) (*Filing, error)
Name() string
}
FilingProvider is the state-of-incorporation filing seam (Delaware / Wyoming). Submit files the formation with the state; Status polls it. No provider is wired by default — the stub records an honest "manual" status and never fabricates a filing id. See filing.go for exactly what a real integration requires.
type Filter ¶
type Filter struct {
Stage Stage // "" = any
Structure Structure // "" = any
Limit int // <=0 = defaultRegisterLimit
Offset int
}
Filter narrows the register. A zero Filter lists every formation.
type Formation ¶
type Formation struct {
Org string `json:"org"`
Structure Structure `json:"structure"`
Jurisdiction Jurisdiction `json:"jurisdiction"`
Name string `json:"name"`
Stage Stage `json:"stage"`
Founders []Founder `json:"founders"`
Paid bool `json:"paid"`
PaymentRef string `json:"paymentRef,omitempty"`
DocumentIDs []string `json:"documentIds,omitempty"` // dataroom doc ids of generated formation docs
Filing *Filing `json:"filing,omitempty"`
Signed bool `json:"signed"`
EsignRef string `json:"esignRef,omitempty"`
Genesis *Genesis `json:"genesis,omitempty"`
// SKIP path.
AlreadyIncorporated bool `json:"alreadyIncorporated"`
Imported bool `json:"imported"`
ImportedDocs []string `json:"importedDocs,omitempty"` // dataroom doc ids ingested from Drive
CapTableImported bool `json:"capTableImported"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
Formation is the one incorporation record per org. It is both the persisted row (store.go) and the value the machine transitions. Every field a guard reads is here, so a transition decision is a pure function of this struct.
type Founder ¶
type Founder struct {
Name string `json:"name"`
Email string `json:"email"`
EquityBps int `json:"equityBps"`
KYCStatus string `json:"kycStatus"`
KYCRef string `json:"kycRef,omitempty"` // idv session reference
DecidedBy string `json:"decidedBy,omitempty"` // who settled a terminal KYC status: the provider name, or a reviewer's user id
}
Founder is one founding stakeholder. EquityBps is the founder's ownership in basis points (1% == 100 bps); the founders' shares seed the cap-table genesis.
type Genesis ¶
type Genesis struct {
Root string `json:"root"` // 0x… keccak root of the founding allocation
TxHash string `json:"txHash,omitempty"` // L1 transaction hash (empty until anchored)
Block uint64 `json:"block,omitempty"`
ChainID int64 `json:"chainId,omitempty"`
At int64 `json:"at"`
Status string `json:"status"` // pending | anchored
Note string `json:"note,omitempty"`
}
Genesis is the cap-table equity genesis: a deterministic root of the founding allocation committed on-chain (chain is the source of truth; the indexer projects it for reads). Root is always computed; TxHash/Block are set only when the L1 anchor is wired — otherwise Status reports the honest pending state.
type GoogleReader ¶
type GoogleReader interface {
ListFolder(ctx context.Context, org, folderID string) ([]DriveFile, error)
Download(ctx context.Context, org string, f DriveFile) (data []byte, contentType string, err error)
SheetValues(ctx context.Context, org, spreadsheetID, rangeA1 string) ([][]string, error)
}
GoogleReader is the read seam over Google Drive + Sheets used by the import path. A real implementation authenticates with the org's custodied OAuth token; tests substitute a fake.
type Jurisdiction ¶
type Jurisdiction string
Jurisdiction is the state of formation. Hanzo Company supports Delaware and Wyoming — the two jurisdictions the state-filing partner seam targets.
const ( JurisdictionDE Jurisdiction = "DE" JurisdictionWY Jurisdiction = "WY" )
type KYCProvider ¶
type KYCProvider interface {
Start(ctx context.Context, org string, f Founder) (ref, verifyURL, status string, err error)
Check(ctx context.Context, ref string) (status string, err error)
Name() string
}
KYCProvider is the identity-verification seam (the clients/idv seam in the product spec). Start begins verification for one founder and returns a provider reference plus, for a hosted flow, a URL the founder visits; Check reports the current status. A real provider (Persona, Stripe Identity, Onfido, …) implements this; manualKYC is the honest default.
type OrgUpgrader ¶
OrgUpgrader records the "this org is now a company" fact. The wired implementation stamps the incorporation on the canonical captable company row; reflecting it onto the IAM Organization is a documented follow-on (see adapters.go).
type RoundInput ¶
type RoundInput struct {
Name string `json:"name"`
RoundType string `json:"roundType"` // PRICED | SAFE | CONVERTIBLE_NOTE
TargetAmount float64 `json:"targetAmount"`
PreMoneyValuation float64 `json:"preMoneyValuation,omitempty"`
}
RoundInput is a fundraising round the CapTable seam records.
type Stage ¶
type Stage string
Stage is one state of the formation machine. The happy (formation) path runs structure → founders → payment → documents → esign → genesis → company; the SKIP path runs structure → import → company. Terminal is company.
const ( StageStructure Stage = "structure" // initial: structure/jurisdiction/name being chosen StageFounders Stage = "founders" // founders added, KYC in flight StagePayment Stage = "payment" // the one-time $999 formation fee StageDocuments Stage = "documents" // formation documents generated StageEsign Stage = "esign" // documents out for signature StageGenesis Stage = "genesis" // cap-table equity genesis recorded on-chain StageCompany Stage = "company" // terminal: org is an incorporated company StageImport Stage = "import" // SKIP path: importing an existing company )
func NextStages ¶
NextStages returns the stages reachable from f's current stage (regardless of whether their guards are satisfied yet) — the machine's out-edges, for the UI to render "what's next".
type Stakeholder ¶
type Stakeholder struct {
Name string `json:"name"`
Email string `json:"email"`
StakeholderType string `json:"stakeholderType"` // INDIVIDUAL | INSTITUTION
CurrentRelationship string `json:"currentRelationship"` // FOUNDER | INVESTOR | EMPLOYEE …
InstitutionName string `json:"institutionName,omitempty"`
}
Stakeholder is the cap-table stakeholder shape the CapTable seam accepts. It mirrors the captable bundle's stakeholders.add contract.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store persists formations. ONE SQLite file ({DataDir}/company.db) holds every org's formation; tenant isolation is the `org` primary key, enforced on EVERY query. There is at most one formation per org (an org forms one company through this flow), so the aggregate is stored as a single row: the machine-relevant projection (stage/structure/name) in columns for cheap listing, and the full Formation as a JSON document in `data`. MaxOpenConns(1) serializes writes.
func (*Store) Count ¶
Count returns how many formations sit at each stage — the register's shape in one query, for a back office that needs to see a queue growing.
func (*Store) List ¶
List returns formations across every org, newest activity first.
This is the ONE cross-tenant read in this package, and it is deliberate: it serves a SuperAdmin operation (the platform reading its own register), never a tenant request. Every other query is keyed by org. Callers MUST establish the platform scope before calling — the store enforces shape, not authority.
Only the projection columns are read. Put maintains them precisely so a listing never decodes a document it is not going to show.
func (*Store) Pending ¶
Pending decodes the formations that can hold a founder awaiting a KYC decision.
A founder's KYC status lives in the JSON document, not in a column, so this is the one listing that decodes. It stays cheap by decoding ONLY StageFounders rows — by construction the sole stage where KYC is in flight, since guardKYCVerified gates the edge out of it. Rows at any other stage cannot contain pending KYC and are never read.
type Summary ¶
type Summary struct {
Org string `json:"org"`
Stage Stage `json:"stage"`
Structure Structure `json:"structure"`
Name string `json:"name"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
Summary is one row of the register: the projection Put already writes, with no JSON decode. Hanzo forms the entity and carries the formation KYC/AML obligation, so the platform needs to read its own book — how many entities it formed, which are stalled, and where. Get answers a question about ONE org and cannot answer any of those.