The `ansible/` package uses an `sshRunner` interface to decouple module implementations from real SSH connections. `mock_ssh_test.go` provides `MockSSHClient` with:
- Interface compliance: the publisher's `Name()` is non-empty and `Publish` with a nil config does not panic.
---
## Coding Standards
### Language
Use **UK English** in all documentation, comments, identifiers, log messages, and error strings:
- colour (not color)
- organisation (not organization)
- centre (not center)
- behaviour (not behavior)
- licence (noun, not license)
### Strict Types
Every Go file must use strict typing. Avoid `any` at API boundaries where a concrete type is knowable. `map[string]any` is acceptable for Ansible task arguments and YAML-decoded data where the schema is dynamic.
### Error Handling
Use the `core.E` helper from `forge.lthn.ai/core/go` for contextual errors:
```go
return core.E("ansible.Executor.runTask", "failed to upload file", err)
```
For packages that do not import `core/go`, use `fmt.Errorf` with `%w`:
Error strings must not be capitalised and must not end with punctuation (Go convention).
### Import Order
Three groups, each separated by a blank line:
1. Standard library
2.`forge.lthn.ai/core/...` packages
3. Third-party packages
```go
import (
"context"
"fmt"
"forge.lthn.ai/core/go/pkg/io"
"gopkg.in/yaml.v3"
"golang.org/x/crypto/ssh"
)
```
### File Headers
Source files do not require a licence header comment beyond the package declaration. The `devkit/` package uses a trailing `// LEK-1 | lthn.ai | EUPL-1.2` comment; maintain this convention in `devkit/` files only.
### Interface Placement
Define interfaces in the package that consumes them, not the package that implements them. The `Builder`, `Publisher`, `Signer`, `Generator`, `Hypervisor`, and `ImageSource` interfaces each live in the package that calls them.
---
## Conventional Commits
All commits follow the Conventional Commits specification.
All source files are licensed under the **European Union Public Licence 1.2 (EUPL-1.2)**. Do not introduce dependencies with licences incompatible with EUPL-1.2. The `github.com/kluctl/go-embed-python` dependency (Apache 2.0) and `golang.org/x/crypto` (BSD-3-Clause) are compatible. Verify new dependencies before adding them.
- HTTPS authentication is not supported on the Forge instance; SSH is required.
---
## Adding a New Module to ansible/
1. Add the module name(s) to `KnownModules` in `types.go`.
2. Implement a function `executeModuleName(ctx, ssh, args, vars) TaskResult` in `modules.go`.
3. Add a `case "modulename":` branch in the dispatch switch in `executor.go`.
4. Add a shim to `mock_ssh_test.go`'s `sshRunner` interface (if the module requires file operations, use `sshFileRunner`).
5. Write tests in `modules_*_test.go` using the mock infrastructure. Cover at minimum: success case, changed vs. unchanged, argument validation failure, and SSH error propagation.
## Adding a New Release Publisher
1. Create `release/publishers/myplatform.go`.
2. Implement `Publisher`:
-`Name() string` — return the platform name.
-`Publish(ctx, release, pubCfg, relCfg, dryRun) error` — when `dryRun` is true, log intent and return nil.
3. Register the publisher in `release/config.go` alongside existing publishers.
4. Write `release/publishers/myplatform_test.go` with dry-run tests. Follow the pattern of existing publisher tests: verify command arguments, generated file content, and interface compliance.
## Adding a New Builder
1. Create `build/builders/mylang.go`.
2. Implement `Builder`:
-`Name() string`
-`Detect(fs io.Medium, dir string) (bool, error)` — check for a marker file.
-`Build(ctx, cfg, targets) ([]Artifact, error)`
3. Register the builder in `build/buildcmd/`.
4. Write tests verifying `Detect` (marker present/absent) and `Build` (at minimum with a mock `io.Medium`).