flowproof 0.23.6__tar.gz → 0.23.7__tar.gz

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 (167) hide show
  1. {flowproof-0.23.6 → flowproof-0.23.7}/Cargo.lock +7 -7
  2. {flowproof-0.23.6 → flowproof-0.23.7}/Cargo.toml +1 -1
  3. {flowproof-0.23.6 → flowproof-0.23.7}/PKG-INFO +1 -1
  4. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/web.rs +242 -15
  5. flowproof-0.23.7/crates/flowproof-adapters/tests/list_scene_e2e.rs +49 -0
  6. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/author.rs +161 -5
  7. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/clarify.rs +5 -5
  8. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/lib.rs +10 -1
  9. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/llm.rs +10 -2
  10. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/recorder.rs +72 -6
  11. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/tests/model_client_http.rs +2 -0
  12. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/config.rs +52 -1
  13. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/lib.rs +62 -5
  14. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/config_seed_e2e.rs +56 -0
  15. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/doctor_ai_e2e.rs +54 -17
  16. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/examples_resolve.rs +38 -0
  17. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/llm_author_e2e.rs +4 -0
  18. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/notepad_author_e2e.rs +1 -0
  19. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/mock.rs +4 -0
  20. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-replay/src/lib.rs +31 -1
  21. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-replay/tests/replay_calc.rs +89 -0
  22. {flowproof-0.23.6 → flowproof-0.23.7}/flowproof/__init__.py +1 -1
  23. {flowproof-0.23.6 → flowproof-0.23.7}/pyproject.toml +1 -1
  24. {flowproof-0.23.6 → flowproof-0.23.7}/README.md +0 -0
  25. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/Cargo.toml +0 -0
  26. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/agent_proxy.rs +0 -0
  27. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/agent_runner.rs +0 -0
  28. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress.rs +0 -0
  29. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_linux.rs +0 -0
  30. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/audit.rs +0 -0
  31. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/capture.rs +0 -0
  32. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/env_block.rs +0 -0
  33. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/filters.rs +0 -0
  34. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/identity.rs +0 -0
  35. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/logon.rs +0 -0
  36. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/netevents.rs +0 -0
  37. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/run.rs +0 -0
  38. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/spawn.rs +0 -0
  39. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows/wfp.rs +0 -0
  40. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/egress_windows.rs +0 -0
  41. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/fiori.rs +0 -0
  42. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/fs_observe.rs +0 -0
  43. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/lib.rs +0 -0
  44. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/mcp_core.rs +0 -0
  45. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/mcp_http.rs +0 -0
  46. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/mcp_stdio.rs +0 -0
  47. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/sap_com.rs +0 -0
  48. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/src/vision.rs +0 -0
  49. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/a11y_capture.rs +0 -0
  50. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/drag_spike.rs +0 -0
  51. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/fs_observation_e2e.rs +0 -0
  52. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/grid_scene_e2e.rs +0 -0
  53. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/network_idle_e2e.rs +0 -0
  54. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/shared_browser_shutdown_e2e.rs +0 -0
  55. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/side_effect_http_e2e.rs +0 -0
  56. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/static_area_e2e.rs +0 -0
  57. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/visibility_detach_e2e.rs +0 -0
  58. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-adapters/tests/windows_containment_e2e.rs +0 -0
  59. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/Cargo.toml +0 -0
  60. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/agent_steps.rs +0 -0
  61. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/doc_author.rs +0 -0
  62. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/doc_formats/hp_alm.rs +0 -0
  63. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/doc_formats.rs +0 -0
  64. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/draft_assembly.rs +0 -0
  65. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/heal.rs +0 -0
  66. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/repair.rs +0 -0
  67. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/rules.rs +0 -0
  68. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-agent/src/spec.rs +0 -0
  69. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/Cargo.toml +0 -0
  70. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/skills/flowproof-config/SKILL.md +0 -0
  71. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/agent_flow.rs +0 -0
  72. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/capture.rs +0 -0
  73. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/doctor.rs +0 -0
  74. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/main.rs +0 -0
  75. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/suite.rs +0 -0
  76. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/suite_checkpoint.rs +0 -0
  77. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/suite_inputs.rs +0 -0
  78. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/suite_journal.rs +0 -0
  79. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/src/update_check.rs +0 -0
  80. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/a11y_selector_e2e.rs +0 -0
  81. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/agent_conversation_anthropic_e2e.rs +0 -0
  82. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/agent_conversation_streaming_e2e.rs +0 -0
  83. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/agent_flow_e2e.rs +0 -0
  84. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/agent_url_record_e2e.rs +0 -0
  85. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/api_pipeline.rs +0 -0
  86. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/audit_record_e2e.rs +0 -0
  87. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/calc_e2e.rs +0 -0
  88. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/clock_e2e.rs +0 -0
  89. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/config_cli_e2e.rs +0 -0
  90. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/config_skill_e2e.rs +0 -0
  91. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/cookie_e2e.rs +0 -0
  92. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/doctor_e2e.rs +0 -0
  93. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/documented_flags.rs +0 -0
  94. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/egress_e2e.rs +0 -0
  95. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_arg_matchers.rs +0 -0
  96. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_audit_since.rs +0 -0
  97. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_call_order.rs +0 -0
  98. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_capability_regression.rs +0 -0
  99. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_control_verdict.rs +0 -0
  100. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_mcp_kill.rs +0 -0
  101. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_no_tool_call.rs +0 -0
  102. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_reply_contains.rs +0 -0
  103. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/falsifiability_side_effect.rs +0 -0
  104. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/fiori_field_commit_e2e.rs +0 -0
  105. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/grid_cell_e2e.rs +0 -0
  106. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/iframe_e2e.rs +0 -0
  107. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/mcp_http_e2e.rs +0 -0
  108. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/mcp_stdio_e2e.rs +0 -0
  109. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/notepad_e2e.rs +0 -0
  110. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/page_title_e2e.rs +0 -0
  111. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/pinned_random_e2e.rs +0 -0
  112. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/sap_e2e.rs +0 -0
  113. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/sap_pipeline.rs +0 -0
  114. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/sap_sim_e2e.rs +0 -0
  115. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/scoped_container_e2e.rs +0 -0
  116. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/select_missing_option_e2e.rs +0 -0
  117. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/sigterm_shared_browser_e2e.rs +0 -0
  118. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/skip_unless_env.rs +0 -0
  119. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/suite_checkpoint.rs +0 -0
  120. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/suite_env_from.rs +0 -0
  121. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/suite_flow_isolation.rs +0 -0
  122. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/suite_missing_trace.rs +0 -0
  123. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/suite_policy.rs +0 -0
  124. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/support/sap_simulator.py +0 -0
  125. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/visibility_e2e.rs +0 -0
  126. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/vision_pipeline.rs +0 -0
  127. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-cli/tests/web_e2e.rs +0 -0
  128. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/Cargo.toml +0 -0
  129. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/app.rs +0 -0
  130. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/backend.rs +0 -0
  131. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/gdi.rs +0 -0
  132. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/lib.rs +0 -0
  133. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/oob.rs +0 -0
  134. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/recording.rs +0 -0
  135. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/redact.rs +0 -0
  136. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/surface.rs +0 -0
  137. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/visual.rs +0 -0
  138. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-driver/src/window.rs +0 -0
  139. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-python/Cargo.toml +0 -0
  140. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-python/src/lib.rs +0 -0
  141. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-replay/Cargo.toml +0 -0
  142. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-replay/src/report.rs +0 -0
  143. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-replay/src/runrecord.rs +0 -0
  144. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/Cargo.toml +0 -0
  145. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/schema/cassette-v1.schema.json +0 -0
  146. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/schema/side-effect-v1.schema.json +0 -0
  147. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/schema/trace-v1.schema.json +0 -0
  148. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/captures.rs +0 -0
  149. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/cassette.rs +0 -0
  150. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/cassette_diff.rs +0 -0
  151. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/egress.rs +0 -0
  152. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/format.rs +0 -0
  153. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/lib.rs +0 -0
  154. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/secret.rs +0 -0
  155. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/secret_scan.rs +0 -0
  156. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/side_effect.rs +0 -0
  157. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/substitution.rs +0 -0
  158. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/src/toolcalls.rs +0 -0
  159. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/tests/cassette_conformance.rs +0 -0
  160. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/tests/fixtures/multi.trace.jsonl +0 -0
  161. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/tests/fixtures/sample.trace.jsonl +0 -0
  162. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/tests/schema_conformance.rs +0 -0
  163. {flowproof-0.23.6 → flowproof-0.23.7}/crates/flowproof-trace/tests/side_effect_conformance.rs +0 -0
  164. {flowproof-0.23.6 → flowproof-0.23.7}/flowproof/cli.py +0 -0
  165. {flowproof-0.23.6 → flowproof-0.23.7}/flowproof/flow.py +0 -0
  166. {flowproof-0.23.6 → flowproof-0.23.7}/flowproof/mcp_server.py +0 -0
  167. {flowproof-0.23.6 → flowproof-0.23.7}/flowproof/py.typed +0 -0
