From e018ecff03f48aba5d51f230b90afd4b9f4d4de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D7=A0=CF=85=CE=B1=CE=B7=20=D7=A0=CF=85=CE=B1=CE=B7=D1=95?= =?UTF-8?q?=CF=83=CE=B7?= Date: Thu, 1 Oct 2026 21:20:48 -0700 Subject: [PATCH] docs: what a domain's tests owe The corpus said where validation is declared and nothing about proving it runs. A tag can be dropped from the specification and the generator will quietly stop emitting it, validation.Struct will quietly stop checking it, and nothing fails until somebody sends bad input to production. States the four obligations: validation proved to fire rather than proved to exist, the RBAC trio per endpoint, a path through the tests for every status the specification declares, and the collection shape asserted for one host and for many. The part worth having written down is that a validation test asserts the body names the rule that rejected the request, not only the status code. A test checking for 400 alone passes when the handler rejects for an unrelated reason, which is the failure it exists to catch. Measured counts included so the next reading can tell drift from a figure somebody guessed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FuKUsHFG1EqZXamffh9M2c --- .claude/skills/add-a-domain/references/api.md | 1 + components/osapi/domains.md | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/.claude/skills/add-a-domain/references/api.md b/.claude/skills/add-a-domain/references/api.md index 4e8eed8..03da2cd 100644 --- a/.claude/skills/add-a-domain/references/api.md +++ b/.claude/skills/add-a-domain/references/api.md @@ -31,6 +31,7 @@ exists to end. | The job client has four methods, and an operation adds none | [the job client](../../../../components/osapi/domains.md#the-job-client-needs-nothing-added) | | `Handler()` returns route closures, and the `Server` struct does not change | [wiring](../../../../components/osapi/domains.md#wiring) | | The permission a new endpoint needs, and where it is declared | [adding a permission](../../../../components/osapi/permissions.md#adding-a-permission) | +| What the tests owe: validation proved to fire, the RBAC trio, every declared status | [what a domain's tests owe](../../../../components/osapi/domains.md#what-a-domains-tests-owe) | | A domain appears everywhere an existing domain appears | [every layer, or not done](../../../../components/osapi/domains.md#a-domain-is-in-every-layer-or-it-is-not-done) | What a caller sees when an agent does not answer is the job system's, not this diff --git a/components/osapi/domains.md b/components/osapi/domains.md index 3337261..9a65be2 100644 --- a/components/osapi/domains.md +++ b/components/osapi/domains.md @@ -221,6 +221,50 @@ What is not allowed is the same concept carrying a different name in each layer, because the only check there is for cross-layer completeness is searching for a domain's name, and that check cannot work when the name changes on the way. +## What a domain's tests owe + +A rule that is declared and never exercised is a rule nobody knows is broken. +The specification carries the validation tags, the generator turns them into +struct tags, and `validation.Struct()` runs them. Nothing in that chain fails +loudly if a tag is dropped, so the tests are what hold it. + +**Validation is proved to fire, not proved to exist.** Each endpoint gets a +`TestXxxValidationHTTP` that sends a request through the full middleware stack +with a field missing or malformed, asserts 400, and asserts the body names the +rule that rejected it. Naming the rule is the part that matters: a test +asserting only the status code passes just as happily when the handler rejects +for some unrelated reason. + +```go +name: "when missing key returns 400", +body: `{"value":"1"}`, + s.Contains(rec.Body.String(), "Key") + +name: "when target agent not found", + s.Contains(rec.Body.String(), "valid_target") +``` + +**Permission is proved three ways.** `TestXxxRBACHTTP` per endpoint: no token is +401, a token without the permission is 403, a token with it succeeds. The third +case is what catches a permission that was spelled differently in the +specification than in the table. + +**Every status the specification declares has a path through the tests.** A +declared 404 nobody produces is either an undocumented behaviour or a lie in the +specification, and both are worth finding. + +**Broadcast returns the same shape for one host and for forty.** The collection +is asserted in both directions, because the single-host case is the one that +tends to get special-cased. + +**The integration tier covers what unit tests cannot.** `cmd/` is excluded from +the coverage gate, so the wiring from CLI flag to HTTP request to agent is only +exercised by `test/integration/{domain}_test.go` against a real binary. + +Measured in October 2026, the domains carry 75 validation suites and 80 RBAC +suites between them, and 21 of 22 have an integration test. The exceptions are +listed as gaps rather than tolerated. + ## A domain is in every layer, or it is not done A domain is not one artifact. It is a provider, an agent processor and its