Your documents are the truth. The change feed follows automatically.
Papuma Kernel is a small application kernel for .NET 10, built as two independent products sharing one model. You store plain C# objects as JSON documents; the kernel derives a reversible change feed in the same atomic transaction, applies your privacy policies before anything is recorded, and delivers every change to your handlers in causal order. You get what event sourcing promises — a complete, auditable history and reactive projections — without the replay obligation, the mandatory event modeling, or the GDPR headache.
This is document-sourced CQRS: the document is the source of truth, the feed is derived from it (never the other way around).
Papuma.Kernel— PostgreSQL ≥ 18. One primitive makes the write path work:RETURNING OLD/NEWgives the before- and after-state in a single statement, so there is no outbox to forget and no window in which the diff can lie. Multi-tenant, row-level-security-isolated, built for servers.Papuma.Kernel.Local— SQLite, no server. Same documents, same reversible diffs, same change feed, same privacy policies — for single-writer desktop and mobile apps that have no business running a database server. See the design rationale for what's shared and what's deliberately different per engine.
The 1.0 reboot — rebuilt from scratch on PostgreSQL ≥ 18; no migration path from 0.x (see CHANGELOG). The marketing one-pager with diagrams and measured numbers lives in docs/factsheet.md.
builder.Services
.AddPapumaKernel(o =>
{
o.ConnectionString = config.GetConnectionString("papuma");
o.Model(m => m
.Document<User>(d => d.UniqueKey(x => x.Email))
.Event<UserLoggedIn>(e => e.Retention(TimeSpan.FromDays(90))));
})
.AddChangeHandler<UserProjection>();
// Write — atomic, versioned, policy-applied, feed included:
await using var session = store.OpenSession(ScopeContext.Tenant("acme"));
await session.SaveAsync(user, expectedVersion: 0);
await session.AppendAsync(new UserLoggedIn(user.Id, "web"));
await session.CommitAsync();Schema, indexes and row-level security are created idempotently at startup. There is no migration step. There is no step two.
No server, same model — Papuma.Kernel.Local mirrors the same call shape:
builder.Services
.AddPapumaKernelLocal(o =>
{
o.DbPath = Path.Combine(appDataDir, "app.db");
o.Model(m => m
.Document<User>(d => d.UniqueKey(x => x.Email))
.Event<UserLoggedIn>(e => e.Retention(TimeSpan.FromDays(90))));
})
.AddChangeHandler<UserProjection>();
await using var session = store.OpenSession(ScopeContext.Tenant("local"));
await session.SaveAsync(user, expectedVersion: 0);
await session.CommitAsync();dotnet build Papuma.Kernel.slnx
dotnet test Papuma.Kernel.slnx # Postgres suite needs Docker/Podman; SQLite suite needs nothing extraLearn by building: docs/tutorial.md · terse API tour: docs/getting-started.md.
Shared between both kernels (same code, Papuma.Kernel.Core, not two
implementations pretending to agree):
- Atomic write primitives — versioned Save/Delete, single-statement patches
(
Set/Remove/Increment), set-based bulk ops, append-only rollback, and a bounded counter that cannot oversell (proven under 12-way concurrency on Postgres). - Privacy by construction — field policies (
Redact/Hash/Reference/DoNotTrack) applied inside the write transaction, so sensitive values never reach the feed, the logs, the traces, or AI consumers. The same policies project onto masked reads (ADR-016). - GDPR tooling — Art.-30 data inventory from the metamodel, Art.-15/20 subject export, history redaction with a mandatory audit trail (gdpr.md).
- Processing engine — per-handler delivery in causal order (per document by version), nothing skipped (snapshot cursor), persisted checkpoints, retry/backoff, poison handling. Projections declare themselves and rebuild when their version rises; effect handlers are never reset and can start at the feed head (ADR-024).
- Event log — first-class facts (
UserLoggedIn) beside state changes, same transaction, same policies, per-type retention. - Schema evolution — lazy upcasting with version guards, same
Upcast(fromVersion, …)registration on either kernel. - Declared keys — unique and lookup keys as partial expression indexes,
including composite keys over several fields
(
UniqueKey(x => new { x.ProjectId, x.Number }), ADR-020).
Postgres-only (Papuma.Kernel):
- Two-layer multi-tenancy — explicit scope predicates plus PostgreSQL
row-level security; fail-closed (a missing scope yields empty reads, never a
leak). Your own tables in the same database join in through
papuma.scope_visible/scope_writable(ADR-019). - Testing that exercises RLS —
Papuma.Kernel.Testingruns tests as a non-superuser role and drains feeds deterministically (ADR-021). - Multi-instance leader failover via
FOR UPDATE SKIP LOCKED— no extra infrastructure for concurrent processor instances. - AI-ready — an MCP server over the diagnostics and scope-bound, policy-masked reads (read-only by default).
- Observability — BCL
Meter+ActivitySource(zero vendor deps), OpenTelemetry-ready, feed-lag health check, and an embedded live dashboard (MapPapumaDashboard()).
SQLite-only (Papuma.Kernel.Local):
- No server, no daemon, no port — one file, opens in milliseconds, single writer enforced by the OS file lock, not application code.
- In-process feed wakeup — a
SqliteChangeNotifierreplaces LISTEN/NOTIFY; no network round trip, near-instant delivery. - WAL journal mode + busy timeout on every connection, applied through one shared factory — a background feed processor writing doesn't block the UI reading, verified empirically, not assumed.
- Genuinely simpler where the single-writer topology allows it: no RLS machinery, no snapshot cursor — see the design rationale for exactly what's dropped and why that's safe, not a shortcut.
| Package | What it is |
|---|---|
Papuma.Kernel |
PostgreSQL kernel — store, diff engine, policies, feeds, hosting |
Papuma.Kernel.Local |
SQLite kernel — same model, single-writer embedded/desktop use, no server |
Papuma.Kernel.AspNetCore |
optional ASP.NET Core integration — tenant middleware, feed-lag health check, embedded dashboard |
Papuma.Kernel.Mcp |
optional MCP server — read-only diagnostics + masked content tools for agents |
Papuma.Kernel.Testing |
optional integration-test support — PostgreSQL 18 test database with a non-superuser role (RLS applies), feed draining; test-framework agnostic |
Papuma.Kernel.FSharp |
optional F# facade — Result-returning writes, an IAsyncDisposable-safe session runner; works with either kernel (details) |
Papuma.Kernel.Core (diff engine, policies, model, validation) is shared
internally by the two kernels; it is not independently published — its
assembly ships embedded inside whichever kernel package you install.
| Sample | Shows |
|---|---|
| shop-minimal-api | the breadth — a mini shop touching every kernel concept: approval workflows with humans in the loop, saga compensation, inventory that cannot oversell, realtime UI push, the MCP endpoint and the dashboard |
| event-modeled-slices | the shape — one vertical slice of each Event Modeling type (Command/View/Automation) with a pure, infrastructure-free Decider test |
| polyglot-consumers | the feed as a cross-language contract — Python (psycopg3) and Go (pgx) consumers, ~50 lines each |
| fsharp-local-todo | Papuma.Kernel.FSharp end to end, on Papuma.Kernel.Local (SQLite — no server, no Docker) |
The first three samples run against Papuma.Kernel (Postgres);
fsharp-local-todo is the first sample against Papuma.Kernel.Local.
Pattern guides on kernel primitives (docs/recipes): workflow-saga · realtime-ui-notifications · same-database-read-models (RLS on your tables) · projection-schema (create, evolve, rebuild) · external-read-models (search/vector/cache) · nats-bridge · field-level-encryption · event-modeling-slices · ai-consumers · backup-restore (backup, restore, replication).
- Start: tutorial.md — build one app end to end (guided) · getting-started.md — the five-minute API tour · marketing one-pager: factsheet.md
- Architecture: architecture.md · the why behind every decision: concepts.md
- Decisions: the ADRs — each a single, dated, reversible choice
Papuma.Kernel.Local(SQLite): design rationale and what's different per engine — no dedicated getting-started yet; the write/read API mirrorsPapuma.Kernel's (SaveAsync/LoadAsync/PatchAsync/… onSqliteDocumentSession,AddPapumaKernelLocalfor hosting)- Cross-language: the feed wire format consumers rely on
- Agents: llms.txt and
docs/ai/are shipped inside the NuGet package - Recipes: pattern guides on kernel primitives
- Frozen 0.x/v1 material (German, unmaintained) lives under docs/legacy.
2.1.0 — the Postgres kernel's design is complete (13 implementation phases,
every decision recorded as an ADR, every identified risk closed with a test or a
measurement; the full integration suite runs against real PostgreSQL 18 on
every CI build), but it has not yet
carried production traffic. Best fit today: internal line-of-business
systems and new products built by teams that control their PostgreSQL
version. For regulated, mission-critical workloads, run a pilot first — the
observability to judge it is built in.
Papuma.Kernel.Local is newer and should be read as such: full parity with
the Postgres kernel's write/read/patch/GDPR/rollback/feed-processing surface,
58 tests green against the real SQLite engine (including empirically
verified driver behavior, not assumed — WAL mode, busy timeouts, expression
index matching), but zero hours of real application traffic yet and no
performance benchmarks (only correctness). Good fit today for exactly what it
was built for: local desktop/mobile storage where a server is the wrong tool.
Treat it as earlier-stage than the Postgres kernel until it's proven the same
way.
- Not an ORM or query DSL — lookups run over declared, indexed keys; anything richer is a projection or a SQL view (enforced, not just advised). True on both kernels.
- Not event sourcing — the document is the truth, the feed is derived.
- Not one database-agnostic abstraction pretending to support everything.
Papuma.Kernelexploits PostgreSQL ≥ 18 without apology;Papuma.Kernel.Localis an independent SQLite implementation sharing the model, not a storage seam bolted under one codebase — see why that's a different (and deliberate) design. - Not a workflow/BPMN engine — durable state machines and timers are documented patterns on kernel primitives (with running sample code).
Issues and pull requests are welcome — start with CONTRIBUTING.md; it covers the build prerequisites (Docker for the PostgreSQL suite), the conventions, and when a change needs an ADR. Security reports go through SECURITY.md, never a public issue.
MIT — see LICENSE.