The engine took a PORT and the SDK an Options.HostPort, so neither end
could use a unix socket for traffic that never leaves the host. The
field name was not the only obstacle: luxfi/zap bound net.Listen("tcp",
":port") and reached a peer with net.DialTimeout("tcp", addr), so a path
was refused at the dialer with "missing port in address" and could not
be bound at all. That is fixed upstream (luxfi/zap Address + Network);
this pins the commit until it is tagged.
EmbedConfig.Address, Embedded.Address, ServeGated(addr) and
client.Options.Address each take ONE address, because a port is a detail
of one transport rather than a thing every listener has. A path binds or
dials a unix socket; anything else is host:port.
TestDialRequiresHostPort became TestDialRequiresAddress: Dial needs an
address, and asserts a socket path is refused for being absent rather
than for being portless. TestE2E_UnixSocket runs a real workflow over a
socket end to end, and TestE2E_UnixSocketRebind proves the socket is
unlinked on stop so a restart binds cleanly.
Co-authored-by: Hanzo Dev <[email protected]>
Both interfaces now carry an unexported sentinel method sealed() so
external packages cannot implement them. A fork that shadows the
workflow package will fail to satisfy the interface at compile
time, preventing silent divergence in selector / coroutine-scheduler
semantics.
The worker package's signalChan wrapper had to go — it lived
outside the workflow package and so cannot satisfy sealed().
Replaced with workflow.NewSignalChannel(name, buffer), which
returns the canonical sealed chanImpl directly. The name is
carried on the channel itself, so the wrapper was cosmetic.
Added pkg/sdk/workflow/channel_sealed_test.go asserting the
canonical impl satisfies the sealed interfaces.
Adds every symbol hanzoai/base/plugins/tasks and hanzoai/commerce
call today that was missing from pkg/sdk/*: SignalWithStartWorkflow,
GetWorkflow, QueryWorkflow, CheckHealth, WorkflowRunGetOptions +
WorkflowRun.GetWithOptions, ExecuteChildWorkflow + ChildWorkflowFuture,
LocalActivityOptions + WithLocalActivityOptions + ExecuteLocalActivity
(Phase-1 remote-path), worker.InterruptCh(), worker.Options rate-limit
fields wired via golang.org/x/time/rate.
New opcodes 0x0066 signalWithStartWorkflow / 0x0067 queryWorkflow
land here; frontend-side handlers were wired in PR-A via the
translate.go dispatch table.
Closes red-review §2 gap table. Dry-run base migration compiles.
NewTimer used to collapse any duration to zero — production workflows
with schedule-level timers (24h dunning, weekly roll-up) fired on
the next dispatch loop tick. Selector busy-spun at 1 kHz.
Real timer.Timer behind each workflow.NewTimer; Selector.Await
blocks on fan-in over Future.readyCh channels. StubEnv becomes
test-only; the production dispatch env in pkg/sdk/worker uses the
real impl.
Closes red-review §5.2 and §5.9.
Replaces StubEnv use in dispatch.go with workerEnv, a real
CoroutineEnv implementation. Activity dispatch now goes over the
wire via ScheduleActivity (opcode 0x006B) + long-poll
WaitActivityResult (0x006C), not a pre-populated in-memory map.
NewTimer uses time.NewTimer in a per-timer goroutine; Select uses
a fan-in wake channel with a 20ms watcher poll (no per-iteration
allocation). Root cancel scope propagates to timers, activity polls,
and child scopes on return.
Workflow return errors are no longer swallowed — non-nil fn returns
get encoded via temporal.Encode into a single FailureCommand entry
in the commands envelope so the frontend can fail the execution
per its retry policy.
Phase-1 note documented in struct comment: every dispatch re-runs
the workflow from the top with the same input — activities must be
idempotent. Phase 2 replaces this with event-sourced replay
without changing the public surface.
Adds WorkerTransport.ScheduleActivity / WaitActivityResult /
StartChildWorkflow with v1 JSON-in-envelope bodies. Schema
schema/tasks.zap gains the 0x006B/0x006C struct definitions.
Ports the workflow-code-facing API that base/plugins/tasks and
commerce/infra rely on today, with zero go.temporal.io or grpc
imports. Wire shape mirrors Temporal exactly so call-site migration
is a pure import-path swap.
Package layout (pkg/sdk/workflow):
env.go — CoroutineEnv interface, the one seam the worker
(pkg/sdk/worker, separately) must satisfy; Info,
SelectCase, CancelFunc. NewContextFromEnv is the
single entry point user ctx flows through.
context.go — Context interface (Deadline/Done/Err/Value), ctx
chain with WithCancel / WithValue / WithActivityOptions;
GetLogger, Now, Sleep, GetInfo.
future.go — Future + settleable impl. NewFuture / Settle split
so both the worker and the in-memory stub drive
completion through the same concrete. EncodePayload
is the Phase-1 JSON codec; Phase 2 swaps for ZAP.
channel.go — ReceiveChannel / Channel; unbuffered + buffered
chanImpl backing both user channels and signal
queues. Send/Receive/ReceiveAsync/Close.
selector.go — Selector with AddFuture / AddReceive / AddDefault;
Select delegates to env.Select and dispatches to
the matched callback.
timer.go — NewTimer schedules a durable timer Future.
signal.go — GetSignalChannel returns shared-by-name signal queues.
activity.go — ActivityOptions mirror of Temporal shape;
WithActivityOptions attaches per-call opts;
ExecuteActivity reads opts from ctx and dispatches.
reflect.go — Sole reflect use site (activity name resolution).
stub.go — StubEnv: synchronous, in-memory CoroutineEnv for
unit-testing workflow code without a worker.
OnActivity(...).Return(...), SignalAsync, AdvanceClock,
Cancel. Used throughout workflow_test.go.
workflow_test.go — 27 tests exercising Context, cancel scopes,
Now/Sleep, timers, activities (success/error/nil),
signal channels, user channels, Selector (future/
receive/default), and an end-to-end AgentTaskWorkflow-
shaped test that mirrors base/plugins/tasks.
Phase 1 determinism caveats (documented in env.go):
* No event-sourced replay yet — crashes restart from the top with
the original input. Same idempotency contract as Temporal.
* Payloads encoded as JSON, not the canonical ZAP codec.
* time.Now / rand / goroutines inside workflow code remain banned;
CI linter comes in a follow-up.
go vet clean. 27 tests pass including -race.
go list -deps reports zero go.temporal.io or google.golang.org/grpc.