# Contributing to Oikos

Developer guide for the Oikos codebase. If you are a homelab client consuming
Oikos, see [CLIENTS.md](CLIENTS.md). If you are an AI agent working on the
repo, see [.agents/dev/CONTRIBUTING.md](.agents/dev/CONTRIBUTING.md).

## Dev setup

- **Go 1.26+** (see `go.mod` for pinned version)
- **PostgreSQL with TimescaleDB** — the compose stack includes `timescale/timescaledb:2.17.2-pg16`
- **Docker** for the full dev stack
- The control-room SPA and desktop app live in their own repo —
  [dtoro/oikos-web](https://git.hubris.network/dtoro/oikos-web) (Node 22+
  there); this repo is backend-only since the hexagonal refactor Phase 1

```bash
# Start dependencies (Postgres + Redis). api/nomos require a shared bearer
# token — no dev-open bypass — so set one even for local dev.
OIKOS_MCP_BEARER_TOKEN=dev-token docker compose --profile dev up -d

# Run all tests
make test

# Run integration tests (needs compose Postgres)
make test-db

# Build the binary
make build

# SPA dev server (own repo — dtoro/oikos-web)
cd ~/Projects/oikos-web/web && OIKOS_API_TOKEN=dev-token npm run dev

# Desktop app (macOS — also in the oikos-web repo)
cd ~/Projects/oikos-web && make install
```

### Desktop app

