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,505 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from agentic_project_kit.workspace import KitConfig, load_workspace
9
+
10
+ _DEFAULT_CONFIG = KitConfig()
11
+ DEFAULT_READINESS_PATH = (
12
+ Path(_DEFAULT_CONFIG.architecture_root)
13
+ / "evidence"
14
+ / "dpa"
15
+ / "assessment"
16
+ / "dp1-assessment-readiness-20260728.json"
17
+ )
18
+ EXPECTED_KIND = "dpa_dp1_assessment_readiness"
19
+ EXPECTED_SCHEMA_VERSION = 1
20
+ BLOCKED_STATUS = "DP2_BLOCKED"
21
+ AUTHORIZED_STATUS = "DP2_AUTHORIZED"
22
+ REQUIRED_PROBE_FAMILIES = ("PROBE-001", "PROBE-002", "RENDERER", "PROBE-003", "PROBE-004")
23
+ REQUIRED_FALSE_CLAIMS = (
24
+ "full_probe_pass_claimed",
25
+ "runtime_behavior_changed",
26
+ "production_mutation_performed",
27
+ "kit_conformance_claimed",
28
+ "generated_outputs_manually_patched",
29
+ )
30
+ PROGRESS_MODEL = "dpa-readiness-v1"
31
+ IMPLEMENTATION_SCOPE = "DP2_SELECTED_SELF_HOSTING_CURRENT_HANDOFF_SCOPE"
32
+ KIT_WIDE_DPA_STATUS = "NOT_ASSESSED_BY_DP2_READINESS"
33
+ PROGRESS_WEIGHTS = (
34
+ ("architecture_staging", "Architecture package staged in Kit", 25),
35
+ ("dp1_evidence_staged", "DP1 evidence inputs staged", 10),
36
+ ("dp1_readiness_recorded", "DP1 Assessment readiness recorded", 5),
37
+ ("probe_001_full_evidence", "PROBE-001 full evidence", 8),
38
+ ("probe_002_full_evidence", "PROBE-002 full evidence", 8),
39
+ ("renderer_full_evidence", "Renderer Probe full evidence", 8),
40
+ ("probe_003_full_evidence", "PROBE-003 full evidence", 8),
41
+ ("probe_004_full_evidence", "PROBE-004 full evidence", 8),
42
+ ("maintainer_assessment", "Maintainer Assessment recorded", 7),
43
+ ("first_dp2_target_scope", "First DP2 target and writer scope selected", 4),
44
+ ("rollback_cleanup_proven", "Rollback and cleanup proven", 4),
45
+ ("maintainer_authorization", "Maintainer DP2 authorization recorded", 5),
46
+ )
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class DpaReadinessFinding:
51
+ code: str
52
+ message: str
53
+ path: str = DEFAULT_READINESS_PATH.as_posix()
54
+
55
+ def as_dict(self) -> dict[str, str]:
56
+ return {"code": self.code, "message": self.message, "path": self.path}
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class DpaProgressItem:
61
+ id: str
62
+ label: str
63
+ weight: int
64
+ earned: int
65
+ status: str
66
+
67
+ def as_dict(self) -> dict[str, object]:
68
+ return {
69
+ "id": self.id,
70
+ "label": self.label,
71
+ "weight": self.weight,
72
+ "earned": self.earned,
73
+ "status": self.status,
74
+ }
75
+
76
+
77
+ @dataclass(frozen=True)
78
+ class DpaReadinessResult:
79
+ path: str
80
+ data: dict[str, Any]
81
+ findings: tuple[DpaReadinessFinding, ...]
82
+ blockers: tuple[str, ...]
83
+
84
+ @property
85
+ def ok(self) -> bool:
86
+ return not self.findings
87
+
88
+ @property
89
+ def dp2_ready(self) -> bool:
90
+ return self.ok and self.data.get("status") == AUTHORIZED_STATUS and not self.blockers
91
+
92
+ @property
93
+ def status(self) -> str:
94
+ if not self.ok:
95
+ return "FAIL"
96
+ if self.dp2_ready:
97
+ return AUTHORIZED_STATUS
98
+ return BLOCKED_STATUS
99
+
100
+ @property
101
+ def progress_items(self) -> tuple[DpaProgressItem, ...]:
102
+ if not self.ok:
103
+ return ()
104
+ return tuple(_progress_items(self.data))
105
+
106
+ @property
107
+ def implementation_percent(self) -> int:
108
+ return sum(item.earned for item in self.progress_items)
109
+
110
+ def as_dict(self) -> dict[str, object]:
111
+ return {
112
+ "schema_version": 1,
113
+ "kind": "dpa_readiness_check",
114
+ "status": self.status,
115
+ "implementation_percent": self.implementation_percent,
116
+ "implementation_scope": IMPLEMENTATION_SCOPE,
117
+ "dp2_implementation_percent": self.implementation_percent,
118
+ "kit_wide_dpa_status": KIT_WIDE_DPA_STATUS,
119
+ "kit_wide_dpa_conformance_claimed": False,
120
+ "progress_model": PROGRESS_MODEL,
121
+ "path": self.path,
122
+ "finding_count": len(self.findings),
123
+ "blocker_count": len(self.blockers),
124
+ "findings": [finding.as_dict() for finding in self.findings],
125
+ "blockers": list(self.blockers),
126
+ "progress": [item.as_dict() for item in self.progress_items],
127
+ "claims": self.data.get("claims", {}),
128
+ }
129
+
130
+
131
+ def evaluate_dpa_readiness(
132
+ root: Path | str = ".",
133
+ *,
134
+ readiness_path: Path | str = DEFAULT_READINESS_PATH,
135
+ ) -> DpaReadinessResult:
136
+ base = Path(root)
137
+ configured_path = Path(readiness_path)
138
+ path = configured_path if configured_path.is_absolute() else base / configured_path
139
+ display_path = _display_path(path, base)
140
+ findings: list[DpaReadinessFinding] = []
141
+
142
+ if not path.exists():
143
+ return DpaReadinessResult(
144
+ path=display_path,
145
+ data={},
146
+ findings=(
147
+ DpaReadinessFinding(
148
+ code="readiness-record-missing",
149
+ message="DPA readiness record is missing",
150
+ path=display_path,
151
+ ),
152
+ ),
153
+ blockers=(),
154
+ )
155
+
156
+ try:
157
+ data = json.loads(path.read_text(encoding="utf-8"))
158
+ except json.JSONDecodeError as exc:
159
+ return DpaReadinessResult(
160
+ path=display_path,
161
+ data={},
162
+ findings=(
163
+ DpaReadinessFinding(
164
+ code="readiness-json-invalid",
165
+ message=f"DPA readiness record is not valid JSON: {exc}",
166
+ path=display_path,
167
+ ),
168
+ ),
169
+ blockers=(),
170
+ )
171
+
172
+ if not isinstance(data, dict):
173
+ findings.append(
174
+ DpaReadinessFinding(
175
+ code="readiness-record-not-object",
176
+ message="DPA readiness record must contain a JSON object",
177
+ path=display_path,
178
+ )
179
+ )
180
+ return DpaReadinessResult(path=display_path, data={}, findings=tuple(findings), blockers=())
181
+
182
+ _validate_top_level(data, findings, display_path)
183
+ _validate_claims(data, findings, display_path)
184
+ _validate_probe_family_status(data, findings, display_path)
185
+ _validate_evidence_inputs(data, findings, display_path, base)
186
+ _validate_command_manifest_ack(data, findings, display_path, base)
187
+ blockers = _collect_blockers(data)
188
+ _validate_status_consistency(data, blockers, findings, display_path)
189
+
190
+ return DpaReadinessResult(
191
+ path=display_path,
192
+ data=data,
193
+ findings=tuple(findings),
194
+ blockers=tuple(blockers),
195
+ )
196
+
197
+
198
+ def render_dpa_readiness_result(result: DpaReadinessResult) -> str:
199
+ lines = [
200
+ f"DPA readiness: {result.status}",
201
+ f"implementation scope: {IMPLEMENTATION_SCOPE}",
202
+ f"DP2 implementation: {result.implementation_percent}%",
203
+ f"kit-wide DPA: {KIT_WIDE_DPA_STATUS}",
204
+ f"record: {result.path}",
205
+ f"findings: {len(result.findings)}",
206
+ f"blockers: {len(result.blockers)}",
207
+ ]
208
+ if result.findings:
209
+ lines.append("")
210
+ lines.append("Findings:")
211
+ lines.extend(f"- {finding.code}: {finding.message}" for finding in result.findings)
212
+ if result.blockers:
213
+ lines.append("")
214
+ lines.append("DP2 blockers:")
215
+ lines.extend(f"- {blocker}" for blocker in result.blockers)
216
+ if result.progress_items:
217
+ lines.append("")
218
+ lines.append(f"Progress model: {PROGRESS_MODEL}")
219
+ lines.extend(
220
+ f"- {item.id}: {item.earned}/{item.weight} ({item.status})"
221
+ for item in result.progress_items
222
+ )
223
+ if not result.findings and not result.blockers:
224
+ lines.append("")
225
+ lines.append("DP2 authorization evidence is structurally complete.")
226
+ return "\n".join(lines) + "\n"
227
+
228
+
229
+ def _display_path(path: Path, base: Path) -> str:
230
+ try:
231
+ return path.relative_to(base).as_posix()
232
+ except ValueError:
233
+ return path.as_posix()
234
+
235
+
236
+ def _validate_top_level(data: dict[str, Any], findings: list[DpaReadinessFinding], path: str) -> None:
237
+ if data.get("schema_version") != EXPECTED_SCHEMA_VERSION:
238
+ findings.append(
239
+ DpaReadinessFinding(
240
+ code="invalid-schema-version",
241
+ message=f"schema_version must be {EXPECTED_SCHEMA_VERSION}",
242
+ path=path,
243
+ )
244
+ )
245
+ if data.get("kind") != EXPECTED_KIND:
246
+ findings.append(
247
+ DpaReadinessFinding(
248
+ code="invalid-kind",
249
+ message=f"kind must be {EXPECTED_KIND!r}",
250
+ path=path,
251
+ )
252
+ )
253
+ if data.get("status") not in {BLOCKED_STATUS, AUTHORIZED_STATUS}:
254
+ findings.append(
255
+ DpaReadinessFinding(
256
+ code="invalid-status",
257
+ message=f"status must be {BLOCKED_STATUS!r} or {AUTHORIZED_STATUS!r}",
258
+ path=path,
259
+ )
260
+ )
261
+
262
+
263
+ def _validate_claims(data: dict[str, Any], findings: list[DpaReadinessFinding], path: str) -> None:
264
+ claims = data.get("claims")
265
+ if not isinstance(claims, dict):
266
+ findings.append(
267
+ DpaReadinessFinding(code="claims-missing", message="claims must be a mapping", path=path)
268
+ )
269
+ return
270
+ for claim in REQUIRED_FALSE_CLAIMS:
271
+ if claims.get(claim) is not False:
272
+ findings.append(
273
+ DpaReadinessFinding(
274
+ code="unsafe-claim",
275
+ message=f"claim {claim!r} must be false in the current DPA readiness record",
276
+ path=path,
277
+ )
278
+ )
279
+ dp2_authorized = claims.get("dp2_authorized")
280
+ if data.get("status") == AUTHORIZED_STATUS:
281
+ if dp2_authorized is not True:
282
+ findings.append(
283
+ DpaReadinessFinding(
284
+ code="authorization-claim-missing",
285
+ message="claim 'dp2_authorized' must be true when status is DP2_AUTHORIZED",
286
+ path=path,
287
+ )
288
+ )
289
+ elif dp2_authorized is not False:
290
+ findings.append(
291
+ DpaReadinessFinding(
292
+ code="premature-authorization-claim",
293
+ message="claim 'dp2_authorized' must be false unless status is DP2_AUTHORIZED",
294
+ path=path,
295
+ )
296
+ )
297
+ maintainer_authorized = claims.get("maintainer_authorization_recorded")
298
+ if maintainer_authorized is not None:
299
+ if data.get("status") == AUTHORIZED_STATUS and maintainer_authorized is not True:
300
+ findings.append(
301
+ DpaReadinessFinding(
302
+ code="maintainer-authorization-claim-missing",
303
+ message="claim 'maintainer_authorization_recorded' must be true when status is DP2_AUTHORIZED",
304
+ path=path,
305
+ )
306
+ )
307
+ if data.get("status") != AUTHORIZED_STATUS and maintainer_authorized is not False:
308
+ findings.append(
309
+ DpaReadinessFinding(
310
+ code="premature-maintainer-authorization-claim",
311
+ message="claim 'maintainer_authorization_recorded' must be false unless status is DP2_AUTHORIZED",
312
+ path=path,
313
+ )
314
+ )
315
+
316
+
317
+ def _validate_probe_family_status(
318
+ data: dict[str, Any], findings: list[DpaReadinessFinding], path: str
319
+ ) -> None:
320
+ probe_status = data.get("probe_family_status")
321
+ if not isinstance(probe_status, dict):
322
+ findings.append(
323
+ DpaReadinessFinding(
324
+ code="probe-family-status-missing",
325
+ message="probe_family_status must be a mapping",
326
+ path=path,
327
+ )
328
+ )
329
+ return
330
+ missing = sorted(set(REQUIRED_PROBE_FAMILIES) - set(probe_status))
331
+ for family in missing:
332
+ findings.append(
333
+ DpaReadinessFinding(
334
+ code="probe-family-missing",
335
+ message=f"required Probe family is missing: {family}",
336
+ path=path,
337
+ )
338
+ )
339
+
340
+
341
+ def _validate_evidence_inputs(
342
+ data: dict[str, Any], findings: list[DpaReadinessFinding], path: str, base: Path
343
+ ) -> None:
344
+ inputs = data.get("evidence_inputs")
345
+ if not isinstance(inputs, list):
346
+ findings.append(
347
+ DpaReadinessFinding(
348
+ code="evidence-inputs-missing",
349
+ message="evidence_inputs must be a list",
350
+ path=path,
351
+ )
352
+ )
353
+ return
354
+ for index, item in enumerate(inputs):
355
+ if not isinstance(item, dict):
356
+ findings.append(
357
+ DpaReadinessFinding(
358
+ code="evidence-input-invalid",
359
+ message=f"evidence_inputs[{index}] must be a mapping",
360
+ path=path,
361
+ )
362
+ )
363
+ continue
364
+ raw_path = item.get("path")
365
+ if not isinstance(raw_path, str) or not raw_path:
366
+ findings.append(
367
+ DpaReadinessFinding(
368
+ code="evidence-input-path-missing",
369
+ message=f"evidence_inputs[{index}].path must be a non-empty string",
370
+ path=path,
371
+ )
372
+ )
373
+ continue
374
+ evidence_path = Path(raw_path)
375
+ if evidence_path.is_absolute():
376
+ findings.append(
377
+ DpaReadinessFinding(
378
+ code="evidence-input-absolute-path",
379
+ message=f"evidence input uses an absolute path: {raw_path}",
380
+ path=path,
381
+ )
382
+ )
383
+ continue
384
+ if not (base / evidence_path).exists():
385
+ findings.append(
386
+ DpaReadinessFinding(
387
+ code="evidence-input-missing",
388
+ message=f"evidence input path does not exist: {raw_path}",
389
+ path=path,
390
+ )
391
+ )
392
+
393
+
394
+ def _validate_command_manifest_ack(
395
+ data: dict[str, Any], findings: list[DpaReadinessFinding], path: str, base: Path
396
+ ) -> None:
397
+ command_ref = load_workspace(base, suppress_legacy_profile_warning=True).reference_file(
398
+ "agentic-kit-commands.json"
399
+ )
400
+ command_ref_text = _display_path(command_ref, base)
401
+ if not command_ref.exists():
402
+ findings.append(
403
+ DpaReadinessFinding(
404
+ code="command-reference-missing",
405
+ message=f"command reference is missing: {command_ref_text}",
406
+ path=path,
407
+ )
408
+ )
409
+ return
410
+ try:
411
+ command_data = json.loads(command_ref.read_text(encoding="utf-8"))
412
+ except json.JSONDecodeError as exc:
413
+ findings.append(
414
+ DpaReadinessFinding(
415
+ code="command-reference-json-invalid",
416
+ message=f"command reference is not valid JSON: {exc}",
417
+ path=path,
418
+ )
419
+ )
420
+ return
421
+ meta = command_data.get("meta")
422
+ manifest_sha = meta.get("manifest_sha") if isinstance(meta, dict) else None
423
+ if not isinstance(manifest_sha, str) or not manifest_sha:
424
+ findings.append(
425
+ DpaReadinessFinding(
426
+ code="command-reference-manifest-sha-missing",
427
+ message="command reference meta.manifest_sha must be a non-empty string",
428
+ path=path,
429
+ )
430
+ )
431
+ return
432
+ expected_ack = f"COMMAND_MANIFEST_ACK {manifest_sha}"
433
+ if data.get("command_manifest_ack") != expected_ack:
434
+ findings.append(
435
+ DpaReadinessFinding(
436
+ code="command-manifest-ack-drift",
437
+ message=f"command_manifest_ack must be {expected_ack!r}",
438
+ path=path,
439
+ )
440
+ )
441
+
442
+
443
+ def _collect_blockers(data: dict[str, Any]) -> list[str]:
444
+ dp2_entry = data.get("dp2_entry_status")
445
+ if not isinstance(dp2_entry, dict):
446
+ return ["dp2_entry_status missing"]
447
+ blockers: list[str] = []
448
+ for requirement, status in dp2_entry.items():
449
+ if status == "BLOCKED":
450
+ blockers.append(requirement)
451
+ return blockers
452
+
453
+
454
+ def _validate_status_consistency(
455
+ data: dict[str, Any],
456
+ blockers: list[str],
457
+ findings: list[DpaReadinessFinding],
458
+ path: str,
459
+ ) -> None:
460
+ status = data.get("status")
461
+ if status == AUTHORIZED_STATUS and blockers:
462
+ findings.append(
463
+ DpaReadinessFinding(
464
+ code="authorized-with-blockers",
465
+ message="status is DP2_AUTHORIZED while DP2 blockers remain recorded",
466
+ path=path,
467
+ )
468
+ )
469
+ if status == BLOCKED_STATUS and not blockers:
470
+ findings.append(
471
+ DpaReadinessFinding(
472
+ code="blocked-without-blockers",
473
+ message="status is DP2_BLOCKED but no DP2 blockers are recorded",
474
+ path=path,
475
+ )
476
+ )
477
+
478
+
479
+ def _progress_items(data: dict[str, Any]) -> list[DpaProgressItem]:
480
+ dp2_entry = data.get("dp2_entry_status")
481
+ evidence_inputs = data.get("evidence_inputs")
482
+ if not isinstance(dp2_entry, dict):
483
+ dp2_entry = {}
484
+ items: list[DpaProgressItem] = []
485
+ for item_id, label, weight in PROGRESS_WEIGHTS:
486
+ if item_id == "dp1_evidence_staged":
487
+ complete = isinstance(evidence_inputs, list) and bool(evidence_inputs)
488
+ status = "SATISFIED_FOR_STAGING" if complete else "BLOCKED"
489
+ elif item_id == "dp1_readiness_recorded":
490
+ complete = data.get("status") in {BLOCKED_STATUS, AUTHORIZED_STATUS}
491
+ status = str(data.get("status") or "BLOCKED")
492
+ else:
493
+ raw_status = dp2_entry.get(item_id)
494
+ complete = isinstance(raw_status, str) and raw_status != "BLOCKED"
495
+ status = str(raw_status or "BLOCKED")
496
+ items.append(
497
+ DpaProgressItem(
498
+ id=item_id,
499
+ label=label,
500
+ weight=weight,
501
+ earned=weight if complete else 0,
502
+ status=status,
503
+ )
504
+ )
505
+ return items