Skip to content

feat(client): workflow repository, first slice (#974) - #1039

Merged
gplanchat merged 5 commits into
mainfrom
feat/workflow-repository-1
Oct 9, 2026
Merged

gplanchat merged 5 commits into
mainfrom
feat/workflow-repository-1

Conversation

@gplanchat

@gplanchat gplanchat commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

What

This is the first slice of #974, taken from openspec/changes/workflow-repository/. It adds the entry point an application uses to reach the executions of one workflow class, without the operations on a running execution yet.

Public names, all in src/Durable:

  • Gplanchat\Durable\WorkflowRepository (abstract, @template T of object): create(?ExecutionId $id = null): WorkflowStub contacts no backend and generates an id when none is given; get(ExecutionId $id): WorkflowStub calls WorkflowRunCatalogInterface::findRun().
  • Attribute\AsWorkflowRepository(workflow) on a repository subclass, and Attribute\RepositoryFor(workflow) on a constructor parameter (a plain attribute, no resolver yet).
  • Stub\WorkflowStub: carries the ExecutionId and exposes executionId().
  • Workflow\ReservedStubMethods::assertFreeFor($workflowClass): refuses a signal, query or update method named start, execute, result, cancel, terminate or executionId, in any case.
  • Exception\WorkflowClassNotFound (workflowClass, previous is the \ReflectionException): get() and ReservedStubMethods raise it for a workflow class that cannot be loaded.
  • Exceptions, all implementing ExceptionInterface, with a stable message and the context in typed properties: WorkflowExecutionNotFound (executionId), WorkflowTypeMismatch (executionId, expectedType, actualType), WorkflowRepositoryNotDeclared, ReservedStubMethodName (workflowClass, method).

Tasks of tasks.md completed: 1.4 and 1.5. Task 1.2 is partly done (WorkflowRepository, #[AsWorkflowRepository], create() and get() for the in-memory run catalogue); the "done when" line "get() fails at once for an unknown execution" holds for the catalogue.

Decisions

  • The constructor of WorkflowRepository takes the run catalogue only and is final, so a subclass cannot override it. The optional WorkflowDefinitionLoader parameter is gone: the loader holds no state and get() creates one where it needs it.
  • When the catalogue reports a type different from the repository's, including UnknownWorkflow (what TemporalWorkflowRunCatalog returns when the type is missing), get() throws WorkflowTypeMismatch on every backend. A test pins it with a catalogue double that reports UnknownWorkflow.
  • #[AsWorkflowRepository] is read on the class itself and is not inherited (the OpenSpec spec does not say otherwise): a subclass of a declared repository raises WorkflowRepositoryNotDeclared. A test pins it.
  • "Unknown" in get() means unknown to the catalogue. The in-memory catalogue is empty in a process that did not run the workflow, and the DBAL, Illuminate and Magento catalogues depend on the projection being fed; the conformance test of task 1.1 must exercise every backend.

Checked

  • The new tests failed before the change and pass after it.
  • Unit suite: 3290 tests, OK. PHPStan: no errors. Psalm: no errors (--threads=1). php-cs-fixer dry-run, file by file: nothing to fix.
  • Not run: the tests that need Temporal, a database server or a Magento install. get() is exercised on the in-memory catalogue only; the DBAL, Illuminate and Temporal catalogues are not covered by a repository test yet.

Not in this pull request

  • WorkflowClientPort and its conformance test case (1.1), and the stub's start(), execute(), signal, query, update, result(), cancel() and terminate() (1.3). The constructor takes the run catalogue only and is final; the port arrives later as a collaborator and leaves this constructor as it is.
  • create() does not take WorkflowStartOptions yet. The stub has nowhere to keep them until start() exists, and adding an optional parameter later is not a breaking change.
  • Registration on the hosts, the #[RepositoryFor] resolvers and the call to ReservedStubMethods at boot (4.x), journal changes (1.6, 1.7), Temporal and documentation.
  • No breaking change, so no UPGRADE entry.

Refs #974

@gplanchat
gplanchat merged commit f8bb510 into main Oct 9, 2026
45 checks passed
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.

1 participant