This directory is the working tree for the from-scratch OpenWatch rebuild in
Go. The existing Python backend lives in ../backend/ and remains in
production until the rebuild is ready to take over.
Status: Stage 0 (walking skeleton) complete. 18/18 specs at 100% under specter coverage --strict. 19-step Definition of Done passes end-to-end (see internal/server/api_signoff_test.go).
What this means: The toolchain is proven — every Stage-0 foundation (config layering, migrations, HTTPS + cert hot-reload + correlation propagation, audit, idempotency, license validation, RBAC, policy framework, queue, in-process worker, native RPM + DEB packaging, FIPS 140-3 build) is wired end-to-end and tested. It is not a working compliance scanner yet — that's Stage 2 (slice A: auth + add host; slice B: Kensa scan; slice C: historical posture).
All design work is in docs/. Read these before changing anything:
| Topic | File |
|---|---|
| Vision, goals, decisions | docs/openwatch_roadmap.md |
| Stage 0 plan (current) | docs/stage_0_walking_skeleton.md |
| Stage 1 audit plan | docs/stage_1_usage_audit.md |
| API design principles | docs/api_design_principles.md |
| Audit event taxonomy | docs/audit_event_taxonomy.md |
| Licensing foundation | docs/licensing_foundation.md |
| Policies-as-data | docs/policies_as_data.md |
| RBAC registry | docs/rbac_registry.md |
| Correlation ID propagation | docs/correlation_id_propagation.md |
Registries (source of truth for codegen):
| Registry | File |
|---|---|
| Audit events | audit/events.yaml |
| Error codes | api/error_codes.yaml |
| License features | license/features.yaml |
| Permissions + built-in roles | auth/permissions.yaml |
OpenAPI domain specs in api/ (4 full-fidelity + 11 skeletons + meta openapi.yaml).
- Go 1.25+ (auto-downloaded by toolchain if local Go is older; raised from
the originally-planned 1.22+ floor when
pressly/goose v3.27required 1.25) make,git
Optional (lands in later days):
golangci-lint(Day 1: lint target works without it, just skips)oapi-codegen,sqlc,redocly(Day 5: codegen — until then, the audit queries are hand-written but match what sqlc would produce)- A running PostgreSQL 15+ for
migrateand integration tests; integration tests ininternal/dbskip ifOPENWATCH_TEST_DSNis unset microsoft/go(Day 12: FIPS build)
# From this directory (app/)
make help # list all targets
make version # show what version metadata will be injected
make build # produces dist/openwatch
make test # run all Go tests
./dist/openwatch --version
./dist/openwatch check-config # uses defaults (silent if /etc/openwatch/openwatch.toml missing)
./dist/openwatch --config configs/openwatch.toml.example check-config
OPENWATCH_SERVER_LISTEN=0.0.0.0:9443 ./dist/openwatch check-config # env override
./dist/openwatch --listen 0.0.0.0:8000 check-config # flag override (wins over env)Config layering (highest precedence first):
- CLI flags (
--listen,--log-level) - Env vars (
OPENWATCH_<SECTION>_<KEY>) - TOML file (
--config, default/etc/openwatch/openwatch.toml) - Built-in defaults
Subcommands beyond check-config are stubbed until their day arrives:
serve (Day 4), migrate (Day 3).
app/
├── api/ # OpenAPI specs and error_codes.yaml registry
├── audit/ # events.yaml registry
├── auth/ # permissions.yaml registry (RBAC)
├── cmd/ # binaries (entry points)
│ └── openwatch/ # the main daemon
├── dist/ # build output (gitignored)
├── docs/ # design docs (the spec)
├── internal/ # Go packages, not importable outside this module
│ ├── config/ # (placeholder, Day 2)
│ ├── server/ # (placeholder, Day 4)
│ └── version/ # build-time metadata
├── license/ # features.yaml registry
├── .golangci.yml
├── .gitignore
├── Makefile
├── README.md # this file
├── go.mod
└── go.sum # (populated by `go mod tidy`)
Foundation packages (internal/audit/, internal/auth/, internal/correlation/,
internal/errors/, internal/license/, internal/log/, internal/policy/,
internal/queue/, internal/httpclient/) come online as their Stage 0 days arrive.
See docs/stage_0_walking_skeleton.md for
the 13-day plan and the 19-step Definition of Done.
| Day | Topic | Status |
|---|---|---|
| 1 | Repository scaffold | complete |
| 2 | Config + flags + TOML | complete |
| 3 | PostgreSQL + goose migrations | complete |
| 4 | HTTP server + chi + TLS + correlation propagation | complete |
| 5a | Audit foundation (migration + codegen + emit/writer/redact) | complete |
| 5b | OpenAPI codegen + endpoints (/health, :echo, /audit/events) | complete |
| 6 | Idempotency middleware (folded into Day 5b) | complete |
| 7 | Licensing foundation (JWT EdDSA + RequireFeature + owlicgen) | complete |
| 8 | RBAC registry | complete |
| 9 | Policies-as-data + queue correlation helpers | complete |
| 10 | Specter spec + AC coverage | complete (18/18 specs at 100% under strict mode) |
| 11 | Native packaging (RPM + DEB) | complete |
| 12 | FIPS build (Go 1.25 native GOFIPS140) |
complete |
| 13 | Documentation, demo, sign-off | complete |
This section walks a new developer through every Stage-0 command. Run from app/.
make build # produces dist/openwatch (non-FIPS)
make build-fips # produces dist/openwatch-fips (Go 1.25 native FIPS 140-3)
./dist/openwatch --version
./dist/openwatch-fips --version # → "fips: true"The integration tests require a running PostgreSQL. Easiest setup uses docker / podman:
docker run -d --name openwatch-pg \
-e POSTGRES_USER=openwatch \
-e POSTGRES_PASSWORD=openwatch \
-e POSTGRES_DB=openwatch \
-p 5432:5432 \
postgres:16-alpine
export OPENWATCH_TEST_DSN="postgres://openwatch:openwatch@127.0.0.1:5432/openwatch?sslmode=disable"
./dist/openwatch migrate # apply all goose migrations
./dist/openwatch check-configTo run the daemon locally with a self-signed cert:
mkdir -p /tmp/ow-tls
bash packaging/common/gen-demo-cert.sh /tmp/ow-tls
OPENWATCH_DATABASE_DSN="$OPENWATCH_TEST_DSN" \
OPENWATCH_SERVER_TLS_CERT=/tmp/ow-tls/cert.pem \
OPENWATCH_SERVER_TLS_KEY=/tmp/ow-tls/key.pem \
./dist/openwatch --listen 127.0.0.1:8443 serveIn another terminal, exercise the surface:
curl -k https://127.0.0.1:8443/api/v1/health
curl -k 'https://127.0.0.1:8443/api/v1/audit/events?limit=5'
curl -k https://127.0.0.1:8443/api/v1/license
curl -k https://127.0.0.1:8443/api/v1/auth/permissions:registry | jq .make test # unit + integration; integration tests skip without DSN
specter sync # spec validation + coverage gatemake check # vet → lint → vuln → test-race, chainedOr individually:
make vet # go vet ./...
make lint # golangci-lint (staticcheck + gosec + govet + ...)
make vuln # govulncheck ./... (stdlib + deps; auto-installs)
make test-race # go test -race -p 1 ./...The same gates run in CI via .github/workflows/go-ci.yml on every PR touching app/**.
For strict-mode AC coverage (requires the test pipeline to ingest results):
export OPENWATCH_TEST_DSN="postgres://openwatch:openwatch@127.0.0.1:5432/openwatch?sslmode=disable"
go test -json -p 1 ./... > /tmp/go-test.json
specter ingest --go-test /tmp/go-test.json
specter coverage --strict # → "18 specs: 18 passing, 0 failing"make rpm # → dist/openwatch-<ver>-1.x86_64.rpm (needs rpmbuild)
make deb # → dist/openwatch_<ver>_amd64.deb (needs dpkg-deb)Install on a target VM via docs/install_guide.md.
When you edit any registry, regenerate the typed Go output:
make generate-audit # audit/events.yaml → internal/audit/events.gen.go
make generate-api # api/openapi.yaml → internal/server/api/server.gen.go
go run scripts/gen-rbac.go # auth/permissions.yaml → internal/auth/{permissions,roles}.gen.go
go run scripts/gen-license-features.go # license/features.yaml → internal/license/features.gen.goCI fails if a generated file is out of sync with its source registry.
Each step below has an enforcing test in the spec registry. Steps that the operator must run on a VM (file-watch cert reload, full binary restart) are flagged.
| # | Step | Spec AC / test |
|---|---|---|
| 1 | git clone + walk-through this README |
covered by this section |
| 2 | make build produces dist/openwatch |
release-package-build/AC-13 |
| 3 | make build-fips produces dist/openwatch-fips with fips: true |
release-fips-build/AC-01, AC-02 |
| 4 | make rpm + make deb produce installable packages |
release-package-build/AC-01, AC-02 |
| 5 | Package installs to /etc/systemd/system/openwatch.service + friends |
release-package-build/AC-04, AC-06 |
| 6 | systemctl start openwatch + journalctl -u openwatch |
operator (install guide) |
| 7 | GET /api/v1/health → 200 + canonical body |
release-stage-0-signoff/AC-01 |
| 8 | POST :echo with Idempotency-Key + X-Correlation-Id → 200 echoed |
release-stage-0-signoff/AC-02 |
| 9 | GET /audit/events includes the row from step 8 |
release-stage-0-signoff/AC-03 |
| 10 | Replay step 8 → cached response, only one audit row | release-stage-0-signoff/AC-04 |
| 11 | viewer permissions include host:read, exclude host:write |
release-stage-0-signoff/AC-05 |
| 12 | viewer + POST :require-host-write → 403 + audit row |
release-stage-0-signoff/AC-06 |
| 13 | security_admin + no license + :require-remediation-execute → 402 |
release-stage-0-signoff/AC-07 |
| 14 | POST :evaluate-alert {score:65} → outcome=high, version 0.0.0 |
release-stage-0-signoff/AC-08 |
| 15 | Drop signed alert_thresholds.yaml v1.0.0; reload; reflect new thresholds |
release-stage-0-signoff/AC-09 |
| 16 | POST :enqueue-test-job with X-Correlation-Id: req-end2end-001; worker emits matching audit event |
release-stage-0-signoff/AC-10 |
| 17 | specter sync → 100% AC coverage on every Active spec |
release-stage-0-signoff/AC-11 |
| 18 | Edit cert on disk; new TLS handshakes pick up the new cert | system-http-server/AC-08 + AC-09; full file-watch is operator |
| 19 | Stop / restart service — DB state survives, audit row persists | system-db/AC-12 (pool reopen); binary restart is operator |
Run all 19 in CI by running go test ./... after make rpm && make deb && make build-fips — the test suite asserts steps 7-17 against a live server; steps 2-5 are exercised by the packaging build itself; steps 1, 6, 18, 19 are the operator's gate.