Skip to content

fix: populate SafeItemsCount in run_summary.json so audits report accurate safe-output write counts#46360

Merged
pelikhan merged 5 commits into
mainfrom
copilot/deep-report-populate-safeitemscount
Jul 18, 2026
Merged

fix: populate SafeItemsCount in run_summary.json so audits report accurate safe-output write counts#46360
pelikhan merged 5 commits into
mainfrom
copilot/deep-report-populate-safeitemscount

Conversation

Copilot AI commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

run_summary.json always wrote SafeItemsCount: 0, causing daily audits (API Consumption, Safe Output Health) to fall back to summary.json → safe_outputs.total_items. Three independent bugs converged to produce this:

Root causes & fixes

  • JSON key mismatch (logs_models.go): WorkflowRun.SafeItemsCount had no JSON tag → marshaled as "SafeItemsCount" (PascalCase). Downstream reads "safe_items_count" (snake_case) → field was always absent.

    // before
    SafeItemsCount int
    // after
    SafeItemsCount int `json:"safe_items_count,omitempty"`
  • Artifact not flattened (logs_download.go): safe-outputs-items contains two files (safe-output-items.jsonl + temporary-id-map.json), so flattenSingleFileArtifacts skipped it. extractCreatedItemsFromManifest looks at the run root, never finding either file. Added flattenSafeOutputsItemsArtifact() mirroring the existing flattenActivationArtifact pattern.

  • Cache persistence gap (logs_run_processor.go): backfillCacheHitIfNeeded healed SafeItemsCount in-memory but never called saveRunSummary. The on-disk run_summary.json stayed at 0 for all cached runs. Now detects when the backfill changed the value and re-persists.

Tests added

  • TestSafeItemsCountJSONKey — asserts "safe_items_count" is present in marshaled JSON (not "SafeItemsCount")
  • TestFlattenSafeOutputsItemsArtifact / TestFlattenSafeOutputsItemsArtifactMissing — flatten moves both files to run root; no-op when artifact absent
  • TestTryLoadCachedRunResultPersistsSafeItemsCountAfterBackfill — on-disk run_summary.json reflects the healed count after a cache hit

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage

Field Value
Category bug
Risk 🟢 Low
Score 18/100 (impact:10, urgency:5, quality:3)
Action defer

Rationale: WIP draft with no files committed yet. Cannot assess scope or risk. Defer until ready for review.

Generated by 🔧 PR Triage Agent · 32.1 AIC · ⌖ 7.02 AIC · ⊞ 5.6K ·

Copilot AI and others added 2 commits July 18, 2026 07:00
…tput write metrics

Three complementary fixes:
1. Add json:"safe_items_count,omitempty" tag to WorkflowRun.SafeItemsCount so it
   serializes with the snake_case key expected by downstream audit tools.
2. Add flattenSafeOutputsItemsArtifact() to move safe-output-items.jsonl and
   temporary-id-map.json from safe-outputs-items/ subdirectory to the run root
   where extractCreatedItemsFromManifest and loadResolvedTemporaryIDTargets look.
3. Persist healed SafeItemsCount back to run_summary.json after cache-hit backfill
   so downstream readers see the correct count without falling back to activity summary.

Closes #46268

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix run_summary.json to populate SafeItemsCount for accurate audits fix: populate SafeItemsCount in run_summary.json so audits report accurate safe-output write counts Jul 18, 2026
Copilot AI requested a review from pelikhan July 18, 2026 07:04
@pelikhan
pelikhan marked this pull request as ready for review July 18, 2026 08:09
Copilot AI review requested due to automatic review settings July 18, 2026 08:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes safe-output counts in cached run summaries and downloaded artifacts.

Changes:

  • Adds the safe_items_count JSON key.
  • Flattens safe-output artifacts and persists backfilled counts.
  • Adds regression tests and changes the auto-upgrade schedule.
