ace-git-forgejo 0.7.0 → 0.9.0

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: ec7d89b79d4c95330a4610f04ce163e8dcd402f3ff72bb468b27dac082d086ea
4
- data.tar.gz: b9a7a5d29fa5fbd4b559ef532018d18e07d9a3b8de20dbd5860938408158d6e9
3
+ metadata.gz: 2f9063026140a927fbd346c52d1bcad410b23dc0fbaa8e8d69eae206688afd72
4
+ data.tar.gz: d2731b929dc9c5adde5d1a21259bc071ad54771424be3d5f3a7e2465891a3eb7
5
5
  SHA512:
6
- metadata.gz: f7a145c24c9720d4748f32cc921f42983f2dc20c56dc0c007de0536972b4b318bba6d35c52b2b360ece6846b24cf4cd6d5b819493a7471d14e13bd59e08dc060
7
- data.tar.gz: e308d391be1d1057de4176e58a698cfef161ed9474e46bb29dd1ab0a5ef83efb13af1f51b8d4bccb53d8322c0ffce89db71438f777762f49a6cadb07bab9880a
6
+ metadata.gz: b69e571f2eb13eff2a71f082d8630a5610fc4827c7ea2fe6e126cef0a04610257e9bc494053ca615b8fccf88ba49b956e51dbf16768e9c2dc990a55c6d9fde7d
7
+ data.tar.gz: 2bda0f62a97a58ef95c67495dabb2085401be57d3002f5cfb97fd5f7a6a92b173f8aebc5c5cde5519ca0f64cc03c49de307df3021f70a7fa6988e4a675589377
data/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.0] - 2026-11-10
11
+
12
+ ### Added
13
+
14
+ - Observe repository-bound open issues including body and label changes. Support formal approval with repository-scoped credentials and verify the returned reviewer identity.
15
+
16
+ ## [0.8.0] - 2026-10-23
17
+
18
+ ### Added
19
+
20
+ - Formal reviewer-owned, exact-head provider approval and read-only approval verification through the forge-neutral review flow; comments do not substitute for approval.
21
+
22
+
23
+ ### Fixed
24
+
25
+ - Publish formal approval directly at explicit commit_id instead of rebinding pending-submit; retain safe comment-free owned pending recovery, refuse unrelated pending, ignore request/team rows, and require the verified formal-review API floor.
26
+
10
27
  ## [0.7.0] - 2026-10-16
11
28
 
12
29
  ### Added
@@ -25,6 +25,10 @@ module Ace
25
25
  request(:get, "issues/#{number}")
26
26
  end
27
27
 
28
+ def open_issues
29
+ paginate("issues?state=open", fail_on_repeated_page: true)
30
+ end
31
+
28
32
  def comments(number)
29
33
  paginate("issues/#{number}/comments")
30
34
  end
@@ -78,22 +82,28 @@ module Ace
78
82
 
79
83
  private
80
84
 
81
- def paginate(suffix)
85
+ def paginate(suffix, fail_on_repeated_page: false)
82
86
  page = 1
83
87
  entries = []
84
88
  seen = []
85
89
  loop do
86
- batch = request(:get, "#{suffix}?page=#{page}&limit=50")
90
+ separator = suffix.include?("?") ? "&" : "?"
91
+ batch = request(:get, "#{suffix}#{separator}page=#{page}&limit=50")
87
92
  unless batch.is_a?(Array)
88
93
  raise Ace::Git::ProviderMalformedOutputError, "Forgejo #{suffix} did not return a list"
89
94
  end
90
- before = entries.length
95
+ unless batch.all? { |entry| entry.is_a?(Hash) && entry["id"].is_a?(Integer) && entry["id"].positive? }
96
+ raise Ace::Git::ProviderMalformedOutputError, "Forgejo #{suffix} contains an invalid item identity"
97
+ end
91
98
  # Stop when a page adds no new items (unique ids): instances
