company

package
v1.801.307 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 33 Imported by: 0

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

View Source
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

func Advance(f *Formation, to Stage) error

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.

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount wires the company surface. It keeps a package global for Shutdown, so it constructs the Service value directly (the "complex flavour").

func Shutdown

func Shutdown(context.Context) error

Shutdown closes the store. Idempotent. Matches cloud.ShutdownFunc so Wire can reference it directly (like captable/dataroom/sign).

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

type OrgUpgrader interface {
	MarkCompany(ctx context.Context, f *Formation) error
}

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"`
	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  string `json:"name"`
	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

func NextStages(f *Formation) []Stage

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) Close

func (s *Store) Close() error

Close closes the underlying database. Idempotent-safe via sql.DB.

func (*Store) Count

func (s *Store) Count(ctx context.Context) (map[Stage]int, error)

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) Delete

func (s *Store) Delete(ctx context.Context, org string) (bool, error)

Delete removes the org's formation (used only in tests / a hard reset).

func (*Store) Get

func (s *Store) Get(ctx context.Context, org string) (*Formation, error)

Get loads the org's formation, or errNotFound.

func (*Store) List

func (s *Store) List(ctx context.Context, f Filter) ([]Summary, error)

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

func (s *Store) Pending(ctx context.Context, limit int) ([]*Formation, error)

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.

func (*Store) Put

func (s *Store) Put(ctx context.Context, f *Formation) error

Put upserts the formation. The org, stage, structure, and name projections are written alongside the JSON so a list/summary never has to decode every row.

type Structure

type Structure string

Structure is the legal entity a formation creates.

const (
	StructureCCorp  Structure = "c-corp"
	StructureLLC    Structure = "llc"
	StructureDAOLLC Structure = "dao-llc"
)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL