pwn 0.5.783 → 0.5.784

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -3
  3. data/Rakefile +19 -1
  4. data/bin/pwn_setup +1 -1
  5. data/build_gem.sh +1 -1
  6. data/documentation/Backup-Retention.md +27 -0
  7. data/documentation/Cron.md +64 -35
  8. data/documentation/How-PWN-Works.md +1 -1
  9. data/documentation/Installation.md +247 -6
  10. data/documentation/Persistence.md +1 -1
  11. data/documentation/Reinforcement-Learning.md +5 -6
  12. data/documentation/diagrams/cron-scheduling.svg +130 -128
  13. data/documentation/diagrams/dot/cron-scheduling.dot +3 -3
  14. data/etc/default_cron/session_cleanup.rb +1217 -10
  15. data/lib/pwn/ai/agent/extrospection.rb +6 -7
  16. data/lib/pwn/ai/agent/learning.rb +48 -7
  17. data/lib/pwn/ai/agent/policy.rb +5 -0
  18. data/lib/pwn/ai/agent/swarm.rb +31 -16
  19. data/lib/pwn/ai/router.rb +45 -2
  20. data/lib/pwn/cron.rb +120 -49
  21. data/lib/pwn/migrate.rb +292 -15
  22. data/lib/pwn/plugins/binary_parser.rb +5 -4
  23. data/lib/pwn/plugins/ghidra.rb +4 -3
  24. data/lib/pwn/plugins/log.rb +18 -2
  25. data/lib/pwn/plugins/repl/ai.rb +12 -1
  26. data/lib/pwn/plugins/repl/ai_console.rb +6 -2
  27. data/lib/pwn/plugins/repl/ai_swarm.rb +4 -4
  28. data/lib/pwn/sessions.rb +58 -16
  29. data/lib/pwn/setup.rb +9 -17
  30. data/lib/pwn/version.rb +1 -1
  31. data/scripts/build_gate.rb +58 -0
  32. data/spec/integration/build_gate_spec.rb +73 -0
  33. data/spec/integration/persistence_roundtrip_spec.rb +1 -7
  34. data/spec/lib/pwn/ai/agent/policy_spec.rb +27 -0
  35. data/spec/lib/pwn/ai/router_spec.rb +30 -2
  36. data/spec/lib/pwn/cron_spec.rb +113 -6
  37. data/spec/lib/pwn/migrate_spec.rb +160 -5
  38. data/spec/lib/pwn/plugins/log_spec.rb +7 -0
  39. data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +16 -4
  40. data/spec/lib/pwn/plugins/repl/ai_swarm_spec.rb +11 -0
  41. data/spec/lib/pwn/redaction_spec.rb +13 -10
  42. data/spec/lib/pwn/session_cleanup_concurrency_spec.rb +201 -0
  43. data/spec/lib/pwn/session_cleanup_default_spec.rb +165 -2
  44. data/spec/lib/pwn/session_cleanup_legacy_spec.rb +101 -0
  45. data/spec/lib/pwn/session_cleanup_payload_spec.rb +335 -2
  46. data/spec/lib/pwn/session_cleanup_recovery_spec.rb +389 -0
  47. data/spec/lib/pwn/sessions_spec.rb +112 -0
  48. data/spec/lib/pwn/setup_spec.rb +3 -1
  49. data/spec/support/gate_failure_formatter.rb +14 -0
  50. data/third_party/pwn_rdoc.jsonl +15 -1
  51. metadata +14 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43f675e7e241c2f9c2ac2b96c52b43259b337871bc3227fb2cbac832457f7f23
4
- data.tar.gz: c053c15cff377d88ee43b25103203928eaa037c3bca976fd6d6483d5543ad5fd
3
+ metadata.gz: 41b180063b404ae6511a5df181eb969dcf2970b1ac128225369d6ec69afb30cc
4
+ data.tar.gz: 7936dc0f2c4552327650e74f3f0af790da4fbd353ca5d48cb651542d3f550acd
5
5
  SHA512:
6
- metadata.gz: eca4d79da6a33b24ec42cbc39c7c07afd0cfec053f98a49655b2bd6ef3c93bbba32f91aa90e755fe88eb8ec7bb13db892f87ade9e6b715b2d14f0b867415e576
7
- data.tar.gz: 8c8dad0dd07d97a0b4941fadf03683dd14cfe7fe045c06431d7f725c39ea4dadaac76988a722efaa9186abe71fa4ba731b95c8ae0fa58b1efd0d7baa367f45b1
6
+ metadata.gz: 1184f82a572f136b6421f04059b5e05e79642e7f68337f376392c816aeb023b0bd0d74c15f0916c84078ad7188d255f0776ac04d2a87cbb31e9ee8fa59e6dc16
7
+ data.tar.gz: 851035353589425ab5ff5fdefb04c9c2a1f2c91fce7cb5d1e02067bd0eebb16905284bbf6cd7cde6373fa2ea9b7b1f6dbf531089ee9319988bd62ec7e83cdeb1
data/Gemfile CHANGED
@@ -22,7 +22,7 @@ gem 'brakeman', '8.1.0'
22
22
  gem 'bson', '5.2.0'
23
23
  gem 'bundler', '>=4.0.23'
24
24
  gem 'bundler-audit', '>=0.9.3'
25
- gem 'bunny', '3.4.0'
25
+ gem 'bunny', '3.5.0'
26
26
  gem 'colorize', '1.1.0'
27
27
  gem 'credit_card_validations', '10.0.0'
28
28
  gem 'curses', '1.7.0'
@@ -49,7 +49,7 @@ gem 'jwt', '3.3.0'
49
49
  gem 'libusb', '0.8.0'
50
50
  gem 'luhn', '3.0.0'
51
51
  gem 'mail', '2.9.1'
52
- gem 'mcp', '1.8.1'
52
+ gem 'mcp', '1.8.2'
53
53
  gem 'meshtastic', '0.0.189'
54
54
  gem 'metasm', '1.0.6'
55
55
  gem 'mongo', '2.26.0'
