diff --git a/register-referentie.slnx b/register-referentie.slnx
index 40d5e27..072c997 100644
--- a/register-referentie.slnx
+++ b/register-referentie.slnx
@@ -9,6 +9,7 @@
+
diff --git a/services/domain/Big.Application/Big.Application.csproj b/services/domain/Big.Application/Big.Application.csproj
new file mode 100644
index 0000000..b877d01
--- /dev/null
+++ b/services/domain/Big.Application/Big.Application.csproj
@@ -0,0 +1,15 @@
+
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/services/domain/Big.Application/OpenZaakWorker.cs b/services/domain/Big.Application/OpenZaakWorker.cs
new file mode 100644
index 0000000..6f01c36
--- /dev/null
+++ b/services/domain/Big.Application/OpenZaakWorker.cs
@@ -0,0 +1,27 @@
+using Big.Domain;
+
+namespace Big.Application;
+
+///
+/// Handles one acquired OpenZaakAanmaken external-worker job (ADR-0009): load the registration
+/// the job correlates to, open a zaak for it via the ACL (§8.1), attach the zaak to the aggregate, and
+/// return the zaak URL so the caller can complete the Flowable job. Pure application logic over ports —
+/// it knows nothing of Flowable; the polling loop that feeds it jobs lives in Infrastructure.
+///
+public sealed class OpenZaakWorker(IRegistrationStore store, IAclClient acl)
+{
+ ///
+ /// Process the job and return the URL of the (existing or newly opened) zaak. Idempotent: if the
+ /// registration already has a zaak — a job redelivered after its completion was lost — it returns
+ /// that zaak without opening a second one (§8.6, at-least-once delivery). An unknown registration
+ /// is an error: it throws, leaving the job un-completed for Flowable to redeliver.
+ ///
+ public async Task HandleAsync(OpenZaakJob job, CancellationToken ct = default)
+ {
+ ArgumentNullException.ThrowIfNull(job);
+
+ // STUB (red): does not load, call the ACL, or attach.
+ await Task.CompletedTask;
+ return new Uri("about:blank");
+ }
+}
diff --git a/services/domain/Big.Application/Ports.cs b/services/domain/Big.Application/Ports.cs
new file mode 100644
index 0000000..800b036
--- /dev/null
+++ b/services/domain/Big.Application/Ports.cs
@@ -0,0 +1,47 @@
+using Big.Domain;
+
+namespace Big.Application;
+
+///
+/// The port to the workflow engine. Implemented by the Workflow Client in Infrastructure — the
+/// only code that talks to Flowable (CLAUDE.md §8.2). The application asks it to start the
+/// registratie process; it never knows Flowable exists.
+///
+public interface IWorkflowClient
+{
+ ///
+ /// Start one registratie process instance for the given registration, carrying the
+ /// registration id so the OpenZaakAanmaken external task can be correlated back to its
+ /// aggregate. Returns the process instance id.
+ ///
+ Task StartRegistrationProcessAsync(RegistrationId registrationId, CancellationToken ct = default);
+}
+
+///
+/// The port to the Anti-Corruption Layer. Implemented in Infrastructure by an HTTP client to the
+/// ACL service — the only code that talks to ZGW (CLAUDE.md §8.1). The domain hands over a
+/// bsn; the ACL default-fills the ZGW-mandatory fields (ADR-0003) and returns the created zaak URL.
+///
+public interface IAclClient
+{
+ Task OpenZaakAsync(string bsn, CancellationToken ct = default);
+}
+
+///
+/// Persistence port for the aggregate. In-memory for the minimal slice
+/// (ADR-0009); an EF-backed store is a documented follow-up, and this port keeps that change additive.
+///
+public interface IRegistrationStore
+{
+ /// Insert or update the registration, keyed on its id.
+ Task SaveAsync(Registration registration, CancellationToken ct = default);
+
+ /// Load a registration by id, or null if none exists.
+ Task GetAsync(RegistrationId id, CancellationToken ct = default);
+}
+
+///
+/// An acquired OpenZaakAanmaken external-worker job: the Flowable job id (needed to complete
+/// it) and the registration id it carries as a process variable.
+///
+public sealed record OpenZaakJob(string JobId, RegistrationId RegistrationId);
diff --git a/services/domain/Big.Application/SubmitRegistration.cs b/services/domain/Big.Application/SubmitRegistration.cs
new file mode 100644
index 0000000..c4f3555
--- /dev/null
+++ b/services/domain/Big.Application/SubmitRegistration.cs
@@ -0,0 +1,25 @@
+using Big.Domain;
+
+namespace Big.Application;
+
+/// A zorgprofessional's request to register, in domain language. No ZGW concepts.
+public sealed record SubmitRegistrationCommand(string Bsn);
+
+///
+/// The submit use case: create the aggregate (INGEDIEND), persist it,
+/// then start the registratie workflow process and record its instance id. It returns as soon as
+/// the process is started — opening the zaak happens later, off the request path, in the external-task
+/// worker (ADR-0009). Persisting before starting the process closes the race where the worker
+/// acquires the OpenZaakAanmaken job before the aggregate it correlates to exists.
+///
+public sealed class SubmitRegistration(IRegistrationStore store, IWorkflowClient workflow)
+{
+ public async Task HandleAsync(SubmitRegistrationCommand command, CancellationToken ct = default)
+ {
+ ArgumentNullException.ThrowIfNull(command);
+
+ // STUB (red): no persistence, no process start.
+ await Task.CompletedTask;
+ return RegistrationId.New();
+ }
+}
diff --git a/services/domain/Big.Tests/Big.Tests.csproj b/services/domain/Big.Tests/Big.Tests.csproj
index 575ce15..52fba41 100644
--- a/services/domain/Big.Tests/Big.Tests.csproj
+++ b/services/domain/Big.Tests/Big.Tests.csproj
@@ -20,6 +20,7 @@
+
diff --git a/services/domain/Big.Tests/Fakes.cs b/services/domain/Big.Tests/Fakes.cs
new file mode 100644
index 0000000..d32397f
--- /dev/null
+++ b/services/domain/Big.Tests/Fakes.cs
@@ -0,0 +1,60 @@
+using Big.Application;
+using Big.Domain;
+
+namespace Big.Tests;
+
+/// An in-memory for the application-layer tests. Upserts
+/// keyed on the registration id, mirroring the production in-memory store.
+internal sealed class InMemoryRegistrationStore : IRegistrationStore
+{
+ private readonly Dictionary _byId = [];
+
+ public int SaveCount { get; private set; }
+
+ public Task SaveAsync(Registration registration, CancellationToken ct = default)
+ {
+ SaveCount++;
+ _byId[registration.Id] = registration;
+ return Task.CompletedTask;
+ }
+
+ public Task GetAsync(RegistrationId id, CancellationToken ct = default)
+ => Task.FromResult(_byId.GetValueOrDefault(id));
+
+ public void Seed(Registration registration) => _byId[registration.Id] = registration;
+}
+
+/// A fake Workflow Client that records the registration it was asked to start a process for
+/// and returns a fixed process-instance id. An optional callback runs at start time, letting a test
+/// assert ordering (e.g. that the registration was persisted before the process started).
+internal sealed class FakeWorkflowClient(string processInstanceId = "proc-1", Action? onStart = null)
+ : IWorkflowClient
+{
+ public RegistrationId? StartedFor { get; private set; }
+
+ public Task StartRegistrationProcessAsync(RegistrationId registrationId, CancellationToken ct = default)
+ {
+ onStart?.Invoke(registrationId);
+ StartedFor = registrationId;
+ return Task.FromResult(processInstanceId);
+ }
+}
+
+/// A fake ACL client that records the bsn it was asked to open a zaak for and returns a
+/// fixed zaak URL.
+internal sealed class FakeAclClient(Uri? zaakUrl = null) : IAclClient
+{
+ public static readonly Uri DefaultZaakUrl = new("http://openzaak/zaken/api/v1/zaken/abc");
+
+ private readonly Uri _zaakUrl = zaakUrl ?? DefaultZaakUrl;
+
+ public string? OpenedForBsn { get; private set; }
+ public int CallCount { get; private set; }
+
+ public Task OpenZaakAsync(string bsn, CancellationToken ct = default)
+ {
+ CallCount++;
+ OpenedForBsn = bsn;
+ return Task.FromResult(_zaakUrl);
+ }
+}
diff --git a/services/domain/Big.Tests/OpenZaakWorkerTests.cs b/services/domain/Big.Tests/OpenZaakWorkerTests.cs
new file mode 100644
index 0000000..c00ba7a
--- /dev/null
+++ b/services/domain/Big.Tests/OpenZaakWorkerTests.cs
@@ -0,0 +1,60 @@
+using Big.Application;
+using Big.Domain;
+
+namespace Big.Tests;
+
+public class OpenZaakWorkerTests
+{
+ private static Registration Submitted(string bsn = "123456782")
+ {
+ var registration = Registration.Submit(bsn);
+ registration.RecordProcessStarted("proc-1");
+ return registration;
+ }
+
+ [Fact]
+ public async Task Handling_a_job_opens_a_zaak_via_the_acl_and_attaches_it()
+ {
+ var registration = Submitted();
+ var store = new InMemoryRegistrationStore();
+ store.Seed(registration);
+ var acl = new FakeAclClient();
+ var worker = new OpenZaakWorker(store, acl);
+
+ var zaakUrl = await worker.HandleAsync(new OpenZaakJob("job-1", registration.Id));
+
+ Assert.Equal(FakeAclClient.DefaultZaakUrl, zaakUrl);
+ Assert.Equal("123456782", acl.OpenedForBsn);
+ var saved = await store.GetAsync(registration.Id);
+ Assert.Equal(FakeAclClient.DefaultZaakUrl, saved!.ZaakUrl);
+ }
+
+ [Fact]
+ public async Task Handling_a_job_for_an_unknown_registration_throws_and_opens_no_zaak()
+ {
+ var store = new InMemoryRegistrationStore();
+ var acl = new FakeAclClient();
+ var worker = new OpenZaakWorker(store, acl);
+
+ await Assert.ThrowsAsync(
+ () => worker.HandleAsync(new OpenZaakJob("job-1", RegistrationId.New())));
+ Assert.Equal(0, acl.CallCount);
+ }
+
+ [Fact]
+ public async Task Handling_a_redelivered_job_is_idempotent_and_does_not_open_a_second_zaak()
+ {
+ var registration = Submitted();
+ var store = new InMemoryRegistrationStore();
+ store.Seed(registration);
+ var acl = new FakeAclClient();
+ var worker = new OpenZaakWorker(store, acl);
+ var job = new OpenZaakJob("job-1", registration.Id);
+
+ var first = await worker.HandleAsync(job);
+ var second = await worker.HandleAsync(job);
+
+ Assert.Equal(first, second);
+ Assert.Equal(1, acl.CallCount);
+ }
+}
diff --git a/services/domain/Big.Tests/SubmitRegistrationTests.cs b/services/domain/Big.Tests/SubmitRegistrationTests.cs
new file mode 100644
index 0000000..b1b15a7
--- /dev/null
+++ b/services/domain/Big.Tests/SubmitRegistrationTests.cs
@@ -0,0 +1,52 @@
+using Big.Application;
+using Big.Domain;
+
+namespace Big.Tests;
+
+public class SubmitRegistrationTests
+{
+ [Fact]
+ public async Task Submitting_persists_an_ingediend_registration_and_starts_the_process()
+ {
+ var store = new InMemoryRegistrationStore();
+ var workflow = new FakeWorkflowClient(processInstanceId: "proc-42");
+ var handler = new SubmitRegistration(store, workflow);
+
+ var id = await handler.HandleAsync(new SubmitRegistrationCommand("123456782"));
+
+ var saved = await store.GetAsync(id);
+ Assert.NotNull(saved);
+ Assert.Equal("123456782", saved.Bsn);
+ Assert.Equal(RegistrationStatus.Ingediend, saved.Status);
+ Assert.Equal("proc-42", saved.ProcessInstanceId);
+ Assert.Equal(id, workflow.StartedFor);
+ Assert.Null(saved.ZaakUrl);
+ }
+
+ [Fact]
+ public async Task Submitting_persists_the_registration_before_starting_the_process()
+ {
+ // The worker correlates the OpenZaakAanmaken job back to the aggregate by id, so the
+ // aggregate must already be persisted when the process starts (ADR-0009).
+ var store = new InMemoryRegistrationStore();
+ Registration? visibleAtStart = null;
+ var workflow = new FakeWorkflowClient(onStart: id => visibleAtStart = store.GetAsync(id).Result);
+ var handler = new SubmitRegistration(store, workflow);
+
+ await handler.HandleAsync(new SubmitRegistrationCommand("123456782"));
+
+ Assert.NotNull(visibleAtStart);
+ }
+
+ [Fact]
+ public async Task Submitting_without_a_bsn_is_rejected_and_starts_no_process()
+ {
+ var store = new InMemoryRegistrationStore();
+ var workflow = new FakeWorkflowClient();
+ var handler = new SubmitRegistration(store, workflow);
+
+ await Assert.ThrowsAnyAsync(
+ () => handler.HandleAsync(new SubmitRegistrationCommand("")));
+ Assert.Null(workflow.StartedFor);
+ }
+}
diff --git a/services/domain/Big.slnx b/services/domain/Big.slnx
index 6125bff..78b7664 100644
--- a/services/domain/Big.slnx
+++ b/services/domain/Big.slnx
@@ -1,4 +1,5 @@
+