Skip to content

Update the SDK to the Open Workflow Specification DSL 1.0.1 - #7

Open
rmcdaniel wants to merge 3 commits into
mainfrom
dsl-1.0
Open

rmcdaniel wants to merge 3 commits into
mainfrom
dsl-1.0

Conversation

@rmcdaniel

@rmcdaniel rmcdaniel commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Updates the PHP SDK from the legacy Serverless Workflow 0.8 DSL to the Open Workflow Specification DSL v1.0.1, the current release of the specification.

  • Typed model for the whole 1.0.1 schema: Workflow, Document, Input/Output/Export, Schema, reusable Components (use), the ordered TaskList, every task type (CallHttp, CallOpenApi, CallGrpc, CallAsyncApi, CallFunction, DoTask, EmitTask, ForTask, ForkTask, ListenTask, RaiseTask, RunTask, SetTask, SwitchTask, TryTask, WaitTask) and their configuration objects, events and consumption strategies, authentication policies, retries, timeouts, durations and schedules.
  • Hydration: a small attribute-driven hydrator (src/Hydration) builds objects from arrays, resolves concrete task types from their properties, handles string-or-object unions (timeout, endpoint, wait, duration, …) and preserves unknown properties so definitions survive a parse → serialize round trip. Empty objects such as backoff: { exponential: {} } serialize as objects, not as empty lists.
  • Validation uses opis/json-schema (JSON Schema 2020-12, including unevaluatedProperties) against the unmodified v1.0.1 schema bundled in schema/workflow.json, so it works offline. WorkflowValidator::errors() returns messages keyed by JSON pointer; validate() throws WorkflowValidationException.
  • Dependencies: phpdocumentor/reflection-docblock and swaggest/json-schema are dropped; symfony/yaml ^6.4 || ^7.0 and opis/json-schema ^2.4 are required; PHP ^8.2 is declared. CI now pins PHP 8.3 via setup-php and uses the v4 actions.
  • Tests: all 63 examples of specification v1.0.1 are fixtures. Each is parsed, serialized to arrays/JSON/YAML without loss, validated against the bundled schema and checked to hydrate typed tasks. Unit tests cover task type resolution, task lists, programmatic building, typed navigation, validation errors and unknown-property preservation (219 tests).
  • README rewritten for the 1.0 API.

Breaking changes

This is a complete replacement of the object model; 0.8 definitions and the 0.8 classes are gone. It should ship as 2.0.0. The README points users of 0.8 definitions to the migration tool and to the 1.0.0 release of this package.

Notes for reviewers

  • The specification also has v1.0.2 and v1.0.3 pre-releases (a2a and mcp call tasks, container pullPolicy, stdin/argv for run tasks). The bundled schema is the v1.0.1 release; definitions using those additions still parse and round-trip thanks to unknown-property preservation, but they are reported by validation. Moving to a later release means replacing schema/workflow.json, bumping Workflow::DSL_VERSION and refreshing the example fixtures (see schema/README.md).
  • toYaml() emits list items as - name: like the specification examples rather than Symfony's default dash-on-its-own-line style.
  • Validating a raw array hydrates it through the model first, so typed empty objects are encoded correctly; data the model cannot hydrate is validated as is.

Test plan

Run on PHP 8.3 with the regenerated composer.lock:

  • composer validate --strict
  • vendor/bin/ecs check
  • vendor/bin/phpstan analyse src tests (level 6)
  • vendor/bin/phpunit — 219 tests, 1080 assertions

Replaces the Serverless Workflow 0.8 object model (states, actions,
functions) with typed classes for the DSL 1.0.1 document: document,
input/output/export, reusable components, the named task list and
every task type (call http/openapi/grpc/asyncapi/function, do, emit,
for, fork, listen, raise, run, set, switch, try, wait), events,
authentication policies, retries, timeouts and schedules.

A small attribute-driven hydrator builds the model from arrays,
resolves concrete task types from their properties and preserves
unknown properties so definitions survive a parse/serialize round
trip. Empty objects are serialized as objects, not empty lists.

Validation now uses opis/json-schema (JSON Schema 2020-12) against
the unmodified v1.0.1 schema bundled in schema/workflow.json, so it
works offline. phpdocumentor/reflection-docblock and
swaggest/json-schema are no longer needed. PHP 8.2 or later is
required and CI pins PHP 8.3.

