events = $events; $this->clock = $clock; } /** * Persist the supplied draft and return the post-save * {@see AuditEventRecord} carrying the database-assigned id. * * Stamps `createdAt` from {@see Clock::now()} (normalized to * UTC so the persistence layer's `Y-m-d H:i:s` MySQL * `DATETIME` storage is always wall-clock UTC), encodes the * draft into a fresh {@see AuditEventRecord} (id = null), and * hands it to {@see AuditEventRepository::save()}. The §10.1 * append-only invariant is enforced inside `save()`; this * writer never produces a record that already carries an id. * * Persistence failures bubble out unchanged so the call site * can decide on log-and-continue vs. escalate (see the * {@see AuditEventWriter} file-level docblock). * * @param AuditEventDraft $draft Validated audit-event draft. */ public function record( AuditEventDraft $draft ): AuditEventRecord { $now = $this->clock->now()->setTimezone( new DateTimeZone( 'UTC' ) ); $record = new AuditEventRecord( null, $draft->eventType(), $draft->auditKind(), $draft->actorType(), $draft->actorId(), $draft->targetType(), $draft->targetId(), $draft->action(), $draft->result(), $draft->safeSummary(), $draft->changedFieldsJson(), $draft->contextJson(), $now ); return $this->events->save( $record ); } }