Repository: cloudflare/daphne
Pull request #506 (2024-02-22 23:36:09)

Prepare to support the next DAP version
Change `DapVersion::DraftLatest` to `DapVersion::Draft09` and use `DapVersion::Latest` to track the next draft we'll target (currently draft 10, which is not yet published). So far there are no substantive changes in the next draft, but changes will be required to enable Mastic in its "heavy hitters" mode of operation.

cjpatton: Rebased.
cjpatton: After some self-review, I realized I forgot to add the latest version to enable the latest version in `async_test_versions!`. Once I did, I found several match statements where I forgot to add `DapVersion::Latest` to the necessary arm.
mendess: > After some self-review, I realized I forgot to add the latest version to enable the latest version in `async_test_versions!`. Once I did, I found several match statements where I forgot to add `DapVersion::Latest` to the necessary arm.

why did these not get caught by CI?
cjpatton: > > After some self-review, I realized I forgot to add the latest version to enable the latest version in `async_test_versions!`. Once I did, I found several match statements where I forgot to add `DapVersion::Latest` to the necessary arm.
> 
> why did these not get caught by CI?

None of the tests were enabled for the latest version, which means the tests didn't exercise the `DapVersion::Latest` variant.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index e3b4469..84da488 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -780,6 +780,7 @@ dependencies = [
  "hex",
  "http",
  "hyper",
+ "paste",
  "prio",
  "prometheus",
  "rand",
diff --git a/daphne/src/constants.rs b/daphne/src/constants.rs
index bab3d30..ebf5668 100644
--- a/daphne/src/constants.rs
+++ b/daphne/src/constants.rs
@@ -63,29 +63,41 @@ impl DapMediaType {
         let (content_type, _) = content_type.split_once(';').unwrap_or((content_type, ""));
         let media_type = match (version, content_type) {
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_AGG_CONT_REQ)
-            | (DapVersion::DraftLatest, MEDIA_TYPE_AGG_JOB_CONT_REQ) => {
+            | (DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_AGG_JOB_CONT_REQ) => {
                 Self::AggregationJobContinueReq
             }
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_AGG_CONT_RESP) => {
                 Self::Draft02AggregateContinueResp
             }
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_AGG_INIT_REQ)
-            | (DapVersion::DraftLatest, MEDIA_TYPE_AGG_JOB_INIT_REQ) => Self::AggregationJobInitReq,
+            | (DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_AGG_JOB_INIT_REQ) => {
+                Self::AggregationJobInitReq
+            }
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_AGG_INIT_RESP)
-            | (DapVersion::DraftLatest, MEDIA_TYPE_AGG_JOB_RESP) => Self::AggregationJobResp,
+            | (DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_AGG_JOB_RESP) => {
+                Self::AggregationJobResp
+            }
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_AGG_SHARE_RESP)
-            | (DapVersion::DraftLatest, MEDIA_TYPE_AGG_SHARE) => Self::AggregateShare,
+            | (DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_AGG_SHARE) => {
+                Self::AggregateShare
+            }
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_COLLECT_RESP)
-            | (DapVersion::DraftLatest, MEDIA_TYPE_COLLECTION) => Self::Collection,
+            | (DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_COLLECTION) => Self::Collection,
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_HPKE_CONFIG)
-            | (DapVersion::DraftLatest, MEDIA_TYPE_HPKE_CONFIG_LIST) => Self::HpkeConfigList,
-            (DapVersion::Draft02 | DapVersion::DraftLatest, MEDIA_TYPE_AGG_SHARE_REQ) => {
-                Self::AggregateShareReq
+            | (DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_HPKE_CONFIG_LIST) => {
+                Self::HpkeConfigList
             }
-            (DapVersion::Draft02 | DapVersion::DraftLatest, MEDIA_TYPE_COLLECT_REQ) => {
-                Self::CollectReq
+            (
+                DapVersion::Draft02 | DapVersion::Draft09 | DapVersion::Latest,
+                MEDIA_TYPE_AGG_SHARE_REQ,
+            ) => Self::AggregateShareReq,
+            (
+                DapVersion::Draft02 | DapVersion::Draft09 | DapVersion::Latest,
+                MEDIA_TYPE_COLLECT_REQ,
+            ) => Self::CollectReq,
+            (DapVersion::Draft02 | DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_REPORT) => {
+                Self::Report
             }
-            (DapVersion::Draft02 | DapVersion::DraftLatest, MEDIA_TYPE_REPORT) => Self::Report,
             (_, _) => return None,
         };
         Some(media_type)
@@ -97,35 +109,44 @@ impl DapMediaType {
             (DapVersion::Draft02, Self::AggregationJobInitReq) => {
                 Some(DRAFT02_MEDIA_TYPE_AGG_INIT_REQ)
             }
-            (DapVersion::DraftLatest, Self::AggregationJobInitReq) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Self::AggregationJobInitReq) => {
                 Some(MEDIA_TYPE_AGG_JOB_INIT_REQ)
             }
             (DapVersion::Draft02, Self::AggregationJobResp) => {
                 Some(DRAFT02_MEDIA_TYPE_AGG_INIT_RESP)
             }
-            (DapVersion::DraftLatest, Self::AggregationJobResp) => Some(MEDIA_TYPE_AGG_JOB_RESP),
+            (DapVersion::Draft09 | DapVersion::Latest, Self::AggregationJobResp) => {
+                Some(MEDIA_TYPE_AGG_JOB_RESP)
+            }
             (DapVersion::Draft02, Self::AggregationJobContinueReq) => {
                 Some(DRAFT02_MEDIA_TYPE_AGG_CONT_REQ)
             }
-            (DapVersion::DraftLatest, Self::AggregationJobContinueReq) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Self::AggregationJobContinueReq) => {
                 Some(MEDIA_TYPE_AGG_JOB_CONT_REQ)
             }
             (DapVersion::Draft02, Self::Draft02AggregateContinueResp) => {
                 Some(DRAFT02_MEDIA_TYPE_AGG_CONT_RESP)
             }
-            (DapVersion::Draft02 | DapVersion::DraftLatest, Self::AggregateShareReq) => {
-                Some(MEDIA_TYPE_AGG_SHARE_REQ)
-            }
+            (
+                DapVersion::Draft02 | DapVersion::Draft09 | DapVersion::Latest,
+                Self::AggregateShareReq,
+            ) => Some(MEDIA_TYPE_AGG_SHARE_REQ),
             (DapVersion::Draft02, Self::AggregateShare) => Some(DRAFT02_MEDIA_TYPE_AGG_SHARE_RESP),
-            (DapVersion::DraftLatest, Self::AggregateShare) => Some(MEDIA_TYPE_AGG_SHARE),
-            (DapVersion::Draft02 | DapVersion::DraftLatest, Self::CollectReq) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Self::AggregateShare) => {
+                Some(MEDIA_TYPE_AGG_SHARE)
+            }
+            (DapVersion::Draft02 | DapVersion::Draft09 | DapVersion::Latest, Self::CollectReq) => {
                 Some(MEDIA_TYPE_COLLECT_REQ)
             }
             (DapVersion::Draft02, Self::Collection) => Some(DRAFT02_MEDIA_TYPE_COLLECT_RESP),
-            (DapVersion::DraftLatest, Self::Collection) => Some(MEDIA_TYPE_COLLECTION),
+            (DapVersion::Draft09 | DapVersion::Latest, Self::Collection) => {
+                Some(MEDIA_TYPE_COLLECTION)
+            }
             (DapVersion::Draft02, Self::HpkeConfigList) => Some(DRAFT02_MEDIA_TYPE_HPKE_CONFIG),
-            (DapVersion::DraftLatest, Self::HpkeConfigList) => Some(MEDIA_TYPE_HPKE_CONFIG_LIST),
-            (DapVersion::Draft02 | DapVersion::DraftLatest, Self::Report) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Self::HpkeConfigList) => {
+                Some(MEDIA_TYPE_HPKE_CONFIG_LIST)
+            }
+            (DapVersion::Draft02 | DapVersion::Draft09 | DapVersion::Latest, Self::Report) => {
                 Some(MEDIA_TYPE_REPORT)
             }
             (_, Self::Draft02AggregateContinueResp) => None,
@@ -137,7 +158,7 @@ impl DapMediaType {
     pub(crate) fn agg_job_cont_resp_for_version(version: DapVersion) -> Self {
         match version {
             DapVersion::Draft02 => Self::Draft02AggregateContinueResp,
-            DapVersion::DraftLatest => Self::AggregationJobResp,
+            DapVersion::Draft09 | DapVersion::Latest => Self::AggregationJobResp,
         }
     }
 }
@@ -207,64 +228,58 @@ mod test {
 
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
-                "application/dap-hpke-config-list"
+                DapVersion::Draft09,
+                "application/dap-hpke-config-list",
             ),
             Some(DapMediaType::HpkeConfigList)
         );
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
+                DapVersion::Draft09,
                 "application/dap-aggregation-job-init-req"
             ),
             Some(DapMediaType::AggregationJobInitReq),
         );
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
+                DapVersion::Draft09,
                 "application/dap-aggregation-job-resp"
             ),
             Some(DapMediaType::AggregationJobResp),
         );
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
+                DapVersion::Draft09,
                 "application/dap-aggregation-job-continue-req"
             ),
             Some(DapMediaType::AggregationJobContinueReq),
         );
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
+                DapVersion::Draft09,
                 "application/dap-aggregate-share-req"
             ),
             Some(DapMediaType::AggregateShareReq),
         );
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
+                DapVersion::Draft09,
                 "application/dap-aggregate-share"
             ),
             Some(DapMediaType::AggregateShare),
         );
         assert_eq!(
-            DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
-                "application/dap-collect-req"
-            ),
+            DapMediaType::from_str_for_version(DapVersion::Draft09, "application/dap-collect-req"),
             Some(DapMediaType::CollectReq),
         );
         assert_eq!(
-            DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
-                "application/dap-collection"
-            ),
+            DapMediaType::from_str_for_version(DapVersion::Draft09, "application/dap-collection"),
             Some(DapMediaType::Collection),
         );
 
         // Invalid media type
         assert_eq!(
-            DapMediaType::from_str_for_version(DapVersion::DraftLatest, "blah-blah-blah"),
+            DapMediaType::from_str_for_version(DapVersion::Draft09, "blah-blah-blah"),
             None,
         );
     }
