feat: strengthen module contracts and shared WebUI runtime

This commit is contained in:
2026-07-29 14:16:28 +02:00
parent 53e947935a
commit 68328f3d8e
57 changed files with 4358 additions and 371 deletions

View File

@@ -42,8 +42,18 @@ inheritance, reuse, derivation, and automation are unavailable.
## Delivery Durability
Domain trigger implementations persist idempotent deliveries before running.
The existing `PlatformEvent` bus is process-local and is not a durable
automation source. A transactional Core event/outbox bridge is still required
to guarantee capture across commits, restarts, and multiple workers. Until
that bridge exists, direct event ingestion must be authenticated, tenant
scoped, and limited to public or internal event envelopes.
`emit_platform_event` binds event delivery to the producer's SQLAlchemy
transaction. When an enabled module provides `platform.eventOutbox`, the event
is stored in that transaction and a dispatcher may retry it across restarts and
workers. The Audit module provides the current SQL outbox implementation; the
Celery `govoplan.events.dispatch_outbox` task drains it through the Dataflow
event-ingestion capability and the local event bus.
The outbox capability remains optional so reduced module combinations can
start. Without it, Core queues events on the SQLAlchemy transaction and
publishes them to the process-local bus only after the outer commit. A rollback,
including a nested savepoint rollback, discards the corresponding events. This
fallback is suitable for local or non-critical reactions, but it is not a
durable multi-worker automation source. Deployments that rely on event-triggered
work must enable the outbox provider and run the `events` worker queue and
periodic dispatcher.

View File

@@ -209,9 +209,13 @@ prefer `FILE_STORAGE_*`.
Interactive password login is enabled with fixed-window limits of 10 failures
per normalized identity and 100 failures per direct client over 900 seconds.
`AUTH_LOGIN_THROTTLE_*` settings change those limits. Counters use `REDIS_URL`
when Redis is reachable so replicas share state; a bounded process-local
fallback keeps development and Redis outages functional, with per-process
enforcement until Redis recovers.
when Redis is reachable so replicas share state. Production-like startup fails
when throttling is enabled without `REDIS_URL`. Set
`GOVOPLAN_ALLOW_PROCESS_LOCAL_LOGIN_THROTTLE=true` only as an explicit
single-process risk acceptance. A bounded process-local fallback keeps
development and temporary Redis outages functional, with per-process
enforcement until Redis recovers; monitor Redis because protection is weaker
during that fallback.
### Outbound Connector Egress

View File

@@ -8,25 +8,32 @@ module reactions, and operator diagnostics.
## Production Transport Decision
The first production target is a **database outbox plus in-process immediate
dispatch**:
The production transport is a **transactional database outbox plus retrying
dispatcher**:
- Use `govoplan_core.core.events.PlatformEvent` for domain and platform events.
- Use `EventBus` as the in-process dispatch contract for same-process module
reactions that are safe to run inline.
- Call `emit_platform_event(session, event)` to bind event delivery to the
domain transaction.
- The optional `platform.eventOutbox` capability persists events atomically.
The Audit module provides the current SQL implementation.
- Without an outbox provider, Core publishes to `EventBus` only after the outer
transaction commits. This preserves reduced installations but is not durable
across process failure or multiple workers.
- Use `EventBus` as the in-process dispatch contract for module reactions
invoked by the outbox dispatcher or for non-critical fallback reactions.
- Use the shared `audit_event` / `audit_from_principal` helper for audited
module actions. The helper persists the audit row and immediately publishes a
module actions. The helper persists the audit row and transactionally emits a
governed `PlatformEvent` whose `type` is the audit action.
- Use `record_change` for module delta feeds. It persists the change-sequence
row and immediately publishes a generic module change event such as
row and transactionally emits a generic module change event such as
`mail.profile.updated`.
- Persist durable integration/workflow events through a database outbox before
acknowledging the state change that produced them.
- Drain the outbox through a small dispatcher process. The dispatcher may call
in-process handlers in the same deployment first, but its storage contract is
database-backed.
- Drain the outbox with the Celery `govoplan.events.dispatch_outbox` task on the
`events` queue. The periodic schedule also retries pending rows.
- The dispatcher invokes Dataflow event ingestion when that capability is
active, then publishes to the process-local bus.
- Treat Redis/Celery as worker/job infrastructure, not as the authoritative
first event transport. A Celery dispatcher can consume the outbox later.
first event transport. PostgreSQL remains authoritative until dispatch is
recorded.
- Keep the dispatch implementation pluggable behind the `PlatformEvent`
envelope so a future message broker can be added without changing event
producers.
@@ -44,17 +51,18 @@ Event producers should write their domain state and outbox event in the same
database transaction wherever possible. Handlers must be idempotent because the
outbox dispatcher can retry after a crash or timeout.
Recommended first outbox columns:
The current outbox stores:
- `event_id`, `event_type`, `module_id`
- `correlation_id`, `causation_id`
- `payload`, `occurred_at`
- `available_at`, `attempt_count`, `claimed_at`, `claim_token`
- `processed_at`, `last_error`
- `classification`, serialized event `payload`
- `status`, `attempts`, `next_attempt_at`
- `dispatched_at`, `last_error`, timestamps
Inline `EventBus` handlers are allowed only for non-critical local reactions.
Anything that must survive process failure, restart, package update, or worker
redeployment belongs in the outbox.
Handlers must be idempotent: a worker may complete an external effect and fail
before marking its outbox row dispatched. Anything that must survive process
failure, restart, package update, or worker redeployment requires the outbox
provider and dispatcher.
## Trace IDs

View File

@@ -3,10 +3,13 @@
Core exposes `govoplan_core.core.throttling` for security-sensitive endpoints
that need bounded fixed-window counters. Subjects are SHA-256 hashed before they
become store keys. A configured Redis instance provides atomic counters shared
across API workers; development, a missing Redis configuration, and temporary
Redis outages use a bounded process-local fallback. When Redis fails, local
attempts are still mirrored so losing the distributed store does not reset the
active worker's protection window.
across API workers. Development and temporary Redis outages use a bounded
process-local fallback. Production-like startup rejects an enabled login
throttle without `REDIS_URL` unless
`GOVOPLAN_ALLOW_PROCESS_LOCAL_LOGIN_THROTTLE=true` explicitly acknowledges the
single-process limitation. When Redis fails at runtime, local attempts are still
mirrored so losing the distributed store does not reset the active worker's
protection window.
Callers define one or more `ThrottleDimension` values with a controlled
namespace, a subject and a positive limit. They must call `check` before an