Skip to content

fix(evolution): preserve immutable refresh provenance - #10

Merged
jhinpan merged 3 commits into
mainfrom
fix/evolution-immutable-runs
Jul 23, 2026
Merged

fix(evolution): preserve immutable refresh provenance#10
jhinpan merged 3 commits into
mainfrom
fix/evolution-immutable-runs

Conversation

@jhinpan

@jhinpan jhinpan commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • assign every refresh a UTC run_id (YYYYMMDDTHHMMSSZ)
  • store ledgers, manifests, and summaries in immutable per-run directories
  • pass one run ID through discovery, optional synthesis, summary publication, and Draft PR update
  • preserve prior same-day candidate decisions instead of overwriting their provenance
  • add a same-day retry regression proving the first include ledger remains intact

Verification

  • python3 tests/test_evolution.py
  • python3 tests/test_validate.py
  • python3 scripts/validate.py
  • git diff --check

Found by the installed scheduled Claude double-check on rolling PR #6: repeated same-day runs overwrote the 2026-07-22 ledger/summary, leaving the automatically added PR-8934 page without its original include record and making per-run budgets unauditable.

Summary by Sourcery

Introduce immutable, per-run evolution refresh provenance keyed by a UTC run identifier and thread that identifier through discovery, refresh, daily worker, and summaries.

New Features:

  • Add a UTC-formatted run_id concept for evolution runs and expose it via CLI flags and summary outputs.
  • Generate per-run candidate ledgers, manifests, and refresh summaries in run_id-based directories to support immutable audit trails.

Bug Fixes:

  • Prevent repeated same-day evolution runs from overwriting prior candidate ledgers and refresh summaries, preserving original include decisions.

Enhancements:

  • Extend discovery and refresh metadata to record run_id alongside run_date for better traceability.
  • Update the daily worker to reuse the original run_id across retry refreshes and summary publication.

Tests:

  • Add regression coverage ensuring same-day discovery reruns create separate ledgers and preserve the first run’s candidates.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@sourcery-ai

sourcery-ai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduce a UTC run_id for each refresh/discovery run, move ledgers/manifests/summaries into immutable per-run directories keyed by run_id, propagate run_id through discovery, refresh, and daily worker flows, and add tests to prevent same-day reruns from overwriting previous candidate provenance.

Sequence diagram for run_id propagation across refresh, discovery, and daily worker

sequenceDiagram
    actor Scheduler
    participant daily_worker_run_daily as daily_worker.run_daily
    participant refresh_run_refresh as refresh.run_refresh
    participant discover_run_discovery as discover.run_discovery

    Scheduler->>daily_worker_run_daily: run_daily()
    daily_worker_run_daily->>refresh_run_refresh: _run(refresh.py args)
    activate refresh_run_refresh
    refresh_run_refresh->>refresh_run_refresh: new_run_id()
    refresh_run_refresh->>refresh_run_refresh: validate_run_id(run_id)
    refresh_run_refresh->>discover_run_discovery: run_discovery(run_id=run_id, captured_at=run_date)
    activate discover_run_discovery
    discover_run_discovery->>discover_run_discovery: validate_run_id(run_id)
    discover_run_discovery->>discover_run_discovery: _write_yaml(candidates/runs/run_id/short.yaml)
    discover_run_discovery->>discover_run_discovery: _write_yaml(candidates/runs/run_id/manifest.yaml)
    discover_run_discovery-->>refresh_run_refresh: discovery result
    deactivate discover_run_discovery
    refresh_run_refresh->>refresh_run_refresh: _finalize_summary(root, run_id, summary)
    refresh_run_refresh->>refresh_run_refresh: _write_summary(candidates/runs/run_id/refresh-summary.yaml)
    refresh_run_refresh-->>daily_worker_run_daily: json { run_id, run_date, ... }
    deactivate refresh_run_refresh

    daily_worker_run_daily->>daily_worker_run_daily: parse run_id from refresh_result
    daily_worker_run_daily->>refresh_run_refresh: _run(refresh.py --skip-discovery --run-id=run_id)
    activate refresh_run_refresh
    refresh_run_refresh->>refresh_run_refresh: validate_run_id(run_id)
    refresh_run_refresh->>refresh_run_refresh: _finalize_summary(root, run_id, summary)
    refresh_run_refresh-->>daily_worker_run_daily: updated summary
    deactivate refresh_run_refresh

    daily_worker_run_daily->>daily_worker_run_daily: open candidates/runs/run_id/refresh-summary.yaml
    daily_worker_run_daily-->>Scheduler: json { run_id, run_date, branch, status, url }
Loading

Flow diagram for immutable per-run candidate artifacts keyed by run_id

