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,417 @@
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
+ PROBE_003_SOURCE_PATHS = (
13
+ "src/agentic_project_kit/transfer_repo_actions.py",
14
+ "src/agentic_project_kit/gatekeeper_core.py",
15
+ "src/agentic_project_kit/local_feature_gate.py",
16
+ "src/agentic_project_kit/workspace_lock.py",
17
+ "src/agentic_project_kit/handoff_freshness.py",
18
+ )
19
+ PROBE_003_TEST_GLOBS = (
20
+ "tests/test_transfer_repo_actions.py",
21
+ "tests/test_gatekeeper_core.py",
22
+ "tests/test_local_feature_gate_contract.py",
23
+ "tests/test_handoff_freshness.py",
24
+ )
25
+ PROBE_003_CONTROL_SURFACES = (
26
+ "docs/architecture/dpa/probes/DP1_PROBE_MANUALS_20260727.md",
27
+ "docs/architecture/dpa/probes/DP1_PROBE_EXECUTION_PACKAGE_DRAFT_20260727.md",
28
+ )
29
+ EVIDENCE_OUTPUT_ROOT_PARTS = ("evidence", "dpa", "probes")
30
+
31
+ REQUIRED_GROUPS = (
32
+ ("P3-G01", "branch/worktree/ref identity capture", "fixture-not-executed"),
33
+ ("P3-G02", "branch switch stale-plan rejection", "mutable-fixture"),
34
+ ("P3-G03", "rebase/reset stale-plan rejection", "mutable-fixture"),
35
+ ("P3-G04", "pull-request head/base identity comparison", "pr-fixture"),
36
+ ("P3-G05", "required-check evidence bound to the exact PR head", "pr-fixture"),
37
+ (
38
+ "P3-G06",
39
+ "merge eligibility blocked by stale source, target, contract, renderer, ownership, "
40
+ "partition, gate-set or base-context dependency",
41
+ "integration-fixture",
42
+ ),
43
+ (
44
+ "P3-G07",
45
+ "integration-time revalidation before acceptance-bearing workflow completion",
46
+ "integration-fixture",
47
+ ),
48
+ ("P3-G08", "clean textual Git merge rejected as projection-freshness evidence", "fixture-not-executed"),
49
+ (
50
+ "P3-G09",
51
+ "deterministic regeneration from current validation ref after stale-plan detection",
52
+ "mutable-fixture",
53
+ ),
54
+ (
55
+ "P3-G10",
56
+ "competing projection refresh conflict across shared targets or registered partitions",
57
+ "integration-fixture",
58
+ ),
59
+ (
60
+ "P3-G11",
61
+ "post-integration refresh bound to the accepted integration ref",
62
+ "integration-fixture",
63
+ ),
64
+ (
65
+ "P3-G12",
66
+ "acceptance-bearing evidence publication blocked while workflow identity is stale",
67
+ "integration-fixture",
68
+ ),
69
+ )
70
+
71
+
72
+ @dataclass(frozen=True)
73
+ class Probe003Finding:
74
+ code: str
75
+ message: str
76
+ path: str
77
+
78
+ def as_dict(self) -> dict[str, str]:
79
+ return {"code": self.code, "message": self.message, "path": self.path}
80
+
81
+
82
+ @dataclass(frozen=True)
83
+ class Probe003ReadinessResult:
84
+ root: str
85
+ validation_ref: str
86
+ readiness_record: str
87
+ data: dict[str, Any]
88
+ findings: tuple[Probe003Finding, ...]
89
+
90
+ @property
91
+ def structural_ok(self) -> bool:
92
+ return not self.findings
93
+
94
+ @property
95
+ def blockers(self) -> tuple[str, ...]:
96
+ return tuple(str(item["id"]) for item in self.data.get("blockers", ()))
97
+
98
+ @property
99
+ def full_evidence_satisfied(self) -> bool:
100
+ return self.structural_ok and not self.blockers
101
+
102
+ @property
103
+ def result_status(self) -> str:
104
+ if not self.structural_ok:
105
+ return "STRUCTURAL_BLOCK"
106
+ if self.full_evidence_satisfied:
107
+ return "SATISFIED_FOR_CURRENT_KIT_REF"
108
+ return "PARTIAL_BLOCKED_FOR_DP2"
109
+
110
+ def as_dict(self) -> dict[str, Any]:
111
+ return {
112
+ "schema_version": 1,
113
+ "kind": "dpa_probe_003_workflow_serialization_readiness",
114
+ "result_status": self.result_status,
115
+ "validation_ref": self.validation_ref,
116
+ "readiness_record": self.readiness_record,
117
+ "structural_ok": self.structural_ok,
118
+ "full_evidence_satisfied": self.full_evidence_satisfied,
119
+ "finding_count": len(self.findings),
120
+ "blocker_count": len(self.blockers),
121
+ "findings": [finding.as_dict() for finding in self.findings],
122
+ **self.data,
123
+ }
124
+
125
+
126
+ def evaluate_probe_003_workflow_readiness(
127
+ root: Path | str = ".",
128
+ *,
129
+ readiness_path: Path | str = DEFAULT_READINESS_PATH,
130
+ validation_ref: str | None = None,
131
+ ) -> Probe003ReadinessResult:
132
+ base = Path(root).resolve()
133
+ record_path = _resolve_under_root(base, readiness_path)
134
+ findings: list[Probe003Finding] = []
135
+
136
+ source_surfaces = _path_records(base, PROBE_003_SOURCE_PATHS)
137
+ test_surfaces = _glob_records(base, PROBE_003_TEST_GLOBS)
138
+ control_surfaces = _path_records(base, PROBE_003_CONTROL_SURFACES)
139
+ for item in (*source_surfaces, *test_surfaces, *control_surfaces):
140
+ if not item["present"]:
141
+ findings.append(
142
+ Probe003Finding(
143
+ code="required-surface-missing",
144
+ message=f"Required PROBE-003 surface is missing: {item['path']}",
145
+ path=item["path"],
146
+ )
147
+ )
148
+
149
+ readiness_data = _load_readiness_data(record_path, base, findings)
150
+ readiness_status = _probe_family_status(readiness_data, record_path, base, findings)
151
+ dp2_entry_status = _probe_003_dp2_entry_status(readiness_data, record_path, base, findings)
152
+ blockers = _blockers_for(readiness_status, dp2_entry_status)
153
+ validation = validation_ref or _git_head(base)
154
+ data = {
155
+ "source_surfaces": source_surfaces,
156
+ "test_surfaces": test_surfaces,
157
+ "control_surfaces": control_surfaces,
158
+ "required_groups": _group_records(blockers),
159
+ "readiness_probe_family_status": readiness_status,
160
+ "readiness_dp2_entry_status": dp2_entry_status,
161
+ "blockers": blockers,
162
+ "claims": {
163
+ "probe_execution_claimed": False,
164
+ "dp2_authorized": False,
165
+ "production_mutation_performed": False,
166
+ "workflow_queue_conformance_claimed": False,
167
+ "generated_outputs_manually_patched": False,
168
+ },
169
+ }
170
+ return Probe003ReadinessResult(
171
+ root=base.as_posix(),
172
+ validation_ref=validation,
173
+ readiness_record=_display_path(record_path, base),
174
+ data=data,
175
+ findings=tuple(findings),
176
+ )
177
+
178
+
179
+ def render_probe_003_workflow_readiness(result: Probe003ReadinessResult) -> str:
180
+ payload = result.as_dict()
181
+ lines = [
182
+ "DPA_PROBE_003_WORKFLOW_READINESS",
183
+ f"STATUS={payload['result_status']}",
184
+ f"VALIDATION_REF={payload['validation_ref']}",
185
+ f"FULL_EVIDENCE_SATISFIED={str(payload['full_evidence_satisfied']).lower()}",
186
+ f"FINDINGS={payload['finding_count']}",
187
+ f"BLOCKERS={payload['blocker_count']}",
188
+ ]
189
+ for blocker in payload["blockers"]:
190
+ lines.append(f"BLOCKER={blocker['id']}|{blocker['message']}")
191
+ if payload["finding_count"]:
192
+ for finding in payload["findings"]:
193
+ lines.append(f"FINDING={finding['code']}|path={finding['path']}|{finding['message']}")
194
+ return "\n".join(lines) + "\n"
195
+
196
+
197
+ def write_probe_003_readiness_json(
198
+ result: Probe003ReadinessResult,
199
+ root: Path | str,
200
+ output: Path | str,
201
+ *,
202
+ execute: bool,
203
+ ) -> dict[str, Any]:
204
+ base = Path(root).resolve()
205
+ output_path = _resolve_under_root(base, output)
206
+ relative = output_path.relative_to(base)
207
+ evidence_root = _evidence_output_root(base)
208
+ if evidence_root not in (output_path, *output_path.parents):
209
+ return {
210
+ "result_status": "BLOCK",
211
+ "reason": "output_outside_dpa_probe_evidence_root",
212
+ "output_path": relative.as_posix(),
213
+ "written": False,
214
+ }
215
+ changed = True
216
+ rendered = json.dumps(result.as_dict(), indent=2, sort_keys=True) + "\n"
217
+ if output_path.exists():
218
+ changed = output_path.read_text(encoding="utf-8") != rendered
219
+ if execute:
220
+ output_path.parent.mkdir(parents=True, exist_ok=True)
221
+ output_path.write_text(rendered, encoding="utf-8")
222
+ return {
223
+ "result_status": "PASS",
224
+ "output_path": relative.as_posix(),
225
+ "changed": changed,
226
+ "written": bool(execute),
227
+ }
228
+
229
+
230
+ def _resolve_under_root(root: Path, path: Path | str) -> Path:
231
+ candidate = Path(path)
232
+ if candidate.is_absolute():
233
+ return candidate
234
+ return root / candidate
235
+
236
+
237
+ def _display_path(path: Path, root: Path) -> str:
238
+ try:
239
+ return path.relative_to(root).as_posix()
240
+ except ValueError:
241
+ return path.as_posix()
242
+
243
+
244
+ def _path_records(root: Path, paths: tuple[str, ...]) -> list[dict[str, Any]]:
245
+ return [{"path": item, "present": (root / item).exists()} for item in paths]
246
+
247
+
248
+ def _glob_records(root: Path, patterns: tuple[str, ...]) -> list[dict[str, Any]]:
249
+ records: list[dict[str, Any]] = []
250
+ for pattern in patterns:
251
+ matches = sorted(path.relative_to(root).as_posix() for path in root.glob(pattern))
252
+ records.append({"path": pattern, "present": bool(matches), "matches": matches})
253
+ return records
254
+
255
+
256
+ def _evidence_output_root(root: Path) -> Path:
257
+ ws = load_workspace(root, suppress_legacy_profile_warning=True)
258
+ return ws.architecture_file(Path(*EVIDENCE_OUTPUT_ROOT_PARTS)).resolve()
259
+
260
+
261
+ def _load_readiness_data(
262
+ path: Path,
263
+ root: Path,
264
+ findings: list[Probe003Finding],
265
+ ) -> dict[str, Any]:
266
+ if not path.exists():
267
+ findings.append(
268
+ Probe003Finding(
269
+ code="readiness-record-missing",
270
+ message="DPA readiness record is missing",
271
+ path=_display_path(path, root),
272
+ )
273
+ )
274
+ return {}
275
+ try:
276
+ data = json.loads(path.read_text(encoding="utf-8"))
277
+ except json.JSONDecodeError as exc:
278
+ findings.append(
279
+ Probe003Finding(
280
+ code="readiness-record-json-invalid",
281
+ message=f"DPA readiness record is not valid JSON: {exc}",
282
+ path=_display_path(path, root),
283
+ )
284
+ )
285
+ return {}
286
+ if not isinstance(data, dict):
287
+ findings.append(
288
+ Probe003Finding(
289
+ code="readiness-record-not-object",
290
+ message="DPA readiness record must contain a JSON object",
291
+ path=_display_path(path, root),
292
+ )
293
+ )
294
+ return {}
295
+ return data
296
+
297
+
298
+ def _probe_family_status(
299
+ readiness_data: dict[str, Any],
300
+ record_path: Path,
301
+ root: Path,
302
+ findings: list[Probe003Finding],
303
+ ) -> str:
304
+ raw = readiness_data.get("probe_family_status")
305
+ if not isinstance(raw, dict):
306
+ findings.append(
307
+ Probe003Finding(
308
+ code="probe-family-status-missing",
309
+ message="readiness record must contain probe_family_status mapping",
310
+ path=_display_path(record_path, root),
311
+ )
312
+ )
313
+ return "MISSING"
314
+ status = raw.get("PROBE-003")
315
+ if not isinstance(status, str):
316
+ findings.append(
317
+ Probe003Finding(
318
+ code="probe-003-status-missing",
319
+ message="readiness record must contain probe_family_status.PROBE-003",
320
+ path=_display_path(record_path, root),
321
+ )
322
+ )
323
+ return "MISSING"
324
+ return status
325
+
326
+
327
+ def _probe_003_dp2_entry_status(
328
+ readiness_data: dict[str, Any],
329
+ record_path: Path,
330
+ root: Path,
331
+ findings: list[Probe003Finding],
332
+ ) -> str:
333
+ raw = readiness_data.get("dp2_entry_status")
334
+ if not isinstance(raw, dict):
335
+ findings.append(
336
+ Probe003Finding(
337
+ code="dp2-entry-status-missing",
338
+ message="readiness record must contain dp2_entry_status mapping",
339
+ path=_display_path(record_path, root),
340
+ )
341
+ )
342
+ return "MISSING"
343
+ status = raw.get("probe_003_full_evidence")
344
+ if not isinstance(status, str):
345
+ findings.append(
346
+ Probe003Finding(
347
+ code="probe-003-dp2-entry-status-missing",
348
+ message="readiness record must contain dp2_entry_status.probe_003_full_evidence",
349
+ path=_display_path(record_path, root),
350
+ )
351
+ )
352
+ return "MISSING"
353
+ return status
354
+
355
+
356
+ def _blockers_for(readiness_status: str, dp2_entry_status: str) -> list[dict[str, str]]:
357
+ blockers: list[dict[str, str]] = []
358
+ if readiness_status != "SATISFIED_FOR_CURRENT_KIT_REF":
359
+ blockers.append(
360
+ {
361
+ "id": "probe-003-family-not-satisfied",
362
+ "message": f"Readiness record reports PROBE-003 as {readiness_status}.",
363
+ }
364
+ )
365
+ if dp2_entry_status != "SATISFIED_FOR_CURRENT_KIT_REF":
366
+ blockers.append(
367
+ {
368
+ "id": "probe-003-dp2-entry-blocked",
369
+ "message": f"DP2 entry record reports probe_003_full_evidence as {dp2_entry_status}.",
370
+ }
371
+ )
372
+ if not blockers:
373
+ return blockers
374
+ blockers.append(
375
+ {
376
+ "id": "probe-003-executable-fixture-run",
377
+ "message": "PROBE-003 has not been executed as a full exact-ref workflow serialization fixture run.",
378
+ }
379
+ )
380
+ blockers.append(
381
+ {
382
+ "id": "probe-003-mutable-workflow-authorization",
383
+ "message": "Branch, PR, push or integration fixtures require Maintainer authorization and rollback instructions.",
384
+ }
385
+ )
386
+ return blockers
387
+
388
+
389
+ def _group_records(blockers: list[dict[str, str]]) -> list[dict[str, str]]:
390
+ records: list[dict[str, str]] = []
391
+ if not blockers:
392
+ return [
393
+ {"id": group_id, "label": label, "status": "SATISFIED_FOR_CURRENT_KIT_REF"}
394
+ for group_id, label, _category in REQUIRED_GROUPS
395
+ ]
396
+ for group_id, label, category in REQUIRED_GROUPS:
397
+ if category in {"mutable-fixture", "pr-fixture", "integration-fixture"}:
398
+ status = "BLOCKED_REQUIRES_AUTHORIZED_DISPOSABLE_WORKFLOW_FIXTURE"
399
+ else:
400
+ status = "PREFLIGHT_SURFACE_PRESENT_NOT_EXECUTED"
401
+ records.append({"id": group_id, "label": label, "status": status})
402
+ return records
403
+
404
+
405
+ def _git_head(root: Path) -> str:
406
+ try:
407
+ completed = subprocess.run(
408
+ ["git", "rev-parse", "HEAD"],
409
+ cwd=root,
410
+ check=True,
411
+ text=True,
412
+ stdout=subprocess.PIPE,
413
+ stderr=subprocess.PIPE,
414
+ )
415
+ except (OSError, subprocess.CalledProcessError):
416
+ return "UNKNOWN"
417
+ return completed.stdout.strip() or "UNKNOWN"