This document is for agentic coding tools operating in this repository.
A Temporal worker implementing preprocessing and postbatch workflows for the CVA-Enduro digital preservation pipeline.
- Module:
github.com/artefactual-sdps/cva-enduro-workflows - Go version: 1.24.13
- Key packages:
internal/workflows,internal/activities,internal/tasks,internal/types,internal/enums
# Build
go build ./...
# Run all tests (preferred — uses gotestsum)
make test
# Run tests with the race detector
make test-race
# Run all tests in a single package
go test ./internal/workflows/...
go test ./internal/activities/...
# Run a single workflow suite test
go test ./internal/workflows/... -run 'TestPreprocessing/TestHappyPath' -v
# Run a single activity table-driven subtest (spaces in name become underscores)
go test ./internal/activities/... -run 'TestCreateCSV_Execute/writes_CSV_with_two_SIPs' -v
# Lint (golangci-lint with --fix)
make lint
# Format long lines (golines, max 120 chars)
make golines
# Security scan (gosec)
make gosec
# Run everything pre-commit (lint, test-race, golines, gosec, shfmt)
make pre-commit
# Regenerate enums after editing internal/enums/*.go source files
make gen-enums
# Check go.mod is tidy
go mod tidy -diff
internal/enumsis excluded from test runs — it is entirely generated code.
- gofumpt with
extra-rules: true. Usegofumpt(notgofmt) to format files. - golines enforces a max line length of 120 characters. Long lines must be split at natural break points.
Imports are organised into exactly three groups, separated by blank lines:
import (
// 1. Standard library
"fmt"
"time"
// 2. External dependencies
"github.com/google/uuid"
temporalsdk_workflow "go.temporal.io/sdk/workflow"
// 3. Internal packages (prefix: github.com/artefactual-sdps/cva-enduro-workflows)
"github.com/artefactual-sdps/cva-enduro-workflows/internal/config"
"github.com/artefactual-sdps/cva-enduro-workflows/internal/tasks"
)Inline comments inside import blocks are forbidden (no-inline-comments: true).
Temporal SDK packages must be aliased. No unaliased Temporal imports are permitted. Required aliases:
| Package pattern | Alias |
|---|---|
go.temporal.io/sdk/workflow |
temporalsdk_workflow |
go.temporal.io/sdk/activity |
temporalsdk_activity |
go.temporal.io/sdk/client |
temporalsdk_client |
go.temporal.io/sdk/worker |
temporalsdk_worker |
go.temporal.io/sdk/temporal |
temporalsdk_temporal |
go.temporal.io/sdk/testsuite |
temporalsdk_testsuite |
go.temporal.io/sdk/interceptor |
temporalsdk_interceptor |
go.temporal.io/sdk/contrib/<pkg> |
temporalsdk_contrib_<pkg> |
go.temporal.io/api/<pkg> |
temporalapi_<pkg> |
- Exported functions and methods must have doc comments.
- Comments must have line breaks at 80 characters or less, unless a line contains a very long URL or other string with no natural break points.
- Workflow structs use PascalCase and implement an
Executemethod. - Constructor functions are
New<Type>(...). - Request/result types are
<WorkflowName>Requestand<WorkflowName>Result. - Activity name constants are
<Name>Namestrings (e.g.CreateCSVName). - Enum types live in
internal/enums/and are generated bygo-enum. Edit the source file (e.g.task_outcome.go) then runmake gen-enums; never edit the*_enum.gogenerated file directly. - The unexported
withFilesysOpts(ctx, duration)helper inworkflows.gosets activity options with aScheduleToCloseTimeoutandMaximumAttempts: 1.
tasks.Task records a single preservation activity:
type Task struct {
Name string
Outcome enums.TaskOutcome
Message string
StartedAt time.Time
CompletedAt time.Time
}Construction and completion:
// Create at the start of an activity call — use workflow clock, not time.Now().
t := tasks.New(temporalsdk_workflow.Now(ctx), "Upload ContainerMetadata.xml")
// After the activity returns, complete with outcome and human-readable message.
t.Complete(temporalsdk_workflow.Now(ctx), enums.TaskOutcomeSuccess, "Successfully uploaded file")- Always use
temporalsdk_workflow.Now(ctx)for timestamps inside workflow code. enums.TaskOutcomeconstants:TaskOutcomeUnspecified,TaskOutcomeSuccess,TaskOutcomeSystemFailure,TaskOutcomeValidationFailure.
Result helpers on *<Workflow>Result:
Workflow result types expose two unexported methods for task lifecycle management:
// completeTask calls t.Complete with OutcomeSuccess and appends to PreservationTasks.
func (r *PreprocessingResult) completeTask(ctx, task, message)
// systemError calls t.Complete with OutcomeSystemFailure, sets r.Outcome = OutcomeSystemError,
// appends to PreservationTasks, and returns (r, nil) — the error is absorbed into the result.
func (r *PreprocessingResult) systemError(ctx, task, msg, err) (*PreprocessingResult, error)Always append tasks via these helpers so PreservationTasks is always populated
even when a step fails, and the workflow itself returns nil error on activity
failures (the caller inspects result.Outcome).
- Wrap errors with
fmt.Errorf("context: %w", err). - Use a short, lowercase description of the operation as context, e.g.
"upload ContainerMetadata.xml file: %w". - On activity failure in a workflow, call
result.systemError(...)which absorbs the error into the result and returns(result, nil)— never propagate activity errors as workflow errors. - Only return a non-nil workflow error for unrecoverable structural problems (e.g. bad parameters), not for activity execution failures.
- Workflow structs hold
cfg config.Config(and nothing else that varies per-run). - Use
withFilesysOpts(ctx, duration)for all filesystem/bucket activity calls. - Workflow test files use
package workflows_test(external test package). - Register workflows and activities via
registerXWorkflowhelpers incmd/worker/workercmd/cmd.go. - A workflow can return a result or an error, but not both at the same time
- Use
testify/suitecombined withtemporalsdk_testsuite.WorkflowTestSuite. - Provide
SetupWorkflowTest(cfg config.Config)to create the environment, open the bucket, register activities, and construct the workflow under test. - Provide
TearDownTest()to close the bucket. - Mock activities:
s.env.OnActivity(name, mock.AnythingOfType("*context.timerCtx"), params).Return(result, err). - Omitting an
OnActivitymock causes the test to fail if that activity is called — use this to implicitly assert activities are not reached. - Always call
s.True(s.env.IsWorkflowCompleted())before reading the result. - On activity failure, assert
result.Outcome == OutcomeSystemErrorand inspectresult.PreservationTasks—GetWorkflowResultwill returnnilerror.
- Use standard
go testtable-driven tests witht.Parallel()at both the top-level function and each sub-test. - Use
gotest.tools/v3/assert(not testify) for assertions. - Use real in-memory or temp-dir buckets (
"mem://"or"file:///"+t.TempDir()). - Activity test files use
package activities_test.