blackops/skeleton

Feature-first BlackOps application skeleton.

Maintainers

Package info

github.com/kubotak-is/blackops-skeleton

Type:project

pkg:composer/blackops/skeleton

Transparency log

Statistics

Installs: 25

Dependents: 0

Suggesters: 0

Stars: 0

1.2.0 2026-08-10 09:57 UTC

This package is auto-updated.

Last update: 2026-08-15 14:29:38 UTC


README

Feature-firstのBlackOps Application Skeletonである。Inline GET /welcome、調査用Inline Failure POST /failures、Database Transaction付きPOST /orders、Deferred POST /reports、PostgreSQL 18、FrankenPHP 1、PHP 8.5 CLIを含む。

Distribution Status

このDirectoryはFramework Repository mainのPreview Quickstartであると同時に、Packagist Package blackops/skeletonのSource of Truthである。Release WorkflowがこのDirectoryだけをkubotak-is/blackops-skeletonへSplitし、Frameworkと同じVersionで公開する。公開済みStable 1.1.0にはHeader AuthenticationとPhase 13のDatabase/Transaction Journeyが未収録で、POST /ordersも含まれない。

Local検証ではCommitted QuickstartだけをPackage Rootへ抽出し、SkeletonとFrameworkをsymlink=falseの別々のLocal Repositoryとして通常/--no-scripts Create-projectする。Remote検証は空のComposer HomeからPackagist Packageだけを取得する。

Setup

php bin/setup
docker compose build app http
docker compose run --rm app composer install
pnpm install --frozen-lockfile
docker compose run --rm app php blackops operation:list
docker compose run --rm app php blackops database:status
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

Composer create-projectpost-create-project-cmdから同じbin/setupを実行する。--no-scriptsで作成した場合、またはSetupを明示的に再実行する場合はProject Root内外のどのWorking Directoryからでもphp /path/to/my-app/bin/setupを実行できる。Setupは.envがない場合だけ.env.exampleをCopyし、var/build/var/log/を準備する。既存.envは変更しない。

Quickstartのbootstrap/app.phpはFrameworkのwithEnvironmentFile()でProcess Environmentを優先してOptional .envを一度だけSnapshotします。Classic/Workerのpublic/index.phppublic/worker.phpBlackOps\Http\SapiRuntimeへApplicationを渡すだけです。Environment、PSR-7、SAPI Emit、UUIDv7のRuntime実装はFrameworkが所有するため、Skeleton Composer MetadataへそのRuntime Packageを重複宣言しません。DBAL/MigrationsなどApplicationが実ImportするPackageだけをApplication Direct Dependencyとして追加します。

composer create-project blackops/skeleton my-app 1.1.0
composer create-project --no-scripts blackops/skeleton my-app 1.1.0
php my-app/bin/setup

Framework Repository内のQuickstartへ直接composer installするだけでは、未公開のblackops/framework:^1.2 candidateをPackagistから解決できず、main PreviewのSourceと一致しない。認証付き/welcome/reportsとPhase 13の/ordersを試す場合は、利用者向けQuickstartのRepository main Preview手順でFramework SourceをLocal Path Repositoryとして組み合わせ、version 1.2.0を明示する。準備後のPreview Directoryでphp bin/setupを実行する。

Install、Build、MigrationはImage startupに含まれない。Default docker compose up はHealthyなPostgreSQLとWorker Mode HTTPだけを起動し、Deferred Worker、Scheduler、Migration、Retention Purgeは起動しない。HTTP Portは .envHTTP_PORT で変更でき、既定は8080である。

Setupは次手順を表示するだけで、Composer/pnpm Install、Network Access、Docker、Database、Migration、Artifact Build、Frontend生成、TypeScript Test、Worker、Scheduler、Retentionを実行しない。Backendだけで利用する場合はpnpm、frontend:generatefrontend:checkpnpm testを省略できる。build:compileはBackend Artifactと小さいFrontend Contract Artifactを作るが、TypeScript SourceやNode Dependencyを生成しない。

