ace-hitl 0.23.2 → 0.24.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: 973228979e65843573a7dc67bc91566a22478ca762f3751babdbd269e0960ca9
4
- data.tar.gz: c4b09f84c60c2bfb225294d56c4bfa4abded7ced7ecc75fe830ecc8987e239ee
3
+ metadata.gz: 066770bf07c22e97f6d1571d8d7697a9681df2872102b9454e26dc934d494162
4
+ data.tar.gz: 398b6537945687a92f34a0284d9e687f7b41cfea777352c9b7046a07ef45cd9a
5
5
  SHA512:
6
- metadata.gz: f08442f7582db2b088cbaa5f0af24d9d81bc7caccdf5e2bdfc9de5feb6a9ee9f5125f541f02e9585f4921933fa08a712fe47e80e8c668dd89330084654e13984
7
- data.tar.gz: c3ad787393cc3c045efda84016b9e2783fa8fb5f3696e4a9da9e072840dfb99e12f706c79bafc6b655f90f4ccd35f661d0f3b05932a96d00d17ed563a63b27c1
6
+ metadata.gz: cb37ecff31019de3e8007964a8631e9f8e33c7dd5ba4be5505b84a4172c030e7eadf4580e8e70b962b8d26942a4b1a6221ed9a7641adb8726e4dce408b11c734
7
+ data.tar.gz: a7ecb1759adaa8e6ae5dae068eb556bc0f094cd905a11b12f30f1f1bd6012ea61f68487eeeddf48298ee92de212b7b5c57c893e371a2234b20b9ca18f1475035
data/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.24.0] - 2026-10-23
11
+
12
+ ### Added
13
+
14
+ - Add read-only semantic event observations with configured-owner filtering; exclude delivery metadata and unrelated recipients.
15
+
10
16
  ## [0.23.2] - 2026-10-20
11
17
 
12
18
  ### Fixed
data/README.md CHANGED
@@ -78,3 +78,15 @@ bin/ace-test ace-hitl all
78
78
  `ace-hitl` owns HITL event semantics and the markdown contract.
79
79
  `ace-support-items` remains generic support infrastructure and does not absorb
80
80
  HITL-specific behaviour.
81
+
82
+ ### Read semantic event revisions
83
+
84
+ `ace-hitl observe --scope current --limit 500` prints one JSON object with schema
85
+ `ace.hitl.observation/v1` and a bounded `events` list. `--scope all` includes linked
86
+ worktrees of this repository. Each row contains a hashed canonical file identity,
87
+ event ID, semantic revision hash, type/status and task/assignment references.
88
+ It emits no private message or answer text. It reads active (non-special-folder) canonical events and
89
+ does not import transports, acknowledge, archive, resume or mutate events.
90
+ Delivery/poll timestamps and filesystem mtime are excluded from revisions;
91
+ content, answers and report/status changes are included. Exceeding the configured
92
+ limit refuses the entire projection rather than silently truncating it.
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ require "ace/support/cli"
3
+ require_relative "../../molecules/event_observation"
4
+ module Ace
5
+ module Hitl
6
+ module CLI
7
+ module Commands
8
+ class Observe < Ace::Support::Cli::Command
9
+ desc "Read bounded semantic event revisions without private message text"
10
+ option :scope, type: :string, default: "current", desc: "current or all linked worktrees"
11
+ option :owner, type: :string, desc: "Configured recipient owning incoming events"
12
+ option :limit, type: :integer, default: 500, desc: "Maximum event count (1..2000)"
13
+ def call(**options)
14
+ scope = options[:scope] || "current"
15
+ raise Ace::Support::Cli::Error, "invalid scope" unless %w[current all].include?(scope)
16
+ puts JSON.generate(Molecules::EventObservation.new.call(scope: scope, limit: options[:limit] || 500, owner: options[:owner]))
17
+ rescue ArgumentError => e
18
+ raise Ace::Support::Cli::Error, e.message
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
data/lib/ace/hitl/cli.rb CHANGED
@@ -8,6 +8,7 @@ require_relative "cli/commands/operator_question"
8
8
  require_relative "cli/commands/read"
9
9
  require_relative "cli/commands/show"
10
10
  require_relative "cli/commands/list"
11
+ require_relative "cli/commands/observe"
11
12
  require_relative "cli/commands/update"
12
13
  require_relative "cli/commands/wait"
13
14
  require_relative "cli/commands/deliver"
@@ -31,6 +32,7 @@ module Ace
31
32
  ["read", "Read authenticated lifecycle request facts"],
32
33
  ["show", "Show HITL event details"],
33
34
  ["list", "List HITL events"],
35
+ ["observe", "Read semantic event revisions"],
34
36
  ["update", "Update HITL event metadata or answer"],
35
37
  ["send", "Send a HITL event through the configured adapter routes"],
36
38
  ["wait", "Wait for an answer on a specific HITL event"],
@@ -58,6 +60,7 @@ module Ace
58
60
  register "read", CLI::Commands::Read
59
61
  register "show", CLI::Commands::Show
60
62
  register "list", CLI::Commands::List
63
+ register "observe", CLI::Commands::Observe
61
64
  register "update", CLI::Commands::Update
62
65
  register "send", CLI::Commands::Send
