pwn 0.5.763 → 0.5.771

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 (145) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +11 -11
  3. data/documentation/Configuration.md +2 -0
  4. data/documentation/General-PWN-Usage.md +68 -0
  5. data/documentation/Harness-Evaluation.md +312 -0
  6. data/documentation/Installation.md +104 -0
  7. data/documentation/Reinforcement-Learning.md +80 -0
  8. data/documentation/pwn-REPL.md +74 -0
  9. data/documentation/pwn-ai-Agent.md +122 -0
  10. data/etc/default_cron/session_cleanup.rb +260 -0
  11. data/etc/default_skills/pwn/ai/agent/dispatch/SKILL.md +1 -0
  12. data/etc/default_skills/pwn/ai/agent/learning/SKILL.md +9 -2
  13. data/etc/default_skills/pwn/ai/agent/open_goal/SKILL.md +1 -0
  14. data/etc/default_skills/pwn/ai/agent/skill_review/SKILL.md +2 -0
  15. data/etc/default_skills/pwn/ai/agent/solve/SKILL.md +4 -2
  16. data/etc/default_skills/pwn/ai/context_ingestion/SKILL.md +1 -1
  17. data/etc/default_skills/pwn/ai/http_retry/SKILL.md +1 -0
  18. data/etc/default_skills/pwn/plugins/repl/SKILL.md +8 -0
  19. data/etc/default_skills/pwn/plugins/repl/ai_console/SKILL.md +1 -1
  20. data/etc/default_skills/pwn/plugins/repl/mesh/SKILL.md +4 -0
  21. data/etc/default_skills/pwn/plugins/repl/mesh/references/urls.md +3 -0
  22. data/etc/default_skills/pwn/redaction/SKILL.md +1 -1
  23. data/etc/default_skills/pwn/runtime_fingerprint/SKILL.md +47 -0
  24. data/etc/default_skills/pwn/sessions/SKILL.md +10 -0
  25. data/lib/pwn/ai/agent/curriculum.rb +9 -2
  26. data/lib/pwn/ai/agent/dispatch.rb +50 -6
  27. data/lib/pwn/ai/agent/learning.rb +365 -0
  28. data/lib/pwn/ai/agent/loop.rb +195 -114
  29. data/lib/pwn/ai/agent/open_goal.rb +43 -1
  30. data/lib/pwn/ai/agent/registry.rb +6 -1
  31. data/lib/pwn/ai/agent/request_runtime.rb +508 -17
  32. data/lib/pwn/ai/agent/result.rb +23 -6
  33. data/lib/pwn/ai/agent/reward.rb +16 -2
  34. data/lib/pwn/ai/agent/skill_review.rb +148 -1
  35. data/lib/pwn/ai/agent/solve.rb +420 -39
  36. data/lib/pwn/ai/agent/swarm.rb +21 -5
  37. data/lib/pwn/ai/agent/tools/learning.rb +20 -3
  38. data/lib/pwn/ai/agent/tools/sessions.rb +2 -1
  39. data/lib/pwn/ai/agent/tools/shell.rb +23 -0
  40. data/lib/pwn/ai/agent/turn_finalizer.rb +37 -6
  41. data/lib/pwn/ai/agent/verification.rb +42 -5
  42. data/lib/pwn/ai/anthropic.rb +17 -6
  43. data/lib/pwn/ai/cli.rb +52 -5
  44. data/lib/pwn/ai/context.rb +1 -1
  45. data/lib/pwn/ai/context_ingestion.rb +16 -1
  46. data/lib/pwn/ai/gemini.rb +9 -3
  47. data/lib/pwn/ai/grok.rb +11 -6
  48. data/lib/pwn/ai/http_retry.rb +14 -0
  49. data/lib/pwn/ai/mcp/combo_nation.rb +5 -2
  50. data/lib/pwn/ai/mcp.rb +5 -2
  51. data/lib/pwn/ai/ollama.rb +7 -3
  52. data/lib/pwn/ai/open_ai.rb +12 -12
  53. data/lib/pwn/ai/open_web_ui.rb +7 -3
  54. data/lib/pwn/ai/router.rb +56 -12
  55. data/lib/pwn/config.rb +6 -2
  56. data/lib/pwn/cron.rb +35 -1
  57. data/lib/pwn/migrate.rb +9 -1
  58. data/lib/pwn/plugins/repl/ai.rb +191 -32
  59. data/lib/pwn/plugins/repl/ai_console.rb +915 -34
  60. data/lib/pwn/plugins/repl/ai_console_commands.rb +23 -5
  61. data/lib/pwn/plugins/repl/ai_swarm.rb +65 -5
  62. data/lib/pwn/plugins/repl/mesh.rb +955 -81
  63. data/lib/pwn/plugins/repl.rb +11 -1
  64. data/lib/pwn/redaction.rb +29 -5
  65. data/lib/pwn/runtime_fingerprint.rb +84 -0
  66. data/lib/pwn/sessions.rb +567 -22
  67. data/lib/pwn/version.rb +1 -1
  68. data/lib/pwn.rb +1 -0
  69. data/spec/fixtures/harness/adapters.json +62 -0
  70. data/spec/fixtures/harness/adapters.json.sha256 +1 -0
  71. data/spec/fixtures/harness/experiment_gold.json +9 -0
  72. data/spec/fixtures/harness/experiment_gold.json.sha256 +1 -0
  73. data/spec/fixtures/harness/experiment_tasks.json +12 -0
  74. data/spec/fixtures/harness/experiment_tasks.json.sha256 +1 -0
  75. data/spec/fixtures/harness/live_comparison.json +21 -0
  76. data/spec/fixtures/harness/live_comparison.json.sha256 +1 -0
  77. data/spec/fixtures/harness/manifest.json +290 -0
  78. data/spec/fixtures/harness/manifest.json.sha256 +1 -0
  79. data/spec/integration/harness_evaluation_spec.rb +171 -0
  80. data/spec/integration/harness_layout_spec.rb +15 -0
  81. data/spec/integration/harness_live_spec.rb +26 -0
  82. data/spec/integration/harness_package_spec.rb +38 -0
  83. data/spec/integration/native_ruby_helpers_spec.rb +4 -2
  84. data/spec/integration/reinforced_feedback_loop_spec.rb +2 -2
  85. data/spec/lib/pwn/ai/agent/dispatch_spec.rb +47 -3
  86. data/spec/lib/pwn/ai/agent/learning_spec.rb +229 -0
  87. data/spec/lib/pwn/ai/agent/loop_spec.rb +479 -17
  88. data/spec/lib/pwn/ai/agent/open_goal_spec.rb +17 -0
  89. data/spec/lib/pwn/ai/agent/registry_spec.rb +10 -0
  90. data/spec/lib/pwn/ai/agent/request_runtime_spec.rb +232 -0
  91. data/spec/lib/pwn/ai/agent/result_spec.rb +33 -0
  92. data/spec/lib/pwn/ai/agent/signal_hygiene_spec.rb +1 -1
  93. data/spec/lib/pwn/ai/agent/skill_review_spec.rb +17 -5
  94. data/spec/lib/pwn/ai/agent/solve_spec.rb +390 -7
  95. data/spec/lib/pwn/ai/agent/swarm_spec.rb +29 -1
  96. data/spec/lib/pwn/ai/agent/tools/learning_spec.rb +14 -0
  97. data/spec/lib/pwn/ai/agent/tools/ruby_eval_spec.rb +2 -2
  98. data/spec/lib/pwn/ai/agent/turn_finalizer_spec.rb +20 -0
  99. data/spec/lib/pwn/ai/agent/verification_spec.rb +12 -0
  100. data/spec/lib/pwn/ai/anthropic_spec.rb +10 -0
  101. data/spec/lib/pwn/ai/cli_spec.rb +55 -1
  102. data/spec/lib/pwn/ai/context_ingestion_spec.rb +12 -0
  103. data/spec/lib/pwn/ai/mcp/combo_nation_spec.rb +10 -0
  104. data/spec/lib/pwn/ai/mcp_spec.rb +10 -0
  105. data/spec/lib/pwn/ai/open_ai_spec.rb +13 -0
  106. data/spec/lib/pwn/ai/router_spec.rb +53 -0
  107. data/spec/lib/pwn/banner_spec.rb +1 -1
  108. data/spec/lib/pwn/cron_spec.rb +4 -3
  109. data/spec/lib/pwn/migrate_spec.rb +37 -4
  110. data/spec/lib/pwn/plugins/repl/ai_console_commands_spec.rb +9 -0
  111. data/spec/lib/pwn/plugins/repl/ai_console_completion_wrap_spec.rb +110 -0
  112. data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +558 -8
  113. data/spec/lib/pwn/plugins/repl/ai_result_chronology_spec.rb +210 -0
  114. data/spec/lib/pwn/plugins/repl/ai_spec.rb +209 -0
  115. data/spec/lib/pwn/plugins/repl/ai_swarm_spec.rb +29 -0
  116. data/spec/lib/pwn/plugins/repl/ai_vault_edit_spec.rb +221 -0
  117. data/spec/lib/pwn/plugins/repl_mesh_channel_identity_spec.rb +134 -0
  118. data/spec/lib/pwn/plugins/repl_mesh_dispatch_spec.rb +2 -0
  119. data/spec/lib/pwn/plugins/repl_mesh_dm_transport_spec.rb +2 -1
  120. data/spec/lib/pwn/plugins/repl_mesh_multi_rx_spec.rb +315 -0
  121. data/spec/lib/pwn/plugins/repl_mesh_origin_gps_spec.rb +120 -0
  122. data/spec/lib/pwn/plugins/repl_mesh_rx_errors_spec.rb +325 -0
  123. data/spec/lib/pwn/plugins/repl_mesh_scroll_spec.rb +49 -0
  124. data/spec/lib/pwn/plugins/repl_mesh_transport_label_spec.rb +264 -0
  125. data/spec/lib/pwn/plugins/repl_spec.rb +8 -5
  126. data/spec/lib/pwn/redaction_spec.rb +20 -2
  127. data/spec/lib/pwn/runtime_fingerprint_spec.rb +53 -0
  128. data/spec/lib/pwn/session_cleanup_default_spec.rb +72 -0
  129. data/spec/lib/pwn/session_cleanup_payload_spec.rb +593 -0
  130. data/spec/lib/pwn/sessions_spec.rb +344 -0
  131. data/spec/spec_helper.rb +3 -1
  132. data/spec/support/harness/cli_faults.rb +107 -0
  133. data/spec/support/harness/evaluate.rb +176 -0
  134. data/spec/support/harness/experiment_preload.rb +20 -0
  135. data/spec/support/harness/experiments.rb +137 -0
  136. data/spec/support/harness/fake_provider.rb +80 -0
  137. data/spec/support/harness/fault_preload.rb +44 -0
  138. data/spec/support/harness/package_acceptance.rb +77 -0
  139. data/spec/support/harness/record_training.rb +18 -0
  140. data/spec/support/harness/reference_adapter.py +128 -0
  141. data/spec/support/harness/runtime_faults.rb +97 -0
  142. data/spec/support/harness/session_acceptance.rb +64 -0
  143. data/spec/support/harness/swarm_acceptance.rb +61 -0
  144. data/third_party/pwn_rdoc.jsonl +169 -2
  145. metadata +67 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fec3b367c44874a819aa4892a5b3c507cf858ed4649eb31d295b0509e8747c83