92
99
  # may cap pages below the requested limit, so a short page alone
93
100
  # does not prove the end of the collection.
94
101
  fresh = batch.reject { |item| seen.include?(item["id"]) }
95
102
  entries.concat(fresh)
96
103
  fresh.each { |item| seen << item["id"] }
104
+ if fail_on_repeated_page && !batch.empty? && fresh.empty?
105
+ raise Ace::Git::ProviderMalformedOutputError, "Forgejo #{suffix} repeated a page; completeness cannot be proven"
106
+ end
97
107
  break if fresh.empty?
98
108
 
99
109
  page += 1
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require "ace/git/providers/formal_approval"
4
5
  require "uri"
5
6
  require "ace/git/atoms/server_url"
6
7
  require_relative "cli_executor"
@@ -118,6 +119,39 @@ module Ace
118
119
  .first(limit)
119
120
  end
120
121
 
122
+ # Open issue intake uses the selected Forgejo repository, and never
123
+ # substitutes pull requests or an unavailable listing for an empty one.
124
+ # @return [Array<ProviderIssue>] open issues, newest first
125
+ # @raise [Ace::Git::ProviderMalformedOutputError] for invalid evidence
126
+ # @raise [Ace::Git::ProviderIdentityMismatchError] for foreign evidence
127
+ def recent_issues(limit:)
128
+ limit = Integer(limit)
129
+ return [] if limit <= 0
130
+
131
+ issue_api.open_issues.filter_map do |entry|
132
+ next if entry["pull_request"]
133
+
134
+ number = entry["number"]
135
+ unless number.is_a?(Integer) && number.positive? && entry["title"].is_a?(String) &&
136
+ !entry["title"].strip.empty? && entry["state"] == "open"
137
+ raise Ace::Git::ProviderMalformedOutputError, "Forgejo issue listing contains an invalid issue"
138
+ end
139
+ unless entry["html_url"] == issue_url(number)
140
+ raise Ace::Git::ProviderIdentityMismatchError, "Forgejo issue listing contains foreign issue ##{number}"
141
+ end
142
+ unless entry["user"].nil? || entry["user"].is_a?(Hash)
143
+ raise Ace::Git::ProviderMalformedOutputError, "Forgejo issue listing contains an invalid author"
144
+ end
145
+ unless (entry["body"].nil? || entry["body"].is_a?(String)) &&
146
+ (entry["labels"].nil? || entry["labels"].is_a?(Array) && entry["labels"].all? { |label| label.is_a?(Hash) && label["name"].is_a?(String) })
147
+ raise Ace::Git::ProviderMalformedOutputError, "Forgejo issue listing contains invalid body or labels"
148
+ end
149
+ Ace::Git::ProviderIssue.new(server_name: server.name, number: number, title: entry["title"],
150
+ state: :open, author: entry.dig("user", "login"), url: entry["html_url"],
151
+ body: entry["body"], labels: Array(entry["labels"]).map { |label| label.fetch("name") })
152
+ end.sort_by { |issue| -issue.number }.first(limit)
153
+ end
154
+
121
155
  # @return [ProviderIssue] normalized issue evidence
122
156
  def issue(number:)
123
157
  view = fj(:issue_view, request_number!(number))
@@ -340,6 +374,14 @@ module Ace
340
374
  raise Ace::Git::ProviderMalformedOutputError, "Malformed Forgejo repository file content: #{e.message}"
341
375
  end
342
376
 
377
+ def approve_pull_request(number:, expected_head:, body:, correlation:)
378
+ formal_approval.approve(number: number, expected_head: expected_head, body: body, correlation: correlation)
379
+ end
380
+
381
+ def pull_request_approval(number:, expected_head:, review_id:)
382
+ formal_approval.read(number: number, expected_head: expected_head, review_id: review_id)
383
+ end
384
+
343
385
  def create_pull_request_comment(number:, expected_head:, body:, correlation:)
