ace-git 0.31.0 → 0.33.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: 83d5b624eab98d754e4bb770fdd1a80e00237d063bc07ba2b412e31cf0b4fa87
4
- data.tar.gz: 1e8a556aedb3f9ba844ebe46a7b3c64adea58b979842bf6df7835742057c20c1
3
+ metadata.gz: 183166f8ce864a5503f765212e4a0a89e26ec22f03dca7ab8a918c9499ec064a
4
+ data.tar.gz: 5c3eaa10c29b1873bf504cecaff12ac77806740f1bf1ee1e3c8372081c9d18e4
5
5
  SHA512:
6
- metadata.gz: 8c33109eb792dea197dc9e29a9f63839ed10d53c95aa7e1817d22cbbe2cbecc51123f8fe90ad420a2e908b35a6aa1bbaef35612340bcd667ad21b5736dbdad49
7
- data.tar.gz: a2b337d9084ff29b01cd0834e2e9e9a3ef36299d0c0704bdad61480d844cd1a0165f1d4b9c482c5acf0feb29d0dc6aaa715e992dca1e2fa50d6abeb736e234f7
6
+ metadata.gz: 21397a89baadadc17c0c18ce73c3fb558a6195133381e6f26df5e4ab897e025cff8aaa563b2cb6169400d8a5a7f43c2ff7c8549d6af261389d0690ba40c35fef
7
+ data.tar.gz: 2067ddf2e8871231ed33e0392aa922a429114b68c25dd9c3fe914c3d88d0df185ecc3ebebb2b4f9a1433a9a882c9c6aa147978616a2c03c31a2967508fd3e790
data/CHANGELOG.md CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.33.0] - 2026-11-10
11
+
12
+ ### Added
13
+
14
+ - Include PR authors and semantic issue bodies in provider evidence. Verify scoped formal reviewer identity and exact HEAD; reject oversized or invalid UTF-8 PR bodies instead of truncating them.
15
+ ## [0.32.0] - 2026-10-30
16
+
17
+ ### Added
18
+
19
+ - `wfi://git/mirror-sync` (task 8xs.t.pu0.d): the repeatable host
20
+ mirror-sync operation — explicit source/target/ref, both full SHAs read
21
+ before acting, identity no-op, fast-forward only (`--force-with-lease`
22
+ with the read SHA), divergence escalated for reviewed reconciliation
23
+ (never reset/force/ref deletion), reversed-direction detection in the
24
+ plan, and a mutation-free dry-run.
25
+
10
26
  ## [0.31.0] - 2026-10-23
11
27
 