@@ -273,30 +288,27 @@ mod test {
     fn round_trip() {
         for (version, media_type) in [
             (DapVersion::Draft02, DapMediaType::AggregationJobInitReq),
-            (DapVersion::DraftLatest, DapMediaType::AggregationJobInitReq),
+            (DapVersion::Draft09, DapMediaType::AggregationJobInitReq),
             (DapVersion::Draft02, DapMediaType::AggregationJobResp),
-            (DapVersion::DraftLatest, DapMediaType::AggregationJobResp),
+            (DapVersion::Draft09, DapMediaType::AggregationJobResp),
             (DapVersion::Draft02, DapMediaType::AggregationJobContinueReq),
-            (
-                DapVersion::DraftLatest,
-                DapMediaType::AggregationJobContinueReq,
-            ),
+            (DapVersion::Draft09, DapMediaType::AggregationJobContinueReq),
             (
                 DapVersion::Draft02,
                 DapMediaType::Draft02AggregateContinueResp,
             ),
             (DapVersion::Draft02, DapMediaType::AggregateShareReq),
-            (DapVersion::DraftLatest, DapMediaType::AggregateShareReq),
+            (DapVersion::Draft09, DapMediaType::AggregateShareReq),
             (DapVersion::Draft02, DapMediaType::AggregateShare),
-            (DapVersion::DraftLatest, DapMediaType::AggregateShare),
+            (DapVersion::Draft09, DapMediaType::AggregateShare),
             (DapVersion::Draft02, DapMediaType::CollectReq),
-            (DapVersion::DraftLatest, DapMediaType::CollectReq),
+            (DapVersion::Draft09, DapMediaType::CollectReq),
             (DapVersion::Draft02, DapMediaType::Collection),
-            (DapVersion::DraftLatest, DapMediaType::Collection),
+            (DapVersion::Draft09, DapMediaType::Collection),
             (DapVersion::Draft02, DapMediaType::HpkeConfigList),
-            (DapVersion::DraftLatest, DapMediaType::HpkeConfigList),
+            (DapVersion::Draft09, DapMediaType::HpkeConfigList),
             (DapVersion::Draft02, DapMediaType::Report),
-            (DapVersion::DraftLatest, DapMediaType::Report),
+            (DapVersion::Draft09, DapMediaType::Report),
         ] {
             assert_eq!(
                 media_type
@@ -319,7 +331,7 @@ mod test {
 
         assert_eq!(
             DapMediaType::AggregationJobResp,
-            DapMediaType::agg_job_cont_resp_for_version(DapVersion::DraftLatest)
+            DapMediaType::agg_job_cont_resp_for_version(DapVersion::Draft09)
         );
     }
 
@@ -327,7 +339,7 @@ mod test {
     fn media_type_parsing_ignores_content_type_paramters() {
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
+                DapVersion::Draft09,
                 "application/dap-aggregation-job-init-req;version=09",
             ),
             Some(DapMediaType::AggregationJobInitReq),
diff --git a/daphne/src/lib.rs b/daphne/src/lib.rs
index b2bfe94..8f9916d 100644
--- a/daphne/src/lib.rs
+++ b/daphne/src/lib.rs
@@ -101,7 +101,10 @@ pub enum DapVersion {
 
     #[serde(rename = "v09")]
     #[default]
-    DraftLatest,
+    Draft09,
+
+    #[serde(rename = "v10")]
+    Latest,
 }
 
 impl FromStr for DapVersion {
@@ -109,7 +112,8 @@ impl FromStr for DapVersion {
     fn from_str(version: &str) -> Result<Self, Self::Err> {
         match version {
             "v02" => Ok(DapVersion::Draft02),
-            "v09" => Ok(DapVersion::DraftLatest),
+            "v09" => Ok(DapVersion::Draft09),
+            "v10" => Ok(DapVersion::Latest),
             _ => Err(DapAbort::version_unknown()),
         }
     }
@@ -119,7 +123,8 @@ impl AsRef<str> for DapVersion {
     fn as_ref(&self) -> &str {
         match self {
             DapVersion::Draft02 => "v02",
-            DapVersion::DraftLatest => "v09",
+            DapVersion::Draft09 => "v09",
+            DapVersion::Latest => "v10",
         }
     }
 }
@@ -493,7 +498,9 @@ impl DapTaskParameters {
         .unwrap();
 
         let (taskprov_advertisement, taskprov_report_extension_payload) = match self.version {
-            DapVersion::DraftLatest => (Some(encode_base64url(&encoded_taskprov_config)), None),
+            DapVersion::Draft09 | DapVersion::Latest => {
+                (Some(encode_base64url(&encoded_taskprov_config)), None)
+            }
             // draft02 compatibility: The taskprov config is advertised in an HTTP header in
             // the latest draft. In draft02, it is carried by a report extension.
             DapVersion::Draft02 => (None, Some(encoded_taskprov_config)),
@@ -1111,7 +1118,7 @@ pub struct DapRequest<S> {
 impl<S> Default for DapRequest<S> {
     fn default() -> Self {
         Self {
-            version: DapVersion::DraftLatest,
+            version: DapVersion::Draft09,
             media_type: None,
             task_id: Default::default(),
             resource: Default::default(),
@@ -1204,7 +1211,7 @@ pub struct DapLeaderProcessTelemetry {
 #[cfg_attr(any(test, feature = "test-utils"), derive(deepsize::DeepSizeOf))]
 pub enum MetaAggregationJobId {
     Draft02(Draft02AggregationJobId),
-    DraftLatest(AggregationJobId),
+    Draft09(AggregationJobId),
 }
 
 macro_rules! generate_from_id_impls_for_meta_agg_job_id {
@@ -1224,7 +1231,7 @@ macro_rules! generate_from_id_impls_for_meta_agg_job_id {
 }
 
 generate_from_id_impls_for_meta_agg_job_id!(Draft02 => Draft02AggregationJobId);
-generate_from_id_impls_for_meta_agg_job_id!(DraftLatest => AggregationJobId);
+generate_from_id_impls_for_meta_agg_job_id!(Draft09 => AggregationJobId);
 
 impl MetaAggregationJobId {
     /// Generate a random ID of the type required for the version.
@@ -1232,7 +1239,7 @@ impl MetaAggregationJobId {
         let mut rng = thread_rng();
         match version {
             DapVersion::Draft02 => Self::Draft02(Draft02AggregationJobId(rng.gen())),
-            DapVersion::DraftLatest => Self::DraftLatest(AggregationJobId(rng.gen())),
+            DapVersion::Draft09 | DapVersion::Latest => Self::Draft09(AggregationJobId(rng.gen())),
         }
     }
 
@@ -1241,7 +1248,7 @@ impl MetaAggregationJobId {
     pub(crate) fn for_request_payload(&self) -> Option<Draft02AggregationJobId> {
         match self {
             Self::Draft02(agg_job_id) => Some(*agg_job_id),
-            Self::DraftLatest(..) => None,
+            Self::Draft09(..) => None,
         }
     }
 
@@ -1251,7 +1258,7 @@ impl MetaAggregationJobId {
         match self {
             // In draft02, the aggregation job ID is not determined until the payload is parsed.
             Self::Draft02(..) => DapResource::Undefined,
-            Self::DraftLatest(agg_job_id) => DapResource::AggregationJob(*agg_job_id),
+            Self::Draft09(agg_job_id) => DapResource::AggregationJob(*agg_job_id),
         }
     }
 
@@ -1259,7 +1266,7 @@ impl MetaAggregationJobId {
     pub fn to_hex(&self) -> String {
         match self {
             Self::Draft02(agg_job_id) => agg_job_id.to_hex(),
-            Self::DraftLatest(agg_job_id) => agg_job_id.to_hex(),
+            Self::Draft09(agg_job_id) => agg_job_id.to_hex(),
         }
     }
 
@@ -1267,7 +1274,7 @@ impl MetaAggregationJobId {
     pub fn to_base64url(&self) -> String {
         match self {
             Self::Draft02(agg_job_id) => agg_job_id.to_base64url(),
-            Self::DraftLatest(agg_job_id) => agg_job_id.to_base64url(),
+            Self::Draft09(agg_job_id) => agg_job_id.to_base64url(),
         }
     }
 }
diff --git a/daphne/src/messages/mod.rs b/daphne/src/messages/mod.rs
index 91a5c15..4bae849 100644
--- a/daphne/src/messages/mod.rs
+++ b/daphne/src/messages/mod.rs
@@ -173,7 +173,7 @@ impl TaskId {
     pub fn for_request_payload(&self, version: &DapVersion) -> Option<TaskId> {
         match version {
             DapVersion::Draft02 => Some(*self),
-            DapVersion::DraftLatest => None,
+            DapVersion::Draft09 | DapVersion::Latest => None,
         }
     }
 }
@@ -223,11 +223,15 @@ impl ParameterizedEncode<DapVersion> for Extension {
             Self::Taskprov { draft02_payload } => {
                 EXTENSION_TASKPROV.encode(bytes)?;
                 match (version, draft02_payload) {
-                    (DapVersion::DraftLatest, None) => {
+                    (DapVersion::Draft09 | DapVersion::Latest, None) => {
                         encode_u16_prefixed(*version, bytes, |_, _| Ok(()))?;
                     }
                     (DapVersion::Draft02, Some(payload)) => encode_u16_bytes(bytes, payload)?,
-                    _ => unreachable!("unhandled version {version:?}"),
+                    _ => {
+                        return Err(CodecError::Other(
+                            format!("unhandled version: {version:?}").into(),
+                        ));
+                    }
                 }
             }
             Self::NotImplemented { typ, payload } => {
@@ -246,7 +250,7 @@ impl ParameterizedDecode<DapVersion> for Extension {
     ) -> Result<Self, CodecError> {
         let typ = u16::decode(bytes)?;
         match (version, typ) {
-            (DapVersion::DraftLatest, EXTENSION_TASKPROV) => {
+            (DapVersion::Draft09 | DapVersion::Latest, EXTENSION_TASKPROV) => {
                 decode_u16_prefixed(*version, bytes, |_version, inner, _len| <()>::decode(inner))?;
                 Ok(Self::Taskprov {
                     draft02_payload: None,
@@ -283,11 +287,15 @@ impl ParameterizedEncode<DapVersion> for ReportMetadata {
         self.id.encode(bytes)?;
         self.time.encode(bytes)?;
         match (version, &self.draft02_extensions) {
-            (DapVersion::DraftLatest, None) => (),
+            (DapVersion::Draft09 | DapVersion::Latest, None) => (),
             (DapVersion::Draft02, Some(extensions)) => {
                 encode_u16_items(bytes, version, extensions)?;
             }
-            _ => unreachable!("extensions should be set in (and only in) draft02"),
+            _ => {
+                return Err(CodecError::Other(
+                    "extensions should be set in (and only in) draft02".into(),
+                ))
+            }
         };
         Ok(())
     }
@@ -303,7 +311,7 @@ impl ParameterizedDecode<DapVersion> for ReportMetadata {
             time: Time::decode(bytes)?,
             draft02_extensions: match version {
                 DapVersion::Draft02 => Some(decode_u16_items(version, bytes)?),
-                DapVersion::DraftLatest => None,
+                DapVersion::Draft09 | DapVersion::Latest => None,
             },
         };
 
@@ -338,7 +346,7 @@ impl ParameterizedEncode<DapVersion> for Report {
         encode_u32_bytes(bytes, &self.public_share)?;
         match version {
             DapVersion::Draft02 => encode_u32_items(bytes, &(), &self.encrypted_input_shares)?,
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 self.encrypted_input_shares[0].encode(bytes)?;
                 self.encrypted_input_shares[1].encode(bytes)?;
             }
@@ -365,7 +373,7 @@ impl ParameterizedDecode<DapVersion> for Report {
                 DapVersion::Draft02 => decode_u32_items(&(), bytes)?
                     .try_into()
                     .map_err(|_| CodecError::UnexpectedValue)?,
-                DapVersion::DraftLatest => [
+                DapVersion::Draft09 | DapVersion::Latest => [
                     HpkeCiphertext::decode(bytes)?,
                     HpkeCiphertext::decode(bytes)?,
                 ],
@@ -377,7 +385,7 @@ impl ParameterizedDecode<DapVersion> for Report {
 /// An initial aggregate sub-request sent in an [`AggregationJobInitReq`]. The contents of this
 /// structure pertain to a single report.
 #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
-#[allow(missing_docs)]
+#[cfg_attr(any(test, feature = "test-utils"), derive(deepsize::DeepSizeOf))]
 pub struct ReportShare {
     pub report_metadata: ReportMetadata,
     pub public_share: Vec<u8>,
@@ -523,9 +531,10 @@ impl Default for BatchSelector {
 
 /// The `PrepareInit` message consisting of the report share and the Leader's initial prep share.
 #[derive(Clone, Debug, PartialEq, Eq)]
+#[cfg_attr(any(test, feature = "test-utils"), derive(deepsize::DeepSizeOf))]
 pub struct PrepareInit {
     pub report_share: ReportShare,
-    pub draft_latest_payload: Option<Vec<u8>>,
+    pub draft09_payload: Option<Vec<u8>>,
 }
 
 impl ParameterizedEncode<DapVersion> for PrepareInit {
@@ -535,12 +544,16 @@ impl ParameterizedEncode<DapVersion> for PrepareInit {
         bytes: &mut Vec<u8>,
     ) -> Result<(), CodecError> {
         self.report_share.encode_with_param(version, bytes)?;
-        match (version, &self.draft_latest_payload) {
+        match (version, &self.draft09_payload) {
             (DapVersion::Draft02, None) => (),
-            (DapVersion::DraftLatest, Some(payload)) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Some(payload)) => {
                 encode_u32_bytes(bytes, payload)?;
             }
-            _ => unreachable!("unhandled version {version:?}"),
+            _ => {
+                return Err(CodecError::Other(
+                    format!("unhandled version: {version:?}").into(),
+                ))
+            }
         };
         Ok(())
     }
@@ -552,14 +565,14 @@ impl ParameterizedDecode<DapVersion> for PrepareInit {
         bytes: &mut Cursor<&[u8]>,
     ) -> Result<Self, CodecError> {
         let report_share = ReportShare::decode_with_param(version, bytes)?;
-        let draft_latest_payload = match version {
+        let draft09_payload = match version {
             DapVersion::Draft02 => None,
-            DapVersion::DraftLatest => Some(decode_u32_bytes(bytes)?),
+            DapVersion::Draft09 | DapVersion::Latest => Some(decode_u32_bytes(bytes)?),
         };
 
         Ok(Self {
             report_share,
-            draft_latest_payload,
+            draft09_payload,
         })
     }
 }
@@ -592,7 +605,7 @@ impl ParameterizedEncode<DapVersion> for AggregationJobInitReq {
                     .encode(bytes)?;
                 encode_u16_bytes(bytes, &self.agg_param)?;
             }
-            DapVersion::DraftLatest => encode_u32_bytes(bytes, &self.agg_param)?,
+            DapVersion::Draft09 | DapVersion::Latest => encode_u32_bytes(bytes, &self.agg_param)?,
         };
         self.part_batch_sel.encode(bytes)?;
         encode_u32_items(bytes, version, &self.prep_inits)?;
@@ -611,7 +624,7 @@ impl ParameterizedDecode<DapVersion> for AggregationJobInitReq {
                 Some(Draft02AggregationJobId::decode(bytes)?),
                 decode_u16_bytes(bytes)?,
             ),
-            DapVersion::DraftLatest => (None, None, decode_u32_bytes(bytes)?),
+            DapVersion::Draft09 | DapVersion::Latest => (None, None, decode_u32_bytes(bytes)?),
         };
 
         Ok(Self {
@@ -650,7 +663,7 @@ impl ParameterizedEncode<DapVersion> for AggregationJobContinueReq {
                     .ok_or_else(|| CodecError::Other("draft02: missing task ID".into()))?
                     .encode(bytes)?;
             }
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 self.round
                     .as_ref()
                     .ok_or_else(|| CodecError::Other("missing round".into()))?
@@ -673,7 +686,7 @@ impl ParameterizedDecode<DapVersion> for AggregationJobContinueReq {
                 Some(Draft02AggregationJobId::decode(bytes)?),
                 None,
             ),
-            DapVersion::DraftLatest => (None, None, Some(u16::decode(bytes)?)),
+            DapVersion::Draft09 | DapVersion::Latest => (None, None, Some(u16::decode(bytes)?)),
         };
         Ok(Self {
             draft02_task_id,
@@ -1004,12 +1017,12 @@ impl ParameterizedEncode<DapVersion> for CollectionReq {
                     .ok_or_else(|| CodecError::Other("draft02: missing task ID".into()))?
                     .encode(bytes)?;
             }
-            DapVersion::DraftLatest => {}
+            DapVersion::Draft09 | DapVersion::Latest => {}
         }
         self.query.encode_with_param(version, bytes)?;
         match version {
             DapVersion::Draft02 => encode_u16_bytes(bytes, &self.agg_param)?,
-            DapVersion::DraftLatest => encode_u32_bytes(bytes, &self.agg_param)?,
+            DapVersion::Draft09 | DapVersion::Latest => encode_u32_bytes(bytes, &self.agg_param)?,
         };
         Ok(())
     }
@@ -1022,14 +1035,14 @@ impl ParameterizedDecode<DapVersion> for CollectionReq {
     ) -> Result<Self, CodecError> {
         let draft02_task_id = match version {
             DapVersion::Draft02 => Some(TaskId::decode(bytes)?),
-            DapVersion::DraftLatest => None,
+            DapVersion::Draft09 | DapVersion::Latest => None,
         };
         Ok(Self {
             draft02_task_id,
             query: Query::decode_with_param(version, bytes)?,
             agg_param: match version {
                 DapVersion::Draft02 => decode_u16_bytes(bytes)?,
-                DapVersion::DraftLatest => decode_u32_bytes(bytes)?,
+                DapVersion::Draft09 | DapVersion::Latest => decode_u32_bytes(bytes)?,
             },
         })
     }
@@ -1043,7 +1056,7 @@ impl ParameterizedDecode<DapVersion> for CollectionReq {
 pub struct Collection {
     pub part_batch_sel: PartialBatchSelector,
     pub report_count: u64,
-    pub draft_latest_interval: Option<Interval>, // Not set in draft02
+    pub draft09_interval: Option<Interval>, // Not set in draft02
     pub encrypted_agg_shares: [HpkeCiphertext; 2],
 }
 
@@ -1055,16 +1068,20 @@ impl ParameterizedEncode<DapVersion> for Collection {
     ) -> Result<(), CodecError> {
         self.part_batch_sel.encode(bytes)?;
         self.report_count.encode(bytes)?;
-        match (version, &self.draft_latest_interval) {
+        match (version, &self.draft09_interval) {
             (DapVersion::Draft02, None) => {
                 encode_u32_items(bytes, &(), &self.encrypted_agg_shares)?;
             }
-            (DapVersion::DraftLatest, Some(interval)) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Some(interval)) => {
                 interval.encode(bytes)?;
                 self.encrypted_agg_shares[0].encode(bytes)?;
                 self.encrypted_agg_shares[1].encode(bytes)?;
             }
-            _ => unreachable!("unhandled variant for version {version:?}"),
+            _ => {
+                return Err(CodecError::Other(
+                    format!("unhandled variant for version {version:?}").into(),
+                ))
+            }
         };
         Ok(())
     }
@@ -1077,14 +1094,14 @@ impl ParameterizedDecode<DapVersion> for Collection {
     ) -> Result<Self, CodecError> {
         let part_batch_sel = PartialBatchSelector::decode(bytes)?;
         let report_count = u64::decode(bytes)?;
-        let (draft_latest_interval, encrypted_agg_shares) = match version {
+        let (draft09_interval, encrypted_agg_shares) = match version {
             DapVersion::Draft02 => (
                 None,
                 decode_u32_items(&(), bytes)?
                     .try_into()
                     .map_err(|_| CodecError::UnexpectedValue)?,
             ),
-            DapVersion::DraftLatest => (
+            DapVersion::Draft09 | DapVersion::Latest => (
                 Some(Interval::decode(bytes)?),
                 [
                     HpkeCiphertext::decode(bytes)?,
@@ -1096,7 +1113,7 @@ impl ParameterizedDecode<DapVersion> for Collection {
         Ok(Self {
             part_batch_sel,
             report_count,
-            draft_latest_interval,
+            draft09_interval,
             encrypted_agg_shares,
         })
     }
@@ -1129,7 +1146,7 @@ impl ParameterizedEncode<DapVersion> for AggregateShareReq {
                 self.batch_sel.encode_with_param(version, bytes)?;
                 encode_u16_bytes(bytes, &self.agg_param)?;
             }
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 self.batch_sel.encode_with_param(version, bytes)?;
                 encode_u32_bytes(bytes, &self.agg_param)?;
             }
@@ -1151,7 +1168,7 @@ impl ParameterizedDecode<DapVersion> for AggregateShareReq {
                 BatchSelector::decode_with_param(version, bytes)?,
                 decode_u16_bytes(bytes)?,
             ),
-            DapVersion::DraftLatest => (
+            DapVersion::Draft09 | DapVersion::Latest => (
                 None,
                 BatchSelector::decode_with_param(version, bytes)?,
                 decode_u32_bytes(bytes)?,
@@ -1480,7 +1497,7 @@ mod test {
                 time: 1_637_364_244,
                 draft02_extensions: match version {
                     DapVersion::Draft02 => Some(Vec::new()),
-                    DapVersion::DraftLatest => None,
+                    DapVersion::Draft09 | DapVersion::Latest => None,
                 },
             },
             public_share: b"public share".to_vec(),
@@ -1553,7 +1570,7 @@ mod test {
                                 payload: b"ciphertext".to_vec(),
                             },
                         },
-                        draft_latest_payload: None,
+                        draft09_payload: None,
                     },
                     PrepareInit {
                         report_share: ReportShare {
@@ -1569,7 +1586,7 @@ mod test {
                                 payload: b"ciphertext".to_vec(),
                             },
                         },
-                        draft_latest_payload: None,
+                        draft09_payload: None,
                     },
                 ],
             },
@@ -1600,7 +1617,7 @@ mod test {
                             payload: b"ciphertext".to_vec(),
                         },
                     },
-                    draft_latest_payload: None,
+                    draft09_payload: None,
                 },
                 PrepareInit {
                     report_share: ReportShare {
@@ -1616,7 +1633,7 @@ mod test {
                             payload: b"ciphertext".to_vec(),
                         },
                     },
-                    draft_latest_payload: None,
+                    draft09_payload: None,
                 },
             ],
         };
@@ -1650,7 +1667,7 @@ mod test {
                             payload: b"ciphertext".to_vec(),
                         },
                     },
-                    draft_latest_payload: Some(b"prep share".to_vec()),
+                    draft09_payload: Some(b"prep share".to_vec()),
                 },
                 PrepareInit {
                     report_share: ReportShare {
@@ -1666,16 +1683,14 @@ mod test {
                             payload: b"ciphertext".to_vec(),
                         },
                     },
-                    draft_latest_payload: Some(b"prep share".to_vec()),
+                    draft09_payload: Some(b"prep share".to_vec()),
                 },
             ],
         };
 
         let got = AggregationJobInitReq::get_decoded_with_param(
-            &DapVersion::DraftLatest,
-            &want
-                .get_encoded_with_param(&DapVersion::DraftLatest)
-                .unwrap(),
+            &DapVersion::Draft09,
+            &want.get_encoded_with_param(&DapVersion::Draft09).unwrap(),
         )
         .unwrap();
         assert_eq!(got, want);
@@ -1739,10 +1754,8 @@ mod test {
         };
 
         let got = AggregationJobContinueReq::get_decoded_with_param(
-            &DapVersion::DraftLatest,
-            &want
-                .get_encoded_with_param(&DapVersion::DraftLatest)
-                .unwrap(),
+            &DapVersion::Draft09,
+            &want.get_encoded_with_param(&DapVersion::Draft09).unwrap(),
         )
         .unwrap();
         assert_eq!(got, want);
@@ -1814,10 +1827,8 @@ mod test {
             checksum: [0; 32],
         };
         let got = AggregateShareReq::get_decoded_with_param(
-            &DapVersion::DraftLatest,
-            &want
-                .get_encoded_with_param(&DapVersion::DraftLatest)
-                .unwrap(),
+            &DapVersion::Draft09,
+            &want.get_encoded_with_param(&DapVersion::Draft09).unwrap(),
         )
         .unwrap();
         assert_eq!(got, want);
diff --git a/daphne/src/messages/taskprov.rs b/daphne/src/messages/taskprov.rs
index d011041..ab7b8bc 100644
--- a/daphne/src/messages/taskprov.rs
+++ b/daphne/src/messages/taskprov.rs
@@ -92,7 +92,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for VdafTypeVar {
                     num_proofs: u8::decode(bytes)?,
                 })
             }
-            (DapVersion::DraftLatest, Some(bytes_left), ..) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Some(bytes_left), ..) => {
                 let mut param = vec![0; bytes_left - 4];
                 bytes.read_exact(&mut param)?;
                 Ok(Self::NotImplemented {
@@ -102,7 +102,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for VdafTypeVar {
             }
             // draft02 compatibility: We don't recognize the VDAF type, which means the rest of
             // this message is not decodable. We must abort.
-            (DapVersion::Draft02, ..) | (DapVersion::DraftLatest, None, ..) => {
+            (DapVersion::Draft02, ..) | (DapVersion::Draft09 | DapVersion::Latest, None, ..) => {
                 Err(CodecError::UnexpectedValue)
             }
         }
@@ -140,7 +140,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for DpConfig {
         let dp_mechanism = u8::decode(bytes)?;
         match (version, bytes_left, dp_mechanism) {
             (.., DP_MECHANISM_NONE) => Ok(Self::None),
-            (DapVersion::DraftLatest, Some(bytes_left), ..) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Some(bytes_left), ..) => {
                 let mut param = vec![0; bytes_left - 1];
                 bytes.read_exact(&mut param)?;
                 Ok(Self::NotImplemented {
@@ -150,7 +150,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for DpConfig {
             }
             // draft02 compatibility: We must abort because unimplemented DP mechansims can't be
             // decoded.
-            (DapVersion::Draft02, ..) | (DapVersion::DraftLatest, None, ..) => {
+            (DapVersion::Draft02, ..) | (DapVersion::Draft09 | DapVersion::Latest, None, ..) => {
                 Err(CodecError::UnexpectedValue)
             }
         }
@@ -268,7 +268,7 @@ impl ParameterizedEncode<DapVersion> for QueryConfig {
         self.time_precision.encode(bytes)?;
         self.max_batch_query_count.encode(bytes)?;
         self.min_batch_size.encode(bytes)?;
-        if *version == DapVersion::DraftLatest {
+        if matches!(version, DapVersion::Draft09 | DapVersion::Latest) {
             self.encode_query_type(bytes)?;
         }
         match &self.var {
@@ -290,7 +290,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for QueryConfig {
         bytes: &mut Cursor<&[u8]>,
     ) -> Result<Self, CodecError> {
         let query_type = match version {
-            DapVersion::DraftLatest => None,
+            DapVersion::Draft09 | DapVersion::Latest => None,
             DapVersion::Draft02 => Some(Ok(u8::decode(bytes)?)),
         };
         let time_precision = Duration::decode(bytes)?;
@@ -302,7 +302,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for QueryConfig {
             (.., QUERY_TYPE_FIXED_SIZE) => QueryConfigVar::FixedSize {
                 max_batch_size: u32::decode(bytes)?,
             },
-            (DapVersion::DraftLatest, Some(bytes_left), ..) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Some(bytes_left), ..) => {
                 let mut param = vec![0; bytes_left - 15];
                 bytes.read_exact(&mut param)?;
 
@@ -313,7 +313,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for QueryConfig {
             }
             // draft02 compatibility: We must abort because unimplemented query configurations
             // can't be decoded.
-            (DapVersion::Draft02, ..) | (DapVersion::DraftLatest, None, ..) => {
+            (DapVersion::Draft02, ..) | (DapVersion::Draft09 | DapVersion::Latest, None, ..) => {
                 return Err(CodecError::UnexpectedValue)
             }
         };
@@ -351,7 +351,7 @@ impl ParameterizedEncode<DapVersion> for TaskConfig {
                 &(),
                 &[self.leader_url.clone(), self.helper_url.clone()],
             )?,
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 self.leader_url.encode(bytes)?;
                 self.helper_url.encode(bytes)?;
             }
@@ -377,7 +377,9 @@ impl ParameterizedDecode<DapVersion> for TaskConfig {
             DapVersion::Draft02 => decode_u16_items(&(), bytes)?
                 .try_into()
                 .map_err(|_| CodecError::UnexpectedValue)?, // Expect exactly two Aggregator endpoints.
-            DapVersion::DraftLatest => [UrlBytes::decode(bytes)?, UrlBytes::decode(bytes)?],
+            DapVersion::Draft09 | DapVersion::Latest => {
+                [UrlBytes::decode(bytes)?, UrlBytes::decode(bytes)?]
+            }
         };
         let query_config = taskprov_decode_u16_prefixed(*version, bytes, |version, inner, len| {
             // We need to know the length of the `QueryConfig` in order to decode variants we don't
@@ -414,7 +416,7 @@ fn taskprov_encode_u16_prefixed(
     e: impl Fn(DapVersion, &mut Vec<u8>) -> Result<(), CodecError>,
 ) -> Result<(), CodecError> {
     match version {
-        DapVersion::DraftLatest => encode_u16_prefixed(version, bytes, e),
+        DapVersion::Draft09 | DapVersion::Latest => encode_u16_prefixed(version, bytes, e),
         // draft02 compatibility: No length prefix is used.
         DapVersion::Draft02 => e(version, bytes),
     }
@@ -426,7 +428,7 @@ fn taskprov_decode_u16_prefixed<O>(
     d: impl Fn(DapVersion, &mut Cursor<&[u8]>, Option<usize>) -> Result<O, CodecError>,
 ) -> Result<O, CodecError> {
     match version {
-        DapVersion::DraftLatest => decode_u16_prefixed(version, bytes, d),
+        DapVersion::Draft09 | DapVersion::Latest => decode_u16_prefixed(version, bytes, d),
         // draft02 compatibility: No length prefix is used.
         DapVersion::Draft02 => d(version, bytes, None),
     }
@@ -519,12 +521,12 @@ mod tests {
             },
         };
         let encoded = query_config
-            .get_encoded_with_param(&DapVersion::DraftLatest)
+            .get_encoded_with_param(&DapVersion::Draft09)
             .unwrap();
 
         assert_eq!(
             QueryConfig::get_decoded_with_param(
-                &(DapVersion::DraftLatest, Some(encoded.len())),
+                &(DapVersion::Draft09, Some(encoded.len())),
                 &encoded,
             )
             .unwrap(),
@@ -573,12 +575,12 @@ mod tests {
             param: b"dp mechanism param".to_vec(),
         };
         let encoded = dp_config
-            .get_encoded_with_param(&DapVersion::DraftLatest)
+            .get_encoded_with_param(&DapVersion::Draft09)
             .unwrap();
 
         assert_eq!(
             DpConfig::get_decoded_with_param(
-                &(DapVersion::DraftLatest, Some(encoded.len())),
+                &(DapVersion::Draft09, Some(encoded.len())),
                 &encoded,
             )
             .unwrap(),
@@ -643,7 +645,7 @@ mod tests {
     test_versions! { roundtrip_vdaf_config_prio3_sum_vec_field64_multiproof_hmac_sha256_aes128 }
 
     #[test]
-    fn roundtrip_vdaf_config_not_implemented_draftlatest() {
+    fn roundtrip_vdaf_config_not_implemented_draft09() {
         let vdaf_config = VdafConfig {
             dp_config: DpConfig::None,
             var: VdafTypeVar::NotImplemented {
@@ -652,12 +654,12 @@ mod tests {
             },
         };
         let encoded = vdaf_config
-            .get_encoded_with_param(&DapVersion::DraftLatest)
+            .get_encoded_with_param(&DapVersion::Draft09)
             .unwrap();
 
         assert_eq!(
             VdafConfig::get_decoded_with_param(
-                &(DapVersion::DraftLatest, Some(encoded.len())),
+                &(DapVersion::Draft09, Some(encoded.len())),
                 &encoded
             )
             .unwrap(),
diff --git a/daphne/src/protocol/aggregator.rs b/daphne/src/protocol/aggregator.rs
index fe1d8ad..54697d0 100644
--- a/daphne/src/protocol/aggregator.rs
+++ b/daphne/src/protocol/aggregator.rs
@@ -35,9 +35,8 @@ use std::{
 };
 
 use super::{
-    CTX_AGG_SHARE_DRAFT02, CTX_AGG_SHARE_DRAFT_LATEST, CTX_INPUT_SHARE_DRAFT02,
-    CTX_INPUT_SHARE_DRAFT_LATEST, CTX_ROLE_CLIENT, CTX_ROLE_COLLECTOR, CTX_ROLE_HELPER,
-    CTX_ROLE_LEADER,
+    CTX_AGG_SHARE_DRAFT02, CTX_AGG_SHARE_DRAFT09, CTX_INPUT_SHARE_DRAFT02, CTX_INPUT_SHARE_DRAFT09,
+    CTX_ROLE_CLIENT, CTX_ROLE_COLLECTOR, CTX_ROLE_HELPER, CTX_ROLE_LEADER,
 };
 
 // Ping-pong message framing as defined in draft-irtf-cfrg-vdaf-08, Section 5.8. We do not
@@ -126,7 +125,7 @@ impl EarlyReportStateConsumed {
 
         let input_share_text = match task_config.version {
             DapVersion::Draft02 => CTX_INPUT_SHARE_DRAFT02,
-            DapVersion::DraftLatest => CTX_INPUT_SHARE_DRAFT_LATEST,
+            DapVersion::Draft09 | DapVersion::Latest => CTX_INPUT_SHARE_DRAFT09,
         };
         let n: usize = input_share_text.len();
         let mut info = Vec::with_capacity(n + 2);
@@ -162,9 +161,9 @@ impl EarlyReportStateConsumed {
 
         // draft02 compatibility: The plaintext is passed to the VDAF directly. In the latest
         // draft, the plaintext also encodes the report extensions.
-        let (input_share, draft_latest_extensions) = match task_config.version {
+        let (input_share, draft09_extensions) = match task_config.version {
             DapVersion::Draft02 => (encoded_input_share, None),
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 match PlaintextInputShare::get_decoded_with_param(
                     &task_config.version,
                     &encoded_input_share,
@@ -183,7 +182,7 @@ impl EarlyReportStateConsumed {
         // Handle report extensions.
         {
             let extensions = match task_config.version {
-                DapVersion::DraftLatest => draft_latest_extensions.as_ref().unwrap(),
+                DapVersion::Draft09 | DapVersion::Latest => draft09_extensions.as_ref().unwrap(),
                 DapVersion::Draft02 => state.metadata.draft02_extensions.as_ref().unwrap(),
             };
 
@@ -204,7 +203,7 @@ impl EarlyReportStateConsumed {
                     }
 
                     // Reject reports with unrecognized extensions.
-                    (DapVersion::DraftLatest, ..) => {
+                    (DapVersion::Draft09 | DapVersion::Latest, ..) => {
                         return Ok(Self::Rejected {
                             metadata: state.metadata,
                             failure: TransitionFailure::InvalidMessage,
@@ -460,9 +459,9 @@ impl DapTaskConfig {
                 } => {
                     // draft02 compatibility: In the latest version, the Leader sends the Helper
                     // its initial prep share in the first request.
-                    let (draft02_prep_share, draft_latest_payload) = match self.version {
+                    let (draft02_prep_share, draft09_payload) = match self.version {
                         DapVersion::Draft02 => (Some(prep_share), None),
-                        DapVersion::DraftLatest => {
+                        DapVersion::Draft09 | DapVersion::Latest => {
                             let mut outbound = Vec::with_capacity(
                                 prep_share
                                     .encoded_len_with_param(&self.version)
@@ -490,7 +489,7 @@ impl DapTaskConfig {
                             public_share: state.public_share,
                             encrypted_input_share: helper_share,
                         },
-                        draft_latest_payload,
+                        draft09_payload,
                     });
                 }
 
@@ -555,7 +554,7 @@ impl DapTaskConfig {
                     ReportState {
                         metadata: prep_init.report_share.report_metadata,
                         public_share: prep_init.report_share.public_share,
-                        draft_latest_prep_init_payload: prep_init.draft_latest_payload,
+                        draft_latest_prep_init_payload: prep_init.draft09_payload,
                     },
                     prep_init.report_share.encrypted_input_share,
                 )
@@ -599,7 +598,7 @@ impl DapTaskConfig {
                 initialized_reports,
                 metrics,
             )),
-            DapVersion::DraftLatest => self.draft_latest_handle_agg_job_init_req(
+            DapVersion::Draft09 | DapVersion::Latest => self.draft09_handle_agg_job_init_req(
                 task_id,
                 report_status,
                 part_batch_sel,
@@ -665,7 +664,7 @@ impl DapTaskConfig {
         )
     }
 
-    fn draft_latest_handle_agg_job_init_req(
+    fn draft09_handle_agg_job_init_req(
         &self,
         task_id: &TaskId,
         report_status: &HashMap<ReportId, ReportProcessedStatus>,
@@ -789,8 +788,8 @@ impl DapTaskConfig {
             DapVersion::Draft02 => self
                 .draft02_handle_agg_job_resp(task_id, agg_job_id, state, agg_job_resp, metrics)
                 .map_err(Into::into),
-            DapVersion::DraftLatest => {
-                self.draft_latest_handle_agg_job_resp(task_id, state, agg_job_resp, metrics)
+            DapVersion::Draft09 | DapVersion::Latest => {
+                self.draft09_handle_agg_job_resp(task_id, state, agg_job_resp, metrics)
             }
         }
     }
@@ -916,7 +915,7 @@ impl DapTaskConfig {
         ))
     }
 
-    fn draft_latest_handle_agg_job_resp(
+    fn draft09_handle_agg_job_resp(
         &self,
         task_id: &TaskId,
         state: DapAggregationJobState,
@@ -1299,7 +1298,7 @@ fn produce_encrypted_agg_share(
 
     let agg_share_text = match version {
         DapVersion::Draft02 => CTX_AGG_SHARE_DRAFT02,
-        DapVersion::DraftLatest => CTX_AGG_SHARE_DRAFT_LATEST,
+        DapVersion::Draft09 | DapVersion::Latest => CTX_AGG_SHARE_DRAFT09,
     };
     let n: usize = agg_share_text.len();
     let mut info = Vec::with_capacity(n + 2);
diff --git a/daphne/src/protocol/client.rs b/daphne/src/protocol/client.rs
index 7c14196..a3355cf 100644
--- a/daphne/src/protocol/client.rs
+++ b/daphne/src/protocol/client.rs
@@ -17,7 +17,7 @@ use prio::codec::{Encode, ParameterizedEncode};
 use rand::prelude::*;
 
 use super::{
-    CTX_INPUT_SHARE_DRAFT02, CTX_INPUT_SHARE_DRAFT_LATEST, CTX_ROLE_CLIENT, CTX_ROLE_HELPER,
+    CTX_INPUT_SHARE_DRAFT02, CTX_INPUT_SHARE_DRAFT09, CTX_ROLE_CLIENT, CTX_ROLE_HELPER,
     CTX_ROLE_LEADER,
 };
 
@@ -84,8 +84,8 @@ impl VdafConfig {
             return Err(fatal_error!(err = "unexpected number of HPKE configs"));
         }
 
-        let (draft02_extensions, mut draft_latest_plaintext_input_share) = match version {
-            DapVersion::DraftLatest => (
+        let (draft02_extensions, mut draft09_plaintext_input_share) = match version {
+            DapVersion::Draft09 | DapVersion::Latest => (
                 None,
                 Some(PlaintextInputShare {
                     extensions,
@@ -102,7 +102,7 @@ impl VdafConfig {
         };
 
         let encoded_input_shares = input_shares.into_iter().map(|input_share| {
-            if let Some(ref mut plaintext_input_share) = draft_latest_plaintext_input_share {
+            if let Some(ref mut plaintext_input_share) = draft09_plaintext_input_share {
                 plaintext_input_share.payload = input_share;
                 plaintext_input_share.get_encoded_with_param(&version)
             } else {
@@ -112,7 +112,7 @@ impl VdafConfig {
 
         let input_share_text = match version {
             DapVersion::Draft02 => CTX_INPUT_SHARE_DRAFT02,
-            DapVersion::DraftLatest => CTX_INPUT_SHARE_DRAFT_LATEST,
+            DapVersion::Draft09 | DapVersion::Latest => CTX_INPUT_SHARE_DRAFT09,
         };
         let n: usize = input_share_text.len();
         let mut info = Vec::with_capacity(n + 2);
diff --git a/daphne/src/protocol/collector.rs b/daphne/src/protocol/collector.rs
index c85fc44..b2ada99 100644
--- a/daphne/src/protocol/collector.rs
+++ b/daphne/src/protocol/collector.rs
@@ -13,7 +13,7 @@ use crate::{
 use prio::codec::Encode;
 
 use super::{
-    CTX_AGG_SHARE_DRAFT02, CTX_AGG_SHARE_DRAFT_LATEST, CTX_ROLE_COLLECTOR, CTX_ROLE_HELPER,
+    CTX_AGG_SHARE_DRAFT02, CTX_AGG_SHARE_DRAFT09, CTX_ROLE_COLLECTOR, CTX_ROLE_HELPER,
     CTX_ROLE_LEADER,
 };
 
@@ -52,7 +52,7 @@ impl VdafConfig {
 
         let agg_share_text = match version {
             DapVersion::Draft02 => CTX_AGG_SHARE_DRAFT02,
-            DapVersion::DraftLatest => CTX_AGG_SHARE_DRAFT_LATEST,
+            DapVersion::Draft09 | DapVersion::Latest => CTX_AGG_SHARE_DRAFT09,
         };
         let n: usize = agg_share_text.len();
         let mut info = Vec::with_capacity(n + 2);
diff --git a/daphne/src/protocol/mod.rs b/daphne/src/protocol/mod.rs
index 8d57c4d..5b8d10f 100644
--- a/daphne/src/protocol/mod.rs
+++ b/daphne/src/protocol/mod.rs
@@ -6,9 +6,9 @@ mod client;
 mod collector;
 
 const CTX_INPUT_SHARE_DRAFT02: &[u8] = b"dap-02 input share";
-const CTX_INPUT_SHARE_DRAFT_LATEST: &[u8] = b"dap-09 input share";
+const CTX_INPUT_SHARE_DRAFT09: &[u8] = b"dap-09 input share";
 const CTX_AGG_SHARE_DRAFT02: &[u8] = b"dap-02 aggregate share";
-const CTX_AGG_SHARE_DRAFT_LATEST: &[u8] = b"dap-09 aggregate share";
+const CTX_AGG_SHARE_DRAFT09: &[u8] = b"dap-09 aggregate share";
 const CTX_ROLE_COLLECTOR: u8 = 0;
 const CTX_ROLE_CLIENT: u8 = 1;
 const CTX_ROLE_LEADER: u8 = 2;
@@ -418,7 +418,7 @@ mod test {
                         public_share: report0.public_share,
                         encrypted_input_share: report0.encrypted_input_shares[1].clone(),
                     },
-                    draft_latest_payload: Some(b"malformed payload".to_vec()),
+                    draft09_payload: Some(b"malformed payload".to_vec()),
                 },
                 PrepareInit {
                     report_share: ReportShare {
@@ -426,7 +426,7 @@ mod test {
                         public_share: report1.public_share,
                         encrypted_input_share: report1.encrypted_input_shares[1].clone(),
                     },
-                    draft_latest_payload: Some(b"malformed payload".to_vec()),
+                    draft09_payload: Some(b"malformed payload".to_vec()),
                 },
             ],
         };
@@ -658,18 +658,12 @@ mod test {
 
     #[tokio::test]
     async fn agg_job_init_req_skip_vdaf_prep_error_draft09() {
-        let t = AggregationJobTest::new(
-            TEST_VDAF,
-            HpkeKemId::X25519HkdfSha256,
-            DapVersion::DraftLatest,
-        );
+        let t =
+            AggregationJobTest::new(TEST_VDAF, HpkeKemId::X25519HkdfSha256, DapVersion::Draft09);
         let mut reports = t.produce_reports(vec![DapMeasurement::U64(1), DapMeasurement::U64(1)]);
         reports.insert(
             1,
-            t.produce_invalid_report_vdaf_prep_failure(
-                DapMeasurement::U64(1),
-                DapVersion::DraftLatest,
-            ),
+            t.produce_invalid_report_vdaf_prep_failure(DapMeasurement::U64(1), DapVersion::Draft09),
         );
 
         let (leader_state, agg_job_init_req) = t
@@ -908,7 +902,7 @@ mod test {
             DapVersion::Draft02 => true,
             // In the latest version we're meant to reject reports containing unrecognized
             // extensions.
-            DapVersion::DraftLatest => false,
+            DapVersion::Draft09 | DapVersion::Latest => false,
         };
         assert_eq!(consumed_report.is_ready(), expect_ready);
     }
diff --git a/daphne/src/roles/aggregator.rs b/daphne/src/roles/aggregator.rs
index 7cbe78a..ea8c66e 100644
--- a/daphne/src/roles/aggregator.rs
+++ b/daphne/src/roles/aggregator.rs
@@ -196,7 +196,7 @@ where
             .as_ref()
             .get_encoded()
             .map_err(DapError::encoding)?,
-        DapVersion::DraftLatest => {
+        DapVersion::Draft09 | DapVersion::Latest => {
             let hpke_config_list = HpkeConfigList {
                 hpke_configs: vec![hpke_config.as_ref().clone()],
             };
diff --git a/daphne/src/roles/helper.rs b/daphne/src/roles/helper.rs
index bf51d22..7b78773 100644
--- a/daphne/src/roles/helper.rs
+++ b/daphne/src/roles/helper.rs
@@ -170,7 +170,7 @@ pub async fn handle_agg_job_init_req<'req, S: Sync, A: DapHelper<S>>(
             agg_job_resp
         }
 
-        DapVersion::DraftLatest => {
+        DapVersion::Draft09 | DapVersion::Latest => {
             let agg_job_resp = finish_agg_job_and_aggregate(
                 aggregator,
                 task_id,
@@ -462,10 +462,12 @@ fn resolve_agg_job_id<'id, S>(
         (DapVersion::Draft02, DapResource::Undefined, Some(agg_job_id)) => {
             Ok(MetaAggregationJobId::Draft02(*agg_job_id))
         }
-        (DapVersion::DraftLatest, DapResource::AggregationJob(agg_job_id), None) => {
-            Ok(MetaAggregationJobId::DraftLatest(*agg_job_id))
-        }
-        (DapVersion::DraftLatest, DapResource::Undefined, None) => {
+        (
+            DapVersion::Draft09 | DapVersion::Latest,
+            DapResource::AggregationJob(agg_job_id),
+            None,
+        ) => Ok(MetaAggregationJobId::Draft09(*agg_job_id)),
+        (DapVersion::Draft09 | DapVersion::Latest, DapResource::Undefined, None) => {
             Err(DapAbort::BadRequest("undefined resource".into()))
         }
         _ => unreachable!("unhandled resource {:?}", req.resource),
diff --git a/daphne/src/roles/leader/mod.rs b/daphne/src/roles/leader/mod.rs
index 3ff08b0..1fc93bb 100644
--- a/daphne/src/roles/leader/mod.rs
+++ b/daphne/src/roles/leader/mod.rs
@@ -300,9 +300,10 @@ pub async fn handle_coll_job_req<S: Sync, A: DapLeader<S>>(
     // from the request path.
     let collect_job_id = match (req.version, &req.resource) {
         (DapVersion::Draft02, DapResource::Undefined) => None,
-        (DapVersion::DraftLatest, DapResource::CollectionJob(ref collect_job_id)) => {
-            Some(*collect_job_id)
-        }
+        (
+            DapVersion::Draft09 | DapVersion::Latest,
+            DapResource::CollectionJob(ref collect_job_id),
+        ) => Some(*collect_job_id),
         (_, DapResource::Undefined) => {
             return Err(DapAbort::BadRequest("missing collection ID".into()).into())
         }
@@ -562,9 +563,9 @@ async fn run_coll_job<S: Sync, A: DapLeader<S>>(
         .map_err(|e| DapAbort::from_codec_error(e, *task_id))?;
     // In the latest draft, the Collection message includes the smallest quantized time
     // interval containing all reports in the batch.
-    let draft_latest_interval = match task_config.version {
+    let draft09_interval = match task_config.version {
         DapVersion::Draft02 => None,
-        DapVersion::DraftLatest => {
+        DapVersion::Draft09 | DapVersion::Latest => {
             let low = task_config.quantized_time_lower_bound(leader_agg_share.min_time);
             let high = task_config.quantized_time_upper_bound(leader_agg_share.max_time);
             Some(Interval {
@@ -583,7 +584,7 @@ async fn run_coll_job<S: Sync, A: DapLeader<S>>(
     let collection = Collection {
         part_batch_sel: batch_sel.clone().into(),
         report_count: leader_agg_share.report_count,
-        draft_latest_interval,
+        draft09_interval,
         encrypted_agg_shares: [leader_enc_agg_share, agg_share_resp.encrypted_agg_share],
     };
     aggregator
diff --git a/daphne/src/roles/mod.rs b/daphne/src/roles/mod.rs
index 5f91a25..7788ea4 100644
--- a/daphne/src/roles/mod.rs
+++ b/daphne/src/roles/mod.rs
@@ -153,7 +153,7 @@ async fn resolve_taskprov<S: Sync>(
 mod test {
     use super::{aggregator, helper, leader, DapAggregator, DapAuthorizedSender, DapLeader};
     use crate::{
-        assert_metrics_include, async_test_versions,
+        assert_metrics_include, async_test_version, async_test_versions,
         auth::BearerToken,
         constants::DapMediaType,
         hpke::{HpkeDecrypter, HpkeKemId, HpkeReceiverConfig},
@@ -187,7 +187,7 @@ mod test {
     fn empty_report_extensions_for_version(version: DapVersion) -> Option<Vec<Extension>> {
         match version {
             DapVersion::Draft02 => Some(Vec::new()),
-            DapVersion::DraftLatest => None,
+            DapVersion::Draft09 | DapVersion::Latest => None,
         }
     }
 
@@ -1458,7 +1458,7 @@ mod test {
         let collection = Collection {
             part_batch_sel: PartialBatchSelector::TimeInterval,
             report_count: 0,
-            draft_latest_interval: if version == DapVersion::Draft02 {
+            draft09_interval: if version == DapVersion::Draft02 {
                 None
             } else {
                 Some(Interval {
@@ -1828,7 +1828,7 @@ mod test {
 
         let agg_job_req_count = match version {
             DapVersion::Draft02 => 2,
-            DapVersion::DraftLatest => 1,
+            DapVersion::Draft09 | DapVersion::Latest => 1,
         };
 
         assert_metrics_include!(t.helper_registry, {
@@ -1865,7 +1865,7 @@ mod test {
 
                 Query::FixedSizeByBatchId { batch_id }
             }
-            DapVersion::DraftLatest => Query::FixedSizeCurrentBatch,
+            DapVersion::Draft09 | DapVersion::Latest => Query::FixedSizeCurrentBatch,
         };
         leader::handle_coll_job_req(&*t.leader, &t.gen_test_coll_job_req(query, task_id).await)
             .await
@@ -1877,7 +1877,7 @@ mod test {
 
         let agg_job_req_count = match version {
             DapVersion::Draft02 => 2,
-            DapVersion::DraftLatest => 1,
+            DapVersion::Draft09 | DapVersion::Latest => 1,
         };
 
         assert_metrics_include!(t.helper_registry, {
@@ -1946,7 +1946,7 @@ mod test {
                     test_measurement.clone(),
                     vec![Extension::Taskprov {
                         draft02_payload: match version {
-                            DapVersion::DraftLatest => None,
+                            DapVersion::Draft09 | DapVersion::Latest => None,
                             DapVersion::Draft02 => taskprov_report_extension_payload.clone(),
                         },
                     }],
@@ -1971,7 +1971,7 @@ mod test {
             DapVersion::Draft02 => Query::FixedSizeByBatchId {
                 batch_id: t.leader.current_batch(&task_id).await.unwrap(),
             },
-            DapVersion::DraftLatest => Query::FixedSizeCurrentBatch,
+            DapVersion::Draft09 | DapVersion::Latest => Query::FixedSizeCurrentBatch,
         };
         leader::handle_coll_job_req(&*t.leader, &t.gen_test_coll_job_req(query, &task_id).await)
             .await
@@ -1983,7 +1983,7 @@ mod test {
 
         let agg_job_req_count = match version {
             DapVersion::Draft02 => 2,
-            DapVersion::DraftLatest => 1,
+            DapVersion::Draft09 | DapVersion::Latest => 1,
         };
 
         assert_metrics_include!(t.helper_registry, {
@@ -2011,10 +2011,9 @@ mod test {
 
     async_test_versions! { e2e_taskprov_prio2 }
 
-    #[tokio::test]
-    async fn e2e_taskprov_prio3_sum_vec_field64_multiproof_hmac_sha256_aes128_draft09() {
+    async fn e2e_taskprov_prio3_sum_vec_field64_multiproof_hmac_sha256_aes128(version: DapVersion) {
         e2e_taskprov(
-            DapVersion::DraftLatest,
+            version,
             VdafConfig::Prio3(Prio3Config::SumVecField64MultiproofHmacSha256Aes128 {
                 bits: 1,
                 length: 10,
@@ -2026,6 +2025,9 @@ mod test {
         .await;
     }
 
+    async_test_version! { e2e_taskprov_prio3_sum_vec_field64_multiproof_hmac_sha256_aes128, Draft09 }
+    async_test_version! { e2e_taskprov_prio3_sum_vec_field64_multiproof_hmac_sha256_aes128, Latest }
+
     fn early_metadata_checks(version: DapVersion) {
         let t = Test::new(version);
         let mut rng = thread_rng();
@@ -2141,7 +2143,7 @@ mod test {
 
                     Query::FixedSizeByBatchId { batch_id }
                 }
-                DapVersion::DraftLatest => Query::FixedSizeCurrentBatch,
+                DapVersion::Draft09 | DapVersion::Latest => Query::FixedSizeCurrentBatch,
             };
             leader::handle_coll_job_req(&*t.leader, &t.gen_test_coll_job_req(query, task_id).await)
                 .await
@@ -2154,7 +2156,7 @@ mod test {
 
         let agg_job_req_count = match version {
             DapVersion::Draft02 => 2,
-            DapVersion::DraftLatest => 1,
+            DapVersion::Draft09 | DapVersion::Latest => 1,
         } * 2;
 
         assert_metrics_include!(t.helper_registry, {
@@ -2175,9 +2177,11 @@ mod test {
 
     // TODO(cjpatton) Test collecting the batch multiple times per the "heavy hitters" mode of
     // operation for Mastic.
+    //
+    // TODO(cjpatton) Create a test for "attribute based metrics" for draft09.
     #[tokio::test]
     async fn heavy_hitters() {
-        let t = Test::new(DapVersion::DraftLatest);
+        let t = Test::new(DapVersion::Latest);
         let task_id = &t.heavy_hitters_task_id;
         let task_config = t
             .leader
diff --git a/daphne/src/taskprov.rs b/daphne/src/taskprov.rs
index d9ff0e9..9ec8572 100644
--- a/daphne/src/taskprov.rs
+++ b/daphne/src/taskprov.rs
@@ -147,7 +147,7 @@ fn get_taskprov_task_config<S>(
             )
         })?)
     } else if let Some(metadata) = report_metadata_advertisement {
-        if req.version == DapVersion::DraftLatest {
+        if matches!(req.version, DapVersion::Draft09 | DapVersion::Latest) {
             return Ok(None);
         }
         let taskprovs: Vec<&Extension> = metadata
@@ -234,7 +234,7 @@ impl VdafConfig {
                 })?,
             }),
             (
-                DapVersion::DraftLatest,
+                DapVersion::Draft09 | DapVersion::Latest,
                 VdafTypeVar::Prio3SumVecField64MultiproofHmacSha256Aes128 {
                     bits,
                     length,
@@ -684,7 +684,7 @@ mod test {
             (DapVersion::Draft02, Err(DapAbort::InvalidMessage { detail, .. })) => {
                 assert_eq!(detail, "codec error: unexpected value");
             }
-            (DapVersion::DraftLatest, Err(DapAbort::InvalidTask { detail, .. })) => {
+            (DapVersion::Draft09, Err(DapAbort::InvalidTask { detail, .. })) => {
                 assert_eq!(detail, "unimplemented VDAF type (1337)");
             }
             (_, r) => panic!("unexpected result: {r:?} ({version})"),
diff --git a/daphne/src/vdaf/mastic.rs b/daphne/src/vdaf/mastic.rs
index b88db1f..df7a1d5 100644
--- a/daphne/src/vdaf/mastic.rs
+++ b/daphne/src/vdaf/mastic.rs
@@ -243,19 +243,18 @@ mod test {
 
     use super::*;
     use crate::{
-        hpke::HpkeKemId, testing::AggregationJobTest, vdaf::VdafConfig, DapAggregateResult,
-        DapMeasurement, DapVersion,
+        async_test_version, hpke::HpkeKemId, testing::AggregationJobTest, vdaf::VdafConfig,
+        DapAggregateResult, DapMeasurement, DapVersion,
     };
 
-    #[tokio::test]
-    async fn roundtrip_count() {
+    async fn roundtrip_count(version: DapVersion) {
         let mut t = AggregationJobTest::new(
             &VdafConfig::Mastic {
                 input_size: 4,
                 weight_config: MasticWeightConfig::Count,
             },
             HpkeKemId::X25519HkdfSha256,
-            DapVersion::DraftLatest,
+            version,
         );
         let got = t
             .roundtrip(
@@ -293,4 +292,7 @@ mod test {
 
         assert_eq!(got, DapAggregateResult::U64Vec(vec![1, 2]));
     }
+
+    async_test_version! { roundtrip_count, Draft09 }
+    async_test_version! { roundtrip_count, Latest }
 }
diff --git a/daphne_server/Cargo.toml b/daphne_server/Cargo.toml
index e560f61..371f7ef 100644
--- a/daphne_server/Cargo.toml
+++ b/daphne_server/Cargo.toml
@@ -41,6 +41,7 @@ clap = { version = "4.4.18", features = ["derive"] }
 config = "0.13.4"
 daphne = { path = "../daphne", features = ["test-utils"] }
 daphne_service_utils = { path = "../daphne_service_utils", features = ["prometheus"] }
+paste.workspace = true
 prometheus.workspace = true
 rand.workspace = true
 tower = "0.4.13"
diff --git a/daphne_server/src/lib.rs b/daphne_server/src/lib.rs
index 38dff1a..6fc9f70 100644
--- a/daphne_server/src/lib.rs
+++ b/daphne_server/src/lib.rs
@@ -55,7 +55,7 @@ mod storage_proxy_connection;
 ///     report_shard_count: 4,
 ///     base_url: None,
 ///     taskprov: None,
-///     default_version: DapVersion::DraftLatest,
+///     default_version: DapVersion::Draft09,
 ///     report_storage_epoch_duration: 300,
 ///     report_storage_max_future_time_skew: 300,
 /// };
diff --git a/daphne_server/src/router/helper.rs b/daphne_server/src/router/helper.rs
index 81479d7..e53e901 100644
--- a/daphne_server/src/router/helper.rs
+++ b/daphne_server/src/router/helper.rs
@@ -56,7 +56,7 @@ where
                 app.server_metrics(),
                 match req.version {
                     daphne::DapVersion::Draft02 => StatusCode::OK,
-                    daphne::DapVersion::DraftLatest => StatusCode::CREATED,
+                    daphne::DapVersion::Draft09 | daphne::DapVersion::Latest => StatusCode::CREATED,
                 },
             )
         }
diff --git a/daphne_server/src/router/leader.rs b/daphne_server/src/router/leader.rs
index dec24df..b86876a 100644
--- a/daphne_server/src/router/leader.rs
+++ b/daphne_server/src/router/leader.rs
@@ -110,7 +110,7 @@ where
             AppendHeaders([(header::LOCATION, uri.as_str())]),
         )
             .into_response(),
-        (Ok(_), DapVersion::DraftLatest) => StatusCode::CREATED.into_response(),
+        (Ok(_), DapVersion::Draft09 | DapVersion::Latest) => StatusCode::CREATED.into_response(),
         (Err(e), _) => AxumDapResponse::new_error(e, app.server_metrics()).into_response(),
     }
 }
diff --git a/daphne_server/src/router/mod.rs b/daphne_server/src/router/mod.rs
index 48f2251..f306cdf 100644
--- a/daphne_server/src/router/mod.rs
+++ b/daphne_server/src/router/mod.rs
@@ -299,7 +299,7 @@ where
                     collect_job_id.map_or(DapResource::Undefined, DapResource::CollectionJob);
                 (task_id, resource)
             }
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 let resource = match media_type {
                     Some(
                         DapMediaType::AggregationJobInitReq
@@ -354,12 +354,12 @@ mod test {
         Router,
     };
     use daphne::{
+        async_test_version, async_test_versions,
         messages::{AggregationJobId, Base64Encode, TaskId},
         DapRequest, DapResource, DapVersion,
     };
     use daphne_service_utils::auth::DaphneAuth;
     use futures::future::BoxFuture;
-    use prio::codec::Encode;
     use rand::{thread_rng, Rng};
     use tokio::sync::mpsc::{self, Sender};
     use tower::ServiceExt;
@@ -426,45 +426,33 @@ mod test {
         }
     }
 
-    #[tokio::test]
-    async fn parse_latest_version() {
+    async fn parse_version(version: DapVersion) {
         let test = test_router();
 
         let req = test(
             Request::builder()
-                .uri("/v09/parse-version")
+                .uri(format!("/{version}/parse-version"))
                 .body(Body::empty())
                 .unwrap(),
         )
         .await;
 
-        assert_eq!(req.version, DapVersion::DraftLatest);
+        assert_eq!(req.version, version);
     }
 
-    #[tokio::test]
-    async fn parse_draft02_version() {
-        let test = test_router();
-
-        let req = test(
-            Request::builder()
-                .uri("/v02/parse-version")
-                .body("1".repeat(32))
-                .unwrap(),
-        )
-        .await;
+    async_test_versions! { parse_version }
 
-        assert_eq!(req.version, DapVersion::Draft02);
-    }
-
-    #[tokio::test]
-    async fn parse_task_id_latest_version() {
+    async fn parse_task_id(version: DapVersion) {
         let test = test_router();
 
         let task_id = TaskId(thread_rng().gen());
 
         let req = test(
             Request::builder()
-                .uri(format!("/v09/{}/parse-task-id", task_id.to_base64url()))
+                .uri(format!(
+                    "/{version}/{}/parse-task-id",
+                    task_id.to_base64url()
+                ))
                 .body(Body::empty())
                 .unwrap(),
         )
@@ -473,29 +461,9 @@ mod test {
         assert_eq!(req.task_id, Some(task_id));
     }
 
-    #[tokio::test]
-    async fn parse_task_id_draft02_version() {
-        let test = test_router();
+    async_test_versions! { parse_task_id }
 
-        let task_id = TaskId(thread_rng().gen());
-
-        let req = test(
-            Request::builder()
-                .uri("/v02/parse-version")
-                .body({
-                    let mut v = Vec::new();
-                    task_id.encode(&mut v).unwrap();
-                    Body::from(v)
-                })
-                .unwrap(),
-        )
-        .await;
-
-        assert_eq!(req.task_id, Some(task_id));
-    }
-
-    #[tokio::test]
-    async fn parse_agg_job_id_latest_version() {
+    async fn parse_agg_job_id(version: DapVersion) {
         let test = test_router();
 
         let agg_job_id = AggregationJobId(thread_rng().gen());
@@ -503,7 +471,7 @@ mod test {
         let req = test(
             Request::builder()
                 .uri(format!(
-                    "/v09/{}/parse-agg-job-id",
+                    "/{version}/{}/parse-agg-job-id",
                     agg_job_id.to_base64url()
                 ))
                 .header(CONTENT_TYPE, "application/dap-aggregation-job-init-req")
@@ -514,4 +482,7 @@ mod test {
 
         assert_eq!(req.resource, DapResource::AggregationJob(agg_job_id));
     }
+
+    async_test_version! { parse_agg_job_id, Draft09 }
+    async_test_version! { parse_agg_job_id, Latest }
 }
diff --git a/daphne_worker/src/durable/aggregate_store.rs b/daphne_worker/src/durable/aggregate_store.rs
index eef4fe4..73c2eb0 100644
--- a/daphne_worker/src/durable/aggregate_store.rs
+++ b/daphne_worker/src/durable/aggregate_store.rs
@@ -61,8 +61,8 @@ pub struct AggregateStore {
     collected: Option<bool>,
 }
 
-/// Minimum number of chunks needed to store 512K of aggregate share data.
-const MAX_AGG_SHARE_CHUNK_KEY_COUNT: usize = 4;
+/// Minimum number of chunks needed to store 1Mb of aggregate share data.
+const MAX_AGG_SHARE_CHUNK_KEY_COUNT: usize = 8;
 
 /// Minimum number of chunks needed to store `10_000` report ids.
 const MAX_REPORT_ID_CHUNK_KEY_COUNT: usize = 2;
diff --git a/daphne_worker/src/storage_proxy.rs b/daphne_worker/src/storage_proxy.rs
index c0b79c3..f7d5ddc 100644
--- a/daphne_worker/src/storage_proxy.rs
+++ b/daphne_worker/src/storage_proxy.rs
@@ -49,7 +49,7 @@
 //!     bindings::AggregateStore::Merge,
 //!     // some mock data here
 //!     (
-//!         daphne::DapVersion::DraftLatest,
+//!         daphne::DapVersion::Draft09,
 //!         "some-task-id-in-hex",
 //!         &daphne::DapBatchBucket::TimeInterval { batch_window: 50 }
 //!     ),
diff --git a/daphne_worker/src/tracing_utils/mod.rs b/daphne_worker/src/tracing_utils/mod.rs
index 9a71491..4da95a4 100644
--- a/daphne_worker/src/tracing_utils/mod.rs
+++ b/daphne_worker/src/tracing_utils/mod.rs
@@ -205,6 +205,7 @@ pub fn initialize_tracing(env: &Env) {
             Ok(_) | Err(_) => {
                 // Console output
                 let ansi = fmt::layer()
+                    .pretty()
                     .with_ansi(true)
                     .with_writer(LogWriter::new)
                     .with_timer(WasmTime::new());


Test patch:
diff --git a/daphne/src/testing.rs b/daphne/src/testing.rs
index 5ed16c1..617ae9c 100644
--- a/daphne/src/testing.rs
+++ b/daphne/src/testing.rs
@@ -499,7 +499,7 @@ impl AggregationJobTest {
 //
 // and
 //
-//     something_draftlatest
+//     something_draft09
 //
 // that called something(version) with the appropriate version.
 //
@@ -522,7 +522,7 @@ macro_rules! test_versions {
     ($($fname:ident),*) => {
         $(
             $crate::test_version! { $fname, Draft02 }
-            $crate::test_version! { $fname, DraftLatest }
+            $crate::test_version! { $fname, Draft09 }
         )*
     };
 }
@@ -544,7 +544,8 @@ macro_rules! async_test_versions {
     ($($fname:ident),*) => {
         $(
             $crate::async_test_version! { $fname, Draft02 }
-            $crate::async_test_version! { $fname, DraftLatest }
+            $crate::async_test_version! { $fname, Draft09 }
+            $crate::async_test_version! { $fname, Latest }
         )*
     };
 }
diff --git a/daphne_worker_test/tests/e2e/e2e.rs b/daphne_worker_test/tests/e2e/e2e.rs
index a8af3ea..5f8eba6 100644
--- a/daphne_worker_test/tests/e2e/e2e.rs
+++ b/daphne_worker_test/tests/e2e/e2e.rs
@@ -289,7 +289,7 @@ async fn leader_upload(version: DapVersion) {
     );
     let builder = match t.version {
         DapVersion::Draft02 => client.post(url.as_str()),
-        DapVersion::DraftLatest => client.put(url.as_str()),
+        DapVersion::Draft09 | DapVersion::Latest => client.put(url.as_str()),
     };
     let resp = builder
         .body(
@@ -300,7 +300,7 @@ async fn leader_upload(version: DapVersion) {
                     time: t.now,
                     draft02_extensions: match version {
                         DapVersion::Draft02 => Some(Vec::default()),
-                        DapVersion::DraftLatest => None,
+                        DapVersion::Draft09 | DapVersion::Latest => None,
                     },
                 },
                 public_share: b"public share".to_vec(),
@@ -621,7 +621,7 @@ async fn leader_collect_ok(version: DapVersion) {
 
     if version != DapVersion::Draft02 {
         // Check that the time interval for the reports is correct.
-        let interval = collection.draft_latest_interval.as_ref().unwrap();
+        let interval = collection.draft09_interval.as_ref().unwrap();
         let low = t.task_config.quantized_time_lower_bound(time_min);
         let high = t.task_config.quantized_time_upper_bound(time_max);
         assert!(low < high);
@@ -1026,7 +1026,7 @@ async_test_versions! { leader_collect_abort_overlapping_batch_interval }
 
 #[tokio::test]
 async fn fixed_size() {
-    let version = DapVersion::DraftLatest;
+    let version = DapVersion::Draft09;
     let t = TestRunner::fixed_size(version).await;
     let path = t.upload_path();
     let client = TestRunner::http_client();
@@ -1064,7 +1064,7 @@ async fn fixed_size() {
         draft02_task_id: t.collect_task_id_field(),
         query: match version {
             DapVersion::Draft02 => Query::FixedSizeByBatchId { batch_id },
-            DapVersion::DraftLatest => Query::FixedSizeCurrentBatch,
+            DapVersion::Draft09 | DapVersion::Latest => Query::FixedSizeCurrentBatch,
         },
         agg_param: agg_param.get_encoded().unwrap(),
     };
@@ -1164,7 +1164,7 @@ async fn fixed_size() {
         draft02_task_id: t.collect_task_id_field(),
         query: match version {
             DapVersion::Draft02 => Query::FixedSizeByBatchId { batch_id },
-            DapVersion::DraftLatest => Query::FixedSizeCurrentBatch,
+            DapVersion::Draft09 | DapVersion::Latest => Query::FixedSizeCurrentBatch,
         },
         agg_param: agg_param.get_encoded().unwrap(),
     };
@@ -1255,7 +1255,7 @@ async fn leader_collect_taskprov_ok(version: DapVersion) {
     for _ in 0..t.task_config.min_batch_size {
         let extensions = vec![Extension::Taskprov {
             draft02_payload: match version {
-                DapVersion::DraftLatest => None,
+                DapVersion::Draft09 | DapVersion::Latest => None,
                 DapVersion::Draft02 => taskprov_report_extension_payload.clone(),
             },
         }];
diff --git a/daphne_worker_test/tests/e2e/test_runner.rs b/daphne_worker_test/tests/e2e/test_runner.rs
index 70f304f..3f4d417 100644
--- a/daphne_worker_test/tests/e2e/test_runner.rs
+++ b/daphne_worker_test/tests/e2e/test_runner.rs
@@ -79,12 +79,8 @@ impl TestRunner {
 
         // When running in a local development environment, override the hostname of each
         // aggregator URL with 127.0.0.1.
-        let version_path = match version {
-            DapVersion::Draft02 => "v02",
-            DapVersion::DraftLatest => "v09",
-        };
-        let mut leader_url = Url::parse(&format!("http://leader:8787/{version_path}/")).unwrap();
-        let mut helper_url = Url::parse(&format!("http://helper:8788/{version_path}/")).unwrap();
+        let mut leader_url = Url::parse(&format!("http://leader:8787/{version}/")).unwrap();
+        let mut helper_url = Url::parse(&format!("http://helper:8788/{version}/")).unwrap();
         println!("leader_url = {leader_url}");
         if let Ok(env) = std::env::var("DAP_DEPLOYMENT") {
             if env == "dev" {
@@ -295,7 +291,7 @@ impl TestRunner {
                 HpkeConfig::get_decoded(&raw_leader_hpke_config).unwrap(),
                 HpkeConfig::get_decoded(&raw_helper_hpke_config).unwrap(),
             ],
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 let mut leader_hpke_config_list =
                     HpkeConfigList::get_decoded(&raw_leader_hpke_config).unwrap();
                 let mut helper_hpke_config_list =
@@ -710,14 +706,16 @@ impl TestRunner {
     pub fn upload_path_for_task(&self, id: &TaskId) -> String {
         match self.version {
             DapVersion::Draft02 => "upload".to_string(),
-            DapVersion::DraftLatest => format!("tasks/{}/reports", id.to_base64url()),
+            DapVersion::Draft09 | DapVersion::Latest => {
+                format!("tasks/{}/reports", id.to_base64url())
+            }
         }
     }
 
     pub fn collect_path_for_task(&self, task_id: &TaskId) -> String {
         match self.version {
             DapVersion::Draft02 => "collect".to_string(),
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 let collection_job_id = CollectionJobId(thread_rng().gen());
                 format!(
                     "tasks/{}/collection_jobs/{}",
@@ -819,7 +817,7 @@ async fn post_internal_delete_all(
     assert_eq!(
         200,
         resp.status(),
-        "unexpected response status: {:?}",
+        "request to {url} failed: response {:?}",
         resp.text().await.unwrap()
     );
 }