Keep Pulsar tenant isolation independent
Scope every lookup and mutation to its tenant independently of authorization; queued work reconstructs actor and tenant data before it acts. Pulsar supplies guidance and generated placeholders, not a tenancy package, Context abstraction, storage recipe, middleware, or binding lifecycle.
Reconstruct durable runtime data
HTTP establishes actor and trusted tenant input at its boundary. A Job, Command, or queued Listener carries durable IDs, scalars, DTOs, or Value Objects, reconstructs actor and tenant data, rejects missing information, and then re-fetches tenant-scoped state. Never serialize a live Model as worker authority.
Scope every subject to its tenant
Permission does not make a subject belong to a tenant. Apply tenant scope to every read and mutation, then reject a mismatch independently of Gate or Policy result. A Command requires an explicit tenant or rejects unattended invocation; a retry repeats reconstruction and scoped lookup.
Keep four enforcement layers separate
| Layer | Owner | Does not prove |
|---|---|---|
| Adapter authorization | HTTP, Job, Command, and Listener authorize before one UseCase. | Tenant membership, business validity, or idempotency. |
| Tenant isolation | Adapter reconstructs tenant and scopes every subject. | Permission, invariant, or a tenancy package. |
| Domain invariant | Action or UseCase validates state inside the UseCase transaction for every caller. | Permission, membership, or deduplication. |
| Idempotency and reentrancy | Retryable effect reuses a stable key; application owns its storage choice. | Authorization, tenant scope, committed dispatch, or durable relay. |
Interpretation. Actor authorization, tenant scope, Audience entry, Domain invariants, and retry idempotency are independent enforcement layers. A deny or mismatch must stop its own path rather than being inferred from another layer.
Reject mismatches and duplicates
For HTTP, a trusted tenant input scopes the lookup before the UseCase. For Job, Command, and Listener work, reconstruct actor and tenant, explicitly authorize with Gate::forUser(actor), scope the re-fetch, then call one UseCase. The UseCase validates the invariant; a retry uses the same idempotency key and a reentrancy guard where reaction loops are possible. Do not treat HTTP validation, a successful Gate, or a queue retry as deduplication.
Test the boundaries
Test permission denial, tenant mismatch, invalid Domain state, and repeated-key handling separately for each audience. The current integration fixture proves a scoped lifecycle and after-commit behavior, but not actor reconstruction, tenant filtering, broker durability, or idempotency. This page intentionally leaves discovery, binding, identifiers, and Octane work to later architecture scope.