Journal
Canonical JournalとObserved Projection、Lifecycle Event、JSONL、Replay、Securityの境界を理解する。
Journalは、OperationのLifecycleで起きた事実を順序付きで追跡するための記録です。このPageでは、保存用のCanonical JournalとObserverへ渡すObserved Journalを区別し、現在のLifecycle Event、JSONL出力、Replay、運用上の保護境界を確認します。
JournalはApplication Log、Outcome、Execution Transport Payloadの代わりではありません。Application Logは診断メッセージ、Outcomeは正常完了時の型付き結果、Transport PayloadはWorkerへ配送する入力を扱い、JournalはLifecycleの事実を扱います。
CanonicalとObservedを分ける
Canonical JournalはFrameworkがTyped RecordとしてPostgreSQLへ保護して保存する正本です。Operationの復元や監査に必要なデータを保持しますが、公開Observerへそのまま渡す契約ではありません。Canonical PostgreSQLの内部RowやPayloadは、このPageのJSON例が表すPublic Serializationではありません。
Observerへ渡すのは、FrameworkのSensitive Filterを通過したObserved Projectionです。#[Sensitive] の既定はOmitで、必要に応じてMask(例ではActor IDを [masked] として表示)または鍵付きHMAC Hashを選べます。Observer AdapterはCanonical Payloadへアクセスせず、Observed Projectionへ追加のFilterだけを適用します。
Canonical Journal (保護された正本)
-> Sensitive Filter
-> Observed Journal Projection
-> JSONLなどのObserver
Sensitive値がObservedから消えることは、保存時暗号化の代わりにはなりません。Canonical Storeには保存時暗号化、Access Control、Retention、Key Rotationを別々の運用Policyとして設定してください。
Lifecycle Event
現在の標準Eventは次の10件です。Event名は小文字のdot-separated Wire Nameで、sequence はOperationごとに1から始まる単調増加値です。
| Event | 記録する事実 |
|---|---|
operation.received |
Operationを受付した |
operation.accepted |
Deferred実行をDurableに引き受けた |
attempt.started |
Attemptを開始した |
attempt.succeeded |
Handlerが成功結果を返した |
attempt.failed |
Attemptが失敗した |
attempt.retry_scheduled |
Supervision Policyが次のRetryを予定した |
operation.completed |
Operationの最終処理を完了した |
operation.rejected |
Validation、Authorization、Business Ruleなどで拒否した |
operation.failed |
Retryせず最終失敗にした |
operation.dead_lettered |
Deferred Operationを隔離した |
operation.accepted はDeferredだけのEventです。Inlineは operation.received から直接 attempt.started へ進みます。attempt.succeeded はHandlerの事実、operation.completed はOperationの最終処理まで終わった事実です。DeferredではTyped OutcomeをOutcome Storeへ保存しますが、InlineではHTTP Responseだけへ返し、Outcome Store Rowを作成しません。Journal Eventの data とOutcome Store Rowは別の契約です。Terminal EventはCompleted、Rejected、Failed、Dead Letteredのいずれか一つです。
Observed JSONL
JSONL Observerは1行に1つのObserved Recordを、JsonlJournalRecordEncoder の形式で出力します。これはCanonical PostgreSQL Journalの公開シリアライズではありません。日時はUTCのRFC3339マイクロ秒(Y-m-d\\TH:i:s.u\\Z)です。
{"schemaVersion":1,"kind":"journal","recordId":"019f32ab-2be0-7b38-a0a7-1ab2f9687697","event":"operation.received","occurredAt":"2026-07-02T12:34:56.123456Z","sequence":1,"operation":{"id":"019f32ab-2be0-7b38-a0a7-1ab2f9687701","type":"report.generate","schemaVersion":1,"strategy":"deferred","correlationId":"019f32ab-2be0-7b38-a0a7-1ab2f9687701","causationId":null,"actors":{"origin":{"id":"[masked]","type":"user"},"authorization":null,"execution":{"id":"[masked]","type":"http"}}},"attempt":null,"data":{"value":{"reportName":"weekly"}}}
{"schemaVersion":1,"kind":"journal","recordId":"019f32ab-2be0-7b38-a0a7-1ab2f9687699","event":"operation.accepted","occurredAt":"2026-07-02T12:34:56.223456Z","sequence":2,"operation":{"id":"019f32ab-2be0-7b38-a0a7-1ab2f9687701","type":"report.generate","schemaVersion":1,"strategy":"deferred","correlationId":"019f32ab-2be0-7b38-a0a7-1ab2f9687701","causationId":null,"actors":{"origin":{"id":"[masked]","type":"user"},"authorization":null,"execution":{"id":"[masked]","type":"http"}}},"attempt":null,"data":{}}
{"schemaVersion":1,"kind":"journal","recordId":"019f32ab-2be0-7b38-a0a7-1ab2f9687700","event":"attempt.started","occurredAt":"2026-07-02T12:34:57.123456Z","sequence":3,"operation":{"id":"019f32ab-2be0-7b38-a0a7-1ab2f9687701","type":"report.generate","schemaVersion":1,"strategy":"deferred","correlationId":"019f32ab-2be0-7b38-a0a7-1ab2f9687701","causationId":null,"actors":{"origin":{"id":"[masked]","type":"user"},"authorization":null,"execution":{"id":"[masked]","type":"worker"}}},"attempt":{"id":"019f32ab-2be0-7b38-a0a7-1ab2f9687702","number":1,"startedAt":"2026-07-02T12:34:57.123456Z"},"data":{}}
JSONL Parameters
Top-level Record
| Parameter | Type | 説明 |
|---|---|---|
schemaVersion |
integer |
Observed RecordのEnvelope Schema Version。現在は1。 |
kind |
string |
固定値journal。 |
recordId |
string |
Recordを識別するUUIDv7 String。Operation IDとは別の値。 |
event |
string |
10個のLifecycle Eventのdot-separated Wire Name。 |
occurredAt |
string |
Event発生時刻。UTC RFC 3339 Microseconds(Y-m-d\\TH:i:s.u\\Z)。 |
sequence |
integer |
Operation内で1から始まる単調増加値。 |
operation |
object |
Operationの識別、Schema、Strategy、相関、Actor Projection。 |
attempt |
object | null |
AttemptがないEventはnull。ある場合はAttempt Object。 |
data |
object |
Event固有のSensitive Projection済みObject。 |
operation
| Parameter | Type | 説明 |
|---|---|---|
operation.id |
string |
Operationを識別するUUIDv7 String。全Recordで同じ値。 |
operation.type |
string |
安定したOperation Type ID(例:report.generate)。 |
operation.schemaVersion |
integer |
Operation PayloadのSchema Version。Envelope Versionとは別。 |
operation.strategy |
string |
実行経路(inlineまたはdeferredなど)。 |
operation.correlationId |
string |
RootではOperation IDと同じUUID値。子Operationは親のCorrelationを引き継ぐ。 |
operation.causationId |
string | null |
子Operationを発生させた親Operation IDと同じUUID値。Rootではnull。 |
operation.actors |
object | null |
ActorContextがない場合は全体がnull。詳細は次のTable。 |
operation.actors/Actor
| Parameter | Type | 説明 |
|---|---|---|
operation.actors |
object | null |
ActorContext全体。存在する場合はorigin、authorization、executionを必ず持つ。 |
operation.actors.origin |
object | null |
元のRequest/Actor。ObservedではIDを[masked]へ置換し、Actorがなければnull。 |
operation.actors.authorization |
object | null |
認可判断に使ったActor。ObservedではIDを[masked]へ置換し、Actorがなければnull。 |
operation.actors.execution |
object |
実行ProcessのActor。ObservedではIDを[masked]へ置換する。ActorContextがある場合は必須。 |
operation.actors.*.id |
string |
Actor ID。Observed Projectionでは[masked]。 |
operation.actors.*.type |
string |
Actorの種別(例:user、http、worker)。 |
attempt
| Parameter | Type | 説明 |
|---|---|---|
attempt |
object | null |
Attemptを開始していないEventはnull。 |
attempt.id |
string |
Attemptを識別するUUIDv7 String。 |
attempt.number |
integer |
Operation内のAttempt番号。1以上。 |
attempt.startedAt |
string |
Attempt開始時刻。UTC RFC 3339 Microseconds。 |
Event固有data
| Event | Parameter | Type | 説明 |
|---|---|---|---|
operation.received(通常) |
data.value |
object |
受付時のOperationValueをSensitive ProjectionしたObject。 |
operation.received(Ephemeral) |
data |
object |
Ephemeral OutcomeではJournalRecordFactoryがEmptyJournalDataを使うため{}。 |
operation.accepted |
data |
object |
DeferredをDurableに受理した事実。EmptyJournalDataのため{}。 |
attempt.started |
data |
object |
Attempt開始の事実。EmptyJournalDataのため{}。 |
attempt.succeeded |
data |
object |
Handlerが成功結果を返した事実。EmptyJournalDataのため{}。 |
attempt.failed |
data.errorType |
string |
Attempt Failureの型名。 |
attempt.failed |
data.errorMessage |
string |
Exception Message。SecretをMessageへ含めない。 |
attempt.failed |
data.retryable |
boolean |
Supervision PolicyがRetry可能と判定したか。 |
attempt.retry_scheduled |
data.failedAttemptId |
string |
Retry対象AttemptのUUIDv7 String。 |
attempt.retry_scheduled |
data.nextAttemptNumber |
integer |
次に開始するAttempt番号。 |
attempt.retry_scheduled |
data.scheduledAt |
string |
Retry予定時刻。UTC RFC 3339 Microseconds。 |
attempt.retry_scheduled |
data.delayMilliseconds |
integer |
RetryまでのDelay(ミリ秒)。0以上。 |
operation.completed |
data.outcome |
object |
Completed時のOutcomeをSensitive ProjectionしたObject。Outcome Store Rowそのものではない。 |
operation.rejected |
data.reason.category |
string |
Rejection Category。 |
operation.rejected |
data.reason.code |
string |
公開可能なRejection Code。 |
operation.rejected |
data.reason.violations |
array<object> |
Violation Objectの配列。Raw Rejection Valueは含めない。 |
operation.rejected |
data.reason.violations[].field |
string |
拒否対象Field名。 |
operation.rejected |
data.reason.violations[].rule |
string |
違反したValidation Rule。 |
operation.rejected |
data.reason.violations[].code |
string |
公開可能なViolation Code。 |
operation.failed |
data.errorType |
string |
Operation Failureの型名。 |
operation.failed |
data.errorMessage |
string |
Exception Message。SecretをMessageへ含めない。 |
operation.failed |
data.retryable |
boolean |
FailureがRetry可能か。 |
operation.dead_lettered |
data.finalAttemptId |
string | null |
隔離された最終AttemptのUUIDv7 String。未指定ならnull。 |
operation.dead_lettered |
data.finalAttemptNumber |
integer | null |
隔離された最終Attempt番号。未指定ならnull。 |
operation.dead_lettered |
data.reasonType |
string |
Dead Letter理由の型名。 |
operation.dead_lettered |
data.reasonMessage |
string |
Reason Message。SecretをMessageへ含めない。 |
operation.dead_lettered |
data.movedAt |
string |
Dead Letterへ移動した時刻。UTC RFC 3339 Microseconds。 |
data はEvent固有のProjectionです。共通EnvelopeへEvent固有Fieldを追加せず、EncoderはScalar、配列、日時、Framework IdentifierをJSONへ正規化し、任意のApplication Objectの__toString()は呼び出しません。対応しないObjectはnullにします。EmptyJournalDataを使うEvent/Variantも省略していません。
JSONLの設定
JSONLの出力先は、Application Configurationで絶対Pathを指定します。相対Pathや暗黙のCurrent Directoryに依存しないでください。編集するApplication-owned Fileは config/journal.php です。既定Configの書式はObserved Journalを参照してください。
return [
'jsonl' => [
'enabled' => true,
'path' => dirname(__DIR__) . '/var/log/journal.jsonl',
'delivery' => 'best_effort',
],
];
- 親Directoryは事前に存在し、実行Processから書込み可能にする
- 出力失敗時の
best_effortはObserver FailureをOperationの失敗にせず処理を継続する。監査要件のあるrequiredは書込み失敗をOperationのエラーとして扱う - File PermissionとDirectory Permissionを最小権限にし、Operator以外の読み取りを許可しない
- Rotation、圧縮、Backup、Retention、Purgeを運用Schedulerで管理し、保持期間とLegal HoldをCanonical/Observedそれぞれに定める
- Canonical Storeの保存時暗号化、鍵の生成・保管・Rotation・失効はFrameworkのJournal設定APIではなく、Application/Infrastructure/運用の責務としてApplication Secret Storeまたは組織のKMSで管理する。鍵とJSONLを同じDirectoryへ置かない
JSONLはObserver Projectionです。Canonical StoreのRetention、Access Control、暗号化、鍵管理を省略する設定ではありません。
Replayの境界
Observer Replayは、Canonical JournalのRecordを現在のObserved Projectionへ変換してObserverへ再配送する運用操作です。Record ID、Operation ID、Sequence、Occurred Atを保ち、at-least-onceで届くためTargetはRecord IDを冪等性Keyとして扱います。詳細なSelector、Dry-run、Checkpoint、AuditはObserver Replayを参照してください。
Observer Replayは、完了済みOperationをもう一度Handlerへ実行するOperation Replayとは別物です。Operation Replayは新しいOperation IDを発行し、元OperationへのCausationを記録するApplicationの再実行手順です。Observer ReplayからHandlerを呼び出したり、Canonical Rowを更新したりしません。
OpenTelemetryとの関係
Repository mainにはOpenTelemetryのAdapter、Exporter、Configurationは実装されていません。したがって、SpanやMetricの出力を現行Public Contractとして構成しないでください。
将来の候補方向として、Operation/AttemptをSpan、Lifecycle EventをSpan Event、Retry・Rejected・Failure・Dead LetterをMetric、Correlation/CausationをTrace Contextへ写像できます。これは設計候補であり、Field名・Sampling・Error処理を含むPublic Contractではありません。実装される場合も、Adapterが受け取るのはCanonicalではなくObserved Projectionです。
次はLifecycleで状態遷移を確認し、保存期間はRetention、再配送はObserver Replayへ進んでください。