Skip to content

Contributing Guidelines

Contributing Guidelines

Branch Strategy

  • Main branch: develop
  • Feature branches: feature/<short-description>
  • Bug fixes: fix/<short-description>
  • All PRs target develop

Code Style

Follow standard Go conventions enforced by gofmt and golint:

Terminal window
gofmt -w ./...
golint ./...
go vet ./...
  • Package names: lowercase, single word (e.g., service, layers, auth)
  • Exported names: CamelCase
  • Unexported names: camelCase
  • Error variables: ErrSomething
  • Constants: CamelCase for exported, camelCase for package-level

Error Handling

Always wrap errors with context:

// βœ… Good β€” context preserved
if err := db.FindOne(ctx, filter, &result); err != nil {
return fmt.Errorf("FlowService.GetById: find flow %s: %w", id, err)
}
// ❌ Bad β€” context lost
if err != nil {
return err
}

Never swallow errors silently. If ignoring an error is intentional, add a comment:

_ = cache.Set(key, value) // best-effort cache; failure is non-critical

Logging

Use the injected zerolog logger. Never use fmt.Println or log.Printf in service code:

type MyService struct {
Logger zerolog.Logger `container:"inject"`
}
func (s *MyService) DoWork(id string) {
s.Logger.Info().Str("id", id).Msg("starting work")
// ...
s.Logger.Error().Err(err).Str("id", id).Msg("work failed")
}

Log levels:

  • Debug β€” detailed tracing (disabled in production)
  • Info β€” normal operations
  • Warn β€” recoverable issues
  • Error β€” failures requiring investigation

MongoDB Operations

Always filter by CompanyId β€” missing this filter causes cross-tenant data leakage:

// βœ… Correct
filter := bson.M{"companyId": companyId, "_id": id}
// ❌ Never do this β€” leaks data across tenants
filter := bson.M{"_id": id}

Use the repo layer for list queries to get consistent pagination:

query := s.Repo.Select("*").From("flows").
Where("companyId", "=", companyId).
Where("deleted", "=", false).
OrderBy("createdAt", "desc").
Limit(size).Offset(page * size)

Security Conventions

  1. Never log sensitive data β€” passwords, tokens, API keys, encryption keys must never appear in logs:

    // ❌ Never
    s.Logger.Info().Str("token", apiKey).Msg("authenticating")
  2. Always use encrypted KV for secrets β€” use the KV store’s encryption, not plain string storage.

  3. Validate all input β€” use struct validation tags and explicit checks:

    if len(input) == 0 {
    return ErrInputRequired
    }
    if len(input) > MaxInputLength {
    return ErrInputTooLong
    }
  4. Parameterize all queries β€” never concatenate user input into MongoDB queries or SQL strings.

  5. Audit significant actions β€” any create/update/delete of user-facing entities must call the audit logger:

    s.AuditLogger.LogEntityAction(ctx, audit.ActionFlowCreate, flow)

Testing Requirements

  • External calls β†’ fixture tests: Any module or service that calls an external API must have a fixture-based test (record once, replay deterministically).
  • Pure logic β†’ unit tests: Data transformations, expression evaluation, and business rules should have direct unit tests.
  • Minimum coverage: New services should have at least 70% test coverage.
Terminal window
# Run all tests
go test ./...
# Run with coverage
go test -cover ./pkg/service/... -coverprofile=coverage.out
go tool cover -html=coverage.out

Module Versioning

When a module’s argument schema changes in a breaking way:

  1. Increment the module’s version constant
  2. Implement the Migrate(fromVersion int, args map[string]any) map[string]any method
  3. Register the migration in ModuleMigrationService

The migration service runs automatically at startup and updates existing flowlet args to the new schema.

PR Checklist

Before opening a PR:

  • gofmt -w ./... applied
  • go vet ./... passes
  • All existing tests pass: go test ./...
  • New code has tests
  • Sensitive data not in logs
  • All MongoDB queries filter by companyId
  • Audit logger called for entity mutations
  • Module catalog CSV updated (if new module added): go run cmd/modulescsv/main.go
  • Environment variable documented in internal/setup/environment.md (if new var added)