Files
884a36e6b7 feat(goja): embed ESM-free e-signature bundle for in-process cloud fold (task #100) (#8)
* feat(goja): embed ESM-free e-signature bundle for in-process cloud fold

Port the Hanzo Sign server-side domain (documents, recipients, fields, signing
flow/state, audit trail, completion) to a self-contained goja bundle exposing
globalThis.handle(req), plus a std-lib-only Go embed module (Bundle()). This is
the HIP-0106 (task #100) fold: the unified hanzoai/cloud binary runs the sign
flow as TS-on-goja backed by Hanzo Base/SQLite — no Next.js/Remix, no Prisma,
no Postgres. Persistence (globalThis.db) and the PDF/PKI seal primitives
(globalThis.pdf) are injected by cloud/clients/sign as Go host-functions.


* docs(llm): note the in-process cloud goja fold (task #100)


* refactor(goja): run on the reusable gojabase RW-Base host (__db/__newId/__now/__pdf)

Retarget the bundle to the clients/gojabase contract the captable pilot (#96)
established: handle({route,params,query,orgId,body}); persistence via
__db.query/__db.exec + __newId/__now (one txn per dispatch, commits iff
status<400); the PDF/PKI seal via __pdf.stamp/__pdf.sign injected through
gojabase Config.HostFns. One binding, not two. No behavior change to the flow.


---------

Co-authored-by: hanzo-dev <[email protected]>
2026-07-10 03:06:24 -07:00

3.2 KiB

goja/ — Hanzo Sign inside the unified cloud binary

bundle.js is a self-contained, ESM-free port of the Hanzo Sign (Documenso fork) server-side e-signature domain, authored so it runs verbatim inside the dop251/goja JavaScript engine embedded in hanzoai/cloud per HIP-0106 (task #100). It replaces the Next.js/Remix + tRPC + Prisma/Postgres runtime for the core signing flow with TS-on-goja + Hanzo Base/SQLite.

Why a bundle?

goja supports ES2020 but not ES-module import/export and not node: builtins, Prisma, or pdf-lib. So the tRPC handlers cannot be loaded directly. Instead, the exact domain flow — create → recipients → fields → send → sign → complete → seal → audit — is ported here as one CommonJS IIFE and runs in goja. It runs on the reusable clients/gojabase read-write-Base host (the same binding the captable pilot established) — the two things goja cannot do are injected by that host:

  • Persistence — globalThis.__db (per-tenant Hanzo Base/SQLite, one transaction per dispatch), NOT Prisma.
  • PDF + PKI — globalThis.__pdf (real x509/PKCS#7 signing + pdfcpu rendering), injected by clients/sign via gojabase Config.HostFns, NOT pdf-lib/node:crypto.

Host contract

globalThis.__db.query(sql, args) -> rows[]                 // gojabase, per-tenant Base/SQLite
globalThis.__db.exec(sql, args)  -> { changes, lastId }    // (one txn per dispatch; commits iff status<400)
globalThis.__newId()             -> crypto-random id
globalThis.__now()               -> unix milliseconds
globalThis.__pdf.stamp(pdfBase64, stampsJSON) -> pdfBase64        // render field values (pdfcpu)
globalThis.__pdf.sign(pdfBase64)              -> signedPdfBase64  // x509/PKCS#7 seal (digitorus/pdfsign)
globalThis.handle({ route, params, query, orgId, body }) -> { status, body }

clients/gojabase provides __db/__newId/__now + the per-tenant SQLite file

  • the request transaction; clients/sign provides the sign.db schema and the __pdf host-functions, maps /v1/sign/* HTTP routes to bundle route names, and calls handle() per request.

Tenancy

The host binds __db to the caller's tenant DB before calling handle — from the validated principal for owner routes (/v1/sign/documents/*), or from the :org path segment for recipient token routes (/v1/sign/o/:org/sign/:token). The bundle never chooses a tenant; isolation is a host property.

Route names

Route name HTTP
documents.create POST /v1/sign/documents
documents.list GET /v1/sign/documents
documents.get GET /v1/sign/documents/:id
recipients.add POST /v1/sign/documents/:id/recipients
fields.add POST /v1/sign/documents/:id/fields
documents.send POST /v1/sign/documents/:id/send
documents.download GET /v1/sign/documents/:id/download
documents.audit GET /v1/sign/documents/:id/audit
sign.view GET /v1/sign/o/:org/sign/:token
sign.field POST /v1/sign/o/:org/sign/:token/fields/:fieldId
sign.complete POST /v1/sign/o/:org/sign/:token/complete
sign.reject POST /v1/sign/o/:org/sign/:token/reject