How this repository is developed. The rules below are the process; changes to the process itself go through a PR to this file.
- Trunk-based.
mainis protected and always releasable. Work happens on short-lived branches, merged via PR with squash-merge, somainhistory is one commit per change. - Conventional Commits. Commit subjects follow
type: summary—feat:,fix:,docs:,chore:,refactor:,test:,ci:. This keeps history greppable and makes changelog generation mechanical. - No long-running feature branches. If a change is too big for one PR, it is
split into steps that each keep
mainreleasable.
Decisions that would be expensive to reverse — protocol, storage, security
boundaries, public contracts — are recorded as ADRs in adr/
before or together with the code that implements them. Reversing one
means writing a superseding ADR, not editing history.
An ADR's body is immutable; its header is not. An ADR that changes part of an
earlier one declares Amends: NNNN, and the earlier one gains the mirror
Amended by: NNNN — so a reader who opens the old file sees that the ground
moved. go test ./docs/adr fails if the graph is one-sided, and prints the line
to add. The rule and its reasoning are in
ADR 0022.
A PR is mergeable when:
- CI is green (see below).
- Behavior changes come with tests. No test, no merge — including bug fixes, which need a test that fails without the fix.
- If the change affects what the agent collects, stores, or transmits,
docs/security.mdis updated in the same PR. The security document is a promise to customers; code must never drift ahead of it. Update it the way ADR 0024 sets out: state what is true and link the ADR for why, rather than restating the decision — and mark a claim[planned]until it is built.go test ./docsholds the parts that are lists: the payload table must mirrorinternal/sink/registry.goexactly, and every cross-reference must resolve. - If the change implements or alters an architectural decision, the ADR exists (see above).
- Everything committed is in English: code, comments, docs, commit messages.
- Golden payload files are the schema contract. The
localsink's output for known inputs is committed as testdata. A change that alters these bytes is a protocol change and must be justified as one — this is the enforcement mechanism for the Stage A invariant: the local sink writes exactly what the backend sink will transmit. - The payload registry is checked against those bytes.
internal/sink/registry.gois the one list of what the agent ships — kind, natural key, delivery discipline, provenance — and tests compare it to the goldens in both directions: a kind that ships without a row fails, and a row describing nothing that ships fails. The list lived in a document for nine slices and drifted in three rows with nothing failing (ADR 0022). - The corpus proves the payload schema. The
.protofiles are not on the wire yet, so nothing but a test exercises them.internal/pb/kubernetesv1parses each covered golden into its generated message withDiscardUnknownoff, so a field the corpus carries and the schema does not fails; a second walk fails on any value that did not survive as a set field, which is what holds the presence declarations honest (ADR 0074). Regenerate withmake proto; the generated Go is committed. - Property tests for rollup math. Rollup merging must be associative and commutative, and merging N partial rollups must equal computing one rollup over the union. These properties are tested with generated inputs, not hand-picked examples — mergeability is what the whole data model rests on.
- The chart's rendered output is asserted, not reviewed.
internal/chartrender/chart_test.gorenders every install profile and checks the promisesdocs/security.mdmakes about an installation: no write verb in the ClusterRole, every cache the agent opens granted, the node bound to nothing and holding no API token, no privileged container, exactly the capabilities each profile claims, every host mount read-only, the spool an emptyDir. It also feeds the rendered configuration to the agent's own strict parser, so a chart that would CrashLoop at startup fails here instead (ADR 0036).make chart-lintadds the helm CLI's own opinion, per profile. - And the absences are asserted too. A test that enumerates what must be
present is indifferent to an addition standing next to it:
secretsadded to an existing read-only rule,hostPIDon the controller, a capability added besidedrop: [ALL], a privileged init container — all five measured passing the whole suite green beforeinternal/chartrender/guardrail_test.goexisted. It names the complete permitted set instead, per rendered pod, so an addition has to be argued for in a diff that changes a test. When you widen the chart, expect to widen a list there; that is the mechanism working, not a test in the way. - Prose has a mechanism too (ADR 0044).
docs/prose_test.gofails on a comment run longer than eight lines and on asecurity.mdpast its ceiling. Both are ratchets: raising one is a decision that appears in a diff. If a comment does not fit, the reasoning belongs in an ADR with a pointer left behind — ADR 0024 settled that forsecurity.mdand it regressed from 750 lines to 1139 in five days, because a rule without a failing test is a habit. - End-to-end on kind installs the chart. The agent runs against a local kind
cluster with synthetic workloads; assertions are made on the spool contents,
not on logs. The suites install
charts/runtime-agentrather than a copy of its output, so a broken template fails in the same run as broken code — which is the whole reason the raw manifests were deleted rather than kept as a reference. The kind node-image matrix pins the oldest supported Kubernetes minor (see the README support policy) and a current release. - Unit tests live next to the code (
*_test.go); e2e lives undertest/.
Every push and PR runs (.github/workflows/ci.yml):
- Secret scan (gitleaks) — nothing that looks like a credential enters history.
- With the Go scaffolding:
go build,go test,golangci-lint. make chart-lint— the Helm chart, once per install profile. helm is wired as a Go tool (like kind), so nothing needs installing separately.make vulncheck—govulncheckover the whole module, standard library included. It gates on reachability: a vulnerability the code calls fails the build, one in a module nothing reaches is reported and does not. That distinction is the point — Dependabot walks the module graph and cannot see the standard library, where all sixteen findings were the first time this ran (ADR 0038).buf lintandbuf breakingagainstmain, over the payload schema inproto/— the N-2 compatibility promise inbackend-requirements.md§6 is enforced mechanically, not by review vigilance.
- Semantic versioning. The payload schema version and the agent version are distinct: the schema evolves slower and is governed by the compatibility window.
- A release is a tag on
main; artifacts (image, Helm chart) are built from the tag, never from a branch. Release process details will be added when the first artifact exists.
- No fixed sprint cadence or issue ceremony — scope is tracked in GitHub issues and milestones, pace is continuous.
- No public roadmap commitments; milestones appear when they are real.