> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# Tactical DDD: behaviour and consistency

Tactical DDD provides building blocks for implementing a domain model. Start
with the decisions and rules, then choose the objects needed to express them.

## Entities and values

An entity has continuity of identity. A Party remains the same canonical Party
when its platform access state changes. A value object is identified by meaning
and values. An exact provider/instance/external-reference tuple describes a binding
without borrowing a provider's identifier as the canonical Party identity.

## Aggregates and invariants

An aggregate groups state whose immediate rules are controlled through one root.
An invariant must hold at the business boundary where it applies. Project owns its
complete child membership set; independent membership writes would bypass its
complete-observation and uniqueness rules.

PartyAuthentication owns its access state and authentication generation. The actual
transition below validates its input, preserves unchanged state, protects overflow
and records a private fact when the generation changes.

<a href="/source/services/beto/contexts/party_governance/domain/authentication.go.txt">Source: authentication.go</a>

```go
func (p *PartyAuthentication) SetAccessState(state AccessState, reason ReasonCategory) error {
	if err := p.state.validate(); err != nil {
		return foundation.Corrupt(err.Error())
	}
	if !validAccess(state) {
		return foundation.Reject("invalid_access_state", "A recognised platform access state is required")
	}
	if !validAccessReason(reason) {
		return foundation.Reject("invalid_reason_category", "An administrative or security reason is required")
	}
	if p.state.AccessState == state {
		return nil
	}
	if p.state.AuthVersion == math.MaxUint64 {
		return foundation.Reject("auth_version_exhausted", "Authentication generation cannot advance")
	}
	p.state.AccessState = state
	p.state.AuthVersion++
	p.events = append(p.events, PartyAuthenticationChanged{
		PartyID: p.state.PartyID, AuthVersion: p.state.AuthVersion,
		AccessState: state, AuthenticationEligible: p.AuthenticationEligible(),
		ReasonCategory: reason,
	})
	return nil
}
```

<a href="/source/services/beto/contexts/party_governance/domain/authentication_test.go.txt">Domain tests</a>
exercise these transitions. Cross-root uniqueness, such as issuer/subject ownership,
also requires the owner repository's concurrent uniqueness enforcement.

## Creation, restoration and repositories

Creation establishes valid initial state and may raise new facts. Restoration
validates persisted state and treats invalid bytes as corruption. It raises no new
business facts merely because an existing aggregate was loaded.

<a href="/source/services/beto/contexts/project/domain/project.go.txt">Source: project.go</a>

```go
func RestoreProject(snapshot ProjectSnapshot) (*Project, error) {
	if err := snapshot.Validate(); err != nil {
		return nil, tactical.Corrupt(err.Error())
	}
	return &Project{state: snapshot.Copy()}, nil
}
```

The application repository loads and saves the aggregate. Its persistence adaptor
owns snapshot and database mappings. Aggregate children have no independent write
repository. Database tables can differ from the object structure while preserving
the root's atomic boundary.

A domain service expresses domain behaviour with no natural entity or value owner.
An application handler coordinates a use case through ports. Keep provider I/O,
transaction mechanisms and dependency-container resolution outside the domain.

Exercise: explain why Project memberships belong to the Project root while a
PartyProviderBinding has its own lifecycle. Use their invariants as evidence.

Next: [Hexagonal architecture](/learn/hexagonal-architecture.md).

Adapted from the [architecture reference](/learn/attribution.md), with Pharos examples.

<details class="page-source"><summary>Page source</summary>

<a href="/source/docs/content/learn/tactical-ddd.md.txt">Source snapshot</a> · Revision `f928834c8`.

</details>
