Canonical brief for AI agents and humans. Per-tool files are generated from .rulesync/; edit the source, then run pnpm gen:agents. Never hand-edit generated mirrors.
Open-source, headless, plugin-based, AI-native igaming framework. Consumers extend modules, plugins, and adapters in their own repo; this repository ships backend contracts, runtime, SDK, tooling, and no consumer UI.
This is real-money regulated gambling. A defect here moves a player's money or breaks an operator's licence, so the money, KYC, responsible-gambling and audit standards get read before those paths change, not after. "Operator" is the company running an igaming on this platform; "player" is the person gambling. docs/platform/glossary.md defines the rest of the vocabulary.
packages/core/- the one published package,@openora/core, exposed as subpaths.src/<domain>/<module>/holds every module;src/contracts/the shared schemas and adapter ports;src/server/the node engine (db, auth, kernel, plugin-host,createApp);src/react/the frontend consumption surface.packages/testing/,packages/mcp/,packages/create-openora/- test harness, the consumer-facing MCP server, the scaffolder.apps/mcp-server-dev/- theoss-devMCP server this repo's agents use.tools/- generators (gen/), lint gates (lint/), db helpers (db/), consumer templates.docs/-platform/what it is,guides/how to do a job,standards/the rules,adapters/vendor binding,modules/a module's own surface,adr/why.docs/catalog.jsonis generated.extensions.config.ts- the one list of enabled plugins. An overlay lives inextensions/<name>/in a consumer's repo, not here.
docs/catalog.jsonis the generated surface: every module, table, route, event, adapter port, config field. Read it, or call theoss-devMCP tools (catalog-overview,list-modules,describe-module,list-routes,list-adapters,schema-get,docs-search) instead of grepping.pnpm setupboots infra, migrates, and prints a summary.pnpm devruns it.pnpm verifyis the gate CI runs; it ends withpnpm check:drift.
- Universal engineering baseline and a topical routing table:
conventions. - Ticket, spec, or design context:
docs/agents/issue-tracker.md- read the whole ticket, comments and images included, before planning or reviewing against it. - Events, jobs, realtime, and outbox choices:
messaging-and-microservices. - Module boundaries, DI, ports, and shared helpers:
docs/standards/module-structure.md. - Money, compliance, and audit: read
docs/standards/{money,compliance,audit}.mdbefore changing those paths. - Wallet, custody rails, and the payment seam:
docs/modules/wallet.md, thendocs/standards/custody.md. - The touched module's contract, schema, plugin, and catalog entry define its current surface. A module carries a nested
AGENTS.mdonly to point at its owning rule; rules themselves live in.rulesync/rules/.
- New module, route, table, seed, schema, enum, adapter, config, hook, or integration: use the matching generator and the topical standard named by
conventions. - Cross-module work: use a command port, domain event, shared contract, or read-only
/schemasubpath as defined indocs/standards/module-structure.md. - Async or cross-process work: choose the channel in
messaging-and-microservicesbefore implementation. - Docs or generated configuration: edit canonical source only.
pnpm regenowns generated artifacts;pnpm gen:agentsowns agent mirrors.
- Headless means no frontend UI belongs here; consumers build over
@openora/core/react. - This repository is public. A client's or vendor's name, and a client's ticket id, must never appear in a file name or in file content -
pnpm check:hygienefails the build on either. Describe the behaviour or name the port instead; ticket ids belong in the commit message and the PR description. - The
SealedTokenservices inpackages/core/src/compliance/sealed.ts(RG enforcement, KYC writes, AML/SAR, ledger writes, RNG) are the ones an operator may never override. Do not add a rebind path around one. - Admin routes resolve the shared
AdminGuardinplugin.ts; the handler's first statement awaitsadminGuard.assert(context, <resource>, <action>). - Serialize
pnpm regenandpnpm gen:agentsacross agents. They rewrite shared generated state; one owner runs each command after parallel edits finish.
Report defects a reader can act on: a concrete input or state and the wrong result it produces. Rank money, compliance, and security findings first. Skip what pnpm verify already fails on (lint, format, types, module boundaries, hygiene) and pure style preferences.
docs/catalog.json is gitignored and the oss-dev MCP server runs only locally, so a hosted review has neither. Read the touched module's contract, schema, and plugin.ts directly.
- Check every change to a balance, ledger, payment, or settlement against
docs/standards/money.md. - Flag floating-point money, a money mutation without a database idempotency guard inside its transaction, a debit and credit that can commit apart, and an auto-approval path that fails open.
- Check KYC and responsible-gambling changes against
docs/standards/compliance.md, and state-changing actions againstdocs/standards/audit.md. - Flag a limit checked outside the transaction and lock that moves the money, a player-state mutation without an audit record, any update or delete of audit rows, and a rebind path around a
SealedTokenservice.
- Flag an admin handler whose first statement does not await
adminGuard.assert(context, <resource>, <action>), a player route that reads or writes another player's resource, an unverified webhook, and a secret or PII written to a log or an error message.
- Flag a breaking change to a route contract, event payload, or adapter port without a migration path, a Drizzle schema change without its migration, and a cross-module write that bypasses the command port.
- Flag a hand-edited generated file (
AGENTS.md,docs/catalog.json, Drizzle output) instead of its source.
- A new or changed route needs an E2E through
bootTestAppwith the happy path and one hostile path, perdocs/standards/testing.md. Flag a test that mocks the database, a repository, or a sibling service.
Delegate to the matching named agent; do not use a generic agent for a roster task. expert (fuzzy ask -> requirements and acceptance criteria), dev (implement a spec), module-author (a whole new module), plugin-author (an overlay), qa (automated tests plus a hands-on walkthrough), docs (prose audited against the code), cleaner (delete the surplus a branch added, before the PR), operator (consume the platform as a downstream operator would), and the reviewers contract-reviewer, quality-reviewer, security-reviewer.
Use pure, composable functions and explicit typed wiring. Match local naming and structure. Prefer clear names, guard clauses, immutable construction, and side effects at boundaries. Reuse an existing helper before adding one.
| Change | Read first |
|---|---|
| schema, type, enum-like value set | docs/standards/types.md |
| SQL, Drizzle, migration, seed, DB tool | docs/standards/database.md |
| function, service method, constructor | docs/standards/functions.md |
| module, DI wiring, integration, cross-module dependency | docs/standards/module-structure.md |
| error class or catch | docs/standards/errors.md |
| money movement or payment settlement | docs/standards/money.md |
| wallet module surface or ledger invariant | docs/modules/wallet.md |
| rank ladder, rank rewards, account levels | docs/modules/gamification.md |
| deposit address, sweep, reconciliation, custody rules | docs/standards/custody.md |
| implementing or binding a payment/custody vendor | docs/adapters/ |
| KYC or responsible gambling | docs/standards/compliance.md |
| audit production or consumption | docs/standards/audit.md |
| async seam, event, job, or realtime | messaging-and-microservices |
| test | docs/standards/testing.md |
| comment or JSDoc | docs/standards/comments.md |
| prose doc, README, guide | docs/standards/documentation.md |
| hook or typed client | docs/standards/react-sdk.md |
| commit or PR | docs/standards/git-delivery.md |
| failing gate or lint rule | docs/standards/enforcement.md |
- Schema-first at trust boundaries. Infer types from their owning schema or row; do not hand-write duplicates.
- Literal config arrays/objects (option lists, key sets) use
as const, not an explicit union type annotation - let TypeScript infer the literal types. - No
any,interface, decorators, reuse inheritance, suppressive casts, or default exports exceptplugin.tsanddrizzle.config.ts. - Keep third-party access behind an owning adapter port. Do not import another module's internals or create cycles.
- Do not hand-edit generated artifacts: migrations,
docs/catalog.json, or per-tool agent mirrors. - State-changing work is transactional where its standard requires it. Money work is also idempotent with a durable database guard inside that transaction.
Use the seams and contracts in code; this rule owns channel selection and the safety limits that affect a design.
| Need | Channel |
|---|---|
| An answer or mutation now, including money | Synchronous command port |
| A fact happened and others may react | Domain event via EventBus |
| Durable, retryable, or scheduled work later | JOB_QUEUE |
| Server-to-client push | REALTIME_TRANSPORT with an SSE eventIterator |
- Handlers and jobs are at-least-once: they must be idempotent. Money-adjacent work needs a durable database guard, not only an event ID or idempotency key.
- Money and any needed-now answer never travel over events. Use a synchronous, transactional command port.
emit()is best-effort. UseemitInTransaction()only when the transactional outbox is explicitly enabled byOUTBOX_ENABLED,AMQP_URL, orRABBITMQ_URL; those AMQP variables enable the outbox but do not bind an AMQP broker.- A Redis Streams deployment uses
SERVICE_NAMEas its durable consumer-group identity. Every independently deployed service sharing Redis needs a distinct name. - The shipped Redis Streams and BullMQ drivers do not honor
orderingKey; use an overlay when strict ordering is required. REALTIME_TRANSPORTis Redis Pub/Sub (RedisPubSubRealtimeTransport, auto-bound onREDIS_URL, no in-process fallback - ADR-0031) - fire-and-forget UI push, not a durable event: a message published while nobody is subscribed is simply lost, which is why a realtime channel carries a change signal the client refetches, never the state itself. Channels are prefixed withSERVICE_NAMEfor the same cross-deployment isolation reason as the streams broker's consumer group.RealtimePresence/getOnlineUserIdsare shared across replicas viaRedisPresenceStore, a per-channel sorted set scored by each member's last heartbeat and read with a TTL cutoff, so a crashed replica's members self-expire instead of lingering forever.
Routing only. Open the file you need; do not work from this list alone.
| Change | Read |
|---|---|
| Any balance change | docs/standards/money.md |
| This module's surface and its own invariants | docs/modules/wallet.md |
| Deposit address, sweep, reconciliation, custody rules | docs/standards/custody.md |
| Implementing or binding a custody vendor | docs/adapters/custody.md |
| Binding a synchronous PSP | docs/adapters/payment.md |