ace-runtime 0.3.0 → 0.4.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: 1a5a447921ab8d6b478e41a38e6b56866977accc8a75250e394c89c6fa0ac658
4
- data.tar.gz: efe75a6544cb3284a9bf544deaaa30975b60971a5c821799666502240442ec7f
3
+ metadata.gz: 95299eb06854907e2402041c388ca25b234b24e220acf2c13911c746b84f3c6b
4
+ data.tar.gz: 9e6cf1b53df631d5a2e764b5deff50d5a34b0d2491d3f5be1fc2ec17ff6b30db
5
5
  SHA512:
6
- metadata.gz: 46343d278d6041fcd7f41c859207ee0303e28811d156b923bc195b88cf73aef852d3f44b50df62a90425f0cec8a942984504ca9a7c52e4cd9152ffc77679d87a
7
- data.tar.gz: b61c518fb64562ddb1975be5b6b71242134f013ec80de029bf259c5d92acdd2572b428f60788e4e4578e7f5f2a59abb3e50ec8664eaa462ceb65482ce3b6857e
6
+ metadata.gz: cfe0a30fdcb212c43d6e9633877aaf3ae35ddb115819066ca3c7b6f30205f916bf33f22afdf9087d4395d595a192807fa3c98ce922ddf1f912bf35a3550048f6
7
+ data.tar.gz: a2e00a11a779cfc017e782617a5823a315ff379f6b3c6f434720df0d252e7dd086b8abf73dd491b4e16dbb7223055773181370def9f5ffcf618f718563e9ec67
data/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.0] - 2026-10-16
10
+
11
+ ### Added
12
+
13
+ - Distinguish positively ended process generations from inaccessible observations for explicit coordinator adoption; retain conservative unknown liveness for existing recovery observers.
14
+
9
15
  ## [0.3.0] - 2026-10-10
10
16
 
11
17
  - Ship the protected task-context/worker entry and readiness source used by the current consumers; these required modules were absent from published 0.2.0. Ordinary agent execution remains the ace-llm terminal CLI, with no additional Codex service.
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fiddle"
4
+
5
+ module Ace
6
+ module Runtime
7
+ module Molecules
8
+ # One ephemeral single-thread helper owns every child in this scope.
9
+ # Linux reparents detached orphans to this subreaper, not container PID1.
10
+ # Unreaped direct children pin their PIDs until this owner reaps them.
11
+ class OwnedChildScope
12
+ class CleanupUncertain < StandardError; end
13
+
14
+ def initialize
15
+ unless RUBY_PLATFORM.include?("linux") && Thread.list.count(&:alive?) == 1
16
+ raise CleanupUncertain, "child scope requires a sole Linux helper thread"
17
+ end
18
+ @owner = Process.pid
19
+ raise CleanupUncertain, "child scope has preexisting children" unless children.empty?
20
+ # An inherited ignore/custom reaper could discard a child's wait
21
+ # status and unpin its PID. This sole ephemeral helper owns the
22
+ # disposition; establish it while the verified child set is empty.
23
+ Signal.trap("CHLD", "DEFAULT")
24
+ prctl = Fiddle::Function.new(Fiddle::Handle::DEFAULT["prctl"],
25
+ [Fiddle::TYPE_INT, Fiddle::TYPE_LONG, Fiddle::TYPE_LONG, Fiddle::TYPE_LONG, Fiddle::TYPE_LONG], Fiddle::TYPE_INT)
26
+ raise CleanupUncertain, "cannot establish child subreaper" unless prctl.call(36, 1, 0, 0, 0).zero?
27
+ end
28
+
29
+ # Preserve the leader's wait status for the executor. Kill/reap only
30
+ # kernel-owned direct children; adoption closes setsid/double-fork escape.
31
+ def cleanup!(leader:, timeout: 5)
32
+ unless Process.pid == @owner && Thread.list.count(&:alive?) == 1 &&
33
+ timeout.is_a?(Numeric) && timeout.finite? && timeout.positive? && timeout <= 5
34
+ raise CleanupUncertain, "child scope ownership is unavailable"
35
+ end
36
+ deadline = monotonic + timeout
37
+ owned = children
38
+ raise CleanupUncertain, "original child is no longer owned" unless owned.include?(leader)
39
+ signal("TERM", -leader)
40
+ loop do
41
+ owned = children
42
+ raise CleanupUncertain, "original child was reaped outside its scope" unless owned.include?(leader)
43
+ owned.each do |pid|
44
+ signal("KILL", pid)
45
+ Process.waitpid(pid, Process::WNOHANG) unless pid == leader
46
+ end
47
+ remaining = children
48
+ return if remaining == [leader] && zombie?(leader)
49
+ raise CleanupUncertain, "owned child cleanup deadline elapsed" if monotonic >= deadline
50
+ IO.select(nil, nil, nil, 0.01)
51
+ end
52
+ rescue Errno::ECHILD, Errno::ENOENT, IOError
53
+ raise CleanupUncertain, "owned child cleanup cannot be confirmed"
54
+ end
55
+
56
+ private
57
+
58
+ def children
59
+ File.open("/proc/self/task/#{@owner}/children", "r") do |file|
60
+ bytes = file.read(65_537) || ""
61
+ raise CleanupUncertain, "owned child inventory exceeds bound" if bytes.bytesize > 65_536
62
+ values = bytes.split.map { |value| Integer(value, 10) }
63
+ unless values.uniq == values && values.all?(&:positive?)
64
+ raise CleanupUncertain, "owned child inventory is invalid"
65
+ end
66
+ values.sort
67
+ end
68
+ end
69
+
70
+ def signal(name, pid)
71
+ Process.kill(name, pid)
72
+ rescue Errno::ESRCH
73
+ nil
74
+ end
75
+
76
+ def zombie?(pid) = File.read("/proc/#{pid}/stat").rpartition(")").last.split.first == "Z"
77
+ def monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -3,6 +3,7 @@
3
3
  require "open3"
