Kaláka

Foundation

How every write happens, how failures are kept, and the rules that cut across all modules.

Layers

Every plugin we ship has the same layers. Dependencies point downward only.

flowchart TB
  D["<b>Delivery</b><br/>REST · abilities · blocks · admin app · portal · WP-CLI · cron<br/><i>resolves the actor, no business rules</i>"]
  A["<b>Application</b><br/>one use case per action, run by the runner<br/>transaction · permission check · audit"]
  M["<b>Domain</b><br/>entities · Money · rules · materializer · conditions"]
  I["<b>Infrastructure</b><br/>repositories and query classes over our tables · payment methods · wp_mail"]
  D --> A
  A --> M
  A --> I
  1. Domain code may use WordPress where that makes sense. There is no “pure domain” rule: this is a WordPress plugin and need not be portable.
  2. An interface exists only where there really are several implementations (payment methods, the clock). There is no general “infrastructure is swappable” layer.
  3. Use cases are the only entry point for writes. REST controllers, abilities, WP-CLI commands, the periodic run and form submissions all call a use case. Nothing writes a table directly.
  4. Module and plugin boundaries are checked in CI (Engineering), not left to discipline.

Two sides, one machinery

Both sides use the same use-case runner (transaction, audit, post-commit evaluation, errors) but have separate action catalogues, authorization and REST namespaces (kalaka-admin/v1, kalaka-portal/v1). Use cases never read the current user themselves: the delivery layer resolves the actor once and passes it in.

The use-case runner

flowchart LR
  R["Resolve actor"] --> S["Check input<br/>against schema"] --> P["Coarse<br/>permission"] --> B["Begin"]
  B --> X["Execute<br/>fine permission checks,<br/>changes"] --> AU["Audit entry"] --> C["Commit"] --> E["Post-commit<br/>evaluation"]
  X -. "any exception" .-> RB["Rollback<br/>+ clear object cache"] --> FA["Failed audit entry<br/>(own transaction)"]

Errors

Entities, repositories and queries

Audit

Failures

Failures are kept on the record they concern; there is no log table.

Deferred work

There is no job queue (no Action Scheduler, no general queue). Each kind of deferred work gets exactly the guarantee it needs:

Bulk changes

A bulk change is never blocked by one broken record:

Because registrations keep their tariff snapshot, saving event settings never touches existing registrations (Money).

Cross-cutting rules

Concern Rule
Storage Our own database tables for the domain (events, registrations, values, submissions, presets, ledger, deliveries, audit, notes, …). WordPress posts only for documents edited in the block editor: forms, field definitions, mail bodies
Time Stored in UTC; displayed and computed in Europe/Budapest. A PSR-20 ClockInterface (now(): DateTimeImmutable) wraps current_datetime() in production and is fixed in tests; WordPress has no clock abstraction of its own. Dates are set through date references (Events)
Permissions Fine-grained capabilities per use case; roles are bundles of capabilities. Core ships default roles; the role set is defined with the owner later (Deferred). Admin actions are also WordPress abilities and use the same capabilities
Personal data Kept only in our tables. WordPress privacy export and erase cover registrations, presets and audit diffs. Retention for guest data and the audit is decided later (Deferred); archiving an event starts its retention clock
Secret links Treated as secrets: never logged, masked in exports (Registrations)
Money DECIMAL(26,8), a currency code on every record, brick/math arithmetic, never floats (Money)
Sorting utf8mb4_hungarian_ci on our text columns (Engineering)
Language Hungarian UI strings directly in code; English identifiers and comments