flowchart LR
    root[candidates/runs]
    run[run_id directory]
    ledger[per-source ledger short.yaml]
    manifest[manifest.yaml]
    summary[refresh-summary.yaml]

    root --> run
    run --> ledger
    run --> manifest
    run --> summary

    subgraph discover_run_discovery
        D1[write per-source ledger]
        D2[write manifest]
    end

    subgraph refresh_run_refresh
        R1[write refresh summary]
    end

    D1 --> ledger
    D2 --> manifest
    R1 --> summary
Loading

File-Level Changes

Change Details Files
Introduce structured UTC run_id generation/validation and propagate it through discovery so each run writes its own immutable ledger and manifest.
  • Add new_run_id() and validate_run_id() helpers to generate and validate UTC-formatted run IDs.
  • Extend run_discovery() to accept an optional run_id, compute an effective_run_id, and use it for ledger, manifest, and summary paths instead of captured_at date.
  • Include run_id in per-run manifest and discovery summary payloads and CLI arguments.
scripts/evolve/discover.py
tests/test_evolution.py
Update refresh workflow to be keyed by run_id rather than run_date and ensure summaries and budgets are tracked per run.
  • Change _write_summary() and _finalize_summary() to use run_id for paths instead of run_date.
  • Generate and validate a run_id at the start of run_refresh(), include it in the refresh summary object, and pass it through to discovery.
  • Adjust tests that assert summary paths to expect the new run_id-based directory structure.
scripts/evolve/refresh.py
tests/test_evolution.py
Wire run_id through the daily_worker orchestration and ensure re-runs on the same day reuse the original run_id while keeping artifacts immutable.
  • Parse the JSON result of the initial refresh run in daily_worker to capture run_id.
  • Pass the captured run_id into subsequent refresh invocations (e.g., synthesis retry) and use it to locate the refresh-summary.yaml.
  • Include run_id in the final daily worker result payload returned to callers.
scripts/evolve/daily_worker.py
Add regression coverage to verify same-day repeated discovery runs preserve initial candidate provenance and use run_id-based directories.
  • Extend evolution tests to pass explicit run_id values to run_discovery for first and second runs on the same date.
  • Assert that the first run’s ledger retains its single candidate while the second run’s ledger is empty, proving immutability of the initial include record.
  • Update budget enforcement tests to expect refresh summaries under the new run_id-based path.
tests/test_evolution.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="tests/test_evolution.py" line_range="261-270" />
<code_context>
         assert page_fm["architectures"] == ["gfx950"]
         assert page_fm["scope_status"] == "active"

+        second = run_discovery(
+            root=root,
+            source_ids=["example"],
+            fixture_path=fixture,
+            captured_at="2026-07-22",
+            run_id="20260722T000100Z",
+            dry_run=False,
+        )
+        assert second["included"] == 0
+        first_ledger = yaml.safe_load(ledger.read_text(encoding="utf-8"))
+        second_ledger = yaml.safe_load(
+            (
+                root
+                / "candidates"
+                / "runs"
+                / "20260722T000100Z"
+                / "example.yaml"
+            ).read_text(encoding="utf-8")
+        )
+        assert len(first_ledger["candidates"]) == 1
+        assert second_ledger["candidates"] == []
+

</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen the same-day retry regression by checking per-run provenance fields.

The current assertions confirm candidate counts for each run. To better capture the provenance behavior you’re guarding, also assert key metadata fields in both ledgers (e.g., `run_id`, `run_date`, counts) to show that the first run’s record remains unchanged and is not overwritten by the second run with a different `run_id`. This helps catch regressions where ledgers might unintentionally share or override metadata despite being in separate directories.
</issue_to_address>

### Comment 2
<location path="tests/test_evolution.py" line_range="429-432" />
<code_context>
         try:
             _finalize_summary(
                 root,
-                "2026-07-22",
+                "20260722T000000Z",
                 summary,
                 max_files=0,
</code_context>
<issue_to_address>
**suggestion (testing):** Explicitly assert `run_id` in the finalized summary object, not just the path.

In `test_final_summary_is_inside_the_enforced_budget`, the current assertion only checks that the summary file path contains the `run_id`. Please also assert that `finalized["run_id"] == "20260722T000000Z"` (and optionally that `summary["run_id"]` is unchanged) so the test verifies the in-memory summary retains the provenance identifier through `_finalize_summary` and protects against future regressions.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread tests/test_evolution.py
Comment thread tests/test_evolution.py
@jhinpan
jhinpan merged commit f7d810c into main Jul 23, 2026
3 checks passed
@jhinpan
jhinpan deleted the fix/evolution-immutable-runs branch July 23, 2026 00:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant