company

package
v1.801.413 Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2026 License: Apache-2.0 Imports: 35 Imported by: 0

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

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

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 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 is the per-share price of a priced round.
	PricePerShare float64 `json:"pricePerShare,omitempty"`
	// ShareClassID is the cap table's share class the round issues into.
	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

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 — 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) 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) ([]Registration, 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"
)

Jump to

Keyboard shortcuts

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