fix(agentnetwork): require access_log_retention_days on the settings PUT

The settings PUT replaces every mutable field, but retention was optional
in the schema while the other three toggles were required. That was not
merely inconsistent: the handler applies the request to a zero-valued
Settings and UpdateSettings copies each field onto the stored row
unconditionally, so an omitted retention was written as 0 — which the API
documents as "keep indefinitely". A client sending only the required
fields silently switched the account from bounded to unbounded access-log
retention, with no error and no signal.

The nil check in FromAPIRequest looked like it guarded against this but
never did: the receiver is a fresh struct, not the loaded row, so skipping
the assignment preserved nothing.

Marking the field required changes the generated client type from *int to
int, so a generated client can no longer omit it. Nothing validates
OpenAPI required-ness at runtime, so a hand-rolled body without the field
still lands as 0 — the same latitude the three booleans already have, left
consistent rather than special-cased, and now pinned by a test that says
so explicitly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Brad Ison
2026-08-10 16:57:21 +02:00
co-authored by Claude Opus 5
parent 13f9bde30e
commit e60e7c9089
6 changed files with 61 additions and 16 deletions
+8
View File
@@ -142,14 +142,21 @@ func TestSettingsRoundTrip(t *testing.T) {
require.NotEmpty(t, before.Endpoint, "settings must carry the bootstrapped endpoint")
require.NotEmpty(t, before.ProxyAddress, "settings must carry the bootstrapped proxy address")
require.NotNil(t, before.AccessLogRetentionDays, "bootstrapped settings must carry a retention")
beforeRetention := *before.AccessLogRetentionDays
flipped, err := srv.UpdateSettings(ctx, api.AgentNetworkSettingsRequest{
EnableLogCollection: !before.EnableLogCollection,
EnablePromptCollection: !before.EnablePromptCollection,
RedactPii: !before.RedactPii,
AccessLogRetentionDays: beforeRetention,
})
require.NoError(t, err, "update settings")
assert.Equal(t, !before.EnableLogCollection, flipped.EnableLogCollection, "log collection toggle must flip")
assert.Equal(t, !before.EnablePromptCollection, flipped.EnablePromptCollection, "prompt collection toggle must flip")
require.NotNil(t, flipped.AccessLogRetentionDays)
assert.Equal(t, beforeRetention, *flipped.AccessLogRetentionDays,
"retention sent unchanged must round-trip, not reset to the zero value")
assert.Equal(t, before.Endpoint, flipped.Endpoint, "endpoint must be immutable across updates")
assert.Equal(t, before.ProxyAddress, flipped.ProxyAddress, "proxy address must be immutable across updates")
@@ -165,6 +172,7 @@ func TestSettingsRoundTrip(t *testing.T) {
EnableLogCollection: before.EnableLogCollection,
EnablePromptCollection: before.EnablePromptCollection,
RedactPii: before.RedactPii,
AccessLogRetentionDays: beforeRetention,
})
require.NoError(t, err, "restore settings")
}
+5 -1
View File
@@ -57,7 +57,8 @@ func TestSettingsBootstrapViaPost(t *testing.T) {
// A PUT has no row to update yet — bootstrap is the explicit POST.
_, err = fresh.UpdateSettings(ctx, api.AgentNetworkSettingsRequest{
EnableLogCollection: true,
EnableLogCollection: true,
AccessLogRetentionDays: 30,
})
requireClientError(t, err)
@@ -95,8 +96,11 @@ func TestSettingsBootstrapViaPost(t *testing.T) {
EnableLogCollection: true,
EnablePromptCollection: false,
RedactPii: true,
AccessLogRetentionDays: 21,
})
require.NoError(t, err, "post-bootstrap update must succeed")
require.NotNil(t, persisted.AccessLogRetentionDays)
assert.Equal(t, 21, *persisted.AccessLogRetentionDays, "retention from the update must apply")
assert.Equal(t, bootstrapped.Endpoint, persisted.Endpoint, "endpoint must survive updates untouched")
assert.Equal(t, cluster, persisted.ProxyAddress, "proxy address must survive updates untouched")
assert.True(t, persisted.EnableLogCollection, "post-bootstrap toggle must apply")