refactor: isolate Feature System input migration and compaction policy

This commit is contained in:
2026-10-20 03:21:22 +09:00
parent bb5c50f9ab
commit 6e729ed06c
27 changed files with 2226 additions and 646 deletions
+137 -43
View File
@@ -2220,10 +2220,48 @@ fn summary_entry(
origin: session_store::LoggedSessionHistoryOrigin,
) -> session_store::LoggedHistoryEntry {
let mut entry = annotated(item);
entry.metadata.system_input = matches!(
&entry.item,
session_store::LoggedItem::Message {
role: session_store::LoggedRole::System,
..
}
)
.then(|| {
session_store::SystemInputMetadata::new(
session_store::SystemInputUsePolicy {
recording_material: session_store::InputEvidenceUse::Exclude,
memory_evidence: session_store::InputEvidenceUse::Exclude,
observation: session_store::InputObservationUse::Hidden,
compaction: match origin {
session_store::LoggedSessionHistoryOrigin::CompactionSummary => {
session_store::InputCompactionUse::OpeningSummary
}
session_store::LoggedSessionHistoryOrigin::JournalContext => {
session_store::InputCompactionUse::ReferenceInput
}
_ => session_store::InputCompactionUse::Retain,
},
},
None,
None,
)
.unwrap()
});
entry.metadata.origin = origin;
entry
}
fn compaction_use(
metadata: &session_store::LoggedSessionHistoryMetadata,
) -> session_store::InputCompactionUse {
metadata
.system_input
.as_ref()
.map(|input| input.usage().compaction)
.unwrap_or(session_store::InputCompactionUse::Retain)
}
fn long_summary(label: &str) -> String {
format!(
"## Completed Tasks\n{label}: investigated the transport boundary.\n\n\
@@ -2359,7 +2397,8 @@ async fn full_initial_summary_survives_small_overview_budgets_and_both_retention
assert_eq!(
replacement
.iter()
.filter(|entry| entry.metadata.origin == CompactionSummary)
.filter(|entry| compaction_use(&entry.metadata)
== session_store::InputCompactionUse::OpeningSummary)
.count(),
1
);
@@ -2604,7 +2643,9 @@ async fn three_chained_compactions_transport_each_complete_predecessor_without_r
let seed = replacement_seed(&fixture.worker);
let inherited = seed
.iter()
.filter(|entry| entry.metadata.origin == CompactionSummary)
.filter(|entry| {
compaction_use(&entry.metadata) == session_store::InputCompactionUse::OpeningSummary
})
.collect::<Vec<_>>();
assert_eq!(inherited.len(), 1);
assert_eq!(
@@ -2892,7 +2933,7 @@ impl worker::subjektiv::SubjektivHost for CompactionJournalHost {
}
fn journal_startup_granted(&self) -> bool {
true
false
}
fn journal_startup_context(
@@ -2996,8 +3037,8 @@ fn compaction_snapshot(generation: usize) -> subjektiv::journal::startup::Journa
fn journal_body(worker: &Worker<MockClient, TestStore>) -> String {
let seed = replacement_seed(worker);
assert_eq!(
seed[1].metadata.origin,
session_store::LoggedSessionHistoryOrigin::JournalContext
compaction_use(&seed[1].metadata),
session_store::InputCompactionUse::ReferenceInput
);
let item = Item::from(seed[1].item.clone());
assert!(matches!(
@@ -3009,8 +3050,8 @@ fn journal_body(worker: &Worker<MockClient, TestStore>) -> String {
));
assert_eq!(
seed.iter()
.filter(|entry| entry.metadata.origin
== session_store::LoggedSessionHistoryOrigin::JournalContext)
.filter(|entry| compaction_use(&entry.metadata)
== session_store::InputCompactionUse::ReferenceInput)
.count(),
1
);
@@ -3119,20 +3160,34 @@ async fn journal_three_compactions_freeze_one_snapshot_preserve_full_summary_tai
projection.source_digest = "a".repeat(64);
let initial_catalog = Arc::new(worker::PromptCatalog::from_projection(projection).unwrap());
fixture.worker.prompts().store(initial_catalog.clone());
let legacy_startup = session_store::LoggedSystemHistoryEntry {
item: session_store::SystemItem::JournalStartup {
body: "OLD_TYPED_STARTUP_JOURNAL".into(),
snapshot: serde_json::json!({"journals": []}),
snapshot_sha256: "legacy-startup-digest".into(),
prompt_provenance: session_store::PromptRenderProvenance {
workspace_id: None,
source_digest: "legacy-source".into(),
projection_digest: "legacy-projection".into(),
logical_name: "internal.subjektiv_journal_startup".into(),
},
},
metadata: summary_entry(Item::system_message(""), DerivedSummary).metadata,
};
// Already migrated generic input with unrelated provenance/payload; Core
// must select it by explicit policy, not identify a Journal Feature.
let legacy_startup = session_store::LoggedSystemHistoryEntry::from_input(
DerivedSummary,
None,
session_store::SystemInputEnvelope::new(
"OLD_TYPED_STARTUP_JOURNAL",
session_store::SystemInputMetadata::new(
session_store::SystemInputUsePolicy {
recording_material: session_store::InputEvidenceUse::Exclude,
memory_evidence: session_store::InputEvidenceUse::Exclude,
observation: session_store::InputObservationUse::Hidden,
compaction: session_store::InputCompactionUse::ReferenceInput,
},
Some(
session_store::FeatureInputPayload::encode(
"renamed-owner:reference",
8,
&serde_json::json!({"private": "retained opaque context"}),
)
.unwrap(),
),
None,
)
.unwrap(),
)
.unwrap(),
);
let legacy_id = legacy_startup.metadata.entry_id.clone();
fixture
.restore_seed_with_system(
@@ -3166,7 +3221,7 @@ async fn journal_three_compactions_freeze_one_snapshot_preserve_full_summary_tai
.unwrap()
.annotation
.origin,
JournalContext
DerivedSummary
);
let session = fixture.worker.session_id();
let original_segment = fixture.worker.segment_id();
@@ -3191,8 +3246,8 @@ async fn journal_three_compactions_freeze_one_snapshot_preserve_full_summary_tai
.unwrap()
.annotation
.origin,
JournalContext,
"legacy startup classification survives a normal turn"
DerivedSummary,
"generic source provenance survives a normal turn unchanged"
);
}
assert_eq!(
@@ -3243,7 +3298,7 @@ async fn journal_three_compactions_freeze_one_snapshot_preserve_full_summary_tai
assert_eq!(host.reads.lock().unwrap().len(), index + 1);
let history = fixture.worker.history();
let overview = input
.split("## Previous Journal reference (historical, not automatically retained)")
.split("## Previous reference input (historical, not automatically retained)")
.next()
.unwrap();
for old in &old_bodies {
@@ -3336,6 +3391,17 @@ async fn journal_three_compactions_freeze_one_snapshot_preserve_full_summary_tai
.run_text("normal post-compaction turn")
.await
.unwrap();
let frozen_seed = replacement_seed(&fixture.worker)
.into_iter()
.filter(|entry| {
matches!(
compaction_use(&entry.metadata),
session_store::InputCompactionUse::ReferenceInput
| session_store::InputCompactionUse::OpeningSummary
)
})
.collect::<Vec<_>>();
assert_eq!(frozen_seed.len(), 2);
let records = fixture
.worker
.store()
@@ -3375,6 +3441,20 @@ async fn journal_three_compactions_freeze_one_snapshot_preserve_full_summary_tai
.run_text("normal turn after typed history restore")
.await
.unwrap();
for frozen in frozen_seed {
let replayed = fixture
.worker
.session_history()
.entries()
.iter()
.find(|entry| entry.annotation.entry_id == frozen.metadata.entry_id)
.expect("frozen entry identity survives re-restore");
assert_eq!(replayed.item, Item::from(frozen.item));
assert_eq!(
replayed.annotation, frozen.metadata,
"origin, derivation, public allowlist, policy and private payload survive atomic seed/restore"
);
}
assert_eq!(
host.reads.lock().unwrap().len(),
3,
@@ -3445,27 +3525,41 @@ async fn journal_absent_disabled_unavailable_and_over_byte_budget_keep_conversat
)
.await;
fixture.worker.compact(0).await.unwrap();
let body = journal_body(&fixture.worker);
let expected = match case {
"no-host" | "disabled" => "disabled",
"absent" => "absent",
_ => "unavailable",
};
assert!(
body.contains(&format!("Journal read status: {expected}.")),
"{case}"
);
let input = first_compactor_input(&observed.requests()[1]);
assert_frozen_journal_transport(&input, &body);
assert_journal_provenance(
&body,
expected_json.as_deref(),
&fixture.worker.prompts().load_full(),
(case != "no-host").then_some(COMPACTION_HOST_WORKSPACE_ID),
);
let body = if matches!(case, "disabled" | "no-host") {
assert_eq!(
replacement_seed(&fixture.worker)
.iter()
.filter(|entry| compaction_use(&entry.metadata)
== session_store::InputCompactionUse::ReferenceInput)
.count(),
0
);
assert!(!input.contains("Journal reference frozen at this compaction boundary"));
String::new()
} else {
let body = journal_body(&fixture.worker);
let expected = if case == "absent" {
"absent"
} else {
"unavailable"
};
assert!(
body.contains(&format!("Journal read status: {expected}.")),
"{case}"
);
assert_frozen_journal_transport(&input, &body);
assert_journal_provenance(
&body,
expected_json.as_deref(),
&fixture.worker.prompts().load_full(),
Some(COMPACTION_HOST_WORKSPACE_ID),
);
body
};
assert_full_initial_summary(&input, &inherited);
assert!(input.contains("UNRECORDED_FALLBACK_GOAL"), "{case}");
assert!(input.contains("## Previous Journal reference (historical, not automatically retained)\nPREVIOUS_JOURNAL_DELEGATED_CONTEXT"));
assert!(input.contains("## Previous reference input (historical, not automatically retained)\nPREVIOUS_JOURNAL_DELEGATED_CONTEXT"));
assert!(
fixture.worker.history().iter().all(|item| !item
.as_text()
+15 -5
View File
@@ -14,7 +14,7 @@ use protocol::{
InvocationArgumentDescriptor, InvocationArgumentType, InvocationArgumentValue,
InvocationCompletion, InvocationValue, Segment,
};
use session_store::{CombinedStore, FsStore, FsWorkerStore, LogEntry, SystemItem};
use session_store::{CombinedStore, FsStore, FsWorkerStore, LogEntry};
use worker::feature::{
FeatureDescriptor, FeatureInstallContext, FeatureInstallError, FeatureInvocationContext,
FeatureInvocationHandler, FeatureInvocationHandlerError, FeatureModule, FeatureRegistryBuilder,
@@ -224,10 +224,20 @@ fn results(worker: &Worker<Trace, TestStore>) -> Vec<FeatureInvocationResult> {
.0
.into_iter()
.filter_map(|entry| match entry {
LogEntry::AnnotatedSystemItem { entry, .. } => match entry.item {
SystemItem::FeatureInvocationResult { result, .. } => Some(result),
_ => None,
},
LogEntry::AnnotatedSystemItem { entry, .. } => {
#[derive(serde::Deserialize)]
struct SavedResult {
result: FeatureInvocationResult,
}
entry
.metadata
.system_input
.as_ref()?
.feature_payload()?
.decode::<SavedResult>("feature:invocation-result", 1)
.ok()
.map(|input| input.result)
}
_ => None,
})
.collect()