policy
The engineering contracts a service is held to: how it is configured, how it
starts and stops, how it is built and released — and one worked example that
runs them, in its own repository (truvity/url-shortener).
A contract here is short, normative and testable. The reasoning lives beside
it as a decision record, the allowed libraries as a canon, and the whole
thing is proven by an example service, kept in its own repository, rather
than by assertion.
What ships
| Artifact |
Where |
Published as |
| the contracts, canons, guides, decisions and glossary |
docs/ |
the tagged source |
| configuration schemas (JSON Schema) |
schemas/ |
the tagged source, and inside every loader |
| the Go configuration loader, conformance helpers and chart schema composer |
config/, conformance/, chartschema/ |
Go module github.com/truvity/policy |
| the TypeScript, Python and Kotlin loaders |
ts/, python/, kotlin/ in the url-shortener repository |
moved there on 2026-10-22; this repository no longer publishes @truvity/policy or the wheel (versions up to v1.54.2 stay where they were published) |
| the shared lint configuration |
lint/ |
the tagged source, copied into a repository |
| the local cluster the example is tested on |
hack/kind/ |
the tagged source, fetched at a pinned tag |
| the library chart service charts render with |
charts/service-lib |
chart service-lib under oci://ghcr.io/truvity/charts; embedded for tests by the Go package charts/ |
| the worked example, a URL shortener |
truvity/url-shortener |
moved there on 2026-10-22: charts url-shortener and url-shortener-infra under oci://ghcr.io/truvity/charts, released from that repository's own tags (up to v1.54.2 they were released from here, images under ghcr.io/truvity/policy/url-shortener/) |
One tag stamps all of them: vX.Y.Z releases the documents, the schemas, the
Go module and the library chart together. A consumer pins one version.
Who it is for
A team running services on Kubernetes that wants the same shape in every
repository and every language, without a framework in the way. The contracts
bind at the process boundary — a configuration file, a set of probe
endpoints, a log format, a signal — so a Go service, a Node service and a JVM
service satisfy them the same way and share no code.
The component contract extends the same idea
to the repositories that ship mechanism rather than services — charts,
images, Go libraries, CLIs, actions — so that every public repository an
estate consumes looks the same from the outside.
It deliberately does not ship a framework, a dependency-injection container,
a code generator or a base image. The example is the reference
implementation; there is nothing to import in order to conform.
This repository is itself public, for the reasons in
0004; a private repository
consumes the same contracts and the same libraries, with a different gate
and a different release destination — see
docs/guides/private-consumer.md.
The model
Three things, in order of how much they bind:
- Contracts (
docs/contracts/) are normative. Each one is short enough
to read in a sitting, carries a version, and has a conformance section
saying how it is checked mechanically.
- Canons (
docs/canon/) are the allowed lists: which libraries, which
toolchain versions, which build tools, and the scope they apply to.
- Guides (
docs/guides/) are how to satisfy the contracts, walking the
example.
Decisions (docs/decisions/) record why a contract reads the way it does.
They are never edited after acceptance; they are superseded by a later one
that links back. The glossary pins down the words the
documents use narrowly — estate, platform, ring, tier, lane.
Install and a worked example
Pin one version; every artifact carries the same one. v1.29.0 is the latest
tag at the time of writing — the
releases page lists every one.
go get github.com/truvity/policy@v1.29.0
The TypeScript, Python and Kotlin loaders are not installed from here any
more: they moved to the url-shortener repository together with the
example that consumes them.
A Go service loads its configuration in three lines, and a misconfiguration is
refused before anything is constructed:
var cfg Config
if err := config.Load(path, schemaBytes, &cfg); err != nil {
return err // names the file and every failing key
}
The worked example runs every contract end to end, and its application
chart renders from the release with only the names it refuses to guess —
the database's host, the root its certificate chains to and the two Secrets
holding its credentials, the broker, the bucket:
# values.yaml
database:
host: pg
tls:
rootCA:
configMapName: pg-root-ca
owner:
passwordSecret: owner
app:
passwordSecret: app
events:
url: nats://nats:4222
archive:
bucket:
name: archive
helm template example oci://ghcr.io/truvity/charts/url-shortener --version 1.29.0 -f values.yaml
What provides those names is the infrastructure chart's job, installed per
install beside it (platform.md §11);
the example's README walks what each
component proves.
Consumers
The repositories that consume a release, and through which surface. No
versions: each consumer's own pin is the record of that.
| Consumer |
Surface |
truvity/ci-actions |
the cluster action fetches hack/kind/ at a pinned release tag |
| 14 repositories |
copy lint/golangci-depguard.yaml into their .golangci.yaml |
truvity/gitops |
deploys the url-shortener example's charts (released from the url-shortener repository) |
| every public component repository |
is held to the component contract |
Neighbours
- ci-workflows runs this
repository's gate (
check.yaml), its release (release-public.yaml) and
the cluster lane of any repository that calls it (integration.yaml, whose
kind tier stands up hack/kind/ at the policy-version it is given).
- ci-actions holds the
cluster
action that fetches hack/kind/ from a policy release; the box is defined
here and nowhere else.
- gemaal provides the
harness
package the url-shortener example's end-to-end suite reaches every Service
through.
- ocictl provides
helmctl, which
bakes image digests into the url-shortener example's charts at its release.
- Every other public component repository is held to the contracts here;
component.md lists them.
Documentation
docs/ is the index, by audience: authoring a service,
authoring a component, reviewing one, operating one.
docs/landscape.md is the other direction: one page
naming every public repository the contracts below apply to today, and how
they fit together. The contracts:
- repository — layout, toolchain, the gate
- service — the process boundary
- config — one typed configuration per binary
- platform — what a service asks of what runs it
- delivery interface — the one number a
chart declares so a platform hands it only what it can take
- release — one tag, what a version means
- component — the rules C1–C18 for every
public repository that ships charts, images, libraries, CLIs or actions
The rule that makes this repository public
Mechanism only. Nothing here names an organisation, an account, a cluster, a
hostname, an environment, a team, a person, an internal repository or a
ticket. Every such thing is an input with a neutral default, supplied by the
consuming estate from its own private repository. hack/leak-canary.sh
enforces the mechanical part of that rule on every commit and in CI; the rest
is a review rule.
The one exception is Consumers: it names a consuming
repository and the surface it uses, and never a version, an environment or
a cluster (component.md C8).
Status
Used in production by its maintainers. Released from v0.1.0; tags follow
semver from v1.25.0 (release.md §2). Every
contract is at version 1.0, effective 2026-09-29.
In progress: the url-shortener's data contracts are also written in Pkl and
generated beside the hand-written schemas, which stay authoritative
(guides/pkl-shadow.md: the shadow phase of
decision 0010),
in the url-shortener repository.
What is not done: two guides — keys and signing, and migrating off a framework — arrive
with the component that proves them
(guides/README.md).
Development
devbox shell # or direnv, which does it on cd
just check # the gate: exactly what CI runs
just example-canary # build and test the url-shortener example against this checkout
just check needs no credentials, no container runtime and no cluster.
The cluster tier needs a container runtime, stands up a local cluster
(hack/kind/); the example installs onto it from the
same artifacts a release publishes, from its own repository:
just cluster # the box: about a minute from nothing
just cluster-verify # ask each server a real question
just cluster-down # remove it
AGENTS.md is the entry point for anyone — or anything —
arriving without context: what to read first, what the gate checks that
surprises people, and the separate set of rules for bringing another
repository to this shape.
Releasing
Manual tags: every first release, minor and major is a tag pushed by a
person after the CHANGELOG heading for that version has merged
(release.md §4). Automatic patch releases are
cut by a bot and need no CHANGELOG heading: their notes are the GitHub
release's, and the next hand-cut heading covers them.
Licence
MIT. See LICENSE.