feat: fence Subject handoff on recorded Journal and seed new bodies once
This commit is contained in:
@@ -561,6 +561,28 @@ fn reasoning_proves_rollback_safety_but_private_and_derived_items_are_not_materi
|
||||
session,
|
||||
vec![derived, message("system", LoggedRole::System, "secret")],
|
||||
),
|
||||
LogEntry::AnnotatedSystemItem {
|
||||
ts: 1,
|
||||
entry: crate::LoggedSystemHistoryEntry {
|
||||
item: crate::SystemItem::JournalStartup {
|
||||
body: "previous Journal must not become new recording material".into(),
|
||||
snapshot: serde_json::json!({"journals": []}),
|
||||
snapshot_sha256: "retained-digest".into(),
|
||||
prompt_provenance: crate::PromptRenderProvenance {
|
||||
workspace_id: None,
|
||||
source_digest: "source".into(),
|
||||
projection_digest: "projection".into(),
|
||||
logical_name: "internal.subjektiv_journal_startup".into(),
|
||||
},
|
||||
},
|
||||
metadata: LoggedSessionHistoryMetadata {
|
||||
entry_id: LoggedSessionHistoryEntryId("startup-journal".into()),
|
||||
origin: LoggedSessionHistoryOrigin::DerivedSummary,
|
||||
derivation: None,
|
||||
},
|
||||
},
|
||||
extensions: vec![],
|
||||
},
|
||||
invoke(),
|
||||
input("u"),
|
||||
LogEntry::AnnotatedAssistantItem {
|
||||
|
||||
@@ -92,6 +92,14 @@ pub struct PromptRenderProvenance {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum SystemItem {
|
||||
/// One-time derived Journal reference context. The bounded Host snapshot is
|
||||
/// retained as JSON independently of its rendered body and prompt projection.
|
||||
JournalStartup {
|
||||
body: String,
|
||||
snapshot: serde_json::Value,
|
||||
snapshot_sha256: String,
|
||||
prompt_provenance: PromptRenderProvenance,
|
||||
},
|
||||
/// Free-form notification sent in by an external caller via
|
||||
/// `Method::Notify`. `message` is the raw caller-supplied text;
|
||||
/// `body` is the wrapped LLM-context form (Worker renders it via
|
||||
@@ -194,6 +202,7 @@ impl SystemItem {
|
||||
/// for this item. Returns the variant's stored `body` verbatim.
|
||||
pub fn history_text(&self) -> String {
|
||||
match self {
|
||||
SystemItem::JournalStartup { body, .. } => body.clone(),
|
||||
SystemItem::Notification { body, .. } => body.clone(),
|
||||
SystemItem::WorkerEvent { body, .. } => body.clone(),
|
||||
SystemItem::FeatureInvocationResult { body, .. } => body.clone(),
|
||||
@@ -220,6 +229,7 @@ impl SystemItem {
|
||||
/// wire — keep flexible.
|
||||
pub fn kind_label(&self) -> &'static str {
|
||||
match self {
|
||||
SystemItem::JournalStartup { .. } => "journal_startup",
|
||||
SystemItem::Notification { .. } => "notification",
|
||||
SystemItem::WorkerEvent { .. } => "worker_event",
|
||||
SystemItem::FeatureInvocationResult { .. } => "feature_invocation_result",
|
||||
|
||||
@@ -762,6 +762,11 @@ impl Store for WorkerSessionStore {
|
||||
}
|
||||
|
||||
fn list_segments(&self, session_id: SessionId) -> Result<Vec<SegmentId>, StoreError> {
|
||||
// A pending Worker can have metadata before its atomic initial seed.
|
||||
// Absence is queryable without materializing or binding this store.
|
||||
if self.session_id()?.is_none() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
self.ensure_session(session_id, false)?;
|
||||
let mut segments: Vec<SegmentId> = Vec::new();
|
||||
for entry in fs::read_dir(self.root.join(SEGMENTS_DIR))? {
|
||||
@@ -804,6 +809,9 @@ impl Store for WorkerSessionStore {
|
||||
}
|
||||
|
||||
fn exists(&self, session_id: SessionId, segment_id: SegmentId) -> Result<bool, StoreError> {
|
||||
if self.session_id()?.is_none() {
|
||||
return Ok(false);
|
||||
}
|
||||
self.ensure_session(session_id, false)?;
|
||||
Ok(self.log_path(segment_id).exists())
|
||||
}
|
||||
@@ -2103,6 +2111,24 @@ mod tests {
|
||||
assert!(matches!(error, StoreError::Corrupt { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_session_probes_are_empty_without_binding_and_foreign_materialized_session_is_rejected()
|
||||
{
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let session_id = new_session_id();
|
||||
let segment_id = new_segment_id();
|
||||
let store = WorkerSessionStore::new(root.path()).unwrap();
|
||||
assert!(!store.exists(session_id, segment_id).unwrap());
|
||||
assert!(store.list_segments(session_id).unwrap().is_empty());
|
||||
assert_eq!(store.session_id().unwrap(), None);
|
||||
let reopened = WorkerSessionStore::new(root.path()).unwrap();
|
||||
assert_eq!(reopened.session_id().unwrap(), None);
|
||||
store.create_segment(session_id, segment_id, &[]).unwrap();
|
||||
let foreign = new_session_id();
|
||||
assert!(store.exists(foreign, segment_id).is_err());
|
||||
assert!(store.list_segments(foreign).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reopen_preserves_session_and_segment_ids() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user