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,312 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ DPA_REGISTRY_OPTIONAL_DOCUMENT_FIELDS = (
7
+ "projection_contract",
8
+ "partition_contract",
9
+ )
10
+ DPA_CONTRACT_SCHEMA_VERSION = 1
11
+ DPA_ALLOWED_TARGET_SEMANTICS = frozenset({"complete-document", "registered-region"})
12
+ DPA_ALLOWED_PROJECTED_PAYLOAD_TARGET_SEMANTICS = frozenset({"region-payload"})
13
+ DPA_ALLOWED_REGION_OWNER_CLASSES = frozenset({"lifecycle", "manual", "historical"})
14
+ DPA_PROJECTION_REQUIRED_FIELDS = frozenset(
15
+ {
16
+ "schema_version",
17
+ "target_identity",
18
+ "primary_document_form",
19
+ "renderer_identifier",
20
+ "renderer_interface_version",
21
+ "renderer_semantic_version",
22
+ "canonical_sources",
23
+ "configuration",
24
+ "target_semantics",
25
+ "target_semantics_version",
26
+ "lifecycle_policy",
27
+ "freshness_policy",
28
+ "evidence_policy",
29
+ "fingerprint_algorithm",
30
+ "input_domain_version",
31
+ "migration_compatibility_version",
32
+ }
33
+ )
34
+ DPA_PROJECTION_REGION_FIELDS = frozenset(
35
+ {
36
+ "parent_document_identity",
37
+ "region_identity",
38
+ "parent_partition_contract_identity",
39
+ "projected_payload_target_semantics",
40
+ }
41
+ )
42
+ DPA_PROJECTION_ALLOWED_FIELDS = DPA_PROJECTION_REQUIRED_FIELDS | DPA_PROJECTION_REGION_FIELDS
43
+ DPA_PARTITION_REQUIRED_FIELDS = frozenset(
44
+ {
45
+ "contract_id",
46
+ "schema_version",
47
+ "regions",
48
+ "boundary_representation",
49
+ "boundary_ownership",
50
+ "encoding",
51
+ "normalization",
52
+ "line_endings",
53
+ "ordering_constraints",
54
+ "malformed_boundary_behavior",
55
+ "byte_ownership",
56
+ "partition_fingerprint_algorithm",
57
+ "input_domain",
58
+ "compatibility_version",
59
+ }
60
+ )
61
+ DPA_PARTITION_ALLOWED_FIELDS = DPA_PARTITION_REQUIRED_FIELDS
62
+
63
+
64
+ def validate_dpa_registry_contracts(
65
+ project_root: Path,
66
+ documents: list[Any],
67
+ *,
68
+ registry_path: Path,
69
+ ) -> list[str]:
70
+ errors: list[str] = []
71
+ document_entries = [entry for entry in documents if isinstance(entry, dict)]
72
+ entries_by_path = {
73
+ str(entry.get("path", "")).strip(): entry
74
+ for entry in document_entries
75
+ if str(entry.get("path", "")).strip()
76
+ }
77
+
78
+ partition_contracts_by_id: dict[str, tuple[str, dict[str, Any]]] = {}
79
+ for entry in document_entries:
80
+ path = str(entry.get("path", "")).strip()
81
+ partition_contract = entry.get("partition_contract")
82
+ if partition_contract is None:
83
+ continue
84
+ errors.extend(_validate_partition_contract(path, partition_contract, registry_path))
85
+ if isinstance(partition_contract, dict):
86
+ contract_id = str(partition_contract.get("contract_id", "")).strip()
87
+ if contract_id:
88
+ if contract_id in partition_contracts_by_id:
89
+ errors.append(
90
+ f"{registry_path}: {path} partition_contract duplicate contract_id "
91
+ f"{contract_id!r}"
92
+ )
93
+ partition_contracts_by_id[contract_id] = (path, partition_contract)
94
+
95
+ for entry in document_entries:
96
+ path = str(entry.get("path", "")).strip()
97
+ projection_contract = entry.get("projection_contract")
98
+ if projection_contract is None:
99
+ continue
100
+ errors.extend(
101
+ _validate_projection_contract(
102
+ project_root,
103
+ path,
104
+ projection_contract,
105
+ registry_path=registry_path,
106
+ entries_by_path=entries_by_path,
107
+ partition_contracts_by_id=partition_contracts_by_id,
108
+ )
109
+ )
110
+
111
+ return errors
112
+
113
+
114
+ def _validate_projection_contract(
115
+ project_root: Path,
116
+ path: str,
117
+ contract: Any,
118
+ *,
119
+ registry_path: Path,
120
+ entries_by_path: dict[str, dict[str, Any]],
121
+ partition_contracts_by_id: dict[str, tuple[str, dict[str, Any]]],
122
+ ) -> list[str]:
123
+ errors: list[str] = []
124
+ if not isinstance(contract, dict):
125
+ return [f"{registry_path}: {path} projection_contract must be a mapping"]
126
+
127
+ unknown_fields = sorted(set(contract) - DPA_PROJECTION_ALLOWED_FIELDS)
128
+ for field in unknown_fields:
129
+ errors.append(f"{registry_path}: {path} projection_contract has unknown field {field!r}")
130
+
131
+ for field in sorted(DPA_PROJECTION_REQUIRED_FIELDS - set(contract)):
132
+ errors.append(f"{registry_path}: {path} projection_contract missing field {field!r}")
133
+
134
+ if contract.get("schema_version") != DPA_CONTRACT_SCHEMA_VERSION:
135
+ errors.append(
136
+ f"{registry_path}: {path} projection_contract schema_version must be "
137
+ f"{DPA_CONTRACT_SCHEMA_VERSION}"
138
+ )
139
+
140
+ target_identity = str(contract.get("target_identity", "")).strip()
141
+ if target_identity and target_identity != path:
142
+ errors.append(
143
+ f"{registry_path}: {path} projection_contract target_identity must match document path"
144
+ )
145
+
146
+ if not isinstance(contract.get("canonical_sources"), list) or not contract.get("canonical_sources"):
147
+ errors.append(
148
+ f"{registry_path}: {path} projection_contract canonical_sources must be a non-empty list"
149
+ )
150
+ if not isinstance(contract.get("configuration"), dict):
151
+ errors.append(f"{registry_path}: {path} projection_contract configuration must be a mapping")
152
+
153
+ target_semantics = str(contract.get("target_semantics", "")).strip()
154
+ if target_semantics not in DPA_ALLOWED_TARGET_SEMANTICS:
155
+ errors.append(
156
+ f"{registry_path}: {path} projection_contract target_semantics is unsupported: "
157
+ f"{target_semantics!r}"
158
+ )
159
+ return errors
160
+
161
+ if target_semantics == "registered-region":
162
+ errors.extend(
163
+ _validate_registered_region_projection(
164
+ project_root,
165
+ path,
166
+ contract,
167
+ registry_path=registry_path,
168
+ entries_by_path=entries_by_path,
169
+ partition_contracts_by_id=partition_contracts_by_id,
170
+ )
171
+ )
172
+ else:
173
+ unexpected_region_fields = sorted(set(contract) & DPA_PROJECTION_REGION_FIELDS)
174
+ for field in unexpected_region_fields:
175
+ errors.append(
176
+ f"{registry_path}: {path} complete-document projection_contract must not "
177
+ f"declare region field {field!r}"
178
+ )
179
+ if "partition_contract" in entries_by_path.get(path, {}):
180
+ errors.append(
181
+ f"{registry_path}: {path} complete-document projection_contract is "
182
+ "inconsistent with parent partition_contract"
183
+ )
184
+
185
+ return errors
186
+
187
+
188
+ def _validate_registered_region_projection(
189
+ project_root: Path,
190
+ path: str,
191
+ contract: dict[str, Any],
192
+ *,
193
+ registry_path: Path,
194
+ entries_by_path: dict[str, dict[str, Any]],
195
+ partition_contracts_by_id: dict[str, tuple[str, dict[str, Any]]],
196
+ ) -> list[str]:
197
+ errors: list[str] = []
198
+ for field in sorted(DPA_PROJECTION_REGION_FIELDS - set(contract)):
199
+ errors.append(f"{registry_path}: {path} projection_contract missing field {field!r}")
200
+
201
+ payload_semantics = str(contract.get("projected_payload_target_semantics", "")).strip()
202
+ if payload_semantics not in DPA_ALLOWED_PROJECTED_PAYLOAD_TARGET_SEMANTICS:
203
+ errors.append(
204
+ f"{registry_path}: {path} projection_contract projected_payload_target_semantics "
205
+ f"is unsupported: {payload_semantics!r}"
206
+ )
207
+
208
+ parent_identity = str(contract.get("parent_document_identity", "")).strip()
209
+ parent_entry = entries_by_path.get(parent_identity)
210
+ if parent_entry is None:
211
+ errors.append(
212
+ f"{registry_path}: {path} projection_contract parent_document_identity is "
213
+ f"dangling: {parent_identity!r}"
214
+ )
215
+ return errors
216
+
217
+ partition_id = str(contract.get("parent_partition_contract_identity", "")).strip()
218
+ partition_owner = partition_contracts_by_id.get(partition_id)
219
+ if partition_owner is None:
220
+ errors.append(
221
+ f"{registry_path}: {path} projection_contract parent_partition_contract_identity "
222
+ f"is dangling: {partition_id!r}"
223
+ )
224
+ return errors
225
+ owner_path, partition_contract = partition_owner
226
+ if owner_path != parent_identity:
227
+ errors.append(
228
+ f"{registry_path}: {path} projection_contract parent_partition_contract_identity "
229
+ f"{partition_id!r} belongs to {owner_path!r}, not {parent_identity!r}"
230
+ )
231
+ return errors
232
+
233
+ region_identity = str(contract.get("region_identity", "")).strip()
234
+ region_ids = _partition_region_ids(partition_contract)
235
+ if region_identity not in region_ids:
236
+ errors.append(
237
+ f"{registry_path}: {path} projection_contract region_identity {region_identity!r} "
238
+ f"is not declared by parent partition_contract {partition_id!r}"
239
+ )
240
+
241
+ target_path = project_root / path
242
+ parent_path = project_root / parent_identity
243
+ if target_path == parent_path:
244
+ errors.append(f"{registry_path}: {path} registered-region projection target must not be parent")
245
+
246
+ return errors
247
+
248
+
249
+ def _validate_partition_contract(path: str, contract: Any, registry_path: Path) -> list[str]:
250
+ errors: list[str] = []
251
+ if not isinstance(contract, dict):
252
+ return [f"{registry_path}: {path} partition_contract must be a mapping"]
253
+
254
+ unknown_fields = sorted(set(contract) - DPA_PARTITION_ALLOWED_FIELDS)
255
+ for field in unknown_fields:
256
+ errors.append(f"{registry_path}: {path} partition_contract has unknown field {field!r}")
257
+
258
+ for field in sorted(DPA_PARTITION_REQUIRED_FIELDS - set(contract)):
259
+ errors.append(f"{registry_path}: {path} partition_contract missing field {field!r}")
260
+
261
+ if contract.get("schema_version") != DPA_CONTRACT_SCHEMA_VERSION:
262
+ errors.append(
263
+ f"{registry_path}: {path} partition_contract schema_version must be "
264
+ f"{DPA_CONTRACT_SCHEMA_VERSION}"
265
+ )
266
+
267
+ regions = contract.get("regions")
268
+ if not isinstance(regions, list) or not regions:
269
+ errors.append(f"{registry_path}: {path} partition_contract regions must be a non-empty list")
270
+ return errors
271
+
272
+ seen_regions: set[str] = set()
273
+ for index, region in enumerate(regions, start=1):
274
+ if not isinstance(region, dict):
275
+ errors.append(
276
+ f"{registry_path}: {path} partition_contract regions[{index}] must be a mapping"
277
+ )
278
+ continue
279
+ if set(region) != {"identity", "owner_class"}:
280
+ errors.append(
281
+ f"{registry_path}: {path} partition_contract regions[{index}] must declare "
282
+ "identity and owner_class only"
283
+ )
284
+ identity = str(region.get("identity", "")).strip()
285
+ if not identity:
286
+ errors.append(
287
+ f"{registry_path}: {path} partition_contract regions[{index}] identity is required"
288
+ )
289
+ elif identity in seen_regions:
290
+ errors.append(
291
+ f"{registry_path}: {path} partition_contract duplicate region identity {identity!r}"
292
+ )
293
+ seen_regions.add(identity)
294
+ owner_class = str(region.get("owner_class", "")).strip()
295
+ if owner_class not in DPA_ALLOWED_REGION_OWNER_CLASSES:
296
+ errors.append(
297
+ f"{registry_path}: {path} partition_contract region {identity or index!r} "
298
+ f"has unsupported owner_class {owner_class!r}"
299
+ )
300
+
301
+ return errors
302
+
303
+
304
+ def _partition_region_ids(contract: dict[str, Any]) -> set[str]:
305
+ regions = contract.get("regions")
306
+ if not isinstance(regions, list):
307
+ return set()
308
+ return {
309
+ str(region.get("identity", "")).strip()
310
+ for region in regions
311
+ if isinstance(region, dict) and str(region.get("identity", "")).strip()
312
+ }