4
- data.tar.gz: d8a004b8eee5f7bd996ae8c9208f895d93354b09f9149050cb3e93d8632fda8c
3
+ metadata.gz: 4a5ecce60b421a59e80f691710dd5ae25cd79d083b59207b25baccf8fe8e5b35
4
+ data.tar.gz: 725ceb379d5436565b707e29d26e61e9cc10262b31e7cec537aec0e210d77fea
5
5
  SHA512:
6
- metadata.gz: 7490168d3e50c940ff771fe6788f7373e89b15f96541a8d0cec29bd13b1cccdce7c377799a415628fcd899a0e912928bf3aeb5fef68c0cf0fda2217871f60d95
7
- data.tar.gz: eb50e5646ee60e9394f92139b61c38901303fc6167b06f95cf559a413d88a09dbfa21eadeb1e46e8e9418295d348e53f81a62d4d4f25956ebb0284f7e9c2e572
6
+ metadata.gz: b94c6d9ca4ebb2cf3835ed1f6951a15eae48e3484966e272861105a1c361964fd90235731e8c9ccfd10cfa03af736b42d69edb990b17aed37cd1c1504954ed11
7
+ data.tar.gz: 39dd6eac404a8f55906c56f9dda74836bd9b934374eea9a8168d556b383ccfd69722b1a16debc893d20a0e58f37adfce54da11fef999a0666bf4f9a4f2258863
data/Gemfile CHANGED
@@ -20,7 +20,7 @@ gem 'base32', '0.3.4'
20
20
  gem 'bitcoin-ruby', '0.0.20'
