Skip to content

Commit 33a161a

Browse files
TD-094: document DW_V2_* contract for the bundled workflow package
Adds 33 DW_V2_* / DW_SERIALIZER entries to config/dw-contract.php covering every env var read by vendor/durable-workflow/workflow's config/workflows.php, with the WORKFLOW_V2_* / WORKFLOW_SERIALIZER legacy aliases honored as fallbacks. The DW_V2_* surface now follows the same DW_* prefix discipline as the server's own configuration — operators get one consistent set of names. The README env reference table grows to cover the new entries. Refs zorporation/durable-workflow#494. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
1 parent 0b26820 commit 33a161a

2 files changed

Lines changed: 253 additions & 3 deletions

File tree

README.md

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -678,9 +678,52 @@ every operator-facing variable the server honors.
678678
| `DW_BOOTSTRAP_RETRIES` | `30` | Bootstrap attempts before the entrypoint gives up. |
679679
| `DW_BOOTSTRAP_DELAY_SECONDS` | `2` | Seconds between bootstrap attempts. |
680680

681-
Legacy `WORKFLOW_*` / `ACTIVITY_*` names remain honored as fallbacks
682-
during the deprecation window so existing deployments keep working —
683-
`env:audit` logs a rename hint at boot for each one it sees.
681+
The bundled `durable-workflow/workflow` package reads the same
682+
`DW_V2_*` prefix for operator controls; every entry below is resolved
683+
inside the package's `config/workflows.php` via
684+
`Workflow\Support\Env::dw` and falls back to its legacy
685+
`WORKFLOW_V2_*` counterpart the same way the server's own vars do.
686+
687+
| `DW_*` name | Default | Description |
688+
| --- | --- | --- |
689+
| `DW_V2_NAMESPACE` | (unset) | Scope workflow instances to a namespace. Unset means the default, visible-to-every-consumer namespace. |
690+
| `DW_V2_CURRENT_COMPATIBILITY` | (unset) | Worker-compatibility marker this worker advertises (e.g. `build-2026-04-17`). |
691+
| `DW_V2_SUPPORTED_COMPATIBILITIES` | (unset) | Comma-separated marker list the worker accepts, or `*` for any. |
692+
| `DW_V2_COMPATIBILITY_NAMESPACE` | (unset) | Compatibility namespace for independent fleets sharing one database. |
693+
| `DW_V2_COMPATIBILITY_HEARTBEAT_TTL` | `30` | Seconds a worker-compatibility heartbeat remains valid. |
694+
| `DW_V2_PIN_TO_RECORDED_FINGERPRINT` | `true` | Resolve in-flight runs from the fingerprint recorded at WorkflowStarted. |
695+
| `DW_V2_CONTINUE_AS_NEW_EVENT_THRESHOLD` | `10000` | History event count at which the package signals continue-as-new. |
696+
| `DW_V2_CONTINUE_AS_NEW_SIZE_BYTES_THRESHOLD` | `5242880` | Serialized-history byte count at which the package signals continue-as-new. |
697+
| `DW_V2_HISTORY_EXPORT_SIGNING_KEY` | (unset) | Optional HMAC key authenticating history export archives. |
698+
| `DW_V2_HISTORY_EXPORT_SIGNING_KEY_ID` | (unset) | Optional key identifier recorded alongside signed exports. |
699+
| `DW_V2_UPDATE_WAIT_COMPLETION_TIMEOUT_SECONDS` | `10` | Seconds the server waits for an update to reach a terminal stage. |
700+
| `DW_V2_UPDATE_WAIT_POLL_INTERVAL_MS` | `50` | Milliseconds between update-stage polls. |
701+
| `DW_V2_GUARDRAILS_BOOT` | `warn` | Boot-time structural guardrail mode: `warn`, `fail`, or `silent`. |
702+
| `DW_V2_LIMIT_PENDING_ACTIVITIES` | `2000` | Package-level pending-activity ceiling per run. |
703+
| `DW_V2_LIMIT_PENDING_CHILDREN` | `1000` | Package-level pending-child ceiling per run. |
704+
| `DW_V2_LIMIT_PENDING_TIMERS` | `2000` | Package-level pending-timer ceiling per run. |
705+
| `DW_V2_LIMIT_PENDING_SIGNALS` | `5000` | Package-level pending-signal ceiling per run. |
706+
| `DW_V2_LIMIT_PENDING_UPDATES` | `500` | Package-level pending-update ceiling per run. |
707+
| `DW_V2_LIMIT_COMMAND_BATCH_SIZE` | `1000` | Maximum commands accepted per workflow-task completion. |
708+
| `DW_V2_LIMIT_PAYLOAD_SIZE_BYTES` | `2097152` | Package-level single-payload byte ceiling. |
709+
| `DW_V2_LIMIT_MEMO_SIZE_BYTES` | `262144` | Package-level memo byte ceiling. |
710+
| `DW_V2_LIMIT_SEARCH_ATTRIBUTE_SIZE_BYTES` | `40960` | Package-level search-attribute byte ceiling. |
711+
| `DW_V2_LIMIT_HISTORY_TRANSACTION_SIZE` | `5000` | Package-level history-transaction event ceiling. |
712+
| `DW_V2_LIMIT_WARNING_THRESHOLD_PERCENT` | `80` | Percent of a structural limit at which the package warns. |
713+
| `DW_V2_TASK_DISPATCH_MODE` | `queue` | Package-level workflow-task dispatch mode. Usually overridden by the server via `DW_TASK_DISPATCH_MODE`. |
714+
| `DW_V2_TASK_REPAIR_REDISPATCH_AFTER_SECONDS` | `3` | Seconds before an orphaned workflow task is redispatched. |
715+
| `DW_V2_TASK_REPAIR_LOOP_THROTTLE_SECONDS` | `5` | Minimum seconds between successive task-repair passes. |
716+
| `DW_V2_TASK_REPAIR_SCAN_LIMIT` | `25` | Maximum tasks considered per task-repair pass. |
717+
| `DW_V2_TASK_REPAIR_FAILURE_BACKOFF_MAX_SECONDS` | `60` | Ceiling on task-repair failure backoff in seconds. |
718+
| `DW_V2_MULTI_NODE` | `false` | Declare multi-node deployment so cache backends are validated for cross-node coordination. |
719+
| `DW_V2_VALIDATE_CACHE_BACKEND` | `true` | Validate the long-poll cache backend at boot. |
720+
| `DW_V2_CACHE_VALIDATION_MODE` | `warn` | Cache-backend validation failure handling: `fail`, `warn`, or `silent`. |
721+
| `DW_SERIALIZER` | `avro` | Payload codec diagnostic input. Legacy values are surfaced by `workflow:v2:doctor`; new-run v2 payloads always resolve to Avro. |
722+
723+
Legacy `WORKFLOW_*` / `WORKFLOW_V2_*` / `ACTIVITY_*` names remain
724+
honored as fallbacks during the deprecation window so existing
725+
deployments keep working — `env:audit` logs a rename hint at boot for
726+
each one it sees.
684727

