Files
zeekayandHanzo Dev 679ae61163 feat(transport): address the engine and the SDK, so both can use a socket
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]>
2026-07-28 16:27:19 -07:00

109 lines
4.1 KiB
Go

// Package client is the Hanzo Tasks workflow client. Native ZAP transport,
// zero go.temporal.io/* and zero google.golang.org/grpc imports.
//
// The wire is ZAP framed per schema/tasks.zap. Opcodes 0x0060-0x009F are
// owned by this package; 0x0050-0x005F stay reserved for the legacy
// one-shot/schedule surface owned by pkg/tasks.
//
// v1 wire note: request and response payloads are transported as a single
// bytes field holding JSON. Native ZAP serde for every RPC in schema/tasks.zap
// replaces JSON in a follow-up without changing opcodes.
package client
import (
"context"
"time"
"github.com/hanzoai/tasks/pkg/sdk/temporal"
)
// Options configures Dial. Fields mirror the upstream Temporal Options
// surface so callers can swap import paths without changing arguments.
type Options struct {
// Address is where the Hanzo Tasks frontend exposes its ZAP
// transport. A filesystem path dials a unix socket — the right shape
// for service-to-service traffic that never leaves the host, and one
// that needs no port. Anything else is a host:port TCP address.
// Required unless Transport is injected.
Address string
// Namespace scopes every RPC issued by the returned Client. Empty
// string is rewritten to "default" at Dial time.
Namespace string
// Identity is sent as the caller identity on long-poll / worker RPCs.
// Optional; defaults to "hanzo-tasks-sdk".
Identity string
// DialTimeout bounds the initial ZAP connect. Zero means "no bound".
DialTimeout time.Duration
// CallTimeout bounds a single request/response round trip. Zero
// means "no bound per call" — callers are expected to pass their
// own ctx deadline.
CallTimeout time.Duration
// Transport overrides the default ZAP transport. Primarily for
// testing — leave nil in production.
Transport Transport
// Token supplies the caller's IAM identity to an identity-gated Tasks
// frontend (a ServeGated listener running RequireIdentity). It is called
// per request so a short-lived client_credentials bearer refreshes
// transparently; the returned token rides every JSON-body RPC (client
// lifecycle + worker Subscribe) as auth_token, and the frontend scopes the
// request to the token owner's org. nil (default) sends no auth_token —
// correct for an ungated / loopback frontend. Binary worker respond frames
// stay task_token-gated and carry none.
Token TokenSource
}
// TokenSource returns the bearer to present to an identity-gated Tasks frontend.
// Use StaticToken for a fixed token; a client_credentials caller returns a fresh
// bearer per call (cache with expiry) so a long-lived worker never presents an
// expired identity.
type TokenSource func(ctx context.Context) (string, error)
// StaticToken adapts a fixed bearer to a TokenSource.
func StaticToken(tok string) TokenSource {
return func(context.Context) (string, error) { return tok, nil }
}
// StartWorkflowOptions mirrors the upstream StartWorkflowOptions struct
// for ExecuteWorkflow. Only the fields currently mapped onto the wire
// (schema/tasks.zap StartWorkflowRequest + Timeouts + RetryPolicy) are
// meaningful; unlisted fields are ignored by the v1 wire.
type StartWorkflowOptions struct {
// ID is the workflow ID. Empty string asks the server to generate
// one (returned as WorkflowRun.GetID()).
ID string
// TaskQueue is required; workers poll this queue for tasks.
TaskQueue string
// WorkflowExecutionTimeout is the total allowed wall-clock time
// across all runs (Continue-As-New chains).
WorkflowExecutionTimeout time.Duration
// WorkflowRunTimeout is the allowed wall-clock time for a single
// run.
WorkflowRunTimeout time.Duration
// WorkflowTaskTimeout bounds a single workflow-task attempt on a
// worker. Default on server is 10s.
WorkflowTaskTimeout time.Duration
// RetryPolicy overrides the workflow-level retry policy (distinct
// from per-activity retry).
RetryPolicy *temporal.RetryPolicy
// CronSchedule makes the workflow recurring. Empty string runs once.
CronSchedule string
// Memo is opaque metadata attached to the execution.
Memo map[string]any
// SearchAttributes are indexed by the visibility store.
SearchAttributes map[string]any
}