Contents

The packages

The one runtime sibling (@pwngh/economy-edge), the vendored arithmetic (@pwngh/money), the optional relay backend (@pwngh/taskq), and the seam where each enters.

economy-lab leans on a few smaller pieces. This page shows the exact line where each one plugs in, and what that line guarantees.

Source src/money.vendored.tssrc/db.vendored.tsscripts/support/edge-host.tsscripts/support/taskq-host.ts

The ledger is the product. One sibling package composes around it at runtime: @pwngh/economy-edge, the front door to a real payout rail. Two more serve it without composing: @pwngh/money is vendored in as its arithmetic, and @pwngh/taskq is an optional backend for the relay. Each enters through one seam; this page quotes each seam from the tree and says what it guarantees.

@pwngh/money — the arithmetic, vendored in

The money package is not a dependency; it is vendored. Two single files — src/money.vendored.ts (bigint minor-unit arithmetic with its conformance vectors embedded) and src/db.vendored.ts (the same semantics as installable SQL functions) — are copied in verbatim, so the lab keeps zero runtime dependencies.

A copy can drift, so the copy is never trusted; it is proven. The SQL engines re-run the package’s conformance vectors against the live database every boot, beside the schema check:

// src/engines/postgres.ts — inside postgresStore's boot, beside assertSchemaCurrent
await installPostgres(runner);
assertMoneyConformant(await provePostgres(runner, moneyVectors), 'Postgres');

If the database computes even one vector differently — a rounding mode, a split remainder — boot fails before any posting trusts it. The MySQL twin runs in selectStore (src/index.ts). The vendored files themselves are pinned by test/money.vendored.test.ts, and test/conformance/money-db.test.ts proves both engines against the vectors in CI.

@pwngh/taskq — the optional relay backend

Committed postings emit events through the outbox, and the relay delivers them at least once. @pwngh/taskq turns those deliveries into durable work: the bridge’s dispatcher enqueues every event as a task, keyed by the event id:

// scripts/support/taskq-host.ts — the bridge's Dispatcher
const worker = taskq.startWorker(db, { queues });
const dispatcher: Dispatcher = async (event) => {
  await taskq.enqueue(
    db,
    'economy.events',
    {
      id: event.id,
      type: event.type,
      version: event.version,
      occurredAt: event.occurredAt,
      subject: event.subject,
      data: event.data,
      audience: event.audience,
    },
    { key: event.id },
  );
  worker.poke();
};

The key makes the enqueue idempotent, so the relay’s at-least-once delivery collapses to exactly one pending task per event, and consumers inherit @pwngh/taskq’s retries instead of writing their own. The bridge is opt-in: it runs only when TASKQ_DATABASE_URL is set and the optional @pwngh/taskq peer dependency is installed; the core never sees it.

@pwngh/economy-edge — the runtime sibling

Every payout rail satisfies the same Processor port. The edge package owns one real rail’s dialect — Tilia — and the host swaps it in at composition time:

// scripts/main.ts — called in runWorker() and runServe(), before composing the economy
const edge = await maybeEdgeTilia(env, log);
if (edge !== undefined) {
  ports.processor = edge.processor;
}

The bridge is off unless TILIA_CLIENT_ID is set, and the package is an optional peer dependency: without it installed, the lab composes exactly as before. When it is on, the shimmed processor replaces the env-selected one, the edge’s hosted-KYC directory feeds the PAYEE_UNVERIFIED gate, its wallet balance feeds the float-coverage sweep, and POST /webhooks/tilia routes the rail’s callbacks into the inbox. How the shim maps the rail onto the port is owned by the processor page.

Not a package: the ops supervisor

The ops supervisor used to be a sibling package; it now ships built in at the @pwngh/economy-lab/ops entry point, composed host-side behind OPS=1. The core never imports it — leaving it out of the composition is the off switch. The supervisor owns it.

Where their own docs live

Each package documents itself; this page only owns how they compose here.

See also