The desktop app's auth (Authentik login → keychain-persisted token) and
auto-update (Gitea releases every 6 hours) moved with it to
[dtoro/oikos-web](https://git.hubris.network/dtoro/oikos-web) — see that
repo's README. Auto-update now tracks oikos-web releases; builds installed
before the split need one manual reinstall.

## Project structure

```
cmd/oikos/          Single-binary entry point
cmd/nomos/          Nomos MCP client gateway
cmd/webhook/        Gitea deploy-webhook receiver (push-to-deploy on mac-mini)
internal/           All Go packages
  core/             Hexagon core ([ADR 0016](docs/adr/0016-hexagonal-ports-adapters.md)):
                    domain/ (pure model, stdlib only), ports/ (driven-port
                    interfaces), app/ (use-case services — EntityService,
                    RelationshipService, SignalService, ObservationService,
                    KnowledgeService, LearningService, etc.) — populated by
                    the phased refactor; core may not import adapters,
                    enforced by depguard (verified Phase 9 audit)
  adapters/         Ports' implementations: postgres/ (pool, migrations, seeds,
                    incl. knowledge seed ingest, sqlcgen, repos — EntityRepo,
                    RelRepo, OntologyRepo, SeedRepo, EntityReader, SignalRepo,
                    MetricsRepo, etc.), ssh/ (CommandExecutor via actuator +
                    Provisioner for pct/qm), remote/ (TargetResolver via
                    internal/remote), probes/ (Checker implementations per
                    probe kind: HTTP, TCP, ping, DNS, SSH)
  httpapi/          REST server (OpenAPI-generated) — driving adapter
  mcp/              MCP tool implementations — driving adapter
  scheduler/        Observe loop, coverage sweep — driving adapter (moves
                    to ObservationService/SignalService in Phase 5)
  execworker/       Execution worker — driving adapter
  actuator/         SSH execution — consumed by adapters/ssh
  remote/           Target resolution — consumed by adapters/remote
  audit/            Audit report helpers
  observability/    Event/Audit recorder helpers
api/openapi.yaml    API contract — the source of truth for endpoints
migrations/         Forward-only SQL migrations (TimescaleDB)
seeds/              Bootstrap YAML: ontology, inventory, policy, knowledge
compose/            Dockerfiles + Caddy config
scripts/            Deploy, watchdog, rollback
checks/             Host health-check scripts run over SSH by the scheduler
tools/              Client auto-setup scripts (checks)
nomos/              Nomos config, persona, skills
.agents/            Agent instruction files + skills
plans/              Design documents
docs/adr/           Architecture decision records
docs/operations/    Runbooks (rollback, etc.)
```

## Commands

| Command | Purpose |
|---------|---------|
| `make build` | Build `oikos` binary |
| `make test` | Run all tests with race detection |
| `make test-db` | Run integration tests against compose Postgres |
| `make lint` | `go vet` + `golangci-lint` |
| `make generate` | Regenerate OpenAPI + sqlc code |
| `make generate-check` | CI drift guard — fail if generated code is stale |
| `make migrate` | Apply DB migrations |
| `make seed` | Ingest seeds into DB |
| `make export` | Export DB state to YAML seeds |
| `make dev` | Start compose dev stack |
| `make clean` | Remove binary + test cache |
| `make webhook` | Build `cmd/webhook` (deploy-webhook receiver) |
| `make tidy` | `go mod tidy` |

## Conventions

### APIs are OpenAPI-first

The REST API is defined in `api/openapi.yaml`. Server code is generated with
`oapi-codegen` into `internal/httpapi/gen/`. To add an endpoint:

1. Add the path + schema to `api/openapi.yaml`
2. Run `make generate`
3. Implement the handler in `internal/httpapi/impl.go`
4. Add tests in `internal/httpapi/api_test.go`

Never hand-edit `internal/httpapi/gen/api.gen.go`.

### Database access is sqlc-first

SQL queries live in `internal/adapters/postgres/queries/*.sql`. Go code is generated with
`sqlc` into `internal/adapters/postgres/sqlcgen/`. Config in `sqlc.yaml`.

- Queries target pgx/v5 with UUID + timestamptz overrides
- Never hand-edit generated sqlc code

### Migrations are forward-only

SQL migrations live in `migrations/` as `NNN_name.up.sql`. There are no down
migrations (see [ADR 0008](docs/adr/0008-forward-only-migrations.md)).
Migrations are idempotent where possible (`IF NOT EXISTS`, `DO $$` blocks).

To add a migration:

1. Create `migrations/NNN_name.up.sql` with the next sequence number
2. Write the DDL
3. Run `make migrate` to apply

### Seeds are DB-generated

`seeds/*.yaml` are the bootstrap files used by `oikos seed`. After making
changes via the API, run `make export` to regenerate the seed files. These
files are version-controlled and serve as DR fallback.

### Writing style

Follow [.agents/shared/writing-style.md](.agents/shared/writing-style.md).
Documentation is reference prose, not marketing. Banned vocabulary includes
"robust", "seamless", "leverage", "utilize", "delve", "cutting-edge".

### Risk classification

Every mutation is classified against `seeds/policy.yaml` before execution.
Four risk classes: `read_only`, `reversible_low`, `config_mutation`,
`destructive`. The classifier can only lower autonomy relative to policy,
never raise it. When in doubt, escalate.

## CI

Gitea Actions runs on push to `main` and pull requests (`ci.yml`):

1. `go vet` + `golangci-lint` + `govulncheck`
2. Generated code drift check (`make generate-check`)
3. Build (`go build ./...`)
4. Test with race detector + coverage
5. Docker build verification (no push)

Coverage gates: policy + learning packages ≥ 80%, others ≥ 60%.

## PR workflow

1. Create a branch from `main`
2. Make changes, write tests
3. Run `make lint test generate-check`
4. Commit with a message following: problem → change → risk → verification
5. Push to Gitea; CI gates PRs on green

## Secrets

Secrets are managed by Infisical (primary) with SOPS as DR fallback. Never
hardcode secrets. Use environment variables from `.env` for local dev.
The `.env` and `.infisical-credentials` files are gitignored.

## Related

- [OIKOS.md](.agents/OIKOS.md) — operating model, OODA loop, ontology
- [CLIENTS.md](CLIENTS.md) — for homelab clients consuming Oikos
- [docs/adr/](docs/adr/) — architecture decision records