Troubleshooting
症状から原因、確認、修正へ進み、Operation、Storage、Observabilityの復旧を完了する。
問題が起きたら、表示された症状だけで判断せず、原因候補を確認してから修正します。Operation IDは一つの処理を受付からTerminal Stateまで追跡する識別子です。出力やLogへCredentialを貼り付けないでください。
Taskの実行順を先に確認する場合は、Testing、Deployment、ConsoleCommand、Outbox、BlackOps CLIへ戻ります。ここではFailureの分類、確認コマンド、安全な出力境界だけを扱います。
database:seedがArtifact Errorになる
症状: Database seeding artifacts are unavailable.またはDatabase seeding runtime could not be resolved.が表示されます。
考えられる原因: Root Seederを追加・変更した後にbuild:compileしていない、Application Build IDが変わった、またはCompiled Containerが欠落しています。
確認方法: php blackops database:statusでMigration状態を確認し、php blackops build:compileのExit Codeと生成されたArtifactの存在を確認します。
修正方法: Migrationを適用し、現在のSourceと設定でBuildしてからSeedを再実行します。
php blackops database:migrate
php blackops build:compile
php blackops database:seed
Database seeding failed.の場合はApplication Seederが失敗しています。CommandはSQL、投入値、Credential、Throwableを意図的に表示しません。Application側の安全なLogとDatabase状態を確認し、Transaction、Conflict、再実行方針を修正してください。
Typed Self-handled Signature Error
症状: build:compileがTyped Self-handled handle()のSignature Errorを表示します。
考えられる原因: handle()がPublic/Non-staticでない、第一引数が具象OperationValueでない、第二引数がExecutionContextでない、Return Typeが具象Outcomeまたはvoidでない、あるいはNullable/Union/Optional Parameterを使っています。Typed標準形と#[HandledBy]を同時に指定した場合もAmbiguousとして失敗します。
確認方法: Operation Classのhandle()を確認し、次のコマンドでもう一度Compileします。
php blackops build:compile -vvv
修正方法: public function handle(ConcreteValue $value): ConcreteOutcome、またはContextが必要な場合だけpublic function handle(ConcreteValue $value, ExecutionContext $context): ConcreteOutcomeへ直します。Typed標準形から#[Accepts]、#[Returns]、OperationHandlerを外します。
401にOperation IDがある場合とない場合
症状: Quickstartの/welcomeが401を返し、Header欠落時はOperation IDがあるのに、不正なX-Sample-TokenではOperation IDがありません。
考えられる原因: Header欠落はAnonymous AuthenticationとしてOperationへ進み、#[Authorize]がLifecycle内でRejectします。不正HeaderはAuthentication MiddlewareがOperation受付前に停止します。
確認方法: Local Example Tokenで3経路を比較します。CredentialをLogへ出力しないでください。
curl -i http://127.0.0.1:8080/welcome
curl -i -H 'X-Sample-Token: invalid' http://127.0.0.1:8080/welcome
curl -i -H 'X-Sample-Token: local-example' http://127.0.0.1:8080/welcome
修正方法: Localでは.envへ空でないSAMPLE_API_TOKENを明示し、Headerと一致させます。未設定または空の設定は既知値へFallbackせずRuntime構成Errorになります。ProductionでSample Token方式を使い続けず、ApplicationのAuthenticator、Secret管理、Actor/Permission検索へ置き換えます。Header値をOperation Valueへ追加して解決しないでください。
Operation Discovery/Manifest未登録
症状: operation:listへ新しいOperationが出ず、HTTP Routeも404になります。
考えられる原因: Sourceがconfig/operations.phpのDiscovery Root外にある、ClassがComposer Autoload対象外である、Operationを実装していない、またはBuild後にSourceだけを変更しています。ManifestはBuild時に生成するRuntime検索Artifactです。
確認方法: Discovery結果とConfigを確認します。
php blackops operation:list
php -r '$config = require "config/operations.php"; var_export($config["discovery"] ?? null); echo PHP_EOL;'
修正方法: OperationをDiscovery Root配下へ置き、Composer Autoloadを更新してから再Buildします。通常のApplication FeatureをOperationProviderへ手動列挙しないでください。PackageやRoot外SourceだけProviderで登録します。
Build Artifact不在/Build ID不一致
症状: HTTP、Worker、またはConsole CommandがArtifact不在、Format不正、Build ID不一致で起動しません。
考えられる原因: var/build/を生成していない、別ReleaseのArtifactをDeployした、またはAPP_BUILD_IDを変えた後に再Buildしていません。Production RuntimeはSource DiscoveryへFallbackしません。
確認方法: Configured PathとFileを確認し、現在のSourceでCompileできるか試します。
php -r '$config = require "config/app.php"; var_export($config["build"] ?? null); echo PHP_EOL;'
ls -l var/build/operations.php var/build/http.php var/build/container.php
php blackops build:compile
修正方法: Deploy対象のSource、Dependency、Configを同じBuild工程へ固定し、その工程で3 Artifactを再生成します。古いArtifactを別ReleaseへCopyしません。
Frontend Contract ArtifactがInvalid/Stale
症状: frontend:generateまたはfrontend:checkがContract Artifact不正として失敗します。
考えられる原因: var/build/frontend.phpがない、Schemaが古い、Operation/HTTP/Frontend ManifestのBuild IDが違う、またはPHP Operation変更後に再Buildしていません。Frontend CommandはSource Reflectionやbuild:compileへFallbackしません。
確認方法: Backend Artifactを同じApplication Build IDで作り直し、Commandを順番どおり実行します。CredentialやArtifact PayloadをErrorへ貼り付けないでください。
php blackops build:compile
php blackops frontend:generate
php blackops frontend:check
修正方法: Source、Composer Dependency、APP_BUILD_ID、config/app.phpを同じBuild工程へ固定し、その工程でArtifactを再生成します。別Releaseのfrontend.phpやGenerated TreeをCopyしません。
Frontend Generated TreeがMissing/Drift
症状: frontend:checkがExit 1でmissingまたはhas driftを表示します。
考えられる原因: resources/js/blackops/をまだ生成していない、生成後にPHP Contractが変わった、Generated Fileを手動編集/追加した、または別Build IDのTreeが残っています。
確認方法: CheckはRead-onlyなので、実行前後のApplication Sourceを変更せず状態を分類できます。
php blackops frontend:check
echo $?
修正方法: Application-owned resources/js/application/を編集し、Generated resources/js/blackops/は編集しません。現在のArtifactからphp blackops frontend:generateを実行し、続けてCheckします。Non-marker DirectoryやSymlinkを強制削除せず、所有者を確認してから別Pathへ退避します。
Generated TypeScriptがCompileできない
症状: pnpm testまたはtscがGenerated OperationのImport、Value Input、Result Narrowingで型Errorを返します。
考えられる原因: Generate前、古いGenerated Tree、手書きのURL/Response型との競合、OperationValue変更にApplication-owned Consumerが追従していない、またはLockfileと異なるTypeScriptを使っています。
確認方法: Frozen LockfileとCanonical Chainを使い、最初にDriftを除外します。
pnpm install --frozen-lockfile
php blackops build:compile
php blackops frontend:generate
php blackops frontend:check
pnpm test
修正方法: Generated FileをCastやanyで隠さず、PHP OperationValue/OutcomeまたはApplication-owned Consumer Sourceを修正して再生成します。Unsupported Collection/DTO/Enumを無理にScalarへ見せず、現行Supported Typeへ戻します。
.fetch()がTransport Resultを返す
症状: .fetch()がmissing_fetch、invalid_base_url、network_error、aborted、unexpected_responseのTransport Resultを返します。
考えられる原因: RuntimeにglobalThis.fetchがなくInjected Fetchもない、Base URLがHTTP/HTTPS Origin形式でない、Network/Abortが発生した、またはResponseのStatus/Content-Type/JSON ShapeがCompiled Contractと一致しません。
確認方法: result.kind === 'transport'と安定したresult.error.codeだけを確認し、Raw Response Body、Token、Thrown Error MessageをLogへ出さないでください。SSR/Node/Testでは呼出単位のfetchとbaseUrlを明示します。
const result = await ShowWelcome.fetch({}, { baseUrl, fetch: runtimeFetch });
if (!result.ok && result.kind === 'transport') {
const safeCode: string = result.error.code;
void safeCode;
}
修正方法: RuntimeへWeb Fetch互換実装を注入し、安全なHTTP/HTTPS Base URLを使います。unexpected_responseではServerの公開Response ContractとGenerated ClientのBuild IDを揃えます。Raw BodyをResultへ追加するPatchやGlobal Mutable Credential Clientで回避しません。
Deferred HTTPが202だがOutcomeがない
症状: HTTPは202 AcceptedとOperation IDを返しますが、Outcomeが作られません。
考えられる原因: Workerを起動していない、Workerが別Database/Schemaを見ている、Retry Delay前である、またはProcess SupervisorがWorkerを停止しています。
確認方法: 同じEnvironmentでWorkerを1 Loopだけ実行し、対象Operation IDのJournalにoperation.accepted、attempt.started、attempt.retry_scheduled、Terminal Eventがあるか確認します。
curl -i -H 'X-Sample-Token: local-example' \
http://127.0.0.1:8080/operations/<operation-id>
php blackops worker:run --iterations=1 --idle-sleep-milliseconds=1
<operation-id>は202 Responseの値へ置き換えます。Worker未起動ならStatusはacceptedのままです。var/log/journal.jsonlはHTTP ProcessのObserved Projectionなので、Worker完了を待つSourceには使いません。
修正方法: HTTPとWorkerへ同じDatabase/Schema/Build Artifactを渡し、常駐WorkerをProcess ManagerまたはCompose Worker Profileで監督します。Retry Scheduledの場合はDelay後のAttemptを待ちます。
Statusが404 operation_unavailableを返す
症状: 202で受け取ったOperation IDをGET /operations/{operationId}へ渡しても404になります。
考えられる原因: IDがUnknown、OperationStatusAuthorizerが未BindingまたはDeny、Current ActorとOrigin Actorが不一致、あるいはSubject自体がRetentionで完全削除されています。Frameworkは存在とDenyを区別させません。
確認方法: 同じCredentialを使っているか、Application Service ProviderがOperationStatusAuthorizer::classをApplication実装へBindingしているかを確認します。Operation IDやActor IDだけをLogへ追加しないでください。
修正方法: ApplicationのStatus PolicyでCurrent Actor、Origin Actor、Tenant/Resource関係を評価します。Operation IDを知っていることだけをAllow条件にせず、Framework既定Denyを無効化する全許可Policyも置きません。QuickstartのSame-origin PolicyはLocal ExampleなのでProduction Policyへ置き換えます。
Statusが410 operation_expiredを返す
症状: 以前は取得できたOperationが410になります。
考えられる原因: AuthorizerはAllowしましたが、Terminal DetailまたはOutcomeがRetentionで削除され、Purge Auditから期限切れを証明できました。
確認方法: retention:planと承認済みRetention Policyを確認します。Unknown/Denyは404なので、410を認可判定の代わりに使いません。
修正方法: 必要な保持期間をApplicationのPolicyとして見直します。削除済みCanonical PayloadをStatus ResponseやBackupから無断で復元せず、Legal Hold、Access Control、Purge承認を運用します。
.wait()がpoll_timeoutを返す
症状: .wait()がTerminal StateではなくTransport Resultのpoll_timeoutを返します。
考えられる原因: Worker未起動、Retry Delay中、処理時間がDeadlineを超えた、またはStatus Request自体が期限内に完了しませんでした。
確認方法: 同じOperation IDへ.status()を一回実行し、accepted/running/retry_scheduledとretryAfterSecondsを確認します。Timeout後にOperationが自動Cancelされたとは解釈しません。
修正方法: Workerを監督し、業務SLOに合う正のmaxWaitMillisecondsを呼出単位で指定します。無限待機や固定間隔の独自Pollingへ置き換えません。Timeout後もWorkerは処理を続けられるため、後から同じIDで.status()または新しい有限.wait()を実行できます。
.status()/.wait()がunexpected_responseを返す
症状: Serverへ到達できるのにGenerated Clientがunexpected_responseで停止します。
考えられる原因: HTTP Status、JSON Media Type、Schema Version、Operation ID/Type、State別Field、Outcome Shape、Retry-AfterがCompiled Contractと一致しません。
確認方法: Generated Treeを再生成し、ServerとClientが同じBuildから作られているか確認します。Raw Body、Credential、Thrown ErrorをResultやLogへ追加しないでください。
修正方法: build:compile -> frontend:generate -> frontend:check -> pnpm testを同じReleaseで実行します。Malformed/5xxをClient側で自動Retryせず、Server ContractまたはDeploy不整合を修正します。
Operation ID付き500を調べる
症状: Responseが{"status":"error","code":"internal_error","operationId":"019..."}を返します。
考えられる原因: Operationは受理後にHandlerまたはInfrastructureで予期しないFailureとなりました。Operation ID付きのSafe Errorは、受理前のProtocol Errorとは異なります。
確認方法: IDを変更せず、Human表示、次にJSON表示で確認します。
php blackops operation:inspect 019...
php blackops operation:inspect 019... --json
received -> attempt.started -> attempt.failed -> operation.failedの順と、Application/Framework JSONL Logの同じOperation IDを確認します。HTTPやCLIにException Messageがないのは意図したSafe Surfaceです。Canonical DatabaseのRaw RecordをSupport Ticketへ貼り付けないでください。
修正方法: 同じOperation IDのJournalとSafe LogからFailure Categoryを確認し、Application Handler、Database、Dependencyの原因を修正してから、冪等性を確認した新しいOperationとして再実行します。ThrowableやProtected PayloadをResponseへ追加しません。
Scheduled Operationがconfiguration_errorで停止する
症状: operation:schedule:runがExit 2で停止し、Configuration Errorを返します。
考えられる原因: database:migrateまたはbuild:compileを先に実行していない、Schedule名/Cron/Timezoneが不正、Required Constructor引数を持つValue、または#[Authorize]へApplication-owned ScheduledActorProviderをBindingしていません。
確認方法: SourceとArtifactを更新し、SafeなJSONだけを確認します。
php blackops database:migrate
php blackops build:compile
php blackops operation:schedule:run --json
Credential、Value Payload、Actor情報はErrorへ追加しません。Providerがnullを返す場合は匿名へFallbackせず認可拒否になります。Scheduled OperationのAuthoringとProvider手順を確認してください。
修正方法: Schedule Metadata、Timezone、Value Constructor、Actor Providerを修正してからdatabase:migrate、build:compile、one-shot実行を順に行います。Exit 0の評価結果だけを成功とし、Credentialや未検証ActorをFallbackしません。
Scheduled Occurrenceがaccepted/claimedのまま
症状: operation:schedule:run --jsonはacceptedを返したのに、OccurrenceまたはOperationが完了しません。
考えられる原因: Deferred Operationのworker:runが起動していない、Workerが別Database/Schemaを見ている、またはLease/Heartbeatの期限切れからRecovery待ちです。
確認方法: Operation IDを変更せず、Occurrenceの安全な列とJournalの同じOperation IDを照合します。
php blackops worker:run --iterations=1 --idle-sleep-milliseconds=1
php blackops operation:inspect <operation-id> --json
operation:schedule:run --jsonは件数だけを返し、Operation IDを出力しません。上のRead-only Occurrence Queryでoperation_idを得て、<operation-id>へ置き換えます。acceptedはDurable受理であり完了ではありません。Lease Recovery後も同じOperation IDを使い、外部副作用はExactly Onceと解釈しません。
修正方法: Deferred Workerを同じDatabase/Schemaへ接続し、LeaseとHeartbeatの設定を確認してからworker:runを再実行します。Recovery後は同じOperation IDのTerminal Eventを確認し、外部副作用を重複実行しないApplication冪等性を保ちます。
Scheduled Occurrenceがskipped_misfire/skipped_overlapになる
症状: JSONのskipped_misfireまたはskipped_overlapが増え、Operation IDがありません。
考えられる原因: Cursorより後から現在のUTC Calendar Minuteまでに一致するSlotが複数あり、最新Slot以外がskipped_misfireになった、または同じScheduleの前回Occurrenceが実行中で最新Slotがskipped_overlapになった状態です。どちらもRunnable Operationを作らない安全なSkipです。
確認方法: schedule_name、UTCのscheduled_at、evaluated_at、state、categoryだけを確認します。
Project Rootで、Framework SchemaのRead-only Queryとして実行します。SkeletonのDocker環境ではPostgreSQLへ次のように接続してからSQLを貼り付けます。
docker compose exec -T postgres psql -U blackops -d blackops
別環境ではApplicationのFramework Connection/Schemaへ接続するRead-only PostgreSQL Clientを使い、blackopsはApplication Configurationに合わせて置き換えます。CredentialをCommand例へ直書きしません。正本のQueryはOccurrenceとJournalを安全に確認するにもあります。
SELECT schedule_name, scheduled_at, evaluated_at, state, category
FROM blackops.schedule_occurrences
WHERE schedule_name = 'reports.daily'
ORDER BY scheduled_at DESC
LIMIT 20;
Cron/Timezoneと外部Supervisorの重複起動を見直します。SkipへOperation IDを補って再実行したり、Occurrenceを直接更新したりしません。
修正方法: UTC Cursor、Misfire Window、ScheduleのOverlap設定、外部Supervisorの多重起動を修正し、次の評価Slotをone-shotで確認します。Skip済みOccurrenceを直接Completedへ変更しません。
IDのない500はOperation成立前のBootstrap/Middleware/Protocol境界の失敗です。operation:inspectでは追跡できないため、Credentialを除いたFramework Error Log、Config Validation、Build Artifact、Database Connectionを確認します。
InspectのExit CodeはInvalid ID=2、Unavailable=3、Storage/Decode/Integrity=4です。--jsonのErrorは{"schemaVersion":1,"status":"error","code":"..."}をstderrへ出します。
Local Viewerが起動/表示できない
症状: viewer.disabled、viewer.invalid_configuration、viewer.runtime_unavailable、viewer.bind_failed、またはBrowserで404が返ります。
考えられる原因: Diagnostics Enable Gateが無効、Loopback以外のBind、Port競合、PCNTL未提供、Bootstrap URLの期限切れ、またはHost/Cookie境界が異なります。
確認方法: config/diagnostics.phpのenabled、127.0.0.1、Port競合、CLI RuntimeのPCNTLを確認します。QuickstartはLocalだけEnabledです。
修正方法: Viewerをphp blackops operation:viewerで明示起動し、その起動で一度だけ出るBootstrap URLへ同じLocal Runtimeからアクセスします。Tokenがない、古いTokenを使う、Session Cookieを捨てる、Host Headerが異なる場合の404はFail-closed動作です。Non-loopback Bindへ変更せず、別のLocal Portへ変える場合はConfigと接続先を同期します。POSTは405で、GET/HEADだけが正常です。
Migration未適用/PostgreSQL接続失敗
症状: HTTP、Worker、Outcome、Retention CommandがTable不在またはPostgreSQL接続Errorで失敗します。
考えられる原因: Migrationを明示適用していない、config/database.phpのHost/Port/Database/UserがProcessごとに異なる、またはPostgreSQLが起動していません。
確認方法: 接続先をSecretなしで確認し、Read-only Statusを実行します。
php blackops database:status --no-interaction
docker compose ps postgres
修正方法: PostgreSQLを起動し、正しいCredentialをEnvironmentから渡してMigrationを適用します。
php blackops database:migrate --dry-run
php blackops database:migrate
HTTP/Worker起動時の暗黙Migrationに頼りません。
StorageKeyProviderが未登録
症状: HTTP、Worker、Console、Scheduled、Outbox、Status/Data Query、またはRotationのRuntime compositionがStorage Protectionを解決できず、安全なConfiguration/Storage Protection Errorで停止します。build:compileはStorageKeyProviderの必須Runtime検査を行わないため、Providerが未登録でも成功する場合があります。
考えられる原因: Application Service ProviderでStorageKeyProvider::classのBindingがない、config/app.phpのservicesへProviderを登録していない、またはProvider ConstructorがContainerで解決できない状態です。
確認方法: 2. Key ProviderをApplicationへ登録するとApplication Commandを照合します。Storage Protectionを解決するRuntime composition(HTTP、Worker、Console等)を実行し、該当SurfaceのSafe Error/Responseだけを確認します。build:compileの成功だけではProvider登録を証明しません。Key Material、Credential、Constructor引数、Throwableを出力せず、build:compileには--json Optionがないことにも注意してください。
修正方法: Application-owned StorageKeyProvider、OperationDataReadAuthorizer、OperationStatusAuthorizer、必要なTenant ProviderをServiceRegistry::autowire()で登録し、config/app.phpへService Providerを追加してからBuildし、HTTP/Worker/Console等のRuntime compositionを再実行します。該当SurfaceのSource/Testで定義されたSafe Error/Responseを期待し、Keyをset()、Config、Manifest、Artifact、Logへ置かないでください。
Unknown Key/Tag Tamper
症状: 認可済みのJournal/Outcome Read、Worker、またはstorage:protection:rotate --confirmがEnvelope Integrityを検証できず、安全な固定Failureへ縮約します。Rotation ConfirmのStorage/Protection FailureはExit 1、Plan/RotateのInput/Configuration FailureはExit 2です。Journal/Outcome Queryはoperation_journal.protection_failedまたはoperation_outcome.protection_failed等のPublic Safe Codeを返します。storage:protection:planはHeader/Clear MetadataのRead-only集計であり、Unknown Key/Tag Tamperを検出しません。
考えられる原因: BOPD HeaderのKey IDをProviderが解決できない、旧KeyのRead期間が終了している、AADのPurpose/Row/Operation/Tenant Scopeが異なる、またはCiphertext/Tagが改変されています。Malformed Headerも同じRuntime Protection Failureへ縮約されます。
確認方法: 認可済みApplicationのOperationJournalQuery::records()またはOperationOutcomeQuery::find()、Worker処理、Rotation Confirmを実際に実行し、該当Surfaceの固定Safe Code/Exitだけを確認します。Read-onlyの5. DatabaseにRaw Valueがないことを確認するのBOPD Prefix countとoperation:inspectはClear lifecycle列のDiagnosticsであり、Envelope Integrity検査やTamper確認ではありません。FingerprintはRotation Auditの失敗記録だけを照合します。Envelope、Nonce、Tag、Key Material、Payload、Tenant Raw IDをSELECT、Log、Ticketへ出しません。
修正方法: Unknown Keyなら旧Keyを削除せずRead可能なProviderへ一時的に復旧し、同じPurpose/Tenant ScopeでPlan(Read-only)を実行します。Tag TamperやAAD不一致はEnvelopeを手動編集せず、原因を隔離して承認済みBackup/Offline変換の手順へ戻します。RuntimeのPlaintext Fallbackは行いません。
非空の旧Protected SchemaでMigrationが停止
症状: database:migrateが変更前のSafe Migration Errorで停止し、Shellへ非ゼロ終了を返します。正確なTop-level終了値はApplicationのConsole Error境界で確認してください。旧保護対象Tableが非空でも、MigrationはRow内容を検査しません。
考えられる原因: 既存の旧Protected SchemaにRowが残っており、Experimental v1の新しいEnvelope/Tenant契約へ自動変換できないためです。非空判定は安全停止のためだけに使われます。
確認方法: Read-onlyのMigration StatusとTableの空/非空件数だけを確認します。database:migrate --dry-runを使い、Payload、Plaintext、Envelope Header、Key Material、Rowの内容をSELECTしません。database:migrateには--no-interaction Optionはありません。空Tableは0件、非空Tableは停止対象です。
修正方法: 新規ApplicationではDatabaseをReset/RecreateしてからMigrationし、必要なDataはApplicationが承認したFramework外のOffline変換で別途移行します。既存Tableを直接ALTER、暗黙変換、削除して再実行しないでください。完了後にdatabase:migrate、build:compileを順に実行します。
Rotationのremainingが0にならない
症状: Confirm済みRotationはExit Code 0でもremainingが残る、または一部Rowがfailed/skippedになります。Protection/Runtime FailureならExit Codeは1、Input/Confirm/Config Errorなら2です。
考えられる原因: Purpose、Tenant Pair、Old Key ID、Checkpointが一致していない、CAS競合でSkipされた、失敗Auditを修復していない、またはDatabase以外のReplica、Backup、Dead Letter、Retention Windowに旧Keyが残っています。
確認方法: 同じScopeでstorage:protection:plan --jsonを再実行し、purpose、Tenant Scope、Key ID、Checkpoint、remaining、failedだけを照合します。Read-only Planは全範囲を集計しますが、Payload、Nonce、Tag、Tenant Raw ID、Key Materialを出しません。失敗FingerprintとCheckpointを安全なAuditから確認します。
修正方法: 旧KeyをRead可能なままにし、同じScope/Checkpointでstorage:protection:rotate --confirm --jsonを再開します。Failed Auditを原因修復後に再処理し、CAS Skipは再Planで残件を確認します。Databaseのremaining=0を確認した後もReplica、Backup、Dead Letter、Retention Windowを別途確認し、すべての境界が完了するまで旧Keyを削除しません。
journal.jsonlへ出力されない
症状: Operationは完了しますが、var/log/journal.jsonlが存在しない、またはRecordが増えません。
考えられる原因: config/journal.phpでenabledがfalse、Pathが相対Path、Parent Directoryがない/書込不能、またはbest_effort Observerの失敗を見落としています。
確認方法: ConfigとDirectory権限を確認します。
php -r '$config = require "config/journal.php"; var_export($config["jsonl"] ?? null); echo PHP_EOL;'
test -d var/log && test -w var/log && printf 'journal directory is writable\n'
修正方法: enabled=true、既存の書込可能な絶対Path、best_effortまたはrequiredを設定します。FrameworkはDirectoryを作らないため、Deploy/Setup工程でParent Directoryを準備します。
Local OpenTelemetry Collector/Traceが届かない
症状: Local Collectorは起動するが、Trace/MetricがLogへ現れない、または停止後にReadinessがFailになります。
考えられる原因: Collectorの起動だけではEmitterがSpan/Metricを作成しません。ApplicationのSDK/OTLP Exporterが未登録、collector:4318/v1/traces//v1/metricsが別Network、Flush/Shutdownが未実行、otel-collector-config.yamlのConfig Mountが不正、またはReadinessへCollector接続を誤って含めています。
確認方法: DockerでLocal Collectorを確認するのProvider/MeterProvider/Health Adapter例を照合し、固定DigestのCollector laneをProject Rootで同じTerminalから再実行します。Host laneの$COLLECTOR確認はread前の同じShellで行い、別Shellの未定義変数を使いません。
docker ps --filter 'name=blackops-otel-' --format '{{.Names}}\t{{.Status}}'
docker logs "$(docker ps --filter 'name=blackops-otel-' --format '{{.Names}}' | head -n 1)" | tail -n 20
この固定Digest laneの自動範囲はCollector Ready、Container/Network Isolation、HealthとCleanupです。ApplicationがEmitterを実行した場合だけ、HTTP→Inline、Deferred→Worker→Retry、Outbox Producer→Relay、Metric、JSONL Correlation、MaskをApplicationの実結果として確認します。docker logsだけを成功根拠にせず、Healthの結果も確認してください。Raw Header、Credential、Payload、Outcome、Provider/Exception Detailを貼り付けません。
修正方法: ApplicationとCollectorを同じLocal Networkへ置き、OTLP HTTP Endpointをhttp://collector:4318へ合わせ、Metric用Endpointを分けてFlush/Shutdownします。Invalid traceparentはRaw Headerを保存せずParentなしで処理します。Provider/Exporter FailureはNo-op/Best-effortへ縮退し、Primary OperationとReadinessを変更しません。CollectorをReadiness Checkから外し、Remote BackendやProduction ComposeへLocal設定をコピーしないでください。
Grafana LGTMでTraceまたはMetricが見つからない
症状: Grafanaは起動するが、TempoのTraceまたはPrometheusの
blackops.operation.durationまたはblackops_operation_duration_seconds(Histogramの
_bucket/_sum/_count)が表示されません。
考えられる原因: Emitter、Collector、Grafana Datasourceが同じNetwork/Endpointを使っていない、またはProviderのFlush/Shutdown前にApplicationが終了しています。
確認方法: Local Grafana LGTMのReadinessを確認するの自動Readiness laneをProject Rootから起動し、Grafana HTTP Health、固定Digest、loopback Port、失敗時Log、Cleanupを確認します。Trace/Metricの保存確認は、同じページのInteractive laneでApplication-owned Emitterを実行した場合だけ行います。Backend Portを直接公開したり、Grafana APIのResponse全体をLogへ貼ったりしません。
修正方法: Interactive laneでだけ、LGTMのNetwork aliasがcollectorであること、Emitterが同じNetworkでOTLP HTTP 4318へ送っていること、ProviderのFlush/Shutdownが完了していることを確認します。CredentialはShellのGRAFANA_PASSWORD(未指定時local-admin)とContainerのGF_SECURITY_ADMIN_PASSWORDを一致させます。Host laneでは127.0.0.1:<random-otlp-port>、Container laneではhttp://collector:4318を使い分けます。Grafanaの停止をReadinessへ追加せず、Remote CredentialやPersistent VolumeをLocal手順へ持ち込みません。Grafana 3000は閲覧Page、4318はIngestion endpointです。
Outcome Status
OutcomeがPending/Not Found/Expiredか判別できない
症状: OperationOutcomeQuery::find()がOperationOutcomeUnavailableになり、処理中、未知のOperation ID、失敗、保持期限切れを区別できません。
考えられる原因: OperationOutcomeQueryはCurrent Actor、Current Tenant、OperationDataPurposeを受けるDefault-deny Queryです。Unknown、Tenant不一致、Deny、Retention削除は同じUnavailableになります。
確認方法: Public OperationStatusQuery、GET /operations/{operationId}、またはGenerated .status()で現在Stateを確認します。
修正方法: Public Status Resultを次のように分類します。PHP AdapterでOutcomeだけが必要な場合も、認可済みOperationOutcomeQueryへ明示的なPurposeを渡します。
| 判定 | Applicationが返す状態 |
|---|---|
| Operationが存在し、非Terminal | Pending |
| CompletedかつOutcomeあり | Completed |
| Rejected/Failed/Dead Letter | Terminal without outcome |
| UnknownまたはDeny | 404 Unavailable |
| Allow済みで期限切れを証明 | 410 Expired |
Persistence PayloadやFramework Table Schemaを利用者向けResponseへ直接公開しません。
Sensitive値がJournalで見えない
症状: #[Sensitive]を付けた値が[masked]、除外、Hashとして表示され、入力値を確認できません。
考えられる原因: Sensitive Projectionが意図どおりObserved Journalへの出力を制限しています。これは不具合ではありません。
確認方法: OperationValueのPropertyに付けた#[Sensitive]とSensitiveModeを確認します。Raw値をLogへ追加して検証しないでください。
修正方法: Debuggingには非Sensitiveな相関ID、Category、安定したError Codeを使います。Raw Secretが業務処理に不要なら保存しません。秘密値の復元が必要な業務要件は、Applicationの暗号化Store、Key管理、Access Control、監査を別途設計します。
FAQ: 202は完了を意味しますか
いいえ。202 AcceptedはDeferred OperationをDurableに受け付けたことだけを意味します。WorkerのAttempt、Retry、Terminal State、Outcomeを同じOperation IDで追跡してください。
FAQ: 失敗をすべてRejectedへ変換できますか
変換しません。予期された業務拒否だけOperationRejectedExceptionを使います。一時障害はRetryableException、BugやInfrastructure Failureは通常のThrowableとしてSupervision/Failure Policyへ渡します。