@@ -357,6 +357,150 @@ There are no known current v1 product features left without a v2 home.
357357Items listed as deferred are either not v1 parity requirements or are
358358explicitly reserved for a future contract before support is advertised.
359359
360+ ## Migration Strategy
361+
362+ V2 ships as a new product surface beside the v1 PHP package. The migration
363+ contract below frames how the durable kernel, the package and naming
364+ boundary, payload/config/model compatibility, and the adoption rollout fit
365+ together. The customer-facing migration guide on the docs site implements
366+ these rules; this section is the durable-kernel authority they cite.
367+
368+ ### Storage compatibility
369+
370+ - V2 does not interpret v1 tables as native runtime truth. The v2 durable
371+ homes named in the [ Feature Compatibility Matrix] ( #feature-compatibility-matrix )
372+ are the only source of replay authority for v2 runs. The v1 tables
373+ (` workflows ` , ` workflow_logs ` , ` workflow_signals ` , ` workflow_timers ` ,
374+ ` workflow_exceptions ` , ` workflow_relationships ` ) remain on disk for the
375+ v1 finish-on-v1 path; v2 code MUST NOT read them as engine truth.
376+ - Active v1 executions finish on v1. They are not rewritten into v2
377+ ` workflow_runs ` mid-flight, and there is no automatic in-place migration
378+ of leased v1 work.
379+ - Completed v1 executions may be imported into v2 archive/history form
380+ through the same ` workflow:v2:history-import ` contract that handles
381+ embedded v2 imports. The importer maps v1 generic logs and
382+ ` PHP_INT_MAX ` sentinels into v2 event/link payloads, and writes the
383+ result into the durable rows named by the matrix above.
384+ - The importer detects pruned or partial v1 executions, normalizes v1
385+ schema inconsistencies, and marks records as partial when source rows
386+ are missing rather than fabricating durable history. Imported runs
387+ carry the ` workflow_runs.import_source ` , ` import_id ` ,
388+ ` import_dedupe_key ` , ` import_contract_version ` , and ` imported_at `
389+ markers so the run is visibly "imported, not natively executed."
390+ - The default upgrade path is "finish on v1, start new on v2." Importing
391+ closed v1 history is opt-in archive support, not the standard cutover
392+ path.
393+
394+ ### Package and naming compatibility
395+
396+ - The Composer rename ` laravel-workflow/laravel-workflow ` →
397+ ` durable-workflow/workflow ` is a packaging boundary. The v1 package
398+ keeps its name on Packagist; v2 ships under the new name with the
399+ ` Workflow\V2 ` namespace.
400+ - Waterline reads from both v1 and v2 durable rows during the cutover
401+ window. It is not part of the standalone-server distribution and does
402+ not require its package coordinates to change for the v2 cutover.
403+ - Microservice upgrade guidance: existing PHP microservices that share
404+ the same v1 package upgrade together by adopting the v2 type-alias
405+ registry (` workflows.v2.types ` ), the language-neutral payload codec,
406+ and the v2 task-id transport. Stable workflow/activity type keys are
407+ the durable boundary across services; PHP fully-qualified class names
408+ are not.
409+ - Laravel embedded-to-server upgrade path: an embedded v2 deployment
410+ that wants to move new work onto the standalone server keeps the same
411+ durable identities (` workflow_instance_id ` , ` run_id ` ), the same task
412+ queue names, and the same payload codec, while pointing the
413+ application at the server's HTTP API as an explicit remote dependency.
414+ Existing embedded runs drain in place; new starts route to the server.
415+ - Remote-endpoint configuration is explicit. The server base URL,
416+ namespace, task queue, and auth material are configured directly
417+ rather than inferred from Laravel-app-local settings, so the cutover
418+ does not depend on hidden defaults.
419+
420+ ### Payload, config, and model compatibility
421+
422+ - Every durable v2 payload carries a payload-envelope codec name (and,
423+ where applicable, a version) so a worker, importer, or replay tool can
424+ decode it without inspecting deployment-local config. The
425+ package-level default codec is the language-neutral ` avro ` codec; the
426+ legacy ` workflow-serializer-y ` and ` workflow-serializer-base64 ` codecs
427+ remain resolvable only as drain/import codecs for v1 history.
428+ - The importer accepts ` SerializableClosure ` -wrapped payloads, mixed
429+ serializer formats inside one v1 history, ` WorkflowMetadata ` envelopes,
430+ and ` ModelIdentifier ` -only payloads. Identifier-only payloads stay
431+ identifier-only on import; the importer does not eagerly hydrate
432+ application models it cannot prove still exist.
433+ - Custom serializer classes from v1 are not a v2 runtime contract.
434+ ` php artisan workflow:v2:doctor ` flags any ` workflows.serializer `
435+ setting that is not ` avro ` , ` workflow-serializer-y ` , or
436+ ` workflow-serializer-base64 ` as migration debt; default-codec
437+ resolution silently falls back to ` avro ` for new runs so encode does
438+ not fail, and the configured custom class is never invoked.
439+ - Custom model-subclass compatibility is a frozen support matrix.
440+ Subclassing ` WorkflowInstance ` , ` WorkflowRun ` , ` WorkflowTask ` ,
441+ history-event/projection/schedule/activity/failure/link/messages
442+ models, and the search-attribute/memo/child-call models is supported
443+ when the subclass keeps the package's column names, primary keys, and
444+ foreign keys. Custom table names with custom foreign-key column names
445+ are out of contract.
446+ - Waterline v2 adapters consume the
447+ ` Workflow\V2\Contracts\OperatorObservabilityRepository ` contract and
448+ the v2 projection rows. They do not depend on a configured Eloquent
449+ subclass to render run detail; swapping the subclass does not require
450+ a Waterline change as long as the package column and key contract is
451+ preserved.
452+
453+ ### Adoption
454+
455+ - Cutover migration guides exist for the v1 surfaces customers actually
456+ ran in production: Laravel queues and chains, batches, scheduled
457+ workflows (cron-driven starts), webhook-driven external ingress, and
458+ PHP microservices. Each guide maps v1 entry points to the v2
459+ primitives named in the matrix and points at the durable home where
460+ the new run's truth lives.
461+ - Reference architectures cover the deployment shapes adopters actually
462+ run:
463+ - ** Monolith.** One Laravel app embedding the workflow package owns
464+ durable rows, workers, and Waterline. Adoption is a same-app
465+ package upgrade plus a cutover window for active v1 runs.
466+ - ** Multi-app.** Several Laravel apps share a database or a server
467+ deployment. Each app stamps stable workflow/activity type keys; one
468+ namespace owns the durable kernel, and other apps participate as
469+ starters or workers using the type-alias registry.
470+ - ** Microservice.** Workers are split across services and possibly
471+ languages. The standalone server owns the durable kernel; PHP, and
472+ later Python or other SDK workers, register against the same
473+ namespace, task queue, payload codec, and worker protocol.
474+ - ** Operator-heavy.** Deployments that lean on schedules, repair,
475+ archive, terminate, and replay-debug surface adopt v2 by exercising
476+ the operator command taxonomy from
477+ [ ` docs/architecture/webhook-and-command-taxonomy.md ` ] ( ../architecture/webhook-and-command-taxonomy.md )
478+ and the deployment lifecycle controls from
479+ [ ` docs/architecture/worker-deployment.md ` ] ( ../architecture/worker-deployment.md )
480+ rather than by editing durable rows directly.
481+ - Single-version rollout mechanics that apply inside a v2 fleet:
482+ - ** Canary.** Stamp newly-started runs with a new compatibility
483+ marker on a small worker cohort before flipping the starter
484+ process's default marker.
485+ - ** Drain.** ` dw task-queue:drain ` flips a worker cohort's drain
486+ intent; pinned runs finish on the cohort that started them or
487+ continue-as-new onto the new marker.
488+ - ** Rollback.** Resume a previously drained cohort and re-point
489+ starter processes at the old marker. In-flight runs on the new
490+ cohort keep running on the new cohort; nothing is silently
491+ rerouted.
492+ - ** Replay-debug.** Versioned history-export bundles plus
493+ ` workflow:v2:replay-verify ` reproduce a closed run on a separate
494+ process so production correctness is debugged out-of-band.
495+ - "Mixed-fleet" is not a v2-internal rollout primitive. There is no
496+ v2-alpha-to-v2 backwards-compatibility lane and no mixed-build
497+ correctness contract that lives outside the compatibility-marker
498+ story above. The only cross-generation surface where mixed-fleet
499+ language remains meaningful is the v1→v2 transition itself: while v1
500+ workers finish v1 runs and v2 workers execute v2 runs, both fleets
501+ may be live at once, and that is the only sense in which a "mixed
502+ fleet" is part of v2 adoption.
503+
360504## Relationship To Other Contracts
361505
362506- [ ` docs/api-stability.md ` ] ( ../api-stability.md ) freezes public API and
@@ -368,7 +512,8 @@ explicitly reserved for a future contract before support is advertised.
368512- [ ` docs/architecture/scheduler-correctness.md ` ] ( ../architecture/scheduler-correctness.md )
369513 defines schedule and timer correctness boundaries.
370514- [ ` docs/architecture/worker-compatibility.md ` ] ( ../architecture/worker-compatibility.md )
371- defines mixed-fleet worker compatibility.
515+ defines worker build identity, compatibility markers, and routing of
516+ in-flight runs across coexisting builds.
372517- [ ` docs/architecture/worker-deployment.md ` ] ( ../architecture/worker-deployment.md )
373518 defines first-class deployment lifecycle and rollout blockage.
374519- [ ` docs/architecture/sticky-execution.md ` ] ( ../architecture/sticky-execution.md )
0 commit comments