Documentation
¶
Overview ¶
Package company is incorporation end to end: pick a structure, add founders, pay, file, and e-sign.
It mounts /v1/company — Hanzo Company: incorporation and fundraising, end to end, 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 Registration
- 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) ([]Registration, error)
- func (s *Store) Pending(ctx context.Context, limit int) ([]*Formation, error)
- func (s *Store) Put(ctx context.Context, f *Formation) error
- type Structure
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 is the filing partner that performed the filing, or "manual" when no
// partner is wired.
Provider string `json:"provider"`
// Ref is the partner's or the state's filing reference. Empty when nothing was
// actually filed — no filing id is ever fabricated.
Ref string `json:"ref,omitempty"`
// Status is manual (no partner wired — a registered agent files out-of-band),
// submitted (the partner accepted it, awaiting the state), filed (the state
// accepted it) or rejected.
Status string `json:"status"`
// Note explains a filing Hanzo did not perform itself: what remains to be done
// and by whom.
Note string `json:"note,omitempty"`
// At is the unix second the filing record was written.
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 is the owning org — the tenant key, and the reason there is exactly one
// formation per org.
Org string `json:"org"`
// Structure is the legal entity being formed: c-corp, llc or dao-llc.
Structure Structure `json:"structure"`
// Jurisdiction is the state of formation: DE or WY.
Jurisdiction Jurisdiction `json:"jurisdiction"`
// Name is the company name the entity is being formed under.
Name string `json:"name"`
// Stage is the machine's current state: structure, founders, payment, documents,
// esign or genesis on the formation path, import on the skip path, and company
// at the terminal.
Stage Stage `json:"stage"`
// Founders is every founding stakeholder, with its equity split and KYC state.
Founders []Founder `json:"founders"`
// Paid reports whether the one-time formation fee has been charged.
Paid bool `json:"paid"`
// PaymentRef is the billing reference recorded for the charged formation fee on
// the org's own ledger.
PaymentRef string `json:"paymentRef,omitempty"`
// DocumentIDs are the data room ids of the GENERATED formation documents.
DocumentIDs []string `json:"documentIds,omitempty"`
// Filing is the state-of-incorporation filing record, once documents exist.
Filing *Filing `json:"filing,omitempty"`
// Signed reports whether the formation documents have come back signed — the
// e-signature provider's answer, which a real provider's webhook drives.
Signed bool `json:"signed"`
// EsignRef is the e-signature provider's reference for the signature request.
EsignRef string `json:"esignRef,omitempty"`
// Genesis is the cap-table equity genesis, once recorded.
Genesis *Genesis `json:"genesis,omitempty"`
// AlreadyIncorporated declares an org that already has a legal entity, which
// takes the import path (structure → import → company) instead of forming one.
AlreadyIncorporated bool `json:"alreadyIncorporated"`
// Imported reports whether the existing company's corporate documents have been
// ingested into the org's data room.
Imported bool `json:"imported"`
// ImportedDocs are the data room ids of the documents ingested from Drive.
ImportedDocs []string `json:"importedDocs,omitempty"`
// CapTableImported reports whether the existing company's cap table has been
// imported onto the canonical cap table.
CapTableImported bool `json:"capTableImported"`
// CreatedAt is the unix second the formation was opened.
CreatedAt int64 `json:"createdAt"`
// UpdatedAt is the unix second of the most recent write to the formation.
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 is the founder's full legal name, as it appears on the formation documents.
Name string `json:"name"`
// Email is the founder's email, and the key a KYC decision addresses a founder
// by — POST /v1/company/kyc/decision matches on it.
Email string `json:"email"`
// EquityBps is the founder's ownership in basis points, 0–10000 (1% == 100 bps,
// so 10000 is the whole company). The founders' shares seed the cap-table genesis.
EquityBps int `json:"equityBps"`
// KYCStatus is the founder's identity-verification state: pending, verified (a
// real idv provider reported a pass), reviewer_confirmed (a privileged reviewer
// confirmed out-of-band) or failed. The payment stage is unreachable until every
// founder passes.
KYCStatus string `json:"kycStatus"`
// KYCRef is the idv provider's session reference for this founder.
KYCRef string `json:"kycRef,omitempty"`
// DecidedBy is who settled a terminal KYC status: the provider name, or a
// reviewer's user id.
DecidedBy string `json:"decidedBy,omitempty"`
}
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 is the 0x-prefixed keccak256 root of the founding allocation. It is
// ALWAYS computed, whether or not the on-chain anchor is wired, because the root
// is the tamper-evident witness.
Root string `json:"root"`
// TxHash is the L1 transaction hash of the anchoring commit. Empty until anchored.
TxHash string `json:"txHash,omitempty"`
// Block is the L1 block the anchoring transaction landed in. Set only once the
// receipt has been read; absent otherwise.
Block uint64 `json:"block,omitempty"`
// ChainID is the EVM chain the root is committed to — the Hanzo L1 by default.
ChainID int64 `json:"chainId,omitempty"`
// At is the unix second the genesis root was computed.
At int64 `json:"at"`
// Status is pending (root computed, not yet on-chain) or anchored (committed).
Status string `json:"status"`
// Note explains an unanchored genesis honestly — anchor wiring absent, or the
// submit error — rather than reporting a commit that did not happen.
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 Registration ¶ added in v1.801.350
type Registration struct {
// Org is the org whose formation this row projects.
Org string `json:"org"`
// Stage is the formation's current state — what the platform reads to see which
// formations are stalled and where.
Stage Stage `json:"stage"`
// Structure is the legal entity being formed: c-corp, llc or dao-llc.
Structure Structure `json:"structure"`
// Name is the company name the entity is being formed under.
Name string `json:"name"`
// CreatedAt is the unix second the formation was opened.
CreatedAt int64 `json:"createdAt"`
// UpdatedAt is the unix second of the most recent write to the formation, and
// the key the register sorts on (newest activity first).
UpdatedAt int64 `json:"updatedAt"`
}
Registration 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.
It is NOT called Summary. Schema names are flat across the fleet document, and apps/marketing already publishes a Summary that means campaign counters — one name, two shapes, which every generated SDK would bind to whichever it read last. openapi.Weave refuses that composition, and it refused this one the moment the register became a typed op.
type RoundInput ¶
type RoundInput struct {
// Name is the round's name on the cap table, e.g. "Seed". Required.
Name string `json:"name"`
// RoundType is PRICED, SAFE or CONVERTIBLE_NOTE. Defaults to PRICED.
RoundType string `json:"roundType"`
// TargetAmount is the amount the round is raising, recorded verbatim on the
// canonical cap table's rounds.create contract.
TargetAmount float64 `json:"targetAmount"`
// PreMoneyValuation is the valuation the round prices off, before the new money.
PreMoneyValuation float64 `json:"preMoneyValuation,omitempty"`
PricePerShare float64 `json:"pricePerShare,omitempty"`
ShareClassID string `json:"shareClassId,omitempty"`
}
RoundInput is a fundraising round the CapTable seam records.
type Signer ¶
type Signer struct {
// Name is the recipient's name, as it appears on the signature request.
Name string `json:"name"`
// Email is the address the signature request is sent to.
Email string `json:"email"`
}
Signer is one e-signature recipient.
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 — the system namespace's "company" — 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.