ace-coworker 0.6.0 → 0.6.2

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: 288508c14f62358d53a0fe59f3a772ea024fa370201285151439e5ab93de88ff
4
- data.tar.gz: 84e997a0876ad657f0caf39502c65e4d553110306f8b9fd63d83e6c7e5623fc8
3
+ metadata.gz: 7420999db530cb82bcb5ec29ec372684ec0f60561b84c230a8e5bb3ac0200843
4
+ data.tar.gz: 7acd12950816f9ec0d9f034ff7f68e7667c5c9eeac94989f8dbd4238a7290c6c
5
5
  SHA512:
6
- metadata.gz: f73a2ccd21437dc1f44525ada59476246c7668abc4f9bfa26ca8356b8505447fd7bfc498a28baa8ad1ced52109f10b3871e53641e85cab4c249f1d0c8ed633b4
7
- data.tar.gz: 4756deff6b2f919b3dfd307e17354605e4e5e5e4449a1a562f014d03045e26f083280efcb69598d49375c114775875a7f3d74e11c233721beabab78cac8a6642
6
+ metadata.gz: b602e3377b4396d3e990fdfb5c3f79cb42b0b82522460039199866afee03e59dfd4cbb41d4069e4a128bfe88aec7b78648985c48ef561095d912f97d77452378
7
+ data.tar.gz: f3aa2e8c21ee006eb2b83326c33deb24f94a39318b509c264dba26ed3520c636981c8ad9e0df5198ef643db51d201d2ac10f4358cabc6432d1fde62d17281da2
data/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.2] - 2026-10-23
11
+
12
+ ### Added
13
+
14
+ - Formal reviewer-owned, exact-head provider approval and read-only approval verification through the forge-neutral review flow; comments do not substitute for approval.
15
+
16
+
17
+ ## [0.6.1] - 2026-10-20
18
+
19
+ ### Fixed
20
+ - Honor `ACE_TOOLCHAIN=installed` for published-gem execution and context propagation without source-runtime fallback; retain default auto development behavior.
21
+
10
22
 
11
23
  ## [0.6.0] - 2026-10-20
12
24
 
data/docs/usage.md CHANGED
@@ -562,3 +562,14 @@ would-be outcome.
562
562
  - Reporting via `ace-hitl` and resume-after-interruption ship with
563
563
  `ace-coworker` 0.4.0+; role presets resolve their `wfi://` instruction
564
564
  sources through `ace-nav` (runtime dependency since 0.5.0).
565
+
566
+
567
+ For commissioned PR work the reviewer publishes formal approval only after the
568
+ accepted review-round verdict and executed tests, using `ace-git pr approve`.
569
+ Its receipt stays with the existing report. The integrator verifies that receipt
570
+ against read-only `ace-git pr approval` and the current candidate before the
571
+ existing integration gate. A comment, local verdict or draft-ready transition
572
+ does not substitute for provider approval. Accounts and credentials come from
573
+ deployment, independently of the role preset; no identity switching or token
574
+ borrowing is performed by coworker. Missing permission or uncertain publication
575
+ blocks successful integration handoff.
@@ -93,7 +93,21 @@ the exact candidate head. Otherwise WITHHOLD the merge and report/escalate.
93
93
  invalidates the review: the gate reports a stale review even when the
94
94
  verdict says accept.
95
95
 
96
- 4. **Merge only a ready candidate.**
96
+ 4. **Consume formal approval, then merge only a ready candidate.**
97
+ - For PR integration, read the reviewer's receipt from the existing report
98
+ and verify current provider evidence without minting another approval:
99
+ `ace-git pr approval PR --expected-head SHA --review-id REVIEW_ID
100
+ --server SERVER --format json` (or the retained `--default-server`). Here
101
+ REVIEW_ID is the numeric provider review ID, not the publication correlation.
102
+ - Require effective `approved` evidence and compare repository, PR, full head,
103
+ reviewer identity, correlation and evidence SHA256 with the accepted
104
+ reviewer report/receipt. The authenticated reviewer must differ from the
105
+ PR author. A stale/dismissed/nonofficial review, comment, unavailable
106
+ evidence or missing account permissions withholds integration. Return to
107
+ the reviewer/overseer; do not issue approval on the reviewer's behalf,
108
+ copy their token or bypass branch protection. This read does not replace
109
+ the dependency/test/current-head gate or authorize a merge by itself.
110
+
97
111
  - Verdict `ready`: perform the project-defined integration with the