63
66
  register "wait", CLI::Commands::Wait
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+ require "digest"
3
+ require "json"
4
+
5
+ module Ace
6
+ module Hitl
7
+ module Molecules
8
+ # Read-only semantic projection. No private text is emitted; delivery
9
+ # timestamps, filesystem metadata and observer acknowledgements are not
10
+ # decision changes. Canonical HITL remains the only event owner.
11
+ class EventObservation
12
+ def initialize(manager: Organisms::HitlManager.new)
13
+ @manager = manager
14
+ end
15
+
16
+ def call(scope: "current", limit: 500, owner: nil)
17
+ raise ArgumentError, "invalid observation limit" unless (1..2000).cover?(limit)
18
+ events = @manager.list(in_folder: "next", scope: scope, limit: limit, strict: true)
19
+ raise ArgumentError, "observation exceeds configured event budget" if events.size > limit
20
+ address = owner && Molecules::HitlAddressResolver.new(config: @manager.config).resolve(owner)
21
+ events = events.select { |event| incoming?(event, address) } if address
22
+ rows = events.map do |event|
23
+ semantic = [event.id, event.type, event.status, event.result, event.kind,
24
+ event.title, event.content, event.answer, event.questions,
25
+ Array(event.tags).sort, canonical(event.recipient), canonical(event.reply_to),
26
+ event.metadata.values_at("task", "assignment", "step", "step_name", "resume_instructions")]
27
+ {"key" => Digest::SHA256.hexdigest(event.file_path.to_s),
28
+ "id" => event.id, "revision" => Digest::SHA256.hexdigest(JSON.generate(canonical(semantic))),
29
+ "type" => event.type, "status" => event.status, "result" => event.result,
30
+ "task" => event.metadata["task"], "assignment" => event.metadata["assignment"]}
31
+ end
32
+ {"schema" => "ace.hitl.observation/v1", "events" => rows.sort_by { |row| row["key"] }}
33
+ end
34
+
35
+ private
36
+
37
+ # Questions created locally are outbound until actually answered. A
38
+ # worker's reply belongs to that worker, not every linked-worktree actor.
39
+ # Native instructions have no recipient; their explicit import root owns them.
40
+ def incoming?(event, address)
41
+ local = File.expand_path(event.file_path).start_with?(File.expand_path(@manager.root_dir) + File::SEPARATOR)
42
+ return local if Array(event.tags).include?("hermes-instruction")
43
+ if event.question? && event.answered?
44
+ return local && event.reply_to &&
45
+ event.reply_to["machine"] == @manager.config.dig("hitl", "machine") &&
46
+ File.expand_path(event.reply_to["folder"].to_s) == File.expand_path(@manager.root_dir)
47
+ end
48
+ recipient = event.recipient
49
+ recipient && address.all? { |key, value| recipient[key] == value }
50
+ end
51
+
52
+ def canonical(value)
53
+ case value
54
+ when Hash then value.sort.to_h.transform_values { |item| canonical(item) }
55
+ when Array then value.map { |item| canonical(item) }
56
+ else value
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -94,10 +94,12 @@ module Ace
94
94
  }
95
95
  end
96
96
 
97
- def list(status: nil, kind: nil, type: nil, delivery: nil, in_folder: "next", tags: [], scope: nil)
97
+ def list(status: nil, kind: nil, type: nil, delivery: nil, in_folder: "next", tags: [], scope: nil, limit: nil, strict: false)
98
98
  effective_scope = @scope_resolver.effective_scope(scope)
99
99
  scan_results = scan_results_for_scope(effective_scope, in_folder: in_folder)
100
+ raise ArgumentError, "observation exceeds configured event budget" if limit && scan_results.size > limit
100
101
  events = load_events(scan_results)
102
+ raise ArgumentError, "incomplete HITL observation" if strict && events.size != scan_results.size
101
103
 
102
104
  events = events.select { |event| event.status == status } if status
103
105
  events = events.select { |event| event.kind == kind } if kind
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Hitl
5
- VERSION = "0.23.2"
5
+ VERSION = "0.24.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-hitl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.2
4
+ version: 0.24.0
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-hitl-contract
@@ -160,6 +160,7 @@ files:
160
160
  - lib/ace/hitl/cli/commands/duty.rb
161
161
  - lib/ace/hitl/cli/commands/lifecycle_command.rb
162
162
  - lib/ace/hitl/cli/commands/list.rb
163
+ - lib/ace/hitl/cli/commands/observe.rb
163
164
  - lib/ace/hitl/cli/commands/operator_question.rb
164
165
  - lib/ace/hitl/cli/commands/pending.rb
165
166
  - lib/ace/hitl/cli/commands/read.rb
@@ -198,6 +199,7 @@ files:
198
199
  - lib/ace/hitl/lifecycle/tls_service.rb
199
200
  - lib/ace/hitl/live_client.rb
200
201
  - lib/ace/hitl/models/hitl_event.rb
202
+ - lib/ace/hitl/molecules/event_observation.rb
201
203
  - lib/ace/hitl/molecules/hitl_address_resolver.rb
202
204
  - lib/ace/hitl/molecules/hitl_answer_editor.rb
203
205
  - lib/ace/hitl/molecules/hitl_config_loader.rb