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,549 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass, field
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from agentic_project_kit.dpa_readiness import DEFAULT_READINESS_PATH, evaluate_dpa_readiness
|
|
13
|
+
from agentic_project_kit.operational_handoff_projection import (
|
|
14
|
+
ensure_generated_operational_handoff_block,
|
|
15
|
+
render_current_operational_handoff_state,
|
|
16
|
+
)
|
|
17
|
+
from agentic_project_kit.safe_file_replace import safe_replace
|
|
18
|
+
from agentic_project_kit.workspace import LEGACY_DEFAULTS, load_workspace
|
|
19
|
+
from agentic_project_kit.workspace_lock import WorkspaceLockBusy, workspace_mutation_lock
|
|
20
|
+
|
|
21
|
+
DEFAULT_ACCEPTANCE_STATE_PATH = Path(LEGACY_DEFAULTS.agentic_root) / LEGACY_DEFAULTS.dpa_current_handoff_acceptance_state_file
|
|
22
|
+
DEFAULT_CURRENT_HANDOFF_FILENAME = "CURRENT_HANDOFF.md"
|
|
23
|
+
|
|
24
|
+
CONTRACT_ID = "DPA-CURRENT-HANDOFF-OPERATIONAL-STATE-v1"
|
|
25
|
+
GATE_SET_ID = "dpa-current-handoff-minimal-gate-set-v1"
|
|
26
|
+
RENDERER_ID = "agentic_project_kit.operational_handoff_projection"
|
|
27
|
+
RENDERER_SEMANTIC_VERSION = "1"
|
|
28
|
+
TARGET_SCOPE = "CURRENT_HANDOFF_OPERATIONAL_STATE_BLOCK"
|
|
29
|
+
WRITER_ID = "WRT-CH-001"
|
|
30
|
+
|
|
31
|
+
OPERATIONAL_REFRESH_MARKER_PATTERN = re.compile(
|
|
32
|
+
r"\n## Operational documentation refresh state after PR #\d+\n\n"
|
|
33
|
+
r"Current administrative handoff refresh state is `[^`]+` \(`[^`]*`\)\. "
|
|
34
|
+
r"Continue next only after this post-PR\d+ refresh is committed and merged; "
|
|
35
|
+
r"the next substantive slice must be created from fresh main\.\n?",
|
|
36
|
+
flags=re.MULTILINE,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class DpaCurrentHandoffFinding:
|
|
42
|
+
code: str
|
|
43
|
+
severity: str
|
|
44
|
+
message: str
|
|
45
|
+
path: str = ""
|
|
46
|
+
|
|
47
|
+
def as_dict(self) -> dict[str, str]:
|
|
48
|
+
return asdict(self)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class DpaCurrentHandoffPlan:
|
|
53
|
+
branch: str
|
|
54
|
+
validation_ref: str
|
|
55
|
+
target_path: str
|
|
56
|
+
source_path: str
|
|
57
|
+
acceptance_state_path: str
|
|
58
|
+
target_before_fingerprint: str
|
|
59
|
+
source_fingerprint: str
|
|
60
|
+
projected_payload_fingerprint: str
|
|
61
|
+
projected_complete_target_fingerprint: str
|
|
62
|
+
would_change_target: bool
|
|
63
|
+
prior_acceptance_status: str
|
|
64
|
+
freshness: str
|
|
65
|
+
|
|
66
|
+
def as_dict(self) -> dict[str, object]:
|
|
67
|
+
return asdict(self)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class DpaCurrentHandoffResult:
|
|
72
|
+
result_status: str
|
|
73
|
+
execute: bool
|
|
74
|
+
initialized_acceptance: bool
|
|
75
|
+
wrote_target: bool
|
|
76
|
+
wrote_acceptance_state: bool
|
|
77
|
+
plan: DpaCurrentHandoffPlan
|
|
78
|
+
findings: tuple[DpaCurrentHandoffFinding, ...] = field(default_factory=tuple)
|
|
79
|
+
acceptance_state: dict[str, Any] | None = None
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def ok(self) -> bool:
|
|
83
|
+
return not any(item.severity == "BLOCKER" for item in self.findings)
|
|
84
|
+
|
|
85
|
+
def as_dict(self) -> dict[str, object]:
|
|
86
|
+
payload = {
|
|
87
|
+
"schema_version": 1,
|
|
88
|
+
"kind": "dpa_current_handoff_lifecycle_result",
|
|
89
|
+
"result_status": self.result_status,
|
|
90
|
+
"execute": self.execute,
|
|
91
|
+
"initialized_acceptance": self.initialized_acceptance,
|
|
92
|
+
"wrote_target": self.wrote_target,
|
|
93
|
+
"wrote_acceptance_state": self.wrote_acceptance_state,
|
|
94
|
+
"plan": self.plan.as_dict(),
|
|
95
|
+
"finding_count": len(self.findings),
|
|
96
|
+
"findings": [item.as_dict() for item in self.findings],
|
|
97
|
+
"claims": {
|
|
98
|
+
"acceptance_state_recorded": self.wrote_acceptance_state,
|
|
99
|
+
"production_mutation_performed": False,
|
|
100
|
+
"kit_conformance_claimed": False,
|
|
101
|
+
"generated_outputs_manually_patched": False,
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
if self.acceptance_state is not None:
|
|
105
|
+
payload["acceptance_state"] = self.acceptance_state
|
|
106
|
+
return payload
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _sha256_bytes(data: bytes) -> str:
|
|
110
|
+
return hashlib.sha256(data).hexdigest()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _read_bytes(path: Path) -> bytes:
|
|
114
|
+
return path.read_bytes() if path.exists() else b""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _read_text(path: Path) -> str:
|
|
118
|
+
return path.read_text(encoding="utf-8") if path.exists() else ""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _repo_rel(root: Path, path: Path) -> str:
|
|
122
|
+
try:
|
|
123
|
+
return path.relative_to(root).as_posix()
|
|
124
|
+
except ValueError:
|
|
125
|
+
return path.as_posix()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _resolve(root: Path, path: Path) -> Path:
|
|
129
|
+
return path if path.is_absolute() else root / path
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _git(root: Path, args: list[str]) -> str:
|
|
133
|
+
completed = subprocess.run(["git", *args], cwd=root, text=True, capture_output=True, check=False)
|
|
134
|
+
if completed.returncode != 0:
|
|
135
|
+
return "UNKNOWN"
|
|
136
|
+
return completed.stdout.strip() or "UNKNOWN"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _load_acceptance_state(path: Path) -> dict[str, Any] | None:
|
|
140
|
+
if not path.exists():
|
|
141
|
+
return None
|
|
142
|
+
try:
|
|
143
|
+
loaded = json.loads(path.read_text(encoding="utf-8"))
|
|
144
|
+
except json.JSONDecodeError as exc:
|
|
145
|
+
raise ValueError(f"acceptance state is not valid JSON: {exc}") from exc
|
|
146
|
+
if not isinstance(loaded, dict):
|
|
147
|
+
raise ValueError("acceptance state must be a JSON object")
|
|
148
|
+
return loaded
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _write_json_atomic(path: Path, payload: dict[str, Any]) -> None:
|
|
152
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
153
|
+
tmp = path.with_name(path.name + ".tmp")
|
|
154
|
+
tmp.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
155
|
+
safe_replace(path, tmp)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _write_text_atomic(path: Path, text: str) -> None:
|
|
159
|
+
tmp = path.with_name(path.name + ".tmp")
|
|
160
|
+
tmp.write_text(text, encoding="utf-8")
|
|
161
|
+
safe_replace(path, tmp)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _render_target_text(root: Path, target_path: Path, source_path: Path) -> str:
|
|
165
|
+
current = _read_text(target_path).replace("\\n", "\n")
|
|
166
|
+
current = OPERATIONAL_REFRESH_MARKER_PATTERN.sub("", current).rstrip() + "\n"
|
|
167
|
+
return ensure_generated_operational_handoff_block(
|
|
168
|
+
current,
|
|
169
|
+
render_current_operational_handoff_state(root, path=source_path),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _build_acceptance_state(
|
|
174
|
+
*,
|
|
175
|
+
plan: DpaCurrentHandoffPlan,
|
|
176
|
+
target_text: str,
|
|
177
|
+
branch: str,
|
|
178
|
+
validation_ref: str,
|
|
179
|
+
contract_id: str = CONTRACT_ID,
|
|
180
|
+
target_scope: str = TARGET_SCOPE,
|
|
181
|
+
writer_id: str = WRITER_ID,
|
|
182
|
+
renderer_id: str = RENDERER_ID,
|
|
183
|
+
renderer_semantic_version: str = RENDERER_SEMANTIC_VERSION,
|
|
184
|
+
) -> dict[str, Any]:
|
|
185
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
186
|
+
return {
|
|
187
|
+
"schema_version": 1,
|
|
188
|
+
"kind": "dpa_acceptance_state",
|
|
189
|
+
"status": "accepted",
|
|
190
|
+
"accepted_at": now,
|
|
191
|
+
"accepted_lifecycle_outcome": "accepted",
|
|
192
|
+
"accepted_gate_result": "pass",
|
|
193
|
+
"accepted_gate_set_id": GATE_SET_ID,
|
|
194
|
+
"branch": branch,
|
|
195
|
+
"validation_ref": validation_ref,
|
|
196
|
+
"contract_id": contract_id,
|
|
197
|
+
"target_scope": target_scope,
|
|
198
|
+
"writer_id": writer_id,
|
|
199
|
+
"renderer": {
|
|
200
|
+
"id": renderer_id,
|
|
201
|
+
"semantic_version": renderer_semantic_version,
|
|
202
|
+
},
|
|
203
|
+
"source": {
|
|
204
|
+
"path": plan.source_path,
|
|
205
|
+
"fingerprint": plan.source_fingerprint,
|
|
206
|
+
},
|
|
207
|
+
"target": {
|
|
208
|
+
"path": plan.target_path,
|
|
209
|
+
"complete_target_fingerprint": _sha256_bytes(target_text.encode("utf-8")),
|
|
210
|
+
"payload_fingerprint": plan.projected_payload_fingerprint,
|
|
211
|
+
"pre_write_fingerprint": plan.target_before_fingerprint,
|
|
212
|
+
},
|
|
213
|
+
"claims": {
|
|
214
|
+
"production_mutation_performed": False,
|
|
215
|
+
"kit_conformance_claimed": False,
|
|
216
|
+
"generated_outputs_manually_patched": False,
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _acceptance_finding(
|
|
222
|
+
code: str,
|
|
223
|
+
message: str,
|
|
224
|
+
*,
|
|
225
|
+
path: Path | None = None,
|
|
226
|
+
root: Path | None = None,
|
|
227
|
+
) -> DpaCurrentHandoffFinding:
|
|
228
|
+
rel = _repo_rel(root, path) if path is not None and root is not None else ""
|
|
229
|
+
return DpaCurrentHandoffFinding(code=code, severity="BLOCKER", message=message, path=rel)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def evaluate_current_handoff_lifecycle(
|
|
233
|
+
root: Path | str = ".",
|
|
234
|
+
*,
|
|
235
|
+
target_path: Path | None = None,
|
|
236
|
+
acceptance_state_path: Path | None = None,
|
|
237
|
+
readiness_path: Path = DEFAULT_READINESS_PATH,
|
|
238
|
+
validation_ref: str | None = None,
|
|
239
|
+
execute: bool = False,
|
|
240
|
+
initialize_acceptance: bool = False,
|
|
241
|
+
require_dp2_authorized: bool = True,
|
|
242
|
+
) -> DpaCurrentHandoffResult:
|
|
243
|
+
resolved_root = Path(root).resolve()
|
|
244
|
+
workspace = load_workspace(resolved_root)
|
|
245
|
+
target = _resolve(resolved_root, target_path) if target_path is not None else workspace.handoff_file(DEFAULT_CURRENT_HANDOFF_FILENAME)
|
|
246
|
+
source = workspace.operational_handoff_state_path()
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
projected_text = _render_target_text(resolved_root, target, source)
|
|
250
|
+
except (OSError, ValueError) as exc:
|
|
251
|
+
projected_text = _read_text(target)
|
|
252
|
+
preflight_findings = (_acceptance_finding("render-failed", str(exc), path=target, root=resolved_root),)
|
|
253
|
+
else:
|
|
254
|
+
preflight_findings = ()
|
|
255
|
+
|
|
256
|
+
return evaluate_current_handoff_text_lifecycle(
|
|
257
|
+
resolved_root,
|
|
258
|
+
target_path=target,
|
|
259
|
+
acceptance_state_path=acceptance_state_path,
|
|
260
|
+
readiness_path=readiness_path,
|
|
261
|
+
validation_ref=validation_ref,
|
|
262
|
+
execute=execute,
|
|
263
|
+
initialize_acceptance=initialize_acceptance,
|
|
264
|
+
require_dp2_authorized=require_dp2_authorized,
|
|
265
|
+
projected_text=projected_text,
|
|
266
|
+
source_path=source,
|
|
267
|
+
writer_id=WRITER_ID,
|
|
268
|
+
renderer_id=RENDERER_ID,
|
|
269
|
+
renderer_semantic_version=RENDERER_SEMANTIC_VERSION,
|
|
270
|
+
contract_id=CONTRACT_ID,
|
|
271
|
+
target_scope=TARGET_SCOPE,
|
|
272
|
+
lock_command="dpa current-handoff-refresh",
|
|
273
|
+
preflight_findings=preflight_findings,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def evaluate_current_handoff_text_lifecycle(
|
|
278
|
+
root: Path | str = ".",
|
|
279
|
+
*,
|
|
280
|
+
projected_text: str,
|
|
281
|
+
writer_id: str,
|
|
282
|
+
renderer_id: str,
|
|
283
|
+
renderer_semantic_version: str = RENDERER_SEMANTIC_VERSION,
|
|
284
|
+
contract_id: str = CONTRACT_ID,
|
|
285
|
+
target_scope: str = TARGET_SCOPE,
|
|
286
|
+
source_path: Path | str | None = None,
|
|
287
|
+
source_fingerprint: str | None = None,
|
|
288
|
+
target_path: Path | None = None,
|
|
289
|
+
acceptance_state_path: Path | None = None,
|
|
290
|
+
readiness_path: Path = DEFAULT_READINESS_PATH,
|
|
291
|
+
validation_ref: str | None = None,
|
|
292
|
+
execute: bool = False,
|
|
293
|
+
initialize_acceptance: bool = False,
|
|
294
|
+
require_dp2_authorized: bool = True,
|
|
295
|
+
lock_command: str = "dpa current-handoff-lifecycle",
|
|
296
|
+
preflight_findings: tuple[DpaCurrentHandoffFinding, ...] = (),
|
|
297
|
+
) -> DpaCurrentHandoffResult:
|
|
298
|
+
resolved_root = Path(root).resolve()
|
|
299
|
+
workspace = load_workspace(resolved_root)
|
|
300
|
+
target = _resolve(resolved_root, target_path) if target_path is not None else workspace.handoff_file(DEFAULT_CURRENT_HANDOFF_FILENAME)
|
|
301
|
+
acceptance_path = (
|
|
302
|
+
_resolve(resolved_root, acceptance_state_path)
|
|
303
|
+
if acceptance_state_path is not None
|
|
304
|
+
else workspace.dpa_current_handoff_acceptance_state_path()
|
|
305
|
+
)
|
|
306
|
+
target_rel = _repo_rel(resolved_root, target)
|
|
307
|
+
acceptance_rel = _repo_rel(resolved_root, acceptance_path)
|
|
308
|
+
source_file: Path | None = None
|
|
309
|
+
if source_fingerprint is None:
|
|
310
|
+
if source_path is None:
|
|
311
|
+
raise ValueError("source_path or source_fingerprint is required")
|
|
312
|
+
source_file = _resolve(resolved_root, Path(source_path))
|
|
313
|
+
source_rel = _repo_rel(resolved_root, source_file)
|
|
314
|
+
source_fingerprint = _sha256_bytes(_read_bytes(source_file))
|
|
315
|
+
else:
|
|
316
|
+
source_rel = str(source_path or "<declared-inputs>")
|
|
317
|
+
|
|
318
|
+
findings: list[DpaCurrentHandoffFinding] = list(preflight_findings)
|
|
319
|
+
branch = _git(resolved_root, ["branch", "--show-current"])
|
|
320
|
+
current_head = _git(resolved_root, ["rev-parse", "HEAD"])
|
|
321
|
+
effective_ref = validation_ref or current_head
|
|
322
|
+
|
|
323
|
+
if validation_ref and current_head != "UNKNOWN" and validation_ref != current_head:
|
|
324
|
+
findings.append(
|
|
325
|
+
_acceptance_finding(
|
|
326
|
+
"stale-validation-ref",
|
|
327
|
+
"validation_ref does not match the current repository HEAD",
|
|
328
|
+
path=target,
|
|
329
|
+
root=resolved_root,
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
if require_dp2_authorized:
|
|
334
|
+
readiness = evaluate_dpa_readiness(resolved_root, readiness_path=readiness_path)
|
|
335
|
+
if not readiness.ok or not readiness.dp2_ready:
|
|
336
|
+
findings.append(
|
|
337
|
+
_acceptance_finding(
|
|
338
|
+
"dp2-not-authorized",
|
|
339
|
+
"DPA readiness record does not structurally authorize DP2 for this target scope",
|
|
340
|
+
path=readiness_path,
|
|
341
|
+
root=resolved_root,
|
|
342
|
+
)
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
previous_acceptance = _load_acceptance_state(acceptance_path)
|
|
347
|
+
except ValueError as exc:
|
|
348
|
+
previous_acceptance = None
|
|
349
|
+
findings.append(_acceptance_finding("acceptance-state-invalid", str(exc), path=acceptance_path, root=resolved_root))
|
|
350
|
+
|
|
351
|
+
target_bytes = _read_bytes(target)
|
|
352
|
+
target_before = _sha256_bytes(target_bytes)
|
|
353
|
+
projected_fingerprint = _sha256_bytes(projected_text.encode("utf-8"))
|
|
354
|
+
would_change = target_bytes != projected_text.encode("utf-8")
|
|
355
|
+
|
|
356
|
+
prior_status = "missing" if previous_acceptance is None else str(previous_acceptance.get("status") or "unknown")
|
|
357
|
+
freshness = "indeterminate"
|
|
358
|
+
if previous_acceptance is None:
|
|
359
|
+
if not initialize_acceptance:
|
|
360
|
+
findings.append(
|
|
361
|
+
_acceptance_finding(
|
|
362
|
+
"acceptance-state-missing",
|
|
363
|
+
"No DPA acceptance-state record exists; pass --initialize-acceptance for the first accepted CURRENT_HANDOFF projection",
|
|
364
|
+
path=acceptance_path,
|
|
365
|
+
root=resolved_root,
|
|
366
|
+
)
|
|
367
|
+
)
|
|
368
|
+
else:
|
|
369
|
+
accepted_target = str((previous_acceptance.get("target") or {}).get("complete_target_fingerprint") or "")
|
|
370
|
+
accepted_source = str((previous_acceptance.get("source") or {}).get("fingerprint") or "")
|
|
371
|
+
accepted_source_path = str((previous_acceptance.get("source") or {}).get("path") or "")
|
|
372
|
+
accepted_target_path = str((previous_acceptance.get("target") or {}).get("path") or "")
|
|
373
|
+
if accepted_target_path != target_rel:
|
|
374
|
+
findings.append(
|
|
375
|
+
_acceptance_finding(
|
|
376
|
+
"acceptance-state-scope-mismatch",
|
|
377
|
+
f"Acceptance-state target path is {accepted_target_path!r}, expected {target_rel!r}",
|
|
378
|
+
path=acceptance_path,
|
|
379
|
+
root=resolved_root,
|
|
380
|
+
)
|
|
381
|
+
)
|
|
382
|
+
elif accepted_target != target_before:
|
|
383
|
+
findings.append(
|
|
384
|
+
_acceptance_finding(
|
|
385
|
+
"target-drift",
|
|
386
|
+
"Current target bytes differ from the accepted complete-target fingerprint",
|
|
387
|
+
path=target,
|
|
388
|
+
root=resolved_root,
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
freshness = "stale"
|
|
392
|
+
elif would_change:
|
|
393
|
+
freshness = "stale"
|
|
394
|
+
elif accepted_source_path == source_rel and accepted_source == source_fingerprint:
|
|
395
|
+
freshness = "fresh"
|
|
396
|
+
else:
|
|
397
|
+
freshness = "fresh"
|
|
398
|
+
|
|
399
|
+
plan = DpaCurrentHandoffPlan(
|
|
400
|
+
branch=branch,
|
|
401
|
+
validation_ref=effective_ref,
|
|
402
|
+
target_path=target_rel,
|
|
403
|
+
source_path=source_rel,
|
|
404
|
+
acceptance_state_path=acceptance_rel,
|
|
405
|
+
target_before_fingerprint=target_before,
|
|
406
|
+
source_fingerprint=source_fingerprint,
|
|
407
|
+
projected_payload_fingerprint=projected_fingerprint,
|
|
408
|
+
projected_complete_target_fingerprint=projected_fingerprint,
|
|
409
|
+
would_change_target=would_change,
|
|
410
|
+
prior_acceptance_status=prior_status,
|
|
411
|
+
freshness=freshness,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
if findings:
|
|
415
|
+
return DpaCurrentHandoffResult(
|
|
416
|
+
result_status="BLOCKED",
|
|
417
|
+
execute=execute,
|
|
418
|
+
initialized_acceptance=initialize_acceptance and previous_acceptance is None,
|
|
419
|
+
wrote_target=False,
|
|
420
|
+
wrote_acceptance_state=False,
|
|
421
|
+
plan=plan,
|
|
422
|
+
findings=tuple(findings),
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
if not execute:
|
|
426
|
+
status = "FRESH" if freshness == "fresh" else "WOULD_ACCEPT_INITIAL" if previous_acceptance is None else "WOULD_REFRESH"
|
|
427
|
+
return DpaCurrentHandoffResult(
|
|
428
|
+
result_status=status,
|
|
429
|
+
execute=False,
|
|
430
|
+
initialized_acceptance=initialize_acceptance and previous_acceptance is None,
|
|
431
|
+
wrote_target=False,
|
|
432
|
+
wrote_acceptance_state=False,
|
|
433
|
+
plan=plan,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
with workspace_mutation_lock(resolved_root, lock_command):
|
|
438
|
+
lock_head = _git(resolved_root, ["rev-parse", "HEAD"])
|
|
439
|
+
if current_head != "UNKNOWN" and lock_head != current_head:
|
|
440
|
+
findings.append(
|
|
441
|
+
_acceptance_finding(
|
|
442
|
+
"under-lock-head-changed",
|
|
443
|
+
"Repository HEAD changed between preflight and under-lock revalidation",
|
|
444
|
+
path=target,
|
|
445
|
+
root=resolved_root,
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
if _sha256_bytes(_read_bytes(target)) != target_before:
|
|
449
|
+
findings.append(
|
|
450
|
+
_acceptance_finding(
|
|
451
|
+
"under-lock-target-changed",
|
|
452
|
+
"Target bytes changed between preflight and under-lock revalidation",
|
|
453
|
+
path=target,
|
|
454
|
+
root=resolved_root,
|
|
455
|
+
)
|
|
456
|
+
)
|
|
457
|
+
if source_file is not None and _sha256_bytes(_read_bytes(source_file)) != source_fingerprint:
|
|
458
|
+
findings.append(
|
|
459
|
+
_acceptance_finding(
|
|
460
|
+
"under-lock-source-changed",
|
|
461
|
+
"Source bytes changed between preflight and under-lock revalidation",
|
|
462
|
+
path=source_file,
|
|
463
|
+
root=resolved_root,
|
|
464
|
+
)
|
|
465
|
+
)
|
|
466
|
+
if findings:
|
|
467
|
+
return DpaCurrentHandoffResult(
|
|
468
|
+
result_status="BLOCKED",
|
|
469
|
+
execute=True,
|
|
470
|
+
initialized_acceptance=initialize_acceptance and previous_acceptance is None,
|
|
471
|
+
wrote_target=False,
|
|
472
|
+
wrote_acceptance_state=False,
|
|
473
|
+
plan=plan,
|
|
474
|
+
findings=tuple(findings),
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
_write_text_atomic(target, projected_text)
|
|
478
|
+
if _sha256_bytes(_read_bytes(target)) != projected_fingerprint:
|
|
479
|
+
findings.append(
|
|
480
|
+
_acceptance_finding(
|
|
481
|
+
"post-write-verification-failed",
|
|
482
|
+
"Target bytes after Write do not match the projected output fingerprint",
|
|
483
|
+
path=target,
|
|
484
|
+
root=resolved_root,
|
|
485
|
+
)
|
|
486
|
+
)
|
|
487
|
+
return DpaCurrentHandoffResult(
|
|
488
|
+
result_status="BLOCKED",
|
|
489
|
+
execute=True,
|
|
490
|
+
initialized_acceptance=initialize_acceptance and previous_acceptance is None,
|
|
491
|
+
wrote_target=True,
|
|
492
|
+
wrote_acceptance_state=False,
|
|
493
|
+
plan=plan,
|
|
494
|
+
findings=tuple(findings),
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
acceptance = _build_acceptance_state(
|
|
498
|
+
plan=plan,
|
|
499
|
+
target_text=projected_text,
|
|
500
|
+
branch=branch,
|
|
501
|
+
validation_ref=effective_ref,
|
|
502
|
+
contract_id=contract_id,
|
|
503
|
+
target_scope=target_scope,
|
|
504
|
+
writer_id=writer_id,
|
|
505
|
+
renderer_id=renderer_id,
|
|
506
|
+
renderer_semantic_version=renderer_semantic_version,
|
|
507
|
+
)
|
|
508
|
+
_write_json_atomic(acceptance_path, acceptance)
|
|
509
|
+
except WorkspaceLockBusy as exc:
|
|
510
|
+
findings.append(_acceptance_finding("workspace-lock-busy", str(exc), path=target, root=resolved_root))
|
|
511
|
+
return DpaCurrentHandoffResult(
|
|
512
|
+
result_status="BLOCKED",
|
|
513
|
+
execute=True,
|
|
514
|
+
initialized_acceptance=initialize_acceptance and previous_acceptance is None,
|
|
515
|
+
wrote_target=False,
|
|
516
|
+
wrote_acceptance_state=False,
|
|
517
|
+
plan=plan,
|
|
518
|
+
findings=tuple(findings),
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
return DpaCurrentHandoffResult(
|
|
522
|
+
result_status="ACCEPTED",
|
|
523
|
+
execute=True,
|
|
524
|
+
initialized_acceptance=initialize_acceptance and previous_acceptance is None,
|
|
525
|
+
wrote_target=True,
|
|
526
|
+
wrote_acceptance_state=True,
|
|
527
|
+
plan=plan,
|
|
528
|
+
acceptance_state=acceptance,
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def render_current_handoff_lifecycle_result(result: DpaCurrentHandoffResult) -> str:
|
|
533
|
+
lines = [
|
|
534
|
+
"DPA_CURRENT_HANDOFF_LIFECYCLE",
|
|
535
|
+
f"STATUS={result.result_status}",
|
|
536
|
+
f"TARGET={result.plan.target_path}",
|
|
537
|
+
f"ACCEPTANCE_STATE={result.plan.acceptance_state_path}",
|
|
538
|
+
f"VALIDATION_REF={result.plan.validation_ref}",
|
|
539
|
+
f"FRESHNESS={result.plan.freshness}",
|
|
540
|
+
f"WOULD_CHANGE_TARGET={str(result.plan.would_change_target).lower()}",
|
|
541
|
+
f"EXECUTE={str(result.execute).lower()}",
|
|
542
|
+
f"WROTE_TARGET={str(result.wrote_target).lower()}",
|
|
543
|
+
f"WROTE_ACCEPTANCE_STATE={str(result.wrote_acceptance_state).lower()}",
|
|
544
|
+
f"FINDINGS={len(result.findings)}",
|
|
545
|
+
]
|
|
546
|
+
for finding in result.findings:
|
|
547
|
+
path = f" path={finding.path}" if finding.path else ""
|
|
548
|
+
lines.append(f"- {finding.severity} {finding.code}:{path} {finding.message}")
|
|
549
|
+
return "\n".join(lines) + "\n"
|