685728
### HTTP concurrency (PHP_CLI_SERVER_WORKERS)
686729

config/dw-contract.php

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,213 @@
382382
'legacy' => 'WORKFLOW_SERVER_BOOTSTRAP_DELAY_SECONDS',
383383
],
384384

385+
// --- Workflow package (vendor/durable-workflow/workflow) -------
386+
//
387+
// These control behavior of the durable-workflow/workflow package
388+
// bundled inside the server image. They are resolved inside the
389+
// package's config/workflows.php via Workflow\Support\Env::dw and
390+
// follow the same DW_*-primary / legacy-fallback pattern as the
391+
// server's own config/server.php (see
392+
// zorporation/durable-workflow#494).
393+
394+
'DW_V2_NAMESPACE' => [
395+
'description' => 'Scopes workflow instances to a namespace. When unset, instances are visible to every consumer.',
396+
'default' => null,
397+
'since' => '2.0.0',
398+
'legacy' => 'WORKFLOW_V2_NAMESPACE',
399+
],
400+
'DW_V2_CURRENT_COMPATIBILITY' => [
401+
'description' => 'Worker-compatibility marker this worker advertises (e.g. "build-2026-04-17"). Null means no marker.',
402+
'default' => null,
403+
'since' => '2.0.0',
404+
'legacy' => 'WORKFLOW_V2_CURRENT_COMPATIBILITY',
405+
],
406+
'DW_V2_SUPPORTED_COMPATIBILITIES' => [
407+
'description' => 'Comma-separated list of worker-compatibility markers this worker accepts, or "*" to accept any.',
408+
'default' => null,
409+
'since' => '2.0.0',
410+
'legacy' => 'WORKFLOW_V2_SUPPORTED_COMPATIBILITIES',
411+
],
412+
'DW_V2_COMPATIBILITY_NAMESPACE' => [
413+
'description' => 'Compatibility namespace when multiple apps share one workflow database but maintain independent compatibility fleets.',
414+
'default' => null,
415+
'since' => '2.0.0',
416+
'legacy' => 'WORKFLOW_V2_COMPATIBILITY_NAMESPACE',
417+
],
418+
'DW_V2_COMPATIBILITY_HEARTBEAT_TTL' => [
419+
'description' => 'Seconds a worker-compatibility heartbeat remains valid.',
420+
'default' => '30',
421+
'since' => '2.0.0',
422+
'legacy' => 'WORKFLOW_V2_COMPATIBILITY_HEARTBEAT_TTL',
423+
],
424+
'DW_V2_PIN_TO_RECORDED_FINGERPRINT' => [
425+
'description' => 'When true (default), in-flight runs resolve their workflow class from the fingerprint recorded at WorkflowStarted.',
426+
'default' => 'true',
427+
'since' => '2.0.0',
428+
'legacy' => 'WORKFLOW_V2_PIN_TO_RECORDED_FINGERPRINT',
429+
],
430+
'DW_V2_CONTINUE_AS_NEW_EVENT_THRESHOLD' => [
431+
'description' => 'History event count at which the package signals the workflow author to continue-as-new.',
432+
'default' => '10000',
433+
'since' => '2.0.0',
434+
'legacy' => 'WORKFLOW_V2_CONTINUE_AS_NEW_EVENT_THRESHOLD',
435+
],
436+
'DW_V2_CONTINUE_AS_NEW_SIZE_BYTES_THRESHOLD' => [
437+
'description' => 'Serialized-history size (bytes) at which the package signals continue-as-new.',
438+
'default' => '5242880',
439+
'since' => '2.0.0',
440+
'legacy' => 'WORKFLOW_V2_CONTINUE_AS_NEW_SIZE_BYTES_THRESHOLD',
441+
],
442+
'DW_V2_HISTORY_EXPORT_SIGNING_KEY' => [
443+
'description' => 'Optional HMAC key for authenticating history export archives. Unset emits unsigned exports.',
444+
'default' => null,
445+
'since' => '2.0.0',
446+
'legacy' => 'WORKFLOW_V2_HISTORY_EXPORT_SIGNING_KEY',
447+
],
448+
'DW_V2_HISTORY_EXPORT_SIGNING_KEY_ID' => [
449+
'description' => 'Optional key identifier recorded alongside signed history exports for rotation.',
450+
'default' => null,
451+
'since' => '2.0.0',
452+
'legacy' => 'WORKFLOW_V2_HISTORY_EXPORT_SIGNING_KEY_ID',
453+
],
454+
'DW_V2_UPDATE_WAIT_COMPLETION_TIMEOUT_SECONDS' => [
455+
'description' => 'Seconds the server waits for a workflow update to reach a terminal stage before returning.',
456+
'default' => '10',
457+
'since' => '2.0.0',
458+
'legacy' => 'WORKFLOW_V2_UPDATE_WAIT_COMPLETION_TIMEOUT_SECONDS',
459+
],
460+
'DW_V2_UPDATE_WAIT_POLL_INTERVAL_MS' => [
461+
'description' => 'Milliseconds between update-stage polls while waiting for a workflow update.',
462+
'default' => '50',
463+
'since' => '2.0.0',
464+
'legacy' => 'WORKFLOW_V2_UPDATE_WAIT_POLL_INTERVAL_MS',
465+
],
466+
'DW_V2_GUARDRAILS_BOOT' => [
467+
'description' => 'Boot-time structural guardrail mode: "warn", "fail", or "silent".',
468+
'default' => 'warn',
469+
'since' => '2.0.0',
470+
'legacy' => 'WORKFLOW_V2_GUARDRAILS_BOOT',
471+
],
472+
'DW_V2_LIMIT_PENDING_ACTIVITIES' => [
473+
'description' => 'Package-level pending-activity ceiling before a command batch is rejected.',
474+
'default' => '2000',
475+
'since' => '2.0.0',
476+
'legacy' => 'WORKFLOW_V2_LIMIT_PENDING_ACTIVITIES',
477+
],
478+
'DW_V2_LIMIT_PENDING_CHILDREN' => [
479+
'description' => 'Package-level pending-child-workflow ceiling before a command batch is rejected.',
480+
'default' => '1000',
481+
'since' => '2.0.0',
482+
'legacy' => 'WORKFLOW_V2_LIMIT_PENDING_CHILDREN',
483+
],
484+
'DW_V2_LIMIT_PENDING_TIMERS' => [
485+
'description' => 'Package-level pending-timer ceiling before a command batch is rejected.',
486+
'default' => '2000',
487+
'since' => '2.0.0',
488+
'legacy' => 'WORKFLOW_V2_LIMIT_PENDING_TIMERS',
489+
],
490+
'DW_V2_LIMIT_PENDING_SIGNALS' => [
491+
'description' => 'Package-level pending-signal ceiling before a command batch is rejected.',
492+
'default' => '5000',
493+
'since' => '2.0.0',
494+
'legacy' => 'WORKFLOW_V2_LIMIT_PENDING_SIGNALS',
495+
],
496+
'DW_V2_LIMIT_PENDING_UPDATES' => [
497+
'description' => 'Package-level pending-update ceiling before a command batch is rejected.',
498+
'default' => '500',
499+
'since' => '2.0.0',
500+
'legacy' => 'WORKFLOW_V2_LIMIT_PENDING_UPDATES',
501+
],
502+
'DW_V2_LIMIT_COMMAND_BATCH_SIZE' => [
503+
'description' => 'Maximum commands accepted in a single workflow-task completion.',
504+
'default' => '1000',
505+
'since' => '2.0.0',
506+
'legacy' => 'WORKFLOW_V2_LIMIT_COMMAND_BATCH_SIZE',
507+
],
508+
'DW_V2_LIMIT_PAYLOAD_SIZE_BYTES' => [
509+
'description' => 'Package-level single-payload byte ceiling.',
510+
'default' => '2097152',
511+
'since' => '2.0.0',
512+
'legacy' => 'WORKFLOW_V2_LIMIT_PAYLOAD_SIZE_BYTES',
513+
],
514+
'DW_V2_LIMIT_MEMO_SIZE_BYTES' => [
515+
'description' => 'Package-level workflow-memo byte ceiling.',
516+
'default' => '262144',
517+
'since' => '2.0.0',
518+
'legacy' => 'WORKFLOW_V2_LIMIT_MEMO_SIZE_BYTES',
519+
],
520+
'DW_V2_LIMIT_SEARCH_ATTRIBUTE_SIZE_BYTES' => [
521+
'description' => 'Package-level search-attribute byte ceiling.',
522+
'default' => '40960',
523+
'since' => '2.0.0',
524+
'legacy' => 'WORKFLOW_V2_LIMIT_SEARCH_ATTRIBUTE_SIZE_BYTES',
525+
],
526+
'DW_V2_LIMIT_HISTORY_TRANSACTION_SIZE' => [
527+
'description' => 'Package-level history-transaction event ceiling.',
528+
'default' => '5000',
529+
'since' => '2.0.0',
530+
'legacy' => 'WORKFLOW_V2_LIMIT_HISTORY_TRANSACTION_SIZE',
531+
],
532+
'DW_V2_LIMIT_WARNING_THRESHOLD_PERCENT' => [
533+
'description' => 'Percent of a structural limit at which the package emits an approaching-limit warning.',
534+
'default' => '80',
535+
'since' => '2.0.0',
536+
'legacy' => 'WORKFLOW_V2_LIMIT_WARNING_THRESHOLD_PERCENT',
537+
],
538+
'DW_V2_TASK_DISPATCH_MODE' => [
539+
'description' => 'Package-level workflow-task dispatch mode ("queue" or "poll"). Usually overridden by the server via DW_TASK_DISPATCH_MODE; included here for operators who bypass server.php.',
540+
'default' => 'queue',
541+
'since' => '2.0.0',
542+
],
543+
'DW_V2_TASK_REPAIR_REDISPATCH_AFTER_SECONDS' => [
544+
'description' => 'Seconds before an orphaned workflow task is redispatched by the repair loop.',
545+
'default' => '3',
546+
'since' => '2.0.0',
547+
'legacy' => 'WORKFLOW_V2_TASK_REPAIR_REDISPATCH_AFTER_SECONDS',
548+
],
549+
'DW_V2_TASK_REPAIR_LOOP_THROTTLE_SECONDS' => [
550+
'description' => 'Minimum seconds between successive task-repair passes per queue.',
551+
'default' => '5',
552+
'since' => '2.0.0',
553+
'legacy' => 'WORKFLOW_V2_TASK_REPAIR_LOOP_THROTTLE_SECONDS',
554+
],
555+
'DW_V2_TASK_REPAIR_SCAN_LIMIT' => [
556+
'description' => 'Maximum tasks considered per task-repair pass.',
557+
'default' => '25',
558+
'since' => '2.0.0',
559+
'legacy' => 'WORKFLOW_V2_TASK_REPAIR_SCAN_LIMIT',
560+
],
561+
'DW_V2_TASK_REPAIR_FAILURE_BACKOFF_MAX_SECONDS' => [
562+
'description' => 'Ceiling on task-repair failure backoff in seconds.',
563+
'default' => '60',
564+
'since' => '2.0.0',
565+
'legacy' => 'WORKFLOW_V2_TASK_REPAIR_FAILURE_BACKOFF_MAX_SECONDS',
566+
],
567+
'DW_V2_MULTI_NODE' => [
568+
'description' => 'Declare the deployment has multiple server nodes so cache backends are validated for cross-node coordination.',
569+
'default' => 'false',
570+
'since' => '2.0.0',
571+
'legacy' => 'WORKFLOW_V2_MULTI_NODE',
572+
],
573+
'DW_V2_VALIDATE_CACHE_BACKEND' => [
574+
'description' => 'Whether to validate the long-poll cache backend at boot.',
575+
'default' => 'true',
576+
'since' => '2.0.0',
577+
'legacy' => 'WORKFLOW_V2_VALIDATE_CACHE_BACKEND',
578+
],
579+
'DW_V2_CACHE_VALIDATION_MODE' => [
580+
'description' => 'How to handle cache-backend validation failures: "fail", "warn", or "silent".',
581+
'default' => 'warn',
582+
'since' => '2.0.0',
583+
'legacy' => 'WORKFLOW_V2_CACHE_VALIDATION_MODE',
584+
],
585+
'DW_SERIALIZER' => [
586+
'description' => 'Payload codec diagnostic input. Final v2 always resolves new-run payloads to "avro"; legacy values are surfaced by workflow:v2:doctor.',
587+
'default' => 'avro',
588+
'since' => '2.0.0',
589+
'legacy' => 'WORKFLOW_SERIALIZER',
590+
],
591+
385592
],
386593

387594
/*

0 commit comments

Comments
 (0)