@@ -94,7 +94,7 @@ gem 'selenium-webdriver', '4.51.0'
94
94
  gem 'slack-ruby-client', '3.3.0'
95
95
  gem 'socksify', '1.8.1'
96
96
  gem 'spreadsheet', '1.3.5'
97
- gem 'sqlite3', '2.10.1'
97
+ gem 'sqlite3', '2.10.2'
98
98
  gem 'thin', '2.0.1'
99
99
  gem 'tty-prompt', '0.23.1'
100
100
  gem 'tty-spinner', '0.9.3'
data/Rakefile CHANGED
@@ -5,12 +5,30 @@
5
5
  exec('bundle', 'exec', 'rake', *ARGV) unless ENV['BUNDLE_BIN_PATH'] || ENV['RUBYOPT'].to_s.include?('bundler/setup')
6
6
 
7
7
  require 'bundler/setup'
8
+ require 'English'
8
9
  require 'bundler/gem_tasks'
9
10
  require 'rspec/core/rake_task'
10
11
  require 'rdoc/task'
11
12
  require 'rubocop/rake_task'
12
13
 
13
- RSpec::Core::RakeTask.new(:spec)
14
+ # RSpec's stock task maps a signalled child to exit 1 without naming the
15
+ # signal. Preserve that evidence when an interrupted run has no summary.
16
+ class DiagnosticSpecTask < RSpec::Core::RakeTask
17
+ def run_task(verbose)
18
+ super
19
+ rescue SystemExit
20
+ status = $CHILD_STATUS
21
+ if status
22
+ result = status.signaled? ? "signal=#{Signal.signame(status.termsig)}" : "exit=#{status.exitstatus}"
23
+ warn "RSpec child failed: #{result}"
24
+ end
25
+ raise
26
+ end
27
+ end
28
+
29
+ DiagnosticSpecTask.new(:spec) do |task|
30
+ task.rspec_opts = "#{task.rspec_opts} --require ./spec/support/gate_failure_formatter.rb --format progress --format GateFailureFormatter"
31
+ end
14
32
  task :silence_rspec_filter_banner do
15
33
  ENV['PWN_RAKE_SPEC'] = '1'
16
34
  end
data/bin/pwn_setup CHANGED
@@ -58,7 +58,7 @@ pwn_driver = PWN::Driver::Parser.new do |options|
58
58
  end
59
59
 
60
60
  options.on('--[no-]cron',
61
- '<Optional - Enable (default) or disable OS-native persistence of pwn-ai cron jobs>') do |o|
61
+ '<Optional - Enable (default) or disable the built-in pwn-ai cron worker; no OS scheduler changes>') do |o|
62
62
  opts[:cron] = o
63
63
  end
64
64
  end
data/build_gem.sh CHANGED
@@ -44,7 +44,7 @@ if [[ $old_ruby_version == $new_ruby_version ]]; then
44
44
  export rvmsudo_secure_path=1
45
45
  rvmsudo /bin/bash --login -c "cd ${pwn_root} && ./reinstall_gemset.sh"
46
46
  cd /tmp && cd "$pwn_root"
47
- rvmsudo /bin/bash --login -c "cd ${pwn_root} && bundle exec rake"
47
+ ruby "${pwn_root}/scripts/build_gate.rb" rvmsudo /bin/bash --login -c "cd ${pwn_root} && bundle exec rake"
48
48
  rvmsudo /bin/bash --login -c "cd ${pwn_root} && bundle exec rake install"
49
49
  rvmsudo /bin/bash --login -c "cd ${pwn_root} && bundle exec rake rerdoc"
50
50
  echo "Invoking bundle-audit Gemfile Scanner..."
@@ -0,0 +1,27 @@
1
+ # Historical migration backup retention
2
+
3
+ ## Explicit legacy diagnostic and generated-copy expiry
4
+
5
+ `PWN::SessionCleanup.expire_legacy` is a separate, explicit maintenance entry point, defaulting to a preview. Apply requires `dry_run: false` and a **new external** `manifest:` path for one compressed metadata ledger; it does not archive payloads back into the managed home or run session/swarm retirement.
6
+
7
+ Only recognized expired closed request logs, the exact `learning.jsonl.bak-lean-YYYYMMDD` and `preferences.jsonl.bak-p15-YYYYMMDDhhmmss` recovery families, and verified duplicate BinaryParser triage caches qualify. Root recovery retains at least the latest three nonempty parseable copies per family, all literal reference pins, and copies younger than thirty days. Cache expiry requires byte-identical retained ArtifactRegistry evidence; absent inputs are not claimed reproducible. Decompiler stdout caches are not adopted by this policy.
8
+
9
+ The operation holds the canonical maintenance lock, rejects unsafe path/inode identities, checks retained references without exposing content, and checks process descriptors before classification and unlink. Existing producer leases must be available. Retained recovery/evidence proofs and candidate hashes are rechecked before unlink. An unreadable process inventory fails closed; do not bypass it when an older client is active. Coordinate any legacy writers before applying, and keep the learning producer lock during root recovery expiry. The external ledger records prepared versus completed state; it is not an undo archive. Existing immutable workers require a separately verified matched release before this entry point is deployed; it is not automatically enabled by importing the source.
10
+
11
+ ## Migration generations
12
+
13
+ Migration backups are recovery generations, not an indefinite archive of every distinct historical byte. `PWN::Migrate.prune_backups` stays duplicate-only until an operator explicitly authorizes historical expiry. Its optional `expire_history: true` argument or private `~/.pwn/backup/.retention-policy.json` enables generation retention:
14
+
15
+ ```json
16
+ {"version":1,"expire_history":true,"keep":3,"pins":[]}
17
+ ```
18
+
19
+ `keep` cannot be less than three. `pins` contains exact timestamp generation directory names. Current structured evidence paths and prepared/interrupted or explicitly pinned maintenance records also retain their referenced generations. Quoted directory inventories and historical prose are not evidence dependencies. No logs, metrics, or historical learning versions are copied aside merely because their bytes are unique.
20
+
21
+ Before expiry, the newest retained point must cover every existing current migration-owned input path, and current encrypted configuration and decryptor bytes must match retained recovery copies. Every retained and expiring payload is hashed, current inputs and references are rechecked, symlinks are rejected, and deletion shares the snapshot producer's permanent `.retention.lock`. The ordinary maintenance transaction supplies its own maintenance lock and cooperative work admission. Manual backup-only calls must also coordinate live writers before apply. Default automatic batches consider at most ten older generations; exhausted admission defers expiry rather than weakening protection.
22
+
23
+ The single mode-0600 `.retention-manifest.json.gz` contains prepared/committed inventories and expiry names, not a second payload tree. Older unpinned versions are permanently expired: that manifest is an audit record, not an undo payload. Incomplete historical snapshots are never retrospectively certified complete. Their extant payloads can be verified for bounded recovery; the newly produced complete snapshot establishes current coverage. Restore by **copying only migration-owned paths**, not overlaying an entire old home, and never edit hardlinked backup bytes in place. Keep explicit pins for any recovery boundary needed during an active migration.
24
+
25
+ Scheduled retention is called by the managed v10 `SessionCleanup.maintain` helper through PWN's internal cron worker. No native scheduler or systemd installation is needed. Use `PWN::Migrate.prune_backups(dry_run: true)` for a strictly backup-only preview; do not invoke full session maintenance to test backup expiry. When no generation can expire, this preview avoids scanning evidence bodies and reports `evidence_scan: not_needed_no_expiry`; it is not a fresh payload-integrity audit.
26
+
27
+ An isolated maintenance release may coexist with older interactive clients: run the worker from the release's immutable `lib` load path, verify source/release/live-helper SHA-256 equality, and leave the active gem and clients untouched. The cron helper does not retrofit methods into those clients. A later normal application upgrade is separate from this scoped maintenance deployment.
@@ -2,17 +2,26 @@
2
2
 
