feat(acl): resolve zaaktype + informatieobjecttype by business key, not a pinned URL (refs #113)

The ACL now discovers its BIG zaaktype (by identificatie) and diploma
informatieobjecttype (by omschrijving) from OpenZaak's Catalogi API, instead of
being handed server-assigned URLs in config. A CachedZaaktypeCatalog resolves
lazily on first use and caches (success only, so a pre-publish miss is retried);
AclDefaults now carries ZaaktypeIdentificatie/InformatieobjecttypeOmschrijving.
Clear errors replace the opaque placeholder-URL 400. Unit tests cover the
resolver (resolve/cache/retry-on-failure) and the gateway lookups (match/miss).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
not
2026-07-22 15:39:01 +02:00
co-authored by Claude Opus 4.8
parent 183d0bce31
commit 0ba410ad78
10 changed files with 370 additions and 57 deletions
+15 -15
View File
@@ -2,9 +2,9 @@ namespace Acl.Application;
/// <summary>The ACL's single operation: open a zaak from a domain payload,
/// default-filling the ZGW-mandatory fields (ADR-0003).</summary>
public sealed class AclService(IZaakGateway gateway, AclDefaults defaults, IClock clock)
public sealed class AclService(IZaakGateway gateway, AclDefaults defaults, IZaaktypeCatalog catalog, IClock clock)
{
public Task<Uri> OpenZaakAsync(DomainRegistration registration, CancellationToken ct = default)
public async Task<Uri> OpenZaakAsync(DomainRegistration registration, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(registration);
@@ -12,34 +12,34 @@ public sealed class AclService(IZaakGateway gateway, AclDefaults defaults, ICloc
defaults.Bronorganisatie,
defaults.VerantwoordelijkeOrganisatie,
defaults.Vertrouwelijkheidaanduiding,
defaults.ZaaktypeUrl,
await catalog.GetZaaktypeUrlAsync(ct),
clock.Today,
registration.Reference);
return gateway.OpenZaakAsync(request, ct);
return await gateway.OpenZaakAsync(request, ct);
}
/// <summary>
/// Approve a zaak: set it to the eindstatus of the configured BIG zaaktype (ADR-0003 default). The
/// domain hands over only the zaak URL; the ACL owns which statustype means "approved" (§8.1).
/// Approve a zaak: set it to the eindstatus of the BIG zaaktype (resolved by identificatie, S-27).
/// The domain hands over only the zaak URL; the ACL owns which statustype means "approved" (§8.1).
/// </summary>
public Task ApproveZaakAsync(Uri zaakUrl, CancellationToken ct = default)
public async Task ApproveZaakAsync(Uri zaakUrl, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(zaakUrl);
return gateway.SetZaakToEindstatusAsync(zaakUrl, defaults.ZaaktypeUrl, clock.Today, ct);
await gateway.SetZaakToEindstatusAsync(zaakUrl, await catalog.GetZaaktypeUrlAsync(ct), clock.Today, ct);
}
/// <summary>
/// Cancel a zaak on document-timeout expiry (S-10c): set it to the configured BIG zaaktype's
/// cancellation statustype + resultaat. The domain hands over only the zaak URL; the ACL owns which
/// Cancel a zaak on document-timeout expiry (S-10c): set it to the BIG zaaktype's cancellation
/// statustype + resultaat. The domain hands over only the zaak URL; the ACL owns which
/// statustype/resultaat means "cancelled" (§8.1).
/// </summary>
public Task CancelZaakAsync(Uri zaakUrl, CancellationToken ct = default)
public async Task CancelZaakAsync(Uri zaakUrl, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(zaakUrl);
return gateway.SetZaakToCancellationStatusAsync(zaakUrl, defaults.ZaaktypeUrl, clock.Today, ct);
await gateway.SetZaakToCancellationStatusAsync(zaakUrl, await catalog.GetZaaktypeUrlAsync(ct), clock.Today, ct);
}
/// <summary>The zaak's reference (its ZGW identificatie), for the read projection (#78).</summary>
@@ -56,7 +56,7 @@ public sealed class AclService(IZaakGateway gateway, AclDefaults defaults, ICloc
/// and hand the file to the gateway, which creates the informatieobject and relates it to the zaak.
/// The domain supplies only the zaak, the bytes, and the file's name/type (§8.1).
/// </summary>
public Task<Uri> StoreDiplomaAsync(Uri zaakUrl, byte[] content, string fileName, string contentType, CancellationToken ct = default)
public async Task<Uri> StoreDiplomaAsync(Uri zaakUrl, byte[] content, string fileName, string contentType, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(zaakUrl);
ArgumentNullException.ThrowIfNull(content);
@@ -65,7 +65,7 @@ public sealed class AclService(IZaakGateway gateway, AclDefaults defaults, ICloc
var request = new DocumentRequest(
defaults.Bronorganisatie,
defaults.InformatieobjecttypeUrl,
await catalog.GetInformatieobjecttypeUrlAsync(ct),
defaults.Vertrouwelijkheidaanduiding,
zaakUrl,
clock.Today,
@@ -76,6 +76,6 @@ public sealed class AclService(IZaakGateway gateway, AclDefaults defaults, ICloc
Formaat: contentType,
Inhoud: content);
return gateway.StoreDocumentAsync(request, ct);
return await gateway.StoreDocumentAsync(request, ct);
}
}