Skip to content

Latest commit

 

History

History
218 lines (161 loc) · 7.68 KB

File metadata and controls

218 lines (161 loc) · 7.68 KB

Agent Guidelines

This document is for agentic coding tools operating in this repository.

Repository overview

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, lint, and test commands

# 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/enums is excluded from test runs — it is entirely generated code.


Code style and formatting

Formatter

  • gofumpt with extra-rules: true. Use gofumpt (not gofmt) to format files.
  • golines enforces a max line length of 120 characters. Long lines must be split at natural break points.

Import grouping (gci)

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).

Import aliases (importas)

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>

Commentary

  • 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.

Naming conventions

  • Workflow structs use PascalCase and implement an Execute method.
  • Constructor functions are New<Type>(...).
  • Request/result types are <WorkflowName>Request and <WorkflowName>Result.
  • Activity name constants are <Name>Name strings (e.g. CreateCSVName).
  • Enum types live in internal/enums/ and are generated by go-enum. Edit the source file (e.g. task_outcome.go) then run make gen-enums; never edit the *_enum.go generated file directly.
  • The unexported withFilesysOpts(ctx, duration) helper in workflows.go sets activity options with a ScheduleToCloseTimeout and MaximumAttempts: 1.

Tasks (internal/tasks)

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.TaskOutcome constants: 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).


Error handling

  • 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 patterns

  • 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 registerXWorkflow helpers in cmd/worker/workercmd/cmd.go.
  • A workflow can return a result or an error, but not both at the same time

Testing

Workflow tests

  • Use testify/suite combined with temporalsdk_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 OnActivity mock 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 == OutcomeSystemError and inspect result.PreservationTasksGetWorkflowResult will return nil error.

Activity tests

  • Use standard go test table-driven tests with t.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.