Replace internal task tracking (TODO.md, FINDINGS.md) with structured documentation in docs/. Trim CLAUDE.md to agent instructions only. Co-Authored-By: Virgil <virgil@lethean.io>
5.9 KiB
Development Guide — go-crypt
Prerequisites
- Go 1.25 or later (the module declares
go 1.25.5). - A Go workspace (
go.work) that resolves the local replace directives forforge.lthn.ai/core/go(at../go) andforge.lthn.ai/core/go-store(at../go-store). If you are working outside the full monorepo, editgo.modreplace directives to point to your local checkouts. - No C toolchain, CGo, or system libraries are required.
Build and Test Commands
# Run all tests
go test ./...
# Run with race detector (always use before committing)
go test -race ./...
# Run a single test by name
go test -v -run TestName ./...
# Run tests in a specific package
go test ./auth/...
go test ./crypt/...
go test ./trust/...
# Static analysis
go vet ./...
# Run benchmarks
go test -bench=. -benchmem ./crypt/...
go test -bench=. -benchmem ./trust/...
There is no build step — this is a library module with no binaries. The
go vet ./... check must pass cleanly before any commit.
Repository Layout
go-crypt/
├── auth/ Authentication package
├── crypt/ Cryptographic utilities
│ ├── chachapoly/ Standalone ChaCha20-Poly1305 sub-package
│ ├── lthn/ RFC-0004 quasi-salted hash
│ ├── openpgp/ Service wrapper (core.Crypt interface)
│ ├── pgp/ OpenPGP primitives
│ └── rsa/ RSA OAEP-SHA256
├── docs/ Architecture, development, and history docs
├── trust/ Agent trust model and policy engine
├── go.mod
└── go.sum
Test Patterns
Tests use the github.com/stretchr/testify library (assert and require).
The naming convention follows three suffixes:
| Suffix | Purpose |
|---|---|
_Good |
Happy path — expected success |
_Bad |
Expected failure — invalid input, wrong credentials, not-found errors |
_Ugly |
Edge cases — panics, zero values, empty inputs, extreme lengths |
Example:
func TestLogin_Good(t *testing.T) { ... }
func TestLogin_Bad(t *testing.T) { ... }
func TestLogin_Ugly(t *testing.T) { ... }
Concurrency tests use t.Parallel() and typically spawn 10 goroutines via a
sync.WaitGroup. The race detector (-race) must pass for all concurrent tests.
Benchmark Structure
Benchmarks live in bench_test.go files alongside the packages they cover.
Benchmark names follow the BenchmarkFuncName_Context pattern:
func BenchmarkArgon2Derive(b *testing.B) { ... }
func BenchmarkChaCha20_1KB(b *testing.B) { ... }
func BenchmarkChaCha20_1MB(b *testing.B) { ... }
Run benchmarks with:
go test -bench=. -benchmem -benchtime=3s ./crypt/...
Do not optimise without measuring first. The Argon2id KDF is intentionally slow (~200ms on typical hardware) — this is a security property, not a defect.
Adding a New Cryptographic Primitive
- Add the implementation in the appropriate sub-package.
- Write tests covering
_Good,_Bad, and_Uglycases. - Add a benchmark if the function is called on hot paths.
- Update
docs/architecture.mdwith the algorithm reference entry. - Run
go vet ./...andgo test -race ./...before committing.
Adding a New Trust Capability
- Add the
Capabilityconstant intrust/trust.go. - Update
isRepoScoped()intrust/policy.goif the capability is repository-scoped. - Update the default policies in
loadDefaults()intrust/policy.go. - Add tests covering all three tiers.
- Update the capability table in
docs/architecture.md.
Coding Standards
Language
UK English throughout: colour, organisation, centre, artefact, licence (noun), license (verb), behaviour, initialise, serialise.
Go Style
declare(strict_types=1)is a PHP convention; Go has no equivalent. Use explicit type assertions and avoidanyexcept at interface boundaries.- Every exported function and type must have a doc comment.
- Error strings are lowercase and do not end with a full stop, per Go convention.
- Use the
core.E(op, msg, err)helper fromforge.lthn.ai/core/gofor contextual error wrapping:opis"package.Function",msgis a brief lowercase description. - Import groups: stdlib →
forge.lthn.ai/core→ third-party. Separate each group with a blank line.
Cryptography
- All randomness from
crypto/rand. Never usemath/randfor cryptographic purposes. - Use
crypto/subtle.ConstantTimeComparefor any comparison of secret material (MACs, hashes). The one exception islthn.Verify, which compares content identifiers (not secrets) and documents this explicitly. - Never log or return secrets in error messages. Error strings should be generic:
"invalid password","session not found","failed to decrypt".
Licence
All files are licenced under EUPL-1.2. Do not add files under a different licence.
Commit Convention
Commits follow the Conventional Commits specification:
type(scope): short imperative description
Optional body explaining motivation and context.
Co-Authored-By: Virgil <virgil@lethean.io>
Types: feat, fix, refactor, test, docs, chore.
Scopes match package names: auth, crypt, trust, pgp, lthn, rsa,
openpgp, chachapoly.
Examples:
feat(auth): add SQLite session store for crash recovery
fix(trust): reject empty ScopedRepos as no-access for Tier 2
test(crypt): add benchmark suite for Argon2 and ChaCha20
Forge Push
The canonical remote is forge.lthn.ai. Push via SSH only; HTTPS authentication
is not configured:
git push forge main
# remote: ssh://git@forge.lthn.ai:2223/core/go-crypt.git
Local Replace Directives
The go.mod contains:
replace (
forge.lthn.ai/core/go => ../go
forge.lthn.ai/core/go-store => ../go-store
)
Do not modify these paths. If you need to work with a different local checkout,
use a Go workspace (go.work) at the parent directory level rather than editing
the replace directives directly.