Open-source observability toolkit that collects, normalizes, and exports compliance evidence by extending the OpenTelemetry standard. Uses a Go workspace monorepo with one active module (proofwatch) and an OTel Collector distribution (beacon-distro).
proofwatch/ # Go module — evidence collection & emission library
internal/metrics/ # OTel metrics observer (evidence counters)
cmd/validate-logs/ # CLI tool for validating log output
beacon-distro/ # OTel Collector distribution (manifest.yaml + Containerfile)
model/ # Weaver semantic convention definitions (source of truth for attributes)
templates/ # Weaver Jinja2 code generation templates
configs/ # Collector and Loki deployment configs (base, storage, auth)
certs/ # Generated TLS certificates (gitignored, created by task infra:generate-self-signed-cert)
deploy/ # Deployment infrastructure (Terraform)
tests/ # Test infrastructure
integration/ # E2E Ginkgo tests, evidence fixtures
docs/ # Architecture (DESIGN.md), dev guide (DEVELOPMENT.md), attribute docs
openspec/ # OpenSpec change proposals and specs
.specify/memory/ # ComplyTime constitution (org-wide standards)
Taskfile.yml # Build automation entry point (canonical)
.taskfiles/ # Task modules (dev, codegen, infra, quality, version, integration) + scripts
task # List all available targets
task build # Build the collector container image
task test # Unit tests with coverage (proofwatch)
task test-race # Tests with race detection
task lint # Lint all modules (golangci-lint v2)
task check # Run all quality gates (lint + test)
task deps # Tidy, verify, download deps for all modules
task clean # Remove build artifacts and test output
task version:sync # Sync Go + OTel versions across all modules, Containerfiles, CI, docs
task version:check # Validate version alignment (read-only, for CI)
task infra:deploy # Start local stack (podman-compose)
task infra:undeploy # Stop local stack — DESTRUCTIVE- Go workspace, no root go.mod: This repo uses
go.workto link modules. All module-level commands iterate overMODULES := ./proofwatch. Runninggo test ./...from root will not work — usetask test. - Generated files — DO NOT EDIT:
proofwatch/attributes.go— regenerate withtask codegen:weaver-codegendocs/attributes/*.md— regenerate withtask codegen:weaver-docsgen
- Build automation: Use
task(taskfile.dev), notmake. A deprecated Makefile exists but is not maintained. - External tools: Install development tools with
task tools:install-allortask tools:install-weaver. SHA256 checksums are pinned in.tool_checksumsfor supply chain security. Ginkgo CLI is managed as atooldirective in the rootgo.modand invoked viago tool ginkgo. - Podman, not Docker: Container operations use
podmanandpodman-compose. Do not referencedockercommands. - Lint: Go linting uses
.golangci.yml(v2 format). Multi-language CI linting uses.mega-linter.yml. No pre-commit hooks — runtask lintlocally. - Integration tests:
tests/integration/contains Ginkgo E2E tests. Run withtask integration:test(all layers) ortask integration:test-profile PROFILE=base|storage|storage-tls|auth. - Standards: All coding standards are in
.specify/memory/constitution.md. For architecture context, seedocs/DESIGN.md. For dev setup, seedocs/DEVELOPMENT.md.
The compose stack (compose.yaml) runs the full evidence pipeline locally:
- Base (always on): Loki (log aggregation), collector (OTel Collector with evidence processing)
- Storage (
--profile storage): adds rustfs (S3-compatible object storage, API http://localhost:9000, console http://localhost:9001, credentialsrustfsadmin/rustfsadmin) - Storage-TLS (
--profile storage-tls): storage profile with TLS-secured S3 export - Auth (
--profile auth): adds Dex (OIDC provider) and configures the collector with OIDC-secured OTLP receivers - Debug (
--profile debug): adds Grafana (visualization, http://localhost:3000)
Collector config is selected via COLLECTOR_CONFIG env var (defaults to configs/collector-base.yaml).
All S3 environment variables have inline defaults that target rustfs. The stack works out-of-the-box with task infra:deploy — no AWS credentials needed. Override env vars in your shell to target real AWS S3 instead.
All commits MUST use Conventional Commits, the -s flag (Signed-off-by), and include an Assisted-by trailer when AI-assisted. See .specify/memory/constitution.md for full details.
CHANGELOG.md follows Keep a Changelog format. The audience is users and adopting developers, not contributors or CI.
Principles:
- Write entries that answer "what can I do now?" or "what changed for me?", not "what did we refactor internally?"
- Group by component scope (
proofwatch,beacon-distro) when an entry is module-specific - Use descriptive, multi-line entries that give enough context for someone unfamiliar with the codebase to understand the value — avoid terse one-liners that require reading source to interpret
- Internal tooling changes (CI actions, linter upgrades, dependency bumps, code quality tooling) are not changelog entries unless they affect the user experience or public API
- Do not use
BREAKINGlabels until there is a prior release to break against ChangedandRemovedsections only apply when a released feature is modified or dropped — pre-release iteration is not a "change"- Reference PR numbers only when they add traceability for significant features
- Auth profile uses Dex as OIDC provider: The
authprofile adds a Dex container (dexidp/dex:v2.45.1) and a dedicatedcollector-authservice withdepends_on: dex (service_healthy)to ensure OIDC discovery succeeds at startup. Dex provides a static test user ([email protected]/testpassword). The collector's OTLP receivers require a valid JWT from Dex. - Compose profiles are mutually exclusive for collector variants: Each collector variant (
collector,collector-tls,collector-auth) has its own profile to prevent port conflicts. The defaultcollectorhasprofiles: [base, storage]. The Taskfile handles profile selection — always usetask integration:test PROFILE=<name>, not rawpodman-composecommands. - RustFS is not MinIO: The health endpoint is
/health, not/minio/health/live(returns 403). The S3 API is compatible but administrative endpoints differ. Therustfs-initcontainer usesrc(RustFS CLI), notmc(MinIO client). - Loki 3.x OTLP labels: Loki no longer auto-creates
{exporter="OTLP"}from OTLP ingestion. Query indexed labels fromotlp_config.resource_attributesinconfigs/loki.yamldirectly (e.g.{policy_rule_id="..."}). Log attributes are stored as structured metadata and can be filtered with| key="value"after the stream selector. - Podman rootless volume permissions: The collector runs as UID 10001. Host-mounted volumes need the
:Uflag (podman ownership remapping) or the container can't write. The:Zflag alone only handles SELinux relabeling. - S3 test portability: Don't depend on host-installed CLI tools (
aws,mc). The test bucket is configured with anonymous public access viarc anonymous set publicinrustfs-init, so tests can query the S3 ListObjectsV2 API with plain HTTP — no auth headers needed. - awss3 exporter partitioning: When
resource_attrs_to_s3.s3_prefixis set, it replaces (not appends to) the statics3_prefix. Objects land at{resource_attr_value}/evidence_logs_{uuid}.json, not{s3_prefix}/{resource_attr_value}/....
artifact-metadatais not a valid GitHub Actions permission scope. The correct scope for build provenance attestations (actions/attest) isattestations: write. Using an invalid scope causes a startup_failure because GitHub validates all permission blocks including in reusable workflows before any job runs.- Reusable workflow permission validation is global: When
ci_local.ymlcallsci_publish_ghcr.ymlviauses:, GitHub parses and validates the called workflow's permissions block even if the calling job wouldn't run it. An invalid permission in any reusable workflow breaks the entire caller. - Required permissions for GHCR publish with attestations: The
build-beacon-distrojob inci_publish_ghcr.ymlneeds exactly:contents: read,packages: write,id-token: write,actions: read,attestations: write. Useactions: read(notwrite) for least privilege. - Quay publish has no attestation support:
ci_publish_quay.ymldoes not useactions/attestand does not needattestationsorid-tokenpermissions. Quay authentication usesQUAY_USERNAMEandQUAY_PASSWORDrepository secrets. - Validate with
actionlint: Runactionlint .github/workflows/*.ymllocally before pushing workflow changes. It catches invalid permission scopes, syntax errors, and expression issues that GitHub only reports at runtime.
- Go 1.26.4, multi-module workspace (
go.work) - OpenTelemetry Collector SDK v1.62.0 / v0.156.0 (stable + experimental series, component framework, pipeline data, processor interfaces)
github.com/gemaraproj/go-gemarav0.7.0 (compliance evidence model — Gemara v1 schema)github.com/telophasehq/go-ocsfv0.2.1 (OCSF cybersecurity schema types)github.com/stretchr/testify(test assertions, proofwatch)- OTel Weaver (semantic convention model — Go constants + docs)
- OTel Collector Builder v0.156.0 (beacon-distro binary)
- golangci-lint v2, MegaLinter, SonarCloud, gaze (quality tooling)
- Task v3 (taskfile.dev) — build automation
- Podman + podman-compose (container runtime)
- Container: UBI10 Minimal (runtime), golang:1.26.4 (build)
- Registries:
ghcr.io(primary), Quay.io (secondary)