344
386
  number = request_number!(number)
345
387
  pr = verify_expected_head!(pull_request(number: number), expected_head)
@@ -914,6 +956,37 @@ module Ace
914
956
  "Cannot post review comment to PR ##{pr.number} in #{pr.state} state"
915
957
  end
916
958
 
959
+ def formal_approval
960
+ pr_api.ensure_formal_review_supported!
961
+ request = lambda do |method, path, payload|
962
+ path = "/api/v1/user" if path == :actor
963
+ payload = payload.merge("event" => "APPROVED") if method == :post
964
+ review_http.request(method, path, body: payload)
965
+ end
966
+ Ace::Git::Providers::FormalApproval.new(provider: self, request: request,
967
+ list: method(:formal_review_rows), official: true, pending_reuse: true,
968
+ expected_reviewer: leased_reviewer_account)
969
+ end
970
+
971
+ # Repository-scoped leases cannot call /api/v1/user. The issuer's
972
+ # account is an expectation; the forge review supplies the identity.
973
+ def leased_reviewer_account
974
+ return unless ENV["ACE_LAB_FORGEJO_ORIGIN"] == RepositoryBinding::Target.resolve(server.url).host_url
975
+
976
+ account = ENV["ACE_LAB_FORGEJO_ACCOUNT"]
977
+ return account if account&.match?(/\A[a-zA-Z0-9_.-]+\z/)
978
+
979
+ raise Ace::Git::ProviderAuthenticationError, "Forgejo lease has no valid reviewer account"
980
+ end
981
+
982
+ # Forgejo lists review requests alongside actual reviews. User and
983
+ # team requests have no reviewed commit; they are not approval evidence.
984
+ def formal_review_rows(path)
985
+ review_http.paginate(path).reject do |row|
986
+ row.is_a?(Hash) && row["state"] == "REQUEST_REVIEW"
987
+ end
988
+ end
989
+
917
990
  def review_http
918
991
  @review_http ||= HttpClient.new(server: server, timeout: timeout, runner: runner)
919
992
  end
@@ -70,9 +70,21 @@ module Ace
70
70
  "Forgejo server reports #{version}; PR delivery requires #{MINIMUM_SERVER_VERSION} " \
71
71
  "(server-enforced expected-head merge is not documented below it)"
72
72
  end
73
+ @server_version = version
73
74
  @version_supported = true
74
75
  end
75
76
 
77
+ # Formal create-with-event preserves explicit commit_id in the
78
+ # inspected Forgejo v11.0.0 and v16.0.5 router/service/model sources.
79
+ # Pending submission does not: it rebinds to current HEAD and is unused.
80
+ def ensure_formal_review_supported!
81
+ ensure_version_supported!
82
+ unless @server_version >= Gem::Version.new("11.0.0")
83
+ raise Ace::Git::ProviderUnsupportedCapabilityError,
84
+ "Formal exact-commit approval requires Forgejo 11.0.0 or newer"
85
+ end
86
+ end
87
+
76
88
  # @return [Hash] the raw validated pull request payload
77
89
  # @raise [Ace::Git::ProviderObjectNotFoundError] when absent
78
90
  # @raise [Ace::Git::ProviderMalformedOutputError] on schema violation
@@ -3,7 +3,7 @@
3
3
  module Ace
4
4
  module Git
5
5
  module Forgejo
6
- VERSION = "0.7.0"
6
+ VERSION = "0.9.0"
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-git-forgejo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-16 00:00:00.000000000 Z
10
+ date: 2026-11-10 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ace-git
@@ -15,14 +15,20 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.30'
18
+ version: '0.31'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 0.33.0
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
26
  - - "~>"
24
27
  - !ruby/object:Gem::Version
25
- version: '0.30'
28
+ version: '0.31'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 0.33.0
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: faraday
28
34
  requirement: !ruby/object:Gem::Requirement