feat: fence Subject handoff on recorded Journal and seed new bodies once

This commit is contained in:
2026-10-16 20:51:08 +09:00
parent be1c030350
commit 04a4e59100
59 changed files with 7167 additions and 632 deletions
@@ -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 {
+10
View File
@@ -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();