3
3
  `PWN::Cron` (`lib/pwn/cron.rb`) stores job definitions in
4
4
  `~/.pwn/cron/jobs.yml`. `pwn setup` starts (or reuses) a background cron
5
- worker and installs an OS-native keep-alive so YAML jobs fire on schedule
6
- without a per-job crontab line:
7
-
8
- - Linux: `systemd --user` (Restart=always), else `crontab @reboot`
9
- - macOS: `launchd`
10
- - Windows: `schtasks`
11
- - otherwise: `crontab @reboot` when `crontab(1)` exists
12
-
13
- You can still pass `install_crontab: true` on `cron_create` to add a
14
- per-job system line. `PWN::Cron.install_worker_crontab` is idempotent and
15
- does not remove existing per-job lines.
5
+ worker. Setup, migration and startup only seed internal job definitions;
6
+ they never install, start, repair or remove native scheduler units. After a
7
+ reboot, start/reuse the portable worker with `pwn setup --cron`; automatic
8
+ jobs require that worker, while manual commands do not. `--no-cron` disables
9
+ the internal worker without changing host services.
10
+
11
+ **Housekeeping must never use systemd or another native scheduler.**
12
+ The three enabled housekeeping defaults below use only this existing worker
13
+ or manual commands. `install_native_job` and each platform-specific job
14
+ installer explicitly skip housekeeping (`housekeeping_internal_only`),
15
+ including recognizable cleanup payloads under another name. Native worker
16
+ contexts exclude housekeeping from ticks and direct job execution.
17
+
18
+ Generic scheduler APIs remain for unrelated custom jobs: `install_crontab:
19
+ true` can still add their per-job system lines. They are not called by setup,
20
+ migration or default seeding. Existing housekeeping units/drop-ins are left
21
+ untouched and require explicit operator approval for removal; they are never
22
+ re-enabled automatically. See [Installation](Installation.md) for the legacy
23
+ service migration boundary. Do not create another worker or run cleanup just
24
+ to validate scheduling.
16
25
 
17
26
  ![Cron scheduling](diagrams/cron-scheduling.svg)
18
27
 
@@ -34,33 +43,53 @@ does not remove existing per-job lines.
34
43
  | `cron_enable` / `cron_disable` | Toggle without deleting |
35
44
  | `cron_remove` | Delete from `jobs.yml` (does **not** scrub crontab - `crontab -e` yourself) |
36
45
 
37
- ## Seeded self-improvement jobs (`PWN::Cron.install_defaults`)
38
-
39
- `pwn setup --migrate` and first-launch default installation seed six jobs into a fresh
40
- `~/.pwn/cron/jobs.yml` via `PWN::Cron.install_defaults`:
41
-
42
- | Name | Schedule | Enabled | Ruby |
43
- |---|---|---|---|
44
- | `curriculum_practice_nightly` | `0 3 * * *` | **no** | `Curriculum.practice(limit: 3)` - opt-in; runs `Loop.run` with real tools |
45
- | `curriculum_offline_judge` | `30 3 * * *` | **no** | `Curriculum.offline_judge(...)` - opt-in; spends API tokens |
46
- | `curriculum_train_weekly` | `0 4 * * 0` | **no** | `Curriculum.train_and_gate(dry_run: true)` - opt-in; needs trainer+GPU to actually train |
47
- | `session_cleanup` | `45 3 * * *` | **yes** | Guarded unsummarized-session cleanup with backups, dependency checks and activity leases; see [Installation](Installation.md) |
48
- | `learning_consolidate_nightly` | `0 5 * * *` | **yes** | `Learning.consolidate` - memory GC |
49
- | `pwn_stores_lean_nightly` | `15 5 * * *` | **yes** | `Learning.gc_stores!` - lean memory, learning.jsonl, mistakes, policy, sessions |
46
+ ## Default housekeeping and explicit curriculum opt-in
47
+
48
+ `PWN::Cron.install_defaults` seeds **one** enabled job on a fresh install:
49
+ `session_cleanup`, daily at `45 3 * * *`, guarded until its first scheduled slot.
50
+ It calls `SessionCleanup.coordinate` through the existing internal worker:
51
+
52
+ 1. Shared maintenance: preflight, evidence migration, protected-session cleanup,
53
+ and disposable storage maintenance.
54
+ 2. Memory consolidation (`Learning.consolidate`, including Memory lean).
55
+ 3. Learning outcome retention (`Learning.prune_outcomes!`).
56
+ 4. Mistakes retention (`Mistakes.lean!`).
57
+ 5. Policy trajectory retention (`Policy.lean!`).
58
+
59
+ The coordinator holds the permanent maintenance lock across these phases and
60
+ records their separate results. Blocked/incomplete maintenance skips retention;
61
+ dry-run does not consolidate memory or mutate stores. Store writer locks and
62
+ existing gold/recent/protected-entry policies remain in force. It does not call
63
+ `gc_stores!`, which would repeat memory consolidation and session cleanup.
64
+
65
+ Existing jobs are preserved verbatim, including disabled/custom schedules and
66
+ legacy curriculum aliases. Existing `learning_consolidate_nightly` and
67
+ `pwn_stores_lean_nightly` are **not** removed automatically. If either exists
68
+ when a missing session job is seeded, that job uses maintenance-only payloads
69
+ rather than duplicate retention. Consolidate those live schedules only after
70
+ all writers and the matched helper have been safely upgraded and tested.
71
+
72
+ Curriculum schedules are **not seeded at all**, including after deletion,
73
+ startup, or repeat migration. `CURRICULUM_JOBS` contains optional templates for
74
+ `curriculum_practice_nightly`, `curriculum_train_weekly`, and
75
+ `curriculum_offline_judge`. Explicitly create a selected template only when
76
+ wanted; templates default disabled:
50
77
 
