Quickstart and Skeleton
Repository main PreviewでGenerated Operation Object、Status、有限Wait、Database、Deferred Workerを確認する。
このPageはRepository mainのPreview Applicationを準備し、Header Authentication、Inline HTTP、Database Transaction、After Commit、Deferred Workerを一続きで確認します。Stable 1.1.0のInstallはInstallで先に完了してください。ここで説明するAuthentication/AuthorizationとDatabase/Transaction Exampleは未Release Surfaceです。Experimental Stable 1.1.0との差はReleasesで確認してください。
1. 実行Channelを選ぶ
Stable 1.1.0
公開済みSkeletonだけを試す場合はVersionを固定し、First OperationのStep 1〜3へ進みます。
composer create-project blackops/skeleton my-app 1.1.0
StableにはGlobal Middleware、Authentication、#[Authorize]がないためFrontend Operation Bridgeもありません。このPageのmain Preview Step 2以降へ進まず、InstallのHTTP 200確認後にFirst OperationのStep 1〜3(Generator、Value、Outcome)を実行してください。Stableの正確な提供範囲はStableとmainで確認してください。
Repository main Preview
以降の認証付きJourneyには、RepositoryのFramework SourceとQuickstartをLocal Path Repositoryで組み合わせたPreviewを使います。これはConsumer E2Eと同じsymlink: false/Version Mappingです。公開VersionのInstall手順ではありません。
git clone https://github.com/kubotak-is/blackops.git blackops-framework
cd blackops-framework
PREVIEW_DIR="$(pwd)/../blackops-preview"
mkdir -p "$PREVIEW_DIR"
cp -a examples/quickstart/. "$PREVIEW_DIR/"
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PWD:/framework:ro" -v "$PREVIEW_DIR:/app" -w /app composer:2 \
composer config repositories.framework \
'{"type":"path","url":"/framework","options":{"symlink":false,"versions":{"blackops/framework":"1.1.0"}}}'
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PWD:/framework:ro" -v "$PREVIEW_DIR:/app" -w /app composer:2 \
composer install --no-interaction --prefer-dist
cd "$PREVIEW_DIR"
cp .env.example .env
mkdir -p var/build var/log
SAMPLE_API_TOKEN=local-exampleは.env.exampleからLocal Previewへ入ります。未設定または空文字の場合、Sample Authenticatorは既知値へFallbackせずRuntime構成を失敗させます。以降はblackops-previewをProject Rootとして実行します。
接続できない、またはContainerの状態が分からない場合はTroubleshootingを参照してください。
2. Image、Artifact、Databaseを準備する
docker compose build app http
pnpm install --frozen-lockfile
docker compose run --rm app php blackops database:migrate
docker compose run --rm app php blackops build:compile
docker compose run --rm app php blackops database:seed
docker compose run --rm app php blackops frontend:generate
docker compose run --rm app php blackops frontend:check
pnpm test
docker compose up -d
BuildはSourceからOperation/HTTP/Frontend Contract ManifestとDI Containerを生成します。frontend:generateはContractからresources/js/blackops/へTypeScript ESMを生成し、frontend:checkは現在のContractとPath/Bytesが一致するかを非破壊で確認します。pnpm testはDOMなしStrict TypeScriptでGenerated SourceとApplication-owned Consumer Sourceを検査します。
Migration、Build、Seed、Frontend生成はHTTP起動時に暗黙実行されません。Install直後の空Root Seederも明示的に実行し、database:migrate -> build:compile -> database:seedのDeployment順序を確認します。docker compose up -dはHealthyなPostgreSQLとWorker Mode HTTPだけを起動し、Deferred WorkerやSchedulerを勝手に常駐させません。Frontendを使わないApplicationはpnpm以降のFrontend Stepを省略できます。Classic Modeはclassic-mode Profileの明示Fallbackです。
3. PHP Operationを先に確認する
Generated ClientはPHP OperationのContractを複製しません。Previewへ含まれるexamples/quickstartの実装を先に確認します。app/Feature/Welcome/ShowWelcome/ShowWelcome.phpはGET /welcome、Operation Type welcome.showを持つInline Operationです。app/Feature/Welcome/ShowWelcome/WelcomeValue.phpは空のInput、WelcomeShown.phpはmessage Outcomeを返します。
use App\Security\SampleUserAuthorizationPolicy;
use BlackOps\Core\Attribute\Authorize;
#[Route(method: 'GET', path: '/welcome')]
#[OperationType('welcome.show')]
#[Authorize(SampleUserAuthorizationPolicy::class)]
final readonly class ShowWelcome implements Operation
{
public function handle(WelcomeValue $value): WelcomeShown
{
return new WelcomeShown('Welcome to BlackOps');
}
}
これはRepository main Previewの/welcomeです。Stable Skeletonの/welcomeは匿名で、#[Authorize]とSample Token Headerを含みません。
final readonly class WelcomeValue implements OperationValue {}
final readonly class WelcomeShown implements Outcome
{
public function __construct(public string $message) {}
}
app/Feature/Report/GenerateReport/GenerateReport.phpはPOST /reports、Operation Type report.generateを持つDeferred Operationです。GenerateReportValueのreportNameとwrite-only recipientEmailから、ReportGeneratedのreportNameとlocationを返します。
4. Generated Operation Objectから呼ぶ
PHP Operationを手書きでTypeScriptへ複製しません。まずProject Rootのtests/Frontend/try-client.tsへ、生成RootとNode 24のglobal fetchを使う最小Clientを書きます。追加Dependencyは不要です。
import { createBlackOpsClient } from '../../resources/js/blackops';
const blackops = createBlackOpsClient({
baseUrl: 'http://127.0.0.1:8080',
fetch,
headers: { 'X-Sample-Token': 'local-example' },
});
async function main(): Promise<void> {
const response = await blackops.ShowWelcome.fetch({});
if (response.ok && response.kind === 'completed') {
console.log(response.status, response.data);
return;
}
throw new Error('Welcome request did not complete.');
}
main().catch(() => {
throw new Error('try-client failed.');
});
HTTPを起動したProject Rootで実行します。
pnpm exec tsc --ignoreConfig --ignoreDeprecations 6.0 --strict --target ES2022 \
--module CommonJS --moduleResolution Node10 --lib ES2022,DOM --rootDir . \
--outDir .build tests/Frontend/try-client.ts
node tests/Frontend/write-runtime-package.mjs
node .build/tests/Frontend/try-client.js
期待結果は200 { message: 'Welcome to BlackOps' }です。SvelteKitではServer-side Requestの補足としてevent.fetchを渡せますが、汎用JavaScriptの前提にはしません。
生成RootからcreateBlackOpsClient()をImportし、Server RequestごとにBase URL、Fetch、Credentialを一度Bindingします。
import {
createBlackOpsClient,
} from './resources/js/blackops';
const blackops = createBlackOpsClient({
baseUrl: 'http://127.0.0.1:8080',
fetch: event.fetch,
headers: { 'X-Sample-Token': 'local-example' },
credentials: 'same-origin',
});
このFactoryはServer-only Moduleで作ります。Browser BundleへPrivate Base URLやCredentialを含めず、ApplicationはSessionから安全なHeaderだけを組み立てます。
まず.url()へURLに必要なInputを渡します。Quickstartの4 OperationはPath/Query Parameterがないため引数は不要です。
const url = blackops.ShowWelcome.url();
/welcome
.toRequest()は送信せず、Inputと呼出単位のCredentialからRequestを作ります。SensitiveなrecipientEmailは送信するWrite-only Inputですが、Generated SourceやResultへ値を埋め込みません。
const request = blackops.GenerateReport.toRequest(
{
reportName: 'weekly',
recipientEmail: 'reports@example.com',
},
);
{
"url": "http://127.0.0.1:8080/reports",
"method": "POST",
"headers": {
"X-Sample-Token": "local-example",
"Content-Type": "application/json"
},
"body": "{\"reportName\":\"weekly\",\"recipientEmail\":\"reports@example.com\"}"
}
.fetch()は同じBindingで実HTTPへ送り、okとkindで判別できるResultを返します。入力と出力は次の対になります。Operation IDと時刻は実行ごとに変わります。
const welcome = await blackops.ShowWelcome.fetch({});
{"ok":true,"kind":"completed","status":200,"data":{"message":"Welcome to BlackOps"}}
const report = await blackops.GenerateReport.fetch(
{ reportName: 'weekly', recipientEmail: 'reports@example.com' },
{ idempotencyKey: 'report-weekly-001' },
);
{"ok":true,"kind":"accepted","status":202,"data":{"operationId":"019f32ab-2be0-7b38-a0a7-1ab2f9687697","acceptedAt":"2026-07-14T01:23:45.678901Z"}}
const validation = await blackops.GenerateReport.fetch(
{ reportName: '', recipientEmail: 'reports@example.com' },
);
{"ok":false,"kind":"validation","status":422,"error":{"code":"validation.failed","operationId":"019f32ab-2be0-7b38-a0a7-1ab2f9687698","violations":[{"field":"reportName","rule":"not_blank","code":"validation.not_blank"}]}}
const order = await blackops.CreateOrder.fetch(
{ reference: 'order-frontend-001' },
{ idempotencyKey: 'order-frontend-001' },
);
{"ok":true,"kind":"completed","status":200,"data":{"reference":"order-frontend-001","status":"created"}}
const internal = await blackops.TriggerFailure.fetch(
{ reference: 'incident-frontend-001', sensitiveNote: 'private note' },
);
{"ok":false,"kind":"internal","status":500,"error":{"code":"internal_error","operationId":"019f76f1-3fdc-7c18-9d62-b182d42df100"}}
Network Errorや到達不能はHTTP Statusを捏造せずTransport Resultになります。Thrown ErrorのMessageやRaw BodyはResultへ含みません。
const unavailable = createBlackOpsClient({
baseUrl: 'http://127.0.0.1:8080',
fetch: async () => {
throw new Error('connection detail must stay private');
},
});
const transport = await unavailable.ShowWelcome.fetch({});
{"ok":false,"kind":"transport","status":null,"error":{"code":"network_error"}}
MetadataはReadonly Literalとして同じObjectから参照できます。
blackops.ShowWelcome.type; // 'welcome.show'
blackops.ShowWelcome.method; // 'GET'
blackops.ShowWelcome.path; // '/welcome'
blackops.ShowWelcome.strategy; // 'inline'
Generated ObjectはCallable/Thenableではありません。通信は.fetch()、一回の状態取得は.status()、有限待機は.wait()、Request参照は.toRequest()、URL参照は.url()と明示します。.fetch()は202後に自動Pollingしません。
202で得たOperation IDを使い、現在状態を一回だけ取得します。
const current = await blackops.GenerateReport.status(report.data.operationId);
{"ok":true,"kind":"accepted","status":200,"data":{"schemaVersion":1,"operationId":"019f32ab-2be0-7b38-a0a7-1ab2f9687697","operationType":"report.generate","state":"accepted"},"retryAfterSeconds":1}
BrowserでTerminal Stateまで待つ場合はnative AbortControllerと有限Deadlineを渡します。
const controller = new AbortController();
const terminal = await blackops.GenerateReport.wait(report.data.operationId, {
signal: controller.signal,
maxWaitMilliseconds: 15_000,
});
if (terminal.ok && terminal.kind === 'completed') {
terminal.data.outcome.reportName;
terminal.data.operationId;
terminal.data.outcome.location;
}
{"ok":true,"kind":"completed","status":200,"data":{"schemaVersion":1,"operationId":"019f32ab-2be0-7b38-a0a7-1ab2f9687697","operationType":"report.generate","state":"completed","outcome":{"reportName":"weekly","location":"/reports/generated/weekly.json"}}}
Worker未起動の別Operationへ短いDeadlineを指定すると、無限に待たずpoll_timeoutで停止します。これはOperationのCancelではなく、Workerは後から同じOperationを処理できます。
const timedOut = await blackops.GenerateReport.wait(otherOperationId, {
signal: new AbortController().signal,
maxWaitMilliseconds: 150,
});
{"ok":false,"kind":"transport","status":null,"error":{"code":"poll_timeout"}}
5. Inline Operationをcurlで呼ぶ
curl -sS -H 'X-Sample-Token: local-example' http://127.0.0.1:8080/welcome
{"message":"Welcome to BlackOps"}
GET /welcomeはRequest内でShowWelcome::handle()を実行し、Typed OutcomeをHTTP 200へ変換します。
X-Sample-Tokenを省略するとOperation ID付き401、不正値を送るとOperation IDなし401になります。Header Credential自体はOperation ValueとJournalへ入りません。
curl -i http://127.0.0.1:8080/welcome
curl -i -H 'X-Sample-Token: invalid' http://127.0.0.1:8080/welcome
6. 失敗をOperation IDで調べる
Quickstartのdiagnostics.failure.triggerは、認証済みInline Operationを意図的に失敗させるLocal Exampleです。入力のreferenceはApplication Logの相関用、sensitiveNoteはSafe SurfaceのMask確認用です。
curl -sS -X POST -H 'Content-Type: application/json' \
-H 'X-Sample-Token: local-example' \
-d '{"reference":"incident-demo-001","sensitiveNote":"private diagnostic note"}' \
http://127.0.0.1:8080/failures
{"status":"error","code":"internal_error","operationId":"019f76f1-3fdc-7c18-9d62-b182d42df100"}
HTTPはException MessageやSensitive Noteを返さず、調査の入口となるUUIDv7 Operation IDを返します。以下のIDと時刻はExampleであり、実行ごとに変わります。
Human形式でLifecycleを読みます。
docker compose run --rm app php blackops operation:inspect \
019f76f1-3fdc-7c18-9d62-b182d42df100
Operation
ID: 019f76f1-3fdc-7c18-9d62-b182d42df100
Type: diagnostics.failure.trigger
Strategy: inline
Schema Version: 1
Correlation ID: 019f76f1-3fdc-7c18-9d62-b182d42df100
Causation ID: none
State
Current: failed
Terminal: yes
Authority Source: journal
Availability
Transport Payload: not_applicable
Journal: available
Outcome: not_applicable
Dead Letter: not_applicable
Actors
Origin: [masked] (user)
Authorization: [masked] (user)
Execution: [masked] (user)
Timeline
#1 2026-07-18T20:35:49.867594Z operation.received | Attempt: none | Data: {"reference":"incident-demo-001","sensitiveNote":"[masked]"}
#2 2026-07-18T20:35:49.869481Z attempt.started | Attempt: 019f76f1-3fed-76f4-bf86-338242667b74 (#1) | Data: {}
#3 2026-07-18T20:35:49.872029Z attempt.failed | Attempt: 019f76f1-3fed-76f4-bf86-338242667b74 (#1) | Data: {"errorType":"RuntimeException","retryable":false}
#4 2026-07-18T20:35:49.873693Z operation.failed | Attempt: 019f76f1-3fed-76f4-bf86-338242667b74 (#1) | Data: {"errorType":"RuntimeException","retryable":false}
Attempts
#1 019f76f1-3fed-76f4-bf86-338242667b74 | Started At: 2026-07-18T20:35:49.869481Z | Sequences: 2, 3, 4
Outcome
Availability: not_applicable
Value: none
ScriptやSupport Toolから読む場合は同じIDをJSONで取得します。
docker compose run --rm app php blackops operation:inspect \
019f76f1-3fdc-7c18-9d62-b182d42df100 --json
{
"schemaVersion": 1,
"status": "found",
"operation": {
"operationId": "019f76f1-3fdc-7c18-9d62-b182d42df100",
"type": "diagnostics.failure.trigger",
"schemaVersion": 1,
"strategy": "inline",
"correlationId": "019f76f1-3fdc-7c18-9d62-b182d42df100",
"causationId": null,
"actors": {
"origin": {"id": "[masked]", "type": "user"},
"authorization": {"id": "[masked]", "type": "user"},
"execution": {"id": "[masked]", "type": "user"}
}
},
"state": {"current": "failed", "terminal": true, "source": "journal"},
"availability": {
"transportPayload": "not_applicable",
"journal": "available",
"outcome": "not_applicable",
"deadLetter": "not_applicable"
},
"timeline": [
{"sequence": 1, "event": "operation.received", "occurredAt": "2026-07-18T20:35:49.867594Z", "attemptId": null, "attemptNumber": null, "data": {"reference": "incident-demo-001", "sensitiveNote": "[masked]"}},
{"sequence": 2, "event": "attempt.started", "occurredAt": "2026-07-18T20:35:49.869481Z", "attemptId": "019f76f1-3fed-76f4-bf86-338242667b74", "attemptNumber": 1, "data": {}},
{"sequence": 3, "event": "attempt.failed", "occurredAt": "2026-07-18T20:35:49.872029Z", "attemptId": "019f76f1-3fed-76f4-bf86-338242667b74", "attemptNumber": 1, "data": {"errorType": "RuntimeException", "retryable": false}},
{"sequence": 4, "event": "operation.failed", "occurredAt": "2026-07-18T20:35:49.873693Z", "attemptId": "019f76f1-3fed-76f4-bf86-338242667b74", "attemptNumber": 1, "data": {"errorType": "RuntimeException", "retryable": false}}
],
"attempts": [{"attemptId": "019f76f1-3fed-76f4-bf86-338242667b74", "number": 1, "startedAt": "2026-07-18T20:35:49.869481Z", "events": [2, 3, 4]}],
"outcome": null
}
Docker-only Quickstartでは、Host BrowserからLocal Viewerを利用できません。PostgreSQLをHostへPublishしておらず、ApplicationのPOSTGRES_HOST=postgresはCompose Network内だけで解決します。また、Viewerは起動したCLI Processの127.0.0.1だけにBindするため、Containerの外から到達できません。この構成では、前述のdocker compose run --rm app php blackops operation:inspect ... Human/JSONを利用してください。Non-loopback Bindへ緩めて回避してはいけません。
Consumer E2EはViewerとHTTP Clientを同じnamed CLI Container、同じLocal Network Namespaceで動かして、Loopback限定のままToken/Session/Read-only動作を検証します。これはHost BrowserへViewerを公開する手順ではありません。
BrowserでViewerを使う場合は、Application/PHP CLI/PostgreSQL/Browserが同じLocal Network Namespaceから相互に到達できるNative Runtimeを準備します。Database HostもそのNative Runtimeから解決可能であることを確認したうえで、Project Rootから明示起動します。
php blackops operation:viewer
http://127.0.0.1:8082/?token=<one-time-bootstrap-token>
このURLを一度開くとSession Cookieへ交換し、/operations/019f76f1-3fdc-7c18-9d62-b182d42df100でHuman表示と同じFailed State、Timeline、Attempt、Mask済みValue/Actorを表示します。Tokenなしは404、POSTは405です。Bootstrap URLを貼り付けたり保存したりせず、調査後はProcessを終了します。
var/log/application.jsonlにはApplication RecordとFramework Failure Recordが同じOperation/Attempt/Correlation IDで残ります。referenceはApplication Contextにありますが、sensitiveNote、Exception Message、Credential、Raw Actor IDはありません。Canonical JournalはRestricted Dataのため、DatabaseのAccess Control、Encryption、RetentionをApplicationで管理してください。
7. Transactional OperationでOrderを作る
Install直後のOrder FeatureはRepository、Transactional Command、Transactional Operation、After Commit Serviceの関係を実行可能な形で示します。まずInputを送ります。
curl -sS -X POST -H 'Content-Type: application/json' \
-H 'X-Sample-Token: local-example' \
-d '{"reference":"order-001"}' \
http://127.0.0.1:8080/orders
HTTP 200と次のTyped Outcomeが返ります。
{"reference":"order-001","status":"created"}
CreateOrder::handle()の#[Transactional]が最外Transactionを開きます。CreateOrderCommand::execute()も#[Transactional]ですが、同じDefault Connectionなので新しいDBAL Transactionを作らずNested Requiredで参加します。DoctrineOrderRepositoryはConstructor InjectionされたDefault DBAL ConnectionでParameterized SQLを実行します。
Business RowとAfter Commit Rowを入力と対にして確認します。
docker compose exec -T postgres psql -U blackops -d blackops -Atc "
SELECT reference FROM quickstart_orders WHERE reference = 'order-001';
SELECT reference FROM quickstart_order_commits WHERE reference = 'order-001';
"
order-001
order-001
RecordOrderCommit::record()はTransaction内で呼び出しますが、#[AfterCommit]により最外Commit後までQueueされます。そのため、二つ目のRowはOrder Rowと成功Terminal JournalのCommit後に追加されます。
Canonical JournalのTerminalも確認できます。
docker compose exec -T postgres psql -U blackops -d blackops -Atc "
SELECT event
FROM blackops.journal
WHERE operation_id = (
SELECT operation_id
FROM blackops.journal
WHERE event = 'operation.received'
AND convert_from(encoded_record, 'UTF8') LIKE '%order-001%'
LIMIT 1
)
ORDER BY sequence;
"
operation.received
attempt.started
attempt.succeeded
operation.completed
After Commitは同期Best-effortで、Callback失敗やProcess Crashを越えた自動Retryを行いません。Email、Webhook、Message Publishなどのat-least-once DeliveryにはTransactional Outboxを使い、Relayの停止/再開、Retry、Dead Letter再開を明示的に運用します。詳しい保証差はTransactionを参照してください。
8. Deferred Operationを受け付ける
curl -sS -X POST -H 'Content-Type: application/json' \
-H 'X-Sample-Token: local-example' \
-d '{"reportName":"weekly","recipientEmail":"reports@example.com"}' \
http://127.0.0.1:8080/reports
{"status":"accepted","operationId":"019f32ab-2be0-7b38-a0a7-1ab2f9687697","acceptedAt":"2026-07-14T01:23:45.678901Z"}
HTTP 202はHandler完了ではなく、ValueとContextをPostgreSQLへDurableに保存した合図です。operationIdとacceptedAtは実行ごとに変わります。
同じCredentialでPublic Status Resourceを読むと、Worker未起動中はacceptedと正整数Retry-Afterを返します。
OPERATION_ID='<operation-id-from-accepted-response>'
curl -i -H 'X-Sample-Token: local-example' \
"http://127.0.0.1:8080/operations/${OPERATION_ID}"
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, no-store
Retry-After: 1
{"schemaVersion":1,"operationId":"019f32ab-2be0-7b38-a0a7-1ab2f9687697","operationType":"report.generate","state":"accepted"}
空のreportNameは宣言的Validationで受付前にHTTP 422となります。Inline/DeferredのどちらもValidation Failureを202にせず、Handlerを実行しません。
curl -sS -X POST -H 'Content-Type: application/json' \
-H 'X-Sample-Token: local-example' \
-d '{"reportName":"","recipientEmail":"reports@example.com"}' \
http://127.0.0.1:8080/reports
{"status":"rejected","operationId":"019f32ab-2be0-7b38-a0a7-1ab2f9687698","category":"validation","code":"validation.failed","violations":[{"field":"reportName","rule":"not_blank","code":"validation.not_blank"}]}
9. Workerで完了させる
Sample Reportは一回目のAttemptでRetryを要求し、二回目で成功します。
docker compose run --rm app php blackops worker:run --iterations=1 --idle-sleep-milliseconds=1
sleep 2
docker compose run --rm app php blackops worker:run --iterations=1 --idle-sleep-milliseconds=1
Worker stopped. Processed claims: 0
Worker stopped. Processed claims: 1
Canonical Journalではauthorization Actorがquickstart-userのまま維持され、Worker Eventのexecution Actorだけがquickstart-worker-1/systemになります。Workerは各Attemptで同じPolicyを再評価します。
var/log/journal.jsonlはHTTP ProcessのObserved Projectionです。Inline Welcomeと、HTTP内で完了するValidation Rejection等ではActor IDとrecipientEmailを[masked]にしますが、Worker Eventは追記しません。Valid Deferred Reportの完了をJSONLで待たないでください。Header CredentialはMask対象として保存するのではなく、最初からValue/Transport/Journalへ含めません。
VALIDATION_OPERATION_ID='<operation-id-from-validation-response>'
grep "$VALIDATION_OPERATION_ID" var/log/journal.jsonl
Deferred Reportの受理から完了まではCanonical PostgreSQL Journalで確認します。
OPERATION_ID='<operation-id-from-accepted-response>'
docker compose exec -T postgres psql -U blackops -d blackops -Atc "
SELECT sequence || '|' || event || '|' ||
(convert_from(encoded_record, 'UTF8')::jsonb #>> '{operation,actors,authorization,id}') || '|' ||
(convert_from(encoded_record, 'UTF8')::jsonb #>> '{operation,actors,execution,id}')
FROM blackops.journal
WHERE operation_id = '${OPERATION_ID}'::uuid
ORDER BY sequence;
"
1|operation.received|quickstart-user|quickstart-user
2|operation.accepted|quickstart-user|quickstart-user
3|attempt.started|quickstart-user|quickstart-worker-1
4|attempt.failed|quickstart-user|quickstart-worker-1
5|retry.scheduled|quickstart-user|quickstart-worker-1
6|attempt.started|quickstart-user|quickstart-worker-1
7|attempt.succeeded|quickstart-user|quickstart-worker-1
8|operation.completed|quickstart-user|quickstart-worker-1
Canonical JournalはRaw Business ValueとActor IDを保持する監査正本です。暗号化、Access Control、RetentionをApplication/運用で構成してください。
Worker完了後は同じStatus ResourceがTyped Outcomeを返し、Terminal ResponseにRetry-Afterは付きません。
{"schemaVersion":1,"operationId":"019f32ab-2be0-7b38-a0a7-1ab2f9687697","operationType":"report.generate","state":"completed","outcome":{"reportName":"weekly","location":"/reports/generated/weekly.json"}}
QuickstartのSampleOperationStatusAuthorizerは、Current Actorと受付時のOrigin Actorがともにuserで、ID/Typeが完全一致するときだけAllowします。これはLocal Exampleであり、ProductionのTenant/Role/Resource Policyではありません。Header欠落はAnonymousのためUnknown/Denyと同じ404、不正TokenはSubject読取前の401です。Operation IDはSecretではありませんが、知っているだけでは参照権限を得ません。
PHP AdapterからOutcomeだけを直接読む場合はPublic OutcomeReaderを利用します。Pending、Terminal、Expiredを区別する主経路はStatus Resourceです。詳しくはOutcomeを参照してください。
10. 終了する
docker compose down
次はFirst Operationで、Generatorが作った3 FileへRoute、Value Validation、Deferred Strategyを追加します。起動、Token、Migration、Buildの問題はTroubleshootingへ戻ってください。