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
78 changes: 73 additions & 5 deletions apps/elf-api/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use elf_domain::{
ConsolidationReviewState,
},
english_gate,
knowledge::KnowledgePageKind,
knowledge::{KnowledgePageKind, KnowledgeSourceKind},
writegate::WritePolicy,
};
use elf_service::{
Expand All @@ -55,10 +55,11 @@ use elf_service::{
DocsSearchL0Response, DreamingReviewQueueRequest, DreamingReviewQueueResponse,
EntityMemoryViewRequest, EntityMemoryViewResponse, Error, EventMessage, GranteeKind,
GraphQueryEntityRef, GraphQueryPredicateRef, GraphQueryRequest, GraphQueryResponse,
GraphReportRequest, GraphReportResponse, IngestionProfileSelector, KnowledgePageGetRequest,
KnowledgePageLintRequest, KnowledgePageLintResponse, KnowledgePageRebuildRequest,
KnowledgePageRebuildResponse, KnowledgePageResponse, KnowledgePageSearchRequest,
KnowledgePageSearchResponse, KnowledgePagesListRequest, KnowledgePagesListResponse,
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,
Expand Down Expand Up @@ -151,6 +152,7 @@ const VIEWER_HTML: &str = include_str!("../static/viewer.html");
dreaming_review_queue,
recall_debug_panel,
knowledge_page_rebuild,
knowledge_pages_watch_rebuild,
knowledge_pages_list,
knowledge_pages_search,
knowledge_page_get,
Expand Down Expand Up @@ -446,6 +448,20 @@ struct KnowledgePageRebuildBody {
provider_metadata: Value,
}

#[derive(Clone, Debug, Deserialize)]
struct KnowledgePageChangedSourceBody {
source_kind: KnowledgeSourceKind,
source_id: Uuid,
}

#[derive(Clone, Debug, Deserialize)]
struct KnowledgePageWatchRebuildBody {
changed_sources: Vec<KnowledgePageChangedSourceBody>,
page_kind: Option<KnowledgePageKind>,
limit: Option<u32>,
generate_memory_candidates: Option<bool>,
}

#[derive(Clone, Debug, Deserialize)]
struct KnowledgePagesListQuery {
page_kind: Option<KnowledgePageKind>,
Expand Down Expand Up @@ -785,6 +801,10 @@ pub fn admin_router(state: AppState) -> Router {
.route("/v2/admin/recall-debug/panel", routing::post(admin_recall_debug_panel))
.route("/v2/admin/knowledge/pages", routing::get(knowledge_pages_list))
.route("/v2/admin/knowledge/pages/rebuild", routing::post(knowledge_page_rebuild))
.route(
"/v2/admin/knowledge/pages/rebuild-changed-sources",
routing::post(knowledge_pages_watch_rebuild),
)
.route("/v2/admin/knowledge/pages/search", routing::post(knowledge_pages_search))
.route("/v2/admin/knowledge/pages/{page_id}", routing::get(knowledge_page_get))
.route("/v2/admin/knowledge/pages/{page_id}/lint", routing::post(knowledge_page_lint))
Expand Down Expand Up @@ -3303,6 +3323,54 @@ async fn knowledge_page_rebuild(
Ok(Json(response))
}

#[utoipa::path(
post,
path = "/v2/admin/knowledge/pages/rebuild-changed-sources",
tag = "knowledge",
request_body = Value,
responses(
(status = 200, description = "Affected knowledge pages were rebuilt.", body = Value),
(status = 400, description = "Invalid request.", body = ErrorBody),
(status = 401, description = "Authentication required.", body = ErrorBody),
(status = 403, description = "Admin access required.", body = ErrorBody),
(status = 500, description = "Internal error.", body = ErrorBody),
)
)]
async fn knowledge_pages_watch_rebuild(
State(state): State<AppState>,
headers: HeaderMap,
payload: Result<Json<KnowledgePageWatchRebuildBody>, JsonRejection>,
) -> Result<Json<KnowledgePageWatchRebuildResponse>, ApiError> {
let ctx = RequestContext::from_headers(&headers)?;
let Json(payload) = payload.map_err(|err| {
tracing::warn!(error = %err, "Invalid request payload.");

json_error(StatusCode::BAD_REQUEST, "INVALID_REQUEST", "Invalid request payload.", None)
})?;
let changed_sources = payload
.changed_sources
.into_iter()
.map(|source| KnowledgePageChangedSource {
source_kind: source.source_kind,
source_id: source.source_id,
})
.collect();
let response = state
.service
.knowledge_pages_watch_rebuild(KnowledgePageWatchRebuildRequest {
tenant_id: ctx.tenant_id,
project_id: ctx.project_id,
agent_id: ctx.agent_id,
changed_sources,
page_kind: payload.page_kind,
limit: payload.limit,
generate_memory_candidates: payload.generate_memory_candidates.unwrap_or(true),
})
.await?;

Ok(Json(response))
}

#[utoipa::path(
get,
path = "/v2/admin/knowledge/pages",
Expand Down
89 changes: 89 additions & 0 deletions docs/evidence/2026-06-22-knowledge-watch-rebuild-drift-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
type: Drift Audit
title: "Knowledge Watch/Rebuild Drift Audit"
description: "Drift audit for the changed-source Knowledge Workspace rebuild and reviewable memory-candidate contract."
resource: docs/evidence/2026-06-22-knowledge-watch-rebuild-drift-audit.md
status: active
authority: current_state
owner: docs
last_verified: 2026-06-22
tags:
- docs
- drift-audit
- knowledge-workspace
source_refs: []
code_refs:
- apps/elf-api/src/routes.rs
- packages/elf-domain/src/knowledge.rs
- packages/elf-service/src/knowledge.rs
- packages/elf-storage/src/knowledge.rs
- docs/spec/system_knowledge_pages_v1.md
- docs/spec/system_elf_memory_service_v2.md
related:
- docs/spec/system_knowledge_pages_v1.md
- docs/spec/system_consolidation_proposals_v1.md
drift_watch:
- apps/elf-api/src/routes.rs
- packages/elf-domain/src/knowledge.rs
- packages/elf-service/src/knowledge.rs
- packages/elf-storage/src/knowledge.rs
- docs/spec/system_knowledge_pages_v1.md
- docs/spec/system_elf_memory_service_v2.md
---
# Knowledge Watch/Rebuild Drift Audit

Purpose: Anchor the changed-source Knowledge Workspace rebuild contract to the
current service and API surfaces.
Read this when: You need evidence boundaries for the knowledge watch/rebuild API,
section-state output, or reviewable memory-candidate proposal path.
Not this document: Benchmark interpretation, external product comparison, or
operator setup procedure.

## Watched Claims

- `POST /v2/admin/knowledge/pages/rebuild-changed-sources` is the admin entrypoint
for changed-source rebuilds.
- Changed-source rebuilds select only pages that already cite supplied source refs.
- Rebuild output reports changed, unchanged, stale, and blocked page/section states.
- Rebuild output emits stale-section, changed-claim, missing-citation, and conflict
classifications.
- Memory candidates from knowledge deltas are queued through consolidation proposals
and do not mutate source records or memory notes directly.

## Evidence Anchors

- `packages/elf-storage/src/knowledge.rs` owns affected-page lookup by source ref.
- `packages/elf-service/src/knowledge.rs` owns changed-source lint, rebuild,
section-state output, and memory-candidate proposal payload construction.
- `apps/elf-api/src/routes.rs` exposes the admin route.
- `docs/spec/system_knowledge_pages_v1.md` owns the normative contract.
- `docs/spec/system_consolidation_proposals_v1.md` owns the reviewable memory
promotion path.

## Reverse Checks

- Run `cargo make check-rust` after code changes to verify the route/service/storage
surface compiles.
- Run `cargo make check-docs` after docs changes to verify links and task references.
- Run the focused knowledge service tests before claiming the source-update and stale
page cases are covered.

## Verdict

pass

## Required Updates

- Keep `docs/spec/system_knowledge_pages_v1.md` aligned with any future changes to
watch/rebuild response fields, output classifications, or proposal routing.
- The repository-native docs gate passed for this lane. The stricter Decodex docs
profile still reports unrelated P1 closeout report metadata shape issues outside
this lane.

## Citations

- `apps/elf-api/src/routes.rs`
- `packages/elf-service/src/knowledge.rs`
- `packages/elf-storage/src/knowledge.rs`
- `docs/spec/system_knowledge_pages_v1.md`
- `docs/spec/system_consolidation_proposals_v1.md`
3 changes: 3 additions & 0 deletions docs/evidence/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ Routes to: Drift audits and evidence concepts under `docs/evidence/`.
migration.
- `2026-06-18-research-artifact-disposition.md`: Evidence record for promoted,
carried-forward, moved, and deleted legacy research JSON artifacts.
- `2026-06-22-knowledge-watch-rebuild-drift-audit.md`: Drift audit for the
changed-source Knowledge Workspace rebuild and reviewable memory-candidate
proposal contract.
- `external_memory_pattern_radar_latest.md`: Latest weekly external memory pattern
radar summary.
4 changes: 4 additions & 0 deletions docs/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,7 @@ logs.
`cargo make real-world-memory-p1-closeout`, preserving the Source Library ->
Memory Candidate -> approved memory -> recall/debug -> correction/rollback
authority chain and keeping P2 queueing conditional on main-thread acceptance.
- Added the Knowledge Workspace changed-source watch/rebuild contract for XY-1065,
plus a drift audit covering the new admin rebuild endpoint, changed/unchanged/
stale/blocked section output, stale-section/changed-claim/missing-citation/conflict
classifications, and reviewable memory-candidate proposal routing.
6 changes: 6 additions & 0 deletions docs/spec/system_elf_memory_service_v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,7 @@ Behavior:

Admin derived knowledge pages:
- POST /v2/admin/knowledge/pages/rebuild
- POST /v2/admin/knowledge/pages/rebuild-changed-sources
- GET /v2/admin/knowledge/pages
- POST /v2/admin/knowledge/pages/search
- GET /v2/admin/knowledge/pages/{page_id}
Expand All @@ -1192,6 +1193,11 @@ Behavior:
lint for derived knowledge pages. The search endpoint exposes derived page section
snippets with visible citations, source coverage, lint summary, trust state, and
repair/rebuild guidance.
- The changed-source rebuild endpoint accepts changed source refs, finds only pages
already citing those refs, lints before rebuilding, returns changed/unchanged/stale/
blocked page and section states, emits stale-section, changed-claim,
missing-citation, and conflict outputs, and may queue reviewable memory candidates
through consolidation proposals.
- Page payloads must follow `elf.knowledge_page/v1`, preserve section citations, and
write normalized source refs for lint.
- Pages are derived and rebuildable; rebuilding or linting a page must not mutate
Expand Down
46 changes: 46 additions & 0 deletions docs/spec/system_knowledge_pages_v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ tags:
- spec
source_refs: []
code_refs:
- apps/elf-api/src/routes.rs
- packages/elf-domain/src/knowledge.rs
- packages/elf-service/src/knowledge.rs
- packages/elf-storage/src/knowledge.rs
Expand All @@ -20,6 +21,7 @@ code_refs:
related: []
drift_watch:
- docs/spec/system_knowledge_pages_v1.md
- apps/elf-api/src/routes.rs
- packages/elf-domain/src/knowledge.rs
- packages/elf-service/src/knowledge.rs
- packages/elf-storage/src/knowledge.rs
Expand Down Expand Up @@ -150,6 +152,42 @@ When future provider-backed or LLM-derived page text is persisted,
`rebuild_metadata.deterministic` must be false unless the provider output is fully
replayable from recorded metadata.

## Changed-Source Watch/Rebuild Contract

The changed-source watch/rebuild path exposes the deterministic operational loop for
source changes. Its response schema is `elf.knowledge_page.watch_rebuild/v1`.

Input:

- `changed_sources`: non-empty list of source refs with `source_kind` and `source_id`.
- `page_kind`: optional page-kind filter.
- `limit`: optional affected-page limit.
- `generate_memory_candidates`: optional boolean, default `true`.

Behavior:

- The service must look up only knowledge pages that already cite one of the supplied
changed source refs. It must not rebuild unrelated pages.
- For each affected page, the service must lint the currently stored page first, then
rebuild from that page's stored normalized source refs and current authoritative
source rows.
- A page that cannot resolve all stored sources or cannot rebuild must be returned as
`blocked` with an operator-readable reason. Other page states are `changed`,
`unchanged`, or `stale`.
- Per-section output must classify `changed`, `unchanged`, `stale`, or `blocked`
sections. Classified outputs must include:
- `stale_section` for stale or missing stored source snapshots.
- `changed_claim` for sections whose derived content changed after rebuild.
- `missing_citation` for citation or normalized backlink gaps.
- `conflict` when a stale stored section also changes after current-source rebuild.
- Responses must include operator-readable summary lines with affected, changed,
unchanged, stale, blocked, and memory-candidate counts.

The watch/rebuild path is a derived-artifact operation. It may update
`knowledge_pages`, `knowledge_page_sections`, `knowledge_page_source_refs`, and
`knowledge_page_lint_findings` for affected pages only. It must not mutate
authoritative source notes, docs, events, graph facts, traces, or source pointers.

## Lint Contract

The v1 lint path compares stored normalized source refs with current source rows.
Expand Down Expand Up @@ -193,6 +231,13 @@ When a page section becomes candidate memory, the candidate must be represented
proposal follows the Memory Promotion Apply Contract in
`system_consolidation_proposals_v1.md`.

Changed-source watch/rebuild may generate `MemoryCandidate` proposal payloads from
`changed_claim` or `conflict` knowledge deltas. These candidates must carry source
refs, source snapshots, a reason, a reviewable diff, and a proposed memory payload.
The service must route them through a queued consolidation run on the
`consolidation_proposals` review surface; it must not directly write the memory
ledger.

## Search and Viewer Readback

Knowledge page search is a derived-artifact readback surface, not the authoritative
Expand Down Expand Up @@ -223,6 +268,7 @@ authoritative memory notes.
Minimal admin readback endpoints:

- `POST /v2/admin/knowledge/pages/rebuild`
- `POST /v2/admin/knowledge/pages/rebuild-changed-sources`
- `GET /v2/admin/knowledge/pages`
- `POST /v2/admin/knowledge/pages/search`
- `GET /v2/admin/knowledge/pages/{page_id}`
Expand Down
2 changes: 2 additions & 0 deletions packages/elf-domain/src/knowledge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub const KNOWLEDGE_PAGE_REBUILD_SCHEMA_V1: &str = "elf.knowledge_page.rebuild/v
pub const KNOWLEDGE_PAGE_SOURCE_COVERAGE_SCHEMA_V1: &str = "elf.knowledge_page.source_coverage/v1";
/// Current previous-version diff metadata schema identifier.
pub const KNOWLEDGE_PAGE_VERSION_DIFF_SCHEMA_V1: &str = "elf.knowledge_page.version_diff/v1";
/// Current changed-source watch/rebuild response schema identifier.
pub const KNOWLEDGE_PAGE_WATCH_REBUILD_SCHEMA_V1: &str = "elf.knowledge_page.watch_rebuild/v1";

/// Derived knowledge page category.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
Expand Down
Loading