use std::collections::HashMap;
use std::path::Path;
use std::process::ExitCode;
use anyhow::{anyhow, bail, Result};
use serde::Serialize;
use time::format_description::well_known::Rfc3339;
use time::{Duration, OffsetDateTime};
use crate::memory::entries::StructuredMemoryEntry;
use crate::memory::file::{remove_entry_blocks, render_entry_block, rewrite_entry_blocks};
use crate::memory::{governance, queue};
use crate::output::CommandReport;
use crate::paths::state::StateLayout;
use crate::profile;
use crate::repo::marker as repo_marker;
use crate::repo::registry as repo_registry;
use crate::state::protected_write::ExclusiveWriteOptions;
use crate::state::runtime::{
self as runtime_state, LoadedRuntimeMemorySurface, RuntimeMemoryEntry,
};
use crate::state::session;
use crate::timestamps;
const PROMPT_BYTE_PRESSURE_THRESHOLD: usize = 2048;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CompactScope {
Profile,
Repo,
Branch,
Clone,
}
impl CompactScope {
fn label(self) -> &'static str {
match self {
Self::Profile => "profile",
Self::Repo => "project",
Self::Branch => "work_stream",
Self::Clone => "workspace",
}
}
fn surface_scope(self) -> &'static str {
match self {
Self::Profile => "profile_memory",
Self::Repo => "project_memory",
Self::Branch => "work_stream_memory",
Self::Clone => "workspace_memory",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DecayClass {
Permanent,
Stable,
Active,
}
impl DecayClass {
pub fn as_str(self) -> &'static str {
match self {
Self::Permanent => "permanent",
Self::Stable => "stable",
Self::Active => "active",
}
}
fn durability_rank(self) -> u8 {
match self {
Self::Permanent => 3,
Self::Stable => 2,
Self::Active => 1,
}
}
fn review_floor_days(self) -> i64 {
match self {
Self::Active => 7,
Self::Stable => 30,
Self::Permanent => 90,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CompactReviewKind {
Expired,
Superseded,
PromotionCandidate,
}
impl CompactReviewKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Expired => "expired",
Self::Superseded => "superseded",
Self::PromotionCandidate => "promotion-candidate",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ReviewOutcome {
Surface,
Remove,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CompactMode<'a> {
Duplicate {
entry_id: &'a str,
keep_id: &'a str,
},
DecayClass {
entry_id: &'a str,
target: DecayClass,
},
Review {
kind: CompactReviewKind,
entry_id: Option<&'a str>,
outcome: ReviewOutcome,
},
}
pub struct CompactModeRequest<'a> {
pub entry_id: Option<&'a str>,
pub keep_id: Option<&'a str>,
pub decay_class: Option<DecayClass>,
pub review: Option<CompactReviewKind>,
pub remove: bool,
pub write: bool,
}
pub fn parse_mode(request: CompactModeRequest<'_>) -> Result<CompactMode<'_>> {
let mut selected_modes = 0;
if request.keep_id.is_some() {
selected_modes += 1;
}
if request.decay_class.is_some() {
selected_modes += 1;
}
if request.review.is_some() {
selected_modes += 1;
}
if selected_modes == 0 {
bail!(
"`ccd memory compact` requires one of `--keep <id>`, `--decay-class <permanent|stable|active>`, or `--review <expired|superseded|promotion-candidate>`"
);
}
if selected_modes > 1 {
bail!(
"`ccd memory compact` accepts exactly one of `--keep`, `--decay-class`, or `--review`"
);
}
if request.remove && request.review.is_none() {
bail!("`--remove` requires `--review <expired|superseded|promotion-candidate>`");
}
if request.write && request.review.is_some() && !request.remove {
bail!("`--write` is only valid with `--remove` when using `--review`");
}
match (request.keep_id, request.decay_class, request.review) {
(Some(keep_id), None, None) => Ok(CompactMode::Duplicate {
entry_id: request
.entry_id
.ok_or_else(|| anyhow!("`--entry <id>` is required with `--keep <id>`"))?,
keep_id,
}),
(None, Some(target), None) => Ok(CompactMode::DecayClass {
entry_id: request.entry_id.ok_or_else(|| {
anyhow!("`--entry <id>` is required with `--decay-class <permanent|stable|active>`")
})?,
target,
}),
(None, None, Some(kind)) => {
if request.remove && request.entry_id.is_none() {
bail!("`--entry <id>` is required when previewing or applying `--remove`");
}
Ok(CompactMode::Review {
kind,
entry_id: request.entry_id,
outcome: if request.remove {
ReviewOutcome::Remove
} else {
ReviewOutcome::Surface
},
})
}
_ => unreachable!("validated mutually-exclusive compact mode"),
}
}
#[derive(Serialize)]
pub struct MemoryCompactReport {
command: &'static str,
ok: bool,
path: String,
profile: String,
#[serde(skip_serializing_if = "Option::is_none")]
project_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
locality_id: Option<String>,
scope: &'static str,
mode: &'static str,
memory_file: MemorySurfaceView,
#[serde(skip_serializing_if = "Option::is_none")]
selected_entry: Option<StructuredMemoryEntry>,
#[serde(skip_serializing_if = "Option::is_none")]
keep_entry: Option<StructuredMemoryEntry>,
governance: governance::GovernanceDecisionView,
#[serde(skip_serializing_if = "Option::is_none")]
proposal: Option<CompactProposalView>,
#[serde(skip_serializing_if = "Option::is_none")]
review: Option<CompactReviewView>,
#[serde(skip_serializing_if = "Option::is_none")]
write_result: Option<WriteResultView>,
#[serde(skip_serializing_if = "Option::is_none")]
staged_write: Option<queue::StagedMemoryOpView>,
#[serde(skip_serializing_if = "Option::is_none")]
message: Option<String>,
warnings: Vec<String>,
}
#[derive(Serialize)]
struct MemorySurfaceView {
scope: &'static str,
path: String,
status: &'static str,
}
#[derive(Serialize)]
struct CompactProposalView {
action: &'static str,
match_rule: &'static str,
selected_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
keep_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
target_decay_class: Option<&'static str>,
diff_preview: String,
note: &'static str,
}
#[derive(Serialize)]
struct CompactReviewView {
review_type: &'static str,
ordering_rule: &'static str,
#[serde(skip_serializing_if = "Vec::is_empty")]
pressure_signals: Vec<PressureSignalView>,
candidates: Vec<ReviewCandidateView>,
note: &'static str,
}
#[derive(Serialize)]
struct PressureSignalView {
signal: &'static str,
detail: String,
}
#[derive(Clone, Serialize)]
struct ReviewCandidateView {
entry: StructuredMemoryEntry,
eligibility_reason: String,
#[serde(skip_serializing_if = "Option::is_none")]
age_basis: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
age_floor_days: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
eligible_at: Option<String>,
content_bytes: usize,
}
#[derive(Serialize)]
struct WriteResultView {
file_action: &'static str,
path: String,
#[serde(skip_serializing_if = "Option::is_none")]
written_keep_entry: Option<StructuredMemoryEntry>,
#[serde(skip_serializing_if = "Option::is_none")]
written_selected_entry: Option<StructuredMemoryEntry>,
#[serde(skip_serializing_if = "Option::is_none")]
removed_entry: Option<StructuredMemoryEntry>,
}
#[derive(Clone)]
struct ReviewCandidate {
entry: StructuredMemoryEntry,
view: ReviewCandidateView,
age_seconds: i64,
}
enum ReviewEligibility {
Eligible {
eligibility_reason: String,
age_basis: Option<String>,
age_floor_days: Option<i64>,
eligible_at: Option<String>,
age_seconds: i64,
},
Ineligible {
reason: String,
},
}
struct BuiltReview {
view: CompactReviewView,
candidates: Vec<ReviewCandidate>,
}
impl CommandReport for MemoryCompactReport {
fn exit_code(&self) -> ExitCode {
if self.ok {
ExitCode::SUCCESS
} else {
ExitCode::from(1)
}
}
fn render_text(&self) {
if !self.ok {
println!(
"{}",
self.message
.as_deref()
.unwrap_or("Queued memory compaction was rejected.")
);
return;
}
let scope_label = self.scope.replace('_', " ");
if let Some(review) = &self.review {
println!(
"Prepared memory compaction review for {} entr{} in {}.",
review.candidates.len(),
if review.candidates.len() == 1 {
"y"
} else {
"ies"
},
scope_label
);
println!(
"Memory file: {} ({})",
self.memory_file.path, self.memory_file.status
);
println!(
"Governance: {} ({})",
self.governance.action.as_str(),
self.governance.status.as_str()
);
println!("Review: {} ({})", review.review_type, review.ordering_rule);
for signal in &review.pressure_signals {
println!("Pressure: {} ({})", signal.signal, signal.detail);
}
if review.candidates.is_empty() {
println!("Eligible entries: none");
} else {
println!("Eligible entries:");
for candidate in &review.candidates {
println!("- {}: {}", candidate.entry.id, candidate.eligibility_reason);
}
}
for warning in &self.warnings {
println!("Warning: {warning}");
}
println!();
println!("{}", review.note);
return;
}
let selected_entry = self
.selected_entry
.as_ref()
.expect("proposal reports always include a selected entry");
match &self.write_result {
Some(write_result) if write_result.removed_entry.is_some() => println!(
"Applied memory compaction removal for entry {} in {}.",
selected_entry.id, self.scope
),
Some(_) => println!(
"Applied memory compaction for entry {} in {}.",
selected_entry.id, self.scope
),
None => println!(
"Prepared memory compaction preview for entry {} in {}.",
selected_entry.id, self.scope
),
}
println!(
"Memory file: {} ({})",
self.memory_file.path, self.memory_file.status
);
if let Some(keep_entry) = &self.keep_entry {
println!("Keep entry: {}", keep_entry.id);
println!("Selected duplicate: {}", selected_entry.id);
} else {
println!("Selected entry: {}", selected_entry.id);
if let Some(target_decay_class) = self
.proposal
.as_ref()
.and_then(|proposal| proposal.target_decay_class)
{
println!("Target decay class: {target_decay_class}");
}
}
let proposal = self.proposal.as_ref().expect("proposal report");
println!("Proposal: {} ({})", proposal.action, proposal.note);
println!(
"Governance: {} ({})",
self.governance.action.as_str(),
self.governance.status.as_str()
);
if let Some(write_result) = &self.write_result {
println!(
"Write: {} ({})",
write_result.file_action, write_result.path
);
}
if let Some(staged_write) = &self.staged_write {
println!("Queued op: {}", staged_write.op_id);
}
for warning in &self.warnings {
println!("Warning: {warning}");
}
println!();
println!("Diff preview:");
println!("{}", proposal.diff_preview);
println!();
println!("{}", proposal.note);
}
}
pub fn run(
repo_root: &Path,
explicit_profile: Option<&str>,
scope: CompactScope,
mode: CompactMode<'_>,
write: bool,
write_options: ExclusiveWriteOptions,
op_id_hint: Option<&str>,
) -> Result<MemoryCompactReport> {
let profile = profile::resolve(explicit_profile)?;
let layout = StateLayout::resolve(repo_root, profile.clone())?;
ensure_profile_exists(&layout)?;
let (locality_id, memory_file) = load_memory_surface(repo_root, &layout, scope)?;
let staged_request = if should_stage_via_queue(write, scope, &layout)? {
let request_key = CompactQueueRequestKey {
command: "memory-compact",
scope: scope.surface_scope(),
mode: compact_mode_key(&mode),
};
let request_fingerprint = queue::fingerprint(&request_key)?;
let op_id = queue::stable_op_id("compact", &request_fingerprint, op_id_hint);
Some(QueuedWriteRequest {
request_fingerprint,
op_id,
})
} else {
None
};
if !memory_file
.entries
.iter()
.any(|entry| entry.as_structured_entry().is_some())
{
bail!(
"no structured {}-memory entries are available at {}; add a `ccd-memory` block before running `ccd memory compact`",
scope.label(),
memory_file.source.path.display()
);
}
match mode {
CompactMode::Duplicate { entry_id, keep_id } => run_duplicate_flow(
repo_root,
&layout,
scope,
locality_id,
&memory_file,
entry_id,
keep_id,
write,
staged_request.as_ref(),
&write_options,
),
CompactMode::DecayClass { entry_id, target } => run_decay_class_flow(
repo_root,
&layout,
scope,
locality_id,
&memory_file,
entry_id,
target,
write,
staged_request.as_ref(),
&write_options,
),
CompactMode::Review {
kind,
entry_id,
outcome,
} => run_review_flow(
repo_root,
&layout,
scope,
locality_id,
&memory_file,
kind,
entry_id,
outcome,
write,
staged_request.as_ref(),
&write_options,
),
}
}
#[derive(Clone, Debug)]
struct QueuedWriteRequest {
request_fingerprint: String,
op_id: String,
}
#[derive(Serialize)]
struct CompactQueueRequestKey<'a> {
command: &'static str,
scope: &'static str,
mode: CompactModeKey<'a>,
}
#[derive(Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum CompactModeKey<'a> {
Duplicate {
entry_id: &'a str,
keep_id: &'a str,
},
DecayClass {
entry_id: &'a str,
target: &'static str,
},
Review {
review: &'static str,
entry_id: Option<&'a str>,
outcome: &'static str,
},
}
fn load_memory_surface(
repo_root: &Path,
layout: &StateLayout,
scope: CompactScope,
) -> Result<(Option<String>, LoadedRuntimeMemorySurface)> {
match scope {
CompactScope::Profile => Ok((None, runtime_state::load_profile_memory_surface(layout)?)),
CompactScope::Repo => {
let marker = repo_marker::load(repo_root)?.ok_or_else(|| {
anyhow!(
"repo is not linked: {} is missing; run `ccd attach --path {}` or `ccd link --path {}` first",
repo_root.join(repo_marker::MARKER_FILE).display(),
repo_root.display(),
repo_root.display()
)
})?;
let locality_id = marker.locality_id;
ensure_repo_registry_exists(layout, repo_root, &locality_id)?;
ensure_repo_overlay_exists(layout, &locality_id)?;
let file = runtime_state::load_locality_memory_surface(layout, &locality_id)?;
Ok((Some(locality_id), file))
}
CompactScope::Branch => {
let marker = repo_marker::load(repo_root)?.ok_or_else(|| {
anyhow!(
"repo is not linked: {} is missing; run `ccd attach --path {}` or `ccd link --path {}` first",
repo_root.join(repo_marker::MARKER_FILE).display(),
repo_root.display(),
repo_root.display()
)
})?;
let locality_id = marker.locality_id;
ensure_repo_registry_exists(layout, repo_root, &locality_id)?;
ensure_repo_overlay_exists(layout, &locality_id)?;
if runtime_state::resolve_active_branch_memory_path(repo_root, layout, &locality_id)?
.is_none()
{
bail!(
"work-stream memory is only available on active non-trunk named branches; switch to a feature branch before using `ccd memory compact --scope work-stream`"
);
}
let file = runtime_state::load_branch_memory_surface(repo_root, layout, &locality_id)?;
Ok((Some(locality_id), file))
}
CompactScope::Clone => {
let marker = repo_marker::load(repo_root)?.ok_or_else(|| {
anyhow!(
"repo is not linked: {} is missing; run `ccd attach --path {}` or `ccd link --path {}` first",
repo_root.join(repo_marker::MARKER_FILE).display(),
repo_root.display(),
repo_root.display()
)
})?;
let locality_id = marker.locality_id;
ensure_repo_registry_exists(layout, repo_root, &locality_id)?;
let file = runtime_state::load_clone_memory_surface(layout)?;
Ok((Some(locality_id), file))
}
}
}
#[allow(clippy::too_many_arguments)]
fn run_duplicate_flow(
repo_root: &Path,
layout: &StateLayout,
scope: CompactScope,
locality_id: Option<String>,
memory_file: &LoadedRuntimeMemorySurface,
entry_id: &str,
keep_id: &str,
write: bool,
staged_request: Option<&QueuedWriteRequest>,
write_options: &ExclusiveWriteOptions,
) -> Result<MemoryCompactReport> {
if entry_id == keep_id {
bail!("`--entry` and `--keep` must refer to different structured memory entries");
}
let (selected_runtime_entry, selected_entry) = load_selected_entry(entry_id, memory_file)?;
let (keep_runtime_entry, keep_entry) = load_selected_entry(keep_id, memory_file)?;
validate_duplicate_pair(&selected_entry, &keep_entry)?;
let selected_superseded_at = timestamps::current_utc_rfc3339()?;
let written_keep_runtime = updated_keep_entry(&keep_runtime_entry, &selected_entry.id)?;
let written_selected_runtime =
superseded_selected_entry(&selected_runtime_entry, selected_superseded_at);
let written_keep_entry = written_keep_runtime
.as_structured_entry()
.expect("updated keep entry");
let written_selected_entry = written_selected_runtime
.as_structured_entry()
.expect("superseded selected entry");
let autonomous_session_id = current_autonomous_session_id(layout)?;
let mut governance = governance::GovernanceDecisionView::new(
governance::GovernanceDecisionInit {
action: governance::GovernanceAction::AutoCompactDuplicate,
status: governance::GovernanceStatus::Allowed,
target_scope: scope.surface_scope().to_owned(),
source_scope: Some(scope.surface_scope().to_owned()),
entry_type: selected_entry.entry_type.clone(),
rationale: "exact duplicates may compact automatically when the match is deterministic and reversible".to_owned(),
evidence_summary: vec![
"duplicate pair matched on exact type and normalized content".to_owned(),
"the selected duplicate remains reversible through supersession metadata".to_owned(),
],
pressure_signals: vec![governance::signal(
"duplicate_match",
format!(
"selected entry `{}` matches keep entry `{}` by normalized content",
selected_entry.id, keep_entry.id
),
)],
duplicate_digest: Some(governance::normalized_duplicate_digest(&selected_entry)),
rate_limit: None,
},
);
let diff_preview = duplicate_diff_preview(
&keep_entry,
&written_keep_entry,
&selected_entry,
&written_selected_entry,
);
let next_contents = if write {
Some(rewrite_entry_blocks(
&memory_file.source.content,
&HashMap::from([
(
keep_entry.id.clone(),
render_entry_block(&written_keep_entry),
),
(
selected_entry.id.clone(),
render_entry_block(&written_selected_entry),
),
]),
)?)
} else {
None
};
let write_result = write.then(|| WriteResultView {
file_action: "rewrite_blocks",
path: memory_file.source.path.display().to_string(),
written_keep_entry: Some(written_keep_entry.clone()),
written_selected_entry: Some(written_selected_entry.clone()),
removed_entry: None,
});
let report = MemoryCompactReport {
command: "memory-compact",
ok: true,
path: repo_root.display().to_string(),
profile: layout.profile().to_string(),
project_id: locality_id.clone(),
locality_id: locality_id.clone(),
scope: scope.label(),
mode: if write {
"exact_duplicate_supersede_write"
} else {
"exact_duplicate_supersede_preview"
},
memory_file: MemorySurfaceView {
scope: scope.surface_scope(),
path: memory_file.source.path.display().to_string(),
status: memory_file.source.status.as_str(),
},
selected_entry: Some(selected_entry.clone()),
keep_entry: Some(keep_entry.clone()),
governance: governance.clone(),
proposal: Some(CompactProposalView {
action: "mark_selected_superseded",
match_rule: "exact_type_and_normalized_content",
selected_id: written_selected_entry.id.clone(),
keep_id: Some(keep_id.to_owned()),
target_decay_class: None,
diff_preview,
note: if write {
"Applied with explicit approval. The selected duplicate was marked superseded and the kept entry now records the supersession link."
} else {
"Preview only. No files were changed. Rerun with `--write` to apply this duplicate compaction."
},
}),
review: None,
write_result,
staged_write: None,
message: None,
warnings: Vec::new(),
};
let mut report = report;
if write {
let mutation_id = staged_request
.map(|request| request.op_id.as_str())
.unwrap_or("memory-compact-direct");
governance = governance.with_rollback(
mutation_id,
write_options.actor_id.as_deref(),
write_options
.session_id
.as_deref()
.or(autonomous_session_id.as_deref()),
vec![keep_entry.id.clone(), selected_entry.id.clone()],
&memory_file.source.content,
None,
);
report.governance = governance.clone();
}
finalize_compact_report(
repo_root,
layout,
report,
write.then(|| queue::MemoryOpPlan {
target: queue::MemoryFileMutation {
scope: scope.surface_scope().to_owned(),
path: memory_file.source.path.display().to_string(),
next_contents: next_contents.expect("write checked"),
},
source: None,
refresh_locality_id: locality_id.clone(),
authored_entry_ids: vec![keep_entry.id.clone(), selected_entry.id.clone()],
governance: Some(governance),
}),
staged_request,
write_options,
)
}
#[allow(clippy::too_many_arguments)]
fn run_decay_class_flow(
repo_root: &Path,
layout: &StateLayout,
scope: CompactScope,
locality_id: Option<String>,
memory_file: &LoadedRuntimeMemorySurface,
entry_id: &str,
target: DecayClass,
write: bool,
staged_request: Option<&QueuedWriteRequest>,
write_options: &ExclusiveWriteOptions,
) -> Result<MemoryCompactReport> {
let (selected_runtime_entry, selected_entry) = load_selected_entry(entry_id, memory_file)?;
validate_decay_class_change(&selected_entry, target)?;
let written_selected_runtime =
selected_runtime_entry.with_decay_class(Some(target.as_str().to_owned()));
let written_selected_entry = written_selected_runtime
.as_structured_entry()
.expect("decay-class updated entry");
let autonomous_session_id = current_autonomous_session_id(layout)?;
let mut governance =
governance::GovernanceDecisionView::new(governance::GovernanceDecisionInit {
action: governance::GovernanceAction::StageNarrowing,
status: governance::GovernanceStatus::Allowed,
target_scope: scope.surface_scope().to_owned(),
source_scope: Some(scope.surface_scope().to_owned()),
entry_type: selected_entry.entry_type.clone(),
rationale:
"durability downgrades are governed narrowing actions rather than silent deletion"
.to_owned(),
evidence_summary: vec![
format!(
"requested decay class `{}` narrows or backfills durability",
target.as_str()
),
"the original entry remains present and reversible".to_owned(),
],
pressure_signals: Vec::new(),
duplicate_digest: Some(governance::normalized_duplicate_digest(&selected_entry)),
rate_limit: None,
});
let diff_preview = single_entry_diff_preview(&selected_entry, &written_selected_entry);
let backfill = selected_entry.decay_class.is_none();
let next_contents = if write {
Some(rewrite_entry_blocks(
&memory_file.source.content,
&HashMap::from([(
selected_entry.id.clone(),
render_entry_block(&written_selected_entry),
)]),
)?)
} else {
None
};
let write_result = write.then(|| WriteResultView {
file_action: "rewrite_blocks",
path: memory_file.source.path.display().to_string(),
written_keep_entry: None,
written_selected_entry: Some(written_selected_entry.clone()),
removed_entry: None,
});
let report = MemoryCompactReport {
command: "memory-compact",
ok: true,
path: repo_root.display().to_string(),
profile: layout.profile().to_string(),
project_id: locality_id.clone(),
locality_id: locality_id.clone(),
scope: scope.label(),
mode: match (backfill, write) {
(true, false) => "decay_class_backfill_preview",
(true, true) => "decay_class_backfill_write",
(false, false) => "decay_class_downgrade_preview",
(false, true) => "decay_class_downgrade_write",
},
memory_file: MemorySurfaceView {
scope: scope.surface_scope(),
path: memory_file.source.path.display().to_string(),
status: memory_file.source.status.as_str(),
},
selected_entry: Some(selected_entry.clone()),
keep_entry: None,
governance: governance.clone(),
proposal: Some(CompactProposalView {
action: "set_decay_class",
match_rule: if backfill {
"explicit_decay_class_backfill"
} else {
"explicit_lower_decay_class"
},
selected_id: written_selected_entry.id.clone(),
keep_id: None,
target_decay_class: Some(target.as_str()),
diff_preview,
note: if write {
"Applied with explicit approval. The selected entry now carries the requested lower durability band."
} else {
"Preview only. No files were changed. Rerun with `--write` to apply this lifecycle compaction."
},
}),
review: None,
write_result,
staged_write: None,
message: None,
warnings: Vec::new(),
};
let mut report = report;
if write {
let mutation_id = staged_request
.map(|request| request.op_id.as_str())
.unwrap_or("memory-compact-direct");
governance = governance.with_rollback(
mutation_id,
write_options.actor_id.as_deref(),
write_options
.session_id
.as_deref()
.or(autonomous_session_id.as_deref()),
vec![selected_entry.id.clone()],
&memory_file.source.content,
None,
);
report.governance = governance.clone();
}
finalize_compact_report(
repo_root,
layout,
report,
write.then(|| queue::MemoryOpPlan {
target: queue::MemoryFileMutation {
scope: scope.surface_scope().to_owned(),
path: memory_file.source.path.display().to_string(),
next_contents: next_contents.expect("write checked"),
},
source: None,
refresh_locality_id: locality_id.clone(),
authored_entry_ids: vec![selected_entry.id.clone()],
governance: Some(governance),
}),
staged_request,
write_options,
)
}
#[allow(clippy::too_many_arguments)]
fn run_review_flow(
repo_root: &Path,
layout: &StateLayout,
scope: CompactScope,
locality_id: Option<String>,
memory_file: &LoadedRuntimeMemorySurface,
kind: CompactReviewKind,
entry_id: Option<&str>,
outcome: ReviewOutcome,
write: bool,
staged_request: Option<&QueuedWriteRequest>,
write_options: &ExclusiveWriteOptions,
) -> Result<MemoryCompactReport> {
let now = OffsetDateTime::now_utc();
let built_review = build_review(memory_file, kind, now, entry_id)?;
let governance_pressure_signals = built_review
.view
.pressure_signals
.iter()
.map(|signal| governance::signal(signal.signal, signal.detail.clone()))
.collect::<Vec<_>>();
match outcome {
ReviewOutcome::Surface => Ok(MemoryCompactReport {
command: "memory-compact",
ok: true,
path: repo_root.display().to_string(),
profile: layout.profile().to_string(),
project_id: locality_id.clone(),
locality_id,
scope: scope.label(),
mode: review_surface_mode(kind),
memory_file: MemorySurfaceView {
scope: scope.surface_scope(),
path: memory_file.source.path.display().to_string(),
status: memory_file.source.status.as_str(),
},
selected_entry: None,
keep_entry: None,
governance: governance::GovernanceDecisionView::new(
governance::GovernanceDecisionInit {
action: governance::GovernanceAction::StageEvictionReview,
status: governance::GovernanceStatus::Allowed,
target_scope: scope.surface_scope().to_owned(),
source_scope: Some(scope.surface_scope().to_owned()),
entry_type: "review".to_owned(),
rationale:
"broad removal candidates stay review-only until an explicit removal is chosen"
.to_owned(),
evidence_summary: vec![format!("review kind `{}` was evaluated", kind.as_str())],
pressure_signals: governance_pressure_signals,
duplicate_digest: None,
rate_limit: None,
},
),
proposal: None,
review: Some(built_review.view),
write_result: None,
staged_write: None,
message: None,
warnings: Vec::new(),
}),
ReviewOutcome::Remove => {
let selected_candidate = built_review
.candidates
.first()
.cloned()
.expect("remove preview requires a selected eligible candidate");
let diff_preview = removal_diff_preview(&selected_candidate.entry);
let next_contents = if write {
Some(remove_entry_blocks(
&memory_file.source.content,
std::slice::from_ref(&selected_candidate.entry.id),
)?)
} else {
None
};
let write_result = write.then(|| WriteResultView {
file_action: "remove_block",
path: memory_file.source.path.display().to_string(),
written_keep_entry: None,
written_selected_entry: None,
removed_entry: Some(selected_candidate.entry.clone()),
});
let autonomous_session_id = current_autonomous_session_id(layout)?;
let mut governance = governance::GovernanceDecisionView::new(
governance::GovernanceDecisionInit {
action: governance::GovernanceAction::StageEvictionReview,
status: governance::GovernanceStatus::Allowed,
target_scope: scope.surface_scope().to_owned(),
source_scope: Some(scope.surface_scope().to_owned()),
entry_type: selected_candidate.entry.entry_type.clone(),
rationale:
"reviewed removals stay governed eviction actions rather than silent deletion"
.to_owned(),
evidence_summary: vec![format!(
"review kind `{}` selected entry `{}`",
kind.as_str(),
selected_candidate.entry.id
)],
pressure_signals: governance_pressure_signals.clone(),
duplicate_digest: Some(governance::normalized_duplicate_digest(
&selected_candidate.entry,
)),
rate_limit: None,
},
);
let report = MemoryCompactReport {
command: "memory-compact",
ok: true,
path: repo_root.display().to_string(),
profile: layout.profile().to_string(),
project_id: locality_id.clone(),
locality_id: locality_id.clone(),
scope: scope.label(),
mode: review_remove_mode(kind, write),
memory_file: MemorySurfaceView {
scope: scope.surface_scope(),
path: memory_file.source.path.display().to_string(),
status: memory_file.source.status.as_str(),
},
selected_entry: Some(selected_candidate.entry.clone()),
keep_entry: None,
governance: governance.clone(),
proposal: Some(CompactProposalView {
action: "remove_entry",
match_rule: review_remove_match_rule(kind),
selected_id: selected_candidate.entry.id.clone(),
keep_id: None,
target_decay_class: None,
diff_preview,
note: if write {
"Applied with explicit approval. The selected stale entry was removed from the authored memory file."
} else {
"Preview only. No files were changed. Rerun with `--write` to apply this reviewed removal."
},
}),
review: None,
write_result,
staged_write: None,
message: None,
warnings: Vec::new(),
};
let mut report = report;
if write {
let mutation_id = staged_request
.map(|request| request.op_id.as_str())
.unwrap_or("memory-compact-direct");
governance = governance.with_rollback(
mutation_id,
write_options.actor_id.as_deref(),
write_options
.session_id
.as_deref()
.or(autonomous_session_id.as_deref()),
vec![selected_candidate.entry.id.clone()],
&memory_file.source.content,
None,
);
report.governance = governance.clone();
}
finalize_compact_report(
repo_root,
layout,
report,
write.then(|| queue::MemoryOpPlan {
target: queue::MemoryFileMutation {
scope: scope.surface_scope().to_owned(),
path: memory_file.source.path.display().to_string(),
next_contents: next_contents.expect("write checked"),
},
source: None,
refresh_locality_id: locality_id.clone(),
authored_entry_ids: vec![selected_candidate.entry.id.clone()],
governance: Some(governance),
}),
staged_request,
write_options,
)
}
}
}
fn build_review(
memory_file: &LoadedRuntimeMemorySurface,
kind: CompactReviewKind,
now: OffsetDateTime,
targeted_entry_id: Option<&str>,
) -> Result<BuiltReview> {
let mut candidates = memory_file
.entries
.iter()
.filter_map(|entry| {
entry
.as_structured_entry()
.map(|structured| candidate_for_review(entry.clone(), structured, kind, now))
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.flatten()
.collect::<Vec<_>>();
let prompt_pressure_active =
memory_file.source.content.len() >= PROMPT_BYTE_PRESSURE_THRESHOLD && candidates.len() > 1;
sort_review_candidates(kind, prompt_pressure_active, &mut candidates);
if let Some(entry_id) = targeted_entry_id {
if let Some(candidate) = candidates
.iter()
.find(|candidate| candidate.entry.id == entry_id)
.cloned()
{
candidates = vec![candidate];
} else {
let (_, selected_entry) = load_selected_entry(entry_id, memory_file)?;
match review_eligibility(&selected_entry, kind, now)? {
ReviewEligibility::Eligible { .. } => unreachable!("eligible candidate must exist"),
ReviewEligibility::Ineligible { reason } => bail!(
"entry `{}` is not eligible for {} review: {}",
entry_id,
kind.as_str(),
reason
),
}
}
}
let pressure_signals = review_pressure_signals(memory_file, prompt_pressure_active);
let ordering_rule = review_ordering_rule(kind, prompt_pressure_active);
let note = review_note(kind);
let view = CompactReviewView {
review_type: kind.as_str(),
ordering_rule,
pressure_signals,
candidates: candidates
.iter()
.map(|candidate| candidate.view.clone())
.collect::<Vec<_>>(),
note,
};
Ok(BuiltReview { view, candidates })
}
fn candidate_for_review(
runtime_entry: RuntimeMemoryEntry,
entry: StructuredMemoryEntry,
kind: CompactReviewKind,
now: OffsetDateTime,
) -> Result<Option<ReviewCandidate>> {
let _ = runtime_entry;
match review_eligibility(&entry, kind, now)? {
ReviewEligibility::Eligible {
eligibility_reason,
age_basis,
age_floor_days,
eligible_at,
age_seconds,
} => Ok(Some(ReviewCandidate {
view: ReviewCandidateView {
entry: entry.clone(),
eligibility_reason,
age_basis,
age_floor_days,
eligible_at,
content_bytes: entry.content.len(),
},
entry,
age_seconds,
})),
ReviewEligibility::Ineligible { .. } => Ok(None),
}
}
fn review_eligibility(
entry: &StructuredMemoryEntry,
kind: CompactReviewKind,
now: OffsetDateTime,
) -> Result<ReviewEligibility> {
match kind {
CompactReviewKind::Expired => {
let Some(expires_at) = entry.expires_at.as_deref() else {
return Ok(ReviewEligibility::Ineligible {
reason: "missing `expires_at`".to_owned(),
});
};
let expires_at = parse_entry_timestamp(entry, "expires_at", expires_at)?;
let age_seconds = (now - expires_at).whole_seconds();
if expires_at > now {
let expires_at = expires_at.format(&Rfc3339)?;
return Ok(ReviewEligibility::Ineligible {
reason: format!("`expires_at` {expires_at} has not passed yet"),
});
}
let expires_at = expires_at.format(&Rfc3339)?;
Ok(ReviewEligibility::Eligible {
eligibility_reason: format!("`expires_at` {expires_at} is in the past"),
age_basis: Some(expires_at.clone()),
age_floor_days: None,
eligible_at: Some(expires_at),
age_seconds,
})
}
CompactReviewKind::Superseded => {
if entry.state != "superseded" {
return Ok(ReviewEligibility::Ineligible {
reason: format!("state is `{}`, not `superseded`", entry.state),
});
}
let Some(superseded_at) = entry.superseded_at.as_deref() else {
return Ok(ReviewEligibility::Ineligible {
reason: "missing `superseded_at`".to_owned(),
});
};
let Some(decay_class) = entry.decay_class.as_deref() else {
return Ok(ReviewEligibility::Ineligible {
reason: "missing `decay_class`".to_owned(),
});
};
let decay_class = parse_decay_class(decay_class)?;
let superseded_at = parse_entry_timestamp(entry, "superseded_at", superseded_at)?;
let floor_days = decay_class.review_floor_days();
let eligible_at = superseded_at + Duration::days(floor_days);
if eligible_at > now {
let eligible_at = eligible_at.format(&Rfc3339)?;
return Ok(ReviewEligibility::Ineligible {
reason: format!(
"entry becomes eligible on {} after the {}-day `{}` floor",
eligible_at,
floor_days,
decay_class.as_str()
),
});
}
let superseded_at_text = superseded_at.format(&Rfc3339)?;
let eligible_at_text = eligible_at.format(&Rfc3339)?;
Ok(ReviewEligibility::Eligible {
eligibility_reason: format!(
"`superseded_at` {} passed the {}-day `{}` review floor",
superseded_at_text,
floor_days,
decay_class.as_str()
),
age_basis: Some(superseded_at_text),
age_floor_days: Some(floor_days),
eligible_at: Some(eligible_at_text),
age_seconds: (now - superseded_at).whole_seconds(),
})
}
CompactReviewKind::PromotionCandidate => {
if entry.state != "promotion_candidate" {
return Ok(ReviewEligibility::Ineligible {
reason: format!("state is `{}`, not `promotion_candidate`", entry.state),
});
}
let Some(expires_at) = entry.expires_at.as_deref() else {
return Ok(ReviewEligibility::Ineligible {
reason: "missing `expires_at`".to_owned(),
});
};
let expires_at = parse_entry_timestamp(entry, "expires_at", expires_at)?;
let age_seconds = (now - expires_at).whole_seconds();
if expires_at > now {
let expires_at = expires_at.format(&Rfc3339)?;
return Ok(ReviewEligibility::Ineligible {
reason: format!("`expires_at` {expires_at} has not passed yet"),
});
}
let expires_at = expires_at.format(&Rfc3339)?;
Ok(ReviewEligibility::Eligible {
eligibility_reason: format!("`promotion_candidate` entry expired at {expires_at}"),
age_basis: Some(expires_at.clone()),
age_floor_days: None,
eligible_at: Some(expires_at),
age_seconds,
})
}
}
}
fn parse_entry_timestamp(
entry: &StructuredMemoryEntry,
field: &str,
value: &str,
) -> Result<OffsetDateTime> {
OffsetDateTime::parse(value, &Rfc3339).map_err(|error| {
anyhow!(
"entry `{}` has invalid `{}` timestamp `{}`: {}",
entry.id,
field,
value,
error
)
})
}
fn sort_review_candidates(
kind: CompactReviewKind,
prompt_pressure_active: bool,
candidates: &mut [ReviewCandidate],
) {
candidates.sort_by(|left, right| {
if prompt_pressure_active {
right
.view
.content_bytes
.cmp(&left.view.content_bytes)
.then_with(|| right.age_seconds.cmp(&left.age_seconds))
.then_with(|| left.entry.id.cmp(&right.entry.id))
} else {
right
.age_seconds
.cmp(&left.age_seconds)
.then_with(|| {
if matches!(kind, CompactReviewKind::Superseded) {
right.view.content_bytes.cmp(&left.view.content_bytes)
} else {
left.view.content_bytes.cmp(&right.view.content_bytes)
}
})
.then_with(|| left.entry.id.cmp(&right.entry.id))
}
});
}
fn review_pressure_signals(
memory_file: &LoadedRuntimeMemorySurface,
prompt_pressure_active: bool,
) -> Vec<PressureSignalView> {
if !prompt_pressure_active {
return Vec::new();
}
vec![PressureSignalView {
signal: "prompt_byte_pressure",
detail: format!(
"memory file has {} bytes; larger eligible entries are surfaced first",
memory_file.source.content.len()
),
}]
}
fn review_ordering_rule(kind: CompactReviewKind, prompt_pressure_active: bool) -> &'static str {
match (kind, prompt_pressure_active) {
(CompactReviewKind::Expired, true) => "prompt_byte_pressure_then_expiry_age",
(CompactReviewKind::Expired, false) => "expiry_age",
(CompactReviewKind::Superseded, true) => "prompt_byte_pressure_then_superseded_age",
(CompactReviewKind::Superseded, false) => "superseded_age",
(CompactReviewKind::PromotionCandidate, true) => {
"prompt_byte_pressure_then_promotion_candidate_age"
}
(CompactReviewKind::PromotionCandidate, false) => "promotion_candidate_age",
}
}
fn review_note(kind: CompactReviewKind) -> &'static str {
match kind {
CompactReviewKind::Expired => {
"Preview only. No files were changed. Re-run with `--review expired --entry <id> --remove` to preview or apply an explicit removal."
}
CompactReviewKind::Superseded => {
"Preview only. No files were changed. Re-run with `--review superseded --entry <id> --remove` to preview or apply an explicit removal."
}
CompactReviewKind::PromotionCandidate => {
"Preview only. No files were changed. Re-run with `--review promotion-candidate --entry <id> --remove` to preview or apply an explicit stale-candidate removal."
}
}
}
fn review_surface_mode(kind: CompactReviewKind) -> &'static str {
match kind {
CompactReviewKind::Expired => "expired_review",
CompactReviewKind::Superseded => "superseded_review",
CompactReviewKind::PromotionCandidate => "promotion_candidate_review",
}
}
fn review_remove_mode(kind: CompactReviewKind, write: bool) -> &'static str {
match (kind, write) {
(CompactReviewKind::Expired, false) => "expired_remove_preview",
(CompactReviewKind::Expired, true) => "expired_remove_write",
(CompactReviewKind::Superseded, false) => "superseded_remove_preview",
(CompactReviewKind::Superseded, true) => "superseded_remove_write",
(CompactReviewKind::PromotionCandidate, false) => "promotion_candidate_remove_preview",
(CompactReviewKind::PromotionCandidate, true) => "promotion_candidate_remove_write",
}
}
fn review_remove_match_rule(kind: CompactReviewKind) -> &'static str {
match kind {
CompactReviewKind::Expired => "eligible_expired_review_remove",
CompactReviewKind::Superseded => "eligible_superseded_review_remove",
CompactReviewKind::PromotionCandidate => "eligible_promotion_candidate_review_remove",
}
}
fn load_selected_entry(
entry_id: &str,
memory_file: &LoadedRuntimeMemorySurface,
) -> Result<(RuntimeMemoryEntry, StructuredMemoryEntry)> {
let runtime_entry = memory_file
.entries
.iter()
.find(|entry| entry.structured_id() == Some(entry_id))
.cloned()
.ok_or_else(|| missing_entry_error(entry_id, memory_file))?;
let structured_entry = runtime_entry
.as_structured_entry()
.expect("selected structured memory entry");
Ok((runtime_entry, structured_entry))
}
fn validate_duplicate_pair(
selected_entry: &StructuredMemoryEntry,
keep_entry: &StructuredMemoryEntry,
) -> Result<()> {
if selected_entry.state == "superseded" {
bail!(
"entry `{}` is already superseded; the first `ccd memory compact` slice only handles live duplicates",
selected_entry.id
);
}
if keep_entry.state == "superseded" {
bail!(
"keep entry `{}` is already superseded; choose a non-superseded canonical entry",
keep_entry.id
);
}
if keep_entry
.supersedes
.iter()
.any(|id| id == &selected_entry.id)
{
bail!(
"keep entry `{}` already records `{}` in `supersedes`; refusing to preview a no-op compaction",
keep_entry.id,
selected_entry.id
);
}
if duplicate_key(selected_entry) != duplicate_key(keep_entry) {
bail!(
"entries `{}` and `{}` are not exact duplicates by normalized content and type",
selected_entry.id,
keep_entry.id
);
}
Ok(())
}
fn updated_keep_entry(
keep_entry: &RuntimeMemoryEntry,
superseded_id: &str,
) -> Result<RuntimeMemoryEntry> {
let mut supersedes = keep_entry.supersedes.clone();
if supersedes.iter().any(|id| id == superseded_id) {
bail!(
"keep entry `{}` already records `{}` in `supersedes`",
keep_entry.structured_id().unwrap_or("<unknown>"),
superseded_id
);
}
supersedes.push(superseded_id.to_owned());
Ok(keep_entry.with_supersedes(supersedes))
}
fn superseded_selected_entry(
selected_entry: &RuntimeMemoryEntry,
superseded_at: String,
) -> RuntimeMemoryEntry {
selected_entry
.with_structured_state("superseded")
.with_superseded_at(Some(superseded_at))
}
fn duplicate_key(entry: &StructuredMemoryEntry) -> (String, String) {
(
entry.entry_type.clone(),
entry
.content
.split_whitespace()
.collect::<Vec<_>>()
.join(" "),
)
}
fn duplicate_diff_preview(
keep_entry: &StructuredMemoryEntry,
written_keep_entry: &StructuredMemoryEntry,
selected_entry: &StructuredMemoryEntry,
written_selected_entry: &StructuredMemoryEntry,
) -> String {
[
format!("@@ keep {}", keep_entry.id),
signed_block('-', &render_entry_block(keep_entry)),
signed_block('+', &render_entry_block(written_keep_entry)),
format!("@@ duplicate {}", selected_entry.id),
signed_block('-', &render_entry_block(selected_entry)),
signed_block('+', &render_entry_block(written_selected_entry)),
]
.join("\n")
}
fn single_entry_diff_preview(
selected_entry: &StructuredMemoryEntry,
written_selected_entry: &StructuredMemoryEntry,
) -> String {
[
format!("@@ entry {}", selected_entry.id),
signed_block('-', &render_entry_block(selected_entry)),
signed_block('+', &render_entry_block(written_selected_entry)),
]
.join("\n")
}
fn removal_diff_preview(selected_entry: &StructuredMemoryEntry) -> String {
[
format!("@@ remove {}", selected_entry.id),
signed_block('-', &render_entry_block(selected_entry)),
]
.join("\n")
}
fn signed_block(sign: char, block: &str) -> String {
block
.lines()
.map(|line| format!("{sign}{line}"))
.collect::<Vec<_>>()
.join("\n")
}
fn missing_entry_error(entry_id: &str, memory_file: &LoadedRuntimeMemorySurface) -> anyhow::Error {
let available_ids = memory_file
.entries
.iter()
.filter_map(RuntimeMemoryEntry::structured_id)
.collect::<Vec<_>>()
.join(", ");
anyhow!(
"structured memory entry `{}` was not found in {}; available ids: {}",
entry_id,
memory_file.source.path.display(),
available_ids
)
}
fn validate_decay_class_change(
selected_entry: &StructuredMemoryEntry,
target: DecayClass,
) -> Result<()> {
let Some(current) = selected_entry.decay_class.as_deref() else {
return Ok(());
};
let current = parse_decay_class(current)?;
if current == target {
bail!(
"entry `{}` already has decay_class `{}`; refusing to preview a no-op lifecycle compaction",
selected_entry.id,
target.as_str()
);
}
if target.durability_rank() > current.durability_rank() {
bail!(
"entry `{}` has decay_class `{}`; `ccd memory compact --decay-class` only supports explicit downgrades or backfills, not upgrades",
selected_entry.id,
current.as_str()
);
}
Ok(())
}
fn parse_decay_class(value: &str) -> Result<DecayClass> {
match value {
"permanent" => Ok(DecayClass::Permanent),
"stable" => Ok(DecayClass::Stable),
"active" => Ok(DecayClass::Active),
other => bail!(
"entry has unsupported decay_class `{other}`; expected permanent, stable, or active"
),
}
}
fn should_stage_via_queue(write: bool, scope: CompactScope, layout: &StateLayout) -> Result<bool> {
if !write {
return Ok(false);
}
let _ = scope;
queue::queue_required_for_autonomous(layout)
}
fn compact_mode_key<'a>(mode: &'a CompactMode<'a>) -> CompactModeKey<'a> {
match *mode {
CompactMode::Duplicate { entry_id, keep_id } => {
CompactModeKey::Duplicate { entry_id, keep_id }
}
CompactMode::DecayClass { entry_id, target } => CompactModeKey::DecayClass {
entry_id,
target: target.as_str(),
},
CompactMode::Review {
kind,
entry_id,
outcome,
} => CompactModeKey::Review {
review: kind.as_str(),
entry_id,
outcome: match outcome {
ReviewOutcome::Surface => "surface",
ReviewOutcome::Remove => "remove",
},
},
}
}
pub(crate) fn replay_request<'a>(
scope: CompactScope,
mode: &'a CompactMode<'a>,
op_id_hint: Option<&str>,
) -> Result<(String, String)> {
let request_key = CompactQueueRequestKey {
command: "memory-compact",
scope: scope.surface_scope(),
mode: compact_mode_key(mode),
};
let request_fingerprint = queue::fingerprint(&request_key)?;
let op_id = queue::stable_op_id("compact", &request_fingerprint, op_id_hint);
Ok((request_fingerprint, op_id))
}
fn finalize_compact_report(
repo_root: &Path,
layout: &StateLayout,
mut report: MemoryCompactReport,
plan: Option<queue::MemoryOpPlan>,
staged_request: Option<&QueuedWriteRequest>,
write_options: &ExclusiveWriteOptions,
) -> Result<MemoryCompactReport> {
let Some(plan) = plan else {
return Ok(report);
};
if let Some(staged_request) = staged_request {
let staged_write = queue::stage_and_reconcile(
repo_root,
layout,
"memory-compact",
&staged_request.op_id,
&staged_request.request_fingerprint,
write_options,
&plan,
)?;
let staged_ok = matches!(
staged_write.outcome,
crate::state::protected_write::AppendWriteOutcome::Applied
| crate::state::protected_write::AppendWriteOutcome::IdempotentNoop
);
if !staged_ok {
report.ok = false;
report.write_result = None;
report.message = staged_write.message.clone();
}
report.staged_write = Some(staged_write);
if report.ok {
queue::persist_report_snapshot(layout, &staged_request.op_id, &report)?;
}
return Ok(report);
}
let outcome = queue::apply_direct_plan(repo_root, layout, &plan)?;
if outcome == crate::state::protected_write::AppendWriteOutcome::IdempotentNoop {
if let Some(write_result) = report.write_result.as_mut() {
write_result.file_action = "already_applied";
}
}
Ok(report)
}
fn current_autonomous_session_id(layout: &StateLayout) -> Result<Option<String>> {
if queue::queue_required_for_autonomous(layout)? {
session::load_session_id(layout)
} else {
Ok(None)
}
}
fn ensure_profile_exists(layout: &StateLayout) -> Result<()> {
let profile_root = layout.profile_root();
if profile_root.is_dir() {
return Ok(());
}
bail!(
"profile `{}` does not exist at {}; bootstrap it with `ccd attach` before using `ccd memory compact`",
layout.profile(),
profile_root.display()
)
}
fn ensure_repo_registry_exists(
layout: &StateLayout,
repo_root: &Path,
locality_id: &str,
) -> Result<()> {
let registry_path = layout.repo_metadata_path(locality_id)?;
match repo_registry::load(®istry_path)? {
Some(_) => Ok(()),
None => bail!(
"repo registry is missing for locality_id `{}` at {}; run `ccd link --path {}` or `ccd attach --path {}` again",
locality_id,
registry_path.display(),
repo_root.display(),
repo_root.display()
),
}
}
fn ensure_repo_overlay_exists(layout: &StateLayout, locality_id: &str) -> Result<()> {
let overlay_root = layout.repo_overlay_root(locality_id)?;
if overlay_root.is_dir() {
return Ok(());
}
bail!(
"repo overlay is missing for locality_id `{}` at {}; run `ccd link --path .` or `ccd attach --path .` again",
locality_id,
overlay_root.display()
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_mode_requires_exactly_one_selector() {
let error = parse_mode(CompactModeRequest {
entry_id: Some("entry-1"),
keep_id: Some("keep-1"),
decay_class: None,
review: Some(CompactReviewKind::Expired),
remove: false,
write: false,
})
.expect_err("multiple selectors should fail");
assert!(error.to_string().contains("exactly one"));
}
#[test]
fn parse_mode_builds_review_remove_variants() {
let mode = parse_mode(CompactModeRequest {
entry_id: Some("entry-2"),
keep_id: None,
decay_class: None,
review: Some(CompactReviewKind::Superseded),
remove: true,
write: false,
})
.expect("review mode should parse");
match mode {
CompactMode::Review {
kind,
entry_id,
outcome,
} => {
assert_eq!(kind, CompactReviewKind::Superseded);
assert_eq!(entry_id, Some("entry-2"));
assert_eq!(outcome, ReviewOutcome::Remove);
}
_ => panic!("expected review mode"),
}
}
}