diff --git a/CHANGELOG.md b/CHANGELOG.md
index f73c2680..26f245eb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,34 @@ Changes for the upcoming release can be found in [changelog.d](https://github.co
+
+## 5.0.0 (2023-12-05)
+
+### Backwards-incompatible changes
+
+- Safir now depends on Pydantic v2. Python code that uses any part of Safir related to Pydantic will also need to update to Pydantic v2, since the API is significantly different. See the [Pydantic migration guide](https://docs.pydantic.dev/latest/migration/) for more information.
+- `safir.pydantic.validate_exactly_one_of` is now a Pydantic model validator. It must be called with `mode="after"`, since it operates in the model rather than on a raw dictionary.
+- Remove the `GitHubAppClientFactory.create_app_client` method, which does not work with the Gidgethub API. Instead, the documentation shows how to create a JWT with the `GitHubAppClientFactory` and pass it with requests.
+- `safir.github.GitHubAppClientFactory` now expects the application ID and installation ID (for `create_installation_client`) to be of type `int`, not `str`. This appears to match what GitHub's API returns, but not what Gidgethub expects. The ID is converted to a string when passing it to Gidgethub.
+
+### New features
+
+- Allow the `safir.logging.LogLevel` enum to be created from strings of any case, which will allow the logging level to be specified with any case for Safir applications that use Pydantic to validate the field.
+- Add validated but ignored optional `propagation_policy` arguments to every delete method of the Kubernetes mock for better compatibility with the actual Kubernetes API. Previously, this argument was only accepted by `delete_namespaced_job`.
+- All mock Kubernetes methods now accept and ignore a `_request_timeout` error for better compatibility with the Kubernetes API.
+- Add delete, list, and watch support for persistent volume claims to the Kubernetes mock.
+
+### Bug fixes
+
+- `safir.database.datetime_to_db`, `safir.datetime.format_datetime_for_logging`, and `safir.datetime.isodatetime` now accept any `datetime` object with a time zone whose offset from UTC is 0, rather than only the `datetime.UTC` time zone object.
+- `safir.pydantic.normalize_datetime` now explicitly rejects input other than seconds since epoch or datetime objects with a validation error rather than attempting to treat the input as a datetime object and potentially throwing more obscure errors.
+- The `_request_timeout` parameters to mock Kubernetes methods now accept a float instead of an int to more correctly match the types of kubernetes_asyncio. The mock still does not accept a tuple of timeouts.
+- Avoid reusing the same metadata object when creating a `Pod` from a `Job`. Previous versions modified the `spec` part of the `Job` when adding additional metadata to the child `Pod`.
+
+### Other changes
+
+- Safir is now tested with Python 3.12 as well as Python 3.11.
+
## 4.5.0 (2023-09-12)
diff --git a/changelog.d/20230911_180355_rra_DM_40744.md b/changelog.d/20230911_180355_rra_DM_40744.md
deleted file mode 100644
index a953ecfa..00000000
--- a/changelog.d/20230911_180355_rra_DM_40744.md
+++ /dev/null
@@ -1,4 +0,0 @@
-### Backwards-incompatible changes
-
-- Safir now depends on Pydantic v2. Python code that uses any part of Safir related to Pydantic will also need to update to Pydantic v2, since the API is significantly different. See the [Pydantic migration guide](https://docs.pydantic.dev/latest/migration/) for more information.
-- `safir.pydantic.validate_exactly_one_of` is now a Pydantic model validator. It must be called with `mode="after"`, since it operates in the model rather than on a raw dictionary.
diff --git a/changelog.d/20230914_153856_rra_DM_40744.md b/changelog.d/20230914_153856_rra_DM_40744.md
deleted file mode 100644
index 4867c51f..00000000
--- a/changelog.d/20230914_153856_rra_DM_40744.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### Bug fixes
-
-- `safir.database.datetime_to_db`, `safir.datetime.format_datetime_for_logging`, and `safir.datetime.isodatetime` now accept any `datetime` object with a time zone whose offset from UTC is 0, rather than only the `datetime.UTC` time zone object.
diff --git a/changelog.d/20230914_173515_jsick_DM_39710_remove_app_factory.md b/changelog.d/20230914_173515_jsick_DM_39710_remove_app_factory.md
deleted file mode 100644
index 4d7e7735..00000000
--- a/changelog.d/20230914_173515_jsick_DM_39710_remove_app_factory.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### Backwards-incompatible changes
-
-- Remove the `GitHubAppClientFactory.create_app_client` method, which does not work with the Gidgethub API. Instead, the documentation shows how to create a JWT with the `GitHubAppClientFactory` and pass it with requests.
diff --git a/changelog.d/20230918_114322_rra_DM_40744.md b/changelog.d/20230918_114322_rra_DM_40744.md
deleted file mode 100644
index e8eef5ec..00000000
--- a/changelog.d/20230918_114322_rra_DM_40744.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### Backwards-incompatible changes
-
-- `safir.github.GitHubAppClientFactory` now expects the application ID and installation ID (for `create_installation_client`) to be of type `int`, not `str`. This appears to match what GitHub's API returns, but not what Gidgethub expects. The ID is converted to a string when passing it to Gidgethub.
diff --git a/changelog.d/20230918_134911_rra_DM_40744.md b/changelog.d/20230918_134911_rra_DM_40744.md
deleted file mode 100644
index a88ab71b..00000000
--- a/changelog.d/20230918_134911_rra_DM_40744.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### Bug fixes
-
-- `safir.pydantic.normalize_datetime` now explicitly rejects input other than seconds since epoch or datetime objects with a validation error rather than attempting to treat the input as a datetime object and potentially throwing more obscure errors.
diff --git a/changelog.d/20231003_135049_rra_DM_23878.md b/changelog.d/20231003_135049_rra_DM_23878.md
deleted file mode 100644
index d9ca374a..00000000
--- a/changelog.d/20231003_135049_rra_DM_23878.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### New features
-
-- Allow the `safir.logging.LogLevel` enum to be created from strings of any case, which will allow the logging level to be specified with any case for Safir applications that use Pydantic to validate the field.
diff --git a/changelog.d/20231110_124506_rra_DM_41630.md b/changelog.d/20231110_124506_rra_DM_41630.md
deleted file mode 100644
index 7d06939f..00000000
--- a/changelog.d/20231110_124506_rra_DM_41630.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### New features
-
-- Add validated but ignored optional `propagation_policy` arguments to every delete method of the Kubernetes mock for better compatibility with the actual Kubernetes API. Previously, this argument was only accepted by `delete_namespaced_job`.
diff --git a/changelog.d/20231110_141912_rra_DM_41630.md b/changelog.d/20231110_141912_rra_DM_41630.md
deleted file mode 100644
index 00581c39..00000000
--- a/changelog.d/20231110_141912_rra_DM_41630.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### New features
-
-- All mock Kubernetes methods now accept and ignore a `_request_timeout` error for better compatibility with the Kubernetes API.
diff --git a/changelog.d/20231110_165902_rra_DM_41630b.md b/changelog.d/20231110_165902_rra_DM_41630b.md
deleted file mode 100644
index 0f0079e3..00000000
--- a/changelog.d/20231110_165902_rra_DM_41630b.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### Bug fixes
-
-- The `_request_timeout` parameters to mock Kubernetes methods now accept a float instead of an int to more correctly match the types of kubernetes_asyncio. The mock still does not accept a tuple of timeouts.
diff --git a/changelog.d/20231114_171851_rra_DM_41708a.md b/changelog.d/20231114_171851_rra_DM_41708a.md
deleted file mode 100644
index 0d38dbcc..00000000
--- a/changelog.d/20231114_171851_rra_DM_41708a.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### New features
-
-- Add delete, list, and watch support for persistent volume claims to the Kubernetes mock.
diff --git a/changelog.d/20231116_110515_rra_DM_41708a.md b/changelog.d/20231116_110515_rra_DM_41708a.md
deleted file mode 100644
index 98deb046..00000000
--- a/changelog.d/20231116_110515_rra_DM_41708a.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### Bug fixes
-
-- Avoid reusing the same metadata object when creating a `Pod` from a `Job`. Previous versions modified the `spec` part of the `Job` when adding additional metadata to the child `Pod`.
diff --git a/changelog.d/20231205_093902_rra_DM_41998.md b/changelog.d/20231205_093902_rra_DM_41998.md
deleted file mode 100644
index 4c8b8352..00000000
--- a/changelog.d/20231205_093902_rra_DM_41998.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### Other changes
-
-- Safir is now tested with Python 3.12 as well as Python 3.11.