fix(acl): read each objecttype version instead of the versions collection (refs #149)

The version resolve assumed `GET {objecttype}/versions` returns a bare list.
Every other collection in the Objecttypen API returns a paginated envelope, and
nothing in the repo exercises that endpoint, so the shape was a guess. Follow
the path infra/registerrecord-check.py already proves against the real API
instead: read the `versions` URLs off the objecttype and fetch each for its
status. Costs a request per version, once per gateway instance.

ACL mutation score 92.23% (baseline 91.37%).
This commit is contained in:
not
2026-08-14 09:33:15 +02:00
parent 5502e4c099
commit 43b45ad756
2 changed files with 61 additions and 27 deletions
@@ -52,16 +52,23 @@ public sealed class ObjectenGateway(HttpClient http, ObjectenOptions options, IC
?? throw new InvalidOperationException(
$"No objecttype '{options.ObjecttypeName}' registered in Objecttypen — is the RegisterRecord seed applied?");
var url = new Uri(match.Url);
// Write against the highest *published* version: a draft version's schema is still being
// shaped, and objects written against it would be validated by a moving target.
var versions = await GetAsync<IReadOnlyList<ObjecttypeVersionDto>>(
new Uri(url + "/versions"), options.ObjecttypenToken, crs: false, "objecttype versions", ct);
var latest = versions.Where(v => v.Status == "published").Select(v => v.Version).DefaultIfEmpty(0).Max();
// shaped, and objects written against it would be validated by a moving target. The objecttype
// carries its versions as URLs, so each is fetched for its status (the collection response
// gives no status) — once per gateway instance, alongside the lookup above.
var latest = 0;
foreach (var versionUrl in match.Versions ?? [])
{
var version = await GetAsync<ObjecttypeVersionDto>(
new Uri(versionUrl), options.ObjecttypenToken, crs: false, "objecttype version", ct);
if (version.Status == "published" && version.Version > latest)
latest = version.Version;
}
if (latest == 0)
throw new InvalidOperationException($"Objecttype '{options.ObjecttypeName}' has no published version");
return new Objecttype(url, latest);
return new Objecttype(new Uri(match.Url), latest);
}
/// <summary>The URL of the object already holding this registration's record, or null if there is none.</summary>
@@ -121,7 +128,8 @@ public sealed class ObjectenGateway(HttpClient http, ObjectenOptions options, IC
private sealed record ObjecttypeDto(
[property: JsonPropertyName("url")] string Url,
[property: JsonPropertyName("name")] string? Name);
[property: JsonPropertyName("name")] string? Name,
[property: JsonPropertyName("versions")] IReadOnlyList<string>? Versions);
private sealed record ObjecttypeVersionDto(
[property: JsonPropertyName("version")] int Version,