Frontend Operation Objects

Install直後のSkeletonは、Application所有のconfig/frontend.phppackage.jsonpnpm-lock.yamltsconfig*.jsonresources/js/application/tests/Frontend/を含む。resources/js/blackops/はPHP Operationから明示生成するため配布物へ固定せず、.gitignore対象にする。

pnpm install --frozen-lockfile
docker compose run --rm app php blackops build:compile
docker compose run --rm app php blackops frontend:generate
docker compose run --rm app php blackops frontend:check
pnpm test

frontend:generatefrontend:checkbuild:compileを暗黙実行しない。frontend:checkはFreshならExit 0、Missing/DriftならExit 1、Config/Artifact/Contract不正ならExit 2を返す。生成したModuleはCallableでもThenableでもなく、通信する.fetch()、一回だけ状態を取得する.status()、明示した期限まで待つ.wait()、送信しない.toRequest()、URLを返す.url()、Readonly Metadataを持つOperation Objectである。

import {
  GenerateReport,
  ShowWelcome,
  operationOptions,
} from './resources/js/application/operations';

const options = operationOptions(runtimeToken, 'http://127.0.0.1:8080');

// Input: {}
// URL output: "/welcome"
const welcomeUrl = ShowWelcome.url();

// Input: reportName、write-onlyのrecipientEmail、呼出単位のCredential
// Request output: POST /reports、JSON Body、Content-Type、X-Sample-Token
const request = GenerateReport.toRequest(
  { reportName: 'weekly', recipientEmail },
  options,
);

// Inputは同じ。OutputはHTTP 202を表すaccepted Result。
const result = await GenerateReport.fetch(
  { reportName: 'weekly', recipientEmail },
  options,
);

if (result.ok && result.kind === 'accepted') {
  result.status; // 202
  result.data.operationId;

  const current = await GenerateReport.status(result.data.operationId, options);

  const controller = new AbortController();
  const terminal = await GenerateReport.wait(result.data.operationId, {
    ...options,
    signal: controller.signal,
    maxWaitMilliseconds: 15_000,
  });

  if (terminal.ok && terminal.kind === 'completed') {
    terminal.data.outcome.reportName;
    terminal.data.outcome.location;
  }
}

ShowWelcome.type;     // 'welcome.show'
ShowWelcome.method;   // 'GET'
ShowWelcome.path;     // '/welcome'
ShowWelcome.strategy; // 'inline'

recipientEmailの名前と型は送信に必要なWrite-only Input Contractへ含むが、その実値をGenerated Tree、Result、Log Helperへ埋め込まない。X-Sample-TokenもApplicationが呼出単位で注入する。Generated TypeはAuthentication/Authorization、CORS、CSRF、Encryption、Browser Storageを代替しない。

.fetch()は202後に自動Pollingしない。.status()GET /operations/{operationId}を一回だけ呼び、.wait()だけがServerのRetry-Afterに従って有限に待つ。.wait()には購読可能なAbort Signalと正のmaxWaitMillisecondsが必須である。401、404、410、500、Network Error、不正Responseでは自動Retryせず停止する。

QuickstartはSampleOperationStatusAuthorizerApplicationServiceProviderから明示Bindingする。同じuser Actorが受け付けたOperationだけを同じActorが参照できる最小Same-origin Exampleであり、Tenant/Role/Resourceを扱うProduction Policyではない。ProductionではApplicationが認証方式とStatus参照Policyを実装する。Operation IDは相関KeyでありSecretではないが、IDを知っているだけでは参照できない。

HTTP

curl -H 'X-Sample-Token: local-example' http://127.0.0.1:8080/welcome

curl -X POST -H 'Content-Type: application/json' \
  -H 'X-Sample-Token: local-example' \
  -d '{"reference":"order-001"}' \
  http://127.0.0.1:8080/orders

curl -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