98
112
  existing tools (`git` / `ace-git`, scoped to the commissioned paths;
99
113
  never force-push; fetch first and resolve conflicts explicitly).
@@ -162,7 +162,27 @@ product decision is escalated to the overseer instead.
162
162
  outcome from the ace-assign verdict and refuses a forced success on an
163
163
  incomplete assignment - the tool floor, not a courtesy.
164
164
 
165
- 8. **Record and hand off.**
165
+ 8. **Publish formal approval, then hand off.**
166
+ - For a commissioned PR review, only AFTER the review-round verdict is
167
+ `accept`, required executed tests pass, and the accepted report binds the
168
+ current full head, publish through the configured reviewer account:
169
+ `ace-git pr approve PR --expected-head SHA --body-file REVIEW_REPORT
170
+ --correlation REVIEW_ID --server SERVER --format json` (or the retained
171
+ `--default-server` selection). Keep the report and returned receipt in the
172
+ existing task/review/assignment artifacts. Use one stable REVIEW_ID for
173
+ this repository/PR publication; changed report/head needs a new publication.
174
+ - A comment, local acceptance or `pr ready` is not formal approval. Confirm
175
+ the receipt's exact repo/PR/head/reviewer/evidence digest and effective
176
+ approval. Missing permission, unsupported capability or unknown outcome
177
+ blocks successful integration handoff; report the concrete cause via the
178
+ existing HITL/report path. Resume by reconciling the same publication,
179
+ never blindly repeating a POST or switching credentials.
180
+ - Reviewer purpose and provider permissions are independent: deployment
181
+ selects the OS/provider identity. Never borrow worker/admin credentials;
182
+ the PR author cannot approve their own PR. Local-only reviews have no
183
+ remote approval to publish and must not claim one.
184
+
185
+ 8a. **Record and hand off.**
166
186
  - `ace-coworker report` writes the durable record
167
187
  (`<task>/coworker/<assignment>.md`) and delivers the HITL report.
168
188
  - The final report names: the exact candidate head, executed checks,
@@ -209,3 +229,12 @@ and reported. Anything less is an error outcome with the cause named.
209
229
  `.ace-local/review/` to ace-review, `.ace-local/hitl/` to ace-hitl.
210
230
  - Inside the ACE repository itself, invoke every tool as `bin/ace-*` (the
211
231
  binstubs pin the checkout's bundle).
232
+
233
+
234
+ Deployment must provide a dedicated automation reviewer identity and one writer
235
+ per actor/repository/PR before formal publication. Missing evidence of that
236
+ mapping/ownership blocks live use; a role name grants no account permission.
237
+ Do not share that identity with manual clients or another host writer. Preserve
238
+ and report unrelated pending reviews/comments instead of publishing over them.
239
+ Resume only when the provider's path preserves the accepted commit/content;
240
+ unsafe or unsupported pending recovery remains blocked with retained evidence.
@@ -9,6 +9,11 @@ module Ace
9
9
  h = brief.to_h
10
10
  lines = []
11
11
  lines << "Coworker brief"
12
+ lines << " toolchain: #{h.dig('toolchain', 'mode')}"
13
+ if h.dig("toolchain", "mode") == "installed"
14
+ lines << " runtime rule: keep ACE_TOOLCHAIN=installed; use installed ACE commands and packaged generic instructions, never project binstubs or bundle exec"
15
+ lines << " project context: retain domain instructions/tasks/config; source-development launch advice does not select this runtime"
16
+ end
12
17
  lines << " role: #{h['role']}"
13
18
  lines << " instructions: #{h['instructions']}"
14
19
  lines << " instructions src: #{h['instructions_source']}"
@@ -27,6 +27,9 @@ module Ace
27
27
  end
28
28
  end
29
29
 
30
+ if raw.key?("toolchain") && !%w[auto installed].include?(raw["toolchain"])
31
+ errors << "toolchain must be auto or installed"
32
+ end
30
33
  errors.concat(prompt_errors(raw))
31
34
  errors.concat(nested_mapping_errors(raw, "reporting"))
32
35
  errors.concat(nested_mapping_errors(raw, "errors"))
@@ -41,6 +44,7 @@ module Ace
41
44
  # by the loader (IO does not belong in this pure atom).
42
45
  def self.normalize(raw)
43
46
  {
47
+ "toolchain" => raw["toolchain"],
44
48
  "role" => str(raw["role"]),
45
49
  "instructions" => str(raw["instructions"]),
46
50
  "start_prompt" => str(raw["start_prompt"]),
@@ -50,7 +54,7 @@ module Ace
50
54
  "overseer" => str(raw["overseer"]),
51
55
  "reporting" => {"channel" => nested_str(raw, "reporting", "channel").empty? ? "hitl" : nested_str(raw, "reporting", "channel")},
52
56
  "errors" => {"on_failure" => nested_str(raw, "errors", "on_failure").empty? ? "report" : nested_str(raw, "errors", "on_failure")}
53
- }
57
+ }.compact
54
58
  end
