fix(session): preflight migration and retain timestamps
This commit is contained in:
@@ -185,12 +185,9 @@ impl Tool for StageMemoryCandidateTool {
|
||||
})?);
|
||||
}
|
||||
if matches!(params.kind, CandidateKind::Preference)
|
||||
&& entries.iter().any(|entry| {
|
||||
!matches!(
|
||||
entry.origin,
|
||||
crate::WorkerHistoryProvenance::HumanInput { .. }
|
||||
)
|
||||
})
|
||||
&& entries
|
||||
.iter()
|
||||
.any(|entry| !matches!(entry.origin, protocol::SessionEntryProvenance::HumanInput))
|
||||
{
|
||||
return Err(ToolError::InvalidArgument(
|
||||
"preference candidates require exclusively HumanInput evidence; model, Worker, Flow, backend, derived, and legacy-unknown origins are not preference authority"
|
||||
@@ -324,10 +321,23 @@ fn evidence_kind(entry: &SessionEntryEvidence) -> EvidenceKind {
|
||||
}
|
||||
}
|
||||
|
||||
fn evidence_origin(origin: &crate::WorkerHistoryProvenance) -> EvidenceOrigin {
|
||||
use crate::WorkerHistoryProvenance as Origin;
|
||||
let mut evidence = EvidenceOrigin {
|
||||
kind: EvidenceOriginKind::LegacyUnknown,
|
||||
fn evidence_origin(origin: &protocol::SessionEntryProvenance) -> EvidenceOrigin {
|
||||
use protocol::SessionEntryProvenance as Origin;
|
||||
let kind = match origin {
|
||||
Origin::HumanInput => EvidenceOriginKind::HumanInput,
|
||||
Origin::WorkerInput => EvidenceOriginKind::WorkerInput,
|
||||
Origin::FlowInstruction => EvidenceOriginKind::FlowInstruction,
|
||||
Origin::BackendInstruction => EvidenceOriginKind::BackendInstruction,
|
||||
Origin::ModelOutput => EvidenceOriginKind::ModelOutput,
|
||||
Origin::ToolOutput => EvidenceOriginKind::ToolOutput,
|
||||
Origin::DerivedSummary => EvidenceOriginKind::DerivedSummary,
|
||||
Origin::LegacyUnknown => EvidenceOriginKind::LegacyUnknown,
|
||||
};
|
||||
EvidenceOrigin {
|
||||
kind,
|
||||
// The public SessionSnapshot intentionally excludes account, Worker,
|
||||
// Runtime, and Flow internals. Preserve the authenticated origin class
|
||||
// without inventing missing control-plane identity fields.
|
||||
account_id: None,
|
||||
workspace_id: None,
|
||||
runtime_id: None,
|
||||
@@ -335,46 +345,7 @@ fn evidence_origin(origin: &crate::WorkerHistoryProvenance) -> EvidenceOrigin {
|
||||
flow_selector: None,
|
||||
flow_definition_id: None,
|
||||
flow_definition_revision: None,
|
||||
};
|
||||
match origin {
|
||||
Origin::HumanInput { account_id } => {
|
||||
evidence.kind = EvidenceOriginKind::HumanInput;
|
||||
evidence.account_id = Some(account_id.clone());
|
||||
}
|
||||
Origin::WorkerInput { actor } => {
|
||||
evidence.kind = EvidenceOriginKind::WorkerInput;
|
||||
evidence.workspace_id = actor.workspace_id.clone();
|
||||
evidence.runtime_id = actor.runtime_id.clone();
|
||||
evidence.worker_id = Some(actor.worker_id.clone());
|
||||
}
|
||||
Origin::FlowInstruction {
|
||||
selector,
|
||||
definition_id,
|
||||
definition_revision,
|
||||
..
|
||||
} => {
|
||||
evidence.kind = EvidenceOriginKind::FlowInstruction;
|
||||
evidence.flow_selector = Some(selector.clone());
|
||||
evidence.flow_definition_id = Some(definition_id.clone());
|
||||
evidence.flow_definition_revision = Some(*definition_revision);
|
||||
}
|
||||
Origin::BackendInstruction { .. } => evidence.kind = EvidenceOriginKind::BackendInstruction,
|
||||
Origin::ModelOutput { worker } => {
|
||||
evidence.kind = EvidenceOriginKind::ModelOutput;
|
||||
evidence.workspace_id = worker.workspace_id.clone();
|
||||
evidence.runtime_id = worker.runtime_id.clone();
|
||||
evidence.worker_id = Some(worker.worker_id.clone());
|
||||
}
|
||||
Origin::ToolOutput { worker } => {
|
||||
evidence.kind = EvidenceOriginKind::ToolOutput;
|
||||
evidence.workspace_id = worker.workspace_id.clone();
|
||||
evidence.runtime_id = worker.runtime_id.clone();
|
||||
evidence.worker_id = Some(worker.worker_id.clone());
|
||||
}
|
||||
Origin::DerivedSummary => evidence.kind = EvidenceOriginKind::DerivedSummary,
|
||||
Origin::LegacyUnknown => evidence.kind = EvidenceOriginKind::LegacyUnknown,
|
||||
}
|
||||
evidence
|
||||
}
|
||||
|
||||
fn staging_evidence(entry: &SessionEntryEvidence) -> StagingEvidence {
|
||||
@@ -502,12 +473,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn human_origin_projects_account_authority_into_evidence() {
|
||||
let origin = evidence_origin(&crate::WorkerHistoryProvenance::HumanInput {
|
||||
account_id: "account-1".into(),
|
||||
});
|
||||
fn public_human_origin_preserves_class_without_inventing_account_authority() {
|
||||
let origin = evidence_origin(&protocol::SessionEntryProvenance::HumanInput);
|
||||
assert_eq!(origin.kind, EvidenceOriginKind::HumanInput);
|
||||
assert_eq!(origin.account_id.as_deref(), Some("account-1"));
|
||||
assert_eq!(origin.account_id, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -794,6 +794,7 @@ mod tests {
|
||||
};
|
||||
Some(protocol::SessionSnapshotEntry {
|
||||
entry_id: format!("fake-{index:08}"),
|
||||
timestamp: index as u64,
|
||||
provenance: protocol::SessionEntryProvenance::LegacyUnknown,
|
||||
derived_from: Vec::new(),
|
||||
data,
|
||||
|
||||
@@ -109,7 +109,7 @@ impl ToolPart {
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OverviewItem {
|
||||
pub id: SessionEntryRef,
|
||||
pub origin: WorkerHistoryProvenance,
|
||||
pub origin: SessionEntryProvenance,
|
||||
pub entry_range: [u64; 2],
|
||||
pub kind: ReferenceKind,
|
||||
pub label: String,
|
||||
@@ -120,7 +120,7 @@ pub(crate) struct OverviewItem {
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ReferenceEntry {
|
||||
pub id: SessionEntryRef,
|
||||
pub origin: WorkerHistoryProvenance,
|
||||
pub origin: SessionEntryProvenance,
|
||||
pub entry_range: [u64; 2],
|
||||
pub kind: ReferenceKind,
|
||||
pub tool_part: Option<ToolPart>,
|
||||
@@ -146,7 +146,7 @@ pub(crate) struct SearchOptions {
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct SearchHit {
|
||||
pub id: SessionEntryRef,
|
||||
pub origin: WorkerHistoryProvenance,
|
||||
pub origin: SessionEntryProvenance,
|
||||
pub kind: ReferenceKind,
|
||||
pub tool_part: Option<ToolPart>,
|
||||
pub tool_name: Option<String>,
|
||||
@@ -192,7 +192,7 @@ impl Default for ReadOptions {
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ReadEntry {
|
||||
pub id: SessionEntryRef,
|
||||
pub origin: WorkerHistoryProvenance,
|
||||
pub origin: SessionEntryProvenance,
|
||||
pub kind: ReferenceKind,
|
||||
pub tool_part: Option<ToolPart>,
|
||||
pub tool_name: Option<String>,
|
||||
@@ -211,7 +211,7 @@ pub(crate) struct ReadResult {
|
||||
pub(crate) struct SessionEntryEvidence {
|
||||
pub segment_id: String,
|
||||
pub entry_ref: SessionEntryRef,
|
||||
pub origin: WorkerHistoryProvenance,
|
||||
pub origin: SessionEntryProvenance,
|
||||
pub entry_range: [u64; 2],
|
||||
pub kind: ReferenceKind,
|
||||
pub tool_part: Option<ToolPart>,
|
||||
@@ -220,10 +220,17 @@ pub(crate) struct SessionEntryEvidence {
|
||||
pub excerpt: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CapturedHistoryEntry {
|
||||
item: Item,
|
||||
entry_id: session_store::LoggedSessionHistoryEntryId,
|
||||
origin: SessionEntryProvenance,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct SessionCapture {
|
||||
segment_id: String,
|
||||
entries: Arc<Vec<HistoryEntry<SessionHistoryMetadata>>>,
|
||||
entries: Arc<Vec<CapturedHistoryEntry>>,
|
||||
overview: Vec<OverviewItem>,
|
||||
index: Vec<ReferenceEntry>,
|
||||
}
|
||||
@@ -280,35 +287,49 @@ impl SessionCapture {
|
||||
SessionSnapshotEntryData::SystemItem { .. }
|
||||
| SessionSnapshotEntryData::RunError { .. } => return None,
|
||||
};
|
||||
Some(HistoryEntry::new(
|
||||
Some(CapturedHistoryEntry {
|
||||
item,
|
||||
public_snapshot_metadata(entry.entry_id, entry.provenance),
|
||||
))
|
||||
entry_id: session_store::LoggedSessionHistoryEntryId(entry.entry_id),
|
||||
origin: entry.provenance,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
Self::from_history_entries(segment_id, entries)
|
||||
Self::from_captured_entries(segment_id, entries)
|
||||
}
|
||||
|
||||
pub(crate) fn new(segment_id: impl Into<String>, items: Vec<Item>) -> Self {
|
||||
let entries = items
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, item)| {
|
||||
let mut metadata = SessionHistoryMetadata::legacy_unknown();
|
||||
metadata.entry_id =
|
||||
session_store::LoggedSessionHistoryEntryId(format!("{index:08}"));
|
||||
HistoryEntry::new(item, metadata)
|
||||
.map(|(index, item)| CapturedHistoryEntry {
|
||||
item,
|
||||
entry_id: session_store::LoggedSessionHistoryEntryId(format!("{index:08}")),
|
||||
origin: SessionEntryProvenance::LegacyUnknown,
|
||||
})
|
||||
.collect();
|
||||
Self::from_history_entries(segment_id, entries)
|
||||
Self::from_captured_entries(segment_id, entries)
|
||||
}
|
||||
|
||||
pub(crate) fn from_history_entries(
|
||||
segment_id: impl Into<String>,
|
||||
entries: Vec<HistoryEntry<SessionHistoryMetadata>>,
|
||||
) -> Self {
|
||||
let entries = entries
|
||||
.into_iter()
|
||||
.map(|entry| CapturedHistoryEntry {
|
||||
item: entry.item,
|
||||
entry_id: entry.annotation.entry_id,
|
||||
origin: public_provenance(&entry.annotation.origin),
|
||||
})
|
||||
.collect();
|
||||
Self::from_captured_entries(segment_id, entries)
|
||||
}
|
||||
|
||||
fn from_captured_entries(
|
||||
segment_id: impl Into<String>,
|
||||
entries: Vec<CapturedHistoryEntry>,
|
||||
) -> Self {
|
||||
let segment_id = segment_id.into();
|
||||
let entries = Arc::new(entries);
|
||||
let mut overview = Vec::new();
|
||||
let mut index = Vec::new();
|
||||
|
||||
@@ -317,7 +338,7 @@ impl SessionCapture {
|
||||
let entry_range = [idx as u64, idx as u64];
|
||||
match item {
|
||||
Item::Message { role, content, .. } => {
|
||||
let Some(kind) = message_reference_kind(&entry.annotation.origin, role) else {
|
||||
let Some(kind) = message_reference_kind(&entry.origin, role) else {
|
||||
continue;
|
||||
};
|
||||
let text = content
|
||||
@@ -327,10 +348,10 @@ impl SessionCapture {
|
||||
.join("");
|
||||
let label = format!("{} message", kind.as_str());
|
||||
let summary = truncate_chars(&text, 240);
|
||||
let id = SessionEntryRef::from_history_entry_id(&entry.annotation.entry_id);
|
||||
let id = SessionEntryRef::from_history_entry_id(&entry.entry_id);
|
||||
index.push(ReferenceEntry {
|
||||
id: id.clone(),
|
||||
origin: entry.annotation.origin.clone(),
|
||||
origin: entry.origin.clone(),
|
||||
entry_range,
|
||||
kind,
|
||||
tool_part: None,
|
||||
@@ -342,7 +363,7 @@ impl SessionCapture {
|
||||
if matches!(kind, ReferenceKind::User | ReferenceKind::Assistant) {
|
||||
overview.push(OverviewItem {
|
||||
id: id.clone(),
|
||||
origin: entry.annotation.origin.clone(),
|
||||
origin: entry.origin.clone(),
|
||||
entry_range,
|
||||
kind,
|
||||
label,
|
||||
@@ -356,8 +377,8 @@ impl SessionCapture {
|
||||
} => {
|
||||
let text = format!("{name}\n{arguments}");
|
||||
index.push(ReferenceEntry {
|
||||
id: SessionEntryRef::from_history_entry_id(&entry.annotation.entry_id),
|
||||
origin: entry.annotation.origin.clone(),
|
||||
id: SessionEntryRef::from_history_entry_id(&entry.entry_id),
|
||||
origin: entry.origin.clone(),
|
||||
entry_range,
|
||||
kind: ReferenceKind::Tool,
|
||||
tool_part: Some(ToolPart::Input),
|
||||
@@ -383,8 +404,8 @@ impl SessionCapture {
|
||||
content.as_deref().unwrap_or_default(),
|
||||
);
|
||||
index.push(ReferenceEntry {
|
||||
id: SessionEntryRef::from_history_entry_id(&entry.annotation.entry_id),
|
||||
origin: entry.annotation.origin.clone(),
|
||||
id: SessionEntryRef::from_history_entry_id(&entry.entry_id),
|
||||
origin: entry.origin.clone(),
|
||||
entry_range,
|
||||
kind: ReferenceKind::Tool,
|
||||
tool_part: Some(ToolPart::Output),
|
||||
@@ -424,7 +445,7 @@ impl SessionCapture {
|
||||
|
||||
Self {
|
||||
segment_id,
|
||||
entries,
|
||||
entries: Arc::new(entries),
|
||||
overview,
|
||||
index,
|
||||
}
|
||||
@@ -607,65 +628,41 @@ impl SessionCapture {
|
||||
}
|
||||
}
|
||||
|
||||
fn public_snapshot_metadata(
|
||||
entry_id: String,
|
||||
provenance: SessionEntryProvenance,
|
||||
) -> SessionHistoryMetadata {
|
||||
let worker = session_store::LoggedWorkerSubject {
|
||||
workspace_id: None,
|
||||
runtime_id: None,
|
||||
worker_id: "public-session-snapshot".to_owned(),
|
||||
};
|
||||
let origin = match provenance {
|
||||
SessionEntryProvenance::HumanInput => WorkerHistoryProvenance::HumanInput {
|
||||
account_id: "public-session-snapshot".to_owned(),
|
||||
},
|
||||
SessionEntryProvenance::WorkerInput => WorkerHistoryProvenance::WorkerInput {
|
||||
actor: worker.clone(),
|
||||
},
|
||||
SessionEntryProvenance::FlowInstruction => WorkerHistoryProvenance::FlowInstruction {
|
||||
selector: "public-session-snapshot".to_owned(),
|
||||
definition_id: "public-session-snapshot".to_owned(),
|
||||
definition_revision: 0,
|
||||
instance_id: "public-session-snapshot".to_owned(),
|
||||
state_id: "public-session-snapshot".to_owned(),
|
||||
},
|
||||
SessionEntryProvenance::BackendInstruction => {
|
||||
WorkerHistoryProvenance::BackendInstruction { operation_id: None }
|
||||
fn public_provenance(origin: &WorkerHistoryProvenance) -> SessionEntryProvenance {
|
||||
match origin {
|
||||
WorkerHistoryProvenance::HumanInput { .. } => SessionEntryProvenance::HumanInput,
|
||||
WorkerHistoryProvenance::WorkerInput { .. } => SessionEntryProvenance::WorkerInput,
|
||||
WorkerHistoryProvenance::FlowInstruction { .. } => SessionEntryProvenance::FlowInstruction,
|
||||
WorkerHistoryProvenance::BackendInstruction { .. } => {
|
||||
SessionEntryProvenance::BackendInstruction
|
||||
}
|
||||
SessionEntryProvenance::ModelOutput => WorkerHistoryProvenance::ModelOutput {
|
||||
worker: worker.clone(),
|
||||
},
|
||||
SessionEntryProvenance::ToolOutput => WorkerHistoryProvenance::ToolOutput { worker },
|
||||
SessionEntryProvenance::DerivedSummary => WorkerHistoryProvenance::DerivedSummary,
|
||||
SessionEntryProvenance::LegacyUnknown => WorkerHistoryProvenance::LegacyUnknown,
|
||||
};
|
||||
SessionHistoryMetadata {
|
||||
entry_id: session_store::LoggedSessionHistoryEntryId(entry_id),
|
||||
origin,
|
||||
derivation: None,
|
||||
WorkerHistoryProvenance::ModelOutput { .. } => SessionEntryProvenance::ModelOutput,
|
||||
WorkerHistoryProvenance::ToolOutput { .. } => SessionEntryProvenance::ToolOutput,
|
||||
WorkerHistoryProvenance::DerivedSummary => SessionEntryProvenance::DerivedSummary,
|
||||
WorkerHistoryProvenance::LegacyUnknown => SessionEntryProvenance::LegacyUnknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn message_reference_kind(
|
||||
origin: &WorkerHistoryProvenance,
|
||||
origin: &SessionEntryProvenance,
|
||||
provider_role: &Role,
|
||||
) -> Option<ReferenceKind> {
|
||||
match origin {
|
||||
WorkerHistoryProvenance::HumanInput { .. }
|
||||
| WorkerHistoryProvenance::WorkerInput { .. } => Some(ReferenceKind::User),
|
||||
WorkerHistoryProvenance::ModelOutput { .. } => Some(ReferenceKind::Assistant),
|
||||
WorkerHistoryProvenance::ToolOutput { .. } => Some(ReferenceKind::Tool),
|
||||
WorkerHistoryProvenance::LegacyUnknown => match provider_role {
|
||||
SessionEntryProvenance::HumanInput | SessionEntryProvenance::WorkerInput => {
|
||||
Some(ReferenceKind::User)
|
||||
}
|
||||
SessionEntryProvenance::ModelOutput => Some(ReferenceKind::Assistant),
|
||||
SessionEntryProvenance::ToolOutput => Some(ReferenceKind::Tool),
|
||||
SessionEntryProvenance::LegacyUnknown => match provider_role {
|
||||
Role::User => Some(ReferenceKind::User),
|
||||
Role::Assistant => Some(ReferenceKind::Assistant),
|
||||
Role::System => None,
|
||||
},
|
||||
// Flow/backend/system content remains out of the observation surface
|
||||
// even when represented with a provider user/system role.
|
||||
WorkerHistoryProvenance::FlowInstruction { .. }
|
||||
| WorkerHistoryProvenance::BackendInstruction { .. }
|
||||
| WorkerHistoryProvenance::DerivedSummary => None,
|
||||
SessionEntryProvenance::FlowInstruction
|
||||
| SessionEntryProvenance::BackendInstruction
|
||||
| SessionEntryProvenance::DerivedSummary => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -762,13 +759,13 @@ mod tests {
|
||||
assert_eq!(overview.len(), 1);
|
||||
assert!(matches!(
|
||||
overview[0].origin,
|
||||
WorkerHistoryProvenance::HumanInput { .. }
|
||||
SessionEntryProvenance::HumanInput
|
||||
));
|
||||
let evidence = capture.evidence_for(overview[0].id.as_str()).unwrap();
|
||||
assert!(evidence.excerpt.ends_with("remember my preference"));
|
||||
assert!(matches!(
|
||||
evidence.origin,
|
||||
WorkerHistoryProvenance::HumanInput { .. }
|
||||
SessionEntryProvenance::HumanInput
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -284,6 +284,7 @@ mod tests {
|
||||
.enumerate()
|
||||
.map(|(index, value)| protocol::SessionSnapshotEntry {
|
||||
entry_id: format!("test-{index}"),
|
||||
timestamp: index as u64,
|
||||
provenance: protocol::SessionEntryProvenance::LegacyUnknown,
|
||||
derived_from: Vec::new(),
|
||||
data: protocol::SessionSnapshotEntryData::RunError {
|
||||
|
||||
Reference in New Issue
Block a user