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 +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/ace/runtime/molecules/owned_child_scope.rb +81 -0
- data/lib/ace/runtime/molecules/process_identity.rb +28 -0
- data/lib/ace/runtime/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 95299eb06854907e2402041c388ca25b234b24e220acf2c13911c746b84f3c6b
|
|
4
|
+
data.tar.gz: 9e6cf1b53df631d5a2e764b5deff50d5a34b0d2491d3f5be1fc2ec17ff6b30db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/ace/runtime/version.rb
CHANGED
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.
|
|
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
|
+
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
|