curl -i -H 'X-Sample-Token: local-example' \
  http://127.0.0.1:8080/operations/<operation-id>

curl -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

Failure ResponseのoperationIdはHuman/JSONのどちらでも調べられる。

docker compose run --rm app php blackops operation:inspect <operation-id>
docker compose run --rm app php blackops operation:inspect <operation-id> --json

config/logging.phpはApplication/Framework相関Logをvar/log/application.jsonlへJSONLで出力する。Docker-only QuickstartではPostgreSQLをHostへPublishせず、ViewerもCLI ContainerのLoopback限定なので、Hostからphp blackops operation:viewerを実行したりHost Browserで開いたりできない。Docker利用時は上記Human/JSON Inspectを使う。ViewerはConsumer E2Eと同様にViewer/HTTP Clientを同じCLI ContainerとLocal Network Namespaceへ置く場合だけ検証でき、Host Browser公開の手順ではない。

BrowserでViewerを利用するには、Application/PHP CLI/PostgreSQL/Browserが同じLocal Network Namespaceから到達可能なNative Runtimeを準備し、Project Rootでphp blackops operation:viewerを明示実行する。Non-loopback Bindへ緩めずLoopbackを維持し、起動ごとに変わるBootstrap Tokenを保存/共有しない。

OrderはDefault DBAL ConnectionをConstructor InjectionしたRepositoryを使う。CreateOrder::handle()#[Transactional]が最外Transactionを所有し、Container管理のCreateOrderCommand#[Transactional]で同じConnectionへNested Required参加する。Order Rowと成功Terminal JournalがCommitした後、#[AfterCommit]RecordOrderCommit::record()がCommit確認Rowを追加する。

{"reference":"order-001","status":"created"}

After Commitは同期Best-effortであり、Process Crashを越える再送やDeliveryを保証しない。Email、Webhook、Message Publish等をat-least-onceで配送する場合はTransactional Outboxを使い、Relay停止/再開、Retry、Dead Letter再開を明示的に運用する。外部配送のExactly Onceは保証しない。

X-Sample-TokenはAuthentication Middlewareだけが読み、Operation Value、Transport、Journalへ保存しない。SAMPLE_API_TOKENが未設定または空なら、Authenticatorは既知値へFallbackせず構成を失敗させる。

ReportのrecipientEmailは業務上のSensitive値の例である。HTTP内で完了するValidation RejectionのObserved Projectionではvar/log/journal.jsonl[masked]として記録する。Valid Deferred ReportのWorker EventはJSONLへ転送せず、Raw ValueとActor IDを含むCanonical PostgreSQL Journalが正本となる。既定Observer Deliveryはbest_effortである。

このHeader Token認証はLocal Development専用のExampleである。ProductionではApplicationがSession、Bearer Token、External IdP等の認証方式とSecret管理へ置き換える。

Headerを省略するとAuthentication MiddlewareはAnonymousとして通過させ、#[Authorize]を持つOperationがOperation ID付き401でRejectする。不正なHeader値はOperation受付前の401となり、Operation IDとJournalを作らない。

curl -i http://127.0.0.1:8080/welcome
curl -i -H 'X-Sample-Token: invalid' http://127.0.0.1:8080/welcome

FrankenPHP Worker Mode

Default HTTPはWorker Modeであり、Process単位でApplication、Environment、Configuration、Compile済みRuntimeを一度だけ構成する。

docker compose up -d http
curl -H 'X-Sample-Token: local-example' http://127.0.0.1:8080/welcome

FRANKENPHP_MAX_REQUESTSはWorker Threadを安全に再起動するRequest上限で、既定は1000である。Frameworkは各Request前にDatabase Connectionをhealth-checkし、Stale Connectionをcloseして一度再接続する。再接続できない場合は500として失敗し、成功Responseとして扱わない。Request終了時はOperation Scopeを検査し、JSONL Observerをflushする。Throwableまたは未完了TransactionのConnectionは次Requestへ持ち越さずcloseする。

