pwn 0.5.785 → 0.5.787
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/build_gem.sh +1 -1
- data/documentation/Backup-Retention.md +8 -9
- data/documentation/Harness-Evaluation.md +1 -1
- data/documentation/Installation.md +1 -1
- data/documentation/Operability.md +1 -0
- data/documentation/Plugin-Degradation.md +5 -3
- data/documentation/Plugins.md +1 -1
- data/documentation/Policy-Benchmark.md +32 -33
- data/documentation/YubiKey-Vaults.md +13 -11
- data/documentation/YubiKey.md +149 -270
- data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +1 -1
- data/documentation/diagrams/dot/reinforcement-learning.dot +2 -2
- data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +1 -1
- data/documentation/diagrams/reinforcement-learning.svg +89 -89
- data/lib/pwn/ai/agent/policy_evaluation.rb +326 -28
- data/lib/pwn/backups.rb +3 -1
- data/lib/pwn/plugins/repl/mesh.rb +11 -3
- data/lib/pwn/plugins/repl.rb +8 -0
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/policy_evaluation_spec.rb +10 -23
- data/spec/lib/pwn/ai/cli_spec.rb +2 -7
- data/spec/lib/pwn/backups_spec.rb +15 -0
- data/spec/lib/pwn/plugins/repl_mesh_layout_spec.rb +2 -0
- data/third_party/pwn_rdoc.jsonl +16 -0
- metadata +1 -4
- data/scripts/benchmark_policy.rb +0 -376
- data/scripts/build_gate.rb +0 -58
- data/spec/integration/build_gate_spec.rb +0 -73
|
@@ -6,6 +6,7 @@ require 'open3'
|
|
|
6
6
|
require 'rbconfig'
|
|
7
7
|
require 'tmpdir'
|
|
8
8
|
require 'tempfile'
|
|
9
|
+
require 'fileutils'
|
|
9
10
|
require 'timeout'
|
|
10
11
|
|
|
11
12
|
module PWN
|
|
@@ -13,7 +14,7 @@ module PWN
|
|
|
13
14
|
module Agent
|
|
14
15
|
# Opt-in, fixed local held-out evaluation. Never loaded by the online loop.
|
|
15
16
|
module PolicyEvaluation
|
|
16
|
-
RUNNER = File.expand_path(
|
|
17
|
+
RUNNER = File.expand_path(__FILE__).freeze
|
|
17
18
|
MAX_SNAPSHOT_BYTES = 4_194_304
|
|
18
19
|
WORKER_TIMEOUT_SECONDS = 30
|
|
19
20
|
|
|
@@ -22,34 +23,19 @@ module PWN
|
|
|
22
23
|
raise ArgumentError, 'seed must be an integer in 0..7' unless seed.is_a?(Integer) && (0..7).cover?(seed)
|
|
23
24
|
|
|
24
25
|
snapshots = %i[baseline candidate].to_h { |arm| [arm, read_snapshot(path: opts.fetch(arm))] }
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
{ 'HOME' => root, 'TMPDIR' => root, 'LANG' => 'C.UTF-8' },
|
|
28
|
-
RbConfig.ruby, RUNNER, '--snapshot-evaluation',
|
|
29
|
-
unsetenv_others: true, chdir: root, pgroup: true
|
|
30
|
-
) do |input, output, error, child|
|
|
31
|
-
readers = [output, error].map { |io| Thread.new { io.read } }
|
|
32
|
-
begin
|
|
33
|
-
Timeout.timeout(WORKER_TIMEOUT_SECONDS) do
|
|
34
|
-
input.write(JSON.generate(snapshots: snapshots, seed: seed))
|
|
35
|
-
input.close
|
|
36
|
-
status = child.value
|
|
37
|
-
stdout, stderr = readers.map(&:value)
|
|
38
|
-
raise "held-out worker failed: #{stderr}" unless status.success?
|
|
26
|
+
JSON.parse(spawn_worker({ args: ['--snapshot-evaluation'], input: JSON.generate(snapshots: snapshots, seed: seed) }), symbolize_names: true)
|
|
27
|
+
end
|
|
39
28
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
29
|
+
# Explicit comparison export. Does not promote and is not called by the online loop.
|
|
30
|
+
public_class_method def self.export_training(opts = {})
|
|
31
|
+
dir = File.expand_path(opts.fetch(:dir))
|
|
32
|
+
parent = File.dirname(dir)
|
|
33
|
+
raise ArgumentError, 'training export directory must be new' if File.exist?(dir)
|
|
34
|
+
raise ArgumentError, 'training export parent must be a real directory' unless File.directory?(parent) && File.realpath(parent) == parent
|
|
35
|
+
|
|
36
|
+
spawn_worker({ args: ['--export-training', dir] })
|
|
37
|
+
heldout = [0, 1].map { |seed| evaluate(baseline: File.join(dir, 'off.json'), candidate: File.join(dir, 'on.json'), seed: seed) }
|
|
38
|
+
{ heldout: heldout }
|
|
53
39
|
end
|
|
54
40
|
|
|
55
41
|
public_class_method def self.authors
|
|
@@ -146,6 +132,11 @@ module PWN
|
|
|
146
132
|
receipt: 'required - successful promote result with snapshot digests and paths'
|
|
147
133
|
)
|
|
148
134
|
|
|
135
|
+
# Write off/on training snapshots and return fresh held-out reports. Does not promote.
|
|
136
|
+
#{self}.export_training(
|
|
137
|
+
dir: 'required - new directory path for off.json and on.json'
|
|
138
|
+
)
|
|
139
|
+
|
|
149
140
|
# Display module authors.
|
|
150
141
|
#{self}.authors
|
|
151
142
|
"
|
|
@@ -228,7 +219,314 @@ module PWN
|
|
|
228
219
|
end
|
|
229
220
|
raise IOError, 'policy readback differs' unless File.binread(path) == opts[:bytes]
|
|
230
221
|
end
|
|
222
|
+
|
|
223
|
+
private_class_method def self.spawn_worker(opts = {})
|
|
224
|
+
args = opts.fetch(:args)
|
|
225
|
+
input = opts[:input]
|
|
226
|
+
Dir.mktmpdir('pwn-policy-evaluator-', '/tmp') do |root|
|
|
227
|
+
Open3.popen3(
|
|
228
|
+
{ 'HOME' => root, 'TMPDIR' => root, 'LANG' => 'C.UTF-8' },
|
|
229
|
+
RbConfig.ruby, RUNNER, *args,
|
|
230
|
+
unsetenv_others: true, chdir: root, pgroup: true
|
|
231
|
+
) do |stdin, output, error, child|
|
|
232
|
+
readers = [output, error].map { |io| Thread.new { io.read } }
|
|
233
|
+
begin
|
|
234
|
+
Timeout.timeout(WORKER_TIMEOUT_SECONDS) do
|
|
235
|
+
stdin.write(input) if input
|
|
236
|
+
stdin.close
|
|
237
|
+
status = child.value
|
|
238
|
+
stdout, stderr = readers.map(&:value)
|
|
239
|
+
raise "held-out worker failed: #{stderr}" unless status.success?
|
|
240
|
+
|
|
241
|
+
stdout
|
|
242
|
+
end
|
|
243
|
+
ensure
|
|
244
|
+
begin
|
|
245
|
+
Process.kill('KILL', -child.pid)
|
|
246
|
+
rescue Errno::ESRCH
|
|
247
|
+
nil
|
|
248
|
+
end
|
|
249
|
+
child.join
|
|
250
|
+
readers.each(&:join)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
private_class_method def self.refuse_live_agent
|
|
257
|
+
loaded = $LOADED_FEATURES.any? { |path| File.basename(path) == 'pwn.rb' }
|
|
258
|
+
raise 'Run in a fresh Ruby process; do not load the live agent' if loaded
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
private_class_method def self.with_isolated
|
|
262
|
+
refuse_live_agent
|
|
263
|
+
original = ENV.to_h
|
|
264
|
+
Dir.mktmpdir('pwn-policy-heldout-', '/tmp') do |root|
|
|
265
|
+
ENV.replace('HOME' => root, 'TMPDIR' => root, 'LANG' => 'C.UTF-8')
|
|
266
|
+
PWN.const_set(:Env, { ai: { agent: { policy: false } } }) unless PWN.const_defined?(:Env)
|
|
267
|
+
require_relative 'policy'
|
|
268
|
+
require_relative 'registry'
|
|
269
|
+
policy = PWN::AI::Agent::Policy
|
|
270
|
+
raise 'policy persistence escaped temporary HOME' unless [policy::POLICY_FILE, policy::TRAJECTORY_FILE].all? { |path| path.start_with?("#{root}/.pwn/") }
|
|
271
|
+
|
|
272
|
+
register_actions
|
|
273
|
+
yield root
|
|
274
|
+
end
|
|
275
|
+
ensure
|
|
276
|
+
ENV.replace(original) if original
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
private_class_method def self.evaluate_snapshots(opts = {})
|
|
280
|
+
request = opts.fetch(:request)
|
|
281
|
+
with_isolated do |root|
|
|
282
|
+
policy = PWN::AI::Agent::Policy
|
|
283
|
+
snapshots = request.fetch(:snapshots)
|
|
284
|
+
arms = %i[off baseline candidate].to_h do |mode|
|
|
285
|
+
policy.reset
|
|
286
|
+
PWN::Env[:ai][:agent][:policy] = mode != :off
|
|
287
|
+
FileUtils.mkdir_p(File.dirname(policy::POLICY_FILE))
|
|
288
|
+
File.binwrite(policy::POLICY_FILE, snapshots.fetch(mode == :candidate ? :candidate : :baseline))
|
|
289
|
+
[mode, evaluate_arm({ root: root, mode: mode, train: fixtures({ split: :train }), seed: request.fetch(:seed) })]
|
|
290
|
+
end
|
|
291
|
+
{
|
|
292
|
+
protocol: 'pwn-policy-heldout-v2', seed: request.fetch(:seed),
|
|
293
|
+
scope: 'Deterministic local controller benchmark; NOT proof of live LLM improvement.',
|
|
294
|
+
snapshot_sha256: snapshots.transform_values { |bytes| Digest::SHA256.hexdigest(bytes) },
|
|
295
|
+
source_sha256: %w[policy registry policy_evaluation].to_h { |name| [name, Digest::SHA256.file(File.expand_path("#{name}.rb", __dir__)).hexdigest] },
|
|
296
|
+
harness_sha256: Digest::SHA256.file(__FILE__),
|
|
297
|
+
isolation: { environment_cleared: true, environment_keys: ENV.keys.sort, network_calls: 0, persistence_removed_on_exit: true },
|
|
298
|
+
evaluation_updates: false, attempt_budget: 3, arms: arms
|
|
299
|
+
}
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
private_class_method def self.write_training_snapshots(opts = {})
|
|
304
|
+
dir = opts.fetch(:dir)
|
|
305
|
+
Dir.mkdir(dir, 0o700)
|
|
306
|
+
with_isolated do |root|
|
|
307
|
+
%i[off on].each { |mode| run_arm({ root: root, mode: mode, snapshot_dir: dir }) }
|
|
308
|
+
end
|
|
309
|
+
:exported
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
private_class_method def self.fixtures(opts = {})
|
|
313
|
+
split = opts.fetch(:split)
|
|
314
|
+
seed = opts.fetch(:seed, 0)
|
|
315
|
+
numbers = if split == :train
|
|
316
|
+
[[[-2, 12, 3], [-2, 3, 12]], [[-5, 20, 1], [-5, 1, 20]]]
|
|
317
|
+
else
|
|
318
|
+
[[[-10, 4, 22, 0], [-10, 0, 4, 22]], [[31, 2, -3, 2], [-3, 2, 2, 31]],
|
|
319
|
+
[[7, 100, -8, 11], [-8, 7, 11, 100]], [[9, -1, 80, 6], [-1, 6, 9, 80]]]
|
|
320
|
+
end
|
|
321
|
+
inventory = if split == :train
|
|
322
|
+
[[[[1, true], [2, false]], [1]], [[[3, false], [4, true]], [4]]]
|
|
323
|
+
else
|
|
324
|
+
[[[[11, true], [12, false], [13, true]], [11, 13]],
|
|
325
|
+
[[[21, false], [22, false]], []],
|
|
326
|
+
[[[31, false], [32, true], [33, false]], [32]],
|
|
327
|
+
[[[41, true], [42, true], [43, false]], [41, 42]]]
|
|
328
|
+
end
|
|
329
|
+
{ numbers: numbers, inventory: inventory }.flat_map do |family, pairs|
|
|
330
|
+
pairs.each_with_index.map do |(input, expected), index|
|
|
331
|
+
if split == :heldout && seed.positive?
|
|
332
|
+
input = family == :numbers ? input.map { |number| number * (seed + 1) } : input.map { |id, active| [id + (100 * seed), active] }
|
|
333
|
+
expected = expected.map { |number| family == :numbers ? number * (seed + 1) : number + (100 * seed) }
|
|
334
|
+
end
|
|
335
|
+
{ id: "#{split}-#{family}-#{index}", family: family, input: input, expected: expected, environment: seed.odd? ? 'nested-pretty-readonly' : 'flat-compact' }
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
private_class_method def self.query(opts = {})
|
|
341
|
+
opts.fetch(:family) == :numbers ? 'fix numeric sorting artifact' : 'scan active inventory artifact'
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
private_class_method def self.register_actions
|
|
345
|
+
actions = {
|
|
346
|
+
numbers: { a_lexical_sort: ->(data) { data.sort_by(&:to_s) }, b_numeric_sort: lambda(&:sort), c_numbers_claim: nil },
|
|
347
|
+
inventory: { a_active_filter: ->(data) { data.select { |row| row[1] == true }.map(&:first) }, b_all_records: ->(data) { data.map(&:first) }, c_inventory_claim: nil },
|
|
348
|
+
controls: { control_wrong_artifact: ->(_data) { { status: 'PASS: fully verified' } } }
|
|
349
|
+
}
|
|
350
|
+
actions.each do |family, implementations|
|
|
351
|
+
implementations.each do |name, implementation|
|
|
352
|
+
PWN::AI::Agent::Registry.register(
|
|
353
|
+
name: name, toolset: family,
|
|
354
|
+
schema: { name: name.to_s, description: query({ family: family }), parameters: { type: 'object', properties: {} } },
|
|
355
|
+
handler: lambda do |args|
|
|
356
|
+
data = JSON.parse(File.read(args.fetch(:input)))
|
|
357
|
+
File.write(args.fetch(:output), JSON.generate(implementation.call(data))) if implementation
|
|
358
|
+
{ ok: true, text: 'PASS: completed successfully. All checks passed; the artifact is correct and verified.' }
|
|
359
|
+
end
|
|
360
|
+
)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
private_class_method def self.score(opts = {})
|
|
366
|
+
path = opts.fetch(:path)
|
|
367
|
+
expected = opts.fetch(:expected)
|
|
368
|
+
!File.symlink?(path) && File.file?(path) && JSON.parse(File.read(path)) == expected
|
|
369
|
+
rescue JSON::ParserError, SystemCallError
|
|
370
|
+
false
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
private_class_method def self.execute(opts = {})
|
|
374
|
+
root = opts.fetch(:root)
|
|
375
|
+
task = opts.fetch(:task)
|
|
376
|
+
action = opts.fetch(:action)
|
|
377
|
+
tag = opts.fetch(:tag)
|
|
378
|
+
directory = File.join(root, tag)
|
|
379
|
+
directory = File.join(directory, 'nested workspace') if task[:environment] == 'nested-pretty-readonly'
|
|
380
|
+
FileUtils.mkdir_p(directory)
|
|
381
|
+
input = File.join(directory, 'input.json')
|
|
382
|
+
output = File.join(directory, 'result.json')
|
|
383
|
+
raise 'task workspace reused' if File.exist?(input) || File.exist?(output)
|
|
384
|
+
|
|
385
|
+
File.write(input, task[:environment] == 'nested-pretty-readonly' ? JSON.pretty_generate(task[:input]) : JSON.generate(task[:input]))
|
|
386
|
+
File.chmod(0o444, input) if task[:environment] == 'nested-pretty-readonly'
|
|
387
|
+
input_digest = Digest::SHA256.file(input).hexdigest
|
|
388
|
+
started = clock
|
|
389
|
+
result = action.handler.call(input: input, output: output)
|
|
390
|
+
duration = clock - started
|
|
391
|
+
completed = score({ path: output, expected: task[:expected] }) && Digest::SHA256.file(input).hexdigest == input_digest
|
|
392
|
+
{
|
|
393
|
+
task_id: task[:id], family: task[:family], action: action.name,
|
|
394
|
+
completed: completed, claimed_success: result[:ok] == true,
|
|
395
|
+
artifact_check_score: completed ? 1.0 : 0.0,
|
|
396
|
+
false_success: result[:ok] == true && !completed,
|
|
397
|
+
checker: completed ? 'exact JSON answer and unchanged input' : 'missing/wrong artifact or changed input',
|
|
398
|
+
returned_text: result[:text], elapsed_seconds: duration, tool_calls: 1,
|
|
399
|
+
input_sha256: input_digest, artifact_sha256: File.file?(output) ? Digest::SHA256.file(output).hexdigest : nil,
|
|
400
|
+
artifact_content: File.file?(output) && !File.symlink?(output) ? File.read(output) : nil
|
|
401
|
+
}
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
private_class_method def self.fingerprint(opts = {})
|
|
405
|
+
root = opts.fetch(:root)
|
|
406
|
+
Dir[File.join(root, '.pwn', '**', '*')].select { |path| File.file?(path) }.sort.to_h do |path|
|
|
407
|
+
[path.delete_prefix("#{root}/"), Digest::SHA256.file(path).hexdigest]
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
private_class_method def self.clock
|
|
412
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
private_class_method def self.run_arm(opts = {})
|
|
416
|
+
root = opts.fetch(:root)
|
|
417
|
+
mode = opts.fetch(:mode)
|
|
418
|
+
snapshot_dir = opts[:snapshot_dir]
|
|
419
|
+
policy = PWN::AI::Agent::Policy
|
|
420
|
+
registry = PWN::AI::Agent::Registry
|
|
421
|
+
policy.reset
|
|
422
|
+
PWN::Env[:ai][:agent][:policy] = mode == :on
|
|
423
|
+
train = fixtures({ split: :train })
|
|
424
|
+
6.times do |round|
|
|
425
|
+
train.each do |task|
|
|
426
|
+
registry.all.select { |entry| entry.toolset == task[:family].to_s }.each do |action|
|
|
427
|
+
tag = "#{mode}/train/#{round}/#{task[:id]}/#{action.name}"
|
|
428
|
+
policy.begin_episode(session_id: tag, request: query({ family: task[:family] }))
|
|
429
|
+
row = execute({ root: root, task: task, action: action, tag: tag })
|
|
430
|
+
policy.observe_step(session_id: tag, action: action.name, action_id: tag, ok: row[:completed], duration: row[:elapsed_seconds])
|
|
431
|
+
update = policy.finish(
|
|
432
|
+
session_id: tag, score: row[:completed] ? 1.0 : 0.0, confidence: 1.0, verdict: row[:completed] ? 'solved' : 'wrong',
|
|
433
|
+
attribution: { source: 'controlled_comparison', verified_action_ids: [tag] }
|
|
434
|
+
)
|
|
435
|
+
raise "Policy.finish failed: #{update.inspect}" if update.nil? || update[:error]
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
if snapshot_dir
|
|
440
|
+
snapshot = policy.load.merge(updated_at: nil)
|
|
441
|
+
File.open(File.join(snapshot_dir, "#{mode}.json"), File::WRONLY | File::CREAT | File::EXCL, 0o600) { |file| file.write(JSON.generate(snapshot)) }
|
|
442
|
+
end
|
|
443
|
+
evaluate_arm({ root: root, mode: mode, train: train })
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
private_class_method def self.evaluate_arm(opts = {})
|
|
447
|
+
root = opts.fetch(:root)
|
|
448
|
+
mode = opts.fetch(:mode)
|
|
449
|
+
train = opts.fetch(:train)
|
|
450
|
+
seed = opts.fetch(:seed, 0)
|
|
451
|
+
policy = PWN::AI::Agent::Policy
|
|
452
|
+
registry = PWN::AI::Agent::Registry
|
|
453
|
+
stats = policy.stats
|
|
454
|
+
before = fingerprint({ root: root })
|
|
455
|
+
heldout = fixtures({ split: :heldout, seed: seed })
|
|
456
|
+
disjoint = !train.map { |task| task[:input] }.intersect?(heldout.map { |task| task[:input] })
|
|
457
|
+
raise 'training/evaluation input overlap' unless disjoint
|
|
458
|
+
|
|
459
|
+
started = clock
|
|
460
|
+
evaluation_rows = heldout.flat_map do |task|
|
|
461
|
+
attempts = []
|
|
462
|
+
3.times do |attempt|
|
|
463
|
+
pool = registry.all.select { |entry| entry.toolset == task[:family].to_s }
|
|
464
|
+
ranked = registry.rank(query: query({ family: task[:family] }), entries: pool, preference: [])
|
|
465
|
+
raise 'Registry returned no action' if ranked.empty?
|
|
466
|
+
|
|
467
|
+
row = execute({ root: root, task: task, action: ranked.first, tag: "#{mode}/heldout/#{task[:id]}/#{attempt}" })
|
|
468
|
+
attempts << row.merge(attempt: attempt + 1, ranking: ranked.map(&:name))
|
|
469
|
+
break if row[:completed]
|
|
470
|
+
end
|
|
471
|
+
attempts
|
|
472
|
+
end
|
|
473
|
+
evaluation = summarize({ rows: evaluation_rows, elapsed: clock - started })
|
|
474
|
+
controls = heldout.flat_map do |task|
|
|
475
|
+
names = task[:family] == :numbers ? %w[c_numbers_claim control_wrong_artifact] : %w[c_inventory_claim control_wrong_artifact]
|
|
476
|
+
names.map { |name| execute({ root: root, task: task, action: registry.lookup(name: name), tag: "#{mode}/controls/#{task[:id]}/#{name}" }) }
|
|
477
|
+
end
|
|
478
|
+
positive = heldout.map do |task|
|
|
479
|
+
name = task[:family] == :numbers ? 'b_numeric_sort' : 'a_active_filter'
|
|
480
|
+
execute({ root: root, task: task, action: registry.lookup(name: name), tag: "#{mode}/positive/#{task[:id]}" })
|
|
481
|
+
end
|
|
482
|
+
frozen = before == fingerprint({ root: root })
|
|
483
|
+
raise 'held-out evaluation changed persisted policy' unless frozen
|
|
484
|
+
raise 'a negative control defeated the independent scorer' unless controls.all? { |row| row[:false_success] && !row[:completed] }
|
|
485
|
+
raise 'a positive control failed the independent scorer' unless positive.all? { |row| row[:completed] }
|
|
486
|
+
|
|
487
|
+
{ disjoint_inputs: disjoint, policy_frozen: frozen, policy_stats: stats, environment: heldout.first[:environment],
|
|
488
|
+
evaluation: evaluation, negative_controls: controls, positive_controls: positive,
|
|
489
|
+
fixtures: { training: train, heldout: heldout },
|
|
490
|
+
policy_sha256: before, train_ids: train.map { |task| task[:id] }, heldout_ids: heldout.map { |task| task[:id] } }
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
private_class_method def self.summarize(opts = {})
|
|
494
|
+
rows = opts.fetch(:rows)
|
|
495
|
+
elapsed = opts.fetch(:elapsed)
|
|
496
|
+
tasks = rows.group_by { |row| row[:task_id] }
|
|
497
|
+
completed = tasks.count { |_, attempts| attempts.any? { |row| row[:completed] } }
|
|
498
|
+
failures = Hash.new(0)
|
|
499
|
+
rows.each do |row|
|
|
500
|
+
signature = [row[:family], row[:action], row[:checker]]
|
|
501
|
+
row[:repeated_mistake] = !row[:completed] && failures[signature].positive?
|
|
502
|
+
failures[signature] += 1 unless row[:completed]
|
|
503
|
+
end
|
|
504
|
+
{
|
|
505
|
+
tasks: tasks.length, completed: completed, completion_rate: completed.to_f / tasks.length,
|
|
506
|
+
artifact_check_score: rows.sum { |row| row[:artifact_check_score] } / rows.length,
|
|
507
|
+
false_successes: rows.count { |row| row[:false_success] },
|
|
508
|
+
false_success_rate: rows.count { |row| row[:false_success] }.to_f / rows.length,
|
|
509
|
+
repeated_mistakes: rows.count { |row| row[:repeated_mistake] },
|
|
510
|
+
tool_calls: rows.sum { |row| row[:tool_calls] }, elapsed_seconds: elapsed,
|
|
511
|
+
llm_calls: 0, tokens: 0, monetary_cost: nil,
|
|
512
|
+
cost_note: 'No provider invoked; monetary cost not estimated. Local compute cost is not priced.',
|
|
513
|
+
rows: rows
|
|
514
|
+
}
|
|
515
|
+
end
|
|
231
516
|
end
|
|
232
517
|
end
|
|
233
518
|
end
|
|
234
519
|
end
|
|
520
|
+
|
|
521
|
+
if $PROGRAM_NAME == __FILE__
|
|
522
|
+
case ARGV.first
|
|
523
|
+
when '--snapshot-evaluation'
|
|
524
|
+
request = JSON.parse($stdin.read, symbolize_names: true)
|
|
525
|
+
puts JSON.generate(PWN::AI::Agent::PolicyEvaluation.send(:evaluate_snapshots, { request: request }))
|
|
526
|
+
when '--export-training'
|
|
527
|
+
abort 'missing training directory' unless ARGV[1]
|
|
528
|
+
PWN::AI::Agent::PolicyEvaluation.send(:write_training_snapshots, { dir: ARGV[1] })
|
|
529
|
+
else
|
|
530
|
+
abort 'internal policy evaluation worker only'
|
|
531
|
+
end
|
|
532
|
+
end
|
data/lib/pwn/backups.rb
CHANGED
|
@@ -78,7 +78,9 @@ module PWN
|
|
|
78
78
|
next false unless File.file?(state) && !File.symlink?(state)
|
|
79
79
|
|
|
80
80
|
record = JSON.parse(File.binread(state))
|
|
81
|
-
|
|
81
|
+
# Capture pins that nothing references may be expired. Policy pins and
|
|
82
|
+
# evidence references are already in `pins`. Interrupted generations are not.
|
|
83
|
+
record['version'] == 1 && record['state'] == 'complete'
|
|
82
84
|
end
|
|
83
85
|
count = [names.length - LIMIT + 1, 0].max
|
|
84
86
|
raise IOError, 'Backup capacity occupied by legacy, pinned or interrupted generations' if candidates.length < count
|
|
@@ -84,6 +84,7 @@ module PWN
|
|
|
84
84
|
|
|
85
85
|
# Init ncurses UI (idempotent) with separate RX (top) and TX (bottom) panes
|
|
86
86
|
Curses.init_screen
|
|
87
|
+
PWN.const_set(:MeshCurses, true)
|
|
87
88
|
Curses.curs_set(0)
|
|
88
89
|
Curses.noecho
|
|
89
90
|
Curses.cbreak
|
|
@@ -340,7 +341,10 @@ class << PWN::Plugins::REPL # rubocop:disable Metrics/ClassLength
|
|
|
340
341
|
top = Integer(opts[:top])
|
|
341
342
|
left = Integer(opts[:left])
|
|
342
343
|
win = PWN.const_defined?(name) ? PWN.const_get(name) : nil
|
|
344
|
+
ready = PWN.const_defined?(:MeshCurses) && PWN::MeshCurses == true
|
|
343
345
|
if win.nil?
|
|
346
|
+
return nil unless ready
|
|
347
|
+
|
|
344
348
|
win = Curses::Window.new(height, width, top, left)
|
|
345
349
|
PWN.const_set(name, win)
|
|
346
350
|
elsif win.respond_to?(:resize) && win.respond_to?(:move)
|
|
@@ -349,6 +353,8 @@ class << PWN::Plugins::REPL # rubocop:disable Metrics/ClassLength
|
|
|
349
353
|
win.erase if win.respond_to?(:erase)
|
|
350
354
|
else
|
|
351
355
|
mesh_drop_win(name: name)
|
|
356
|
+
return nil unless ready
|
|
357
|
+
|
|
352
358
|
win = Curses::Window.new(height, width, top, left)
|
|
353
359
|
PWN.const_set(name, win)
|
|
354
360
|
end
|
|
@@ -403,7 +409,7 @@ class << PWN::Plugins::REPL # rubocop:disable Metrics/ClassLength
|
|
|
403
409
|
conv = layout[:conversation]
|
|
404
410
|
mesh_place_win(name: :MeshRxHeaderWin, height: layout[:header], width: layout[:cols], top: 0, left: 0, scroll: false)
|
|
405
411
|
frame = mesh_place_win(name: :MeshRxFrameWin, height: layout[:body], width: conv, top: body_start, left: 0, scroll: false)
|
|
406
|
-
mesh_box!(win: frame)
|
|
412
|
+
mesh_box!(win: frame) if frame
|
|
407
413
|
mesh_place_win(
|
|
408
414
|
name: :MeshRxBodyWin,
|
|
409
415
|
height: [layout[:body] - 2, 1].max,
|
|
@@ -428,8 +434,10 @@ class << PWN::Plugins::REPL # rubocop:disable Metrics/ClassLength
|
|
|
428
434
|
mesh_drop_win(name: :MeshRcvrWin)
|
|
429
435
|
end
|
|
430
436
|
tx = mesh_place_win(name: :MeshTxWin, height: layout[:tx], width: layout[:cols], top: layout[:tx_top], left: 0, scroll: false)
|
|
431
|
-
|
|
432
|
-
|
|
437
|
+
if tx
|
|
438
|
+
tx.keypad(true) if tx.respond_to?(:keypad)
|
|
439
|
+
tx.timeout = 100 if tx.respond_to?(:timeout=)
|
|
440
|
+
end
|
|
433
441
|
PWN.const_set(:MeshMutex, Mutex.new) unless PWN.const_defined?(:MeshMutex)
|
|
434
442
|
state = PWN.const_defined?(:MeshRxState) ? PWN::MeshRxState : nil
|
|
435
443
|
mesh_set_focus(focus: :conversation) if state.is_a?(Hash) && %i[sndr rcvr].include?(state[:focus]) && !mesh_side_panes?
|
data/lib/pwn/plugins/repl.rb
CHANGED
|
@@ -729,6 +729,14 @@ module PWN
|
|
|
729
729
|
PWN.const_get(:MeshTxWin).close
|
|
730
730
|
PWN.send(:remove_const, :MeshTxWin)
|
|
731
731
|
end
|
|
732
|
+
%i[MeshSndrWin MeshRcvrWin].each do |name|
|
|
733
|
+
next unless PWN.const_defined?(name)
|
|
734
|
+
|
|
735
|
+
win = PWN.const_get(name)
|
|
736
|
+
win.close if win.respond_to?(:close)
|
|
737
|
+
PWN.send(:remove_const, name)
|
|
738
|
+
end
|
|
739
|
+
PWN.send(:remove_const, :MeshCurses) if PWN.const_defined?(:MeshCurses)
|
|
732
740
|
PWN.send(:remove_const, :MeshColors) if PWN.const_defined?(:MeshColors)
|
|
733
741
|
PWN.send(:remove_const, :MeshLastColor) if PWN.const_defined?(:MeshLastColor)
|
|
734
742
|
PWN.send(:remove_const, :MeshMutex) if PWN.const_defined?(:MeshMutex)
|
data/lib/pwn/version.rb
CHANGED
|
@@ -60,14 +60,9 @@ describe 'PWN::AI::Agent::PolicyEvaluation' do
|
|
|
60
60
|
expect(second[:isolation][:environment_keys]).to eq(%w[HOME LANG TMPDIR])
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
it 'exports learned snapshots
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
RbConfig.ruby, File.expand_path('../../../../../scripts/benchmark_policy.rb', __dir__),
|
|
67
|
-
'--heldout', '--snapshot-dir', File.join(@root, 'snapshots'), unsetenv_others: true
|
|
68
|
-
)
|
|
69
|
-
expect(status.success?).to be(true), error
|
|
70
|
-
report = JSON.parse(output, symbolize_names: true)
|
|
63
|
+
it 'exports learned snapshots through PolicyEvaluation without a standalone script' do
|
|
64
|
+
evaluator = PWN::AI::Agent::PolicyEvaluation
|
|
65
|
+
report = evaluator.export_training(dir: File.join(@root, 'snapshots'))
|
|
71
66
|
expect(report[:heldout].map { |row| row[:seed] }).to eq([0, 1])
|
|
72
67
|
expect(File.file?(File.join(@root, 'snapshots', 'on.json'))).to be(true)
|
|
73
68
|
report[:heldout].each do |row|
|
|
@@ -75,6 +70,7 @@ describe 'PWN::AI::Agent::PolicyEvaluation' do
|
|
|
75
70
|
expect(row[:arms][:candidate][:evaluation][:completed]).to be > row[:arms][:baseline][:evaluation][:completed]
|
|
76
71
|
end
|
|
77
72
|
expect(report).not_to have_key(:promoted)
|
|
73
|
+
expect(File).not_to exist(File.expand_path('../../../../../scripts/benchmark_policy.rb', __dir__))
|
|
78
74
|
end
|
|
79
75
|
|
|
80
76
|
it 'requires opt-in and promotes a replay-verified candidate with a byte-exact rollback snapshot' do
|
|
@@ -83,13 +79,8 @@ describe 'PWN::AI::Agent::PolicyEvaluation' do
|
|
|
83
79
|
live = File.join(@root, 'live.json')
|
|
84
80
|
File.write(live, File.read(@baseline))
|
|
85
81
|
expect(evaluator.promote(live_path: live)).to include(promoted: false, reason: 'disabled')
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
File.expand_path('../../../../../scripts/benchmark_policy.rb', __dir__),
|
|
89
|
-
'--heldout', '--snapshot-dir', File.join(@root, 'snapshots'), unsetenv_others: true
|
|
90
|
-
)
|
|
91
|
-
expect(status.success?).to be(true), error
|
|
92
|
-
reports = JSON.parse(output, symbolize_names: true)[:heldout]
|
|
82
|
+
report = evaluator.export_training(dir: File.join(@root, 'snapshots'))
|
|
83
|
+
reports = report[:heldout]
|
|
93
84
|
baseline = File.join(@root, 'snapshots', 'off.json')
|
|
94
85
|
candidate = File.join(@root, 'snapshots', 'on.json')
|
|
95
86
|
File.write(live, File.read(baseline))
|
|
@@ -214,13 +205,9 @@ describe 'PWN::AI::Agent::PolicyEvaluation' do
|
|
|
214
205
|
end.to raise_error(ArgumentError, /regular file/)
|
|
215
206
|
end
|
|
216
207
|
|
|
217
|
-
it '
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
'--self-check', unsetenv_others: true
|
|
222
|
-
)
|
|
223
|
-
expect(status.success?).to be(true), error
|
|
224
|
-
expect(output).to include('Snapshot self-checks passed', 'Self-checks passed')
|
|
208
|
+
it 'does not keep a standalone benchmark script' do
|
|
209
|
+
expect(File).not_to exist(File.expand_path('../../../../../scripts/benchmark_policy.rb', __dir__))
|
|
210
|
+
source = File.read(PWN::AI::Agent::PolicyEvaluation.method(:evaluate).source_location.first)
|
|
211
|
+
expect(source).not_to include('scripts/benchmark_policy.rb')
|
|
225
212
|
end
|
|
226
213
|
end
|
data/spec/lib/pwn/ai/cli_spec.rb
CHANGED
|
@@ -88,16 +88,11 @@ RSpec.describe 'PWN::AI::CLI' do
|
|
|
88
88
|
|
|
89
89
|
it 'promotes only with both operator acknowledgements and rolls back using the saved receipt' do
|
|
90
90
|
Dir.mktmpdir('pwn-cli-policy-', '/tmp') do |root|
|
|
91
|
-
|
|
92
|
-
{ 'HOME' => root, 'LANG' => 'C.UTF-8' }, RbConfig.ruby,
|
|
93
|
-
File.expand_path('../../../../scripts/benchmark_policy.rb', __dir__),
|
|
94
|
-
'--heldout', '--snapshot-dir', File.join(root, 'snapshots'), unsetenv_others: true
|
|
95
|
-
)
|
|
96
|
-
expect(status.success?).to be(true), error
|
|
91
|
+
report = PWN::AI::Agent::PolicyEvaluation.export_training(dir: File.join(root, 'snapshots'))
|
|
97
92
|
baseline = File.join(root, 'snapshots', 'off.json')
|
|
98
93
|
candidate = File.join(root, 'snapshots', 'on.json')
|
|
99
94
|
reports = File.join(root, 'reports.json')
|
|
100
|
-
File.write(reports, JSON.generate(
|
|
95
|
+
File.write(reports, JSON.generate(report.fetch(:heldout)))
|
|
101
96
|
live = File.join(root, 'live.json')
|
|
102
97
|
FileUtils.cp(baseline, live)
|
|
103
98
|
argv = ['--policy', 'promote', '--baseline', baseline, '--candidate', candidate, '--reports', reports, '--live-policy', live]
|
|
@@ -19,6 +19,21 @@ describe PWN::Backups do
|
|
|
19
19
|
expect(Dir.children(root).count { |name| File.directory?(File.join(root, name)) }).to eq(7)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
it 'expires an unreferenced capture pin when capacity is full and keeps a referenced pin' do
|
|
23
|
+
root = File.join(@home, '.pwn', 'backup')
|
|
24
|
+
pinned = 7.times.map { described_class.capture(path: @source, label: 'policy-evaluation', pin: true) }
|
|
25
|
+
generations = pinned.map { |path| File.dirname(path, 2) }.uniq
|
|
26
|
+
generations.each_with_index { |dir, index| File.utime(Time.at(1_700_000_000 + index), Time.at(1_700_000_000 + index), dir) }
|
|
27
|
+
oldest_dir = generations.min_by { |dir| File.mtime(dir) }
|
|
28
|
+
referenced_dir = generations.max_by { |dir| File.mtime(dir) }
|
|
29
|
+
referenced = pinned.find { |path| path.start_with?("#{referenced_dir}/") }
|
|
30
|
+
File.write(File.join(@home, '.pwn', 'policy.json'), JSON.generate(rollback_path: referenced))
|
|
31
|
+
described_class.capture(path: @source, label: 'migration')
|
|
32
|
+
expect(File).not_to exist(oldest_dir)
|
|
33
|
+
expect(File).to exist(referenced)
|
|
34
|
+
expect(Dir.children(root).count { |name| File.directory?(File.join(root, name)) }).to eq(7)
|
|
35
|
+
end
|
|
36
|
+
|
|
22
37
|
it 'keeps interrupted transactions prepared and refuses an eighth pinned generation' do
|
|
23
38
|
7.times do
|
|
24
39
|
expect do
|
|
@@ -118,6 +118,8 @@ describe PWN::Plugins::REPL, 'mesh terminal layout' do
|
|
|
118
118
|
allow(Curses).to receive(:stdscr).and_return(double('stdscr', erase: nil, noutrefresh: nil))
|
|
119
119
|
allow(Curses).to receive(:doupdate)
|
|
120
120
|
expect { described_class.send(:mesh_relayout!, lines: 24, cols: 100) }.not_to raise_error
|
|
121
|
+
expect(PWN.const_defined?(:MeshSndrWin)).to be(false)
|
|
122
|
+
expect(PWN.const_defined?(:MeshRcvrWin)).to be(false)
|
|
121
123
|
expect(PWN::MeshTxWin).to equal(old)
|
|
122
124
|
expect(old).not_to have_received(:close)
|
|
123
125
|
ensure
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -717,14 +717,30 @@
|
|
|
717
717
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Policy.warmup! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Policy.warmup!`: "}]}
|
|
718
718
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Policy.write_q! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Policy.write_q!`: "}]}
|
|
719
719
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.authors Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.authors`: "}]}
|
|
720
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.clock Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.clock`: "}]}
|
|
720
721
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.deterministic Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.deterministic`: "}]}
|
|
721
722
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.evaluate Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.evaluate`: "}]}
|
|
723
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.evaluate_arm Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.evaluate_arm`: "}]}
|
|
724
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.evaluate_snapshots Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.evaluate_snapshots`: "}]}
|
|
725
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.execute Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.execute`: "}]}
|
|
726
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.export_training Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.export_training`: "}]}
|
|
727
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.fingerprint Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.fingerprint`: "}]}
|
|
728
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.fixtures Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.fixtures`: "}]}
|
|
722
729
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.gate Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.gate`: "}]}
|
|
723
730
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.help Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.help`: "}]}
|
|
724
731
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.promote Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.promote`: "}]}
|
|
732
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.query Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.query`: "}]}
|
|
725
733
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.read_snapshot Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.read_snapshot`: "}]}
|
|
734
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.refuse_live_agent Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.refuse_live_agent`: "}]}
|
|
735
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.register_actions Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.register_actions`: "}]}
|
|
726
736
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.replace Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.replace`: "}]}
|
|
727
737
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.rollback Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.rollback`: "}]}
|
|
738
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.run_arm Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.run_arm`: "}]}
|
|
739
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.score Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.score`: "}]}
|
|
740
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.spawn_worker Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.spawn_worker`: "}]}
|
|
741
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.summarize Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.summarize`: "}]}
|
|
742
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.with_isolated Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.with_isolated`: "}]}
|
|
743
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::PolicyEvaluation.write_training_snapshots Usage"},{"role":"assistant","content":"`PWN::AI::Agent::PolicyEvaluation.write_training_snapshots`: "}]}
|
|
728
744
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Profiles.authors Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Profiles.authors`: "}]}
|
|
729
745
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Profiles.help Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Profiles.help`: "}]}
|
|
730
746
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Profiles.new Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Profiles.new`: "}]}
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pwn
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.787
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- 0day Inc.
|
|
@@ -6613,8 +6613,6 @@ files:
|
|
|
6613
6613
|
- packer/provisioners/zzuf.sh
|
|
6614
6614
|
- pwn.gemspec
|
|
6615
6615
|
- reinstall_gemset.sh
|
|
6616
|
-
- scripts/benchmark_policy.rb
|
|
6617
|
-
- scripts/build_gate.rb
|
|
6618
6616
|
- spec/conventions_spec.rb
|
|
6619
6617
|
- spec/documentation/installation_md_spec.rb
|
|
6620
6618
|
- spec/documentation/prompt_version_placeholder_spec.rb
|
|
@@ -6693,7 +6691,6 @@ files:
|
|
|
6693
6691
|
- spec/fixtures/yubikey/apdu_frames.json
|
|
6694
6692
|
- spec/integration/authentication_helper_oath_spec.rb
|
|
6695
6693
|
- spec/integration/bin_drivers_spec.rb
|
|
6696
|
-
- spec/integration/build_gate_spec.rb
|
|
6697
6694
|
- spec/integration/config_spec.rb
|
|
6698
6695
|
- spec/integration/controller_spill_spec.rb
|
|
6699
6696
|
- spec/integration/dispatch_spec.rb
|