51
- - **practice / judge / train** ship **disabled**. Practice self-plays via `Loop.run` (shell, browsers). Judge hits the live model. Train is a no-op without a LoRA trainer. Enable with `cron_enable` when you want that loop.
52
- - **consolidate + lean + session cleanup** stay on: they operate on local `~/.pwn` state; cleanup can delete eligible transcripts after verified backups and guards, so the injected MEMORY / LEARNING / session tail stays high-signal.
53
-
54
- - **practice** - top unresolved `Mistakes` under `Reward.judge`; auto-`resolve` with ≥2 holdouts
55
- - **offline_judge** - backfill outcome/process labels + plan calibration from PLAN `p(success)=` so `:failure_only` local introspect does not starve the corpus; also runs `Reward.warm_sentinel` so the reward-sentinel window can fill on local hosts
56
- - **dedupe** - legacy alias `offline_judge_nightly` counts as the same job; `install_defaults` will not double-seed `30 3 * * *` and disables the alias when both exist
57
- - **train** - export SFT + balanced DPO, LoRA-train `pwn-vN+1`, replay Mistakes.top, promote only on win. `dry_run: false` only with a trainer+GPU
58
- - **consolidate** - memory GC (semantic merge + importance eviction) so the injected MEMORY block stays high-signal
78
+ ```ruby
79
+ spec = PWN::Cron::CURRICULUM_JOBS.find { |job| job[:name] == 'curriculum_practice_nightly' }
80
+ PWN::Cron.create(spec.merge(enabled: false, sync_native: false))
81
+ ```
59
82
 
60
- See [Reinforcement Learning](Reinforcement-Learning.md).
83
+ Practice runs real tools, judge can spend provider tokens, and training needs
84
+ its own explicit configuration/approval. See [Reinforcement Learning](Reinforcement-Learning.md).
61
85
 
62
- `cron_disable(id:)` turns either off; `install_defaults` is idempotent (including legacy alias collapse) and
63
- never overwrites a job you already have with the same name.
86
+ For schedule-only deletion without touching native schedulers, use
87
+ `PWN::Cron.remove(id: exact_id, sync_native: false)`. It preserves job logs and
88
+ other evidence. Registry writes use a permanent lock and atomic replacement;
89
+ completion updates merge into the current registry and never resurrect a deleted
90
+ job. Old workers without this protocol must drain safely before a registry edit;
91
+ never use a forced stop while a job is writing. A current worker reloads the
92
+ registry each tick; ordinary schedule changes need no restart.
64
93
 
65
94
  ## Example
66
95
 
@@ -20,7 +20,7 @@ hardware).
20
20
  | `pwn --ai PROMPT` | `bin/pwn` | Headless one-shot agent (CI-friendly) |
21
21
  | `pwn setup` | `lib/pwn/setup.rb` · `bin/pwn_setup` | Post-install doctor + capability provisioner + `--migrate` state doctor (also `pwn --setup[=PROFILE]`) |
22
22
  | `bin/` (including `pwn-ai` and `pwn-capd`) | 56 files | Thin OptionParser wrappers plus the `pwn` REPL / one-shot agent |
23
- | `PWN::Cron` | `lib/pwn/cron.rb` | Scheduled jobs + background worker (`pwn setup`). Fresh install seeds hygiene on (`learning_consolidate_nightly`, `pwn_stores_lean_nightly`) and curriculum practice/judge/train off |
23
+ | `PWN::Cron` | `lib/pwn/cron.rb` | Scheduled jobs + background worker (`pwn setup`). Fresh install seeds one `session_cleanup` housekeeping coordinator; curriculum schedules require explicit creation. Existing schedules are preserved |
24
24
 
25
25
  ## L2 - AI agent core (`lib/pwn/ai/agent/`)
26
26
 
@@ -1,5 +1,190 @@
1
1
  # Installation
2
2
 