FrankenPHPはWorker callback終了後にRequest Superglobalをcleanupするが、$_ENVはRequest間でresetしない。EntrypointはProcess開始時の$_ENVへ毎Request復元する。Application ServiceはRequest Body、Actor、Tenant、PSR-7 Request等のRequest固有Stateをpropertyやstaticへ保持せず、Operation ValueまたはExecution Contextから受け取る。

Classic Modeは明示Fallbackとしてclassic-mode Profileから起動できる。既定Portは8081で、CLASSIC_HTTP_PORTから変更する。

docker compose --profile classic-mode up -d http-classic
curl -H 'X-Sample-Token: local-example' http://127.0.0.1:8081/welcome

Worker and Maintenance

docker compose run --rm app php blackops worker:run --iterations=1
docker compose --profile worker up worker
docker compose run --rm app php blackops retention:plan
docker compose run --rm app php blackops retention:purge --dry-run
docker compose --profile maintenance up scheduler

Sample Report Operationは最初のAttemptでRetryを要求し、次のAttemptで成功する。変更を適用するPurgeは --confirm を明示した場合だけ実行する。

Removing Starter Features

Welcomeは app/Feature/Welcome/、Reportは app/Feature/Report/、Local Failure Exampleはapp/Feature/Diagnostics/を削除するだけでよい。Orderを削除する場合はapp/Feature/Order/migrations/Version20260718000000.phpを削除し、ApplicationServiceProviderからOrder用の3 Bindingを外す。config/operations.php のDiscovery Rootへ追加したOperationは次のBuildで検出される。

Operation自身に handle(OperationValue): Outcome、またはAttempt等の実行情報が必要なら handle(OperationValue, ExecutionContext): Outcome を定義するTyped Self-handledが標準形である。ValueとOutcomeはSignatureから推論され、#[Accepts]#[Returns]OperationResult::completed()は不要である。予期された業務拒否はFrameworkの OperationRejectedException、一時障害は通常のRetryable Exceptionをthrowする。FrameworkはOperationをContainerへAutowireする。Repository InterfaceやExternal Client等のConstructor Dependencyが必要な場合だけ、ServiceProvider を作り config/app.phpservices へ登録する。

Creating an Operation

Framework所有のGeneratorから、Build可能なTyped Self-handled Operation、Value、Outcomeを作成できる。

php blackops make:operation Billing/CreateInvoice --type=billing.invoice.create
php blackops build:compile

Generatorはapp/Feature/Billing/CreateInvoice/へ3 Fileを作成する。既存Fileは上書きせず、Route、Deferred設定、Build、Database操作は行わない。生成後のSourceはApplication所有であり、Framework Updateによって書き換えられない。CommandとStubはFramework Packageが所有するため、composer update blackops/framework後の新規生成には更新済み実装が使われ、Projectのblackopsを置き換える必要はない。

Creating a Migration

Application固有のMigrationはFramework所有Generatorで作成する。

php blackops make:migration CreateOrdersTable

最初の実行時にmigrations/が作られ、UTC VersionのApp\Migrations Classが生成される。up()down()へApplicationのSQLを記述した後、Framework Migrationと同じ明示Commandで確認・適用する。

php blackops database:status
php blackops database:migrate --dry-run
php blackops database:migrate

Generator自身はDatabase接続、Migration適用、Buildを行わない。Application Migrationがない場合、空のmigrations/は不要である。

Creating and Running a Seeder

Install直後のSkeletonは標準Root app/Infrastructure/Seed/DatabaseSeeder.phpを持つ。追加SeederはFramework所有Generatorで作り、Rootから明示順に実行する。

php blackops make:seeder Catalog/ProductSeeder
php blackops database:migrate
php blackops build:compile
php blackops database:seed

Migration、Build、Seedは互いを暗黙実行しない。SeederはBuild時に検出されるため、Source変更後は必ず再Buildする。Transaction、再実行方針、Conflict処理はApplicationが所有する。