Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 57 additions & 19 deletions apps/elf-api/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,25 @@ use elf_service::{
ConsolidationRunsListRequest, ConsolidationRunsListResponse, CoreBlockAttachRequest,
CoreBlockAttachResponse, CoreBlockDetachRequest, CoreBlockDetachResponse,
CoreBlockUpsertRequest, CoreBlockUpsertResponse, CoreBlocksGetRequest, CoreBlocksResponse,
DeleteRequest, DeleteResponse, DocType, DocsExcerptResponse, DocsExcerptsGetRequest,
DocsGetRequest, DocsGetResponse, DocsPutRequest, DocsPutResponse, DocsSearchL0Request,
DocsSearchL0Response, DreamingReviewQueueRequest, DreamingReviewQueueResponse,
EntityMemoryViewRequest, EntityMemoryViewResponse, Error, EventMessage, GranteeKind,
GraphQueryEntityRef, GraphQueryPredicateRef, GraphQueryRequest, GraphQueryResponse,
GraphReportRequest, GraphReportResponse, IngestionProfileSelector, KnowledgePageChangedSource,
KnowledgePageGetRequest, KnowledgePageLintRequest, KnowledgePageLintResponse,
KnowledgePageRebuildRequest, KnowledgePageRebuildResponse, KnowledgePageResponse,
KnowledgePageSearchRequest, KnowledgePageSearchResponse, KnowledgePageWatchRebuildRequest,
KnowledgePageWatchRebuildResponse, KnowledgePagesListRequest, KnowledgePagesListResponse,
ListRequest, ListResponse, MemoryCorrectionAction, MemoryCorrectionRequest,
MemoryCorrectionResponse, MemoryHistoryGetRequest, MemoryHistoryResponse, NoteFetchRequest,
NoteFetchResponse, NoteProvenanceBundleResponse, NoteProvenanceGetRequest, PayloadLevel,
PublishNoteRequest, QueryPlan, RankingRequestOverride, RebuildReport, RecallDebugPanelRequest,
RecallDebugPanelResponse, SearchDetailsRequest, SearchDetailsResult, SearchExplainRequest,
SearchExplainResponse, SearchIndexItem, SearchRequest, SearchResponse, SearchSessionGetRequest,
SearchTimelineGroup, SearchTimelineRequest, SearchTrajectoryResponse, SearchTrajectorySummary,
ShareScope, SpaceGrantRevokeRequest, SpaceGrantRevokeResponse, SpaceGrantUpsertRequest,
DeleteRequest, DeleteResponse, DocType, DocsDeleteRequest, DocsDeleteResponse,
DocsExcerptResponse, DocsExcerptsGetRequest, DocsGetRequest, DocsGetResponse, DocsPutRequest,
DocsPutResponse, DocsSearchL0Request, DocsSearchL0Response, DreamingReviewQueueRequest,
DreamingReviewQueueResponse, EntityMemoryViewRequest, EntityMemoryViewResponse, Error,
EventMessage, GranteeKind, GraphQueryEntityRef, GraphQueryPredicateRef, GraphQueryRequest,
GraphQueryResponse, GraphReportRequest, GraphReportResponse, IngestionProfileSelector,
KnowledgePageChangedSource, KnowledgePageGetRequest, KnowledgePageLintRequest,
KnowledgePageLintResponse, KnowledgePageRebuildRequest, KnowledgePageRebuildResponse,
KnowledgePageResponse, KnowledgePageSearchRequest, KnowledgePageSearchResponse,
KnowledgePageWatchRebuildRequest, KnowledgePageWatchRebuildResponse, KnowledgePagesListRequest,
KnowledgePagesListResponse, ListRequest, ListResponse, MemoryCorrectionAction,
MemoryCorrectionRequest, MemoryCorrectionResponse, MemoryHistoryGetRequest,
MemoryHistoryResponse, NoteFetchRequest, NoteFetchResponse, NoteProvenanceBundleResponse,
NoteProvenanceGetRequest, PayloadLevel, PublishNoteRequest, QueryPlan, RankingRequestOverride,
RebuildReport, RecallDebugPanelRequest, RecallDebugPanelResponse, SearchDetailsRequest,
SearchDetailsResult, SearchExplainRequest, SearchExplainResponse, SearchIndexItem,
SearchRequest, SearchResponse, SearchSessionGetRequest, SearchTimelineGroup,
SearchTimelineRequest, SearchTrajectoryResponse, SearchTrajectorySummary, ShareScope,
SpaceGrantRevokeRequest, SpaceGrantRevokeResponse, SpaceGrantUpsertRequest,
SpaceGrantsListRequest, TextPositionSelector, TextQuoteSelector, TraceBundleGetRequest,
TraceBundleResponse, TraceGetRequest, TraceGetResponse, TraceRecentListRequest,
TraceRecentListResponse, TraceTrajectoryGetRequest, UnpublishNoteRequest, UpdateRequest,
Expand Down Expand Up @@ -116,6 +117,7 @@ const VIEWER_HTML: &str = include_str!("../static/viewer.html");
events_ingest,
docs_put,
docs_get,
docs_delete,
docs_search_l0,
docs_excerpts_get,
core_blocks_get,
Expand Down Expand Up @@ -737,7 +739,7 @@ pub fn router(state: AppState) -> Router {
.layer(DefaultBodyLimit::max(MAX_REQUEST_BYTES));
let docs_router = Router::new()
.route("/v2/docs", routing::post(docs_put))
.route("/v2/docs/{doc_id}", routing::get(docs_get))
.route("/v2/docs/{doc_id}", routing::get(docs_get).delete(docs_delete))
.route("/v2/docs/search/l0", routing::post(docs_search_l0))
.route("/v2/docs/excerpts", routing::post(docs_excerpts_get))
.with_state(state)
Expand Down Expand Up @@ -1756,6 +1758,39 @@ async fn docs_get_inner(
Ok(Json(response))
}

#[utoipa::path(
delete,
path = "/v2/docs/{doc_id}",
tag = "docs",
params(("doc_id" = Uuid, Path, description = "Document ID.")),
responses(
(status = 200, description = "Document was deleted.", body = Value),
(status = 400, description = "Invalid request.", body = ErrorBody),
(status = 401, description = "Authentication required.", body = ErrorBody),
(status = 403, description = "Scope denied.", body = ErrorBody),
(status = 404, description = "Document was not found.", body = ErrorBody),
(status = 500, description = "Internal error.", body = ErrorBody),
)
)]
async fn docs_delete(
State(state): State<AppState>,
headers: HeaderMap,
Path(doc_id): Path<Uuid>,
) -> Result<Json<DocsDeleteResponse>, ApiError> {
let ctx = RequestContext::from_headers(&headers)?;
let response = state
.service
.docs_delete(DocsDeleteRequest {
tenant_id: ctx.tenant_id,
project_id: ctx.project_id,
agent_id: ctx.agent_id,
doc_id,
})
.await?;

Ok(Json(response))
}