Signed-off-by: Richard McDaniel <richard.lee.mcdaniel@gmail.com>
Copies the 63 workflow examples of specification v1.0.1 as fixtures.
Each one is parsed, serialized to arrays, JSON and YAML without loss,
validated against the bundled schema and checked to hydrate typed
tasks. Unit tests cover task type resolution, named task lists,
programmatic building, typed navigation, validation errors and the
preservation of unknown properties.

Signed-off-by: Richard McDaniel <richard.lee.mcdaniel@gmail.com>
Signed-off-by: Richard McDaniel <richard.lee.mcdaniel@gmail.com>
@rmcdaniel
rmcdaniel requested review from cdavernas and ricardozanini and a balanced review from Copilot October 22, 2026 01:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Hydration and serialization can alter workflow data and conceal schema violations, undermining lossless conversion and reliable validation.

7 open issues
What changed in this PR

Migrates the PHP SDK from the legacy 0.8 DSL to Open Workflow Specification 1.0.1, replacing the state-based API with typed workflow and task models.

Changes:

  • Adds attribute-driven hydration, serialization, reusable components and task configuration models.
  • Replaces remote schema validation with bundled-schema validation and structured errors.
  • Updates documentation, dependencies and CI, and adds specification fixtures and unit coverage.
File Description
tests/​Unit/​WorkflowTest.php Tests building, navigation, serialization and validation.
tests/​Unit/​TaskTest.php Tests task resolution and property ordering.
tests/​Unit/​TaskListTest.php Tests named-task collection behavior.
tests/​Unit/​ExamplesTest.php Checks specification examples across SDK operations.
tests/​Fixtures/​workflow.yaml Replaces the legacy YAML fixture.
tests/​Fixtures/​workflow.json Replaces the legacy JSON fixture.
tests/​Fixtures/​examples/​wait-duration-iso8601.yaml Adds ISO-duration waiting example.
tests/​Fixtures/​examples/​wait-duration-inline.yaml Adds inline-duration waiting example.
tests/​Fixtures/​examples/​try-catch.yaml Adds error-catching example.
tests/​Fixtures/​examples/​try-catch-then.yaml Adds catch-handler task example.
tests/​Fixtures/​examples/​try-catch-retry-reusable.yaml Adds reusable retry example.
tests/​Fixtures/​examples/​try-catch-retry-inline.yaml Adds inline retry example.
tests/​Fixtures/​examples/​switch-then-string.yaml Adds conditional routing example.
tests/​Fixtures/​examples/​star-wars-homeworld.yaml Adds chained HTTP calls example.
tests/​Fixtures/​examples/​set.yaml Adds mapped data assignment example.
tests/​Fixtures/​examples/​set-expression.yaml Adds expression-based assignment example.
tests/​Fixtures/​examples/​schedule-event-driven.yaml Adds event-triggered scheduling example.
tests/​Fixtures/​examples/​schedule-cron.yaml Adds cron scheduling example.
tests/​Fixtures/​examples/​run-subflow.yaml Adds workflow invocation example.
tests/​Fixtures/​examples/​run-script-with-arguments.yaml Adds script arguments example.
tests/​Fixtures/​examples/​run-return-stderr.yaml Adds standard-error output example.
tests/​Fixtures/​examples/​run-return-none.yaml Adds suppressed output example.
tests/​Fixtures/​examples/​run-return-code.yaml Adds exit-code output example.
tests/​Fixtures/​examples/​run-return-all.yaml Adds combined process output example.
tests/​Fixtures/​examples/​run-container.yaml Adds container execution example.
tests/​Fixtures/​examples/​run-container-with-name.yaml Adds container naming example.
tests/​Fixtures/​examples/​run-container-cleanup-eventually.yaml Adds delayed container cleanup example.
tests/​Fixtures/​examples/​run-container-cleanup-always.yaml Adds unconditional container cleanup example.
tests/​Fixtures/​examples/​README.md Documents example provenance.
tests/​Fixtures/​examples/​raise-reusable.yaml Adds reusable error example.
tests/​Fixtures/​examples/​raise-inline.yaml Adds inline error example.
tests/​Fixtures/​examples/​mock-service-extension.yaml Adds task extension example.
tests/​Fixtures/​examples/​listen-to-one.yaml Adds single-event listening example.
tests/​Fixtures/​examples/​listen-to-any.yaml Adds unrestricted listening example.
tests/​Fixtures/​examples/​listen-to-any-until-consumed.yaml Adds event-based stopping example.
tests/​Fixtures/​examples/​listen-to-any-until-condition.yaml Adds condition-based stopping example.
tests/​Fixtures/​examples/​listen-to-any-forever-foreach.yaml Adds continuous event iteration example.
tests/​Fixtures/​examples/​listen-to-any-filter.yaml Adds filtered event listening example.
tests/​Fixtures/​examples/​listen-to-all.yaml Adds combined event listening example.
tests/​Fixtures/​examples/​listen-to-all read-envelope.yaml Adds event-envelope reading example.
tests/​Fixtures/​examples/​fork.yaml Adds competing parallel tasks example.
tests/​Fixtures/​examples/​for.yaml Adds collection iteration example.
tests/​Fixtures/​examples/​emit.yaml Adds event publication example.
tests/​Fixtures/​examples/​do-single.yaml Adds single-task workflow example.
tests/​Fixtures/​examples/​do-multiple.yaml Adds sequential tasks example.
tests/​Fixtures/​examples/​conditional-task.yaml Adds conditional execution example.
tests/​Fixtures/​examples/​call-openapi.yaml Adds OpenAPI invocation example.
tests/​Fixtures/​examples/​call-openapi-redirect.yaml Adds OpenAPI redirect example.
tests/​Fixtures/​examples/​call-http-redirect.yaml Adds HTTP redirect example.
tests/​Fixtures/​examples/​call-http-query-parameters.yaml Adds HTTP query parameters example.
tests/​Fixtures/​examples/​call-http-query-headers-expressions.yaml Adds dynamic headers and query example.
tests/​Fixtures/​examples/​call-http-endpoint-interpolation.yaml Adds endpoint expression example.
tests/​Fixtures/​examples/​call-http-endpoint-interpolation-shorthand.yaml Adds shorthand endpoint example.
tests/​Fixtures/​examples/​call-grpc.yaml Adds gRPC invocation example.
tests/​Fixtures/​examples/​call-custom-function-inline.yaml Adds reusable function example.
tests/​Fixtures/​examples/​call-custom-function-cataloged.yaml Adds external function example.
tests/​Fixtures/​examples/​call-asyncapi-subscribe-consume-while.yaml Adds conditional message consumption example.
tests/​Fixtures/​examples/​call-asyncapi-subscribe-consume-until.yaml Adds bounded message consumption example.
tests/​Fixtures/​examples/​call-asyncapi-subscribe-consume-forever-foreach.yaml Adds continuous message iteration example.
tests/​Fixtures/​examples/​call-asyncapi-subscribe-consume-amount.yaml Adds message-count limit example.
tests/​Fixtures/​examples/​call-asyncapi-publish.yaml Adds AsyncAPI publication example.
tests/​Fixtures/​examples/​authentication-reusable.yaml Adds reusable authentication example.
tests/​Fixtures/​examples/​authentication-oidc.yaml Adds inline OpenID Connect example.
tests/​Fixtures/​examples/​authentication-oidc-secret.yaml Adds secret-based OpenID Connect example.
tests/​Fixtures/​examples/​authentication-oauth2.yaml Adds inline OAuth2 example.
tests/​Fixtures/​examples/​authentication-oauth2-secret.yaml Adds secret-based OAuth2 example.
tests/​Fixtures/​examples/​authentication-bearer.yaml Adds bearer authentication example.
tests/​Fixtures/​examples/​authentication-bearer-uri-format.yaml Adds bearer endpoint-template example.
tests/​Fixtures/​examples/​accumulate-room-readings.yaml Adds correlated event processing example.
src/​WorkflowValidator.php Uses bundled schema and structured validation errors.
src/​WorkflowValidationException.php Exposes validation messages by JSON pointer.
src/​WorkflowExecTimeout.php Removes legacy execution timeout model.
src/​Workflow.php Replaces workflow structure and conversion methods.
src/​TransitionDataCondition.php Removes legacy transition condition model.
src/​Transition.php Removes legacy transition model.
src/​Timeout.php Models duration-based timeouts.
src/​TaskList.php Adds typed named-task lists.
src/​Task/​WaitTask.php Models duration-based waiting.
src/​Task/​TryTask.php Models guarded task execution.
src/​Task/​TryCatch.php Models catch handlers and retries.
src/​Task/​Task.php Defines shared properties and task resolution.
src/​Task/​SwitchTask.php Models ordered conditional routing.
src/​Task/​SwitchCase.php Models switch conditions and directives.
src/​Task/​SetTask.php Models data assignment.
src/​Task/​RunTask.php Models external process execution.
src/​Task/​RunConfiguration.php Defines execution targets and output options.
src/​Task/​Run/​SubflowConfiguration.php Models workflow invocation arguments.
src/​Task/​Run/​Shell.php Models shell command configuration.
src/​Task/​Run/​Script.php Models inline and external scripts.
src/​Task/​Run/​ContainerLifetime.php Models container cleanup policy.
src/​Task/​Run/​Container.php Models container execution configuration.
src/​Task/​RaiseTask.php Models explicit error raising.
src/​Task/​RaiseConfiguration.php Supports inline and reusable errors.
src/​Task/​ListenTask.php Models event listening and iteration.
src/​Task/​ListenConfiguration.php Defines event selection and reading mode.
src/​Task/​ForTask.php Models iterative task execution.
src/​Task/​ForkTask.php Models concurrent task execution.
src/​Task/​ForkConfiguration.php Defines branches and competition mode.
src/​Task/​ForConfiguration.php Defines collection iteration variables.
src/​Task/​EmitTask.php Models event publication.
src/​Task/​EmitEvent.php Models emitted event properties.
src/​Task/​EmitConfiguration.php Defines event emission configuration.
src/​Task/​DoTask.php Models nested sequential tasks.
src/​Task/​CatchErrors.php Defines catch error filtering.
src/​Task/​CallTask.php Defines shared call-task properties.
src/​Task/​CallOpenApi.php Models OpenAPI calls.
src/​Task/​CallHttp.php Models HTTP calls.
src/​Task/​CallGrpc.php Models gRPC calls.
src/​Task/​CallFunction.php Models custom function calls.
src/​Task/​CallAsyncApi.php Models AsyncAPI calls.
src/​Task/​Call/​OpenApiArguments.php Defines OpenAPI call arguments.
src/​Task/​Call/​HttpArguments.php Defines HTTP request arguments.
src/​Task/​Call/​GrpcService.php Defines gRPC service connection properties.
src/​Task/​Call/​GrpcArguments.php Defines gRPC call arguments.
src/​Task/​Call/​AsyncApiSubscription.php Defines message subscription behavior.
src/​Task/​Call/​AsyncApiServer.php Defines AsyncAPI server selection.
src/​Task/​Call/​AsyncApiMessageConsumptionPolicy.php Defines message consumption limits.
src/​Task/​Call/​AsyncApiMessage.php Defines message payload and headers.
src/​Task/​Call/​AsyncApiArguments.php Defines AsyncAPI operation arguments.
src/​StateExecTimeout.php Removes legacy state timeout model.
src/​State.php Removes legacy state abstraction.
src/​StartDef.php Removes legacy workflow start model.
src/​SleepStateTimeout.php Removes legacy sleep timeout model.
src/​SleepState.php Removes legacy sleep state.
src/​Sleep.php Removes legacy action sleep model.
src/​Serialization/​Serializer.php Adds shared recursive normalization.
src/​Schema.php Models inline and external schemas.
src/​Schedule.php Replaces legacy scheduling properties.
src/​RetryPolicy.php Models retry conditions and timing.
src/​RetryLimitAttempt.php Defines retry attempt limits.
src/​RetryLimit.php Defines retry duration limits.
src/​RetryJitter.php Defines retry timing variation.
src/​RetryBackoff.php Models backoff strategy selection.
src/​ProduceEventDef.php Removes legacy event production model.
src/​ParallelStateTimeout.php Removes legacy parallel timeout model.
src/​ParallelState.php Removes legacy parallel state.
src/​Output.php Models output schemas and transformations.
src/​OperationStateTimeout.php Removes legacy operation timeout model.
src/​OperationState.php Removes legacy operation state.
src/​OnEvents.php Removes legacy event-action grouping.
src/​NamedList.php Adds ordered named-item collections.
src/​Input.php Models input schemas and transformations.
src/​InjectState.php Removes legacy injection state.
src/​Hydration/​NamedListOf.php Declares named-list element types.
src/​Hydration/​MapOf.php Declares map value types.
src/​Hydration/​ListOf.php Declares list element types.
src/​Hydration/​Hydrator.php Hydrates typed properties and retains unknown data.
src/​Hydration/​Hydratable.php Defines array hydration contract.
src/​Hydration/​ArrayConvertible.php Defines array conversion contract.
src/​ForeachStateTimeout.php Removes legacy iteration timeout model.
src/​ForeachState.php Removes legacy iteration state.
src/​FlowDirective.php Defines reserved flow directives.
src/​ExternalResource.php Models named external resources.
src/​Extension.php Models before/after task extensions.
src/​Export.php Models workflow-context exports.
src/​EventStateTimeout.php Removes legacy event timeout model.
src/​EventState.php Removes legacy event state.
src/​EventBasedSwitchStateTimeOut.php Removes legacy event-switch timeout model.
src/​EventBasedSwitchState.php Removes legacy event-switch state.
src/​Event/​SubscriptionIterator.php Models per-event or per-message iteration.
src/​Event/​EventProperties.php Models event attributes and data.
src/​Event/​EventFilter.php Models event filtering and correlation.
src/​Event/​EventCorrelation.php Defines event correlation expressions.
src/​Event/​EventConsumptionStrategy.php Models event selection and stopping rules.
src/​ErrorFilter.php Models static error filters.
src/​Error.php Replaces legacy error handling properties.
src/​Endpoint.php Models endpoints and authentication.
src/​Duration.php Models inline duration units.
src/​Document.php Models workflow identity and metadata.
src/​Components.php Models reusable workflow components.
src/​Catalog.php Models resource catalog endpoints.
src/​CallbackState.php Removes legacy callback state.
src/​BaseObject.php Centralizes hydration and additional properties.
src/​BackoffDefinition.php Models backoff configuration objects.
src/​Authentication/​OpenIdConnectAuthentication.php Models OpenID Connect authentication.
src/​Authentication/​OAuth2TokenRequest.php Models token request encoding.
src/​Authentication/​OAuth2Token.php Models typed OAuth2 tokens.
src/​Authentication/​OAuth2Properties.php Defines shared OAuth2/OpenID Connect properties.
src/​Authentication/​OAuth2Endpoints.php Models OAuth2 endpoint paths.
src/​Authentication/​OAuth2Client.php Models OAuth2 client credentials.
src/​Authentication/​OAuth2Authentication.php Models OAuth2 authentication.
src/​Authentication/​DigestAuthentication.php Models digest authentication.
src/​Authentication/​BearerAuthentication.php Models bearer authentication.
src/​Authentication/​BasicAuthentication.php Models basic authentication.
src/​Authentication/​AuthenticationPolicy.php Models authentication schemes and references.
schema/​README.md Documents schema provenance and upgrades.
README.md Documents the new API and migration guidance.
composer.json Updates runtime dependencies and test autoloading.
.github/​workflows/​php.yml Pins PHP 8.3 and updates CI actions.