21
21
  gem 'brakeman', '8.1.0'
22
22
  gem 'bson', '5.2.0'
23
- gem 'bundler', '>=4.0.22'
23
+ gem 'bundler', '>=4.0.23'
24
24
  gem 'bundler-audit', '>=0.9.3'
25
25
  gem 'bunny', '3.4.0'
26
26
  gem 'colorize', '1.1.0'
@@ -50,19 +50,19 @@ gem 'libusb', '0.8.0'
50
50
  gem 'luhn', '3.0.0'
51
51
  gem 'mail', '2.9.1'
52
52
  gem 'mcp', '1.7.0'
53
- gem 'meshtastic', '0.0.186'
53
+ gem 'meshtastic', '0.0.188'
54
54
  gem 'metasm', '1.0.6'
55
55
  gem 'mongo', '2.26.0'
56
56
  gem 'msfrpc-client', '1.1.2'
57
57
  gem 'netaddr', '2.0.6'
58
- gem 'net-ldap', '0.20.0'
58
+ gem 'net-ldap', '0.21.0'
59
59
  gem 'net-openvpn', '0.8.7'
60
60
  gem 'net-smtp', '0.5.2'
61
61
  gem 'nexpose', '7.3.0'
62
62
  gem 'nokogiri', '1.19.4'
63
63
  gem 'nokogiri-diff', '0.3.0'
64
64
  gem 'oily_png', '1.2.1'
65
- gem 'open3', '0.2.1'
65
+ gem 'open3', '0.3.0'
66
66
  gem 'os', '1.1.4'
67
67
  gem 'ostruct', '0.6.3'
68
68
  gem 'packetfu', '2.0.0'
@@ -73,15 +73,15 @@ gem 'pry', '0.16.0'
73
73
  gem 'pry-doc', '1.7.0'
74
74
  gem 'rake', '13.4.2'
75
75
  gem 'rb-readline', '0.5.5'
76
- gem 'rbvmomi2', '3.10.0'
76
+ gem 'rbvmomi2', '3.11.0'
77
77
  gem 'rdoc', '7.0.4'
78
78
  gem 'rest-client', '2.1.0'
79
79
  gem 'rex', '2.0.13'
80
- gem 'rmagick', '7.1.7'
80
+ gem 'rmagick', '7.2.0'
81
81
  gem 'rqrcode', '3.2.0'
82
82
  gem 'rspec', '3.13.2'
83
83
  gem 'rtesseract', '3.1.4'
84
- gem 'rubocop', '1.91.0'
84
+ gem 'rubocop', '1.92.0'
85
85
  gem 'rubocop-rake', '0.7.1'
86
86
  gem 'rubocop-rspec', '3.10.2'
87
87
  gem 'ruby-audio', '1.6.1'
@@ -89,12 +89,12 @@ gem 'ruby-nmap', '1.0.3'
89
89
  gem 'ruby-saml', '1.18.1'
90
90
  gem 'rvm', '1.11.3.9'
91
91
  gem 'savon', '2.17.5'
92
- gem 'selenium-devtools', '0.154.0'
93
- gem 'selenium-webdriver', '4.50.0'
94
- gem 'slack-ruby-client', '3.2.0'
92
+ gem 'selenium-devtools', '0.155.0'
93
+ gem 'selenium-webdriver', '4.51.0'
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.9.6'
97
+ gem 'sqlite3', '2.10.0'
98
98
  gem 'thin', '2.0.1'
99
99
  gem 'tty-prompt', '0.23.1'
100
100
  gem 'tty-spinner', '0.9.3'
@@ -234,6 +234,7 @@ plugins:
234
234
  topic: 2/e/# # MQTT topic filter to subscribe/publish for this channel.
235
235
  channel_num: 8 # Meshtastic channel index (slot number on the device).
236
236
  from: '!deadbeef' # Sender node id used on outbound packets. Optional - defaults to !<mqtt client_id>.
237
+ short_name: # Optional display name for that from node. When set, it replaces the last four bytes of from.
237
238
  PWN: # Example second (private) channel definition.
238
239
  psk: ... # Private channel pre-shared key (base64). Redacted.
239
240
  region: US/UT # LoRa region tag for this channel.
@@ -403,6 +404,7 @@ PWN::Config.refresh_env
403
404
  | `plugins.meshtastic.channel.<NAME>.topic` | String | - | `pwn-mesh` | MQTT topic filter to subscribe/publish (e.g. `2/e/#`). |
404
405
  | `plugins.meshtastic.channel.<NAME>.channel_num` | Integer | - | `pwn-mesh` | Meshtastic channel index. |
405
406
  | `plugins.meshtastic.channel.<NAME>.from` | String | `!<mqtt client_id>` | `pwn-mesh` | Sender node id used on outbound packets. |
407
+ | `plugins.meshtastic.channel.<NAME>.short_name` | String | - | `pwn-mesh` | Optional short name for that `from` node. A non-empty value is shown instead of the last four bytes of `from`. |
406
408
  | `plugins.shodan.api_key` | String | - | `PWN::Plugins::Shodan` | Shodan API key (passed as `api_key:` to `Shodan.*`). |
407
409
 
408
410
  ### `memory` / `sessions` / `cron`
@@ -51,6 +51,74 @@ history # what you've typed → copy into a driver
51
51
  | `welcome-banner` | Redraw a random `PWN::Banner` |
52
52
  | `toggle-pager` | Pry pager on/off |
53
53
 