12
28
  ### Added
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: git-mirror-sync
3
+ description: Repeatable host mirror-sync operation — explicit source/target/ref, both full SHAs read before acting, identity no-op, fast-forward only, divergence escalated for reviewed reconciliation
4
+ allowed-tools: Bash, Read
5
+ doc-type: workflow
6
+ title: Host Mirror Sync Workflow
7
+ purpose: Bind the host operator's forge-mirror sync as a repeatable, evidenced operation: never destructive, always checkable, with the actual source and target SHAs in the result.
8
+ ace-docs:
9
+ last-updated: '2026-10-30'
10
+ last-checked: '2026-10-30'
11
+ ---
12
+ # Host Mirror Sync Workflow
13
+
14
+ ## Goal
15
+
16
+ Synchronize an explicitly named target ref from an explicitly named source
17
+ ref (task 8xs.t.pu0.d — for this family: source `fg` Forgejo `cs3b/ace`,
18
+ target `origin` GitHub `cs3b/ace`) as a REPEATABLE host operation with a
19
+ checkable result. This is an ordinary Git workflow — no new script, no
20
+ hardcoded lab topology, no tool change; if a capability is missing it
21
+ belongs in ace-git's adapters.
22
+
23
+ ## Instructions
24
+
25
+ Run the loaded workflow as the source of truth and execute it end-to-end
26
+ instead of only summarizing it.
27
+
28
+ ### 1) Name the operation and read BOTH full SHAs first
29
+
30
+ Before ANY action, read and record the full SHAs of the exact source and
31
+ target refs. An operation without both SHAs is not runnable:
32
+
33
+ ```bash
34
+ git ls-remote fg refs/heads/main # source SHA (record it)
35
+ git ls-remote origin refs/heads/main # target SHA (record it)
36
+ ```
37
+
38
+ ### 2) Classify
39
+
40
+ - **Identity** (source SHA == target SHA): the sync is a NO-OP. Report both
41
+ SHAs and the result `identity`; do not touch anything.
42
+ - **Fast-forward** (target SHA is an ancestor of source): proceed.
43
+ - **Divergence** (neither of the above): STOP. Divergence needs its own
44
+ reviewed reconciliation decision — NEVER resolve it here with a reset,
45
+ force push, or ref deletion. Report both SHAs and the divergence; a
46
+ historical SHA (e.g. a recorded closeout base like `af68f8fc1…`) is INPUT
47
+ EVIDENCE for the review, never a SHA to impose unconditionally on the
48
+ live target.
49
+ - **Reversed direction** (the plan shows the target ahead of the source):
50
+ report it explicitly — a dry-run that names source and target swapped is
51
+ a caught error, not a sync.
52
+
53
+ ### 3) Execute (fast-forward only)
54
+
55
+ ```bash
56
+ git fetch fg main
57
+ git push origin <source-SHA>:refs/heads/main --force-with-lease=refs/heads/main:<target-SHA>
58
+ ```
59
+
60
+ `--force-with-lease` with the READ target SHA is the guard: if anything
61
+ moved the target between the read and the push, the push is REFUSED — the
62
+ operation restarts from step 1. Plain `--force` is forbidden.
63
+
64
+ ### 4) Verify and report
65
+
66
+ Re-read the target SHA; the result names the ref, the before/after SHAs of
67
+ BOTH sides, and the verification (`git ls-remote origin` equals the source
68
+ SHA). Repetition after success reports `identity` — the repeatable
69
+ operation is idempotent at the evidence level.
70
+
71
+ ### 5) Dry-run
72
+
73
+ The same classification with no mutation: report source/target/ref, both
74
+ SHAs, the planned action (no-op / fast-forward / divergence-blocked /
75
+ reversed), and NOTHING else changed.
76
+
77
+ ## Boundaries
78
+
79
+ - Never delete refs, never reset, never `--force` without a lease.
80
+ - Salvage branches, open HITL cases, and active resources on either side
81
+ are untouched by a mirror sync; an active resource conflicting with the
82
+ plan is reported as preserved with its reason.
83
+ - Closing a single Herdr pane remains a separate follow-up — never close a
84
+ whole tab as a substitute.
@@ -121,6 +121,7 @@ module Ace
121
121
  number: pr.number,
122
122
  url: pr.url,
123
123
  title: pr.title,
124
+ author: pr.author,
124
125
  state: pr.state,
125
126
  draft: pr.draft,
126
127
  head: pr.head_sha,
@@ -363,7 +363,14 @@ module Ace
363
363
  raise ArgumentError, "--body-file is required" unless path
364
364
  raise ArgumentError, "Body file not found: #{path}" unless File.file?(path)
365
365
 
366
- File.read(path, limit)
366
+ body = File.binread(path, limit) || +""
367
+ if limit && body.bytesize >= limit
368
+ raise ArgumentError, "Body file exceeds #{limit - 1} bytes: #{path}"
369
+ end
370
+ body.force_encoding(Encoding::UTF_8)
371
+ raise ArgumentError, "Body file must contain valid UTF-8: #{path}" unless body.valid_encoding?
372
+
373
+ body
367
374
  rescue SystemCallError => e
368
375
  raise ArgumentError, "Cannot read body file #{path}: #{e.message}"
369
376
  end
@@ -16,7 +16,11 @@ module Ace
16
16
  :head_repository_url, :base_repository_url, :merge_commit_sha
17
17
  )
18
18
 
19
- ProviderIssue = Data.define(:server_name, :number, :title, :state, :author, :url, :labels)
19
+ ProviderIssue = Data.define(:server_name, :number, :title, :state, :author, :url, :labels, :body) do
20
+ def initialize(body: nil, **fields)
21
+ super(body: body, **fields)
22
+ end
23
+ end
20
24
 
21
25
  # Normalized check/CI evidence for one check run.
22
26
  ProviderCheck = Data.define(:server_name, :name, :state, :conclusion, :url)
@@ -16,9 +16,10 @@ module Ace
16
16
  CORRELATION = /\A[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}\z/
