ddd-ts-kit/authz-actor-explicit
Architecture id: authz/actor-explicit. Default severity: error in recommended, error in strict.
Applies to: domain/ and application/ of a context, tests excluded.
A use case that reads "the current actor" from the air works behind an HTTP request and nowhere else: an integration event handler, a scheduled job and a test have to set a global first, and forget. The actor is data of the command or the query, like every other input. The ExecutionScope opened at the boundary (and ActorContext, a view of it) exists for code that is not handed the context: infrastructure and the platform read it for audit columns, tenant scoping and correlation.
Reported
Section titled “Reported”- an import of
ActorContext,ExecutionScope,AsyncLocalStorageExecutionScope,ScopedActorContextorAsyncLocalStorage, from any module, as a type or as a value: the port class is also its injection token, so a value import is the one a dependency list would name - the same names reached through a namespace (
kernel.ExecutionScope,hooks.AsyncLocalStorage) - a constructor parameter typed
ActorContextorExecutionScope - a call of
.current()or.require()without argument on a receiver declared with one of these types, or, when the file does not show its declaration, namedactors,scope,scopes,...actorContextor...executionScope - an import of the infrastructure helpers
stampCreation,stampUpdateortenantScopeOf, and their use through an imported namespace (kernel.stampCreation)
Incorrect:
import { stampCreation, type ExecutionScope } from '@ddd-ts-kit/kernel';
export class VoidInvoiceHandler { constructor(private readonly invoices: InvoiceRepository, private readonly scope: ExecutionScope) {}
async execute(command: VoidInvoice): Promise<void> { const { actor } = this.scope.require(); const stamp = stampCreation(this.scope, this.clock); }}Correct:
export class VoidInvoiceHandler { constructor(private readonly invoices: InvoiceRepository, private readonly access: Access) {}
async execute(command: VoidInvoice): Promise<void> { await this.access.authorize(command.actor, billingPermissions['invoice.void'], invoice); }}
// infrastructure/persistence/drizzle-invoice.repository.tsconst row = { ...toRow(invoice), ...stampCreation(this.scope, this.clock), ...tenantScopeOf(this.scope) };Details
Section titled “Details”infrastructure/ and the platform may read the ExecutionScope and the ActorContext for audit columns, tenant scoping, log fields and the correlation id, and the module binds ActorContext and ExecutionScope: none of them is judged. Tests are not judged either, so FixedExecutionScope and FixedActorContext stay available to them. An import, the constructor parameter it types and the read are three reports, one per line to change.
The type of a receiver is read from its declaration in the same file (a parameter, a variable, a property, a parameter property): no type information is needed. A receiver named scope and declared with another type (AccessScope, a concept of the domain) is not reported.
Options
Section titled “Options”None.
rules."authz/actor-explicit" in ddd-ts-kit.config.json set to off silences the rule, and // ddd-ts-kit-disable-next-line authz/actor-explicit -- <reason> silences one line, exactly as for ddd-ts-kit check.
Limits
Section titled “Limits”- An ambient context reached under another name (a port of the project wrapping
ExecutionScope, a module-level variable) is not seen. - A re-export through a file of the context that renames the port is not followed.
- A receiver named
scopewhose declaration is not in the file (a destructured dependency, a property of a base class) is judged by its name: a concept of the domain calledscopewith acurrent()orrequire()method of its own is reported. Annotate the declaration, or record the suppression. - A method of the project named
stampCreation,stampUpdateortenantScopeOfis not reported; a helper imported under these names from another module than the kernel is.
With the CLI checker
Section titled “With the CLI checker”ddd-ts-kit check reports the same id from the text of the files and stays the fast edit-time guard: it knows no declared type, so it reports .current() and .require() on anything named scope, scopes or ...executionScope, and a helper only when the file imports it under its own name. This rule is the authority.