54
+ ### Concurrent mesh reception and reply routing
55
+
56
+ `pwn-mesh` keeps the selected primary transport for manual TX and RX. A
57
+ nonempty `plugins.meshtastic.mqtt` configuration also opens an independent
58
+ MQTT receiver when the primary is Serial, Bluetooth, or TCP. Missing/empty
59
+ MQTT configuration makes no additional connection attempt; primary MQTT uses
60
+ only one subscription. MQTT connection failures leave the primary usable and
61
+ are reported in the UI. `/transport` reconnects both subscriptions; `/back`
62
+ closes them. Ctrl+T switches the active pane between COMPOSE and
63
+ CONVERSATION. COMPOSE keeps Up/Down history. CONVERSATION scrolls with
64
+ Up/Down one row, PgUp/PgDn a page, Home to the top, and End to the live
65
+ bottom. The live bottom is not row 0, so PgDn there stays put. Typing or
66
+ Enter returns to COMPOSE; Enter does not send while CONVERSATION is active.
67
+ The active title is reversed and marked active.
68
+
69
+ There is **no dual TX or automatic bridging**. Compose and `/msg` use only the
70
+ selected primary, including explicit node/channel targets. The implicit `/msg`
71
+ target remains the primary's last sender/channel; secondary MQTT arrivals do
72
+ not change it. Automatic AI replies instead use only the connection that
73
+ received the triggering message. A duplicate arriving over both links is
74
+ displayed once. If that packet is also seen on Serial, Bluetooth, or TCP, the
75
+ row is relabeled to that primary transport even when MQTT arrived first.
76
+ MQTT remains the label only when it is the sole observation. Automatic AI
77
+ replies still use the first owning connection, not the display label. Dedup
78
+ uses sender, packet ID, network/channel path, destination and payload (not
79
+ text alone), retaining at most 1000 packets for five minutes. No-ID packets
80
+ cannot be safely merged.
81
+
82
+ Each header ends with the sender's last known position. A valid fix is
83
+ `lat,lon >> https://maps.google.com/?q=lat,lon` on that same header. The pane
84
+ wraps on ` · `, and only at ` >> ` if the GPS field itself does not fit, so
85
+ the coordinate is not split. This is the compact Maps URL; a maps.app.goo.gl
86
+ code cannot be minted without a Google share request. Unknown, unset, or
87
+ out-of-range positions display `GPS N/A` and omit the URL. `0,0` is kept when
88
+ a real location source is present and omitted when the source is unset. The
89
+ value is the last position recorded for that sender, not a freshness guarantee.
90
+
91
+ MQTT TX uses `channel.<name>.from` when set, otherwise the connected radio's
92
+ node number, otherwise a node-shaped `mqtt.client_id`. It does not publish the
93
+ Meshtastic library default `!00000b0b` when one of those identities exists.
94
+ When that `from` value is not empty, the short name for that node is its last
95
+ four bytes, unless the same channel sets a non-empty `short_name`. Other
96
+ senders keep their own short names.
97
+
98
+ AI dispatch still requires the toggle, a private channel PSK, `ai_whitelist`,
99
+ and leading `@ai`. MQTT-origin shared-channel replies publish through that
100
+ MQTT connection only; radio-origin replies use that radio only. MQTT PKI DMs
101
+ remain unsupported: no AI DM dispatch, no radio fallback, and no plaintext or
102
+ legacy-PSK DM fallback. Closing/replacing the origin while AI is running leaves
103
+ its reply unsent. Host submission and local conversation display are not proof
104
+ of RF or peer delivery.
105
+
106
+ MQTT TX selects the resolved channel name explicitly as `channel_id` (for
107
+ example, `LongFast`); the Meshtastic encoder calculates its wire channel hash.
108
+ That name is not the radio's numeric slot or the legacy `channel_num` setting.
109
+ Multiple configured names may share a PSK without changing the reply channel.
110
+ Radio replies retain the received slot; an unnamed slot with an ambiguous PSK
111
+ needs an explicit `radio_index` mapping rather than choosing the first key.
112
+
113
+ AI conversations use durable `PWN::Sessions` transcripts scoped to the sender,
114
+ network/channel, private channel key and destination. Reconnecting or receiving
115
+ the same scope on another transport keeps that history; ordinary non-`@ai` RX
116
+ does not create an AI session. Session/runtime/provider errors are reported as
117
+ `AI failed`, separately from `TX failed` after a reply has been generated.
118
+ Broker EOF/protocol errors appear as bounded, generation-scoped UI notices;
119
+ they close the failed receiver without a Ruby thread crash dump. Use
120
+ `/transport` to open fresh subscriptions rather than replaying failed AI sends.
121
+
54
122
  ## A 60-second attack chain
55
123
 