17
17
  MARKER = /\n\n<!-- ace-approval:([a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}) sha256:([0-9a-f]{64}) -->\z/
18
18
 
19
- def initialize(provider:, request:, list:, official: false, pending_reuse: false)
19
+ def initialize(provider:, request:, list:, official: false, pending_reuse: false, expected_reviewer: nil)
20
20
  @provider, @request, @list, @official = provider, request, list, official
21
21
  @pending_reuse = pending_reuse
22
+ @expected_reviewer = expected_reviewer
22
23
  end
23
24
 
24
25
  def approve(number:, expected_head:, body:, correlation:)
@@ -44,7 +45,7 @@ module Ace
44
45
  end
45
46
  lock.flock(File::LOCK_EX)
46
47
  pr = current!(number, expected_head)
47
- actor = identity!(@request.call(:get, :actor, nil))
48
+ actor = @expected_reviewer ? [nil, @expected_reviewer] : identity!(@request.call(:get, :actor, nil))
48
49
  deny_author!(pr, actor)
49
50
  rows = reviews!(number)
50
51
  matches = rows.select { |row| row.fetch("body").include?("<!-- ace-approval:#{correlation} ") }
@@ -52,14 +53,18 @@ module Ace
52
53
  existing = matches.first
53
54
  row!(existing) if existing
54
55
  if existing && (existing["body"] != sent_body || existing["commit_id"] != expected_head ||
55
- identity!(existing["user"]) != actor)
56
+ !matches_actor?(actor, identity!(existing["user"])))
56
57
  raise ProviderConflictingMatchesError, "Approval correlation is already bound to different evidence"
57
58
  end
58
59
  # A provider may reuse its current pending object on direct create.
59
60
  # Only an exact known, comment-free pending review is resumable, and
60
61
  # only when the adapter proves explicit commit preservation on reuse.
61
62
  owned_pending = rows.select do |row|
62
- row["state"] == "PENDING" && row["user"].is_a?(Hash) && row["user"]["id"] == actor.first
63
+ next false unless row["state"] == "PENDING"
64
+
65
+ # A repository-scoped Forgejo token can only see its own
66
+ # pending reviews. Preserve every visible unrelated pending.
67
+ @expected_reviewer || (row["user"].is_a?(Hash) && row["user"]["id"] == actor.first)
63
68
  end
64
69
  unrelated = owned_pending.any? { |row| !existing || row["id"] != existing["id"] }
65
70
  if unrelated
@@ -140,6 +145,10 @@ module Ace
140
145
  [user["id"], user["login"]]
141
146
  end
142
147
 
148
+ def matches_actor?(expected, actual)
149
+ expected.first ? expected == actual : expected.last.casecmp?(actual.last)
150
+ end
151
+
143
152
  def deny_author!(pr, actor)
144
153
  unless pr.author.is_a?(String) && !pr.author.empty?
145
154
  raise ProviderMalformedOutputError, "PR author is unverified"
@@ -199,7 +208,7 @@ module Ace
199
208
  row["state"] == "APPROVED" && row["commit_id"] == expected_head &&
200
209
  !row["dismissed"] && !row["stale"] && (!@official || row["official"] == true) &&
201
210
  (!latest || latest["id"] == row["id"]) && (!expected_body || row["body"] == expected_body) &&
202
- (!actor || actor == actual_actor) && row["html_url"].is_a?(String) &&
211
+ (!actor || matches_actor?(actor, actual_actor)) && row["html_url"].is_a?(String) &&
203
212
  row["html_url"].start_with?("#{pr.url}#")
204
213
  raise ProviderIdentityMismatchError, "Review is not the effective exact-head formal approval"
205
214
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Git
5
- VERSION = "0.31.0"
5
+ VERSION = "0.33.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-git
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.31.0
4
+ version: 0.33.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-23 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-support-config
@@ -133,6 +133,7 @@ files:
133
133
  - handbook/templates/pr/bugfix.template.md
134
134
  - handbook/templates/pr/default.template.md
135
135
  - handbook/templates/pr/feature.template.md
136
+ - handbook/workflow-instructions/git/mirror-sync.wf.md
136
137
  - handbook/workflow-instructions/git/pr/create.wf.md
137
138
  - handbook/workflow-instructions/git/pr/update.wf.md
138
139
  - handbook/workflow-instructions/git/rebase.wf.md