4
4
  require "socket"
5
5
  require "fiddle"
6
+ require "time"
6
7
 
7
8
  module Ace
8
9
  module Runtime
@@ -117,6 +118,33 @@ module Ace
117
118
  {"liveness" => "unknown", "observed_at" => at, "reason" => "process identity no longer observable"}
118
119
  end
119
120
  end
121
+
122
+ # Positive evidence that this exact historical generation ended. A
123
+ # failed capture alone says nothing: EPERM/inaccessible probes remain
124
+ # unknown. PID reuse is conclusive only when the exact birth differs.
125
+ def generation_status(identity, process: Process)
126
+ at = Time.now.utc.iso8601
127
+ unknown = {"liveness" => "unknown", "observed_at" => at}
128
+ unless identity.is_a?(Hash) && identity.keys.sort == %w[host pid started_at uid] &&
129
+ identity["host"] == @hostname && identity["pid"].is_a?(Integer) && identity["pid"].positive? &&
130
+ identity["uid"].is_a?(Integer) && identity["uid"] >= 0 &&
131
+ identity["started_at"].is_a?(String) && !identity["started_at"].empty?
132
+ return unknown.merge("reason" => "verified process generation unavailable")
133
+ end
134
+ current = capture(identity.fetch("pid"))
135
+ return {"liveness" => "live", "observed_at" => at, "identity" => current} if current == identity
136
+ if current && current.fetch("started_at") != identity.fetch("started_at")
137
+ return {"liveness" => "ended", "observed_at" => at, "reason" => "pid generation replaced", "identity" => current}
138
+ end
139
+ begin
140
+ process.kill(0, identity.fetch("pid"))
141
+ unknown.merge("reason" => "process address exists but generation is unobservable")
142
+ rescue Errno::ESRCH
143
+ {"liveness" => "ended", "observed_at" => at, "reason" => "kernel reports no process at address"}
144
+ rescue SystemCallError
145
+ unknown.merge("reason" => "kernel process presence is unavailable")
146
+ end
147
+ end
120
148
  end
121
149
  end
122
150
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Runtime
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-runtime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-10 00:00:00.000000000 Z
10
+ date: 2026-10-16 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: fiddle
@@ -208,6 +208,7 @@ files:
208
208
  - lib/ace/runtime/molecules/kernel_view_topology.rb
209
209
  - lib/ace/runtime/molecules/linux_mount_info.rb
210
210
  - lib/ace/runtime/molecules/network_installation_evidence.rb
211
+ - lib/ace/runtime/molecules/owned_child_scope.rb
211
212
  - lib/ace/runtime/molecules/process_identity.rb
212
213
  - lib/ace/runtime/molecules/protected_artifact_set.rb
213
214
  - lib/ace/runtime/molecules/protected_linux.rb