3
+ ## Housekeeping deployment and diagnostics
4
+
5
+ The packaged `etc/default_cron/session_cleanup.rb` is the shared implementation
6
+ for `/maintenance run`, `/maintenance dry-run`, `/sessions cleanup`, and the
7
+ managed `session_cleanup` cron job. `/maintenance status` reports physical file
8
+ counts, bytes, configured retention, budgets, incomplete scans and safe error
9
+ classes. These are storage counts, not the session menu's pagination count.
10
+ Curses executes maintenance on its existing command worker, not its render/input
11
+ thread. Ctrl+C requests a safe boundary; committed work is not rolled back.
12
+
13
+ Upgrade the installed gem, run `pwn setup --migrate --fix`, then restart PWN
14
+ clients **and the existing cron worker**. Source-checkout edits alone do not
15
+ update installed endpoints. Schema 11 seeds a version-1 housekeeping policy and
16
+ refreshes the managed helper. Existing job IDs, schedules, disabled states,
17
+ custom Ruby payloads, custom unmarked helpers and policy edits are preserved.
18
+ A custom helper is not automatically upgraded. Fresh setup is idempotent and
19
+ registration never runs cleanup; the default job retains its next-slot guard.
20
+ Startup performs fixed-path checks and coalesces a daily background request in
21
+ `maintenance/background.json`. The existing cron worker consumes it through the
22
+ existing enabled job, never through a second worker. Disabled jobs stay disabled.
23
+ Do not apply maintenance while old untracked clients are running: unknown
24
+ legacy ownership blocks mutation rather than guessing which sessions are active.
25
+
26
+ Storage is organized into logical categories without relocating literal evidence
27
+ anchors:
28
+
29
+ | Category | Existing/new locations under `~/.pwn` | Retention contract |
30
+ |---|---|---|
31
+ | sessions | `sessions/` including support and requests | Eligible unsummarized, inactive, unreferenced transcripts only; physical deletion with backups |
32
+ | learning | `learning.jsonl`, `learning/`, `memory*.json`, `mistakes.json`, policy/curriculum/extrospection stores, finetune and preference records | Evidence migration retains exact verified snapshots; unknown records remain protected |
33
+ | artifacts | `artifacts/`, `exports/`, `findings.jsonl`, engagements, corpora and binary workspaces | Durable evidence, not disposable cache |
34
+ | state | `swarm/`, `cron/`, `agents.yml`, `jobs/`, `missions/`, `runs/`, metrics | Preserve continuation associations and active work |
35
+ | logs | `logs/` | Only explicitly registered disposable closed logs are eligible; unknown logs remain |
36
+ | cache | `cache/` | Only producer-registered, checksum-verified, inactive disposable files |
37
+ | tmp | `tmp/` | Same registration contract; never glob-delete arbitrary temp files |
38
+ | maintenance | `maintenance/`, legacy `session_cleanup_archive/` | Policy, permanent locks, metadata ledgers and recoverable quarantine |
39
+
40
+ `maintenance/policy.json` is non-secret JSON, version 1. `batch_size` defaults to
41
+ 100; `scan_limit` to 100000. Cooperative admission defaults to `work_items: 100000`,
42
+ `work_seconds: 30` and `work_bytes: 67108864` for dependency and maintenance
43
+ payload reads, plus reserved disposable backup writes. Existing policy
44
+ files acquire these defaults in memory, without rewriting operator values.
45
+ `maintenance/layout.json` upgrades recognized inventory v1 to v2 separately from
46
+ policy; unknown inventory versions are refused. `retention_days` defaults to sessions 0, logs 30,
47
+ cache 7, tmp 1, maintenance 90. Null disables that category's expiry. Session
48
+ age is an additional floor, never an override of leases, provenance or evidence.
49
+ Learning/artifacts/state remain non-expiring: their policy slots are descriptive,
50
+ not authorization to erase evidence. `budget_bytes` enforces pressure eviction
51
+ for verified inactive disposable files (logs 256 MiB, cache 512 MiB, tmp 256 MiB,
52
+ maintenance 1 GiB), including before ordinary expiry. Null retention still
53
+ disables deletion. Budgets never evict protected evidence. Over-budget protected storage needs
54
+ operator review, not automatic deletion. Inventory scans are bounded and report
55
+ incompleteness rather than claiming a complete total. `bytes` counts logical file
56
+ bytes; `unique_bytes` counts each inode once per category. Directory discovery is
57
+ streamed for inventory and dependency trees. Work/time admission is cooperative
58
+ between phases and items, not a hard deadline: an admitted read, hash or atomic
59
+ commit completes. Exhausted/partial dependency indexes block deletion and report
60
+ `incomplete`/`interrupted`. Retrying recomputes dependencies; no timeout kills a writer.
61
+ Disposable cleanup reserves the complete payload transaction before its first
62
+ hash: one bounded read for preview, or five payload-size passes for verification,
63
+ backup read/write, backup verification and final source verification on apply.
64
+ Reads reject size changes and cannot follow unlimited concurrent growth. Quarantine,
65
+ legacy-backup inventory/compaction, atomic-temp recovery and transaction-status
66
+ reads also use byte admission. Policy/cursor metadata and durable ledger writes
67
+ are control overhead, not a hard total-filesystem-I/O quota; directory name
68
+ discovery is not a payload-byte allowance. An oversized item is retained, with
69
+ the overall maintenance run marked `incomplete`/`interrupted`; raise the explicit
70
+ work budget to process it. Once admitted, backup/commit finishes even if the
71
+ deadline expires mid-write.
72
+
73
+ Producer integration uses `PWN::SessionCleanup.register_disposable(path:,
74
+ category:, expires_at:)` after closing a reproducible file. Producers must hold
75
+ its permanent `<path>.lease` exclusively while writing, and readers hold it
76
+ shared throughout use. The registry records exact SHA-256, inode and byte count.
77
+ Changed, unknown, symlinked, active and invalid files are retained with reasons.
78
+ Disposable scans persist a lexical round-robin cursor in
79
+ `maintenance/disposable.cursor`; `scan_limit` bounds inspected manifests per run.
80
+ Completed/missing manifests remain for audit, including normal temporary-producer
81
+ unlinks, but cannot monopolize later scans. Active/changed entries are revisited
82
+ on the next circuit. The cursor is scheduling state only: cleanup never removes
83
+ or relocates registry entries, and rechecks registration bytes under the producer
84
+ lease so a concurrent renewal cannot authorize deletion using an old expiry.
85
+ Dry-run does not advance the cursor. A partial circuit reports `incomplete`.
86
+ Registration is not permission to classify evidence as disposable. Legacy caches
87
+ and logs are not implicitly registered. Transcript compaction/append instead use
88
+ a permanent transcript `.write.lock`; compaction temporary files are unique,
89
+ cleaned in ensure, fsynced and atomically replaced. Corrupt transcripts raise,
90
+ not silently compact into partial transcripts.
91
+
92
+ Ghidra decompile and BinaryParser triage now publish and read their canonical
93
+ caches under permanent leases. Ordinary `Log.append` diagnostics now go to
94
+ `logs/pwn.log`, rotating at 1,024,000 bytes into registered closed segments.
95
+ Debug streams hold a lifetime lease and register after request closure; active
96
+ streams cannot be evicted. Historical `/tmp/pwn.log` and learning logs are not
97
+ implicitly deleted. Cache writes and transcript compaction clean their own
98
+ temporary outputs on failure rather than leaving routine temp garbage.
99
+
100
+ Routine disposable cleanup writes exact private backups and prepared/committed
101
+ ledgers under `maintenance/quarantine/`. Only verified committed disposable
102
+ backups expire after the maintenance retention period or under budget pressure.
103
+ Interrupted `expiry_prepared` operations resume from verified remaining backups.
104
+ Broad operations also have prepared/committed/interrupted metadata ledgers under
105
+ `maintenance/runs/`, exposed by status. Prepared/corrupt ledgers
106
+ and unique evidence are never expired. `prune_legacy_backups` recognizes actual
107
+ committed session snapshots, dependency-removal backups, learning migration and
108
+ continuation-retirement ledgers. After retention or under maintenance pressure it
109
+ reclaims **redundant physical bytes** using checksum/readback-verified atomic
110
+ hardlink replacement. Recovery filenames remain intact; the last copy is never
111
+ removed. Unknown/uncommitted records are excluded. A cross-filesystem link failure
112
+ retains the original. Prepared/committed compaction journals are resumable.
113
+
114
+ `PWN::SessionCleanup.recovery_status(ledger: absolute_ledger_path)` identifies
115
+ supported actions. `recover(ledger:, action:, dry_run: true)` is explicit:
116
+ - `restore`: quarantine or legacy session copies, with no overwrite of different bytes;
117
+ - `rollback`: Learning's validated rollback or an unchanged continuation retirement;
118
+ - `rollback_dependencies`: exact targeted store rollback, after restoring source sessions;
119
+ - `resume`: re-evaluate broad maintenance, resume backup compaction, or re-evaluate
120
+ offline layout migration (apply requires `writers_stopped: true`).
121
+ Use `dry_run: false` only after reviewing the plan. Restoring a metadata ledger
122
+ never undoes data changes. Broad operations have independently committed children,
123
+ not a fictitious all-or-nothing rollback. Unknown schemas require manual review.
124
+ `reorganize` remains explicit and offline; this upgrade does not move existing paths.
125
+
126
+ ### Housekeeping acceptance ledger (isolated fixtures)
127
+
128
+ `spec/lib/pwn/session_cleanup_recovery_spec.rb` exercises the actual maintenance
129
+ entry point, without live stores or providers:
130
+
131
+ | Requirement | Regression evidence | Boundary |
132
+ |---|---|---|
133
+ | Byte admission precedes disposable payload I/O | Expired 1 MiB cache with `work_bytes: 1024` remains intact; no payload read/hash or backup, run incomplete/interrupted | Cooperative allowance, not a hard filesystem-I/O cap |
134
+ | Repeated bounded scans progress | `scan_limit: 1`, two expired caches and a normally unlinked temporary producer; repeated runs delete both caches and retain all manifests | Registry pathname discovery still enumerates names; inspection is bounded |
135
+ | Active/mutated data remains protected | Held shared lease and changed payload survive while later eligible data is processed; released lease is revisited | Requires cooperating permanent producer leases |
136
+ | Re-registration is not lost | Renewing the same file between discovery and lease acquisition retains it; republished cache survives a later cursor circuit | Cursor never moves/deletes registry records |
137
+ | Admitted commit finishes safely | Deadline expires after backup publication; exact backup and committed child deletion survive, enclosing run is interrupted | Not all-or-nothing rollback |
138
+ | Related quarantine reads respect admission | Expired 1 MiB backup exceeds an 8 KiB work allowance and remains committed/unexpired | Legacy/atomic paths also admit reads; their existing integrity/recovery regressions remain enabled |
139
+
140
+ The completed isolated-HOME `bundle exec rake` gate passed 4,568 examples with
141
+ zero failures and inspected 1,161 files with no RuboCop offenses (exit 0).
142
+ Cancellation regressions distinguish preservation before deletion admission from
143
+ completion of an already-admitted commit; redaction covers the canonical log and
144
+ debug/progress/mirror/stderr/fallback sinks. These are fixture-based verification,
145
+ not live deployment acceptance: no live-store maintenance, installed-helper
146
+ deployment or provider run is implied.
147
+
148
+ ### Temporary producer ownership audit
149
+
150
+ The audit covers `Dir.tmpdir`, `Tempfile`, `mktmpdir` and atomic replacement
151
+ call sites, not a filesystem filename sweep:
152
+ - Festival speech input now uses `SessionCleanup.temporary` in `~/.pwn/tmp`, with
153
+ a lifetime lease and exact registration. Concurrent calls have unique names;
154
+ normal completion unlinks the input and a crashed consumer leaves registered bytes.
155
+ - Ghidra/BinaryParser canonical cache publication and managed temporary inputs
156
+ journal atomic ownership (inode/device, expected hash, exact target) under
157
+ `maintenance/atomic`. `recover_atomic_temps` rejects active writers, changed or
158
+ partial bytes, missing journals and unknown paths. Routine maintenance applies
159
+ retention; explicit recovery can inspect an abandoned journal immediately.
160
+ - Sandbox/Detonate, GPS, RFID, OSINT bridge and GhidraHeadless work directories,
161
+ GDB stdin, and policy-evaluation/Solve verification trees are scoped block or
162
+ owner-cleaned resources. Old unregistered crash remnants are retained, not
163
+ inferred disposable from a prefix. GhidraHeadless's legacy/custom cache is not
164
+ silently enrolled as a canonical cache.
165
+ - Nmap/Nuclei scan output, proxy HAR, browser screenshots, APK output, Workspace
166
+ downloads, history spill and Burp diagnostics may be unique evidence. Their
167
+ existing literal paths remain; no age-based global `/tmp` cleanup is authorized.
168
+ - OAuth/vault/selection, learning/session/map and SDR image atomic outputs may be
169
+ only-copy state or evidence. Their own ensure/rollback protocols remain; unknown
170
+ remnants are not automatically discarded. Solve lock inodes are permanent.
171
+ This is an intentional ownership/evidence boundary, not a claim that every plugin
172
+ scratch file is automatically reclaimable.
173
+
174
+ Swarm registry retirement alone deliberately keeps its saved conversation.
175
+ The command route exposes `/swarm retire NAME --continuation` explicitly.
176
+ `PWN::AI::Agent::Swarm.retire(name:, swarm_id:, local: true, continuation: true)`
177
+ explicitly retires the inactive association with a backup and prepared/committed
178
+ ledger. It does not itself delete the transcript. A later persona request creates
179
+ a new session; active associations cannot be retired. Persona map writers and
180
+ cleanup share a permanent continuation lock.
181
+
182
+ Safe checks: `/maintenance status` and `/maintenance dry-run` do not call a
183
+ provider. Do not use `run` as a diagnostic on production data without reviewing
184
+ the preview. Permission and disk failures retain recoverable bytes and report
185
+ error classes; no blanket root-directory chmod is attempted.
186
+
187
+
3
188
  PWN ships as a **single Ruby gem** whose runtime is 100 % `autoload`ed -
