agentic-project-kit 1.0.1__py3-none-any.whl

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 (310) hide show
  1. agentic_project_kit/__init__.py +1 -0
  2. agentic_project_kit/absolute_path_portability_audit.py +241 -0
  3. agentic_project_kit/access_level_policy.py +14 -0
  4. agentic_project_kit/access_levels.py +22 -0
  5. agentic_project_kit/action_registry.py +195 -0
  6. agentic_project_kit/action_specs.py +286 -0
  7. agentic_project_kit/agent_command_runner.py +516 -0
  8. agentic_project_kit/audit_test_matrix.py +36 -0
  9. agentic_project_kit/audit_test_matrix_cli.py +43 -0
  10. agentic_project_kit/chat_bootloader.py +257 -0
  11. agentic_project_kit/chat_entrypoint_contract.py +297 -0
  12. agentic_project_kit/checks.py +511 -0
  13. agentic_project_kit/ci_readiness.py +211 -0
  14. agentic_project_kit/cli.py +147 -0
  15. agentic_project_kit/cli_commands/__init__.py +1 -0
  16. agentic_project_kit/cli_commands/absolute_path_audit.py +25 -0
  17. agentic_project_kit/cli_commands/actions.py +21 -0
  18. agentic_project_kit/cli_commands/artifact_gc.py +240 -0
  19. agentic_project_kit/cli_commands/boot.py +55 -0
  20. agentic_project_kit/cli_commands/chat.py +34 -0
  21. agentic_project_kit/cli_commands/checks.py +305 -0
  22. agentic_project_kit/cli_commands/cockpit.py +103 -0
  23. agentic_project_kit/cli_commands/command_taxonomy.py +25 -0
  24. agentic_project_kit/cli_commands/commands.py +129 -0
  25. agentic_project_kit/cli_commands/dev.py +23 -0
  26. agentic_project_kit/cli_commands/direction.py +92 -0
  27. agentic_project_kit/cli_commands/doc_currency_audit.py +25 -0
  28. agentic_project_kit/cli_commands/doc_registry.py +126 -0
  29. agentic_project_kit/cli_commands/docs.py +223 -0
  30. agentic_project_kit/cli_commands/dpa.py +1338 -0
  31. agentic_project_kit/cli_commands/evidence.py +251 -0
  32. agentic_project_kit/cli_commands/github.py +34 -0
  33. agentic_project_kit/cli_commands/governance.py +14 -0
  34. agentic_project_kit/cli_commands/gui.py +23 -0
  35. agentic_project_kit/cli_commands/gui_readiness_gate.py +27 -0
  36. agentic_project_kit/cli_commands/handoff.py +183 -0
  37. agentic_project_kit/cli_commands/human_workflows.py +430 -0
  38. agentic_project_kit/cli_commands/init.py +132 -0
  39. agentic_project_kit/cli_commands/instruction.py +75 -0
  40. agentic_project_kit/cli_commands/mutation_lock_audit.py +24 -0
  41. agentic_project_kit/cli_commands/ns_legacy_audit.py +25 -0
  42. agentic_project_kit/cli_commands/pass_already_done.py +66 -0
  43. agentic_project_kit/cli_commands/patch_failure_discipline_audit.py +26 -0
  44. agentic_project_kit/cli_commands/patch_preflight.py +37 -0
  45. agentic_project_kit/cli_commands/path_literal_audit.py +39 -0
  46. agentic_project_kit/cli_commands/patterns.py +40 -0
  47. agentic_project_kit/cli_commands/planning_docs_audit.py +25 -0
  48. agentic_project_kit/cli_commands/pr.py +133 -0
  49. agentic_project_kit/cli_commands/pr_hygiene.py +27 -0
  50. agentic_project_kit/cli_commands/profiles.py +26 -0
  51. agentic_project_kit/cli_commands/program_redundancy_audit.py +25 -0
  52. agentic_project_kit/cli_commands/project_direction.py +25 -0
  53. agentic_project_kit/cli_commands/release.py +392 -0
  54. agentic_project_kit/cli_commands/release_publish_orchestration.py +36 -0
  55. agentic_project_kit/cli_commands/remote_branch_hygiene.py +152 -0
  56. agentic_project_kit/cli_commands/remote_next.py +57 -0
  57. agentic_project_kit/cli_commands/removed_ns_commands.py +65 -0
  58. agentic_project_kit/cli_commands/rule_registry.py +129 -0
  59. agentic_project_kit/cli_commands/rules.py +222 -0
  60. agentic_project_kit/cli_commands/scaffold.py +46 -0
  61. agentic_project_kit/cli_commands/slice.py +21 -0
  62. agentic_project_kit/cli_commands/standard_gates_audit_suite.py +27 -0
  63. agentic_project_kit/cli_commands/state.py +51 -0
  64. agentic_project_kit/cli_commands/status_current_state_audit.py +30 -0
  65. agentic_project_kit/cli_commands/todo.py +53 -0
  66. agentic_project_kit/cli_commands/transfer.py +19 -0
  67. agentic_project_kit/cli_commands/transfer_context_flow.py +398 -0
  68. agentic_project_kit/cli_commands/transfer_context_helpers.py +398 -0
  69. agentic_project_kit/cli_commands/transfer_diagnostics.py +767 -0
  70. agentic_project_kit/cli_commands/transfer_evidence_flow.py +368 -0
  71. agentic_project_kit/cli_commands/transfer_handoff_flow.py +594 -0
  72. agentic_project_kit/cli_commands/transfer_order_contract.py +74 -0
  73. agentic_project_kit/cli_commands/transfer_post_merge_complete.py +442 -0
  74. agentic_project_kit/cli_commands/transfer_post_merge_settle.py +171 -0
  75. agentic_project_kit/cli_commands/transfer_pr_closeout_complete.py +124 -0
  76. agentic_project_kit/cli_commands/transfer_pr_create_flow.py +743 -0
  77. agentic_project_kit/cli_commands/transfer_pr_merge_flow.py +547 -0
  78. agentic_project_kit/cli_commands/transfer_repo_after_pr.py +160 -0
  79. agentic_project_kit/cli_commands/transfer_repo_pre_pr.py +664 -0
  80. agentic_project_kit/cli_commands/transfer_runner_flow.py +131 -0
  81. agentic_project_kit/cli_commands/transfer_runtime_initial.py +194 -0
  82. agentic_project_kit/cli_commands/transfer_shared.py +274 -0
  83. agentic_project_kit/cli_commands/validation.py +200 -0
  84. agentic_project_kit/cli_commands/version.py +27 -0
  85. agentic_project_kit/cli_commands/work_orders.py +219 -0
  86. agentic_project_kit/cli_commands/workflow.py +471 -0
  87. agentic_project_kit/cli_commands/workflow_guard.py +26 -0
  88. agentic_project_kit/cli_commands/workspace.py +252 -0
  89. agentic_project_kit/cockpit.py +608 -0
  90. agentic_project_kit/cockpit_readiness.py +67 -0
  91. agentic_project_kit/command_authority_audit.py +192 -0
  92. agentic_project_kit/command_inbox_check.py +103 -0
  93. agentic_project_kit/command_manifest.py +591 -0
  94. agentic_project_kit/command_queue_contract.py +161 -0
  95. agentic_project_kit/command_selector.py +245 -0
  96. agentic_project_kit/command_taxonomy.py +264 -0
  97. agentic_project_kit/commit_guard.py +67 -0
  98. agentic_project_kit/communication_artifact_gc.py +495 -0
  99. agentic_project_kit/communication_rule_context.py +437 -0
  100. agentic_project_kit/communication_state.py +115 -0
  101. agentic_project_kit/composed_wrapper_invariants.py +97 -0
  102. agentic_project_kit/composite_short_circuit.py +140 -0
  103. agentic_project_kit/contract.py +251 -0
  104. agentic_project_kit/control_state.py +83 -0
  105. agentic_project_kit/doc_currency_audit.py +536 -0
  106. agentic_project_kit/doc_lifecycle.py +1179 -0
  107. agentic_project_kit/doc_lifecycle_signals.py +446 -0
  108. agentic_project_kit/doc_lifecycle_sweep.py +639 -0
  109. agentic_project_kit/doc_mesh.py +491 -0
  110. agentic_project_kit/doctor.py +379 -0
  111. agentic_project_kit/documentation_registry.py +1122 -0
  112. agentic_project_kit/documentation_system_audit.py +310 -0
  113. agentic_project_kit/dpa_current_handoff_lifecycle.py +549 -0
  114. agentic_project_kit/dpa_dp2_decision_readiness.py +362 -0
  115. agentic_project_kit/dpa_dp3_dp4_adjudication.py +604 -0
  116. agentic_project_kit/dpa_dp5_block_new_gate.py +294 -0
  117. agentic_project_kit/dpa_dp5_stage_adoption.py +615 -0
  118. agentic_project_kit/dpa_dp5_strict_gate.py +187 -0
  119. agentic_project_kit/dpa_final_closeout.py +627 -0
  120. agentic_project_kit/dpa_fixture_evidence.py +680 -0
  121. agentic_project_kit/dpa_maintainer_record_check.py +711 -0
  122. agentic_project_kit/dpa_post_dp2_scope_assessment.py +734 -0
  123. agentic_project_kit/dpa_probe_002_readiness.py +509 -0
  124. agentic_project_kit/dpa_probe_003_readiness.py +417 -0
  125. agentic_project_kit/dpa_probe_004_readiness.py +437 -0
  126. agentic_project_kit/dpa_readiness.py +505 -0
  127. agentic_project_kit/dpa_readonly_probe_execution.py +500 -0
  128. agentic_project_kit/dpa_registry_contracts.py +312 -0
  129. agentic_project_kit/dpa_renderer_readiness.py +407 -0
  130. agentic_project_kit/dpa_repo_adoption_assessment.py +574 -0
  131. agentic_project_kit/dpa_stable_readiness.py +743 -0
  132. agentic_project_kit/dpa_successor_projection.py +168 -0
  133. agentic_project_kit/dpa_workspace_init_projection.py +93 -0
  134. agentic_project_kit/dpa_wrt_ch001_evidence.py +327 -0
  135. agentic_project_kit/entrypoint_slice_runner.py +153 -0
  136. agentic_project_kit/evidence_clean.py +152 -0
  137. agentic_project_kit/evidence_commit_paths.py +214 -0
  138. agentic_project_kit/evidence_finalize_log.py +204 -0
  139. agentic_project_kit/evidence_guard.py +114 -0
  140. agentic_project_kit/evidence_inspector.py +507 -0
  141. agentic_project_kit/evidence_state_contract.py +99 -0
  142. agentic_project_kit/execution_mode_state.py +148 -0
  143. agentic_project_kit/final_summary_contract.py +85 -0
  144. agentic_project_kit/finalize_guard.py +139 -0
  145. agentic_project_kit/gatekeeper_core.py +232 -0
  146. agentic_project_kit/github.py +25 -0
  147. agentic_project_kit/governance.py +59 -0
  148. agentic_project_kit/gui_action_execution.py +105 -0
  149. agentic_project_kit/gui_action_renderer.py +81 -0
  150. agentic_project_kit/gui_action_run_intent.py +98 -0
  151. agentic_project_kit/gui_action_views.py +195 -0
  152. agentic_project_kit/gui_activity_log.py +76 -0
  153. agentic_project_kit/gui_button_catalog.py +605 -0
  154. agentic_project_kit/gui_cockpit.py +365 -0
  155. agentic_project_kit/gui_cockpit_actions.py +695 -0
  156. agentic_project_kit/gui_cockpit_common.py +172 -0
  157. agentic_project_kit/gui_cockpit_header.py +501 -0
  158. agentic_project_kit/gui_cockpit_sidebar.py +387 -0
  159. agentic_project_kit/gui_cockpit_task.py +748 -0
  160. agentic_project_kit/gui_command_projection.py +355 -0
  161. agentic_project_kit/gui_communication_modes.py +130 -0
  162. agentic_project_kit/gui_dry_run.py +219 -0
  163. agentic_project_kit/gui_gatekeeper_status.py +259 -0
  164. agentic_project_kit/gui_layout_plan.py +148 -0
  165. agentic_project_kit/gui_open_folder.py +18 -0
  166. agentic_project_kit/gui_output_status_panel.py +61 -0
  167. agentic_project_kit/gui_panel_state.py +47 -0
  168. agentic_project_kit/gui_presenter.py +62 -0
  169. agentic_project_kit/gui_readiness_gate.py +206 -0
  170. agentic_project_kit/gui_release_flow.py +59 -0
  171. agentic_project_kit/gui_task_editor.py +1077 -0
  172. agentic_project_kit/gui_tk_widgets.py +177 -0
  173. agentic_project_kit/gui_tkinter_renderer.py +87 -0
  174. agentic_project_kit/gui_tkinter_shell.py +1176 -0
  175. agentic_project_kit/gui_transfer_contract.py +28 -0
  176. agentic_project_kit/gui_viewmodel.py +698 -0
  177. agentic_project_kit/gui_window_guard.py +46 -0
  178. agentic_project_kit/handoff_freshness.py +390 -0
  179. agentic_project_kit/handoff_prompt.py +180 -0
  180. agentic_project_kit/handoff_state.py +254 -0
  181. agentic_project_kit/instruction_lint.py +455 -0
  182. agentic_project_kit/llm_context_carriers.py +66 -0
  183. agentic_project_kit/llm_execution_context.py +285 -0
  184. agentic_project_kit/local_command_stack.py +133 -0
  185. agentic_project_kit/local_feature_gate.py +82 -0
  186. agentic_project_kit/local_garbage_collector.py +269 -0
  187. agentic_project_kit/models.py +18 -0
  188. agentic_project_kit/mutation_lock_audit.py +421 -0
  189. agentic_project_kit/next_turn_evidence.py +253 -0
  190. agentic_project_kit/next_turn_merge_if_green.py +459 -0
  191. agentic_project_kit/next_turn_pr_status.py +255 -0
  192. agentic_project_kit/next_turn_result.py +183 -0
  193. agentic_project_kit/next_turn_runner.py +159 -0
  194. agentic_project_kit/next_turn_slot.py +107 -0
  195. agentic_project_kit/next_turn_status.py +274 -0
  196. agentic_project_kit/ns_legacy_reference_audit.py +258 -0
  197. agentic_project_kit/ns_slice_runner.py +16 -0
  198. agentic_project_kit/operational_handoff_projection.py +124 -0
  199. agentic_project_kit/output_contract.py +80 -0
  200. agentic_project_kit/output_repair.py +61 -0
  201. agentic_project_kit/pass_already_done.py +216 -0
  202. agentic_project_kit/patch_artifact_preflight.py +147 -0
  203. agentic_project_kit/patch_cycle_workflow.py +440 -0
  204. agentic_project_kit/patch_failure_discipline_audit.py +223 -0
  205. agentic_project_kit/patch_preflight.py +215 -0
  206. agentic_project_kit/path_literal_audit.py +612 -0
  207. agentic_project_kit/patterns.py +99 -0
  208. agentic_project_kit/planning_docs_consolidation_audit.py +313 -0
  209. agentic_project_kit/portability_shell_gate.py +40 -0
  210. agentic_project_kit/post_merge_handoff_refresh.py +56 -0
  211. agentic_project_kit/post_release.py +252 -0
  212. agentic_project_kit/post_release_closeout.py +616 -0
  213. agentic_project_kit/pr_cleanup.py +172 -0
  214. agentic_project_kit/pr_closeout.py +50 -0
  215. agentic_project_kit/pr_closeout_complete.py +344 -0
  216. agentic_project_kit/pr_create_or_skip.py +71 -0
  217. agentic_project_kit/pr_hygiene.py +210 -0
  218. agentic_project_kit/program_redundancy_audit.py +251 -0
  219. agentic_project_kit/project_direction.py +840 -0
  220. agentic_project_kit/protected_change_planner.py +194 -0
  221. agentic_project_kit/release.py +401 -0
  222. agentic_project_kit/release_doi_safety.py +24 -0
  223. agentic_project_kit/release_gate_core.py +174 -0
  224. agentic_project_kit/release_metadata_authority_gate.py +328 -0
  225. agentic_project_kit/release_metadata_prep.py +43 -0
  226. agentic_project_kit/release_notes.py +823 -0
  227. agentic_project_kit/release_prep_core.py +204 -0
  228. agentic_project_kit/release_prepare.py +369 -0
  229. agentic_project_kit/release_process_guardrails.py +247 -0
  230. agentic_project_kit/release_publish_core.py +114 -0
  231. agentic_project_kit/release_publish_orchestration.py +469 -0
  232. agentic_project_kit/release_state.py +552 -0
  233. agentic_project_kit/release_verify_core.py +175 -0
  234. agentic_project_kit/remote_branch_hygiene.py +223 -0
  235. agentic_project_kit/remote_next.py +89 -0
  236. agentic_project_kit/remote_next_closeout.py +101 -0
  237. agentic_project_kit/remote_next_order_contract.py +391 -0
  238. agentic_project_kit/removed_ns_commands.py +458 -0
  239. agentic_project_kit/removed_source_audit.py +225 -0
  240. agentic_project_kit/rendering.py +13 -0
  241. agentic_project_kit/repair_report.py +79 -0
  242. agentic_project_kit/repo_identity.py +59 -0
  243. agentic_project_kit/result_report_classifier.py +57 -0
  244. agentic_project_kit/rule_ack.py +105 -0
  245. agentic_project_kit/rule_preservation.py +58 -0
  246. agentic_project_kit/rule_refresh.py +174 -0
  247. agentic_project_kit/rule_registry_registration.py +322 -0
  248. agentic_project_kit/rule_registry_report.py +206 -0
  249. agentic_project_kit/rule_registry_validator.py +704 -0
  250. agentic_project_kit/rule_snapshot.py +98 -0
  251. agentic_project_kit/rule_source_validator.py +133 -0
  252. agentic_project_kit/run_summary.py +68 -0
  253. agentic_project_kit/run_summary_renderer.py +238 -0
  254. agentic_project_kit/runtime_validator.py +68 -0
  255. agentic_project_kit/safe_file_replace.py +35 -0
  256. agentic_project_kit/safe_patch.py +47 -0
  257. agentic_project_kit/safe_push.py +218 -0
  258. agentic_project_kit/safe_remove_diagnostic.py +51 -0
  259. agentic_project_kit/scaffold.py +69 -0
  260. agentic_project_kit/site_claims.py +444 -0
  261. agentic_project_kit/site_generator.py +1140 -0
  262. agentic_project_kit/slice_gate.py +225 -0
  263. agentic_project_kit/slice_runner.py +75 -0
  264. agentic_project_kit/standard_gates_audit_suite.py +167 -0
  265. agentic_project_kit/state_freshness.py +135 -0
  266. agentic_project_kit/status_current_state_audit.py +762 -0
  267. agentic_project_kit/successor_handoff_package.py +1763 -0
  268. agentic_project_kit/templates.py +292 -0
  269. agentic_project_kit/terminal_block_guard.py +166 -0
  270. agentic_project_kit/terminal_logging.py +275 -0
  271. agentic_project_kit/todo.py +97 -0
  272. agentic_project_kit/transfer_closeout.py +136 -0
  273. agentic_project_kit/transfer_continue.py +215 -0
  274. agentic_project_kit/transfer_local_runner.py +101 -0
  275. agentic_project_kit/transfer_operation_monitor.py +236 -0
  276. agentic_project_kit/transfer_post_merge_lifecycle.py +356 -0
  277. agentic_project_kit/transfer_post_merge_settle.py +383 -0
  278. agentic_project_kit/transfer_pr_actions.py +164 -0
  279. agentic_project_kit/transfer_remote_next.py +959 -0
  280. agentic_project_kit/transfer_repo_actions.py +2253 -0
  281. agentic_project_kit/transfer_runner.py +347 -0
  282. agentic_project_kit/transfer_safety_context.py +291 -0
  283. agentic_project_kit/transfer_state.py +483 -0
  284. agentic_project_kit/transfer_uplink.py +471 -0
  285. agentic_project_kit/transfer_workflow_next.py +148 -0
  286. agentic_project_kit/typed_work_order_evidence.py +58 -0
  287. agentic_project_kit/typed_work_order_queue.py +166 -0
  288. agentic_project_kit/typed_work_order_runner.py +168 -0
  289. agentic_project_kit/work_cycle.py +317 -0
  290. agentic_project_kit/work_discard_changes.py +129 -0
  291. agentic_project_kit/work_order_patch.py +199 -0
  292. agentic_project_kit/work_order_runner.py +120 -0
  293. agentic_project_kit/work_order_uploader.py +220 -0
  294. agentic_project_kit/work_order_validator.py +185 -0
  295. agentic_project_kit/work_orders.py +246 -0
  296. agentic_project_kit/workflow_guard.py +262 -0
  297. agentic_project_kit/workflow_summary_runner.py +124 -0
  298. agentic_project_kit/workspace.py +665 -0
  299. agentic_project_kit/workspace_adopt.py +471 -0
  300. agentic_project_kit/workspace_dpa_intake.py +406 -0
  301. agentic_project_kit/workspace_init.py +487 -0
  302. agentic_project_kit/workspace_lock.py +129 -0
  303. agentic_project_kit/workspace_remove.py +381 -0
  304. agentic_project_kit/workspace_upgrade.py +324 -0
  305. agentic_project_kit/wrapper_live_status.py +104 -0
  306. agentic_project_kit-1.0.1.dist-info/METADATA +724 -0
  307. agentic_project_kit-1.0.1.dist-info/RECORD +310 -0
  308. agentic_project_kit-1.0.1.dist-info/WHEEL +4 -0
  309. agentic_project_kit-1.0.1.dist-info/entry_points.txt +3 -0
  310. agentic_project_kit-1.0.1.dist-info/licenses/LICENSE +10 -0