Review effort: Balanced

}

try {
$property->setValue($target, self::cast($property, $value));
Comment thread src/NamedList.php
) {
foreach ($items as $key => $value) {
if (is_int($key)) {
if (! is_array($value)) {
Comment on lines +53 to +55
$items[] = [
$name => self::normalize($item, $preserveEmptyObjects),
];
Comment on lines +65 to +70
if (is_array($value)) {
foreach ($value as $key => $item) {
$value[$key] = self::normalize($item, $preserveEmptyObjects);
}

return $value;
Comment thread src/BaseObject.php
Comment on lines +85 to +87
$value = $reflectionProperty->getValue($this);
if ($value !== null) {
$properties[$reflectionProperty->getName()] = $value;
Comment thread src/Workflow.php
);

// Start list items that hold a mapping on the dash line ("- name:"), as the specification examples do.
return preg_replace('/^( *)-\n\1 (?=\S)/m', '$1- ', $yaml) ?? $yaml;
Comment on lines +54 to +55
self::assertEquals($expected, Workflow::fromJson($workflow->toJson())->toArray());
self::assertEquals($expected, Workflow::fromYaml($workflow->toYaml())->toArray());
@ricardozanini

Copy link
Copy Markdown

@rmcdaniel, many thanks for this work! A couple of things:

  1. Can you have a look at Copilot's comments? See what's relevant and what we can ignore.
  2. There are a few restrictions on the libs we are using; can we replace them with non-GPL ones?
Screenshot 2026-10-22 at 9 00 38 AM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants