diff --git a/services/acl/Acl.Api/Program.cs b/services/acl/Acl.Api/Program.cs
index 72ca25c..659f1e6 100644
--- a/services/acl/Acl.Api/Program.cs
+++ b/services/acl/Acl.Api/Program.cs
@@ -90,6 +90,16 @@ app.MapPost("/zaken/reference", async (ZaakReferenceRequest body, AclService acl
return Results.Ok(new { reference });
});
+// Read the register record an object in Objecten holds. The Event Subscriber projects a register
+// write from the notification NRC delivers, which carries only the object URL, and may not talk to
+// Objecten itself (§8.1, ADR-0028/ADR-0030). 404 when the object holds no record — the subscriber
+// treats that as "nothing to project" rather than an error (§8.6).
+app.MapPost("/register-records/read", async (RegisterRecordReadRequest body, AclService acl, CancellationToken ct) =>
+{
+ var record = await acl.GetRegisterRecordAsync(new Uri(body.ObjectUrl), ct);
+ return record is null ? Results.NotFound() : Results.Ok(record);
+});
+
// Store an uploaded diploma against a zaak (S-10b): the domain sends the file as base64; the ACL
// creates the ZGW enkelvoudiginformatieobject and relates it to the zaak (§8.1). Returns its URL.
app.MapPost("/documenten", async (StoreDocumentRequest body, AclService acl, CancellationToken ct) =>
@@ -131,6 +141,9 @@ public sealed record CancelZaakRequest(string ZaakUrl);
public sealed record ZaakReferenceRequest(string ZaakUrl);
+/// The object whose register record the Event Subscriber wants read back (S-19b-2).
+public sealed record RegisterRecordReadRequest(string ObjectUrl);
+
public sealed record StoreDocumentRequest(string ZaakUrl, string ContentBase64, string FileName, string ContentType);
public partial class Program;
diff --git a/services/acl/Acl.Application/AclService.cs b/services/acl/Acl.Application/AclService.cs
index f3b7973..1195df2 100644
--- a/services/acl/Acl.Application/AclService.cs
+++ b/services/acl/Acl.Application/AclService.cs
@@ -24,7 +24,16 @@ public sealed class AclService(
clock.Today,
registration.Reference);
- return await gateway.OpenZaakAsync(request, ct);
+ var zaakUrl = await gateway.OpenZaakAsync(request, ct);
+
+ // The register — not ZGW — is what the read projection is sourced from (ADR-0028/ADR-0030),
+ // so the record exists from submission, not only from approval. Same two-writes-converging
+ // posture as ApproveZaakAsync: the upsert is keyed on the zaak id, so a retried submit
+ // updates the record rather than adding a second one (§8.6).
+ await register.UpsertAsync(
+ new RegisterRecord(ZaakId(zaakUrl), RegisterRecordStatus.Ingediend, registration.Reference), ct);
+
+ return zaakUrl;
}
///
diff --git a/services/acl/Acl.Infrastructure/ObjectenGateway.cs b/services/acl/Acl.Infrastructure/ObjectenGateway.cs
index 80aa715..36603c4 100644
--- a/services/acl/Acl.Infrastructure/ObjectenGateway.cs
+++ b/services/acl/Acl.Infrastructure/ObjectenGateway.cs
@@ -1,3 +1,4 @@
+using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
@@ -38,8 +39,29 @@ public sealed class ObjectenGateway(HttpClient http, ObjectenOptions options, IC
"Updating the register record", ct);
}
- public Task GetAsync(Uri objectUrl, CancellationToken ct = default)
- => throw new NotImplementedException();
+ public async Task GetAsync(Uri objectUrl, CancellationToken ct = default)
+ {
+ ArgumentNullException.ThrowIfNull(objectUrl);
+
+ // Fetched by the URL the notification carried, so no objecttype resolution and no search —
+ // unlike a write, which has to find the object for a registration id.
+ using var message = new HttpRequestMessage(HttpMethod.Get, objectUrl);
+ message.Headers.Authorization = new AuthenticationHeaderValue("Token", options.Token);
+ message.Headers.Add("Accept-Crs", "EPSG:4326");
+
+ using var response = await http.SendAsync(message, ct);
+ // The object may be gone by the time a (possibly redelivered) notification is handled —
+ // there is simply nothing to project, which is not a failure (§8.6).
+ if (response.StatusCode == HttpStatusCode.NotFound)
+ return null;
+
+ await EnsureSuccessAsync(response, "Reading the register record", ct);
+
+ var body = await response.Content.ReadFromJsonAsync(ct)
+ ?? throw new InvalidOperationException("Objecten returned an empty object response");
+ var data = body.Record?.Data;
+ return data is null ? null : new RegisterRecord(data.Id, data.Status, data.Reference);
+ }
private RecordDto NewRecord(int typeVersion, RecordDataDto data) =>
new(typeVersion, data, clock.Today.ToString("yyyy-MM-dd"));
@@ -144,6 +166,12 @@ public sealed class ObjectenGateway(HttpClient http, ObjectenOptions options, IC
private sealed record ObjectDto(
[property: JsonPropertyName("url")] string Url);
+ private sealed record ReadObjectDto(
+ [property: JsonPropertyName("record")] ReadRecordDto? Record);
+
+ private sealed record ReadRecordDto(
+ [property: JsonPropertyName("data")] RecordDataDto? Data);
+
private sealed record CreateObjectDto(
[property: JsonPropertyName("type")] string Type,
[property: JsonPropertyName("record")] RecordDto Record);