ace-review 0.57.0 → 0.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad21edc3f01275162f0b17aaa9033413abdca90a54a89b7240305b350087ac1a
4
- data.tar.gz: 8a516e358225b6351f05b631f0866bb51c23c7cbfdf69dfd6c19e59a764c7d29
3
+ metadata.gz: 865eb269684936c32eb5d91b002f98419e71b1c89155715bc486382bfa4549f3
4
+ data.tar.gz: 0a4bbea96d948953da5fd157c10ae83c5d6b548888663975342ae11823784195
5
5
  SHA512:
6
- metadata.gz: a22b32abda5cdfdef0c9c01a0cdeb9668aa60a98884ba93c48ea96d3c1f8ec1455fe08cbff16944afc661f2d4760a1aa644ea944b874e53ef6b5c033636d28d4
7
- data.tar.gz: 3e65df04186e2748b7ab716555157ecb2c5e4153cb78928a8a15823717ee750f5bb346c9328f8f401f09f7570c02cf81cdade75f08159bc2aaee0afd427019c4
6
+ metadata.gz: 70a1f4d8f88fb5d9971e961fc0918b5c0477e87a52447b877814b429775b3196c648f2d99f24b216453d30e685ea29da97cb62b93809743a8f26f484402ec622
7
+ data.tar.gz: 39a724d6327716608165c95de3955ae77528fd19baaf107cf035e7cdef2e99697647cc247797f9e72c8639a6535e962791fa12f70fe878a582c181e87e746b43
data/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.57.1] - 2026-10-02
11
+
12
+ ### Fixed
13
+
14
+ - A failed or incomplete provider entry no longer invalidates a campaign session's completed reviewer executions; one completed reviewer per needed scope still completes the round.
15
+
16
+
10
17
  ## [0.57.0] - 2026-10-01
11
18
 
12
19
  ### Fixed
@@ -18,6 +18,16 @@ module Ace
18
18
  class CampaignEvidence
19
19
  Contract = Atoms::CampaignContract
20
20
 
21
+ # One completion predicate for provider executions, shared with the
22
+ # extraction-inventory writer so session metadata and campaign
23
+ # evidence can never disagree about which reports were reviewed.
24
+ def self.completed_entry?(entry)
25
+ execution = entry["execution"] || {}
26
+ entry["status"] == "success" && execution["status"].to_s == "succeeded" &&
27
+ !execution["provider"].to_s.empty? && !execution["model"].to_s.empty? &&
28
+ !entry["completed_at"].to_s.empty?
29
+ end
30
+
21
31
  def initialize(repo_root:, check_evidence: nil, review_evidence: nil, approval_evidence: nil)
22
32
  @repo_root = File.realpath(repo_root)
23
33
  authority = CampaignExecutionEvidence.new(repo_root: @repo_root)
@@ -57,9 +67,9 @@ module Ace
57
67
  metadata = json_keys(YAML.safe_load_file(metadata_path, permitted_classes: [Time, Date, Symbol]))
58
68
  Contract.object!(metadata, "session metadata")
59
69
  expected = {"campaign_id" => record["id"], "contract_identity" => record["contract_identity"],
60
- "subject" => record["subject"], "round_id" => binding["round_id"], "scope" => scope,
61
- "head" => binding["head"], "base" => binding["base"],
62
- "scope_identity" => binding["scope_identity"][scope]}
70
+ "subject" => record["subject"], "round_id" => binding["round_id"], "scope" => scope,
71
+ "head" => binding["head"], "base" => binding["base"],
72
+ "scope_identity" => binding["scope_identity"][scope]}
63
73
  unless metadata["campaign_binding"] == expected && metadata["preset"] == expected["scope_identity"]["preset"]
64
74
  raise Contract::Invalid, "session campaign/contract/scope/head/base identity mismatch"
65
75
  end
@@ -83,12 +93,7 @@ module Ace
83
93
  reports = []
84
94
  entries.each do |entry|
85
95
  Contract.object!(entry, "model execution")
86
- execution = entry["execution"] || {}
87
- complete = entry["status"] == "success" && execution["status"] == "succeeded" &&
88
- !execution["provider"].to_s.empty? && !execution["model"].to_s.empty? &&
89
- !entry["completed_at"].to_s.empty?
90
- completed &&= complete
91
- next unless complete
96
+ next unless self.class.completed_entry?(entry)
92
97
  path = entry["output_file"].to_s
93
98
  raise Contract::Invalid, "completed execution has no report" if path.empty?
94
99
  path = File.expand_path(path, dir)
@@ -99,7 +104,7 @@ module Ace
99
104
  resolved, report_ref = artifact("path" => path, "sha256" => entry["report_sha256"])
100
105
  raise Contract::Invalid, "empty reviewer report" if File.read(resolved).strip.empty?
101
106
  refs << report_ref
