diff --git a/plugins/os-context/audit/bin/extract b/plugins/os-context/audit/bin/extract index 3aec5e0..be36011 100755 --- a/plugins/os-context/audit/bin/extract +++ b/plugins/os-context/audit/bin/extract @@ -14,6 +14,7 @@ require "optparse" module OrchAudit READ_TOOLS = %w[Read Grep Glob Bash].freeze RUN_TOOLS = %w[Read Grep Glob Edit Write].freeze + RW_TOOLS = %w[Read Write Edit].freeze class Line attr_reader :number, :raw @@ -139,19 +140,23 @@ module OrchAudit @threshold = threshold end - def candidates - runs = [] - current = [] - @calls.each do |call| - if extend_run?(current, call) - current << call - else - runs << current if qualifying?(current) - current = RUN_TOOLS.include?(call.name) ? [call] : [] + def candidates = runs.map { |r| describe(r) } + + def runs + @runs ||= begin + found = [] + current = [] + @calls.each do |call| + if extend_run?(current, call) + current << call + else + found << current if qualifying?(current) + current = RUN_TOOLS.include?(call.name) ? [call] : [] + end end + found << current if qualifying?(current) + found end - runs << current if qualifying?(current) - runs.map { |r| describe(r) } end private @@ -220,7 +225,16 @@ module OrchAudit m[:targets_sample].each { |t| md << " - `#{t}`\n" } end end - md << "\nHeuristic candidates require auditor judgment; sequential-dependent work is a valid reason not to delegate.\n" + rw = d[:read_write_profile] + md << "\n## Main-loop Read/Write/Edit profile (missed-delegation/read-write denominator)\n\n" + counts = rw[:tool_counts].map { |k, v| "#{k}:#{v}" }.join(" ") + md << "- Total: #{rw[:total]} (#{counts}); in flagged runs: #{rw[:in_flagged_runs]}; scattered: #{rw[:scattered].size}\n" + rw[:scattered].each do |c| + md << " - #{c[:tool]} line #{c[:line]} `#{c[:target]}`\n" + end + md << "\nHeuristic candidates and scattered calls require auditor judgment via the rubric's " \ + "exemption decision procedure (E-a..E-d); under ADR-0043 there are no validity defenses " \ + "for direct grunt work, only severity modifiers.\n" md end @@ -235,12 +249,31 @@ module OrchAudit attach_rounds(main, agent_spawns) orchestrator_calls = calls.values.reject { |c| c.name == "Agent" } + scan = MissedDelegationScan.new(orchestrator_calls, @run_threshold) { metadata: metadata(main, agent_spawns), agent_spawns: agent_spawns.map(&:to_h), segments: segments(main).map(&:to_h), - missed_delegation_candidates: - MissedDelegationScan.new(orchestrator_calls, @run_threshold).candidates + missed_delegation_candidates: scan.candidates, + read_write_profile: read_write_profile(orchestrator_calls, scan) + } + end + + # Denominator for the rubric's missed-delegation/read-write sub-tag: total + # main-loop Read/Write/Edit calls regardless of adjacency. The run scan only + # sees consecutive same-tool runs; "scattered" lists the calls it can't + # flag, for per-call exemption judgment by the auditor. + def read_write_profile(orchestrator_calls, scan) + rw = orchestrator_calls.select { |c| RW_TOOLS.include?(c.name) } + in_run = scan.runs.flatten.select { |c| RW_TOOLS.include?(c.name) } + scattered = rw - in_run + { + total: rw.size, + tool_counts: rw.map(&:name).tally, + in_flagged_runs: in_run.size, + scattered: scattered.map do |c| + { tool: c.name, line: c.line.number, target: c.target.to_s[0, 120] } + end } end