OMLA Architecture
This document is the single source of truth for how the Open Model Licensing Association's technical system works. It covers identity, addressing, royalty math, the hoster fork, compliance, deployment, and trust boundaries. It is also the knowledge base the OMLA chatbot retrieves from — changes here change what the bot says.
1. Mission and scope
OMLA is a Washington State nonprofit corporation (501(c) status pending) that publishes an open model license and operates the infrastructure for its royalty flow. OMLA does not host model weights, does not mediate IP disputes, and does not take a cut of royalties. Its role is three things: publish the license, operate the public registry, and run the payout mechanism.
In scope: registration, wallet addressing, lineage graphs, contribution splits, hoster reimbursement, commercial usage reports, compliance state, quarterly blacklist. Out of scope: weight hosting, deciding IP ownership, content moderation of model outputs, tax advice.
2. Identity: Ed25519
Every principal (creator, hoster, OMLA itself) is identified by an Ed25519 public key. Ed25519 was chosen because:
- Small keys (32 bytes public, 64 bytes secret) and signatures (64 bytes).
- Fast, constant-time signing/verification.
- No per-signature randomness requirement — deterministic signatures remove a whole class of nonce-reuse bugs.
- Shipped in every mainstream crypto library (tweetnacl, libsodium, Node, WebCrypto with P-384-not-Ed25519 limitation; we use tweetnacl in the browser).
Secret keys never leave the user's device. Keypairs are generated in the browser for registration, then the user copies the secret to a password manager. For hosters, identity is a separate keypair from any model creator identity.
3. Addressing: Bech32m
Each model has a primary wallet address in the form omla1<payload><checksum> using Bech32m encoding (BIP-350 variant). Payload is 16 bytes — either the model's UUID or a random routing ID. Bech32m gives us:
- Human-readable prefix (
omla1…). - 4-error detecting checksum, much stronger than Base58.
- Case-insensitive (lowercase canonical).
- No ambiguous characters (
0/O/I/lare excluded).
The address is not a payment instrument by itself — it is a routing identifier that the quarterly payout job resolves to a real payment rail (Stripe Connect, PayPal, Lightning, ACH, SEPA, Wise).
4. The registry
The registry is a PostgreSQL database (hosted on Supabase) with these primary tables: models, model_metadata, lineage_edges, wallets, contribution_splits, commercial_users, usage_reports, payments, compliance_state, complaints, audit_log, hoster_claims, hoster_volume_reports, and hoster_reimbursements.
Access is gated by row-level security (RLS). Anonymous readers can see public registry rows; authenticated users see their own private data; writes that require signatures run through Supabase Edge Functions that verify Ed25519 signatures before touching the DB with service_role credentials.
The canonical schema lives at docs/schema-v2.sql as a concatenation of numbered migrations (001, 003–006, 008, 009). Migrations are idempotent and safe to re-run.
5. Royalty math and splits
A commercial user of an OMLA-licensed model owes a royalty per quarter equal to:
royalty = 0.30 × max(attributable_revenue, total_model_run_cost)
For pipeline / composite uses, the royalty is allocated across the pipeline's models by their declared pipeline weights, which must sum to ≤ 1.0.
Each model has:
- Contribution splits — named recipients (creator, collaborators, funders) with percentages summing to 100. Local to this model.
- Lineage edges — declared relationships to upstream OMLA-registered models, each with a contribution weight 0–1.0. Weights across all upstream edges for a model must sum to ≤ 1.0.
Payout resolution (function resolve_splits()):
- Compute the hoster fork (see §6). Remainder = creator pool.
- For the creator pool at this model: allocate by
contribution_splitsfor the local share, and for each lineage edge recurse upstream with weight = edge.contribution_weight × (1 − sum_of_other_edge_weights_at_this_level_already_applied). The sum of contribution_splits at this model is scaled to fill the remainder after lineage flow-through. - At each upstream model, repeat from step 1 — but hoster forks apply only at the root of the resolution, not at each ancestor, because the royalty is paid once at the root model.
6. The hoster fork
Added in migration 009 as an opt-in carve-out of the royalty at the root model.
Each model has a column hoster_share_pct in [0, 50]. A royalty of $R arriving at the model wallet splits:
hoster_pool = R × (hoster_share_pct / 100)
creator_pool = R − hoster_pool
The creator pool flows through the regular contribution_splits + lineage_edges machinery (§5). The hoster pool is distributed among verified, unrevoked hoster claims for the model, weighted by reported inference volume for the quarter:
for each verified_claim h:
h.payout = hoster_pool × (h.inferences_count / Σ verified_claims.inferences_count)
If a creator opts in but no verified hoster has reported volume for the quarter, the hoster pool rolls back to the creator pool (no money is stranded). If hoster_share_pct = 0, the fork is a no-op and payouts behave as pre-migration-009.
The DB function resolve_hoster_fork(model_id, quarter) returns the allocations; splits.js mirrors this math client-side for previews and for locally-generated registration JSON.
Hoster identity (Ed25519 keypair, independent of the creator's) is attested at claim time by signing the string "host:" + model_id. Volume reports are signed by the same key. Verification of a claim happens by either (a) the model's creator counter-signing, or (b) OMLA compliance review. Unverified claims accrue balance in the hoster_reimbursements table but are not paid out until verified.
7. Commercial-user workflow
- Register company once via
/commercial.html. Creates acommercial_usersrow and a one-time API key (shown once, hashed in the DB). - Submit quarterly report via
/commercial-report.html. Declares per-model inferences, attributable revenue, run cost, and pipeline weights. Browser computes the royalty; server re-verifies. - Pay the computed royalty to the model wallet(s) via the rails shown.
- Track history at
/commercial-history.html.
8. Compliance state machine
Each model has one of: REGISTERED, REPORTING, COMPLIANT, UNDER_REVIEW, DELINQUENT, BLACKLISTED. Transitions are deterministic, idempotent, and audit-logged. Two triggers:
- Weekly cron (
tick_compliance()via thecompliance-tickEdge Function) advances every model based on recent reports and payments. - Event triggers — inserting a
complaintsrow moves the target toUNDER_REVIEWimmediately.
Reverse transitions are symmetric: a DELINQUENT model that pays up returns to COMPLIANT on the next tick; UNDER_REVIEW rolls back to the previous state when the complaint resolves.
9. Quarterly blacklist
A signed JSON file published at /api/blacklist.json each quarter. Includes entries for willfully non-compliant models and commercial users, plus a generated_at, expires_at, and an Ed25519 signature by OMLA's association key.
Consumers (payment processors, SDKs, inference gateways) verify the signature against the public key shipped in assets/js/config.js as omlaSigningPublicKey. Until the association key is generated and published, the file ships "signature": "UNSIGNED_PLACEHOLDER" and consumers must treat it as advisory only.
10. Deployment topology
- Static site — plain HTML + vanilla JS, hosted on DreamHost. No build step; uploads go through the DreamHost File Manager (SSH is blocked on this account).
- Database — Supabase PostgreSQL, schema in
docs/schema-v2.sql, RLS enforced on every table. - Edge functions — Supabase Edge Functions (TypeScript) for operations that require signature verification or service-role writes:
register-model,hoster-claim,hoster-report,quarterly-payout,compliance-tick,publish-blacklist. - Chatbot — self-hosted on an ROG Flow Z13 (Ryzen AI MAX+ 395, 128 GB unified memory, Bazzite). FastAPI service with local embedding + generation. Publicly reachable via Cloudflare Tunnel to keep the Z13's inbound ports closed.
- OpenClaw agent daemon — also on the Z13. Handles site edits, publish, backup, and Rocket.Chat integrations. Not publicly reachable.
11. Trust boundaries and threat model
Principals and their capabilities:
- Anonymous visitor — can browse registry, read architecture, download blacklist JSON, ask the chatbot. Cannot write.
- Signed-in creator — can create and manage their own models (scoped by
creator_emailvia RLS). - Signed-in commercial user — can submit reports for their own company.
- Hoster — authenticates by Ed25519 signature per-request (no Supabase session needed for claim/report). Reads their own accruals.
- OMLA compliance reviewer — service_role access, can verify claims, mark complaints resolved, move compliance states.
Adversary capabilities we assume: control over the CDN bytes (mitigated by SRI hashes), ability to replay old signed payloads (mitigated by signed payload including quarter and unique-per-quarter constraints), ability to file spam complaints (mitigated by per-IP/per-session rate limits and the UNDER_REVIEW state resolving back to the prior state on dismissal).
Not in scope: state-level adversaries who can compromise Supabase itself; in that event the audit log and off-site signed snapshots are the recovery surface.
12. Multi-currency reimbursement
Added in migration 010. Creators are not forced into one payout currency, and platforms with their own internal currencies (Civitai Buzz, OpenRouter Credits, etc.) can remit natively without round-tripping through a bank.
Creator side. Each model has an ordered creator_currency_prefs list: an ordered set of currency codes from the currency_registry. Exactly one entry must be marked is_backup = true, and its kind must be crypto. This backup is the guaranteed-payable currency and is enforced by a deferred constraint trigger.
Remitter side. Each remitter (commercial user / platform, or hoster) declares:
- A list of
remitter_currenciesit can pay in natively (e.g. Civitai declaresBUZZandUSD). - A list of
remitter_conversion_feescapped at 50%, publicly visible, for converting each native currency into each backup-crypto currency (e.g. Civitai declaresBUZZ → BTCat 30%).
Resolution. The DB function resolve_payout_currency(model_id, remitter_kind, remitter_id) runs this algorithm per payout:
- Walk the creator's preferences in order.
- First currency the remitter supports natively → return that currency with 0 fee (route = native).
- If no intersection, return the backup crypto with the remitter's declared conversion fee from its first native currency (route = backup_with_fee).
- If the remitter declared no native currencies at all, return the backup crypto with 0 fee (route = backup_direct — the remitter is assumed to be paying in backup directly).
Worked example. Creator prefers BUZZ → USD → BTC* (backup). If Civitai pays, route = native, currency = BUZZ, fee = 0. If Fireworks pays (only supports USD), route = native, currency = USD, fee = 0. If a hypothetical platform supports only BUZZ, route = backup_with_fee, currency = BTC, fee = 30% (BUZZ→BTC).
Disclosure. All remitter conversion fees are publicly readable. Commercial users see the effective net payout before committing. Silent fees are an integrity violation handled by the complaint workflow.