Status
Proposed on 2026-03-14 by Lars Solem.
Context
Dataverket spans several kinds of software:
- long-lived control-plane services
- CLIs and operator tooling
- infrastructure workers and agents
- discovery and migration utilities
- performance-sensitive or security-sensitive components
Using one language everywhere is not mandatory, but using many languages without a rationale would increase operational and cognitive cost.
The same practical concern applies to configuration management. A platform that automates infrastructure needs an explicit posture for how its own configuration, policy, and runtime state are managed.
Decision
Dataverket adopts a pragmatic multi-language posture:
- Go is the default choice for core control-plane services, CLIs, and controllers
- Rust is used selectively where stronger correctness or performance properties are worth the additional complexity
- Python is used intentionally for tooling, discovery, validation, and rapid operational automation, not as the default language for every long-lived service
This is guidance, not an absolute ban on other choices, but deviations should be justified.
Dataverket also adopts a hybrid configuration-management posture:
- Git-managed declarative configuration for platform-owned definitions, policy, and reviewed change sets
- API and database-managed desired state for tenant-facing resources and runtime control-plane data
- reconciliation to converge the running platform toward those declared sources of truth
This avoids treating Git as the database for all mutable runtime state while still keeping important platform configuration under reviewable change control.
Go guidance
Go is the default because it offers:
- simple deployment and operations
- strong fit for service daemons and CLIs
- mature ecosystem around Kubernetes and infrastructure software
- practical concurrency model for controllers and background workers
Typical uses:
- Sentral APIs and bounded contexts
- Nett and Maskin service processes
- operator-facing APIs
- CLI tools
Rust guidance
Rust should be used when its strengths matter materially.
Advantages:
- memory safety and strong correctness properties
- good fit for protocol-heavy or high-throughput components
- attractive for security-sensitive code
Tradeoffs:
- steeper learning curve
- slower onboarding and iteration in some teams
Typical uses:
- selected high-performance components
- security-sensitive agents
- correctness-critical low-level infrastructure code
Python guidance
Python remains useful when fast iteration and ecosystem leverage matter more than static rigor.
Advantages:
- fast development for scripts and utilities
- strong ecosystem for automation and integration work
- practical for validation and migration tooling
Tradeoffs:
- easier to accumulate inconsistent runtime and packaging patterns
- easier to overuse for long-lived services that later need stronger operational discipline
Typical uses:
- discovery scripts
- migration tools
- validation jobs
- operational and support tooling
- early prototypes
Repository and service posture
Configuration management posture
Dataverket should distinguish clearly between:
Platform configuration Configuration that defines how Dataverket itself is operated.
Tenant and operator resource intent Desired state submitted through the public API or operator workflows.
Runtime and operational state Task state, observed state, leases, approvals, health, and other mutable control-plane records.
These do not need the same storage or change workflow.
Git-managed platform configuration
The preferred posture for platform-owned configuration is GitOps-style declarative management.
This should cover at least:
- Kubernetes manifests or Helm values for Dataverket services
- environment- or site-specific service configuration
- policy defaults and reviewed platform feature flags
- inventory seed data and bootstrap definitions where appropriate
- network intent templates or other reviewed infrastructure baselines
The goal is that important platform configuration changes are:
- reviewable
- auditable
- reproducible
- promotable across environments
API and database-managed resource intent
Git should not be the only path for tenant-facing resource creation.
Tenant and operator workflows such as:
- creating projects and environments
- requesting VMs, clusters, networks, or buckets
- triggering failover or approval workflows
- updating mutable service resources through supported APIs
should be submitted through the public API and stored in service databases as authoritative desired state.
This keeps the self-service and operator model coherent and avoids forcing all platform usage through repository commits.
Runtime state is not GitOps
Dataverket should not attempt to manage these primarily through Git:
- task and workflow state
- observed inventory facts
- ephemeral leases or locks
- health and degradation signals
- retry counters and dead-letter records
- approvals already in progress
Those belong in the control plane’s runtime systems, primarily PostgreSQL and supporting infrastructure such as NATS JetStream.
Hybrid model
The intended model is therefore:
- Git is the reviewable source for platform-owned configuration and bootstrap definitions
- the public API is the supported source for tenant and operator resource intent
- databases hold authoritative mutable state
- controllers and reconciliation loops make running systems converge toward those declared inputs
This is compatible with GitOps principles without pretending that every piece of platform state should be represented as files in a repository.
This ADR does not yet lock:
- monorepo versus multi-repo
- shared library structure
- build system standard
But the language posture implies:
- keep the number of production languages intentionally small
- prefer common operational patterns across services
- avoid introducing a language only for one marginal convenience
The same discipline should apply to configuration tooling:
- prefer a small number of standard config formats and deployment patterns
- avoid each service inventing its own config distribution model
- keep the boundary between reviewed platform config and mutable runtime state explicit
Consequences
- the platform gets a default language direction without pretending one language fits every job
- Go becomes the expected baseline for most long-lived services
- Rust remains available where it is actually justified
- Python is legitimized for tooling without becoming the unexamined default for the whole platform
- platform-owned configuration gets a reviewable GitOps-friendly path without forcing tenant self-service through Git commits
- runtime control-plane state remains in systems designed for mutable operational data
Decision Outcome
Proposed. This ADR records the current preferred direction and still needs acceptance before it becomes binding.
Related Decisions
- This ADR supports the implementation guidance in platform-plan.md.
- Configuration posture must align with the control-plane and desired-state model in platform-plan.md.
- CLI implications must align with 002-cli-naming-and-structure.md.
- Operator tooling and local development implications must align with 003-development-and-test-environments.md.
- Public resource workflows must align with 008-public-api-style.md.
- Workflow and reconciliation behavior must align with 019-workflow-retry-dead-letter-and-reconciliation.md.
More Information
- repository and build layout
- shared library policy
- service template and bootstrap standards
- platform configuration repository and promotion model
Audit
- 2026-03-14: ADR proposed.