/dev — Developer Experience
Developer Experience
Domain kernel puro. Event sourcing. MCP como única surface até v1.0.
Se você é dev e quer entender como o sistema é desenhado antes de cogitar contribuir ou self-host: esta página é o ponto de entrada.
Arquitetura em uma tela
Capability vira surface, não o contrário.
┌─────────────────────────────────────────────────────────────────┐
│ SURFACES (humano + IA falam aqui) │
│ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐ │
│ │ Open WebUI chat │ │ MCP server │ │ REST Fastify │ │
│ │ + MCP Apps │ │ (níveis 1-3) │ │ (nível 4 TOTP) │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬───────┘ │
│ │ │ │ │
└───────────┼────────────────────┼────────────────────┼───────────┘
▼ ▼ ▼
┌──────────────────────────────────────────────────┐
│ APPLICATION LAYER (@canna/app-services) │
│ Orquestra comandos → domain → projections → fx │
└────────────────────────┬─────────────────────────┘
▼
┌──────────────────────────────────────────────────┐
│ DOMAIN KERNEL (@canna/domain — TS puro) │
│ decide() · evolve() — zero side effects │
│ Bounded contexts: membership · inventory · │
│ dispensation · compliance · cultivation │
└────────────────────────┬─────────────────────────┘
▼
┌──────────────────────────────────────────────────┐
│ EVENT STORE (Emmett + Postgres) │
│ Append-only. Source of truth. Audit por design.│
└────────────────────────┬─────────────────────────┘
▼
┌──────────────────────────────────────────────────┐
│ PROJECTIONS (Drizzle read models) │
│ member-quota · inventory-lot · dispensation-hist│
└──────────────────────────────────────────────────┘
Background: @canna/worker (BullMQ)
→ SNGPC XML, PDF render, emailExemplo MCP call
Registrar uma dispensação = um tool call.
Request — MCP tool call
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "request_record_dispensation",
"arguments": {
"memberId": "MBR-01HXX...",
"lotId": "LOT-01HXY...",
"quantityG": 30,
"dispenserId": "USR-01HXZ...",
"prescriptionId": "RX-01HX0..."
}
},
"id": "call-42"
}Response — eventos emitidos atomicamente
{
"jsonrpc": "2.0",
"result": {
"content": [{
"type": "resource",
"resource": {
"uri": "canna://dispensation/DSP-01HY0",
"mimeType": "application/json",
"text": "{\n \"events\": [\n \"DispensationRecorded\",\n \"LotQuantityDeducted\",\n \"MemberQuotaConsumed\"\n ]\n}"
}
}],
"structuredContent": {
"dispensationId": "DSP-01HY0...",
"remainingQuotaG": 0,
"lotRemainingG": 1450
}
},
"id": "call-42"
}O que aconteceu por baixo: Domain kernel valida invariantes (quota suficiente? lote liberado? prescrição vigente?), produz 3 eventos atômicos (DispensationRecorded + LotQuantityDeducted + MemberQuotaConsumed), Emmett persiste no Postgres event store, projections atualizam read models (quota restante, histórico). Se algo falhar, nada é gravado — atomicidade event-sourced. Mesma capability é alcançável via REST /api/dispensations quando o caller é máquina não-conversacional.
Eventos canonical (do código)
20 eventos no domain kernel — fonte: packages/domain
Lista extraída automaticamente da @canna/domain. Cada evento é imutável, append-only, e participa do audit trail seed-to-dispensação.
Membership
MemberRegisteredConsentGrantedConsentRevokedPrescriptionValidatedQuotaUpdatedMemberQuotaConsumedQuotaExceededAttemptMemberSuspendedMemberReinstatedMemberAnonymized
Inventory (lote)
LotCreatedLotQuarantinedLotReleasedLotQuantityDeductedLotQuantityDeductedByDispensationLotRecalledLotExhaustedLotInsufficientQuantityLotInsufficientForDispensation
Dispensation
DispensationRecorded
Dispensation triggers compound transaction: append DispensationRecorded + LotQuantityDeducted + MemberQuotaConsumed atomicamente. Falha de qualquer invariant → 0 eventos persistidos.
Próximos bounded contexts (v0.5+): Cultivation (PlantRegistered, StageAdvanced, HarvestRecorded), Processing (BatchProcessed, COASubmitted, COAApproved), Compliance (SngpcXmlSubmitted, BspoSigned, RipdGenerated). Veja /build/domain para o modelo completo.
Setup local · 60s
Rode tudo em pnpm dev.
# Pré-requisitos: Node 22+, pnpm, Docker (para Postgres testcontainers)
git clone https://github.com/biliboss/obsidian.git
cd obsidian/99-development/canna-oss
pnpm install
pnpm verify # typecheck + tests (154 specs) across all workspaces
pnpm test:domain # @canna/domain vitest scenario coverage
pnpm dev # docs site em http://localhost:4335
# MCP server stdio
pnpm --filter @canna/mcp dev
# Open WebUI sidecar end-to-end (Docker Compose)
cd ops/openwebui && docker compose up -d