Show a summary per file
File Description
pkg/cli/logs_models.go Adds JSON serialization metadata.
pkg/cli/logs_download.go Flattens safe-output artifacts.
pkg/cli/logs_download_test.go Tests artifact flattening.
pkg/cli/logs_run_processor.go Persists backfilled counts.
pkg/cli/logs_run_processor_test.go Tests cache persistence.
pkg/cli/logs_summary_test.go Tests the JSON key.
.github/workflows/agentic-auto-upgrade.yml Changes the weekly schedule.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 7/7 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment thread pkg/cli/logs_download.go
// This artifact contains safe-output-items.jsonl and temporary-id-map.json.
// Flattening moves them to the run root so extractCreatedItemsFromManifest
// and loadResolvedTemporaryIDTargets can find them at their expected paths.
if err := flattenSafeOutputsItemsArtifact(opts.outputDir, opts.verbose); err != nil {
on:
schedule:
- cron: "11 4 * * 6" # Weekly (auto-upgrade)
- cron: "21 3 * * 5" # Weekly (auto-upgrade)
@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review

The three-bug diagnosis is accurate and the fixes are well-targeted. Tests are thorough.

Existing comments already flag:

  • The agentic-auto-upgrade.yml schedule change appears unrelated to this fix; worth verifying it's intentional or generated.
  • flattenSafeOutputsItemsArtifact only helps fresh downloads; the stale-cache path bypasses downloadRunArtifacts. The persistence fix in logs_run_processor.go addresses that correctly — but only if the artifact files are already at the expected path on disk.

Additional note on omitempty: SafeItemsCount int with json:"safe_items_count,omitempty" means a true zero is omitted from JSON, making a "never computed" entry and an "explicitly zero safe-outputs run" indistinguishable. Benign for current reads (both default to 0), but worth noting for future tooling.

No blocking issues found.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 19.7 AIC · ⌖ 4.33 AIC · ⊞ 5K

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /diagnosing-bugs — three independent root causes, each fixed surgically with regression tests. This is a well-diagnosed and cleanly executed bug fix.

📋 Key Themes & Highlights

Positive Highlights

  • ✅ Each root cause is independently identified with a clear before/after in the PR description
  • ✅ Test coverage is thorough: JSON key contract, artifact flattening (present + absent), and cache-persist healing are all verified
  • flattenSafeOutputsItemsArtifact mirrors the existing flattenActivationArtifact pattern — consistent codebase design
  • ✅ The heal-persistence guard (SafeItemsCount != safeItemsBefore) is conservative and correct given backfillCacheHitIfNeeded's own guard

One Minor Note

SafeItemsCount now uses omitempty, which means runs with genuinely 0 safe outputs will omit the key from JSON entirely. Downstream readers that can't fall back would interpret absence as 0, which is behaviorally equivalent here — but worth keeping in mind if the field is later queried with an existence check rather than a null-safe read.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 24.5 AIC · ⌖ 4.36 AIC · ⊞ 6.7K
Comment /matt to run again

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

⚠️ Test Quality Score: 70/100 — Acceptable

Analyzed 4 test(s): 4 design, 0 implementation, 0 violation(s).

📊 Metrics (4 tests)
Metric Value
Analyzed 4 (Go: 4, JS: 0)
✅ Design 4 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 2 (50%)
Duplicate clusters 0
Inflation Yes (2 of 3 ratios > 2:1)
🚨 Violations 0
Test File Classification Coverage
TestFlattenSafeOutputsItemsArtifact logs_download_test.go:893 Design: artifact flattening Happy + edge (6 assertions)
TestFlattenSafeOutputsItemsArtifactMissing logs_download_test.go:927 Design: graceful missing Edge case only
TestTryLoadCachedRunResultPersistsSafeItemsCountAfterBackfill logs_run_processor_test.go:184 Design: disk persistence Regression (3 assertions)
TestSafeItemsCountJSONKey logs_summary_test.go:402 Design: JSON contract Contract (4 assertions)
⚠️ Test Inflation Details

Three test files show elevated test-to-code ratios:

File Test Lines Prod Lines Ratio Justification
logs_download_test.go +71 +25 2.84:1 Artifact I/O requires verification of file placement + content + cleanup
logs_run_processor_test.go +41 +14 2.93:1 Disk persistence & backfill healing requires cross-layer verification
logs_summary_test.go +26 +1 26:1 JSON serialization contract (single tag change) needs exhaustive testing

Assessment: While ratios exceed 2:1, they are justified by:

  • File I/O operations requiring explicit path and content verification
  • Cross-layer testing (memory → disk → reload) for cache persistence
  • Contract tests for JSON key naming (critical for downstream audit tools)

Inflation is acceptable given the nature of the changes.

Verdict

Passed. 0% implementation tests (threshold: 30%).

Test design is strong. All 4 tests enforce genuine behavioral contracts:

  1. Artifact flattening — files discoverable at run root after download
  2. Graceful missing artifact — no errors when optional files absent
  3. Disk persistence — cached SafeItemsCount healed and persisted to disk
  4. JSON serialization — snake_case key for downstream audit tool compatibility

No mock libraries, no missing build tags, edge cases covered. The test-to-code ratios are high but justified by the complexity of file I/O and cross-layer validation.


Sentinel: Go tests passed structural validation. No blocking issues detected.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 16.9 AIC · ⌖ 8.92 AIC · ⊞ 7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 70/100. 0% implementation tests (threshold: 30%). All 4 tests enforce genuine behavioral contracts with no mock libraries or guideline violations.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Three correctness issues need fixing before merge.

Blocking findings

1. omitempty defeats the JSON fix (logs_models.go:80)

Adding json:"safe_items_count,omitempty" corrects the key name but breaks serialisation of zero — omitempty on int drops the field entirely when it is 0. Runs that produce no safe outputs will serialize without the key, reload as 0, and trigger the backfill on every cache hit indefinitely. Remove omitempty.

2. Over-broad heal guard can overwrite correct persisted values (logs_run_processor.go:164)

The guard result.Run.SafeItemsCount != safeItemsBefore fires when any change occurs, not only on zero→non-zero transitions. If the stored count was already non-zero and the backfill computes a different value, this silently replaces the authoritative persisted value. The guard should be safeItemsBefore == 0 && result.Run.SafeItemsCount != 0.

3. Heal write silently persists Metrics as a side-effect (logs_run_processor.go:167)

healed.Metrics = result.Metrics commits whatever token-usage state backfillCacheHitIfNeeded wrote, regardless of whether that data is better or worse than what was already on disk. This is undocumented, unguarded, and can regress previously-correct Metrics.

🔎 Code quality review by PR Code Quality Reviewer · 53.1 AIC · ⌖ 4.82 AIC · ⊞ 5.6K
Comment /review to run again

Comments that could not be inline-anchored

pkg/cli/logs_models.go:80

omitempty on an int field silently drops safe_items_count from JSON when zero, making legitimate zero-item runs indistinguishable from stale cache entries — triggering unnecessary backfill I/O on every subsequent cache hit.

<details>
<summary>💡 Suggested fix</summary>

omitempty on int omits the key entirely when the value is 0. Any run that produced zero safe-output items serializes without safe_items_count; on reload the field deserializes back to 0, which is the exact s…

pkg/cli/logs_run_processor.go:164

The heal guard fires on any change to SafeItemsCount, not just the zero→non-zero transition the comment describes, risking silent overwrites of previously-correct persisted values.

<details>
<summary>💡 Detail and fix</summary>

The inline comment at line 157 explicitly documents the intent as "it was 0 before and is now non-zero", but the guard is result.Run.SafeItemsCount != safeItemsBefore. This also fires when the stored value was already non-zero (e.g., 5) and `backfillCacheHitIfN…

pkg/cli/logs_run_processor.go:167

healed.Metrics = result.Metrics silently persists backfilled token-usage data as a side-effect of the SafeItemsCount heal, potentially overwriting a previously-authoritative Metrics value with an inferred estimate.

<details>
<summary>💡 Detail</summary>

backfillCacheHitIfNeeded can modify result.Metrics (via backfillRunTokenUsageFromFirewall) as a side-effect. The heal write here is triggered solely by a SafeItemsCount change, but unconditionally commits whatever state `resu…

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (177 new lines in pkg/cli/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/46360-fix-safe-items-count-population.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-46360: Fix SafeItemsCount Population in run_summary.json

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 43.8 AIC · ⌖ 13 AIC · ⊞ 8.5K ·
Comment /review to run again

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

@pelikhan
pelikhan merged commit 017cdbc into main Jul 18, 2026
28 of 30 checks passed
@pelikhan
pelikhan deleted the copilot/deep-report-populate-safeitemscount branch July 18, 2026 09:00
Copilot stopped work on behalf of pelikhan due to an error July 18, 2026 09:00
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.13

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[deep-report] Populate SafeItemsCount in run_summary.json so audits stop reporting 0 safe-output writes

3 participants