From cecff94b276b6f083516e0412a6532dd06de0095 Mon Sep 17 00:00:00 2001 From: E Ismail Date: Sat, 19 Sep 2026 12:43:15 +0100 Subject: [PATCH] feat(wire): a liveness response joins the contract, with its all-zero state pinned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The contract gains a response type for a liveness endpoint. Nothing in this client calls that route, and the type is here anyway, because this package is where the protocol is defined once — before anything serves it — and a supervisor or a person establishing that a listening port is attached to a server that answers is as much a consumer as the CLI is. Liveness is the whole claim and the type says so. The handler reads no datastore and reaches nothing else, so a success means this process is up and serving; it does not mean the dependencies are well. One endpoint answering both questions can only ever answer the weaker one while reporting the stronger, which is worse than not answering at all. TWO FIXTURES, because the all-zero state is reachable rather than degenerate. A supervisor's first poll arrives before a second of uptime has accrued; a binary that cannot read its own file reports no digest; nothing obliges an installation to declare a revision. All three answer success, so all three need pinning — the same argument the closed-capacity fixture already makes, and that fixture's comment claiming to be the only one of its kind moves in this commit rather than being left beside its own counterexample. Both string fields may be empty and empty is not an error. The digest is what the binary computes of ITSELF at startup — its own identity rather than an assertion about it, the one form of that fact which cannot disagree with reality — and a process that cannot read its own file still serves. The revision is configuration, and the field name says so rather than implying the binary knows: whatever installed it is the only thing that does, so the value is exactly as trustworthy as that installation. It is deliberately not compiled in, because stamping it would make the binary's bytes depend on where it came from, and the build that produces it is pinned precisely so its output follows from its inputs alone. Uptime is a count rather than an instant. At zero a count reads as zero, where an unset timestamp renders as a date in year one that every reader must decode before dismissing — and a committed fixture is read far more often than it is written. FIXTURE VALUES ARE SYNTHETIC ON PURPOSE. A fixture pins field names; it does not record a measurement. This repository is world-readable, and a real digest with a real revision id would have published two facts about a private deployment in a file that needed three keys. Both new rows were proved by mutation before being trusted: dropping a key from the zero fixture reds with "emitted but absent from the fixture", and tagging the field omitempty reds both health rows with "vanishes when the zero value is marshalled". Restored byte-identical, with the baseline green again afterwards, so the restore is measured rather than assumed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XfBYBGuUkF7zUnZfAFD3dW --- pkg/wire/testdata/health_response.json | 5 +++ pkg/wire/testdata/health_response_zero.json | 5 +++ pkg/wire/wire.go | 46 +++++++++++++++++++++ pkg/wire/wire_test.go | 33 ++++++++++++++- 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 pkg/wire/testdata/health_response.json create mode 100644 pkg/wire/testdata/health_response_zero.json diff --git a/pkg/wire/testdata/health_response.json b/pkg/wire/testdata/health_response.json new file mode 100644 index 0000000..1990a0d --- /dev/null +++ b/pkg/wire/testdata/health_response.json @@ -0,0 +1,5 @@ +{ + "uptime_seconds": 3600, + "artefact_sha256": "1111111122222222333333334444444455555555666666667777777788888888", + "deployed_commit": "0abcdef" +} diff --git a/pkg/wire/testdata/health_response_zero.json b/pkg/wire/testdata/health_response_zero.json new file mode 100644 index 0000000..b93b166 --- /dev/null +++ b/pkg/wire/testdata/health_response_zero.json @@ -0,0 +1,5 @@ +{ + "uptime_seconds": 0, + "artefact_sha256": "", + "deployed_commit": "" +} diff --git a/pkg/wire/wire.go b/pkg/wire/wire.go index 66afbc5..88322d3 100644 --- a/pkg/wire/wire.go +++ b/pkg/wire/wire.go @@ -411,6 +411,52 @@ type DeployPublishResponse struct { ExpiresAt time.Time `json:"expires_at"` } +// HealthResponse is the body of GET /v1/health: a LIVENESS answer, and +// deliberately nothing else. The handler reads no datastore and reaches no +// other service, so a 200 means "this process is up and serving requests" +// and never "its dependencies are healthy". Those are two different +// questions, and one endpoint answering both can only ever answer the +// weaker one — while reporting the stronger. +// +// This route is UNAUTHENTICATED, because a liveness check that needs a +// credential cannot be used by the thing that starts the process, which is +// the first caller that needs it. It carries nothing a stranger could not +// already infer from the service answering at all. +// +// No client behaviour depends on this type, and the CLI never calls this +// route. It is here because the contract is defined once, in this package, +// for whoever does call it — a supervisor deciding whether to proceed, or a +// person establishing that a port which accepts connections is attached to +// a server that answers them. +// +// # Both string fields may be empty, and empty is not an error +// +// ArtefactSHA256 is what the running binary computes of ITSELF at startup: +// its own identity rather than an assertion about it, which is the one form +// of this fact that cannot disagree with reality. A process that cannot +// read its own file reports nothing here and still serves — a liveness +// endpoint that failed on self-inspection would not be a liveness endpoint. +// +// DeployedCommit is CONFIGURATION, and the field name says so rather than +// implying the binary knows. Whatever installed this binary is the only +// thing that knows which source revision produced it, so the value is +// exactly as trustworthy as that installation and no more. It is not +// compiled in: stamping a revision at build time would make the binary's +// bytes depend on where it came from, and the build that produces it is +// pinned precisely so its output is reproducible from its inputs alone. +// Empty means nobody told this process, which is a fact worth reporting +// and not a reason to refuse. +// +// UptimeSeconds is measured from one reading taken at startup. It is a +// count rather than a timestamp deliberately: at zero it reads as zero, +// where an unset instant renders as a date in year one that every reader +// has to decode before dismissing. +type HealthResponse struct { + UptimeSeconds int `json:"uptime_seconds"` + ArtefactSHA256 string `json:"artefact_sha256"` + DeployedCommit string `json:"deployed_commit"` +} + // DeployStatus is the lifecycle state of a deploy, as recorded in the // deploy record's status attribute. It is what the record says the deploy IS — // contrast Phase, which is what the build pipeline is DOING. The two diff --git a/pkg/wire/wire_test.go b/pkg/wire/wire_test.go index 2280e19..bf65928 100644 --- a/pkg/wire/wire_test.go +++ b/pkg/wire/wire_test.go @@ -99,8 +99,11 @@ func goldenCases() []goldenCase { }, { // The closed state is the whole point of the endpoint — the - // step-zero gate — and the only fixture where the meaningful - // values are zero values. + // step-zero gate — and the first fixture here whose meaningful + // values are zero values. HealthResponseZero below is the + // second, and for the same reason: a state the endpoint is + // EXPECTED to report needs a fixture of its own, or the only + // pinned shape is the populated one. name: "CapacityResponseClosed", fixture: "capacity_response_closed.json", value: &CapacityResponse{ @@ -110,6 +113,32 @@ func goldenCases() []goldenCase { }, newEmpty: func() any { return &CapacityResponse{} }, }, + { + // Values are SYNTHETIC on purpose. A fixture's job is to pin + // field names, not to record a measurement, and this repository + // is world-readable: a real digest and a real revision id here + // would publish two facts about a private deployment in a file + // that only needed three keys. + name: "HealthResponse", + fixture: "health_response.json", + value: &HealthResponse{ + UptimeSeconds: 3600, + ArtefactSHA256: "1111111122222222333333334444444455555555666666667777777788888888", + DeployedCommit: "0abcdef", + }, + newEmpty: func() any { return &HealthResponse{} }, + }, + { + // The all-zero state is REACHABLE and not a degenerate case: a + // supervisor's first poll arrives before a second of uptime has + // accrued, a binary that cannot read its own file reports no + // digest, and nothing obliges an installation to declare a + // revision. All three are 200s, so all three need pinning. + name: "HealthResponseZero", + fixture: "health_response_zero.json", + value: &HealthResponse{}, + newEmpty: func() any { return &HealthResponse{} }, + }, { name: "WaitlistRequest", fixture: "waitlist_request.json",