55
59
 
56
60
  # Protocol instructions reference an external resource; plain paths
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ace/support/cli/toolchain"
4
+
3
5
  module Ace
4
6
  module Coworker
5
7
  module Models
@@ -17,6 +19,7 @@ module Ace
17
19
 
18
20
  def to_h
19
21
  {
22
+ "toolchain" => Ace::Support::Cli::Toolchain.evidence,
20
23
  "role" => config.role,
21
24
  "instructions" => config.instructions,
22
25
  "instructions_source" => instructions_source,
@@ -6,10 +6,11 @@ module Ace
6
6
  # Immutable validated coworker run configuration.
7
7
  class Config
8
8
  attr_reader :role, :instructions, :start_prompt, :start_prompt_file, :task, :assignment,
9
- :overseer, :reporting_channel, :on_failure, :source_path
9
+ :overseer, :reporting_channel, :on_failure, :source_path, :toolchain
10
10
 
11
11
  def initialize(role:, instructions:, start_prompt:, task:, assignment:, overseer:,
12
- start_prompt_file: nil, reporting_channel: "hitl", on_failure: "report", source_path: nil)
12
+ start_prompt_file: nil, reporting_channel: "hitl", on_failure: "report", source_path: nil, toolchain: nil)
13
+ @toolchain = toolchain&.freeze
13
14
  @role = role.freeze
14
15
  @instructions = instructions.freeze
15
16
  @start_prompt = start_prompt.freeze
@@ -32,11 +33,12 @@ module Ace
32
33
  start_prompt: start_prompt, start_prompt_file: start_prompt_file,
33
34
  task: task, assignment: assignment, overseer: overseer,
34
35
  reporting_channel: reporting_channel, on_failure: on_failure,
35
- source_path: source_path)
36
+ source_path: source_path, toolchain: toolchain)
36
37
  end
37
38
 
38
39
  def to_h
39
40
  {
41
+ "toolchain" => toolchain,
40
42
  "role" => role,
41
43
  "instructions" => instructions,
42
44
  "start_prompt" => start_prompt,
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ace/support/cli/toolchain"
4
+
3
5
  require "open3"
4
6
  require "json"
5
7
 
@@ -9,7 +11,7 @@ module Ace
9
11
  # Probes assignment truth via `ace-assign status --assignment <id> --format json`
10
12
  # with an injectable runner (ADR-031: argv array + explicit cwd).
11
13
  class AssignmentProbe
12
- DEFAULT_RUNNER = ->(argv, cwd) { Open3.capture3(*argv, chdir: cwd) }
14
+ DEFAULT_RUNNER = ->(argv, cwd) { Open3.capture3(Ace::Support::Cli::Toolchain.child_environment, *argv, chdir: cwd) }
13
15
 
14
16
  def initialize(project_root:, runner: DEFAULT_RUNNER)
15
17
  @project_root = project_root
@@ -48,6 +50,8 @@ module Ace
48
50
  # Prefer the repo binstub when present (agent shells do not activate
49
51
  # mise, so bare ace-* may resolve to stale installed gems).
50
52
  def assign_binary
53
+ return Ace::Support::Cli::Toolchain.executable("ace-assign") if Ace::Support::Cli::Toolchain.installed?
54
+
51
55
  binstub = File.join(@project_root, "bin", "ace-assign")
52
56
  File.executable?(binstub) ? binstub : "ace-assign"
53
57
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ace/support/cli/toolchain"
4
+
3
5
  require "yaml"
4
6
 
5
7
  module Ace
@@ -46,6 +48,10 @@ module Ace
46
48
  raise Error, "invalid config #{relativize(target)}: #{errors.join('; ')}"
47
49
  end
48
50
 
51
+ if raw["toolchain"] == "installed" && !Ace::Support::Cli::Toolchain.installed?
52
+ raise Error, "this coworker run requires ACE_TOOLCHAIN=installed; restart through the installed executable"
53
+ end
54
+ Ace::Support::Cli::Toolchain.verify_loaded! if raw["toolchain"] == "installed"
49
55
  build_config(Atoms::ConfigSchema.normalize(raw), target)
50
56
  end
51
57
 
@@ -86,7 +92,7 @@ module Ace
86
92
  overseer: normalized["overseer"],
87
93
  reporting_channel: normalized.dig("reporting", "channel"),
88
94
  on_failure: normalized.dig("errors", "on_failure"),
89
- source_path: target
95
+ source_path: target, toolchain: normalized["toolchain"]
90
96
  )
