Two cross-cutting decorators that wrap any Repository<TDto> from #7.
TemporalRepository<TDto>:
- Requires TDto : ITemporalEntity (compile-time static_assert).
- save() finds the existing live version, closes its valid_until, and
inserts a new row at valid_until = '9999-12-31T23:59:59Z' sentinel.
- findByEntityId() returns the live row; findByEntityIdAt(id, at) does
the [valid_from, valid_until) point-in-time read.
- list() returns live rows only; history(id) returns all versions
ordered by valid_from. Implements IHistoryRepository<TDto>.
- softDelete closes the live row without inserting a new version.
- Clock and id-generator are constructor-injected (defaults: system_clock
+ 32-char hex from mt19937_64) so the unit tests are deterministic.
The decorator's contract on the inner repository: list() must expose all
rows including historical, and save() must be upsert keyed by
(entity_id, valid_from). Documented on the class.
ScopeGuardRepository<TDto>:
- Generic; knows nothing about "property"/"tenant"/etc. Constructor
takes a std::function<bool(ActorContext, TDto)> predicate plus a
std::function<ActorContext()> accessor (so a single instance can
serve many requests with different actors).
- list() filters; findByEntityId/save/softDelete throw
ScopeDeniedException on deny.
Tests cover the five acceptance criteria from the issue body:
- Temporal save closes the prior version
- Live read returns only the row with valid_until = sentinel
- Point-in-time read returns the version live at that time
- History returns all versions in order
- Scope guard short-circuits when the predicate returns false
ctest: 6/6 green (4 prior + repository_interface + repository_decorators).
Closes#8
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>