package.json and goja/package.json return to the ranges the lockfile was
generated from, so `pnpm i --frozen-lockfile` installs again and the image
builds the dependency set that runs in ghcr.io/hanzoai/captable:0.1.6.
capTable summed ALL option rows into optionShares / per-stakeholder options
regardless of status, so EXERCISED, EXPIRED and CANCELLED grants inflated
fullyDilutedShares and understated every real ownership %. A cap-table surface
must not do this.
Filter both dilution queries (the totals sum and the per-stakeholder subquery)
to OUTSTANDING options only: status NOT IN ('EXERCISED','EXPIRED','CANCELLED').
EXERCISED options already became shares (counted in `share`) — counting the
option too double-counts; EXPIRED/CANCELLED are void. DRAFT + ACTIVE still dilute.
Rebuilt goja/bundle.js. Existing goja bundle tests pass.
Co-authored-by: hanzo-dev <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>
Add a self-contained, ESM-free goja bundle of the FULL captable business logic so
the unified hanzoai/cloud binary can host it in-process via dop251/goja
(HIP-0106), backed by per-tenant Base/SQLite — dropping Next.js/Prisma/tRPC from
this path. This is the PILOT of the read-write-Base-via-goja pattern (epic #96)
that esign (#100) and dataroom (#101) reuse via cloud's clients/gojabase binding.
Full fold, ported from the tRPC routers with Prisma/next-auth/zod removed and
persistence delegated to an injected globalThis.__db bridge:
- company (root, seeded per tenant by the host), stakeholders (list/add/update/
delete), share classes (list/create/update), equity plans (list/create).
- securities issuance: shares (add/list/delete) + options (add/list/delete);
share transfers (full reassign + partial split, atomic).
- convertibles: SAFEs + convertible notes (list/create/delete).
- rounds + investments: a PRICED round issues shares to each investor and
dilutes the cap table; SAFE/CONVERTIBLE rounds record capital.
- captable: computed ownership on a fully-diluted basis (shares + options),
per-class authorized-vs-issued, convertibles + rounds summary.
Structure:
- goja/src/*.ts — domain logic split by concern (host bridge, validators, and a
routes/ module per domain), exposing globalThis.handle({route,params,orgId,
body}) -> {status,body}. The bundle carries logic, never a DB engine.
- goja/build.mjs — esbuild → goja/bundle.js (IIFE, ES2015, platform=neutral, no
node: builtins). bundle.js committed so cloud go:embeds it (as @hanzo/plans
ships goja/bundle.js).
- goja/test/bundle.test.mjs — smoke + wiring test in node:vm vs a mock __db
(tenant gate, route table, validation, INSERT shape, idx auto-increment, cap
table shape, full transfer, round-create gating). 9/9 pass.
- embed.go + go.mod — tiny std-only Go embed module (github.com/hanzoai/captable)
exposing Bundle(); mirrors github.com/hanzoai/plans. The Next app + goja/src
stay the single source of truth; the Go layer is a read-only embed.
- tsconfig/biome — exclude goja/ from the Next typecheck + lint.
Co-authored-by: hanzo-dev <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>