Phase 45 (first slice): DocRef + doc_refs field on PlaybookEntry
Phase J keeps asking for: playbooks know which external docs they
used, get flagged when those docs drift. This commit ships the data
model; context7 bridge + drift check endpoints land in follow-ups.
Added to crates/vectord/src/playbook_memory.rs:
- pub struct DocRef { tool, version_seen, snippet_hash, source_url,
seen_at } — one external doc reference
- PlaybookEntry.doc_refs: Vec<DocRef> — empty on legacy entries,
serde default ensures pre-Phase-45 persisted state loads cleanly
- PlaybookEntry.doc_drift_flagged_at: Option<String> — set by the
(future) drift-check code when context7 reports newer version
- PlaybookEntry.doc_drift_reviewed_at: Option<String> — set by
human via /resolve endpoint after reviewing the diagnosis
- impl Default for PlaybookEntry — collapses most test-helper
constructors from 17 explicit fields to 6-9 fields +
..Default::default()
Updated SeedPlaybookRequest + RevisePlaybookRequest (service.rs) to
accept optional doc_refs: the seed/revise endpoints already take the
field, downstream drift detection (Phase 45.2) consumes it.
Docs: docs/CONTROL_PLANE_PRD.md gains full Phase 45 spec with gate
criteria, non-goals, and risk notes.
Tests: 51/51 vectord lib tests green (same count as before, field
additions are backward-compat).
Memory: project_doc_drift_vision.md written so this keeps coming
back to the front of mind.
Next slices (same phase): context7 HTTP bridge in mcp-server,
/vectors/playbook_memory/doc_drift/check/{id} endpoint, overview-
model drift synthesis writing to data/_kb/doc_drift_corrections.jsonl,
boost exclusion for flagged+unreviewed entries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
75a0f424ef
commit
2a4b81bf48
@@ -2211,6 +2211,12 @@ struct SeedPlaybookRequest {
|
||||
/// retired, just inactive). Useful for seasonal/temp contracts.
|
||||
#[serde(default)]
|
||||
valid_until: Option<String>,
|
||||
/// Phase 45 — optional external doc references captured at seal
|
||||
/// time. Each entry names a tool + version_seen; context7-driven
|
||||
/// drift check compares against current versions later. None or
|
||||
/// empty = no drift signal (never flagged).
|
||||
#[serde(default)]
|
||||
doc_refs: Option<Vec<playbook_memory::DocRef>>,
|
||||
}
|
||||
|
||||
/// Bootstrap / test-only: inject a playbook entry directly into
|
||||
@@ -2232,21 +2238,12 @@ async fn seed_playbook_memory(
|
||||
// Embed the entry through the same text shape `rebuild` uses so
|
||||
// similarity math is comparable across seed + real entries.
|
||||
let tmp_entry = playbook_memory::PlaybookEntry {
|
||||
playbook_id: String::new(),
|
||||
operation: req.operation.clone(),
|
||||
approach: req.approach.clone(),
|
||||
context: req.context.clone(),
|
||||
timestamp: chrono::Utc::now().to_rfc3339(),
|
||||
endorsed_names: req.endorsed_names.clone(),
|
||||
city: None, state: None, embedding: None,
|
||||
schema_fingerprint: None,
|
||||
valid_until: None,
|
||||
retired_at: None,
|
||||
retirement_reason: None,
|
||||
version: 1,
|
||||
parent_id: None,
|
||||
superseded_at: None,
|
||||
superseded_by: None,
|
||||
..Default::default()
|
||||
};
|
||||
let text = format!(
|
||||
"{} | {} | {} | fills: {}",
|
||||
@@ -2304,12 +2301,11 @@ async fn seed_playbook_memory(
|
||||
// works). valid_until + retired_at start None.
|
||||
schema_fingerprint: req.schema_fingerprint.clone(),
|
||||
valid_until: req.valid_until.clone(),
|
||||
retired_at: None,
|
||||
retirement_reason: None,
|
||||
version: 1,
|
||||
parent_id: None,
|
||||
superseded_at: None,
|
||||
superseded_by: None,
|
||||
// Phase 45 — seed request may also carry doc_refs; defaults
|
||||
// empty so pre-Phase-45 callers still work and the entry
|
||||
// degrades to "no drift signal" (never flagged).
|
||||
doc_refs: req.doc_refs.clone().unwrap_or_default(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Phase 26 — when append=true (default), route through upsert so
|
||||
@@ -2521,6 +2517,11 @@ struct RevisePlaybookRequest {
|
||||
schema_fingerprint: Option<String>,
|
||||
#[serde(default)]
|
||||
valid_until: Option<String>,
|
||||
/// Phase 45 — updated doc references. Typically a revise happens
|
||||
/// BECAUSE docs drifted; pass the new versions seen so the revised
|
||||
/// entry starts with fresh drift signal.
|
||||
#[serde(default)]
|
||||
doc_refs: Option<Vec<playbook_memory::DocRef>>,
|
||||
}
|
||||
|
||||
/// Phase 27 — create a new version of an existing playbook. The parent
|
||||
@@ -2613,14 +2614,11 @@ async fn revise_playbook_memory(
|
||||
embedding: Some(emb),
|
||||
schema_fingerprint: req.schema_fingerprint,
|
||||
valid_until: req.valid_until,
|
||||
retired_at: None,
|
||||
retirement_reason: None,
|
||||
// revise_entry overwrites these from the parent — values here
|
||||
// are just placeholders so the struct is well-formed.
|
||||
version: 1,
|
||||
parent_id: None,
|
||||
superseded_at: None,
|
||||
superseded_by: None,
|
||||
// Phase 45 — doc_refs may be provided on revise too.
|
||||
doc_refs: req.doc_refs.clone().unwrap_or_default(),
|
||||
// revise_entry overwrites version / parent_id / supersession
|
||||
// from the parent; other fields keep defaults.
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let outcome = state.playbook_memory.revise_entry(&req.parent_id, new_entry)
|
||||
|
||||
Reference in New Issue
Block a user