4
189
  a plugin whose native extension or OS binary is missing costs nothing until
5
190
  you actually touch that constant. That means the install is **two steps**:
@@ -82,7 +267,7 @@ External toolchain used by
82
267
  skills/ drift 1 legacy flat file(s) PWN::Config → run --migrate --fix
83
268
  ...
84
269
 
85
- cron worker ok systemd --user pid=...
270
+ cron worker ok internal worker pid=...
86
271
 
87
272
  31 / 36 capabilities usable · 5 degraded
88
273
 
@@ -196,9 +381,11 @@ predates the running gem (`PWN::Migrate.needed?`).
196
381
  Schema `v1` also seeds `PWN::Cron.install_defaults` - the nightly
197
382
  `curriculum_practice` and weekly `curriculum_train` self-improvement jobs
198
383
  (see [Reinforcement Learning](Reinforcement-Learning.md)). `pwn setup`
199
- also starts the background cron worker and persists it via the OS scheduler
200
- (systemd --user / launchd / schtasks / crontab) so those YAML jobs run
201
- without a per-job crontab line. Live Policy files (`policy.json`,
384
+ also starts or reuses the single portable background cron worker. Setup,
385
+ migration and startup seeding never install, enable, repair or remove OS
386
+ scheduler units. Defaults live only in `~/.pwn/cron/jobs.yml`; the worker
387
+ must be running for automatic execution (start it again after reboot with
388
+ `pwn setup --cron`). Manual commands remain available without a worker. Live Policy files (`policy.json`,
202
389
  `policy_traj.jsonl`) are created on first agent turn when `ai.agent.policy`
203
390
  is on.
204
391
 
@@ -208,8 +395,40 @@ in the scheduler's local timezone. First launch also seeds it through
208
395
  is installed to `~/.pwn/cron/session_cleanup.rb`; managed copies refresh on
209
396
  subsequent launches/migrations, while unmarked custom helpers are preserved.
210
397
  An existing job named `session_cleanup` keeps its ID, schedule, enabled state,
211
- payload and native scheduler drop-ins. New jobs defer cleanup until their first
212
- scheduled time, rather than deleting during initial catch-up.
398
+ payload. Legacy native scheduler files are left untouched for an explicit
399
+ operator-approved removal; they are never refreshed or re-enabled by setup.
400
+ New jobs defer cleanup until their first scheduled time, rather than deleting
401
+ during initial catch-up.
402
+
403
+ Fresh installs now seed only this job, with `SessionCleanup.coordinate`: shared
404
+ evidence-first maintenance, memory consolidation, then separate learning,
405
+ mistakes and policy retention phases. Existing split maintenance schedules are
406
+ preserved until explicitly consolidated after a safe matched-code deployment.
407
+ Curriculum schedules require explicit creation; startup and repeat migrations
408
+ never recreate deleted curriculum jobs. See [Cron](Cron.md) for opt-in templates
409
+ and schedule-only deletion. Already-running old binaries must be upgraded before
410
+ their startup/config-refresh hooks can be relied on to honor the new defaults.
411
+
412
+ **Housekeeping never uses systemd or any other native scheduler.**
413
+ `session_cleanup`, `learning_consolidate_nightly`, and `pwn_stores_lean_nightly`
414
+ run only through PWN's built-in cron worker or manual commands. Native job
415
+ installers explicitly skip them with `housekeeping_internal_only`; this also
416
+ covers renamed payloads that invoke `SessionCleanup` or `session_cleanup.rb`.
417
+ Generic scheduler APIs remain available for unrelated custom jobs. Native
418
+ worker launchers mark their execution context and exclude housekeeping;
419
+ legacy PWN systemd cgroups are also excluded. An inherited terminal
420
+ `INVOCATION_ID` alone is not a scheduler context.
421
+
422
+ Existing installations require an explicit operator decision before stopping
423
+ legacy services or processes. Inventory `pwn-cron-*.timer` and `.service`, map
424
+ job IDs to `jobs.yml`, preserve custom schedules, then (only with approval)
425
+ disable and stop the housekeeping timers/services and the legacy shared
426
+ worker service, archive their unit files/drop-ins, remove those exact files,
427
+ and run the user manager's daemon-reload. Do not remove unrelated custom job
428
+ units or change lingering globally. Coordinate old clients before deploying
429
+ the new code, retain `jobs.yml` unchanged, then start/reuse one internal
430
+ worker and verify its PID and guarded tick. Do not run cleanup to validate
431
+ this transition or re-enable legacy housekeeping units.
213
432
 
214
433
  Cleanup calls a session named only when its persisted metadata has
215
434
  `title_source: summary`, a nonblank title and summary, and a positive integer
@@ -321,6 +540,19 @@ Loop requests and title writers hold independent leases, and crashes release
321
540
  descriptors automatically. Stale lock contents or reused PIDs confer no ownership.
322
541
  Never unlink lock files: that would split the synchronization inode.
323
542
 
543
+ Routine maintenance supports multiple simultaneously running upgraded clients;
544
+ there is no blanket shutdown requirement. Evidence migration takes exclusive
545
+ nonblocking leases per source transcript through index publication, skips active
546
+ sources (`evidence_migration.deferred_active_sessions`), and revisits them on a
547
+ later run after their writers finish. Unrelated eligible sessions can still be
548
+ removed. A busy Learning writer returns
549
+ `evidence_migration.deferred: learning_writer_active` with `apply_blocked: true`
550
+ rather than waiting or interrupting the writer; retry on a later cron tick or
551
+ manual invocation. The permanent maintenance lock admits one applying owner;
552
+ a second receives `Session maintenance already running` immediately. Title
553
+ workers and durable job dependencies retain their existing protections.
554
+ This is routine maintenance, not the explicitly offline layout migration.
555
+
324
556
  Old installed processes cannot be upgraded by refreshing the cron helper alone.
325
557
  `legacy_owners` reports launchers without the held PID/start-identity capability
326
558
  marker; `unknown_legacy_ownership` means their in-memory session IDs are unknown,
@@ -347,6 +579,15 @@ rake install # or: rvmsudo rake install (multi-user RVM)
347
579
  pwn setup --profile full --yes # same provisioner, same data tables
348
580
  ```
349
581
 
582
+ The `upgrade_pwn.sh` → `build_gem.sh` validation gate still runs through
583
+ `rvmsudo` on multi-user RVM installations. Its merged stdout/stderr is streamed
584
+ and retained in the invoking user's `~/.pwn/logs/build-gate.log` (mode 0600,
585
+ last 1 MiB, replaced on the next gate). The path is printed before execution
586
+ and on failure; installation does not continue after a failed gate. `rake spec`
587
+ also prints each failure immediately and reports the RSpec child's exit status
588
+ or signal, so a later terminated example cannot hide earlier failure details.
589
+ A signal report is diagnostic evidence, not a claim about who sent the signal.
590
+
350
591
  The legacy `./install.sh` / `packer/provisioners/pwn.sh` bash paths still
351
592
  work, but they now simply delegate to `pwn setup` - the `case $os` package
352
593
  lists have been consolidated into `PWN::Setup::NATIVE_GEMS` / `::TOOLCHAIN`.
@@ -26,7 +26,7 @@ external services and environment overrides can place evidence elsewhere.
26
26
  | `extrospection/voice/*` | `Extrospection` | wav / txt | `rm -rf` | TTS/STT artifacts from `extro_voice` |
27
27
  | `sessions/*.jsonl` | `PWN::Sessions` | JSON-per-line | `sessions_delete` | full transcript per pwn-ai run (with per-step `step_reward` from `Reward.prm`) |
28
28
  | `open_goal.json` | `PWN::AI::Agent::OpenGoal` | JSON | delete the file | unfinished host-work request so `continue` / `resume` picks it up |
29
- | `cron/jobs.yml` | `PWN::Cron` | YAML | `cron_remove` | scheduled jobs. Fresh install seeds hygiene (`learning_consolidate_nightly` + `pwn_stores_lean_nightly` **on**) and curriculum practice/judge/train **off** |
29
+ | `cron/jobs.yml` | `PWN::Cron` | YAML | `cron_remove` | Scheduled jobs; fresh install seeds only the `session_cleanup` housekeeping coordinator. Curriculum schedules require explicit creation and are not recreated after deletion |
30
30
  | `cron/log/*.log` | `PWN::Cron` | text | rm | last_run output |
31
31
  | `agents.yml` | `PWN::AI::Agent::Swarm` | YAML | edit / `agent_spawn` | persona registry |
32
32
  | `swarm/<id>/bus.jsonl` | `Swarm` | JSON-per-line | rm -rf | append-only multi-agent chat |
@@ -359,14 +359,13 @@ This is the live control list. Track the outcomes, not source comments.
359
359
  ```ruby
360
360
  # Seeded idempotently by PWN::Cron.install_defaults (pwn setup --migrate):
361
361
  PWN::Cron.install_defaults
362
- # -> curriculum_practice_nightly 0 3 * * * Curriculum.practice(limit: 3)
363
- # -> curriculum_offline_judge 30 3 * * * Curriculum.offline_judge(since_hours: 24, limit: 40)
364
- # -> curriculum_train_weekly 0 4 * * 0 Curriculum.train_and_gate(dry_run: true) # false only with trainer+GPU
365
- # -> learning_consolidate_nightly 0 5 * * * Learning.consolidate
362
+ # -> session_cleanup 45 3 * * * SessionCleanup.coordinate(dry_run: false)
366
363
  ```
367
364
 
368
- `install_defaults` treats the legacy name `offline_judge_nightly` as an alias of
369
- `curriculum_offline_judge` and will not double-seed the same slot.
365
+ Curriculum practice, judge and training are explicit opt-ins, not seeded jobs.
366
+ Select a template from `PWN::Cron::CURRICULUM_JOBS` and explicitly create it only
367
+ when wanted (see [Cron](Cron.md)). Deletion survives repeated default installation
368
+ and migration. Existing jobs and legacy aliases remain unchanged.
370
369
  `pwn setup` also starts the background cron worker so these YAML jobs fire
371
370
  without a per-job crontab line.
372
371