Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/wire/testdata/health_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"uptime_seconds": 3600,
"artefact_sha256": "1111111122222222333333334444444455555555666666667777777788888888",
"deployed_commit": "0abcdef"
}
5 changes: 5 additions & 0 deletions pkg/wire/testdata/health_response_zero.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"uptime_seconds": 0,
"artefact_sha256": "",
"deployed_commit": ""
}
46 changes: 46 additions & 0 deletions pkg/wire/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 31 additions & 2 deletions pkg/wire/wire_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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",
Expand Down
Loading