56
124
  ```ruby
@@ -0,0 +1,312 @@
1
+ # Deployment fingerprints and fair harness evaluation
2
+
3
+ ## Deployment diagnosis
4
+
5
+ `PWN::RuntimeFingerprint.report(expected_root: '/intended/gem/lib')` is read-only.
6
+ The existing pwn-ai `/status` prints the report. The observer starts from `pwn.rb`,
7
+ before version/config loading. It reports PID, Linux boot ID/process start ticks
8
+ (or unavailable), observer timestamp, loaded version, activated gem root, actual
9
+ loaded Ruby source paths, SHA-256 observed on disk at compilation, and current
10
+ on-disk SHA-256. Changed or missing observed source and expected-root mismatches
11
+ require restart. Unknown pre-observer bytes are **not** reported as a match.
12
+
13
+ These are disk observations at compilation, **not VM bytecode attestation**.
14
+ Concurrent replacement during compilation, native extensions, evaluated strings,
15
+ and later monkey patches cannot be attested. `restart_required: false` means no
16
+ known mismatch, not proof every component is current. `pwn.rb` and the observer
17
+ itself were compiled before observation and intentionally remain unknown.
18
+
19
+ Preserve the original request, session ID, request checkpoint and evidence first.
20
+ Rebuild/install the intended tree, restart the process, and recheck the report.
21
+ Do not hot-reload an active request or equate restoring a checkpoint with safely
22
+ replaying a side effect. No automatic deployment or recovery mutation occurs.
23
+
24
+ ## Offline acceptance (no credentials or provider calls)
25
+
26
+ ```sh
27
+ HOME=$(mktemp -d /tmp/pwn-rspec-home.XXXXXX) bundle exec rspec \
28
+ spec/integration/harness_evaluation_spec.rb \
29
+ spec/integration/harness_package_spec.rb \
30
+ spec/integration/harness_layout_spec.rb
31
+ ```
32
+
33
+ RSpec owns acceptance and is included in the default offline `bundle exec rake`
34
+ gate. Callable execution/grading helpers and subprocess-only preloads live in
35
+ `spec/support/harness`; locked JSON manifests, independent gold and SHA-256 files
36
+ live in `spec/fixtures/harness`. Helpers are explicitly required, not recursively
37
+ loaded into the test process: the provider preloads must only run in isolated
38
+ children. There is no separate script-suite CLI. The package example builds and
39
+ executes the installed gem, not a mocked packaging check.
40
+
41
+ The package lane copies current tracked and nonignored untracked working files
42
+ into a temporary checkout, records every copied SHA-256, builds the actual gem,
43
+ installs it using `--local --ignore-dependencies` into a temporary GEM_HOME, and
44
+ runs that installation's **actual pwn-ai executable → CLI → Loop → provider →
45
+ Dispatch → shell**. It never stages the source checkout or modifies its installed
46
+ gem. Dependencies are read from existing gem paths; this is **not** proof of a
47
+ clean dependency installation. Source edits concurrent with snapshot creation
48
+ are represented by the copied-byte ledger, not an atomic Git revision claim.
49
+
50
+ Each subprocess gets an isolated temporary HOME and cleared environment. The
51
+ configuration loader and provider methods are replaced in a test-only preload;
52
+ transparent observers forward to real Dispatch, Learning and Swarm methods.
53
+ Fault stimuli supply public Loop runtime/steering options without replacing its
54
+ implementation. Loop, Dispatch, the verifier and tools remain real. The commands are local
55
+ file operations. HTTP/TCP provider access is rejected, and no credentials are
56
+ passed. This is not a general OS-level sandbox for arbitrary untrusted fixtures.
57
+ The preload is never selected by production configuration.
58
+
59
+ The fixed public tasks cover exact-file output, structured extraction, safe Ruby
60
+ coding and a nonexecuted parameterized-SQL toy classification. Grading reads
61
+ actual artifact bytes against authored literal expectations, separately from
62
+ provider text. An intentionally wrong artifact with a success claim must remain
63
+ FAIL with CLI exit 1 and `false_success: false`. The arbiter checks the explicit
64
+ `containing exactly ... followed by newline` literal against the observed digest;
65
+ this deliberately narrow grammar is not a general natural-language content judge.
66
+ The CLI now derives its exit code from the persisted outcome, not answer prose.
67
+ A detected false success is a defect and fails the evaluator itself. Fixture
68
+ outcomes are not evidence of live model competence.
69
+
70
+ `runtime_faults.rb` retains the older, explicitly labeled component fixtures.
71
+ `cli_faults.rb` adds actual executable → Loop → runtime → real shell fault lanes:
72
+
73
+ - **Compaction plus additive steering:** real writes/reads generate long context,
74
+ a small runtime window forces EngagementMemory, a public Steering submission
75
+ adds a second deliverable, and independent checks require both actual files.
76
+ - **Provider retry after a successful effect:** the fake provider raises EOFError,
77
+ the real retry path recovers, and the effect log must still contain one write.
78
+ - **Cancel during a blocked provider:** after one real write, Steering cancels
79
+ the sleeping provider; the runtime must be cancelled, no final claim released,
80
+ and the CLI nonzero. This is not a native-terminal Ctrl+C test.
81
+ - **Checkpoint restart:** one real mutation times out, the process exits abruptly,
82
+ then a new CLI process restores the same request and refuses duplicate replay.
83
+ - **Hard resource cap and restart:** after at most 35 seconds of bounded executable
84
+ startup, an observed provider-fault readiness marker arms a four-second hard
85
+ process-group cap. The supervisor records INCOMPLETE, restores the real checkpoint
86
+ and proves that the uncertain mutation is not repeated. Missing readiness cannot
87
+ pass this lane. This separates cold gem activation from the injected blocked
88
+ provider: killing a still-booting executable proves neither effect nor recovery.
89
+
90
+ The fault preload supplies public request/session/runtime/steering objects.
91
+ These are real CLI integration tests with injected controls; the separate
92
+ `session_acceptance.rb` exercises public session selection and replacement approval
93
+ without injected runtime objects. They cover finite stimuli, not every provider HTTP
94
+ retry implementation. Package acceptance runs these same lanes against installed
95
+ gem code and checks deployment fingerprints. No live provider is contacted.
96
+
97
+ The additional baseline `timeout-replay` task executes one append before timeout,
98
+ refuses the second mutation, and must exit nonzero. The evaluator now reports
99
+ INCOMPLETE when a real observed Dispatch timeout prevents success; it does not
100
+ mistake an incomplete task for model-quality failure or hide it from results.
101
+
102
+ ## Request-lifetime integration boundary
103
+
104
+ RequestRuntime persists tool start/end events and uncertain mutation fingerprints
105
+ before releasing results. An interrupted write is not automatically replayed,
106
+ including after checkpoint restore. Changing the action ID or timeout does not
107
+ change that fingerprint. This is conservative uncertainty handling, not proof of
108
+ remote exactly-once execution; spelling changes can still change the fingerprint.
109
+ Explicit caller timeouts are not increased by Dispatch on runtime-owned requests.
110
+ Legacy standalone Dispatch budgeting remains separate. Runtime-owned inference
111
+ has one total provider/profile attempt allowance; all six provider adapters use
112
+ private configuration snapshots and one transport attempt under that owner.
113
+
114
+ Runtime-bound OpenGoal files are session/request-keyed, private atomic JSON, with
115
+ a session pointer for bare `continue` in the same session. Legacy global records
116
+ are read only for their matching session. Checkpoint generations reject stale
117
+ writers. Loop also holds an exclusive per-session execution flock for the entire
118
+ request, acquired before runtime loading/checkpointing and released on return,
119
+ exception or process death. This is separate from Sessions' shared cleanup lease;
120
+ both apply to real CLI/TUI requests. Distinct sessions remain independent. The
121
+ session execution lock serializes OpenGoal pointer updates on production Loop
122
+ paths; individual atomic file writes are not a general multi-file transaction.
123
+ A new one-shot CLI session is not an automatic cross-session resume.
124
+
125
+ Explicit operator entry points:
126
+
127
+ ```sh
128
+ pwn-ai --resume-session EXACT_SESSION_ID
129
+ pwn-ai --ai 'New objective' --verification-contract /path/to/authored.json
130
+ pwn-ai --replace-session EXACT_SESSION_ID --ai 'Replacement objective' \
131
+ --verification-contract /path/to/replacement.json --approve-replacement
132
+ ```
133
+
134
+ `--resume-session` rejects a replacement prompt/contract, selects the saved request
135
+ identity, and preserves its original objective, permissions and authored checks.
136
+ The existing TUI Sessions workspace and `/sessions resume ID` select exact stable
137
+ session IDs; a subsequent `continue` uses the same core restore path. A replacement
138
+ requires explicit approval, creates a **new** request identity and contract, and
139
+ retains the old checkpoint unchanged. It never edits the immutable original
140
+ objective or weakens an existing request's contract. `--resume RUN_ID` still means
141
+ TaskDAG resume and is not repurposed. Bare steering `replace:` remains an explicit
142
+ operator instruction; an existing authored contract is not replaced by steering.
143
+
144
+ Nested Loop turns now bind their own runtime artifact contract. Completed child
145
+ host receipts cross the enclosing Loop boundary only for a parent-required exact
146
+ path, matching child turn, current stat/readback/SHA-256 and a child started during
147
+ the parent request. Parent receipts retain child request/turn/action provenance.
148
+ The parent reruns its own literal/authored integration checks; child prose is never
149
+ proof. `swarm_acceptance.rb` executes agent_spawn → agent_ask → nested Loop and
150
+ real shell operations for correct, lying, preexisting/stale, missing and later
151
+ tampered artifacts. Negative task outcomes remain blocked/nonzero; PASS there
152
+ means the rejection invariant held, not that the requested work succeeded.
153
+
154
+ For nonreasoning OpenAI requests, explicit or configured temperature `0` is now
155
+ preserved in the actual serialized HTTP payload. The adapter regression intercepts
156
+ RestClient at the transport boundary rather than checking declared settings alone.
157
+ This is offline payload evidence, not a paired live reference-provider comparison.
158
+
159
+ Host-supplied verification contracts and tool permissions survive checkpoint
160
+ reload. Dispatch checks runtime permissions in addition to existing boundaries.
161
+ Additive steering verification binds the ordered original/amendment transcript,
162
+ not merely the last user line. Verifier events include original/effective request
163
+ and amendment digests, and report requirement character spans. An explicit
164
+ `answer` check compares actual final text and, when supplied, quoted/cited source
165
+ files through the bounded in-root verifier. No assistant PASS string is evidence.
166
+ These authored checks do not imply arbitrary research-answer semantic coverage.
167
+
168
+ These fixtures do not by themselves certify universal retry ownership, every
169
+ provider configuration, general semantic verification or scheduler correctness.
170
+ Those production requirements need their own acceptance evidence. An executable
171
+ arm which exposes a production defect remains FAIL and fails the aggregate
172
+ acceptance; actual correct artifact bytes do not override a nonzero CLI exit.
173
+
174
+ Results preserve stdout, stderr, exit/signal status, observed provider calls,
175
+ fingerprints, artifact hashes, wall latency, fixture-marked repeated effects and
176
+ interventions. Distinct effect IDs are distinct writes, not duplication: only
177
+ repeated observed IDs contribute to repeated_effects. Tool attempts alone do not.
178
+ Unknown usage/cost and uninstrumented all-learning activity remain null. The
179
+ parent supervisor kills the process group on wall cap and records INCOMPLETE,
180
+ never success. A generic child error is not silently converted into a pass.
181
+
182
+ ## Recorded offline integration acceptance
183
+
184
+ Final integration artifacts are retained under `/tmp/pwn-harness-final-acceptance`.
185
+ These are local execution results, not live provider scores:
186
+
187
+ - `HOME=/tmp/pwn-harness-final-acceptance/home bundle exec rake`:
188
+ **4369 examples, 0 failures**, then **1150 files, no RuboCop offenses**, exit 0
189
+ (`rake-post-package-fix.log`). The process was awaited through actual exit.
190
+ - The focused Loop/runtime/Dispatch/Swarm/verifier/CLI/OpenAI/console/evaluator
191
+ suite: **423 examples, 0 failures**, exit 0 (`focused-post-package-fix.log`).
192
+ - `HOME=/tmp/pwn-harness-final-acceptance/home bundle exec rubocop`:
193
+ **1150 files, no offenses**, exit 0 (`rubocop-post-package-fix.log`).
194
+ - Historical offline evaluator run (before the RSpec layout migration):
195
+ exit 0. Four baseline tasks PASS; artifact-lie remains FAIL/exit1, and
196
+ timeout-replay remains INCOMPLETE/exit1 with no repeated effect. All five
197
+ executable fault acceptance lanes, eight frozen task/arm trials, five Swarm
198
+ evidence acceptance lanes and two public session-selection lanes passed.
199
+ Negative task outcomes were not relabeled successful work.
200
+ - Reusing `offline-final/experiments` as the frozen source via
201
+ `HarnessExperiments.run(output: '/tmp/pwn-harness-final-acceptance/frozen-replay',
202
+ frozen: '/tmp/pwn-harness-final-acceptance/offline-final/experiments')` returned
203
+ exit 0 with equal snapshot digest, schedule, input hashes and graded outcomes.
204
+ - Historical packaged acceptance output: `package-verified` beneath the artifact root above.
205
+ Current entry point: `bundle exec rspec spec/integration/harness_package_spec.rb`.
206
+ Its `acceptance.json` is the authority for installed execution and source-byte
207
+ equality, not the earlier source-only counts above.
208
+
209
+ The initial real evaluator's two Swarm failures, temperature-zero red regression,
210
+ cold-gem cap-before-readiness package failure and corrected lint failures remain
211
+ in the acceptance ledger rather than being hidden. Natural-language truth requires
212
+ authored checks; all live/private model
213
+ comparison, quality and superiority claims remain **NOT_RUN**.
214
+
215
+ ## Live comparison protocol — NOT_RUN
216
+
217
+ `manifest.json` and `adapters.json` have SHA-256 lock files. The runner rejects
218
+ manifest drift. Adapter metadata pins the inspected reference source revision
219
+ `601b550b4b5addecc0b6dacf9a472a54dd8c6799` and relevant source hashes. It documents
220
+ real inspected entry points and constructor fields rather than invented CLI
221
+ flags. The reference API is `run_agent.AIAgent(...).run_conversation(...)`.
222
+ No reference agent or live provider is launched by the offline evaluator.
223
+ `reference_adapter.py` is a separate optional Python-library boundary. It checks
224
+ the source revision/hashes and real constructor signature, then invokes the
225
+ public API with explicit provider, endpoint, model, reasoning, temperature via
226
+ `request_overrides`, toolsets, token/iteration/wall budgets, fresh history, and
227
+ disabled context files/memory/background review. It requires `--approve-live`,
228
+ an explicitly supplied key, temporary HOME/cwd, a locked same-model parity
229
+ manifest, reviewed sandbox/tool parity, and an operator-attested provider-side
230
+ hard cost cap. Unknown or unequal provider/model/revision values reject execution.
231
+ Raw returned data stays UNVERIFIED until an independent artifact checker grades
232
+ it. Aggregate unobserved usage/cost remain null rather than fabricated zeros.
233
+
234
+ The optional `spec/integration/harness_live_spec.rb` is tagged `:harness_live`
235
+ and excluded unless `PWN_TEST_HARNESS_LIVE=1`. It never adds pending examples to
236
+ the default gate. Explicit opt-in also requires `PWN_HARNESS_REFERENCE_SOURCE`,
237
+ `PWN_HARNESS_LIVE_MANIFEST`, `PWN_HARNESS_PROMPT_FILE`, and `PWN_EVAL_API_KEY`;
238
+ missing prerequisites fail rather than skip. The adapter's approval, locked
239
+ parity, pinned-source, temporary HOME/cwd and cost-cap checks remain mandatory.
240
+ This optional example observes only the reference API response; it does not
241
+ establish a paired comparison or model-quality gain. No live lane was run for
242
+ the layout migration.
243
+
244
+ Safe static inspection (does not import the agent or read credentials):
245
+
246
+ ```sh
247
+ python3 spec/support/harness/reference_adapter.py --inspect-source /path/to/pinned/reference/source
248
+ ```
249
+
250
+ `live_comparison.json` deliberately has unselected provider/model/revision fields
251
+ and unapproved budget/parity. It is not runnable until an operator supplies and
252
+ locks those choices. Static API validation is not live provider validation or
253
+ proof that both providers honor all parity settings on the wire.
254
+
255
+ Before any comparison, fill and freeze the **same immutable model/version,
256
+ reasoning setting, temperature, tools/permissions, task inputs, environment,
257
+ iteration/token/wall/cost budget and learned snapshot** for both adapters. These
258
+ fields are deliberately unselected, not guessed. Audit equivalent sandbox and
259
+ sidecar behavior, instrument all provider calls and billable usage, and enforce
260
+ operator-approved cost caps outside both agents. Missing parity means NOT_RUN.
261
+
262
+ Use paired repeated seeds and randomized arm order. Freeze independently authored
263
+ heldout tasks before tuning; these public scripted regression examples are not a
264
+ private holdout. Keep raw transcripts and independent artifact checks. Report
265
+ success, false-success, repeated effects, interventions, latency and observed
266
+ usage/cost, including failures and caps; do not drop incomplete runs. Confidence
267
+ intervals and claims of improvement require actual paired live trials.
268
+
269
+ ## Frozen split execution and scoped ablations
270
+
271
+ `experiments.rb` executes separate train, validation and heldout task identities.
272
+ `experiment_tasks.json` and separately authored `experiment_gold.json` are locked
273
+ before execution. The checker reads actual bytes; gold is not included in provider
274
+ fixtures or task workspaces. Duplicate task IDs, prompts or input bytes across
275
+ splits are rejected. This checks explicit leakage, not semantic dataset similarity.
276
+ The supplied public corpus and scripted provider are infrastructure regressions,
277
+ **not a secret holdout, a live intelligence benchmark, or evidence of learning gains**.
278
+
279
+ Training uses the real CLI and real Policy paths. After host byte checks succeed,
280
+ the real Learning.note_outcome API records a labeled synthetic result without an
281
+ invented reward score. The entire isolated training state is copied and frozen
282
+ with a per-file hash ledger. Every evaluation trial clones it into a fresh HOME;
283
+ no heldout writes flow back into the source snapshot or another trial. The runner
284
+ checks the source ledger again after evaluation. Per-trial policy episode counts
285
+ exclude episodes inherited from the snapshot. A seeded randomized schedule is
286
+ recorded verbatim; reusing a frozen snapshot preserves the input state, not elapsed
287
+ times or model randomness.
288
+
289
+ All offered arms execute real PWN paths:
290
+
291
+ | Arm | Executed path |
292
+ | --- | --- |
293
+ | `single-cold` | CLI → Loop, empty HOME, policy disabled |
294
+ | `single-snapshot` | CLI → Loop → real learning retrieval, frozen training stores, policy disabled |
295
+ | `single-policy` | CLI → Loop → Policy begin/observe/finish, same snapshot, per-trial writes |
296
+ | `swarm-snapshot` | CLI → Dispatch agent_spawn/agent_ask → Swarm.ask → nested Loop → real shell; parent readback |
297
+
298
+ Cold HOME is **not** an all-learning-off switch. The fixture disables automatic
299
+ introspection/extrospection in every arm, but ordinary metrics and retrieval are
300
+ real. No unimplemented all-learning-off/on arm is offered. Swarm provider calls,
301
+ learning-context invocations and policy episodes are observed rather than inferred.
302
+ These unequal fixture call counts are wiring evidence, not fair budget-normalized
303
+ model superiority. Model quality remains NOT_RUN.
304
+
305
+ For a diagnostic replay of a previously retained frozen training state, require
306
+ `spec/support/harness/experiments` and call `HarnessExperiments.run(output: ...,
307
+ frozen: ...)` from Ruby. Use a fresh `/tmp` output directory. This returns raw
308
+ observations; the integration specs own acceptance, not a helper's exit status.
309
+ Custom locked manifests and gold use the `manifest:` and `gold:` options. They
310
+ are trusted local executable fixtures, not untrusted code; the HTTP guard is not
311
+ an OS sandbox for arbitrary shell commands. Keep private benchmark gold outside
312
+ any live agent sandbox. Do not tune on heldout outcomes.
@@ -202,6 +202,110 @@ without a per-job crontab line. Live Policy files (`policy.json`,
202
202
  `policy_traj.jsonl`) are created on first agent turn when `ai.agent.policy`
203
203
  is on.
204
204
 
205
+ Schema `v8` seeds the enabled `session_cleanup` hygiene job, daily at 03:45
206
+ in the scheduler's local timezone. First launch also seeds it through
207
+ `PWN::Cron.install_defaults`. The bundled `etc/default_cron/session_cleanup.rb`
208
+ is installed to `~/.pwn/cron/session_cleanup.rb`; managed copies refresh on
209
+ subsequent launches/migrations, while unmarked custom helpers are preserved.
210
+ 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.
213
+
214
+ Cleanup calls a session named only when its persisted metadata has
215
+ `title_source: summary`, a nonblank title and summary, and a positive integer
216
+ `summary_turns` cursor: the fields published after validated LLM title generation.
217
+ Provisional first-request titles, manual titles, persona labels, aliases and
218
+ historical embedded titles are not evidence of LLM summarization. Legacy records
219
+ without provenance are unsummarized with `missing_summary_provenance`; invalid or
220
+ incomplete metadata is unknown, never named. `/sessions list` and cleanup share
221
+ `title_source`, `is_summarized`, `classification` and `title_reason`. Classification
222
+ does not establish deletion safety: malformed records, symlinks and uncertain pin
223
+ stores remain protected. Age alone does not preserve an unsummarized session.
224
+ Version 5 backs up unreferenced unsummarized sessions before a guarded `Sessions.delete`
225
+ call that bypasses the ordinary age pin. Current sessions and every known
226
+ learning/mistake/memory reference remain protected. `/sessions list` exposes
227
+ `dependencies` as an array of files **referencing this session** (empty when
228
+ none), not files this session depends on. `dependency_errors` lists unknown,
229
+ malformed or unsafe stores; cleanup fails closed when it is nonempty.
230
+ The shared index follows defined structural fields, not ID-looking text.
231
+ Dependency-backed sessions remain visible and resumable, not archived away.
232
+ Vault/config/credential payloads and historical backups are not scanned.
233
+ Same-user interactive PWN
234
+ processes defer deletion because existing writers have no lifetime leases; this is a
235
+ conservative guard, not a cross-process session lease. Inspect without deleting:
236
+ `ruby ~/.pwn/cron/session_cleanup.rb --dry-run`.
237
+
238
+ `--apply` backs up eligible transcript/metadata bytes under
239
+ `~/.pwn/session_cleanup_archive/<run>/` (directories 0700, files 0600), outside
240
+ `/sessions list`. Atomic, fsynced backups are SHA-256 verified before deletion;
241
+ file identities/hashes, title classification, reference stores and process state
242
+ are rechecked under per-ID/transcript locks. `ledger.json` records exact IDs,
243
+ hashes, skips and completed deletions; dry-run JSON contains metadata only and
244
+ creates no archive. Backups are for recovery, not an automatic reference
245
+ resolver: referenced placeholders remain in the active list until a separately
246
+ verified archive-aware lookup policy exists. No memory, learning or mistake
247
+ records are rewritten. The process checks and advisory locks do not claim a
248
+ transactional guarantee against arbitrary non-cooperating external writers.
249
+
250
+ The cleanup-specific pins additionally preserve every session ID referenced in
251
+ retained learning, mistake and durable memory stores, including non-gold learning
252
+ and old resolved mistakes. References are schema-aware:
253
+
254
+ | Store / writer | Session reference |
255
+ | --- | --- |
256
+ | `learning.jsonl` / Learning; `policy_traj.jsonl` / Policy; `open_goal.json` / OpenGoal | Top-level `session_id` |
257
+ | `mistakes.json` / Mistakes | Each signature record's `sessions[]` and `verified_sessions[]`; signature keys are not session IDs |
258
+ | `memory.json`, `memory.archive.json` / Learning.reflect, Memory | Exact `reflect_<session ID>_<8-hex lesson digest>` keys; arbitrary memory keys, values and source strings are not links |
259
+ | `jobs/<12-hex job ID>.json` / Jobs | Top-level `session_id`; command, environment and job ID are not links |
260
+ | `swarm/*/personas.json` / Swarm | Persona-to-session map values, not persona names or swarm IDs |
261
+ | `curriculum/log.jsonl` / Curriculum | `practice` event's `data[].runs[].session_id`; other events' prose is not traversed |
262
+ | `extrospection.json` / Extrospection | `auto_extrospect` observations' second tag in `['drift', session ID]`; manual/other tags and observation data are not links |
263
+ | `engagements/*/facts.jsonl` / Swarm | Top-level `source_session` |
264
+ | `findings.jsonl` / Findings | Top-level `session_id`; exact transcript/sidecar paths in `evidence_paths[]` and `poc_artifacts[]` |
265
+ | Engagement evidence and recon ledgers / Findings, Recon | Exact transcript/sidecar paths in `path` or `evidence_path`; includes the legacy root `engagements/evidence.jsonl` |
266
+ | `artifacts/manifest.jsonl` / ArtifactRegistry | Top-level `session` and exact transcript/sidecar `source_path`; `kind: swarm-bus` uses `session` for a swarm ID, not session provenance |
267
+ | `artifacts/<session ID>/index.jsonl`, `engagement-memory.json` | Session ownership from that exact directory; index `src` may also reference a transcript/sidecar |
268
+ | `embeddings/<session ID>.db`, `sessions/<session ID>/trace.jsonl` | Ownership from that exact filename/directory, not database contents or trace tool payloads |
269
+
270
+ IDs must be complete canonical values, not substrings or whitespace-normalized
271
+ tokens. Only known evidence/source path fields pointing directly into the
272
+ configured sessions directory are path references. There is currently no
273
+ cross-session link field in transcript or title metadata schemas: listing,
274
+ recalling, quoting JSON, or mentioning a session never pins it, including when
275
+ `sessions_list` output is saved repeatedly. Self listings do not create pins.
276
+
277
+ The index also validates the JSON containers in policy, preference, sentinel,
278
+ skill-review and KPI stores, run checkpoints, mission ledgers, swarm bus/solve
279
+ records, curriculum model/cooldown maps, engagement descriptors/hosts/scans and
280
+ binary facts. These writers do **not** persist session ownership links; their
281
+ arbitrary payloads, prompts, run/job IDs and user-selected labels must not be
282
+ interpreted as such. Unknown JSON stores within the indexed directories are
283
+ reported as uncertainty rather than silently ignored. Adding a new reference
284
+ schema requires auditing its writer/callers and adding positive and negative
285
+ fixtures, not a recursive key-name or text search. Vaults, loot payloads, arbitrary
286
+ artifact content and backups remain outside this index.
287
+
288
+ Native scalar tool `step_reward` fields are recognized;
289
+ unknown transcript fields remain protected.
290
+
291
+ `/sessions list` reads all transcript files afresh, not just cleanup candidates.
292
+ In cleanup output, `unnamed` includes protected sessions; `candidate_ids` excludes
293
+ them. Active sessions hold shared OS locks on permanent per-ID cleanup lock
294
+ files; cleanup takes an exclusive nonblocking lock on the same inode before
295
+ rechecking, backing up and deleting. An active A does not retain inactive B.
296
+ CLI/REPL activation and resume transfer ownership, exit releases it, nested
297
+ Loop requests and title writers hold independent leases, and crashes release
298
+ descriptors automatically. Stale lock contents or reused PIDs confer no ownership.
299
+ Never unlink lock files: that would split the synchronization inode.
300
+
301
+ Old installed processes cannot be upgraded by refreshing the cron helper alone.
302
+ `legacy_owners` reports launchers without the held PID/start-identity capability
303
+ marker; `unknown_legacy_ownership` means their in-memory session IDs are unknown,
304
+ not that every session is active. Ask the operator to identify/quiesce those
305
+ writers and restart using the updated source or deployed version before deleting
306
+ ambiguous candidates. Do not infer active IDs from mtimes, titles or process-name
307
+ matches, and do not bypass reference/summary guards. No config migration is needed.
308
+
205
309
  From a checkout:
206
310
 
207
311
  ```bash