@@ -0,0 +1,509 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ import json
5
+ from pathlib import Path
6
+ import subprocess
7
+ from typing import Any
8
+
9
+ from agentic_project_kit.dpa_readiness import DEFAULT_READINESS_PATH
10
+ from agentic_project_kit.workspace import load_workspace
11
+
12
+ EXPECTED_WRITERS = (
13
+ "WRT-CH-001",
14
+ "WRT-CH-002",
15
+ "WRT-CH-003",
16
+ "WRT-CH-004",
17
+ "WRT-CH-005",
18
+ "WRT-CH-006",
19
+ )
20
+ PROBE_002_SOURCE_PATHS = (
21
+ "src/agentic_project_kit/doc_lifecycle.py",
22
+ "src/agentic_project_kit/doc_lifecycle_sweep.py",
23
+ "src/agentic_project_kit/cli_commands/docs.py",
24
+ "src/agentic_project_kit/workspace.py",
25
+ "src/agentic_project_kit/workspace_lock.py",
26
+ "src/agentic_project_kit/transfer_repo_actions.py",
27
+ "src/agentic_project_kit/release_prepare.py",
28
+ "src/agentic_project_kit/post_release_closeout.py",
29
+ "src/agentic_project_kit/action_specs.py",
30
+ "src/agentic_project_kit/dpa_workspace_init_projection.py",
31
+ "src/agentic_project_kit/templates.py",
32
+ "src/agentic_project_kit/workspace_init.py",
33
+ )
34
+ PROBE_002_TEST_GLOBS = (
35
+ "tests/test_doc_lifecycle.py",
36
+ "tests/test_docs_lifecycle_*.py",
37
+ "tests/test_transfer_repo_actions.py",
38
+ "tests/test_release_prepare_command.py",
39
+ "tests/test_release_command_authority.py",
40
+ "tests/test_release_prep_core.py",
41
+ "tests/test_post_release.py",
42
+ "tests/test_post_release_doi_closeout_atomicity.py",
43
+ "tests/test_action_specs.py",
44
+ "tests/test_generator.py",
45
+ "tests/test_workspace_init.py",
46
+ )
47
+ SELECTED_WRITER_PLAN_PATH = "docs/architecture/dpa/probes/DP1_SELECTED_WRITER_FIXTURE_PLAN_20260727.md"
48
+ PROBE_MANUAL_PATH = "docs/architecture/dpa/probes/DP1_PROBE_MANUALS_20260727.md"
49
+ PROBE_EXECUTION_PACKAGE_PATH = "docs/architecture/dpa/probes/DP1_PROBE_EXECUTION_PACKAGE_DRAFT_20260727.md"
50
+ EVIDENCE_OUTPUT_ROOT_PARTS = ("evidence", "dpa", "probes")
51
+
52
+ REQUIRED_GROUPS = (
53
+ ("P2-G01", "immutable plan capture", "candidate-preflight"),
54
+ ("P2-G02", "source, target, base, contract, renderer, partition and ownership guards", "candidate-preflight"),
55
+ ("P2-G03", "local Workspace lock and same-process reentrancy rejection", "candidate-preflight"),
56
+ ("P2-G04", "stale-plan rejection before Write and under lock", "candidate-preflight"),
57
+ ("P2-G05", "atomic complete-target or partition-preserving replacement", "candidate-preflight"),
58
+ ("P2-G06", "post-Write verification", "candidate-preflight"),
59
+ ("P2-G07", "acceptance-state creation, tamper detection and scope validation", "candidate-preflight"),
60
+ ("P2-G08", "conditional accepted-base persistence", "candidate-preflight"),
61
+ ("P2-G09", "base-independent post-acceptance evaluation", "candidate-preflight"),
62
+ ("P2-G10", "gate-set re-acceptance without renderer invocation or target mutation", "candidate-preflight"),
63
+ ("P2-G11", "layered acceptance for registered-region projections", "candidate-preflight"),
64
+ ("P2-G12", "lifecycle finding and severity compatibility", "candidate-preflight"),
65
+ ("P2-G13", "every then-known selected writer for selected targets", "selected-writers"),
66
+ ("P2-G14", "explicit out-of-scope adjudication for write-capable paths", "selected-writers"),
67
+ )
68
+
69
+
70
+ @dataclass(frozen=True)
71
+ class Probe002Finding:
72
+ code: str
73
+ message: str
74
+ path: str
75
+
76
+ def as_dict(self) -> dict[str, str]:
77
+ return {"code": self.code, "message": self.message, "path": self.path}
78
+
79
+
80
+ @dataclass(frozen=True)
81
+ class Probe002ReadinessResult:
82
+ root: str
83
+ validation_ref: str
84
+ readiness_record: str
85
+ data: dict[str, Any]
86
+ findings: tuple[Probe002Finding, ...]
87
+
88
+ @property
89
+ def structural_ok(self) -> bool:
90
+ return not self.findings
91
+
92
+ @property
93
+ def blockers(self) -> tuple[str, ...]:
94
+ return tuple(str(item["id"]) for item in self.data.get("blockers", ()))
95
+
96
+ @property
97
+ def full_evidence_satisfied(self) -> bool:
98
+ return self.structural_ok and not self.blockers
99
+
100
+ @property
101
+ def result_status(self) -> str:
102
+ if not self.structural_ok:
103
+ return "STRUCTURAL_BLOCK"
104
+ if self.full_evidence_satisfied:
105
+ return "SATISFIED_FOR_CURRENT_KIT_REF"
106
+ return "PARTIAL_BLOCKED_FOR_DP2"
107
+
108
+ def as_dict(self) -> dict[str, Any]:
109
+ return {
110
+ "schema_version": 1,
111
+ "kind": "dpa_probe_002_lifecycle_readiness",
112
+ "result_status": self.result_status,
113
+ "validation_ref": self.validation_ref,
114
+ "readiness_record": self.readiness_record,
115
+ "structural_ok": self.structural_ok,
116
+ "full_evidence_satisfied": self.full_evidence_satisfied,
117
+ "finding_count": len(self.findings),
118
+ "blocker_count": len(self.blockers),
119
+ "findings": [finding.as_dict() for finding in self.findings],
120
+ **self.data,
121
+ }
122
+
123
+
124
+ def evaluate_probe_002_lifecycle_readiness(
125
+ root: Path | str = ".",
126
+ *,
127
+ readiness_path: Path | str = DEFAULT_READINESS_PATH,
128
+ validation_ref: str | None = None,
129
+ ) -> Probe002ReadinessResult:
130
+ base = Path(root).resolve()
131
+ record_path = _resolve_under_root(base, readiness_path)
132
+ findings: list[Probe002Finding] = []
133
+
134
+ source_surfaces = _path_records(base, PROBE_002_SOURCE_PATHS)
135
+ test_surfaces = _glob_records(base, PROBE_002_TEST_GLOBS)
136
+ for item in (*source_surfaces, *test_surfaces):
137
+ if not item["present"]:
138
+ findings.append(
139
+ Probe002Finding(
140
+ code="required-surface-missing",
141
+ message=f"Required PROBE-002 surface is missing: {item['path']}",
142
+ path=item["path"],
143
+ )
144
+ )
145
+
146
+ for required_path in (
147
+ SELECTED_WRITER_PLAN_PATH,
148
+ PROBE_MANUAL_PATH,
149
+ PROBE_EXECUTION_PACKAGE_PATH,
150
+ ):
151
+ if not (base / required_path).exists():
152
+ findings.append(
153
+ Probe002Finding(
154
+ code="required-dpa-control-surface-missing",
155
+ message=f"Required DPA control surface is missing: {required_path}",
156
+ path=required_path,
157
+ )
158
+ )
159
+
160
+ readiness_data = _load_readiness_data(record_path, base, findings)
161
+ selected_writers = _selected_writer_records(readiness_data)
162
+ readiness_status = _probe_family_status(readiness_data, record_path, base, findings)
163
+ dp2_entry_status = _probe_002_dp2_entry_status(readiness_data, record_path, base, findings)
164
+ missing_writers = sorted(set(EXPECTED_WRITERS) - {item["id"] for item in selected_writers})
165
+ for writer in missing_writers:
166
+ findings.append(
167
+ Probe002Finding(
168
+ code="selected-writer-missing",
169
+ message=f"Selected-writer disposition is missing from readiness record: {writer}",
170
+ path=_display_path(record_path, base),
171
+ )
172
+ )
173
+
174
+ blockers = _blockers_for(selected_writers, readiness_status, dp2_entry_status)
175
+ groups = _group_records(blockers)
176
+ validation = validation_ref or _git_head(base)
177
+ data = {
178
+ "source_surfaces": source_surfaces,
179
+ "test_surfaces": test_surfaces,
180
+ "control_surfaces": [
181
+ {"path": SELECTED_WRITER_PLAN_PATH, "present": (base / SELECTED_WRITER_PLAN_PATH).exists()},
182
+ {"path": PROBE_MANUAL_PATH, "present": (base / PROBE_MANUAL_PATH).exists()},
183
+ {
184
+ "path": PROBE_EXECUTION_PACKAGE_PATH,
185
+ "present": (base / PROBE_EXECUTION_PACKAGE_PATH).exists(),
186
+ },
187
+ ],
188
+ "required_groups": groups,
189
+ "readiness_probe_family_status": readiness_status,
190
+ "readiness_dp2_entry_status": dp2_entry_status,
191
+ "selected_writers": selected_writers,
192
+ "blockers": blockers,
193
+ "claims": {
194
+ "probe_execution_claimed": False,
195
+ "dp2_authorized": False,
196
+ "production_mutation_performed": False,
197
+ "generated_outputs_manually_patched": False,
198
+ },
199
+ }
200
+ return Probe002ReadinessResult(
201
+ root=base.as_posix(),
202
+ validation_ref=validation,
203
+ readiness_record=_display_path(record_path, base),
204
+ data=data,
205
+ findings=tuple(findings),
206
+ )
207
+
208
+
209
+ def render_probe_002_lifecycle_readiness(result: Probe002ReadinessResult) -> str:
210
+ payload = result.as_dict()
211
+ lines = [
212
+ "DPA_PROBE_002_READINESS",
213
+ f"STATUS={payload['result_status']}",
214
+ f"VALIDATION_REF={payload['validation_ref']}",
215
+ f"FULL_EVIDENCE_SATISFIED={str(payload['full_evidence_satisfied']).lower()}",
216
+ f"FINDINGS={payload['finding_count']}",
217
+ f"BLOCKERS={payload['blocker_count']}",
218
+ ]
219
+ for blocker in payload["blockers"]:
220
+ lines.append(f"BLOCKER={blocker['id']}|{blocker['message']}")
221
+ if payload["finding_count"]:
222
+ for finding in payload["findings"]:
223
+ lines.append(f"FINDING={finding['code']}|path={finding['path']}|{finding['message']}")
224
+ return "\n".join(lines) + "\n"
225
+
226
+
227
+ def write_probe_002_readiness_json(
228
+ result: Probe002ReadinessResult,
229
+ root: Path | str,
230
+ output: Path | str,
231
+ *,
232
+ execute: bool,
233
+ ) -> dict[str, Any]:
234
+ base = Path(root).resolve()
235
+ output_path = _resolve_under_root(base, output)
236
+ relative = output_path.relative_to(base)
237
+ evidence_root = _evidence_output_root(base)
238
+ if evidence_root not in (output_path, *output_path.parents):
239
+ return {
240
+ "result_status": "BLOCK",
241
+ "reason": "output_outside_dpa_probe_evidence_root",
242
+ "output_path": relative.as_posix(),
243
+ "written": False,
244
+ }
245
+ changed = True
246
+ rendered = json.dumps(result.as_dict(), indent=2, sort_keys=True) + "\n"
247
+ if output_path.exists():
248
+ changed = output_path.read_text(encoding="utf-8") != rendered
249
+ if execute:
250
+ output_path.parent.mkdir(parents=True, exist_ok=True)
251
+ output_path.write_text(rendered, encoding="utf-8")
252
+ return {
253
+ "result_status": "PASS",
254
+ "output_path": relative.as_posix(),
255
+ "changed": changed,
256
+ "written": bool(execute),
257
+ }
258
+
259
+
260
+ def _resolve_under_root(root: Path, path: Path | str) -> Path:
261
+ candidate = Path(path)
262
+ if candidate.is_absolute():
263
+ return candidate
264
+ return root / candidate
265
+
266
+
267
+ def _display_path(path: Path, root: Path) -> str:
268
+ try:
269
+ return path.relative_to(root).as_posix()
270
+ except ValueError:
271
+ return path.as_posix()
272
+
273
+
274
+ def _path_records(root: Path, paths: tuple[str, ...]) -> list[dict[str, Any]]:
275
+ return [{"path": item, "present": (root / item).exists()} for item in paths]
276
+
277
+
278
+ def _glob_records(root: Path, patterns: tuple[str, ...]) -> list[dict[str, Any]]:
279
+ records: list[dict[str, Any]] = []
280
+ for pattern in patterns:
281
+ matches = sorted(path.relative_to(root).as_posix() for path in root.glob(pattern))
282
+ records.append({"path": pattern, "present": bool(matches), "matches": matches})
283
+ return records
284
+
285
+
286
+ def _evidence_output_root(root: Path) -> Path:
287
+ ws = load_workspace(root, suppress_legacy_profile_warning=True)
288
+ return ws.architecture_file(Path(*EVIDENCE_OUTPUT_ROOT_PARTS)).resolve()
289
+
290
+
291
+ def _load_readiness_data(
292
+ path: Path,
293
+ root: Path,
294
+ findings: list[Probe002Finding],
295
+ ) -> dict[str, Any]:
296
+ if not path.exists():
297
+ findings.append(
298
+ Probe002Finding(
299
+ code="readiness-record-missing",
300
+ message="DPA readiness record is missing",
301
+ path=_display_path(path, root),
302
+ )
303
+ )
304
+ return {}
305
+ try:
306
+ data = json.loads(path.read_text(encoding="utf-8"))
307
+ except json.JSONDecodeError as exc:
308
+ findings.append(
309
+ Probe002Finding(
310
+ code="readiness-record-json-invalid",
311
+ message=f"DPA readiness record is not valid JSON: {exc}",
312
+ path=_display_path(path, root),
313
+ )
314
+ )
315
+ return {}
316
+ if not isinstance(data, dict):
317
+ findings.append(
318
+ Probe002Finding(
319
+ code="readiness-record-not-object",
320
+ message="DPA readiness record must contain a JSON object",
321
+ path=_display_path(path, root),
322
+ )
323
+ )
324
+ return {}
325
+ return data
326
+
327
+
328
+ def _selected_writer_records(readiness_data: dict[str, Any]) -> list[dict[str, str]]:
329
+ raw = readiness_data.get("selected_writer_status")
330
+ if not isinstance(raw, dict):
331
+ return []
332
+ records = []
333
+ for writer_id in EXPECTED_WRITERS:
334
+ status = raw.get(writer_id)
335
+ if isinstance(status, str):
336
+ records.append(
337
+ {
338
+ "id": writer_id,
339
+ "status": status,
340
+ "fixture_status": _fixture_status_for(writer_id, status),
341
+ }
342
+ )
343
+ return records
344
+
345
+
346
+ def _fixture_status_for(writer_id: str, status: str) -> str:
347
+ if writer_id == "WRT-CH-006":
348
+ return "DEFERRED_TO_PROBE_004_GENERATED_OUTPUT_CONTRACT"
349
+ if status == "EXTERNAL_HABITABILITY_ONLY":
350
+ return "OUT_OF_SCOPE_FOR_FIRST_SELF_HOSTING_TARGET"
351
+ if status in {"FIXTURE_EVIDENCE_RECORDED_FOR_FIRST_DP2_TARGET", "AUTHORIZED_FOR_DP2_TARGET"}:
352
+ return "SATISFIED_FOR_CURRENT_DP2_SCOPE"
353
+ if status == "NEEDS_MAINTAINER_DECISION":
354
+ return "BLOCKED_REQUIRES_MAINTAINER_DECISION"
355
+ if status in {"SELECTED_FOR_FIXTURE", "OBSERVED_ADMIN_REFRESH_REQUIRES_DISPOSABLE_FIXTURE"}:
356
+ return "BLOCKED_REQUIRES_CURRENT_DISPOSABLE_FIXTURE_EXECUTION"
357
+ return "BLOCKED_REQUIRES_REVALIDATION"
358
+
359
+
360
+ def _probe_family_status(
361
+ readiness_data: dict[str, Any],
362
+ record_path: Path,
363
+ root: Path,
364
+ findings: list[Probe002Finding],
365
+ ) -> str:
366
+ raw = readiness_data.get("probe_family_status")
367
+ if not isinstance(raw, dict):
368
+ findings.append(
369
+ Probe002Finding(
370
+ code="probe-family-status-missing",
371
+ message="readiness record must contain probe_family_status mapping",
372
+ path=_display_path(record_path, root),
373
+ )
374
+ )
375
+ return "MISSING"
376
+ status = raw.get("PROBE-002")
377
+ if not isinstance(status, str):
378
+ findings.append(
379
+ Probe002Finding(
380
+ code="probe-002-status-missing",
381
+ message="readiness record must contain probe_family_status.PROBE-002",
382
+ path=_display_path(record_path, root),
383
+ )
384
+ )
385
+ return "MISSING"
386
+ return status
387
+
388
+
389
+ def _probe_002_dp2_entry_status(
390
+ readiness_data: dict[str, Any],
391
+ record_path: Path,
392
+ root: Path,
393
+ findings: list[Probe002Finding],
394
+ ) -> str:
395
+ raw = readiness_data.get("dp2_entry_status")
396
+ if not isinstance(raw, dict):
397
+ findings.append(
398
+ Probe002Finding(
399
+ code="dp2-entry-status-missing",
400
+ message="readiness record must contain dp2_entry_status mapping",
401
+ path=_display_path(record_path, root),
402
+ )
403
+ )
404
+ return "MISSING"
405
+ status = raw.get("probe_002_full_evidence")
406
+ if not isinstance(status, str):
407
+ findings.append(
408
+ Probe002Finding(
409
+ code="probe-002-dp2-entry-status-missing",
410
+ message="readiness record must contain dp2_entry_status.probe_002_full_evidence",
411
+ path=_display_path(record_path, root),
412
+ )
413
+ )
414
+ return "MISSING"
415
+ return status
416
+
417
+
418
+ def _blockers_for(
419
+ selected_writers: list[dict[str, str]],
420
+ readiness_status: str,
421
+ dp2_entry_status: str,
422
+ ) -> list[dict[str, str]]:
423
+ blockers: list[dict[str, str]] = []
424
+ if readiness_status != "SATISFIED_FOR_CURRENT_KIT_REF":
425
+ blockers.append(
426
+ {
427
+ "id": "probe-002-family-not-satisfied",
428
+ "message": f"Readiness record reports PROBE-002 as {readiness_status}.",
429
+ }
430
+ )
431
+ if dp2_entry_status != "SATISFIED_FOR_CURRENT_KIT_REF":
432
+ blockers.append(
433
+ {
434
+ "id": "probe-002-dp2-entry-blocked",
435
+ "message": f"DP2 entry record reports probe_002_full_evidence as {dp2_entry_status}.",
436
+ }
437
+ )
438
+ selected_fixture_ids = [
439
+ item["id"]
440
+ for item in selected_writers
441
+ if item["fixture_status"] == "BLOCKED_REQUIRES_CURRENT_DISPOSABLE_FIXTURE_EXECUTION"
442
+ ]
443
+ if selected_fixture_ids:
444
+ blockers.append(
445
+ {
446
+ "id": "selected-writer-current-fixtures",
447
+ "message": "Selected writers still require current disposable fixture execution: "
448
+ + ", ".join(selected_fixture_ids),
449
+ }
450
+ )
451
+ maintainer_decision_ids = [
452
+ item["id"]
453
+ for item in selected_writers
454
+ if item["fixture_status"] == "BLOCKED_REQUIRES_MAINTAINER_DECISION"
455
+ ]
456
+ if maintainer_decision_ids:
457
+ blockers.append(
458
+ {
459
+ "id": "selected-writer-maintainer-decisions",
460
+ "message": "Write-capable paths still need Maintainer select/defer decisions: "
461
+ + ", ".join(maintainer_decision_ids),
462
+ }
463
+ )
464
+ revalidation_ids = [
465
+ item["id"]
466
+ for item in selected_writers
467
+ if item["fixture_status"] == "BLOCKED_REQUIRES_REVALIDATION"
468
+ ]
469
+ if revalidation_ids:
470
+ blockers.append(
471
+ {
472
+ "id": "selected-writer-revalidation",
473
+ "message": "Selected writers have statuses that require explicit PROBE-002 revalidation: "
474
+ + ", ".join(revalidation_ids),
475
+ }
476
+ )
477
+ return blockers
478
+
479
+
480
+ def _group_records(blockers: list[dict[str, str]]) -> list[dict[str, str]]:
481
+ has_writer_blocker = any(item["id"].startswith("selected-writer") for item in blockers)
482
+ records: list[dict[str, str]] = []
483
+ if not blockers:
484
+ return [
485
+ {"id": group_id, "label": label, "status": "SATISFIED_FOR_CURRENT_KIT_REF"}
486
+ for group_id, label, _category in REQUIRED_GROUPS
487
+ ]
488
+ for group_id, label, category in REQUIRED_GROUPS:
489
+ if category == "selected-writers" and has_writer_blocker:
490
+ status = "BLOCKED_REQUIRES_FIXTURE_OR_ADJUDICATION"
491
+ else:
492
+ status = "PREFLIGHT_SURFACE_PRESENT_NOT_EXECUTED"
493
+ records.append({"id": group_id, "label": label, "status": status})
494
+ return records
495
+
496
+
497
+ def _git_head(root: Path) -> str:
498
+ try:
499
+ completed = subprocess.run(
500
+ ["git", "rev-parse", "HEAD"],
501
+ cwd=root,
502
+ check=True,
503
+ text=True,
504
+ stdout=subprocess.PIPE,
505
+ stderr=subprocess.PIPE,
506
+ )
507
+ except (OSError, subprocess.CalledProcessError):
508
+ return "UNKNOWN"
509
+ return completed.stdout.strip() or "UNKNOWN"