91
97
  end
92
98
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ace/support/cli/toolchain"
4
+
3
5
  require "open3"
4
6
 
5
7
  module Ace
@@ -9,7 +11,7 @@ module Ace
9
11
  # argv contract, ADR-031; injectable runner). ace-hitl owns its files;
10
12
  # this molecule never writes HITL state directly.
11
13
  class HitlReporter
12
- DEFAULT_RUNNER = ->(argv, cwd) { Open3.capture3(*argv, chdir: cwd) }
14
+ DEFAULT_RUNNER = ->(argv, cwd) { Open3.capture3(Ace::Support::Cli::Toolchain.child_environment, *argv, chdir: cwd) }
13
15
 
14
16
  Failure = Struct.new(:stage, :message, keyword_init: true)
15
17
 
@@ -118,6 +120,8 @@ module Ace
118
120
  # Prefer the repo binstub when present (agent shells do not activate
119
121
  # mise, so bare ace-* may resolve to stale installed gems).
120
122
  def hitl_binary
123
+ return Ace::Support::Cli::Toolchain.executable("ace-hitl") if Ace::Support::Cli::Toolchain.installed?
124
+
121
125
  binstub = File.join(@project_root, "bin", "ace-hitl")
122
126
  File.executable?(binstub) ? binstub : "ace-hitl"
123
127
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ace/support/cli/toolchain"
4
+
3
5
  require "open3"
4
6
 
5
7
  module Ace
@@ -9,7 +11,7 @@ module Ace
9
11
  # project root, or a protocol URI (wfi://, prompt:// tmpl://) resolved
10
12
  # through ace-nav.
11
13
  class InstructionResolver
12
- DEFAULT_NAV_RUNNER = ->(argv, cwd) { Open3.capture3(*argv, chdir: cwd) }
14
+ DEFAULT_NAV_RUNNER = ->(argv, cwd) { Open3.capture3(Ace::Support::Cli::Toolchain.child_environment, *argv, chdir: cwd) }
13
15
 
14
16
  # The ace-nav CLI itself is missing (ENOENT on exec): the resolver
15
17
  # never ran, so no verdict exists. Prepare treats this distinctly
@@ -72,6 +74,8 @@ module Ace
72
74
  # executes the repository-controlled binstub — PATH only
73
75
  # (trust-posture parity with ace-lab Verify#nav_binary).
74
76
  def nav_binary
77
+ return Ace::Support::Cli::Toolchain.executable("ace-nav") if Ace::Support::Cli::Toolchain.installed?
78
+
75
79
  binstub = File.join(@project_root, "bin", "ace-nav")
76
80
  use_binstub = !Process.uid.zero? && File.executable?(binstub)
77
81
  use_binstub ? binstub : "ace-nav"
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ace/support/cli/toolchain"
4
+
3
5
  require "open3"
4
6
  require "json"
5
7
  require "yaml"
@@ -16,7 +18,7 @@ module Ace
16
18
  # `ace-assign list --all --format json` (subprocess argv contract
17
19
  # with explicit cwd, ADR-031).