102
- reports << {"artifact" => report_ref, "execution" => execution}
107
+ reports << {"artifact" => report_ref, "execution" => entry["execution"] || {}}
103
108
  prompts = entry["prompt_sha256"]
104
109
  Contract.object!(prompts, "execution prompt checksums")
105
110
  %w[system user].each do |name|
@@ -117,6 +122,10 @@ module Ace
117
122
  raise Contract::Invalid, "feedback extraction inventory or reviewed reports changed"
118
123
  end
119
124
  end
125
+ # One completed reviewer per needed scope suffices: a failed or
126
+ # incomplete provider entry does not count as a reviewer, but it
127
+ # does not invalidate the model executions that did complete.
128
+ completed &&= reports.any?
120
129
  completed &&= extracted
121
130
  proof = completed ? @review_evidence.call(input.fetch("receipt"), head: binding["head"], artifacts: refs) : nil
122
131
  {"receipt" => input["receipt"], "execution_proof" => proof, "path" => dir.delete_prefix(@repo_root + File::SEPARATOR), "scope" => scope,
@@ -417,7 +417,8 @@ module Ace
417
417
  pr_metadata: metadata, delta: delta,
418
418
  diff_manifest: manifest.merge(scoped[:manifest]).merge(
419
419
  exempt_files: classification[:exempt],
420
- exempt_patterns: Array(config[:exempt_paths]))}
420
+ exempt_patterns: Array(config[:exempt_paths])
421
+ )}
421
422
  end
422
423
 
423
424
  scoped = apply_exempt_paths(scoped, config)
@@ -497,10 +498,10 @@ module Ace
497
498
  lines << ""
498
499
  lines << "# No-op review round"
499
500
  lines << ""
500
- if content[:noop_reason] == :empty_delta && delta
501
- lines << "Scope: delta since `#{delta[:reference_head]}` — the delta is empty; there is no change to review."
501
+ lines << if content[:noop_reason] == :empty_delta && delta
502
+ "Scope: delta since `#{delta[:reference_head]}` — the delta is empty; there is no change to review."
502
503
  else
503
- lines << "Scope: #{content[:noop_scope] || "no-op"}."
504
+ "Scope: #{content[:noop_scope] || "no-op"}."
504
505
  end
505
506
  lines << ""
506
507
  lines << "Verdict: no new findings. This round is clean only if no carried-forward Critical/High finding remains unresolved."
@@ -1636,7 +1637,17 @@ module Ace
1636
1637
  # completes; an absent directory cannot attest a zero-findings review.
1637
1638
  def save_campaign_feedback_metadata(session_dir, result)
1638
1639
  path = File.join(session_dir, "metadata.yml")
1639
- metadata = YAML.safe_load_file(path, permitted_classes: [Time, Date, Symbol])
1640
+ # Session metadata is symbol-keyed YAML; normalize before any key
1641
+ # reads so the completed-execution rule and the binding check see
1642
+ # string keys, matching the campaign evidence reader.
1643
+ normalize = lambda do |value|
1644
+ case value
1645
+ when Hash then value.to_h { |k, v| [k.to_s, normalize.call(v)] }
1646
+ when Array then value.map { |v| normalize.call(v) }
1647
+ else value
1648
+ end
1649
+ end
1650
+ metadata = normalize.call(YAML.safe_load_file(path, permitted_classes: [Time, Date, Symbol]))
1640
1651
  return unless metadata["campaign_binding"]
1641
1652
  extraction = {"status" => result ? "failed" : "skipped"}
1642
1653
  if result && result[:success]
@@ -1649,9 +1660,13 @@ module Ace
1649
1660
  unless result[:items_count] == ids.size && ids.uniq.size == ids.size
1650
1661
  raise Errors::BundleProcessingError, "feedback extraction inventory is incomplete"
1651
1662
  end
1652
- entries = metadata["models"] || [YAML.safe_load_file(File.join(session_dir, "llm_metadata.yml"),
1653
- permitted_classes: [Time, Date, Symbol])]
1654
- digests = entries.map { |entry| entry["report_sha256"] }.compact
1663
+ entries = metadata["models"] || [normalize.call(YAML.safe_load_file(
1664
+ File.join(session_dir, "llm_metadata.yml"), permitted_classes: [Time, Date, Symbol]
1665
+ ))]
1666
+ # Same completed rule as the campaign evidence reader: a failed
1667
+ # provider that left partial output is not a reviewed report.
1668
+ digests = entries.select { |entry| Molecules::CampaignEvidence.completed_entry?(entry) }
1669
+ .map { |entry| entry["report_sha256"] }.compact
1655
1670
  extraction = {"status" => "succeeded", "finding_ids" => ids, "report_sha256" => digests}
1656
1671
  end
1657
1672
  metadata["feedback_extraction"] = extraction
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Review
5
- VERSION = "0.57.0"
5
+ VERSION = "0.57.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-review
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.57.0
4
+ version: 0.57.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-01 00:00:00.000000000 Z
10
+ date: 2026-10-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ace-support-cli