Commit dc60117
committed
Freeze v2 routing precedence and inheritance contract
Freeze v2 routing precedence and inheritance contract
Issue #78 freezes how every workflow-task and activity-task
routing target is resolved, snapped into the durable row it
belongs to, and inherited across retries, continue-as-new, and
parent-to-child transitions. Today the concrete logic lives in
RoutingResolver, WorkflowOptions, ActivityOptions,
ChildWorkflowOptions, WorkflowStub::start, WorkflowExecutor's
scheduleChildWorkflow / scheduleActivity / continueAsNew /
retryChildWorkflow paths, ScheduleManager, PhpClassScheduleStarter,
and TaskRepair, but "which input wins at which level", "when a
retry reuses the snapshot", "where snapped routing is exposed in
projections", and "what is intentionally deferred to matching,
sharding, and priority roadmap work" were not frozen as one
product contract. A subsystem that recomputes (connection, queue)
from class defaults after a snapshot is written, that widens
WorkflowOptions to carry additional fields, or that silently
rewrites snapped routing during retry/repair/continue-as-new is
therefore silently out of contract.
This lands the contract doc and a pinning test.
docs/architecture/routing-precedence.md:
- scopes the contract to resolution precedence, snapshot columns,
inheritance, retries, continue-as-new, schedule-triggered runs,
effective routing in projections, and the interaction with
compatibility
- freezes RoutingResolver as the sole authority on turning
per-call options and class defaults into a (connection, queue)
pair, and names the four resolver methods workflowConnection,
workflowQueue, activityConnection, activityQueue as the only
surface callers may use
- freezes the WorkflowOptions wire shape at exactly ?string
$connection and ?string $queue; additional fields are a
protocol-level change
- freezes the resolution precedence at workflow start: per-call
WorkflowOptions > workflow class $connection/$queue defaults >
null with config('queue.connections.<connection>.queue',
'default') tail
- freezes that workflow start options override workflow defaults
only for workflow tasks; WorkflowOptions does not propagate into
activity scheduling
- freezes the resolution precedence at child-workflow scheduling:
per-call ChildWorkflowOptions/WorkflowOptions > child workflow
class defaults > null with the same tail, and makes explicit
that child-call options override the child's own task routing,
not every activity inside that child
- freezes the resolution precedence at activity scheduling:
ActivityOptions > activity class defaults > parent run's snapped
routing > config('queue.default') tail; the resolver never walks
past the owning run row
- freezes the schedule-triggered-run precedence: schedule.connection
and schedule.queue flow into a WorkflowOptions value appended to
the start arguments, after which workflow start resolution
applies as above
- freezes the durable snapshot columns on workflow_runs,
workflow_tasks, activity_executions, and workflow_schedules, and
states that workflow-task rows and timer-task rows inherit from
$run->connection/$run->queue and activity-task rows inherit from
$execution->connection/$execution->queue without re-reading
class defaults
- freezes retry inheritance: activity retries reuse the execution's
snapped routing, child-workflow retries copy the failed child
run's snapped routing, and TaskRepair::repairRun /
recoverExistingTask paths reuse the run's snapshot; heartbeats
never change routing
- freezes continue-as-new inheritance: the continued run inherits
both routing and compatibility from the previous run by default,
with WorkflowOptions as the only override surface, and snapshots
inherited values on creation so later mutations to the original
run do not retroactively move the continued run
- freezes the dedicated-queue and same-server affinity patterns as
operator-visible contract behaviour preserved by the snapshot
and inheritance rules, including across the repair path
- freezes which projections expose effective routing for operator
diagnostics: RunDetailView, RunListItemView, and
OperatorQueueVisibility consume the snapshot and must not
recompute from class defaults
- freezes the independence of routing and compatibility as two
separate claim-time axes, so a routing override cannot silently
weaken the compatibility guarantee frozen in
docs/architecture/worker-compatibility.md
- freezes the config surface at queue.default,
queue.connections.<name>.queue, and workflows.v2.namespace, and
explicitly states the contract introduces no new environment
variables
- defers queue priority semantics, task-id sharding, retry-time
rerouting, and cross-namespace routing to follow-on roadmap
issues (#72, #83) and to future contract extensions so a future
phase extends this contract rather than redefining it
tests/Unit/V2/RoutingPrecedenceDocumentationTest.php:
- pins the frozen headings, terminology, authority class names,
durable column names, resolver method names, config keys,
cross-contract citations, and roadmap references
- pins the wire shape of WorkflowOptions at exactly (connection,
queue) via reflection, and pins the routing fields on
ActivityOptions and ChildWorkflowOptions including their
hasRoutingOverrides() surface
- pins the rule statements that workflow start options affect
workflow tasks only, child-call options affect the child's own
task routing only, activity-level defaults win for activity
executions, retries reuse the snapshot, continue-as-new inherits
both routing and compatibility, and snapshots are written at
creation
- pins the dedicated-queue / same-server affinity preservation
rule, the repair-path snapshot reuse, the projection contract
that Waterline-facing surfaces consume snapshots and never
recompute, the resolver's role as sole authority, the
"compatibility and routing are independent" rule, and the
explicit deferrals for priority and sharding
The routing precedence shift is a contract change, not a code
change: RoutingResolver, WorkflowOptions, ActivityOptions,
ChildWorkflowOptions, WorkflowStub, WorkflowExecutor,
ScheduleManager, PhpClassScheduleStarter, TaskRepair,
RunDetailView, RunListItemView, OperatorQueueVisibility,
WorkerCompatibilityFleet, and the workflow_runs, workflow_tasks,
activity_executions, and workflow_schedules tables are preserved
verbatim, and this document adds the rules that govern which of
their inputs wins at which level and when the resulting snapshot
is reused rather than recomputed.
Verified:
- bash scripts/check-public-boundary.sh (exit 0)
- vendor/bin/phpunit tests/Unit/V2/RoutingPrecedenceDocumentationTest.php
(26 tests, 129 assertions, OK) against PHP 8.5
- vendor/bin/phpunit across all eight V2 DocumentationTest suites
(165 tests, 1015 assertions, OK)
- vendor/bin/ecs check docs/architecture/routing-precedence.md
tests/Unit/V2/RoutingPrecedenceDocumentationTest.php (no errors)
Refs: #781 parent 8342eb8 commit dc60117
2 files changed
Lines changed: 1077 additions & 0 deletions
0 commit comments