Skip to main content

open_kioku_patch/
lib.rs

1use open_kioku_actions::{ActionKind, PolicyGate};
2use open_kioku_config::OkConfig;
3use open_kioku_context::ContextPackBuilder;
4use open_kioku_core::{PatchId, PatchPlan};
5use open_kioku_errors::{OkError, Result};
6use open_kioku_storage::OkStore;
7use sha2::{Digest, Sha256};
8
9pub struct PatchPlanner<'a> {
10    config: &'a OkConfig,
11    store: &'a dyn OkStore,
12}
13
14impl<'a> PatchPlanner<'a> {
15    pub fn new(config: &'a OkConfig, store: &'a dyn OkStore) -> Self {
16        Self { config, store }
17    }
18
19    pub fn plan(&self, task: &str) -> Result<PatchPlan> {
20        let context = ContextPackBuilder::new(self.store).build(task, 12)?;
21        Ok(PatchPlan {
22            id: PatchId::new(stable_id(task)),
23            task: task.into(),
24            allowed_files: context.recommended_change_boundary.allowed_files,
25            caution_files: context.recommended_change_boundary.caution_files,
26            forbidden_files: context.recommended_change_boundary.forbidden_files,
27            change_steps: vec![
28                "Inspect primary symbols and definitions from the context pack".into(),
29                "Constrain edits to allowed files unless evidence justifies expansion".into(),
30                "Run the recommended validation plan after approval".into(),
31            ],
32            risks: context.risk_report.reasons,
33            assumptions: vec![
34                "Generated and vendor files remain out of scope".into(),
35                "Patch application requires explicit write mode and approval".into(),
36            ],
37            tests: context.test_candidates,
38            rollback_notes: vec!["Revert the unified diff if validation fails".into()],
39            unified_diff: None,
40            requires_approval: self.config.security.approval_required,
41            evidence: context.evidence,
42        })
43    }
44
45    pub fn apply(&self, _patch: &PatchPlan, approved: bool) -> Result<()> {
46        PolicyGate::new(self.config).ensure_allowed(ActionKind::ApplyPatch)?;
47        if self.config.security.approval_required && !approved {
48            return Err(OkError::PolicyDenied(
49                "patch application requires explicit approval".into(),
50            ));
51        }
52        Err(OkError::Unsupported(
53            "patch application is intentionally not implemented without a diff applicator".into(),
54        ))
55    }
56}
57
58fn stable_id(value: &str) -> String {
59    let mut hasher = Sha256::new();
60    hasher.update(value.as_bytes());
61    format!("{:x}", hasher.finalize())
62}