@@ -930,7 +930,7 @@ dependencies = [
930
930
 
931
931
  [[package]]
932
932
  name = "flowproof-adapters"
933
- version = "0.23.6"
933
+ version = "0.23.7"
934
934
  dependencies = [
935
935
  "anyhow",
936
936
  "flowproof-driver",
@@ -949,7 +949,7 @@ dependencies = [
949
949
 
950
950
  [[package]]
951
951
  name = "flowproof-agent"
952
- version = "0.23.6"
952
+ version = "0.23.7"
953
953
  dependencies = [
954
954
  "chrono",
955
955
  "flowproof-driver",
@@ -967,7 +967,7 @@ dependencies = [
967
967
 
968
968
  [[package]]
969
969
  name = "flowproof-cli"
970
- version = "0.23.6"
970
+ version = "0.23.7"
971
971
  dependencies = [
972
972
  "ab_glyph",
973
973
  "chrono",
@@ -992,7 +992,7 @@ dependencies = [
992
992
 
993
993
  [[package]]
994
994
  name = "flowproof-driver"
995
- version = "0.23.6"
995
+ version = "0.23.7"
996
996
  dependencies = [
997
997
  "calamine",
998
998
  "flowproof-trace",
@@ -1008,7 +1008,7 @@ dependencies = [
1008
1008
 
1009
1009
  [[package]]
1010
1010
  name = "flowproof-python"
1011
- version = "0.23.6"
1011
+ version = "0.23.7"
1012
1012
  dependencies = [
1013
1013
  "flowproof-agent",
1014
1014
  "flowproof-cli",
@@ -1020,7 +1020,7 @@ dependencies = [
1020
1020
 
1021
1021
  [[package]]
1022
1022
  name = "flowproof-replay"
1023
- version = "0.23.6"
1023
+ version = "0.23.7"
1024
1024
  dependencies = [
1025
1025
  "chrono",
1026
1026
  "flowproof-agent",
@@ -1034,7 +1034,7 @@ dependencies = [
1034
1034
 
1035
1035
  [[package]]
1036
1036
  name = "flowproof-trace"
1037
- version = "0.23.6"
1037
+ version = "0.23.7"
1038
1038
  dependencies = [
1039
1039
  "jsonschema",
1040
1040
  "regex",
@@ -22,7 +22,7 @@ members = ["crates/flowproof-driver", "crates/flowproof-trace", "crates/flowproo
22
22
  exclude = ["spike/windows-containment"]
23
23
 
24
24
  [workspace.package]
25
- version = "0.23.6"
25
+ version = "0.23.7"
26
26
  edition = "2021"
27
27
  license = "Apache-2.0"
28
28
  repository = "https://github.com/automators-com/flowproof"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowproof
3
- Version: 0.23.6
3
+ Version: 0.23.7
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Programming Language :: Python :: 3
@@ -1040,6 +1040,19 @@ fn launch_browser(extra_args: &[String]) -> Result<Browser, AdapterError> {
1040
1040
  .map_err(|e| AdapterError::Web(launch_failure_message(&e.to_string(), headed)))
1041
1041
  }
1042
1042
 
1043
+ /// Reuse Chromium's startup page in a privately owned browser. Never use
1044
+ /// this for shared contexts: each flow there must keep its isolated storage.
1045
+ fn private_flow_tab(browser: &Browser) -> anyhow::Result<Arc<Tab>> {
1046
+ // This API is deprecated in favor of new_tab(), but waiting for and reusing
1047
+ // the startup target is intentional: new_tab() leaves an extra visible tab.
1048
+ #[allow(deprecated)]
1049
+ let initial = browser.wait_for_initial_tab()?;
1050
+ match initial.get_url().as_str() {
1051
+ "about:blank" | "chrome://newtab/" | "chrome://new-tab-page/" => Ok(initial),
1052
+ _ => browser.new_tab(),
1053
+ }
1054
+ }
1055
+
1043
1056
  /// A private per-launch downloads directory when the flow didn't pin one via
1044
1057
  /// `browser.downloads_dir`. Unique per launch (pid + a monotonic counter) so
1045
1058
  /// two flows sharing the same shared-browser process never race over the
@@ -1314,6 +1327,7 @@ pub struct WebAppDriver {
1314
1327
  /// Active request identities, maintained by CDP. Redirects reuse their
1315
1328
  /// request ID and must not leave a phantom in-flight request behind.
1316
1329
  network_inflight: Arc<NetworkActivity>,
1330
+ observed_collections: Vec<serde_json::Value>,
1317
1331
  }
1318
1332
 
1319
1333
  /// Shared state between the driver and the flow-wide dialog listener.
@@ -1515,6 +1529,7 @@ impl WebAppDriver {
1515
1529
  downloads_dir: None,
1516
1530
  claimed_downloads: Default::default(),
1517
1531
  network_inflight: Default::default(),
1532
+ observed_collections: Vec::new(),
1518
1533
  });
1519
1534
  }
1520
1535
  let browser = shared_browser()?;
@@ -1535,6 +1550,7 @@ impl WebAppDriver {
1535
1550
  downloads_dir: None,
1536
1551
  claimed_downloads: Default::default(),
1537
1552
  network_inflight: Default::default(),
1553
+ observed_collections: Vec::new(),
1538
1554
  })
1539
1555
  }
1540
1556
 
@@ -2144,6 +2160,55 @@ fn is_transport_fault(message: &str) -> bool {
2144
2160
  || m.contains("unable to make method calls")
2145
2161
  }
2146
2162
 
2163
+ /// How long a single CDP call is allowed to wait for a response before this
2164
+ /// side gives up on it (issue #616). The vendored `headless_chrome` fork
2165
+ /// bounds that wait with `idle_browser_timeout` - but that same field also
2166
+ /// sets the reader thread's own shutdown-poll interval and a separate
2167
+ /// browser-level idle-connection timer (three different jobs, one shared
2168
+ /// knob), and flowproof sets it to 300s for those other purposes. Lowering
2169
+ /// it directly would risk shortening the other two as a side effect.
2170
+ /// Wrapping the call from this side instead - the same fix browser-use
2171
+ /// applied for the identical failure shape in `_cdp_timeout.py` - needs no
2172
+ /// change to the vendored fork at all. 30s is well above every attempt's
2173
+ /// normal total duration observed live (~35-50s for an entire multi-step
2174
+ /// flow, not one call) and far below the 300s a stuck call currently costs.
2175
+ const CDP_CALL_TIMEOUT: Duration = Duration::from_secs(30);
2176
+
2177
+ /// Waits at most [`CDP_CALL_TIMEOUT`] for `f`, running it on its own thread
2178
+ /// since Rust has no way to cancel a blocking call already in flight (`f`
2179
+ /// itself is still bound by the vendored fork's much longer wait and keeps
2180
+ /// running to completion on that thread regardless - this only stops THIS
2181
+ /// caller from waiting on it). Confirmed live: about a third of attempts on
2182
+ /// one spec spent 3-10 minutes on a single stuck call instead of the normal
2183
+ /// ~35-50s for the whole flow; retrying immediately instead of waiting out
2184
+ /// that stall gives a fresh attempt a chance to land well within the same
2185
+ /// wall-clock budget a client would tolerate.
2186
+ ///
2187
+ /// The timeout's own error text matches `is_transport_fault`, so a caller
2188
+ /// that already retries on transport faults (`frame_act`, `probe_frame`)
2189
+ /// treats a fast timeout exactly like the slow error it now stands in for -
2190
+ /// no separate handling needed at the call site.
2191
+ fn with_cdp_timeout<T: Send + 'static>(
2192
+ f: impl FnOnce() -> Result<T, String> + Send + 'static,
2193
+ ) -> Result<T, String> {
2194
+ with_cdp_timeout_after(CDP_CALL_TIMEOUT, f)
2195
+ }
2196
+
2197
+ /// The actual wait, with the deadline injectable so tests can prove the
2198
+ /// timeout fires (and what it produces) in milliseconds instead of at the
2199
+ /// real 30s production value.
2200
+ fn with_cdp_timeout_after<T: Send + 'static>(
2201
+ deadline: Duration,
2202
+ f: impl FnOnce() -> Result<T, String> + Send + 'static,
2203
+ ) -> Result<T, String> {
2204
+ let (tx, rx) = std::sync::mpsc::channel();
2205
+ std::thread::spawn(move || {
2206
+ let _ = tx.send(f());
2207
+ });
2208
+ rx.recv_timeout(deadline)
2209
+ .unwrap_or_else(|_| Err("the event waited for never came".to_string()))
2210
+ }
2211
+
2147
2212
  /// Attempts allowed beyond the first for a transport-faulting operation.
2148
2213
  /// Was 1 (a single fixed ~300ms retry) until issue #608: a clean live
2149
2214
  /// re-verification of `medium-02-change-info-record-readonly.flow.yaml`
@@ -2304,6 +2369,57 @@ mod transport_retry_tests {
2304
2369
  }
2305
2370
  }
2306
2371
 
2372
+ #[cfg(test)]
2373
+ mod cdp_timeout_tests {
2374
+ use super::with_cdp_timeout_after;
2375
+ use std::time::Duration;
2376
+
2377
+ /// The common case: a call that returns well within the deadline is
2378
+ /// unaffected by the wrapper.
2379
+ #[test]
2380
+ fn a_fast_call_returns_its_own_result() {
2381
+ let result = with_cdp_timeout_after(Duration::from_millis(200), || Ok::<_, String>(42));
2382
+ assert_eq!(result, Ok(42));
2383
+ }
2384
+
2385
+ /// issue #616: a call still stuck past the deadline must not block the
2386
+ /// caller - the timeout fires and hands back control, even though the
2387
+ /// spawned thread (standing in for the real CDP wait) is still running.
2388
+ #[test]
2389
+ fn a_call_still_stuck_past_the_deadline_times_out_instead_of_blocking() {
2390
+ let result = with_cdp_timeout_after(Duration::from_millis(50), || {
2391
+ std::thread::sleep(Duration::from_secs(5));
2392
+ Ok::<_, String>(())
2393
+ });
2394
+ assert_eq!(result, Err("the event waited for never came".to_string()));
2395
+ }
2396
+
2397
+ /// The timeout's own error text must match `is_transport_fault`, so a
2398
+ /// caller that already retries on transport faults treats a fast
2399
+ /// timeout exactly like the slow error it now stands in for, with no
2400
+ /// separate handling needed at the call site.
2401
+ #[test]
2402
+ fn the_timeout_error_is_itself_a_recognized_transport_fault() {
2403
+ let result: Result<(), String> = with_cdp_timeout_after(Duration::from_millis(50), || {
2404
+ std::thread::sleep(Duration::from_secs(5));
2405
+ Ok(())
2406
+ });
2407
+ match result {
2408
+ Err(e) => assert!(super::is_transport_fault(&e)),
2409
+ Ok(()) => panic!("expected a timeout error"),
2410
+ }
2411
+ }
2412
+
2413
+ /// A genuine error from `f` (not a timeout) passes through unchanged -
2414
+ /// the wrapper must not turn every real failure into "timed out".
2415
+ #[test]
2416
+ fn a_real_error_from_f_is_not_reported_as_a_timeout() {
2417
+ let result: Result<(), String> =
2418
+ with_cdp_timeout_after(Duration::from_millis(200), || Err("no_element".to_string()));
2419
+ assert_eq!(result, Err("no_element".to_string()));
2420
+ }
2421
+ }
2422
+
2307
2423
  /// How a [`UiaSelector`] resolves on a page: a CSS selector or a text
2308
2424
  /// anchor, optionally narrowed to the nth match (1-based).
2309
2425
  struct WebLocator {
@@ -2584,11 +2700,14 @@ impl WebAppDriver {
2584
2700
  // uses for every field inside an iframe) killed the whole flow. A
2585
2701
  // single retry was also not enough on its own (issue #608) - this
2586
2702
  // is the call site that failed twice in a row live.
2703
+ let tab = self.tab().map(Arc::clone).map_err(|e| e.to_string());
2587
2704
  let status = retry_on_transport_fault(|| {
2588
- self.tab()
2589
- .map_err(|e| e.to_string())?
2590
- .evaluate(&call, false)
2591
- .map_err(|e| e.to_string())
2705
+ let tab = tab.clone()?;
2706
+ let call = call.clone();
2707
+ // issue #616: this call can stall for minutes on a dead
2708
+ // connection before the vendored transport's own much larger
2709
+ // timeout gives up - bound the wait from this side instead.
2710
+ with_cdp_timeout(move || tab.evaluate(&call, false).map_err(|e| e.to_string()))
2592
2711
  })
2593
2712
  .map_err(|e| web_err("acting inside an iframe", e))?
2594
2713
  .value
@@ -2897,6 +3016,7 @@ impl AppDriver for WebAppDriver {
2897
3016
  _window_name: &str,
2898
3017
  _timeout: Duration,
2899
3018
  ) -> Result<(), DriverError> {
3019
+ self.observed_collections.clear();
2900
3020
  let staged_browser = self.staged_browser.take();
2901
3021
  // Extra Chrome flags only apply at process start: swap in a
2902
3022
  // PRIVATE browser for this flow (a plain tab on it is already
@@ -2925,7 +3045,7 @@ impl AppDriver for WebAppDriver {
2925
3045
  for_tab: None,
2926
3046
  hidden: None,
2927
3047
  }),
2928
- None => self.browser.new_tab(),
3048
+ None => private_flow_tab(&self.browser),
2929
3049
  }
2930
3050
  .map_err(|e| web_err("opening tab", e))?;
2931
3051
  // A visible flow is the only Chrome window the user should have to
@@ -4312,11 +4432,13 @@ impl AppDriver for WebAppDriver {
4312
4432
  // the tab directly, with no element to re-resolve, so it never went
4313
4433
  // through that retry at all - any transient CDP hiccup here killed
4314
4434
  // the whole flow on the first blip, with zero chance to recover.
4435
+ let tab = self.tab().map(Arc::clone).map_err(|e| e.to_string());
4315
4436
  let status = retry_on_transport_fault(|| {
4316
- self.tab()
4317
- .map_err(|e| e.to_string())?
4318
- .evaluate(&call, false)
4319
- .map_err(|e| e.to_string())
4437
+ let tab = tab.clone()?;
4438
+ let call = call.clone();
4439
+ // issue #616: bound this call's wait from the caller's side
4440
+ // instead of the vendored transport's much larger one.
4441
+ with_cdp_timeout(move || tab.evaluate(&call, false).map_err(|e| e.to_string()))
4320
4442
  })
4321
4443
  .map_err(|e| web_err("probing an iframe", e))?
4322
4444
  .value
@@ -5124,6 +5246,14 @@ impl AppDriver for WebAppDriver {
5124
5246
  const ordered = all.filter(priorityField).concat(all.filter(interactive),
5125
5247
  all.filter(el => !interactive(el) && readableLeaf(el))
5126
5248
  );
5249
+ const hitTestableTick = el => {
5250
+ if (!el.matches('input[type=checkbox], input[type=radio]')) return false;
5251
+ const r = el.getBoundingClientRect();
5252
+ // Transparent native controls often overlay a drawn checkbox.
5253
+ // Include only controls that really receive the pointer here.
5254
+ return r.width > 1 && r.height > 1 &&
5255
+ document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2) === el;
5256
+ };
5127
5257
  const seen = new Set();
5128
5258
  const chosen = ordered.filter(el => {
5129
5259
  const r = el.getBoundingClientRect();
@@ -5133,7 +5263,7 @@ impl AppDriver for WebAppDriver {
5133
5263
  // even when the field starts below the fold, and every web
5134
5264
  // action already scrolls its target into view before acting.
5135
5265
  const rendered = style.display !== 'none' && style.visibility !== 'hidden' &&
5136
- Number(style.opacity) > 0 && r.width > 0 && r.height > 0;
5266
+ (Number(style.opacity) > 0 || hitTestableTick(el)) && r.width > 0 && r.height > 0;
5137
5267
  if (!rendered || seen.has(el)) return false;
5138
5268
  seen.add(el);
5139
5269
  return true;
@@ -5208,6 +5338,22 @@ impl AppDriver for WebAppDriver {
5208
5338
  // final cell without naming one current row. Represent those
5209
5339
  // collection identities directly so the model can ground the
5210
5340
  // intent without inventing a selector from today's DOM.
5341
+ // Keep empty list identities available for assertions. A model
5342
+ // must not invent a selector just because the last item vanished.
5343
+ for (const list of document.querySelectorAll('ul, ol, [role="list"]')) {
5344
+ const listCss = semanticCss(list);
5345
+ if (!listCss) continue;
5346
+ const itemCss = listCss + ' > :is(li, [role="listitem"])';
5347
+ entries.push({
5348
+ target: 'css:' + itemCss,
5349
+ css: itemCss,
5350
+ tag: 'collection',
5351
+ actionable: false,
5352
+ count: list.querySelectorAll(':scope > :is(li, [role="listitem"])').length,
5353
+ label: 'items in ' + (list.getAttribute('aria-label') || listCss),
5354
+ });
5355
+ }
5356
+
5211
5357
  for (const table of document.querySelectorAll('table')) {
5212
5358
  const tableCss = semanticCss(table);
5213
5359
  if (!tableCss) continue;
@@ -5304,11 +5450,16 @@ impl AppDriver for WebAppDriver {
5304
5450
  });
5305
5451
  })()
5306
5452
  "#;
5453
+ let tab = Arc::clone(self.tab()?);
5307
5454
  let sample = || {
5308
- let value = self
5309
- .tab()?
5310
- .evaluate(SCENE_JS, false)
5311
- .map_err(|e| web_err("evaluating scene script", e))?;
5455
+ let tab = Arc::clone(&tab);
5456
+ // Replay now asks for a settled scene before judging text after
5457
+ // navigation. Keep that defensive wait from opening another
5458
+ // unbounded direct-CDP path: if this read stalls, the assertion
5459
+ // falls back to its ordinary recorded polling window.
5460
+ let value =
5461
+ with_cdp_timeout(move || tab.evaluate(SCENE_JS, false).map_err(|e| e.to_string()))
5462
+ .map_err(|e| web_err("evaluating scene script", e))?;
5312
5463
  let raw = value
5313
5464
  .value
5314
5465
  .and_then(|v| v.as_str().map(str::to_string))
@@ -5320,13 +5471,50 @@ impl AppDriver for WebAppDriver {
5320
5471
  entries: parsed["entries"].as_array().cloned().unwrap_or_default(),
5321
5472
  })
5322
5473
  };
5323
- let entries = settled_scene(
5474
+ let mut entries = settled_scene(
5324
5475
  sample,
5325
5476
  || std::thread::sleep(SCENE_SETTLE_INTERVAL),
5326
5477
  || self.network_idle(),
5327
5478
  SCENE_SETTLE_ROUNDS,
5328
5479
  SCENE_SETTLE_BUSY_ROUNDS,
5329
5480
  )?;
5481
+ for entry in entries.iter().filter(|e| e["tag"] == "collection") {
5482
+ if !self
5483
+ .observed_collections
5484
+ .iter()
5485
+ .any(|old| old["target"] == entry["target"])
5486
+ && self.observed_collections.len() < 50
5487
+ {
5488
+ self.observed_collections.push(entry.clone());
5489
+ }
5490
+ }
5491
+ // Some apps remove the whole list when its last item is deleted.
5492
+ // Retain only collection identities seen in this browser session,
5493
+ // and re-count the DOM now; never infer absence from missing inventory.
5494
+ let missing: Vec<_> = self
5495
+ .observed_collections
5496
+ .iter()
5497
+ .filter(|old| !entries.iter().any(|e| e["target"] == old["target"]))
5498
+ .cloned()
5499
+ .collect();
5500
+ if !missing.is_empty() {
5501
+ let input =
5502
+ serde_json::to_string(&missing).map_err(|e| DriverError::Browser(e.to_string()))?;
5503
+ let script = format!("JSON.stringify(({input}).map(e => ({{...e, count: document.querySelectorAll(e.css).length, observed_before: true}})))");
5504
+ let value = self
5505
+ .tab()?
5506
+ .evaluate(&script, false)
5507
+ .map_err(|e| web_err("counting previously observed lists", e))?;
5508
+ let previous: Vec<serde_json::Value> = serde_json::from_str(
5509
+ value
5510
+ .value
5511
+ .as_ref()
5512
+ .and_then(|v| v.as_str())
5513
+ .ok_or_else(|| DriverError::Browser("collection counts missing".into()))?,
5514
+ )
5515
+ .map_err(|e| DriverError::Browser(format!("collection counts: {e}")))?;
5516
+ entries.extend(previous);
5517
+ }
5330
5518
  let json = serde_json::to_string(&entries)
5331
5519
  .map_err(|e| DriverError::Browser(format!("re-serialising scene: {e}")))?;
5332
5520
  Ok(Some(json))
@@ -5913,3 +6101,42 @@ mod tests {
5913
6101
  assert_eq!(got, vec![&a, &b]);
5914
6102
  }
5915
6103
  }
6104
+
6105
+ #[cfg(test)]
6106
+ mod startup_tab_tests {
6107
+ #[test]
6108
+ fn private_browser_reuses_startup_tab_without_replacing_a_loaded_page() {
6109
+ if std::env::var("FLOWPROOF_E2E").as_deref() != Ok("1") {
6110
+ eprintln!("set FLOWPROOF_E2E=1 to run the real browser check");
6111
+ return;
6112
+ }
6113
+ let browser = super::Browser::new(
6114
+ super::launch_options_for(&[], true).expect("valid launch options"),
6115
+ )
6116
+ .expect("browser starts");
6117
+ let first = super::private_flow_tab(&browser).expect("flow tab opens");
6118
+ assert_eq!(
6119
+ browser
6120
+ .get_tabs()
6121
+ .lock()
6122
+ .expect("tab registry is available")
6123
+ .len(),
6124
+ 1
6125
+ );
6126
+ first
6127
+ .navigate_to("data:text/html,<title>Demo</title>Ready")
6128
+ .expect("demo page loads");
6129
+ first.wait_until_navigated().expect("demo page loads");
6130
+ let second = super::private_flow_tab(&browser).expect("flow tab opens");
6131
+ assert_ne!(first.get_target_id(), second.get_target_id());
6132
+ assert!(first.get_url().starts_with("data:text/html,"));
6133
+ assert_eq!(
6134
+ browser
6135
+ .get_tabs()
6136
+ .lock()
6137
+ .expect("tab registry is available")
6138
+ .len(),
6139
+ 2
6140
+ );
6141
+ }
6142
+ }
@@ -0,0 +1,49 @@
1
+ use flowproof_driver::AppDriver;
2
+
3
+ #[test]
4
+ fn empty_and_populated_lists_offer_grounded_collection_targets() {
5
+ if std::env::var("FLOWPROOF_E2E").as_deref() != Ok("1") {
6
+ return;
7
+ }
8
+ let _guard = flowproof_adapters::SharedBrowserGuard::new();
9
+ let mut driver = flowproof_adapters::WebAppDriver::new().expect("browser starts");
10
+ driver
11
+ .launch(
12
+ "data:text/html,<input id='tick' type='checkbox' aria-label='Toggle task' style='opacity:0;width:30px;height:30px'><input id='hidden' type='checkbox' style='display:none'><button id='remove' onclick='document.getElementById(&quot;steps&quot;).remove()'>Remove</button><ul id='tasks'></ul><ol id='steps'><li>One</li><li>Two</li></ol>",
13
+ "",
14
+ std::time::Duration::from_secs(10),
15
+ )
16
+ .expect("fixture opens");
17
+ let scene = driver.scene().expect("scene reads").expect("scene exists");
18
+ let entries: Vec<serde_json::Value> = serde_json::from_str(&scene).expect("scene JSON");
19
+ let tick = entries
20
+ .iter()
21
+ .find(|e| e["target"] == "css:#tick")
22
+ .expect("transparent hit-testable checkbox offered");
23
+ assert_eq!(tick["actionable"], true);
24
+ assert_eq!(tick["checked"], false);
25
+ assert!(!entries.iter().any(|e| e["target"] == "css:#hidden"));
26
+ for (id, count) in [("tasks", 0), ("steps", 2)] {
27
+ let token = format!("css:#{id} > :is(li, [role=\"listitem\"])");
28
+ let entry = entries
29
+ .iter()
30
+ .find(|e| e["target"] == token)
31
+ .expect("collection target exists even when empty");
32
+ assert_eq!(entry["count"], count);
33
+ assert_eq!(entry["actionable"], false);
34
+ }
35
+ driver
36
+ .invoke(&flowproof_driver::UiaSelector::css("#remove"))
37
+ .expect("remove populated list");
38
+ let scene = driver
39
+ .scene()
40
+ .expect("scene after removal")
41
+ .expect("scene exists");
42
+ let entries: Vec<serde_json::Value> = serde_json::from_str(&scene).expect("scene JSON");
43
+ let previous = entries
44
+ .iter()
45
+ .find(|e| e["target"] == "css:#steps > :is(li, [role=\"listitem\"])")
46
+ .expect("previous list retained");
47
+ assert_eq!(previous["count"], 0);
48
+ assert_eq!(previous["observed_before"], true);
49
+ }