diff --git a/action.yml b/action.yml index f14d13a..8c245e2 100644 --- a/action.yml +++ b/action.yml @@ -283,7 +283,14 @@ runs: with: name: codeboarding-base-${{ steps.state.outputs.cfg_hash }}-${{ steps.sync_commit.outputs.baseline_sha }} path: ${{ runner.temp }}/cb-state/${{ github.action }}/out/base - retention-days: 30 + # Longer than a review's 30 days on purpose, and the margin is the whole + # point: correctness comes from the renewal check, which republishes any + # base with less than a review's lifetime left, while this surplus + # decides how often that fires. Equal lifetimes renew on every run and + # publish a base per run rather than per merge base — more storage, not + # less. 60 leaves a 30-day reuse window, so a pull request open a month + # never triggers one. + retention-days: 60 if-no-files-found: ignore - name: Publish baseline analysis for the analyzed commit @@ -293,7 +300,14 @@ runs: with: name: codeboarding-base-${{ steps.state.outputs.cfg_hash }}-${{ steps.sync_commit.outputs.analyzed_sha }} path: ${{ runner.temp }}/cb-state/${{ github.action }}/out/base - retention-days: 30 + # Longer than a review's 30 days on purpose, and the margin is the whole + # point: correctness comes from the renewal check, which republishes any + # base with less than a review's lifetime left, while this surplus + # decides how often that fires. Equal lifetimes renew on every run and + # publish a base per run rather than per merge base — more storage, not + # less. 60 leaves a 30-day reuse window, so a pull request open a month + # never triggers one. + retention-days: 60 if-no-files-found: ignore - name: Write sync summary @@ -357,7 +371,14 @@ runs: with: name: ${{ steps.state.outputs.base_name }} path: ${{ runner.temp }}/cb-state/${{ github.action }}/out/base - retention-days: 30 + # Longer than a review's 30 days on purpose, and the margin is the whole + # point: correctness comes from the renewal check, which republishes any + # base with less than a review's lifetime left, while this surplus + # decides how often that fires. Equal lifetimes renew on every run and + # publish a base per run rather than per merge base — more storage, not + # less. 60 leaves a 30-day reuse window, so a pull request open a month + # never triggers one. + retention-days: 60 if-no-files-found: ignore - name: Render review diagram diff --git a/tests/test_action_state.py b/tests/test_action_state.py index 27aca15..95e2ed4 100644 --- a/tests/test_action_state.py +++ b/tests/test_action_state.py @@ -482,13 +482,20 @@ def test_the_base_is_inlined_whenever_no_artifact_holds_it(self) -> None: self.assertIn("fetch_base.outputs.artifact_id == ''", inline[0]) def test_a_base_outlives_the_reviews_that_reference_it(self) -> None: - # A review artifact points at a base by id for 30 days, so a base kept - # for less leaves that review unusable for the rest of its life. - for step in self._uploads(): - if "out/base" in step.get("path", ""): - self.assertEqual( - step.get("retention-days"), "30", f"{step['name']} may expire before the reviews naming it" - ) + # A review points at a base by id for its whole life, so a base kept for + # the same period is only ever good at the instant it is written: the + # renewal check would then fire on every run and republish it every time, + # which is exactly the duplication that splitting it out removed. + review = next(s for s in self._uploads() if "review_artifact.outputs.artifact_dir" in s.get("path", "")) + review_days = int(review["retention-days"]) + bases = [s for s in self._uploads() if "out/base" in s.get("path", "")] + self.assertTrue(bases, "no base publication step found") + for step in bases: + self.assertGreater( + int(step.get("retention-days", 0)), + review_days, + f"{step['name']} does not outlive the reviews that reference it", + ) def test_the_reusable_analysis_honours_the_configured_retention(self) -> None: warmstart = [s for s in self._uploads() if "warmstart" in s.get("path", "")]