Module: Harnex::DispatchHistory
- Defined in:
- lib/harnex/dispatch_history.rb
Constant Summary collapse
- MAX_REPO_WALK_LEVELS =
10
Class Method Summary collapse
- .append(path, record) ⇒ Object
- .build_record(session) ⇒ Object
-
.build_start_record(session) ⇒ Object
Appended at registration so a running dispatch always has a durable trace; the dispatch_end row written in finalize_session! completes it.
- .classify(session) ⇒ Object
- .commit_sha(git_start, git_end) ⇒ Object
- .end_matches_start?(end_record, start_record) ⇒ Boolean
- .end_record?(record) ⇒ Boolean
- .find_git_root(start_path) ⇒ Object
- .git_toplevel(path) ⇒ Object
- .global_path ⇒ Object
-
.latest_rows(path, id) ⇒ Object
Latest start row for
idand any end row that completes it. -
.live_start_record(repo_root:, id:) ⇒ Object
A dispatch_start row with no completing end row whose pid is alive on this host — evidence of a running session even when the live registry is not visible from the caller's context.
- .path_for(start_path = Dir.pwd, global: false) ⇒ Object
- .same_host?(record) ⇒ Boolean
- .start_record?(record) ⇒ Boolean
- .tmux_state(tmux_session) ⇒ Object
Class Method Details
.append(path, record) ⇒ Object
54 55 56 57 58 59 60 61 62 63 |
# File 'lib/harnex/dispatch_history.rb', line 54 def append(path, record) FileUtils.mkdir_p(File.dirname(path)) line = JSON.generate(record) + "\n" File.open(path, File::WRONLY | File::APPEND | File::CREAT, 0o644) do |file| file.flock(File::LOCK_EX) file.write(line) ensure file.flock(File::LOCK_UN) unless file.closed? end end |
.build_record(session) ⇒ Object
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/harnex/dispatch_history.rb', line 158 def build_record(session) ended_at = session.ended_at || Time.now status, terminal_event = classify(session) { schema_version: 1, record_type: "dispatch_end", id: session.id, session_id: session.session_id, description: session.description, cli: session.adapter.key, started_at: session.started_at.utc.iso8601, ended_at: ended_at.utc.iso8601, duration_s: (ended_at - session.started_at).to_i, status: status, terminal_event: terminal_event, commit_sha: commit_sha(session.git_start, session.git_end), tier: session.__send__(:meta_hash)["tier"], meta: session.__send__(:meta_hash), summary_out_path: session.summary_out, events_log_path: session.events_log_path, tmux_state: tmux_state(session.__send__(:summary_tmux_session)) } end |
.build_start_record(session) ⇒ Object
Appended at registration so a running dispatch always has a durable trace; the dispatch_end row written in finalize_session! completes it.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/harnex/dispatch_history.rb', line 139 def build_start_record(session) { schema_version: 1, record_type: "dispatch_start", id: session.id, session_id: session.session_id, pid: session.pid, host: Harnex.host_info[:host], cli: session.adapter.key, description: session.description, started_at: session.started_at.utc.iso8601, repo_root: session.repo_root, tier: session.__send__(:meta_hash)["tier"], meta: session.__send__(:meta_hash), summary_out_path: session.summary_out, events_log_path: session.events_log_path } end |
.classify(session) ⇒ Object
182 183 184 185 186 187 188 189 190 |
# File 'lib/harnex/dispatch_history.rb', line 182 def classify(session) return ["failed", "task_failed"] if session.respond_to?(:task_failed?) && session.task_failed? return ["completed", "task_complete"] if session.task_complete? return ["timeout", "timeout"] if session.exit_code == 124 return ["killed", "process_kill"] if session.term_signal return ["completed", "process_exit"] if session.exit_code == 0 ["failed", "dispatch_failed"] end |
.commit_sha(git_start, git_end) ⇒ Object
192 193 194 195 196 197 198 |
# File 'lib/harnex/dispatch_history.rb', line 192 def commit_sha(git_start, git_end) start_sha = git_start[:sha].to_s end_sha = git_end[:sha].to_s return nil if start_sha.empty? || end_sha.empty? || start_sha == end_sha end_sha end |
.end_matches_start?(end_record, start_record) ⇒ Boolean
77 78 79 80 81 82 83 84 |
# File 'lib/harnex/dispatch_history.rb', line 77 def end_matches_start?(end_record, start_record) end_session = end_record["session_id"].to_s start_session = start_record["session_id"].to_s return end_session == start_session unless end_session.empty? || start_session.empty? end_record["id"].to_s == start_record["id"].to_s && end_record["started_at"].to_s == start_record["started_at"].to_s end |
.end_record?(record) ⇒ Boolean
69 70 71 72 73 74 75 |
# File 'lib/harnex/dispatch_history.rb', line 69 def end_record?(record) return false unless record.is_a?(Hash) return true if record["record_type"] == "dispatch_end" # Legacy end rows predate record_type. record["schema_version"] == 1 && record.key?("status") && !record.key?("record_type") end |
.find_git_root(start_path) ⇒ Object
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/harnex/dispatch_history.rb', line 25 def find_git_root(start_path) path = File.(start_path.to_s.empty? ? Dir.pwd : start_path) path = File.dirname(path) unless File.directory?(path) git_root = git_toplevel(path) return git_root if git_root (MAX_REPO_WALK_LEVELS + 1).times do return path if File.directory?(File.join(path, ".git")) parent = File.dirname(path) break if parent == path path = parent end nil end |
.git_toplevel(path) ⇒ Object
44 45 46 47 48 49 50 51 52 |
# File 'lib/harnex/dispatch_history.rb', line 44 def git_toplevel(path) output, status = Open3.capture2("git", "-C", path, "rev-parse", "--show-toplevel", err: File::NULL) root = output.to_s.strip return root if status.success? && !root.empty? nil rescue StandardError nil end |
.global_path ⇒ Object
12 13 14 |
# File 'lib/harnex/dispatch_history.rb', line 12 def global_path File.join(STATE_DIR, "dispatch.jsonl") end |
.latest_rows(path, id) ⇒ Object
Latest start row for id and any end row that completes it.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/harnex/dispatch_history.rb', line 87 def latest_rows(path, id) latest_start = nil matching_end = nil return { start: nil, end: nil } unless File.file?(path) File.foreach(path) do |line| record = JSON.parse(line) next unless record.is_a?(Hash) next unless record["id"].to_s == id if start_record?(record) latest_start = record matching_end = nil elsif end_record?(record) matching_end = record if latest_start && end_matches_start?(record, latest_start) end rescue JSON::ParserError next end { start: latest_start, end: matching_end } end |
.live_start_record(repo_root:, id:) ⇒ Object
A dispatch_start row with no completing end row whose pid is alive on this host — evidence of a running session even when the live registry is not visible from the caller's context.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/harnex/dispatch_history.rb', line 114 def live_start_record(repo_root:, id:) normalized_id = Harnex.normalize_id(id) rows = latest_rows(path_for(repo_root), normalized_id) start = rows[:start] return nil unless start return nil if rows[:end] return nil unless same_host?(start) pid = start["pid"] return nil unless pid && Harnex.alive_pid?(pid) start rescue StandardError nil end |
.path_for(start_path = Dir.pwd, global: false) ⇒ Object
16 17 18 19 20 21 22 23 |
# File 'lib/harnex/dispatch_history.rb', line 16 def path_for(start_path = Dir.pwd, global: false) return global_path if global repo_root = find_git_root(start_path) return global_path unless repo_root File.join(repo_root, ".harnex", "dispatch.jsonl") end |
.same_host?(record) ⇒ Boolean
130 131 132 133 134 135 |
# File 'lib/harnex/dispatch_history.rb', line 130 def same_host?(record) host = record["host"].to_s return true if host.empty? host == Harnex.host_info[:host].to_s end |
.start_record?(record) ⇒ Boolean
65 66 67 |
# File 'lib/harnex/dispatch_history.rb', line 65 def start_record?(record) record.is_a?(Hash) && record["record_type"] == "dispatch_start" end |
.tmux_state(tmux_session) ⇒ Object
200 201 202 203 204 205 206 |
# File 'lib/harnex/dispatch_history.rb', line 200 def tmux_state(tmux_session) return "torn-down" if tmux_session.to_s.empty? system("tmux", "has-session", "-t", tmux_session.to_s, out: File::NULL, err: File::NULL) ? "live" : "torn-down" rescue StandardError "torn-down" end |