pwn 0.5.784 → 0.5.787
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile +6 -6
- data/README.md +37 -0
- data/bin/pwn +2 -1
- data/bin/pwn_web_cache_deception +35 -7
- data/build_gem.sh +1 -1
- data/documentation/AI-Integration.md +7 -5
- data/documentation/Agent-Tool-Registry.md +2 -2
- data/documentation/Backup-Retention.md +69 -15
- data/documentation/Configuration.md +10 -6
- data/documentation/Contributing.md +1 -1
- data/documentation/Cron.md +2 -2
- data/documentation/Diagrams.md +2 -2
- data/documentation/General-PWN-Usage.md +4 -3
- data/documentation/Harness-Evaluation.md +1 -1
- data/documentation/Home.md +6 -5
- data/documentation/How-PWN-Works.md +6 -5
- data/documentation/Installation.md +12 -9
- data/documentation/Mission-Cancellation.md +134 -0
- data/documentation/Operability.md +18 -0
- data/documentation/Persistence.md +7 -5
- data/documentation/Plugin-Degradation.md +14 -0
- data/documentation/Plugins.md +3 -2
- data/documentation/Policy-Benchmark.md +32 -33
- data/documentation/Runtime-Source-Protection.md +162 -0
- data/documentation/Transparent-Browser.md +11 -0
- data/documentation/Troubleshooting.md +3 -2
- data/documentation/What-is-PWN.md +2 -2
- data/documentation/YubiKey-Vaults.md +185 -0
- data/documentation/YubiKey.md +218 -0
- data/documentation/diagrams/cron-scheduling.svg +130 -130
- data/documentation/diagrams/dot/cron-scheduling.dot +3 -3
- data/documentation/diagrams/dot/persistence-filesystem.dot +5 -3
- data/documentation/diagrams/dot/plugin-ecosystem.dot +2 -2
- data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +1 -1
- data/documentation/diagrams/dot/reinforcement-learning.dot +2 -2
- data/documentation/diagrams/persistence-filesystem.svg +172 -142
- data/documentation/diagrams/plugin-ecosystem.svg +64 -64
- data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +1 -1
- data/documentation/diagrams/reinforcement-learning.svg +89 -89
- data/documentation/pwn-ai-Agent.md +10 -4
- data/etc/default_cron/session_cleanup.rb +43 -27
- data/etc/default_skills/pwn/ai/agent/mission_namespace/SKILL.md +21 -0
- data/etc/default_skills/pwn/ai/agent/tools/browser_act/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/loot_query/SKILL.md +27 -26
- data/etc/default_skills/pwn/ai/agent/tools/recon_run/SKILL.md +25 -15
- data/etc/default_skills/pwn/ai/agent/tools/workspace_tool/SKILL.md +24 -0
- data/etc/default_skills/pwn/ai/server_tools/SKILL.md +25 -0
- data/etc/default_skills/pwn/backups/SKILL.md +54 -0
- data/etc/default_skills/pwn/plugins/recon/SKILL.md +12 -1
- data/etc/default_skills/pwn/plugins/yubikey/SKILL.md +157 -0
- data/etc/default_skills/pwn/plugins/yubikey/fido/SKILL.md +189 -0
- data/etc/default_skills/pwn/plugins/yubikey/hsmauth/SKILL.md +83 -0
- data/etc/default_skills/pwn/plugins/yubikey/management/SKILL.md +80 -0
- data/etc/default_skills/pwn/plugins/yubikey/oath/SKILL.md +115 -0
- data/etc/default_skills/pwn/plugins/yubikey/openpgp/SKILL.md +144 -0
- data/etc/default_skills/pwn/plugins/yubikey/otp/SKILL.md +97 -0
- data/etc/default_skills/pwn/plugins/yubikey/piv/SKILL.md +110 -0
- data/etc/default_skills/pwn/plugins/yubikey/protocol/SKILL.md +21 -0
- data/etc/default_skills/pwn/plugins/yubikey/security_domain/SKILL.md +75 -0
- data/etc/default_skills/pwn/plugins/yubikey/security_domain/scp/SKILL.md +46 -0
- data/etc/default_skills/pwn/plugins/yubikey/transport/SKILL.md +98 -0
- data/etc/default_skills/pwn/redaction/SKILL.md +13 -1
- data/lib/pwn/ai/agent/dispatch.rb +26 -15
- data/lib/pwn/ai/agent/learning.rb +14 -18
- data/lib/pwn/ai/agent/loop.rb +17 -1
- data/lib/pwn/ai/agent/mission_namespace.rb +331 -0
- data/lib/pwn/ai/agent/policy_evaluation.rb +334 -36
- data/lib/pwn/ai/agent/prompt_builder.rb +13 -2
- data/lib/pwn/ai/agent/request_runtime.rb +122 -18
- data/lib/pwn/ai/agent/reward.rb +5 -3
- data/lib/pwn/ai/agent/skill_consolidation.rb +3 -2
- data/lib/pwn/ai/agent/skill_review.rb +9 -5
- data/lib/pwn/ai/agent/swarm.rb +4 -0
- data/lib/pwn/ai/agent/tools/browser_act.rb +32 -0
- data/lib/pwn/ai/agent/tools/loot_query.rb +11 -1
- data/lib/pwn/ai/agent/tools/recon_run.rb +11 -1
- data/lib/pwn/ai/agent/tools/workspace_tool.rb +29 -0
- data/lib/pwn/ai/agent/turn_finalizer.rb +6 -2
- data/lib/pwn/ai/agent.rb +1 -0
- data/lib/pwn/ai/anthropic.rb +8 -33
- data/lib/pwn/ai/cli.rb +34 -5
- data/lib/pwn/ai/gemini.rb +1 -0
- data/lib/pwn/ai/grok.rb +39 -28
- data/lib/pwn/ai/ollama.rb +4 -2
- data/lib/pwn/ai/open_ai.rb +11 -32
- data/lib/pwn/ai/open_web_ui.rb +62 -4
- data/lib/pwn/ai/router.rb +8 -1
- data/lib/pwn/ai/server_tools.rb +231 -0
- data/lib/pwn/ai.rb +1 -0
- data/lib/pwn/backups.rb +349 -0
- data/lib/pwn/config.rb +70 -39
- data/lib/pwn/migrate.rb +85 -280
- data/lib/pwn/module_skills.rb +7 -0
- data/lib/pwn/plugins/ai_sandbox.rb +538 -1
- data/lib/pwn/plugins/authentication_helper.rb +74 -4
- data/lib/pwn/plugins/basic_auth.rb +2 -1
- data/lib/pwn/plugins/google_workspace.rb +4 -21
- data/lib/pwn/plugins/jobs.rb +1 -1
- data/lib/pwn/plugins/log.rb +3 -1
- data/lib/pwn/plugins/preflight_checker.rb +34 -9
- data/lib/pwn/plugins/recon.rb +4 -1
- data/lib/pwn/plugins/repl/ai.rb +293 -26
- data/lib/pwn/plugins/repl/ai_console.rb +268 -88
- data/lib/pwn/plugins/repl/ai_swarm.rb +177 -58
- data/lib/pwn/plugins/repl/mesh.rb +14 -19
- data/lib/pwn/plugins/repl/vault.rb +4 -15
- data/lib/pwn/plugins/repl.rb +9 -216
- data/lib/pwn/plugins/sandbox/driver.rb +4 -0
- data/lib/pwn/plugins/transparent_browser.rb +215 -0
- data/lib/pwn/plugins/vault.rb +412 -135
- data/lib/pwn/plugins/yubikey/fido.rb +1288 -0
- data/lib/pwn/plugins/yubikey/hsmauth.rb +439 -0
- data/lib/pwn/plugins/yubikey/management.rb +686 -0
- data/lib/pwn/plugins/yubikey/oath.rb +766 -0
- data/lib/pwn/plugins/yubikey/openpgp.rb +1063 -0
- data/lib/pwn/plugins/yubikey/otp.rb +897 -0
- data/lib/pwn/plugins/yubikey/piv.rb +1315 -0
- data/lib/pwn/plugins/yubikey/protocol.rb +341 -0
- data/lib/pwn/plugins/yubikey/security_domain/scp.rb +482 -0
- data/lib/pwn/plugins/yubikey/security_domain.rb +464 -0
- data/lib/pwn/plugins/yubikey/transport.rb +1017 -0
- data/lib/pwn/plugins/yubikey.rb +844 -0
- data/lib/pwn/plugins.rb +1 -0
- data/lib/pwn/redaction.rb +69 -12
- data/lib/pwn/session_trace.rb +4 -0
- data/lib/pwn/setup.rb +114 -24
- data/lib/pwn/version.rb +1 -1
- data/lib/pwn/www/app_cobalt_io.rb +16 -5
- data/lib/pwn/www/bug_crowd.rb +16 -5
- data/lib/pwn/www/coinbase_pro.rb +16 -5
- data/lib/pwn/www/github.rb +30 -14
- data/lib/pwn/www/paypal.rb +15 -4
- data/lib/pwn/www/synack.rb +16 -5
- data/lib/pwn/www/twitter.rb +17 -6
- data/lib/pwn.rb +1 -0
- data/spec/documentation/installation_md_spec.rb +1 -1
- data/spec/fixtures/yubikey/apdu_frames.json +162 -0
- data/spec/integration/authentication_helper_oath_spec.rb +117 -0
- data/spec/integration/controller_spill_spec.rb +48 -0
- data/spec/integration/harness_process_spec.rb +97 -0
- data/spec/integration/mission_cancel_spec.rb +79 -0
- data/spec/integration/mission_curses_pty_spec.rb +44 -0
- data/spec/integration/readme_security_spec.rb +47 -0
- data/spec/integration/web_cache_deception_yubikey_spec.rb +159 -0
- data/spec/lib/pwn/ai/agent/dispatch_spec.rb +6 -3
- data/spec/lib/pwn/ai/agent/dispatch_vault_safety_spec.rb +86 -0
- data/spec/lib/pwn/ai/agent/injection_guard_spec.rb +2 -0
- data/spec/lib/pwn/ai/agent/learning_spec.rb +4 -4
- data/spec/lib/pwn/ai/agent/loop_artifacts_spec.rb +5 -0
- data/spec/lib/pwn/ai/agent/loop_spec.rb +104 -29
- data/spec/lib/pwn/ai/agent/mission_namespace_spec.rb +138 -0
- data/spec/lib/pwn/ai/agent/policy_evaluation_spec.rb +10 -23
- data/spec/lib/pwn/ai/agent/request_runtime_spec.rb +48 -0
- data/spec/lib/pwn/ai/agent/shell_search_effect_spec.rb +44 -0
- data/spec/lib/pwn/ai/agent/solve_spec.rb +6 -4
- data/spec/lib/pwn/ai/agent/tools/browser_act_spec.rb +44 -0
- data/spec/lib/pwn/ai/agent/tools/loot_query_yubikey_spec.rb +55 -0
- data/spec/lib/pwn/ai/agent/tools/recon_run_yubikey_spec.rb +43 -0
- data/spec/lib/pwn/ai/agent/tools/workspace_tool_spec.rb +20 -0
- data/spec/lib/pwn/ai/agent/turn_finalizer_spec.rb +7 -0
- data/spec/lib/pwn/ai/anthropic_vault_enrollment_spec.rb +37 -0
- data/spec/lib/pwn/ai/cli_spec.rb +8 -16
- data/spec/lib/pwn/ai/oauth_vault_persistence_spec.rb +195 -0
- data/spec/lib/pwn/ai/open_ai_spec.rb +2 -2
- data/spec/lib/pwn/ai/open_web_ui_workspace_spec.rb +36 -0
- data/spec/lib/pwn/ai/router_spec.rb +1 -1
- data/spec/lib/pwn/ai/server_tools_spec.rb +39 -0
- data/spec/lib/pwn/backups_spec.rb +129 -0
- data/spec/lib/pwn/config_redaction_yubikey_spec.rb +28 -0
- data/spec/lib/pwn/config_yubikey_spec.rb +92 -0
- data/spec/lib/pwn/migrate_spec.rb +29 -81
- data/spec/lib/pwn/migrate_yubikey_spec.rb +128 -0
- data/spec/lib/pwn/module_skills_spec.rb +8 -0
- data/spec/lib/pwn/plugins/ai_sandbox_spec.rb +230 -0
- data/spec/lib/pwn/plugins/authentication_helper_native_yubikey_spec.rb +40 -0
- data/spec/lib/pwn/plugins/authentication_helper_yubikey_spec.rb +90 -0
- data/spec/lib/pwn/plugins/capability_broker/daemon_spec.rb +1 -1
- data/spec/lib/pwn/plugins/google_workspace_spec.rb +17 -35
- data/spec/lib/pwn/plugins/log_yubikey_spec.rb +47 -0
- data/spec/lib/pwn/plugins/preflight_checker_native_yubikey_spec.rb +19 -0
- data/spec/lib/pwn/plugins/preflight_checker_yubikey_spec.rb +55 -0
- data/spec/lib/pwn/plugins/recon_yubikey_spec.rb +43 -0
- data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +110 -75
- data/spec/lib/pwn/plugins/repl/ai_legacy_containment_spec.rb +200 -0
- data/spec/lib/pwn/plugins/repl/ai_swarm_spec.rb +19 -16
- data/spec/lib/pwn/plugins/repl/ai_vault_edit_yubikey_spec.rb +103 -0
- data/spec/lib/pwn/plugins/repl/ai_yubikey_spec.rb +48 -0
- data/spec/lib/pwn/plugins/repl/mesh_vault_persistence_spec.rb +149 -0
- data/spec/lib/pwn/plugins/repl/vault_yaml_serialization_spec.rb +11 -0
- data/spec/lib/pwn/plugins/repl/vault_yubikey_spec.rb +39 -0
- data/spec/lib/pwn/plugins/repl_mesh_layout_spec.rb +2 -0
- data/spec/lib/pwn/plugins/repl_spec.rb +56 -5
- data/spec/lib/pwn/plugins/transparent_browser_jev_spec.rb +46 -0
- data/spec/lib/pwn/plugins/vault_native_yubikey_spec.rb +29 -0
- data/spec/lib/pwn/plugins/vault_publication_safety_spec.rb +258 -0
- data/spec/lib/pwn/plugins/vault_store_safety_spec.rb +241 -0
- data/spec/lib/pwn/plugins/vault_yubikey_safety_spec.rb +168 -0
- data/spec/lib/pwn/plugins/vault_yubikey_spec.rb +128 -0
- data/spec/lib/pwn/plugins/vault_yubikey_vector_spec.rb +45 -0
- data/spec/lib/pwn/plugins/yubikey/fido_spec.rb +854 -0
- data/spec/lib/pwn/plugins/yubikey/hsmauth_spec.rb +235 -0
- data/spec/lib/pwn/plugins/yubikey/management_spec.rb +277 -0
- data/spec/lib/pwn/plugins/yubikey/oath_spec.rb +349 -0
- data/spec/lib/pwn/plugins/yubikey/openpgp_spec.rb +810 -0
- data/spec/lib/pwn/plugins/yubikey/otp_spec.rb +337 -0
- data/spec/lib/pwn/plugins/yubikey/piv_spec.rb +646 -0
- data/spec/lib/pwn/plugins/yubikey/protocol_secure_spec.rb +28 -0
- data/spec/lib/pwn/plugins/yubikey/protocol_spec.rb +149 -0
- data/spec/lib/pwn/plugins/yubikey/protocol_vectors_spec.rb +18 -0
- data/spec/lib/pwn/plugins/yubikey/security_domain/scp_spec.rb +256 -0
- data/spec/lib/pwn/plugins/yubikey/security_domain/support.rb +153 -0
- data/spec/lib/pwn/plugins/yubikey/security_domain_spec.rb +296 -0
- data/spec/lib/pwn/plugins/yubikey/transport_spec.rb +760 -0
- data/spec/lib/pwn/plugins/yubikey_security_spec.rb +260 -0
- data/spec/lib/pwn/plugins/yubikey_spec.rb +209 -0
- data/spec/lib/pwn/plugins/yubikey_terminal_spec.rb +147 -0
- data/spec/lib/pwn/redaction_yubikey_spec.rb +61 -0
- data/spec/lib/pwn/session_cleanup_default_spec.rb +7 -5
- data/spec/lib/pwn/session_cleanup_payload_spec.rb +11 -11
- data/spec/lib/pwn/session_cleanup_recovery_spec.rb +34 -20
- data/spec/lib/pwn/setup_native_yubikey_spec.rb +66 -0
- data/spec/lib/pwn/setup_vault_yubikey_spec.rb +61 -0
- data/spec/lib/pwn/setup_yubikey_spec.rb +40 -0
- data/spec/lib/pwn/www/login_yubikey_spec.rb +273 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/harness/cli_faults.rb +13 -9
- data/spec/support/harness/controller_spill.rb +26 -0
- data/spec/support/harness/controller_state.rb +54 -0
- data/spec/support/harness/evaluate.rb +12 -1
- data/spec/support/harness/experiments.rb +1 -1
- data/spec/support/harness/fake_provider.rb +22 -6
- data/spec/support/harness/fault_preload.rb +3 -1
- data/spec/support/harness/mission_console.rb +54 -0
- data/spec/support/harness/mission_curses_pty.rb +41 -0
- data/spec/support/harness/mission_dispatch.rb +30 -0
- data/spec/support/harness/mission_fixture_preload.rb +24 -0
- data/spec/support/harness/mission_job_lifetime.rb +25 -0
- data/spec/support/harness/mission_legacy.rb +25 -0
- data/spec/support/harness/mission_normal_exit.rb +34 -0
- data/spec/support/harness/mission_swarm.rb +23 -0
- data/spec/support/harness/mission_unsupported.rb +29 -0
- data/spec/support/harness/package_acceptance.rb +33 -5
- data/spec/support/harness/session_acceptance.rb +4 -4
- data/spec/support/harness/swarm_acceptance.rb +1 -1
- data/spec/support/harness/swarm_control.rb +44 -0
- data/spec/support/mission_namespace.rb +14 -0
- data/spec/support/mission_pipe_queue.rb +33 -0
- data/third_party/pwn_rdoc.jsonl +605 -19
- metadata +129 -16
- data/scripts/benchmark_policy.rb +0 -376
- data/scripts/build_gate.rb +0 -58
- data/spec/integration/build_gate_spec.rb +0 -73
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
<!-- Generated by graphviz version 14.1.2 (0)
|
|
5
5
|
-->
|
|
6
6
|
<!-- Title: PWN_Reinforcement_Learning Pages: 1 -->
|
|
7
|
-
<svg width="
|
|
8
|
-
viewBox="0.00 0.00
|
|
7
|
+
<svg width="1020pt" height="901pt"
|
|
8
|
+
viewBox="0.00 0.00 1020.00 901.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
9
9
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(28.8 872.29)">
|
|
10
10
|
<title>PWN_Reinforcement_Learning</title>
|
|
11
|
-
<polygon fill="#0f172a" stroke="none" points="-28.8,28.8 -28.8,-872.29
|
|
12
|
-
<text xml:space="preserve" text-anchor="start" x="
|
|
13
|
-
<text xml:space="preserve" text-anchor="start" x="
|
|
11
|
+
<polygon fill="#0f172a" stroke="none" points="-28.8,28.8 -28.8,-872.29 990.8,-872.29 990.8,28.8 -28.8,28.8"/>
|
|
12
|
+
<text xml:space="preserve" text-anchor="start" x="199" y="-821.49" font-family="sans-Serif" font-weight="bold" font-size="20.00" fill="#e2e8f0">PWN::AI::Agent - Reward + Curriculum Reinforcement Loop</text>
|
|
13
|
+
<text xml:space="preserve" text-anchor="start" x="293.5" y="-810.04" font-family="sans-Serif" font-size="11.00" fill="#94a3b8">live accounting · optional curriculum / weights · explicit routing-policy evaluation</text>
|
|
14
14
|
<g id="clust1" class="cluster">
|
|
15
15
|
<title>cluster_live</title>
|
|
16
16
|
<path fill="#1e293b" stroke="#334155" d="M20,-8C20,-8 286,-8 286,-8 292,-8 298,-14 298,-20 298,-20 298,-777.74 298,-777.74 298,-783.74 292,-789.74 286,-789.74 286,-789.74 20,-789.74 20,-789.74 14,-789.74 8,-783.74 8,-777.74 8,-777.74 8,-20 8,-20 8,-14 14,-8 20,-8"/>
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
</g>
|
|
19
19
|
<g id="clust2" class="cluster">
|
|
20
20
|
<title>cluster_curriculum</title>
|
|
21
|
-
<path fill="#1e293b" stroke="#94a3b8" stroke-dasharray="5,2" d="
|
|
22
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
21
|
+
<path fill="#1e293b" stroke="#94a3b8" stroke-dasharray="5,2" d="M326,-8C326,-8 636,-8 636,-8 642,-8 648,-14 648,-20 648,-20 648,-784.49 648,-784.49 648,-790.49 642,-796.49 636,-796.49 636,-796.49 326,-796.49 326,-796.49 320,-796.49 314,-790.49 314,-784.49 314,-784.49 314,-20 314,-20 314,-14 320,-8 326,-8"/>
|
|
22
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-781.09" font-family="sans-Serif" font-size="12.00" fill="#e2e8f0">OPTIONAL CURRICULUM  ·  independent invocation</text>
|
|
23
23
|
</g>
|
|
24
24
|
<g id="clust3" class="cluster">
|
|
25
25
|
<title>cluster_policy</title>
|
|
26
|
-
<path fill="#1e293b" stroke="#94a3b8" stroke-dasharray="5,2" d="
|
|
27
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
26
|
+
<path fill="#1e293b" stroke="#94a3b8" stroke-dasharray="5,2" d="M676,-142.29C676,-142.29 942,-142.29 942,-142.29 948,-142.29 954,-148.29 954,-154.29 954,-154.29 954,-777.74 954,-777.74 954,-783.74 948,-789.74 942,-789.74 942,-789.74 676,-789.74 676,-789.74 670,-789.74 664,-783.74 664,-777.74 664,-777.74 664,-154.29 664,-154.29 664,-148.29 670,-142.29 676,-142.29"/>
|
|
27
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-774.34" font-family="sans-Serif" font-size="12.00" fill="#e2e8f0">FROZEN POLICY  ·  explicit operator workflow</text>
|
|
28
28
|
</g>
|
|
29
29
|
<!-- Run -->
|
|
30
30
|
<g id="node1" class="node">
|
|
@@ -117,158 +117,158 @@
|
|
|
117
117
|
<!-- Schedule -->
|
|
118
118
|
<g id="node7" class="node">
|
|
119
119
|
<title>Schedule</title>
|
|
120
|
-
<path fill="#7dd3fc" stroke="#334155" stroke-width="1.3" d="
|
|
121
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
122
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
123
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
124
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
125
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
120
|
+
<path fill="#7dd3fc" stroke="#334155" stroke-width="1.3" d="M628.15,-766.24C628.15,-766.24 333.85,-766.24 333.85,-766.24 327.85,-766.24 321.85,-760.24 321.85,-754.24 321.85,-754.24 321.85,-687.7 321.85,-687.7 321.85,-681.7 327.85,-675.7 333.85,-675.7 333.85,-675.7 628.15,-675.7 628.15,-675.7 634.15,-675.7 640.15,-681.7 640.15,-687.7 640.15,-687.7 640.15,-754.24 640.15,-754.24 640.15,-760.24 634.15,-766.24 628.15,-766.24"/>
|
|
121
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-744.27" font-family="sans-Serif" font-size="11.00" fill="#0f172a">A  OPT IN / INVOKE EXPLICITLY</text>
|
|
122
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-730.77" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Curriculum jobs are not seeded</text>
|
|
123
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-717.27" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Create a CURRICULUM_JOBS template only when wanted</text>
|
|
124
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-703.77" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Templates stay disabled until explicitly enabled</text>
|
|
125
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-690.27" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Seeding does not install system crontab</text>
|
|
126
126
|
</g>
|
|
127
127
|
<!-- Practice -->
|
|
128
128
|
<g id="node8" class="node">
|
|
129
129
|
<title>Practice</title>
|
|
130
|
-
<path fill="#6ee7b7" stroke="#334155" stroke-width="1.3" d="
|
|
131
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
132
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
133
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
134
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
135
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
130
|
+
<path fill="#6ee7b7" stroke="#334155" stroke-width="1.3" d="M605.8,-627.7C605.8,-627.7 356.2,-627.7 356.2,-627.7 350.2,-627.7 344.2,-621.7 344.2,-615.7 344.2,-615.7 344.2,-549.16 344.2,-549.16 344.2,-543.16 350.2,-537.16 356.2,-537.16 356.2,-537.16 605.8,-537.16 605.8,-537.16 611.8,-537.16 617.8,-543.16 617.8,-549.16 617.8,-549.16 617.8,-615.7 617.8,-615.7 617.8,-621.7 611.8,-627.7 605.8,-627.7"/>
|
|
131
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-605.73" font-family="sans-Serif" font-size="11.00" fill="#0f172a">B  PRACTICE / OFFLINE JUDGING</text>
|
|
132
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-592.23" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Practice: budget-first; real tools may run</text>
|
|
133
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-578.73" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Judge-qualified resolution + winning traces</text>
|
|
134
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-565.23" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Offline judge: backfill, calibrate, warm sentinel</text>
|
|
135
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-551.73" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Records: curriculum/ + mistakes / preferences</text>
|
|
136
136
|
</g>
|
|
137
137
|
<!-- Schedule->Practice -->
|
|
138
138
|
<g id="edge6" class="edge">
|
|
139
139
|
<title>Schedule->Practice</title>
|
|
140
|
-
<path fill="none" stroke="#94a3b8" stroke-width="1.3" stroke-dasharray="5,2" d="
|
|
141
|
-
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="
|
|
142
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
140
|
+
<path fill="none" stroke="#94a3b8" stroke-width="1.3" stroke-dasharray="5,2" d="M481,-675.26C481,-663.36 481,-650.36 481,-637.99"/>
|
|
141
|
+
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="483.8,-638.21 481,-630.21 478.2,-638.21 483.8,-638.21"/>
|
|
142
|
+
<text xml:space="preserve" text-anchor="middle" x="552.62" y="-648.2" font-family="sans-Serif" font-size="10.00" fill="#cbd5e1">independent jobs; not a job chain</text>
|
|
143
143
|
</g>
|
|
144
144
|
<!-- Export -->
|
|
145
145
|
<g id="node9" class="node">
|
|
146
146
|
<title>Export</title>
|
|
147
|
-
<path fill="#fcd34d" stroke="#334155" stroke-width="1.3" d="
|
|
148
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
149
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
150
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
151
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
152
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
147
|
+
<path fill="#fcd34d" stroke="#334155" stroke-width="1.3" d="M605.8,-489.16C605.8,-489.16 356.2,-489.16 356.2,-489.16 350.2,-489.16 344.2,-483.16 344.2,-477.16 344.2,-477.16 344.2,-410.62 344.2,-410.62 344.2,-404.62 350.2,-398.62 356.2,-398.62 356.2,-398.62 605.8,-398.62 605.8,-398.62 611.8,-398.62 617.8,-404.62 617.8,-410.62 617.8,-410.62 617.8,-477.16 617.8,-477.16 617.8,-483.16 611.8,-489.16 605.8,-489.16"/>
|
|
148
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-467.19" font-family="sans-Serif" font-size="11.00" fill="#0f172a">C  EXPORT TRAINING DATA</text>
|
|
149
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-453.69" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Learning.export_finetune + Reward.export_dpo</text>
|
|
150
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-440.19" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Eligible sessions / outcomes + preference pairs</text>
|
|
151
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-426.69" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Scrub, geometry and source-balance filters</text>
|
|
152
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-413.19" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Output: finetune/*.jsonl + evaluation set</text>
|
|
153
153
|
</g>
|
|
154
154
|
<!-- Practice->Export -->
|
|
155
155
|
<g id="edge7" class="edge">
|
|
156
156
|
<title>Practice->Export</title>
|
|
157
|
-
<path fill="none" stroke="#94a3b8" stroke-width="1.3" stroke-dasharray="5,2" d="
|
|
158
|
-
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="
|
|
159
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
157
|
+
<path fill="none" stroke="#94a3b8" stroke-width="1.3" stroke-dasharray="5,2" d="M481,-536.72C481,-524.82 481,-511.82 481,-499.45"/>
|
|
158
|
+
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="483.8,-499.67 481,-491.67 478.2,-499.67 483.8,-499.67"/>
|
|
159
|
+
<text xml:space="preserve" text-anchor="middle" x="525.62" y="-509.66" font-family="sans-Serif" font-size="10.00" fill="#cbd5e1">eligible retained data</text>
|
|
160
160
|
</g>
|
|
161
161
|
<!-- Train -->
|
|
162
162
|
<g id="node10" class="node">
|
|
163
163
|
<title>Train</title>
|
|
164
|
-
<path fill="#fda4af" stroke="#334155" stroke-width="1.3" d="
|
|
165
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
166
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
167
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
168
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
169
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
164
|
+
<path fill="#fda4af" stroke="#334155" stroke-width="1.3" d="M605.8,-361.62C605.8,-361.62 356.2,-361.62 356.2,-361.62 350.2,-361.62 344.2,-355.62 344.2,-349.62 344.2,-349.62 344.2,-283.08 344.2,-283.08 344.2,-277.08 350.2,-271.08 356.2,-271.08 356.2,-271.08 605.8,-271.08 605.8,-271.08 611.8,-271.08 617.8,-277.08 617.8,-283.08 617.8,-283.08 617.8,-349.62 617.8,-349.62 617.8,-355.62 611.8,-361.62 605.8,-361.62"/>
|
|
165
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-339.65" font-family="sans-Serif" font-size="11.00" fill="#0f172a">D  train_and_gate</text>
|
|
166
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-326.15" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Dry run or no trainer → export_ready only</text>
|
|
167
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-312.65" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Otherwise: configured trainer → LoRA adapter</text>
|
|
168
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-299.15" font-family="sans-Serif" font-size="11.00" fill="#0f172a">ollama create → candidate vs baseline gate</text>
|
|
169
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-285.65" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Preference-diet gate also required</text>
|
|
170
170
|
</g>
|
|
171
171
|
<!-- Export->Train -->
|
|
172
172
|
<g id="edge8" class="edge">
|
|
173
173
|
<title>Export->Train</title>
|
|
174
|
-
<path fill="none" stroke="#94a3b8" stroke-width="1.3" d="
|
|
175
|
-
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="
|
|
174
|
+
<path fill="none" stroke="#94a3b8" stroke-width="1.3" d="M481,-398.28C481,-389.78 481,-380.79 481,-372.02"/>
|
|
175
|
+
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="483.8,-372.21 481,-364.21 478.2,-372.21 483.8,-372.21"/>
|
|
176
176
|
</g>
|
|
177
177
|
<!-- Model -->
|
|
178
178
|
<g id="node11" class="node">
|
|
179
179
|
<title>Model</title>
|
|
180
|
-
<path fill="#6ee7b7" stroke="#334155" stroke-width="1.3" d="
|
|
181
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
182
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
183
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
184
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
180
|
+
<path fill="#6ee7b7" stroke="#334155" stroke-width="1.3" d="M605.8,-227.33C605.8,-227.33 356.2,-227.33 356.2,-227.33 350.2,-227.33 344.2,-221.33 344.2,-215.33 344.2,-215.33 344.2,-162.29 344.2,-162.29 344.2,-156.29 350.2,-150.29 356.2,-150.29 356.2,-150.29 605.8,-150.29 605.8,-150.29 611.8,-150.29 617.8,-156.29 617.8,-162.29 617.8,-162.29 617.8,-215.33 617.8,-215.33 617.8,-221.33 611.8,-227.33 605.8,-227.33"/>
|
|
181
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-205.36" font-family="sans-Serif" font-size="11.00" fill="#0f172a">E  RETAIN GATED ADAPTER VERSION</text>
|
|
182
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-191.86" font-family="sans-Serif" font-size="11.00" fill="#0f172a">On passing gates: update curriculum model state</text>
|
|
183
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-178.36" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Optional local weight path, not routing policy</text>
|
|
184
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-164.86" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Export alone is not training or improvement</text>
|
|
185
185
|
</g>
|
|
186
186
|
<!-- Train->Model -->
|
|
187
187
|
<g id="edge9" class="edge">
|
|
188
188
|
<title>Train->Model</title>
|
|
189
|
-
<path fill="none" stroke="#94a3b8" stroke-width="1.3" d="
|
|
190
|
-
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="
|
|
189
|
+
<path fill="none" stroke="#94a3b8" stroke-width="1.3" d="M481,-270.74C481,-259.94 481,-248.37 481,-237.44"/>
|
|
190
|
+
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="483.8,-237.6 481,-229.6 478.2,-237.6 483.8,-237.6"/>
|
|
191
191
|
</g>
|
|
192
192
|
<!-- Hygiene -->
|
|
193
193
|
<g id="node12" class="node">
|
|
194
194
|
<title>Hygiene</title>
|
|
195
|
-
<path fill="#1e293b" stroke="#334155" stroke-width="1.3" stroke-dasharray="5,2" d="
|
|
196
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
197
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
198
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
199
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
200
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
195
|
+
<path fill="#1e293b" stroke="#334155" stroke-width="1.3" stroke-dasharray="5,2" d="M605.8,-106.54C605.8,-106.54 356.2,-106.54 356.2,-106.54 350.2,-106.54 344.2,-100.54 344.2,-94.54 344.2,-94.54 344.2,-28 344.2,-28 344.2,-22 350.2,-16 356.2,-16 356.2,-16 605.8,-16 605.8,-16 611.8,-16 617.8,-22 617.8,-28 617.8,-28 617.8,-94.54 617.8,-94.54 617.8,-100.54 611.8,-106.54 605.8,-106.54"/>
|
|
196
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-84.57" font-family="sans-Serif" font-size="11.00" fill="#cbd5e1">SEPARATE ENABLED HYGIENE SEED</text>
|
|
197
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-71.07" font-family="sans-Serif" font-size="11.00" fill="#cbd5e1">session_cleanup at 03:45 daily</text>
|
|
198
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-57.57" font-family="sans-Serif" font-size="11.00" fill="#cbd5e1">Includes memory consolidation; not model training</text>
|
|
199
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-44.07" font-family="sans-Serif" font-size="11.00" fill="#cbd5e1">ESR = verified_exploit_tools / vulnerable_tools</text>
|
|
200
|
+
<text xml:space="preserve" text-anchor="middle" x="481" y="-30.57" font-family="sans-Serif" font-size="11.00" fill="#cbd5e1">ASR = successful_attacks / total_attack_attempts</text>
|
|
201
201
|
</g>
|
|
202
202
|
<!-- Select -->
|
|
203
203
|
<g id="node13" class="node">
|
|
204
204
|
<title>Select</title>
|
|
205
|
-
<path fill="#c4b5fd" stroke="#334155" stroke-width="1.3" d="
|
|
206
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
207
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
208
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
209
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
205
|
+
<path fill="#c4b5fd" stroke="#334155" stroke-width="1.3" d="M933.8,-759.49C933.8,-759.49 684.2,-759.49 684.2,-759.49 678.2,-759.49 672.2,-753.49 672.2,-747.49 672.2,-747.49 672.2,-694.45 672.2,-694.45 672.2,-688.45 678.2,-682.45 684.2,-682.45 684.2,-682.45 933.8,-682.45 933.8,-682.45 939.8,-682.45 945.8,-688.45 945.8,-694.45 945.8,-694.45 945.8,-747.49 945.8,-747.49 945.8,-753.49 939.8,-759.49 933.8,-759.49"/>
|
|
206
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-737.52" font-family="sans-Serif" font-size="11.00" fill="#0f172a">I  SELECT SNAPSHOTS</text>
|
|
207
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-724.02" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Explicit baseline + candidate policy bytes</text>
|
|
208
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-710.52" font-family="sans-Serif" font-size="11.00" fill="#0f172a">PolicyEvaluation; not live online writers</text>
|
|
209
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-697.02" font-family="sans-Serif" font-size="11.00" fill="#0f172a">No automatic handoff from RSI or training</text>
|
|
210
210
|
</g>
|
|
211
211
|
<!-- Eval -->
|
|
212
212
|
<g id="node14" class="node">
|
|
213
213
|
<title>Eval</title>
|
|
214
|
-
<path fill="#c4b5fd" stroke="#334155" stroke-width="1.3" d="
|
|
215
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
216
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
217
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
218
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
219
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
214
|
+
<path fill="#c4b5fd" stroke="#334155" stroke-width="1.3" d="M933.8,-627.7C933.8,-627.7 684.2,-627.7 684.2,-627.7 678.2,-627.7 672.2,-621.7 672.2,-615.7 672.2,-615.7 672.2,-549.16 672.2,-549.16 672.2,-543.16 678.2,-537.16 684.2,-537.16 684.2,-537.16 933.8,-537.16 933.8,-537.16 939.8,-537.16 945.8,-543.16 945.8,-549.16 945.8,-549.16 945.8,-615.7 945.8,-615.7 945.8,-621.7 939.8,-627.7 933.8,-627.7"/>
|
|
215
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-605.73" font-family="sans-Serif" font-size="11.00" fill="#0f172a">II  EVALUATE LOCALLY</text>
|
|
216
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-592.23" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Public fixed held-out tasks</text>
|
|
217
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-578.73" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Off / baseline / candidate comparisons</text>
|
|
218
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-565.23" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Two disposable filesystem environments</text>
|
|
219
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-551.73" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Regression replay is a separate split</text>
|
|
220
220
|
</g>
|
|
221
221
|
<!-- Select->Eval -->
|
|
222
222
|
<g id="edge10" class="edge">
|
|
223
223
|
<title>Select->Eval</title>
|
|
224
|
-
<path fill="none" stroke="#94a3b8" stroke-width="1.3" d="
|
|
225
|
-
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="
|
|
224
|
+
<path fill="none" stroke="#94a3b8" stroke-width="1.3" d="M809,-681.94C809,-668.21 809,-652.45 809,-637.61"/>
|
|
225
|
+
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="811.8,-637.99 809,-629.99 806.2,-637.99 811.8,-637.99"/>
|
|
226
226
|
</g>
|
|
227
227
|
<!-- Gate -->
|
|
228
228
|
<g id="node15" class="node">
|
|
229
229
|
<title>Gate</title>
|
|
230
|
-
<path fill="#fcd34d" stroke="#334155" stroke-width="1.3" d="
|
|
231
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
232
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
233
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
234
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
235
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
230
|
+
<path fill="#fcd34d" stroke="#334155" stroke-width="1.3" d="M933.8,-489.16C933.8,-489.16 684.2,-489.16 684.2,-489.16 678.2,-489.16 672.2,-483.16 672.2,-477.16 672.2,-477.16 672.2,-410.62 672.2,-410.62 672.2,-404.62 678.2,-398.62 684.2,-398.62 684.2,-398.62 933.8,-398.62 933.8,-398.62 939.8,-398.62 945.8,-404.62 945.8,-410.62 945.8,-410.62 945.8,-477.16 945.8,-477.16 945.8,-483.16 939.8,-489.16 933.8,-489.16"/>
|
|
231
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-467.19" font-family="sans-Serif" font-size="11.00" fill="#0f172a">III  REPLAY ACCEPTANCE</text>
|
|
232
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-453.69" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Fresh replay + aggregate nonregression</text>
|
|
233
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-440.19" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Per-task nonregression + held-out gain</text>
|
|
234
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-426.69" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Local controller evidence only</text>
|
|
235
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-413.19" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Not proof of general live LLM gains</text>
|
|
236
236
|
</g>
|
|
237
237
|
<!-- Eval->Gate -->
|
|
238
238
|
<g id="edge11" class="edge">
|
|
239
239
|
<title>Eval->Gate</title>
|
|
240
|
-
<path fill="none" stroke="#94a3b8" stroke-width="1.3" d="
|
|
241
|
-
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="
|
|
240
|
+
<path fill="none" stroke="#94a3b8" stroke-width="1.3" d="M809,-536.72C809,-524.82 809,-511.82 809,-499.45"/>
|
|
241
|
+
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="811.8,-499.67 809,-491.67 806.2,-499.67 811.8,-499.67"/>
|
|
242
242
|
</g>
|
|
243
243
|
<!-- Approve -->
|
|
244
244
|
<g id="node16" class="node">
|
|
245
245
|
<title>Approve</title>
|
|
246
|
-
<path fill="#6ee7b7" stroke="#334155" stroke-width="1.3" d="
|
|
247
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
248
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
249
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
250
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
246
|
+
<path fill="#6ee7b7" stroke="#334155" stroke-width="1.3" d="M933.8,-354.87C933.8,-354.87 684.2,-354.87 684.2,-354.87 678.2,-354.87 672.2,-348.87 672.2,-342.87 672.2,-342.87 672.2,-289.83 672.2,-289.83 672.2,-283.83 678.2,-277.83 684.2,-277.83 684.2,-277.83 933.8,-277.83 933.8,-277.83 939.8,-277.83 945.8,-283.83 945.8,-289.83 945.8,-289.83 945.8,-342.87 945.8,-342.87 945.8,-348.87 939.8,-354.87 933.8,-354.87"/>
|
|
247
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-332.9" font-family="sans-Serif" font-size="11.00" fill="#0f172a">IV  OPERATOR APPROVAL</text>
|
|
248
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-319.4" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Explicit live policy target</text>
|
|
249
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-305.9" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Approval + stopped writers required</text>
|
|
250
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-292.4" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Replace routing policy, not model weights</text>
|
|
251
251
|
</g>
|
|
252
252
|
<!-- Gate->Approve -->
|
|
253
253
|
<g id="edge12" class="edge">
|
|
254
254
|
<title>Gate->Approve</title>
|
|
255
|
-
<path fill="none" stroke="#94a3b8" stroke-width="1.3" d="
|
|
256
|
-
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="
|
|
255
|
+
<path fill="none" stroke="#94a3b8" stroke-width="1.3" d="M809,-398.28C809,-387.48 809,-375.91 809,-364.98"/>
|
|
256
|
+
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="811.8,-365.14 809,-357.14 806.2,-365.14 811.8,-365.14"/>
|
|
257
257
|
</g>
|
|
258
258
|
<!-- Receipt -->
|
|
259
259
|
<g id="node17" class="node">
|
|
260
260
|
<title>Receipt</title>
|
|
261
|
-
<path fill="#fcd34d" stroke="#334155" stroke-width="1.3" d="
|
|
262
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
263
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
264
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
265
|
-
<text xml:space="preserve" text-anchor="middle" x="
|
|
261
|
+
<path fill="#fcd34d" stroke="#334155" stroke-width="1.3" d="M933.8,-227.33C933.8,-227.33 684.2,-227.33 684.2,-227.33 678.2,-227.33 672.2,-221.33 672.2,-215.33 672.2,-215.33 672.2,-162.29 672.2,-162.29 672.2,-156.29 678.2,-150.29 684.2,-150.29 684.2,-150.29 933.8,-150.29 933.8,-150.29 939.8,-150.29 945.8,-156.29 945.8,-162.29 945.8,-162.29 945.8,-215.33 945.8,-215.33 945.8,-221.33 939.8,-227.33 933.8,-227.33"/>
|
|
262
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-205.36" font-family="sans-Serif" font-size="11.00" fill="#0f172a">V  RETAIN RECEIPT / ROLLBACK</text>
|
|
263
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-191.86" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Versioned receipt v1 + byte digests</text>
|
|
264
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-178.36" font-family="sans-Serif" font-size="11.00" fill="#0f172a">Rollback refuses intervening live writes</text>
|
|
265
|
+
<text xml:space="preserve" text-anchor="middle" x="809" y="-164.86" font-family="sans-Serif" font-size="11.00" fill="#0f172a">No silent overwrite of newer policy state</text>
|
|
266
266
|
</g>
|
|
267
267
|
<!-- Approve->Receipt -->
|
|
268
268
|
<g id="edge13" class="edge">
|
|
269
269
|
<title>Approve->Receipt</title>
|
|
270
|
-
<path fill="none" stroke="#94a3b8" stroke-width="1.3" d="
|
|
271
|
-
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="
|
|
270
|
+
<path fill="none" stroke="#94a3b8" stroke-width="1.3" d="M809,-277.34C809,-264.81 809,-250.7 809,-237.53"/>
|
|
271
|
+
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="1.3" points="811.8,-237.66 809,-229.66 806.2,-237.66 811.8,-237.66"/>
|
|
272
272
|
</g>
|
|
273
273
|
</g>
|
|
274
274
|
</svg>
|
|
@@ -5,6 +5,9 @@ the goal; the agent plans a sequence of tool calls (`pwn_eval`, `shell`,
|
|
|
5
5
|
`memory_*`, `skill_*`, `mistakes_*`, `reward_*`, `curriculum_*`, `extro_*`,
|
|
6
6
|
`agent_*`, ...), executes them against the live process, observes the results,
|
|
7
7
|
and checks completion against the original request and available host evidence.
|
|
8
|
+
Unspecified tool writes go to `~/.pwn/workspaces/runtime`. A path outside that
|
|
9
|
+
directory is allowed only when the original request names it. See
|
|
10
|
+
[Operability](Operability.md).
|
|
8
11
|
Learning records failures and corrections; it cannot guarantee nonrecurrence.
|
|
9
12
|
|
|
10
13
|
## Two ways to run it
|
|
@@ -336,7 +339,9 @@ anthropic` opens that nested mapping, and `/vault edit ai anthropic model`
|
|
|
336
339
|
opens a scalar. Tokens identify one nested key path, never filenames or multiple
|
|
337
340
|
disjoint scopes. The target and decryptor are the same `driver_opts.pwn_env_path`
|
|
338
341
|
and `driver_opts.pwn_dec_path` used by `pwn-vault`, defaulting to
|
|
339
|
-
`~/.pwn/pwn.yaml` and its existing `.decryptor` sidecar.
|
|
342
|
+
`~/.pwn/pwn.yaml` and its existing legacy `.decryptor` sidecar. Hardware-backed
|
|
343
|
+
[YubiKey vaults](YubiKey-Vaults.md) are detected without requiring a sidecar.
|
|
344
|
+
Unknown paths are rejected; edit the parent mapping
|
|
340
345
|
to add a new key. Arrays are edited as a whole, not by numeric path indices.
|
|
341
346
|
Existing non-null types must be retained; an existing null may remain null or
|
|
342
347
|
be replaced by a YAML value. The root must retain an `ai` mapping, a supported
|
|
@@ -355,8 +360,9 @@ only shipped schema key names, never reads/decrypts the vault, and never offers
|
|
|
355
360
|
configuration values; existing custom keys can be typed explicitly.
|
|
356
361
|
|
|
357
362
|
Plaintext is decrypted/edited in memory. Only encrypted bytes enter the private
|
|
358
|
-
temporary file, which is atomically renamed over the vault using
|
|
359
|
-
AES-256-CBC
|
|
363
|
+
temporary file, which is atomically renamed over the vault using its existing
|
|
364
|
+
backend: legacy AES-256-CBC key/IV or a fresh authenticated YubiKey envelope.
|
|
365
|
+
The device is never provisioned and the legacy decryptor is not regenerated.
|
|
360
366
|
Save re-reads the current document, preserves changes outside the selected scope,
|
|
361
367
|
and refuses a changed selected scope (the whole document for unscoped edits).
|
|
362
368
|
Editor instances serialize saves with a directory lock; a final ciphertext check
|
|
@@ -642,7 +648,7 @@ After the reply, `Learning.auto_introspect` calls `rsi_tick`. That tick reads ES
|
|
|
642
648
|
|
|
643
649
|
## What the agent can call
|
|
644
650
|
|
|
645
|
-
16 toolsets · **
|
|
651
|
+
16 toolsets · **152 tools** - full table at
|
|
646
652
|
[Agent Tool Registry](Agent-Tool-Registry.md).
|
|
647
653
|
|
|
648
654
|
The two that matter most:
|
|
@@ -20,7 +20,7 @@ module PWN
|
|
|
20
20
|
'learning' => %w[learning.jsonl learning lessons.json memory.json memory.archive.json memory.idx mistakes.json policy policy.json policy_traj.jsonl curriculum curriculum_kpi.jsonl extrospection.json finetune preferences.jsonl reward_sentinel.json skill_review.jsonl operator-learning-review.jsonl],
|
|
21
21
|
'artifacts' => %w[artifacts exports findings.jsonl engagements corpora binws redb ghidra sandbox_snapshots],
|
|
22
22
|
'state' => %w[swarm cron agents.yml open_goal.json jobs missions runs metrics metrics.json capabilities.json],
|
|
23
|
-
'logs' => %w[logs], 'cache' => %w[cache], 'tmp' => %w[
|
|
23
|
+
'logs' => %w[logs], 'cache' => %w[cache], 'tmp' => %w[workspaces/runtime],
|
|
24
24
|
'maintenance' => %w[maintenance session_cleanup_archive],
|
|
25
25
|
'backups' => %w[backup]
|
|
26
26
|
}.freeze
|
|
@@ -38,16 +38,17 @@ module PWN
|
|
|
38
38
|
raise IOError, 'Symlinked maintenance policy' if File.symlink?(path)
|
|
39
39
|
|
|
40
40
|
publish(path, JSON.pretty_generate(POLICY)) unless File.exist?(path)
|
|
41
|
-
|
|
41
|
+
private_directory(File.join(root, 'cache')) unless File.exist?(File.join(root, 'cache'))
|
|
42
|
+
private_directory(File.join(root, 'workspaces', 'runtime')) unless File.exist?(File.join(root, 'workspaces', 'runtime'))
|
|
42
43
|
layout = File.join(directory, 'layout.json')
|
|
43
44
|
raise IOError, 'Unsafe maintenance inventory' if File.symlink?(layout)
|
|
44
45
|
|
|
45
46
|
previous = File.file?(layout) ? JSON.parse(File.binread(layout)) : nil
|
|
46
|
-
raise IOError, 'Unknown maintenance inventory version' if previous && ![1, 2].include?(previous['version'])
|
|
47
|
+
raise IOError, 'Unknown maintenance inventory version' if previous && ![1, 2, 3].include?(previous['version'])
|
|
47
48
|
|
|
48
|
-
publish(layout, JSON.generate(version:
|
|
49
|
+
publish(layout, JSON.generate(version: 3, categories: STORAGE)) unless previous && previous['version'] == 3
|
|
49
50
|
end
|
|
50
|
-
{ version:
|
|
51
|
+
{ version: 3, root: directory }
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
# Startup performs only fixed-path checks and one coalesced request write.
|
|
@@ -368,7 +369,7 @@ module PWN
|
|
|
368
369
|
def self.register_disposable(path:, category:, expires_at: nil)
|
|
369
370
|
raise ArgumentError, 'Not a disposable category' unless %w[cache tmp logs].include?(category)
|
|
370
371
|
|
|
371
|
-
root =
|
|
372
|
+
root = disposable_directory(category)
|
|
372
373
|
path = File.expand_path(path)
|
|
373
374
|
raise IOError, 'Disposable path escapes its category' unless path.start_with?("#{root}/") && File.realpath(path) == path
|
|
374
375
|
raise IOError, 'Disposable must be an owned regular file' unless File.file?(path) && File.stat(path).uid == Process.uid
|
|
@@ -421,7 +422,7 @@ module PWN
|
|
|
421
422
|
# Known disposable input producer. The permanent lease covers publication,
|
|
422
423
|
# consumption and unlink; a killed consumer leaves a registered exact file.
|
|
423
424
|
def self.temporary(bytes:)
|
|
424
|
-
directory = private_directory(
|
|
425
|
+
directory = private_directory(disposable_directory('tmp'))
|
|
425
426
|
path = File.join(directory, "input-#{SecureRandom.hex(12)}.txt")
|
|
426
427
|
File.open("#{path}.lease", File::RDWR | File::CREAT | File::NOFOLLOW, 0o600) do |lease|
|
|
427
428
|
lease.flock(File::LOCK_EX)
|
|
@@ -465,6 +466,14 @@ module PWN
|
|
|
465
466
|
path
|
|
466
467
|
end
|
|
467
468
|
|
|
469
|
+
# Disposable category tmp lives in the runtime workspace, not ~/.pwn/tmp.
|
|
470
|
+
def self.disposable_directory(category)
|
|
471
|
+
raise ArgumentError, 'Not a disposable category' unless %w[cache tmp logs].include?(category)
|
|
472
|
+
|
|
473
|
+
base = File.join(Dir.home, '.pwn')
|
|
474
|
+
category == 'tmp' ? File.join(base, 'workspaces', 'runtime') : File.join(base, category)
|
|
475
|
+
end
|
|
476
|
+
|
|
468
477
|
def self.private_directory(path)
|
|
469
478
|
ancestor = File.expand_path(path)
|
|
470
479
|
loop do
|
|
@@ -499,7 +508,7 @@ module PWN
|
|
|
499
508
|
return 'unknown_schema' unless row.is_a?(Hash) && row['version'] == 1 && %w[cache tmp logs].include?(row['category']) && row['expires_at'].is_a?(Integer)
|
|
500
509
|
|
|
501
510
|
path = row['path']
|
|
502
|
-
root =
|
|
511
|
+
root = disposable_directory(row['category'])
|
|
503
512
|
return 'unsafe_path' unless path.is_a?(String) && path.start_with?("#{root}/") && File.expand_path(path) == path
|
|
504
513
|
return 'missing' unless File.exist?(path)
|
|
505
514
|
return 'unsafe_path' unless File.realpath(path) == path && File.file?(path) && File.stat(path).uid == Process.uid
|
|
@@ -574,7 +583,7 @@ module PWN
|
|
|
574
583
|
next if dry_run
|
|
575
584
|
|
|
576
585
|
unless result[:ledger]
|
|
577
|
-
directory =
|
|
586
|
+
directory = PWN::Backups.directory(label: 'disposable')
|
|
578
587
|
result[:ledger] = File.join(directory, 'ledger.json')
|
|
579
588
|
end
|
|
580
589
|
backup_path = File.join(File.dirname(result[:ledger]), "#{Digest::SHA256.hexdigest(path)}.bin")
|
|
@@ -788,11 +797,14 @@ module PWN
|
|
|
788
797
|
# children. Restoring a ledger alone never undoes deletion or migration.
|
|
789
798
|
def self.recovery_status(ledger:)
|
|
790
799
|
root = File.join(Dir.home, '.pwn')
|
|
791
|
-
prefixes = %w[maintenance session_cleanup_archive learning/migrations].map { |name| "#{File.join(root, name)}/" }
|
|
800
|
+
prefixes = %w[backup maintenance session_cleanup_archive learning/migrations].map { |name| "#{File.join(root, name)}/" }
|
|
792
801
|
raise IOError, 'Unsafe recovery ledger' unless prefixes.any? { |prefix| ledger.start_with?(prefix) } && File.realpath(ledger) == ledger
|
|
793
802
|
|
|
794
803
|
data = JSON.parse(File.binread(ledger))
|
|
795
804
|
operation = data['operation']
|
|
805
|
+
operation = 'session_cleanup' if ledger.include?('/backup/') && data['records'].is_a?(Array) && data['deleted_ids'].is_a?(Array)
|
|
806
|
+
operation = 'continuation_retirement' if ledger.include?('/backup/') && data['backup'] == File.join(File.dirname(ledger), 'personas.json')
|
|
807
|
+
operation = 'session_layout' if ledger.include?('/backup/') && File.basename(ledger) == 'layout.json'
|
|
796
808
|
operation = 'session_layout' if File.basename(ledger) == 'layout.json' && ledger.start_with?("#{File.join(root, 'session_cleanup_archive')}/") && data['records'].is_a?(Array)
|
|
797
809
|
operation = 'learning_migration' if ledger.start_with?("#{File.join(root, 'learning', 'migrations')}/") && data['version'] == 1 && data['before_sha256'] == File.basename(ledger, '.json')
|
|
798
810
|
operation = 'session_cleanup' if ledger.start_with?("#{File.join(root, 'session_cleanup_archive')}/") && data['dry_run'] == false && data['records'].is_a?(Array) && data['deleted_ids'].is_a?(Array)
|
|
@@ -841,7 +853,7 @@ module PWN
|
|
|
841
853
|
end
|
|
842
854
|
end
|
|
843
855
|
root = File.join(Dir.home, '.pwn', 'maintenance', 'quarantine')
|
|
844
|
-
raise IOError, 'Unsafe recovery ledger' unless ledger.start_with?("#{
|
|
856
|
+
raise IOError, 'Unsafe recovery ledger' unless [root, File.join(Dir.home, '.pwn', 'backup')].any? { |base| ledger.start_with?("#{base}/") } && File.realpath(ledger) == ledger
|
|
845
857
|
|
|
846
858
|
data = JSON.parse(File.binread(ledger))
|
|
847
859
|
raise IOError, 'Unknown recovery ledger' unless data['operation'] == 'routine' && data['records'].is_a?(Array)
|
|
@@ -851,7 +863,7 @@ module PWN
|
|
|
851
863
|
data['records'].each do |row|
|
|
852
864
|
path = row.fetch('path')
|
|
853
865
|
category = row.fetch('category')
|
|
854
|
-
target_root =
|
|
866
|
+
target_root = disposable_directory(category)
|
|
855
867
|
backup_path = row.fetch('backup')
|
|
856
868
|
raise IOError, 'Unsafe recovery path' unless %w[cache tmp logs].include?(category) && path.start_with?("#{target_root}/") && File.expand_path(path) == path && File.realpath(File.dirname(path)) == File.dirname(path)
|
|
857
869
|
raise IOError, 'Unsafe recovery backup' unless File.dirname(backup_path) == File.dirname(ledger) && File.realpath(backup_path) == backup_path && Digest::SHA256.file(backup_path).hexdigest == row['sha256']
|
|
@@ -1021,7 +1033,7 @@ module PWN
|
|
|
1021
1033
|
File.open(PWN::Sessions.support_path(kind: :lease, session_id: id), File::RDWR | File::CREAT | File::NOFOLLOW, 0o600) do |lease|
|
|
1022
1034
|
raise IOError, 'Swarm continuation active' unless lease.flock(File::LOCK_EX | File::LOCK_NB)
|
|
1023
1035
|
|
|
1024
|
-
directory =
|
|
1036
|
+
directory = PWN::Backups.directory(label: 'continuation')
|
|
1025
1037
|
backup_path = File.join(directory, 'personas.json')
|
|
1026
1038
|
publish(backup_path, bytes)
|
|
1027
1039
|
raise IOError, 'Continuation backup mismatch' unless File.binread(backup_path) == bytes
|
|
@@ -1141,7 +1153,7 @@ module PWN
|
|
|
1141
1153
|
def self.publish(path, bytes)
|
|
1142
1154
|
temporary = "#{path}.#{SecureRandom.hex(8)}.tmp"
|
|
1143
1155
|
root = File.join(Dir.home, '.pwn')
|
|
1144
|
-
category = %w[cache tmp].find { |name| path.start_with?("#{
|
|
1156
|
+
category = %w[cache tmp].find { |name| path.start_with?("#{disposable_directory(name)}/") }
|
|
1145
1157
|
if category
|
|
1146
1158
|
directory = private_directory(File.join(root, 'maintenance', 'atomic'))
|
|
1147
1159
|
journal = File.join(directory, "#{SecureRandom.hex(12)}.json")
|
|
@@ -1183,7 +1195,7 @@ module PWN
|
|
|
1183
1195
|
next if days.nil? || (expired_only && row.fetch('created_at') > Time.now.to_i - (days * 86_400))
|
|
1184
1196
|
|
|
1185
1197
|
path, target = row.values_at('path', 'target')
|
|
1186
|
-
base = "#{
|
|
1198
|
+
base = "#{disposable_directory(row['category'])}/"
|
|
1187
1199
|
raise IOError, 'Unsafe atomic path' unless [path, target].all? { |name| name.is_a?(String) && name.start_with?(base) && File.expand_path(name) == name } && File.dirname(path) == File.dirname(target) && path.match?(/\A#{Regexp.escape(target)}\.[a-f0-9]{16}\.tmp\z/)
|
|
1188
1200
|
next unless File.exist?(path) || File.symlink?(path)
|
|
1189
1201
|
raise IOError, 'Changed atomic bytes' unless verified_backup?(path, row['sha256']) && File.stat(path).ino == row['inode'] && File.stat(path).dev == row['device']
|
|
@@ -1210,16 +1222,7 @@ module PWN
|
|
|
1210
1222
|
end
|
|
1211
1223
|
|
|
1212
1224
|
def self.backup(path, expected, result)
|
|
1213
|
-
|
|
1214
|
-
base = File.join(Dir.home, '.pwn', 'session_cleanup_archive')
|
|
1215
|
-
raise 'Unsafe archive directory' if File.symlink?(base)
|
|
1216
|
-
|
|
1217
|
-
FileUtils.mkdir_p(base, mode: 0o700)
|
|
1218
|
-
raise 'Unsafe archive permissions' unless File.stat(base).uid == Process.uid && (File.stat(base).mode & 0o777) == 0o700
|
|
1219
|
-
|
|
1220
|
-
result[:archive_dir] = File.join(base, "#{Time.now.utc.strftime('%Y%m%dT%H%M%S')}-#{SecureRandom.hex(8)}")
|
|
1221
|
-
Dir.mkdir(result[:archive_dir], 0o700)
|
|
1222
|
-
end
|
|
1225
|
+
result[:archive_dir] ||= PWN::Backups.directory(label: 'session-cleanup')
|
|
1223
1226
|
expected.each do |name, record|
|
|
1224
1227
|
next unless record
|
|
1225
1228
|
|
|
@@ -1273,6 +1276,9 @@ module PWN
|
|
|
1273
1276
|
locks = []
|
|
1274
1277
|
begin
|
|
1275
1278
|
paths.each do |path|
|
|
1279
|
+
owner = Thread.current[:pwn_learning_write_lock]
|
|
1280
|
+
next if File.basename(path) == '.learning-write.lock' && owner && owner[:pid] == Process.pid && owner[:thread] == Thread.current
|
|
1281
|
+
|
|
1276
1282
|
lock = File.open(path, File::RDWR | File::CREAT | File::NOFOLLOW, 0o600) # rubocop:disable Style/FileOpen -- closed together in ensure
|
|
1277
1283
|
locks << lock
|
|
1278
1284
|
raise 'Dependency writer active' unless lock.flock(File::LOCK_EX | File::LOCK_NB)
|
|
@@ -1327,7 +1333,7 @@ module PWN
|
|
|
1327
1333
|
# Legacy writers block all mutations, including evidence migration.
|
|
1328
1334
|
# One permanent inode serializes CLI, curses and cron maintenance writers.
|
|
1329
1335
|
# Read-only previews never acquire a writer lock or create lock files.
|
|
1330
|
-
def self.with_maintenance_lock(dry_run
|
|
1336
|
+
def self.with_maintenance_lock(dry_run:, &block)
|
|
1331
1337
|
return yield if dry_run
|
|
1332
1338
|
|
|
1333
1339
|
root = PWN::Sessions.sessions_dir
|
|
@@ -1342,8 +1348,16 @@ module PWN
|
|
|
1342
1348
|
|
|
1343
1349
|
begin
|
|
1344
1350
|
Thread.current[:pwn_session_maintenance_lock] = { pid: Process.pid, thread: Thread.current, path: path, file: file }
|
|
1345
|
-
|
|
1351
|
+
learning = PWN::AI::Agent::Learning.send(:learning_lock, nonblocking: true)
|
|
1352
|
+
if learning == :busy
|
|
1353
|
+
Thread.current[:pwn_backup_learning_busy] = true
|
|
1354
|
+
yield
|
|
1355
|
+
else
|
|
1356
|
+
PWN::Backups.transaction(&block)
|
|
1357
|
+
end
|
|
1346
1358
|
ensure
|
|
1359
|
+
PWN::AI::Agent::Learning.send(:learning_unlock, lock: learning) unless learning == :busy
|
|
1360
|
+
Thread.current[:pwn_backup_learning_busy] = nil
|
|
1347
1361
|
Thread.current[:pwn_session_maintenance_lock] = owner
|
|
1348
1362
|
end
|
|
1349
1363
|
end
|
|
@@ -1452,6 +1466,8 @@ module PWN
|
|
|
1452
1466
|
|
|
1453
1467
|
def self.maintenance_transaction(dry_run:, current_session_id:, proc_root:)
|
|
1454
1468
|
preflight = sweep(dry_run: true, current_session_id: current_session_id, proc_root: proc_root)
|
|
1469
|
+
return preflight.merge(apply_blocked: true, evidence_migration: { deferred: 'learning_writer_active', migrated_rows: 0 }) if Thread.current[:pwn_backup_learning_busy] && !dry_run
|
|
1470
|
+
|
|
1455
1471
|
blocked = stopping? || !preflight[:legacy_owners].empty? || Array(preflight[:unresolved_store_remediation]).any?
|
|
1456
1472
|
unless dry_run || blocked
|
|
1457
1473
|
# Private metadata-only preflight survives an interrupted migration.
|