Don't generate observations for metrics/reports in earlier release stages.

Bug: b/280897137
Change-Id: Iba5da796512b9c2d5a20daa8247e8c276b79092d
Reviewed-on: https://fuchsia-review.googlesource.com/c/cobalt/+/853096
Fuchsia-Auto-Submit: Cameron Dale <camrdale@google.com>
Reviewed-by: Steve Fung <stevefung@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/src/local_aggregation/observation_generator.cc b/src/local_aggregation/observation_generator.cc
index 272ffdf..ea121a7 100644
--- a/src/local_aggregation/observation_generator.cc
+++ b/src/local_aggregation/observation_generator.cc
@@ -124,6 +124,11 @@
 
     for (lib::MetricIdentifier metric_identifier : project->ListMetrics()) {
       const MetricDefinition* metric = project->GetMetric(metric_identifier);
+      if (system_data_.release_stage() > metric->meta_data().max_release_stage()) {
+        // Quietly ignore metric not collected in this device's release stage.
+        continue;
+      }
+
       lib::statusor::StatusOr<LocalAggregateStorage::MetricAggregateRef> aggregate_or =
           aggregate_storage_.GetMetricAggregate(metric_identifier);
       if (!aggregate_or.ok()) {
@@ -134,6 +139,12 @@
       logger::MetricRef metric_ref = project->RefMetric(metric);
 
       for (const ReportDefinition& report : metric->reports()) {
+        if (report.max_release_stage() != ReleaseStage::RELEASE_STAGE_NOT_SET &&
+            system_data_.release_stage() > report.max_release_stage()) {
+          // Quietly ignore report not collected in this device's release stage.
+          continue;
+        }
+
         lib::statusor::StatusOr<util::NotNullUniquePtr<AggregationProcedure>> not_null_procedure =
             AggregationProcedure::Get(*metric, report);
         if (!not_null_procedure.ok()) {
diff --git a/src/local_aggregation/observation_generator_test.cc b/src/local_aggregation/observation_generator_test.cc
index 0a1d169..0183c41 100644
--- a/src/local_aggregation/observation_generator_test.cc
+++ b/src/local_aggregation/observation_generator_test.cc
@@ -1857,4 +1857,122 @@
   }
 }
 
+TEST_F(PrivacyObservationGeneratorTest, SkipsReportsNotInTheReleaseStage) {
+  const MetricDefinition* metric =
+      project_context_->GetMetric(privacy::kOccurrenceMetricPrivacyMetricId);
+  std::unique_ptr<util::UtcTimeConverter> converter = std::make_unique<util::UtcTimeConverter>();
+  CB_ASSERT_OK_AND_ASSIGN(uint32_t start_day_index,
+                          util::TimePointToDayIndex(starting_time_, *converter, *metric));
+
+  SystemProfile system_profile;
+  system_profile.set_os(SystemProfile::FUCHSIA);
+  system_profile.set_system_version("100");
+  uint64_t system_profile_hash = util::Farmhash64(system_profile.SerializeAsString());
+  {
+    MetricAggregateRef aggregate = GetMetricAggregate(privacy::kOccurrenceMetricPrivacyMetricId);
+    ReportAggregate* report =
+        &(*aggregate.aggregate()->mutable_by_report_id())
+            [privacy::kOccurrenceMetricPrivacyNotCollectedDebugReportReportId];
+    report->mutable_daily()->set_last_day_index(start_day_index - 1);
+    SystemProfileAggregate* system_profile_agg =
+        (*report->mutable_daily()->mutable_by_day_index())[start_day_index]
+            .add_system_profile_aggregates();
+    system_profile_agg->set_system_profile_hash(system_profile_hash);
+    system_profile_agg->add_by_event_code()
+        ->mutable_data()
+        ->mutable_at_least_once()
+        ->set_at_least_once(true);
+    aggregate.StoreFilteredSystemProfile(system_profile_hash, system_profile);
+    ASSERT_TRUE(aggregate.Save().ok());
+  }
+
+  std::vector<Observation> observations;
+  std::vector<ObservationMetadata> metadatas;
+  int contribution_count = 0;
+  TestObservationStoreWriter test_writer(
+      [&observations, &metadatas, &contribution_count](
+          std::unique_ptr<observation_store::StoredObservation> observation,
+          std::unique_ptr<ObservationMetadata> metadata) {
+        if (metadata->metric_id() == privacy::kOccurrenceMetricPrivacyMetricId &&
+            metadata->report_id() ==
+                privacy::kOccurrenceMetricPrivacyNotCollectedDebugReportReportId) {
+          metadatas.push_back(*metadata);
+          if (observation->has_unencrypted()) {
+            observations.push_back(observation->unencrypted());
+          }
+          if (!observation->contribution_id().empty()) {
+            contribution_count++;
+          }
+        }
+      });
+
+  logger::ObservationWriter observation_writer(test_writer, nullptr);
+  ConstructObservationGenerator(observation_writer, *converter);
+  GenerateObservationsOnce(util::FromUnixSeconds(util::DayIndexToUnixSeconds(start_day_index + 1)));
+
+  // No observations should be generated for the DEBUG report on the GA device.
+  EXPECT_EQ(observations.size(), 0);
+  EXPECT_EQ(contribution_count, 0);
+  EXPECT_EQ(metadatas.size(), 0);
+}
+
+TEST_F(PrivacyObservationGeneratorTest, SkipsMetricsNotInTheReleaseStage) {
+  const MetricDefinition* metric =
+      project_context_->GetMetric(privacy::kNotCollectedDebugMetricMetricId);
+  std::unique_ptr<util::UtcTimeConverter> converter = std::make_unique<util::UtcTimeConverter>();
+  CB_ASSERT_OK_AND_ASSIGN(uint32_t start_day_index,
+                          util::TimePointToDayIndex(starting_time_, *converter, *metric));
+
+  SystemProfile system_profile;
+  system_profile.set_os(SystemProfile::FUCHSIA);
+  system_profile.set_system_version("100");
+  uint64_t system_profile_hash = util::Farmhash64(system_profile.SerializeAsString());
+  {
+    MetricAggregateRef aggregate = GetMetricAggregate(privacy::kNotCollectedDebugMetricMetricId);
+    ReportAggregate* report =
+        &(*aggregate.aggregate()->mutable_by_report_id())
+            [privacy::kNotCollectedDebugMetricUniqueDeviceCountsReport1DayReportId];
+    report->mutable_daily()->set_last_day_index(start_day_index - 1);
+    SystemProfileAggregate* system_profile_agg =
+        (*report->mutable_daily()->mutable_by_day_index())[start_day_index]
+            .add_system_profile_aggregates();
+    system_profile_agg->set_system_profile_hash(system_profile_hash);
+    system_profile_agg->add_by_event_code()
+        ->mutable_data()
+        ->mutable_at_least_once()
+        ->set_at_least_once(true);
+    aggregate.StoreFilteredSystemProfile(system_profile_hash, system_profile);
+    ASSERT_TRUE(aggregate.Save().ok());
+  }
+
+  std::vector<Observation> observations;
+  std::vector<ObservationMetadata> metadatas;
+  int contribution_count = 0;
+  TestObservationStoreWriter test_writer(
+      [&observations, &metadatas, &contribution_count](
+          std::unique_ptr<observation_store::StoredObservation> observation,
+          std::unique_ptr<ObservationMetadata> metadata) {
+        if (metadata->metric_id() == privacy::kNotCollectedDebugMetricMetricId &&
+            metadata->report_id() ==
+                privacy::kNotCollectedDebugMetricUniqueDeviceCountsReport1DayReportId) {
+          metadatas.push_back(*metadata);
+          if (observation->has_unencrypted()) {
+            observations.push_back(observation->unencrypted());
+          }
+          if (!observation->contribution_id().empty()) {
+            contribution_count++;
+          }
+        }
+      });
+
+  logger::ObservationWriter observation_writer(test_writer, nullptr);
+  ConstructObservationGenerator(observation_writer, *converter);
+  GenerateObservationsOnce(util::FromUnixSeconds(util::DayIndexToUnixSeconds(start_day_index + 1)));
+
+  // No observations should be generated for the DEBUG metric on the GA device.
+  EXPECT_EQ(observations.size(), 0);
+  EXPECT_EQ(contribution_count, 0);
+  EXPECT_EQ(metadatas.size(), 0);
+}
+
 }  // namespace cobalt::local_aggregation
diff --git a/src/local_aggregation/testing/test_privacy_registry/CustomerA/ProjectA1/metrics.yaml b/src/local_aggregation/testing/test_privacy_registry/CustomerA/ProjectA1/metrics.yaml
index 1c1745e..da60b02 100644
--- a/src/local_aggregation/testing/test_privacy_registry/CustomerA/ProjectA1/metrics.yaml
+++ b/src/local_aggregation/testing/test_privacy_registry/CustomerA/ProjectA1/metrics.yaml
@@ -26,3 +26,32 @@
         event_vector_buffer_max: 100
         system_profile_field: [OS, SYSTEM_VERSION]
         system_profile_selection: SELECT_LAST
+      - id: 3
+        report_name: "not_collected_debug_report"
+        report_type: UNIQUE_DEVICE_COUNTS
+        local_aggregation_procedure: AT_LEAST_ONCE
+        local_aggregation_period: WINDOW_1_DAY
+        privacy_level: LOW_PRIVACY
+        event_vector_buffer_max: 100
+        system_profile_field: [OS, SYSTEM_VERSION]
+        system_profile_selection: SELECT_LAST
+        max_release_stage: DEBUG
+
+  - id: 2
+    metric_name: "not_collected_debug_metric"
+    metric_type: OCCURRENCE
+    metric_semantics: [NETWORK_COMMUNICATION]
+    time_zone_policy: UTC
+    meta_data:
+      expiration_date: "2023/01/01"
+      max_release_stage: DEBUG
+    reports:
+      - id: 1
+        report_name: "unique_device_counts_report_1_day"
+        report_type: UNIQUE_DEVICE_COUNTS
+        local_aggregation_procedure: AT_LEAST_ONCE
+        local_aggregation_period: WINDOW_1_DAY
+        privacy_level: LOW_PRIVACY
+        event_vector_buffer_max: 100
+        system_profile_field: [OS, SYSTEM_VERSION]
+        system_profile_selection: SELECT_LAST