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.
- agentic_project_kit/__init__.py +1 -0
- agentic_project_kit/absolute_path_portability_audit.py +241 -0
- agentic_project_kit/access_level_policy.py +14 -0
- agentic_project_kit/access_levels.py +22 -0
- agentic_project_kit/action_registry.py +195 -0
- agentic_project_kit/action_specs.py +286 -0
- agentic_project_kit/agent_command_runner.py +516 -0
- agentic_project_kit/audit_test_matrix.py +36 -0
- agentic_project_kit/audit_test_matrix_cli.py +43 -0
- agentic_project_kit/chat_bootloader.py +257 -0
- agentic_project_kit/chat_entrypoint_contract.py +297 -0
- agentic_project_kit/checks.py +511 -0
- agentic_project_kit/ci_readiness.py +211 -0
- agentic_project_kit/cli.py +147 -0
- agentic_project_kit/cli_commands/__init__.py +1 -0
- agentic_project_kit/cli_commands/absolute_path_audit.py +25 -0
- agentic_project_kit/cli_commands/actions.py +21 -0
- agentic_project_kit/cli_commands/artifact_gc.py +240 -0
- agentic_project_kit/cli_commands/boot.py +55 -0
- agentic_project_kit/cli_commands/chat.py +34 -0
- agentic_project_kit/cli_commands/checks.py +305 -0
- agentic_project_kit/cli_commands/cockpit.py +103 -0
- agentic_project_kit/cli_commands/command_taxonomy.py +25 -0
- agentic_project_kit/cli_commands/commands.py +129 -0
- agentic_project_kit/cli_commands/dev.py +23 -0
- agentic_project_kit/cli_commands/direction.py +92 -0
- agentic_project_kit/cli_commands/doc_currency_audit.py +25 -0
- agentic_project_kit/cli_commands/doc_registry.py +126 -0
- agentic_project_kit/cli_commands/docs.py +223 -0
- agentic_project_kit/cli_commands/dpa.py +1338 -0
- agentic_project_kit/cli_commands/evidence.py +251 -0
- agentic_project_kit/cli_commands/github.py +34 -0
- agentic_project_kit/cli_commands/governance.py +14 -0
- agentic_project_kit/cli_commands/gui.py +23 -0
- agentic_project_kit/cli_commands/gui_readiness_gate.py +27 -0
- agentic_project_kit/cli_commands/handoff.py +183 -0
- agentic_project_kit/cli_commands/human_workflows.py +430 -0
- agentic_project_kit/cli_commands/init.py +132 -0
- agentic_project_kit/cli_commands/instruction.py +75 -0
- agentic_project_kit/cli_commands/mutation_lock_audit.py +24 -0
- agentic_project_kit/cli_commands/ns_legacy_audit.py +25 -0
- agentic_project_kit/cli_commands/pass_already_done.py +66 -0
- agentic_project_kit/cli_commands/patch_failure_discipline_audit.py +26 -0
- agentic_project_kit/cli_commands/patch_preflight.py +37 -0
- agentic_project_kit/cli_commands/path_literal_audit.py +39 -0
- agentic_project_kit/cli_commands/patterns.py +40 -0
- agentic_project_kit/cli_commands/planning_docs_audit.py +25 -0
- agentic_project_kit/cli_commands/pr.py +133 -0
- agentic_project_kit/cli_commands/pr_hygiene.py +27 -0
- agentic_project_kit/cli_commands/profiles.py +26 -0
- agentic_project_kit/cli_commands/program_redundancy_audit.py +25 -0
- agentic_project_kit/cli_commands/project_direction.py +25 -0
- agentic_project_kit/cli_commands/release.py +392 -0
- agentic_project_kit/cli_commands/release_publish_orchestration.py +36 -0
- agentic_project_kit/cli_commands/remote_branch_hygiene.py +152 -0
- agentic_project_kit/cli_commands/remote_next.py +57 -0
- agentic_project_kit/cli_commands/removed_ns_commands.py +65 -0
- agentic_project_kit/cli_commands/rule_registry.py +129 -0
- agentic_project_kit/cli_commands/rules.py +222 -0
- agentic_project_kit/cli_commands/scaffold.py +46 -0
- agentic_project_kit/cli_commands/slice.py +21 -0
- agentic_project_kit/cli_commands/standard_gates_audit_suite.py +27 -0
- agentic_project_kit/cli_commands/state.py +51 -0
- agentic_project_kit/cli_commands/status_current_state_audit.py +30 -0
- agentic_project_kit/cli_commands/todo.py +53 -0
- agentic_project_kit/cli_commands/transfer.py +19 -0
- agentic_project_kit/cli_commands/transfer_context_flow.py +398 -0
- agentic_project_kit/cli_commands/transfer_context_helpers.py +398 -0
- agentic_project_kit/cli_commands/transfer_diagnostics.py +767 -0
- agentic_project_kit/cli_commands/transfer_evidence_flow.py +368 -0
- agentic_project_kit/cli_commands/transfer_handoff_flow.py +594 -0
- agentic_project_kit/cli_commands/transfer_order_contract.py +74 -0
- agentic_project_kit/cli_commands/transfer_post_merge_complete.py +442 -0
- agentic_project_kit/cli_commands/transfer_post_merge_settle.py +171 -0
- agentic_project_kit/cli_commands/transfer_pr_closeout_complete.py +124 -0
- agentic_project_kit/cli_commands/transfer_pr_create_flow.py +743 -0
- agentic_project_kit/cli_commands/transfer_pr_merge_flow.py +547 -0
- agentic_project_kit/cli_commands/transfer_repo_after_pr.py +160 -0
- agentic_project_kit/cli_commands/transfer_repo_pre_pr.py +664 -0
- agentic_project_kit/cli_commands/transfer_runner_flow.py +131 -0
- agentic_project_kit/cli_commands/transfer_runtime_initial.py +194 -0
- agentic_project_kit/cli_commands/transfer_shared.py +274 -0
- agentic_project_kit/cli_commands/validation.py +200 -0
- agentic_project_kit/cli_commands/version.py +27 -0
- agentic_project_kit/cli_commands/work_orders.py +219 -0
- agentic_project_kit/cli_commands/workflow.py +471 -0
- agentic_project_kit/cli_commands/workflow_guard.py +26 -0
- agentic_project_kit/cli_commands/workspace.py +252 -0
- agentic_project_kit/cockpit.py +608 -0
- agentic_project_kit/cockpit_readiness.py +67 -0
- agentic_project_kit/command_authority_audit.py +192 -0
- agentic_project_kit/command_inbox_check.py +103 -0
- agentic_project_kit/command_manifest.py +591 -0
- agentic_project_kit/command_queue_contract.py +161 -0
- agentic_project_kit/command_selector.py +245 -0
- agentic_project_kit/command_taxonomy.py +264 -0
- agentic_project_kit/commit_guard.py +67 -0
- agentic_project_kit/communication_artifact_gc.py +495 -0
- agentic_project_kit/communication_rule_context.py +437 -0
- agentic_project_kit/communication_state.py +115 -0
- agentic_project_kit/composed_wrapper_invariants.py +97 -0
- agentic_project_kit/composite_short_circuit.py +140 -0
- agentic_project_kit/contract.py +251 -0
- agentic_project_kit/control_state.py +83 -0
- agentic_project_kit/doc_currency_audit.py +536 -0
- agentic_project_kit/doc_lifecycle.py +1179 -0
- agentic_project_kit/doc_lifecycle_signals.py +446 -0
- agentic_project_kit/doc_lifecycle_sweep.py +639 -0
- agentic_project_kit/doc_mesh.py +491 -0
- agentic_project_kit/doctor.py +379 -0
- agentic_project_kit/documentation_registry.py +1122 -0
- agentic_project_kit/documentation_system_audit.py +310 -0
- agentic_project_kit/dpa_current_handoff_lifecycle.py +549 -0
- agentic_project_kit/dpa_dp2_decision_readiness.py +362 -0
- agentic_project_kit/dpa_dp3_dp4_adjudication.py +604 -0
- agentic_project_kit/dpa_dp5_block_new_gate.py +294 -0
- agentic_project_kit/dpa_dp5_stage_adoption.py +615 -0
- agentic_project_kit/dpa_dp5_strict_gate.py +187 -0
- agentic_project_kit/dpa_final_closeout.py +627 -0
- agentic_project_kit/dpa_fixture_evidence.py +680 -0
- agentic_project_kit/dpa_maintainer_record_check.py +711 -0
- agentic_project_kit/dpa_post_dp2_scope_assessment.py +734 -0
- agentic_project_kit/dpa_probe_002_readiness.py +509 -0
- agentic_project_kit/dpa_probe_003_readiness.py +417 -0
- agentic_project_kit/dpa_probe_004_readiness.py +437 -0
- agentic_project_kit/dpa_readiness.py +505 -0
- agentic_project_kit/dpa_readonly_probe_execution.py +500 -0
- agentic_project_kit/dpa_registry_contracts.py +312 -0
- agentic_project_kit/dpa_renderer_readiness.py +407 -0
- agentic_project_kit/dpa_repo_adoption_assessment.py +574 -0
- agentic_project_kit/dpa_stable_readiness.py +743 -0
- agentic_project_kit/dpa_successor_projection.py +168 -0
- agentic_project_kit/dpa_workspace_init_projection.py +93 -0
- agentic_project_kit/dpa_wrt_ch001_evidence.py +327 -0
- agentic_project_kit/entrypoint_slice_runner.py +153 -0
- agentic_project_kit/evidence_clean.py +152 -0
- agentic_project_kit/evidence_commit_paths.py +214 -0
- agentic_project_kit/evidence_finalize_log.py +204 -0
- agentic_project_kit/evidence_guard.py +114 -0
- agentic_project_kit/evidence_inspector.py +507 -0
- agentic_project_kit/evidence_state_contract.py +99 -0
- agentic_project_kit/execution_mode_state.py +148 -0
- agentic_project_kit/final_summary_contract.py +85 -0
- agentic_project_kit/finalize_guard.py +139 -0
- agentic_project_kit/gatekeeper_core.py +232 -0
- agentic_project_kit/github.py +25 -0
- agentic_project_kit/governance.py +59 -0
- agentic_project_kit/gui_action_execution.py +105 -0
- agentic_project_kit/gui_action_renderer.py +81 -0
- agentic_project_kit/gui_action_run_intent.py +98 -0
- agentic_project_kit/gui_action_views.py +195 -0
- agentic_project_kit/gui_activity_log.py +76 -0
- agentic_project_kit/gui_button_catalog.py +605 -0
- agentic_project_kit/gui_cockpit.py +365 -0
- agentic_project_kit/gui_cockpit_actions.py +695 -0
- agentic_project_kit/gui_cockpit_common.py +172 -0
- agentic_project_kit/gui_cockpit_header.py +501 -0
- agentic_project_kit/gui_cockpit_sidebar.py +387 -0
- agentic_project_kit/gui_cockpit_task.py +748 -0
- agentic_project_kit/gui_command_projection.py +355 -0
- agentic_project_kit/gui_communication_modes.py +130 -0
- agentic_project_kit/gui_dry_run.py +219 -0
- agentic_project_kit/gui_gatekeeper_status.py +259 -0
- agentic_project_kit/gui_layout_plan.py +148 -0
- agentic_project_kit/gui_open_folder.py +18 -0
- agentic_project_kit/gui_output_status_panel.py +61 -0
- agentic_project_kit/gui_panel_state.py +47 -0
- agentic_project_kit/gui_presenter.py +62 -0
- agentic_project_kit/gui_readiness_gate.py +206 -0
- agentic_project_kit/gui_release_flow.py +59 -0
- agentic_project_kit/gui_task_editor.py +1077 -0
- agentic_project_kit/gui_tk_widgets.py +177 -0
- agentic_project_kit/gui_tkinter_renderer.py +87 -0
- agentic_project_kit/gui_tkinter_shell.py +1176 -0
- agentic_project_kit/gui_transfer_contract.py +28 -0
- agentic_project_kit/gui_viewmodel.py +698 -0
- agentic_project_kit/gui_window_guard.py +46 -0
- agentic_project_kit/handoff_freshness.py +390 -0
- agentic_project_kit/handoff_prompt.py +180 -0
- agentic_project_kit/handoff_state.py +254 -0
- agentic_project_kit/instruction_lint.py +455 -0
- agentic_project_kit/llm_context_carriers.py +66 -0
- agentic_project_kit/llm_execution_context.py +285 -0
- agentic_project_kit/local_command_stack.py +133 -0
- agentic_project_kit/local_feature_gate.py +82 -0
- agentic_project_kit/local_garbage_collector.py +269 -0
- agentic_project_kit/models.py +18 -0
- agentic_project_kit/mutation_lock_audit.py +421 -0
- agentic_project_kit/next_turn_evidence.py +253 -0
- agentic_project_kit/next_turn_merge_if_green.py +459 -0
- agentic_project_kit/next_turn_pr_status.py +255 -0
- agentic_project_kit/next_turn_result.py +183 -0
- agentic_project_kit/next_turn_runner.py +159 -0
- agentic_project_kit/next_turn_slot.py +107 -0
- agentic_project_kit/next_turn_status.py +274 -0
- agentic_project_kit/ns_legacy_reference_audit.py +258 -0
- agentic_project_kit/ns_slice_runner.py +16 -0
- agentic_project_kit/operational_handoff_projection.py +124 -0
- agentic_project_kit/output_contract.py +80 -0
- agentic_project_kit/output_repair.py +61 -0
- agentic_project_kit/pass_already_done.py +216 -0
- agentic_project_kit/patch_artifact_preflight.py +147 -0
- agentic_project_kit/patch_cycle_workflow.py +440 -0
- agentic_project_kit/patch_failure_discipline_audit.py +223 -0
- agentic_project_kit/patch_preflight.py +215 -0
- agentic_project_kit/path_literal_audit.py +612 -0
- agentic_project_kit/patterns.py +99 -0
- agentic_project_kit/planning_docs_consolidation_audit.py +313 -0
- agentic_project_kit/portability_shell_gate.py +40 -0
- agentic_project_kit/post_merge_handoff_refresh.py +56 -0
- agentic_project_kit/post_release.py +252 -0
- agentic_project_kit/post_release_closeout.py +616 -0
- agentic_project_kit/pr_cleanup.py +172 -0
- agentic_project_kit/pr_closeout.py +50 -0
- agentic_project_kit/pr_closeout_complete.py +344 -0
- agentic_project_kit/pr_create_or_skip.py +71 -0
- agentic_project_kit/pr_hygiene.py +210 -0
- agentic_project_kit/program_redundancy_audit.py +251 -0
- agentic_project_kit/project_direction.py +840 -0
- agentic_project_kit/protected_change_planner.py +194 -0
- agentic_project_kit/release.py +401 -0
- agentic_project_kit/release_doi_safety.py +24 -0
- agentic_project_kit/release_gate_core.py +174 -0
- agentic_project_kit/release_metadata_authority_gate.py +328 -0
- agentic_project_kit/release_metadata_prep.py +43 -0
- agentic_project_kit/release_notes.py +823 -0
- agentic_project_kit/release_prep_core.py +204 -0
- agentic_project_kit/release_prepare.py +369 -0
- agentic_project_kit/release_process_guardrails.py +247 -0
- agentic_project_kit/release_publish_core.py +114 -0
- agentic_project_kit/release_publish_orchestration.py +469 -0
- agentic_project_kit/release_state.py +552 -0
- agentic_project_kit/release_verify_core.py +175 -0
- agentic_project_kit/remote_branch_hygiene.py +223 -0
- agentic_project_kit/remote_next.py +89 -0
- agentic_project_kit/remote_next_closeout.py +101 -0
- agentic_project_kit/remote_next_order_contract.py +391 -0
- agentic_project_kit/removed_ns_commands.py +458 -0
- agentic_project_kit/removed_source_audit.py +225 -0
- agentic_project_kit/rendering.py +13 -0
- agentic_project_kit/repair_report.py +79 -0
- agentic_project_kit/repo_identity.py +59 -0
- agentic_project_kit/result_report_classifier.py +57 -0
- agentic_project_kit/rule_ack.py +105 -0
- agentic_project_kit/rule_preservation.py +58 -0
- agentic_project_kit/rule_refresh.py +174 -0
- agentic_project_kit/rule_registry_registration.py +322 -0
- agentic_project_kit/rule_registry_report.py +206 -0
- agentic_project_kit/rule_registry_validator.py +704 -0
- agentic_project_kit/rule_snapshot.py +98 -0
- agentic_project_kit/rule_source_validator.py +133 -0
- agentic_project_kit/run_summary.py +68 -0
- agentic_project_kit/run_summary_renderer.py +238 -0
- agentic_project_kit/runtime_validator.py +68 -0
- agentic_project_kit/safe_file_replace.py +35 -0
- agentic_project_kit/safe_patch.py +47 -0
- agentic_project_kit/safe_push.py +218 -0
- agentic_project_kit/safe_remove_diagnostic.py +51 -0
- agentic_project_kit/scaffold.py +69 -0
- agentic_project_kit/site_claims.py +444 -0
- agentic_project_kit/site_generator.py +1140 -0
- agentic_project_kit/slice_gate.py +225 -0
- agentic_project_kit/slice_runner.py +75 -0
- agentic_project_kit/standard_gates_audit_suite.py +167 -0
- agentic_project_kit/state_freshness.py +135 -0
- agentic_project_kit/status_current_state_audit.py +762 -0
- agentic_project_kit/successor_handoff_package.py +1763 -0
- agentic_project_kit/templates.py +292 -0
- agentic_project_kit/terminal_block_guard.py +166 -0
- agentic_project_kit/terminal_logging.py +275 -0
- agentic_project_kit/todo.py +97 -0
- agentic_project_kit/transfer_closeout.py +136 -0
- agentic_project_kit/transfer_continue.py +215 -0
- agentic_project_kit/transfer_local_runner.py +101 -0
- agentic_project_kit/transfer_operation_monitor.py +236 -0
- agentic_project_kit/transfer_post_merge_lifecycle.py +356 -0
- agentic_project_kit/transfer_post_merge_settle.py +383 -0
- agentic_project_kit/transfer_pr_actions.py +164 -0
- agentic_project_kit/transfer_remote_next.py +959 -0
- agentic_project_kit/transfer_repo_actions.py +2253 -0
- agentic_project_kit/transfer_runner.py +347 -0
- agentic_project_kit/transfer_safety_context.py +291 -0
- agentic_project_kit/transfer_state.py +483 -0
- agentic_project_kit/transfer_uplink.py +471 -0
- agentic_project_kit/transfer_workflow_next.py +148 -0
- agentic_project_kit/typed_work_order_evidence.py +58 -0
- agentic_project_kit/typed_work_order_queue.py +166 -0
- agentic_project_kit/typed_work_order_runner.py +168 -0
- agentic_project_kit/work_cycle.py +317 -0
- agentic_project_kit/work_discard_changes.py +129 -0
- agentic_project_kit/work_order_patch.py +199 -0
- agentic_project_kit/work_order_runner.py +120 -0
- agentic_project_kit/work_order_uploader.py +220 -0
- agentic_project_kit/work_order_validator.py +185 -0
- agentic_project_kit/work_orders.py +246 -0
- agentic_project_kit/workflow_guard.py +262 -0
- agentic_project_kit/workflow_summary_runner.py +124 -0
- agentic_project_kit/workspace.py +665 -0
- agentic_project_kit/workspace_adopt.py +471 -0
- agentic_project_kit/workspace_dpa_intake.py +406 -0
- agentic_project_kit/workspace_init.py +487 -0
- agentic_project_kit/workspace_lock.py +129 -0
- agentic_project_kit/workspace_remove.py +381 -0
- agentic_project_kit/workspace_upgrade.py +324 -0
- agentic_project_kit/wrapper_live_status.py +104 -0
- agentic_project_kit-1.0.1.dist-info/METADATA +724 -0
- agentic_project_kit-1.0.1.dist-info/RECORD +310 -0
- agentic_project_kit-1.0.1.dist-info/WHEEL +4 -0
- agentic_project_kit-1.0.1.dist-info/entry_points.txt +3 -0
- 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
|