Skip to content

Code conventions and implementation contracts

Code conventions and implementation contracts

Section titled “Code conventions and implementation contracts”

Status: Authoritative engineering convention reference.

This document owns durable code-level contracts for gondolier. AGENTS.md keeps a short checklist; this file carries the details future agents and reviewers should enforce.

  • Module path: git.rbtr.dev/laputacloudco/gondolier. All imports use this prefix.

  • Package naming: short, lowercase, single-word package names. No underscores, no camelCase.

  • Export policy: only export what the public API needs. Internal helpers go in unexported files or in the same package.

  • Error handling: explicit error checks on every error-producing call. No swallowed errors. Use %w for wrapping errors that callers might inspect with errors.Is. Return sentinel errors for expected failure modes.

  • Context propagation: every external call (database, forge API, HTTP, KMS) must accept and respect a context.Context. Pass request-scoped context through the entire call chain.

  • Logging: structured logging with consistent keys. Never log raw tenant tokens, OAuth secrets, or decrypted credential blobs. Use <REDACTED> as a placeholder when a credential field is relevant for debugging.

    log.Error().Str("tenant_id", t.ID).Str("token_preview", "<REDACTED>").Msg("forge API call failed")
  • Configuration: use env vars or a config file for deployment settings. No hardcoded secrets. Configuration loading must validate and fail fast on missing required values.

  • Envelope encryption: tenant forge tokens are envelope-encrypted with a per-tenant DEK, which is itself encrypted with a KMS-backed master key. See pkg/crypto/ for the implementation contract.
  • Never log plaintext: audit every log.*, fmt.*, slog.*, and zap.* call in any diff touching credentials. The gosec linter can help catch obvious patterns, but manual review is required.
  • Decryption scope: a decrypted token lives in memory only for the duration of one worker tick. Clear it from variables and slice buffers when done.
  • Credential rotation: rotating a tenant’s token must atomically replace the old encrypted blob. No window where both old and new tokens are valid.
  • Migrations: all database changes go through versioned migration files in migrations/. Migrations must be forward-compatible — new code must handle both old and new schema.
  • Null safety: use nullable types for columns that can be NULL. Do not use zero values to represent missing data.
  • Transactions: wrap multi-row, multi-table changes in transactions. Use explicit BEGIN, COMMIT, and ROLLBACK.
  • Connection pooling: configure MaxOpenConns, MaxIdleConns, ConnMaxLifetime, and ConnMaxIdleTime on every database connection. Never use zero values.
  • RESTful paths: resource-oriented URLs (/tenants, /tenants/:id/connections, /tenants/:id/repos). No verbs in paths.

  • Request/Response: all API responses have a consistent envelope:

    {"status": "ok|error", "data": {...}, "error": {"code": "...", "message": "..."}}
  • Pagination: all list endpoints support ?page=1&limit=50. Default limit is 50, max is 100.

  • Rate limiting: per-tenant rate limiting on API endpoints. Use a sliding window algorithm.

  • Idempotency: mutation endpoints accept an Idempotency-Key header. Responses for the same key are cached and returned without re-executing.

  • Tenant context: every request must carry a tenant context. The context propagates through all database queries, forge API calls, and internal calls. Verify tenant scope on every database operation.
  • Worker isolation: a worker handles one tenant at a time. It must not hold another tenant’s decrypted token in memory.
  • Error isolation: errors from one tenant’s operations must not affect other tenants. Wrap tenant-scoped errors in a tenant-scoped context.
  • Token scope: tokens are scoped to the minimum required permissions. On Gitea 1.23+, use granular OAuth2 scopes (read:repository, write:repository). On Forgejo, fall back to PAT with repo-scoped permissions.
  • Rate limit awareness: each forge instance has its own rate limit. Implement per-instance rate limiting with backoff on 429/5xx.
  • Timeline-based auto-merge detection: shunt’s merge queue works by detecting Forgejo’s merge_when_checks_succeed behavior. Do not rely on Forgejo events being delivered in a specific order.
  • Branch protection: shunt enforces merge queue policy through Forgejo branch protection rules (required status + push allow-list). The merge-queue status is written by the shunt gate workflow, not by this service.
  • Table-driven tests: all Go tests use table-driven test style.

    func TestSomething(t *testing.T) {
    tests := []struct{
    name string
    input string
    want int
    wantErr bool
    }{
    {"valid", "hello", 5, false},
    {"invalid", "", 0, true},
    }
    for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
    // ...
    })
    }
    }
  • Integration tests: database and forge API integration tests use test containers or a dedicated test database. Integration tests live in tests/integration/.

  • Mock interfaces: external dependencies (database, forge API, KMS) are accessed through interfaces. Tests use mock implementations.

  • Test coverage: aim for high coverage on business logic and edge cases. Integration tests are expected to cover critical paths.

  • Structured logging: use a structured logger (zap, slog, or zerolog) with consistent fields: tenant_id, repo, action, duration_ms.
  • Audit trail: all credential access, tenant changes, and forge API calls are logged for audit. Audit logs must not contain raw credentials.
  • Metrics: expose basic metrics (requests served, errors, queue depth) via an HTTP metrics endpoint at /metrics.
  • Health checks: expose /healthz (ready) and /readyz (healthy) endpoints.
  • CI must not skip the mq/** gate: the mq/** push trigger has no paths-ignore. Forgejo reports an empty changed-file set for newly created queue branches, so any paths-ignore would suppress the gate and strand the queue.
  • Workflow naming: CI workflows live in .forgejo/workflows/. The main CI is ci.yaml. The merge queue gate is shunt-merge-queue.yaml.
  • OpenID Connect: workflows use enable-openid-connect: true for keyless authentication to OpenBao. Do not add long-lived secrets to workflow files.

Productionization SOP: documentation and observability

Section titled “Productionization SOP: documentation and observability”

A feature is not production-ready until it is both documented and observable.

  • Documentation (design + implementation). Any non-trivial feature or system gets a durable doc that explains the design and the implementation, so the next engineer learns the model instead of reverse-engineering it. Keep AGENTS.md short and link the focused doc.
  • Observability (instrumentation / telemetry). Ship signals that make the feature’s behavior visible: primary action, success, failure/empty, and for safety-critical paths the protective action taken.
  • PR body. State Docs: <paths> (or Docs: N/A) and Telemetry: <events> (or Telemetry: N/A).