Veto/docs
Meow Gateway

Onboarding

Four-step wizard from KYB documents to sealed genesis receipt. Durable mid-flow state; replay-safe transitions.

Onboarding is the multi-step flow that brings a new entity onto the gateway. Each step persists durably so an operator can pause Monday, resume Wednesday, submit Friday — no lost work.

The four steps

draft  ─upload docs→  docs_uploaded  ─select pack→  policy_selected


                                                     submitted
                                                      │     │
                                                      ▼     ▼
                                                 approved  rejected

Flow

1 · Create

POST /v1/onboarding/applications
{
  "entity_name": "Acme Corp LLC",
  "entity_type": "llc",
  "contact_email": "ops@acme.example",
  "created_by": "op_1"
}

Response includes application_id, freshly-minted entity_id, and required_docs — the document kinds that must be uploaded before submit.

2 · Upload documents

Per-entity-type required kinds:

entity_typerequired docs
llcarticles_of_incorporation, beneficial_ownership, ein_letter, operating_agreement, government_id
ccorp / scorparticles_of_incorporation, beneficial_ownership, ein_letter, government_id
partnershiparticles_of_incorporation, beneficial_ownership, ein_letter, operating_agreement, government_id
sole_propgovernment_id, proof_of_address, ein_letter
POST /v1/onboarding/applications/$ID/documents
{
  "kind": "articles_of_incorporation",
  "content_hash": "sha256:...",
  "filename": "articles.pdf",
  "uploaded_by": "op_1"
}

The gateway stores only the hash — you keep the blob. Managed mode mirrors the hash to Convex; self-host keeps it local.

3 · Select policy pack

POST /v1/onboarding/applications/$ID/policy
{
  "policy_pack_id": "ap_strict_v1",
  "actor": "op_1"
}

The gateway verifies the pack exists in storage and snapshots its sha256 into the application row.

4 · Review + submit

# Readiness check — pure validator, no state change:
GET /v1/onboarding/applications/$ID/review

# Seal genesis + submit:
POST /v1/onboarding/applications/$ID/submit
{ "submitted_by": "op_1" }

Submit atomically:

  1. Re-runs the readiness check (no racing upload-rollback can slip through)
  2. Appends a genesis decision receipt to the entity's chain with tool: veto_onboarding_submit, decision: allow, bound to the pack's sha256
  3. Transitions the application to submitted

Why a sealed genesis receipt

The per-entity receipt chain is only trustworthy if you can prove where it started. The genesis receipt:

  • Anchors the chain to a known point (prev_receipt_hash = sha256:e3b0..., SHA-256 of empty)
  • Binds the exact policy pack sha256 active at birth
  • Hashes the onboarding snapshot (entity name, type, policy pack, document hashes) into args_hash

An auditor can:

  1. Pull the application row (or the genesis receipt alone)
  2. Recompute args_hash from the canonical snapshot
  3. Verify the full chain with verifyReceiptChain() — any tampering after submit breaks verification instantly

Terminal decisions

POST /v1/onboarding/applications/$ID/decision
{
  "decision": "approved",     # or "rejected"
  "actor": "op_compliance",
  "reason": "reviewed 2026-04-25"
}

Managed mode forwards this to Meow's partner API. Self-host is free-form operator input — you're the compliance officer.

Replay safety

Every transition is an atomic CAS on the current state:

  • Submit twice? Second call returns 409 workflow_invalid_transition
  • Upload docs after submit? 409
  • Select policy before docs? 409
  • Pick a nonexistent pack? 404 policy_pack_not_found

The CAS runs inside BEGIN IMMEDIATE so multi-operator concurrency is safe: two admins hitting submit at the same moment have one succeed and one get a clean 409.