#[utoipa::path(
post,
path = "/v2/docs/search/l0",
Expand Down Expand Up @@ -3532,6 +3567,7 @@ async fn knowledge_pages_search(
payload: Result<Json<KnowledgePagesSearchBody>, JsonRejection>,
) -> Result<Json<KnowledgePageSearchResponse>, ApiError> {
let ctx = RequestContext::from_headers(&headers)?;
let read_profile = required_read_profile(&headers)?;
let Json(payload) = payload.map_err(|err| {
tracing::warn!(error = %err, "Invalid request payload.");

Expand All @@ -3542,6 +3578,8 @@ async fn knowledge_pages_search(
.knowledge_pages_search(KnowledgePageSearchRequest {
tenant_id: ctx.tenant_id,
project_id: ctx.project_id,
agent_id: ctx.agent_id,
read_profile,
query: payload.query,
page_kind: payload.page_kind,
limit: payload.limit,
Expand Down
2 changes: 2 additions & 0 deletions apps/elf-eval/src/bin/real_world_live_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4681,6 +4681,8 @@ async fn materialize_elf_knowledge(
.knowledge_pages_search(KnowledgePageSearchRequest {
tenant_id: TENANT_ID.to_string(),
project_id,
agent_id: AGENT_ID.to_string(),
read_profile: "private_only".to_string(),
query: "source notes".to_string(),
page_kind: Some(KnowledgePageKind::Project),
limit: Some(10),
Expand Down
12 changes: 12 additions & 0 deletions apps/elf-mcp/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,18 @@ impl ElfMcp {
self.forward(HttpMethod::Get, &path, JsonObject::new(), None).await
}

#[rmcp::tool(
name = "elf_docs_delete",
description = "Delete a Source Library document by doc_id and enqueue derived doc-vector removal.",
input_schema = docs_get_schema()
)]
async fn elf_docs_delete(&self, mut params: JsonObject) -> Result<CallToolResult, ErrorData> {
let doc_id = take_required_string(&mut params, "doc_id")?;
let path = format!("/v2/docs/{doc_id}");

self.forward(HttpMethod::Delete, &path, JsonObject::new(), None).await
}

#[rmcp::tool(
name = "elf_docs_search_l0",
description = "Run a minimal Doc search (L0): chunk-level results with short snippets.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
type: Drift Audit
title: "Privacy, Delete, Export, and Retention Boundaries Drift Audit"
description: "Drift audit for current-recall suppression across Source Library, Knowledge Workspace, graph-lite facts, and relation context."
resource: docs/evidence/2026-06-23-privacy-delete-export-boundaries-drift-audit.md
status: active
authority: evidence
owner: evidence
last_verified: 2026-06-23
tags:
- docs
- evidence
- privacy
- retention
source_refs:
- docs/runbook/privacy_delete_export.md
code_refs:
- apps/elf-api/src/routes.rs
- apps/elf-mcp/src/server.rs
- packages/elf-service/src/docs.rs
- packages/elf-service/src/graph_query.rs
- packages/elf-service/src/graph_report.rs
- packages/elf-service/src/knowledge.rs
- packages/elf-service/src/search.rs
- packages/elf-storage/src/docs.rs
- packages/elf-storage/src/knowledge.rs
- packages/elf-service/tests/acceptance/docs_extension_v1.rs
- packages/elf-service/tests/acceptance/graph_ingestion.rs
- packages/elf-service/tests/acceptance/knowledge_pages.rs
related:
- docs/spec/system_doc_source_ref_v1.md
- docs/spec/system_elf_memory_service_v2.md
- docs/spec/system_graph_memory_postgres_v1.md
- docs/spec/system_knowledge_pages_v1.md
drift_watch:
- docs/runbook/privacy_delete_export.md
- docs/spec/system_doc_source_ref_v1.md
- docs/spec/system_elf_memory_service_v2.md
- docs/spec/system_graph_memory_postgres_v1.md
- docs/spec/system_knowledge_pages_v1.md
- apps/elf-api/src/routes.rs
- apps/elf-mcp/src/server.rs
- packages/elf-service/src/docs.rs
- packages/elf-service/src/graph_query.rs
- packages/elf-service/src/graph_report.rs
- packages/elf-service/src/knowledge.rs
- packages/elf-service/src/search.rs
- packages/elf-storage/src/docs.rs
- packages/elf-storage/src/knowledge.rs
---
# Privacy, Delete, Export, and Retention Boundaries Drift Audit

Purpose: Record the code and test evidence behind the privacy/delete/export boundary
docs added for XY-1078.
Read this when: You need to verify whether docs for source deletion, private spans,
graph evidence suppression, and export boundaries match current code.
Not this document: A legal compliance assessment, provider terms review, or raw
benchmark report.
Evidence for: `docs/runbook/privacy_delete_export.md` and the related Source
Library, Knowledge Workspace, graph memory, and core service specs.

## Claims Checked

- Source Library direct and derived readback uses current active source rows for
recallable snippets.
- Source Library delete has an explicit public HTTP and MCP path that marks the
source non-active and enqueues derived doc-vector deletion.
- Knowledge Workspace page search suppresses snippets whose normalized source refs
are deleted, expired, unreadable, ignored, rejected, unapplied, or contain
non-captured spans.
- Graph query, graph report, and search relation context return facts only when
current readable evidence notes exist and omit deleted or unreadable evidence ids.
- Delete and forget docs distinguish current-recall suppression from retained
provenance, history, trace, and benchmark evidence.
- Export docs route through authorized read APIs and do not describe a bypass around
scope, payload level, or write-policy spans.

## Implementation Evidence

- `apps/elf-api/src/routes.rs` exposes `DELETE /v2/docs/{doc_id}` through the public
docs router and OpenAPI path list.
- `apps/elf-mcp/src/server.rs` exposes `elf_docs_delete` as a thin MCP forwarding
tool with no policy logic.
- `packages/elf-service/src/docs.rs` marks owned Source Library documents deleted and
enqueues one doc-index `DELETE` outbox job per persisted chunk.
- `packages/elf-storage/src/docs.rs` provides the status update used by the service
delete path.
- `packages/elf-storage/src/knowledge.rs` now resolves Knowledge Workspace note,
event, relation, document, and chunk sources through active/readable source rows.
- `packages/elf-service/src/knowledge.rs` resolves current source keys before page
search and suppresses sections with non-recallable source refs or non-captured
spans.
- `packages/elf-service/src/graph_query.rs` and
`packages/elf-service/src/graph_report.rs` require active, unexpired, readable
graph evidence notes for fact readback.
- `packages/elf-service/src/search.rs` filters relation-context evidence notes to
active, unexpired, readable notes and drops malformed relation rows with no
evidence ids.

## Test Evidence

- `packages/elf-service/src/knowledge.rs` has pure coverage for deleted, ignored,
missing, and non-captured source refs.
- `packages/elf-service/src/graph_query.rs` has pure coverage for suppressing graph
rows without readable evidence.
- `packages/elf-service/src/graph_report.rs` has pure coverage for suppressing graph
report facts without readable evidence.
- `packages/elf-service/src/search.rs` has pure coverage for suppressing relation
context rows without evidence.
- `packages/elf-service/tests/acceptance/docs_extension_v1.rs` adds an ignored
integration case for Source Library delete marking the doc deleted, enqueueing
doc-vector deletion, suppressing direct/search readback, and removing Qdrant doc
points.
- `packages/elf-service/tests/acceptance/knowledge_pages.rs` adds an ignored
integration case for Source Library document deletion suppressing page search.
- `packages/elf-service/tests/acceptance/graph_ingestion.rs` adds an ignored
integration case for memory-note delete suppressing graph query readback.

## Residual Boundaries

- Provenance, note history, recall traces, and checked benchmark artifacts are audit
evidence, not current recall. They may retain historical ids or snippets until
their own retention or purge path runs.
- Provider retention remains outside ELF control once content is sent to external
embedding, rerank, or LLM extractor providers.
- The runbook does not claim a universal public erase endpoint. Operators must act on
the explicit authority surface and verify derived projections.
3 changes: 3 additions & 0 deletions docs/evidence/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ Routes to: Drift audits and evidence concepts under `docs/evidence/`.
proposal contract.
- `2026-06-23-local-agent-loop-drift-audit.md`: Drift audit for the one-command
local setup and agent integration recipes.
- `2026-06-23-privacy-delete-export-boundaries-drift-audit.md`: Drift audit for
privacy, delete/forget, export, retention, source visibility, and graph evidence
suppression boundaries.
- `external_memory_pattern_radar_latest.md`: Latest weekly external memory pattern
radar summary.
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ The split below is by question type, not by human-versus-agent audience.
- Need one-command local setup, the minimal memory+knowledge demo loop, or
Codex/Claude/Cursor/MCP/CLI agent integration recipes ->
`docs/runbook/agent-setup.md`
- Need privacy, delete/forget, export, retention, connected-source, provider, or
private-span operating boundaries -> `docs/runbook/privacy_delete_export.md`
- Need the single-user production backup, restore, and Qdrant rebuild path ->
`docs/runbook/single_user_production.md`
- Need benchmark commands or interpretation steps -> `docs/runbook/benchmarking/`
Expand Down
4 changes: 4 additions & 0 deletions docs/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,7 @@ logs.
The new drift audit anchors the deterministic source import -> proposal approval
-> recall/debug -> correction/rollback route to current HTTP, MCP, config, and
task surfaces.
- Added the privacy, delete, export, and retention boundary runbook for XY-1078,
plus a drift audit and spec updates for Source Library span suppression, Knowledge
Workspace source visibility, graph evidence readback, and relation-context
evidence filtering.
2 changes: 2 additions & 0 deletions docs/runbook/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Routes to: Runbook concepts under `docs/runbook/`.
Qdrant rebuild, rollback, and cleanup.
- `agent-setup.md`: agent-oriented local installation flow, one-command local
memory+knowledge loop, and Codex/Claude/Cursor/MCP/CLI recipes.
- `privacy_delete_export.md`: privacy, delete/forget, export, retention, and source
visibility operating boundaries.
- `evaluation.md`: retrieval evaluation commands and interpretation flow.
- `integration-testing.md`: integration and E2E test workflow.
- `testing.md`: test names, scopes, and matching commands.
Expand Down
Loading