18
20
  class WorktreeExecutorProbe
19
- DEFAULT_RUNNER = ->(argv, cwd) { Open3.capture3(*argv, chdir: cwd) }
21
+ DEFAULT_RUNNER = ->(argv, cwd) { Open3.capture3(Ace::Support::Cli::Toolchain.child_environment, *argv, chdir: cwd) }
20
22
  # Coworker config cascade files that may carry a `config_path`
21
23
  # override for the probed worktree (project `.ace` and user tiers;
22
24
  # the gem-defaults tier equals the loader's built-in default).
@@ -130,6 +132,8 @@ module Ace
130
132
  # mise, so bare ace-* may resolve to stale installed gems); the
131
133
  # subprocess still runs with cwd = the probed worktree.
132
134
  def assign_binary
135
+ return Ace::Support::Cli::Toolchain.executable("ace-assign") if Ace::Support::Cli::Toolchain.installed?
136
+
133
137
  binstub = File.join(@project_root, "bin", "ace-assign")
134
138
  File.executable?(binstub) ? binstub : "ace-assign"
135
139
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ace/support/cli/toolchain"
4
+
3
5
  require "fileutils"
4
6
  require "tmpdir"
5
7
 
@@ -103,6 +105,7 @@ module Ace
103
105
  "assignment" => assignment,
104
106
  "overseer" => overseer
105
107
  }
108
+ config["toolchain"] = "installed" if Ace::Support::Cli::Toolchain.installed?
106
109
  config["reporting"] = {"channel" => preset["reporting"]} unless preset["reporting"].to_s.empty?
107
110
  config["errors"] = {"on_failure" => preset["errors"]} unless preset["errors"].to_s.empty?
108
111
  config
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Coworker
5
- VERSION = "0.6.0"
5
+ VERSION = "0.6.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-coworker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-20 00:00:00.000000000 Z
10
+ date: 2026-10-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ace-support-cli
@@ -16,6 +16,9 @@ dependencies:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
18
  version: '0.6'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 0.6.10
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -23,6 +26,9 @@ dependencies:
23
26
  - - "~>"
24
27
  - !ruby/object:Gem::Version
25
28
  version: '0.6'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 0.6.10
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: ace-support-core
28
34
  requirement: !ruby/object:Gem::Requirement
@@ -72,6 +78,9 @@ dependencies:
72
78
  - - "~>"
73
79
  - !ruby/object:Gem::Version
74
80
  version: '0.28'
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 0.28.8
75
84
  type: :runtime
76
85
  prerelease: false
77
86
  version_requirements: !ruby/object:Gem::Requirement
@@ -79,6 +88,9 @@ dependencies:
79
88
  - - "~>"
80
89
  - !ruby/object:Gem::Version
81
90
  version: '0.28'
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: 0.28.8
82
94
  - !ruby/object:Gem::Dependency
83
95
  name: ace-task
84
96
  requirement: !ruby/object:Gem::Requirement
@@ -100,6 +112,9 @@ dependencies:
100
112
  - - "~>"
101
113
  - !ruby/object:Gem::Version
102
114
  version: '0.72'
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 0.73.1
103
118
  type: :runtime
104
119
  prerelease: false
105
120
  version_requirements: !ruby/object:Gem::Requirement
@@ -107,6 +122,9 @@ dependencies:
107
122
  - - "~>"
108
123
  - !ruby/object:Gem::Version
109
124
  version: '0.72'
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: 0.73.1
110
128
  - !ruby/object:Gem::Dependency
111
129
  name: ace-hitl
112
130
  requirement: !ruby/object:Gem::Requirement
@@ -141,14 +159,14 @@ dependencies:
141
159
  requirements:
142
160
  - - "~>"
143
161
  - !ruby/object:Gem::Version
144
- version: '0.62'
162
+ version: 0.62.2
145
163
  type: :runtime
146
164
  prerelease: false
147
165
  version_requirements: !ruby/object:Gem::Requirement
148
166
  requirements:
149
167
  - - "~>"
150
168
  - !ruby/object:Gem::Version
151
- version: '0.62'
169
+ version: 0.62.2
152
170
  - !ruby/object:Gem::Dependency
153
171
  name: ace-support-test-helpers
154
172
  requirement: !ruby/object:Gem::Requirement