diff --git a/docs/log.md b/docs/log.md index 1d398c4f..df953b68 100644 --- a/docs/log.md +++ b/docs/log.md @@ -170,3 +170,6 @@ logs. - Preserved historical benchmark evidence rows that recorded the former singular check command, and constrained docs task validation to allow only those exact legacy evidence references. +- Added the XY-1153 Knowledge Workspace authority-boundary marker for changed-source + memory candidates so derived page deltas remain reviewable consolidation proposals + and cannot directly mutate Memory Authority or source evidence. diff --git a/docs/spec/system_knowledge_pages_v1.md b/docs/spec/system_knowledge_pages_v1.md index 93749b59..e507b862 100644 --- a/docs/spec/system_knowledge_pages_v1.md +++ b/docs/spec/system_knowledge_pages_v1.md @@ -176,6 +176,15 @@ nested source refs. - `allowed_variance` - `previous_version_diff` +Knowledge delta memory candidates produced by changed-source watch/rebuild are +review proposals only. Their proposed payload and diff must carry +`elf.knowledge_delta.authority_boundary/v1` with +`authority = "derived_non_authoritative"`, +`review_surface = "consolidation_proposals"`, +`promotion_required_for_memory_authority = true`, +`direct_memory_ledger_mutation_allowed = false`, and +`source_mutation_allowed = false`. + `previous_version_diff` must use schema `elf.knowledge_page.version_diff/v1`. Initial rebuilds must set `available = false` and explain that no previous version exists. Later rebuilds must set `available = true` and include previous and new diff --git a/packages/elf-service/src/knowledge/tests_helpers.rs b/packages/elf-service/src/knowledge/tests_helpers.rs index 4cd39bcb..c11083a0 100644 --- a/packages/elf-service/src/knowledge/tests_helpers.rs +++ b/packages/elf-service/src/knowledge/tests_helpers.rs @@ -165,6 +165,14 @@ pub(super) fn assert_candidate_is_reviewable(candidate: &KnowledgeDeltaMemoryCan assert_eq!(candidate.source_refs[0].kind.as_str(), "note"); assert_eq!(candidate.source_snapshot["source_mutation_allowed"], false); assert_eq!(candidate.diff.after["reason"], "changed_claim"); + assert_eq!( + candidate.diff.after["authority_boundary"]["review_surface"], + "consolidation_proposals" + ); assert_eq!(candidate.proposed_payload["type"], "plan"); + assert_eq!( + candidate.proposed_payload["authority_boundary"]["authority"], + "derived_non_authoritative" + ); assert_eq!(candidate.proposed_payload["source_ref"]["schema"], "elf.knowledge_delta/v1"); } diff --git a/packages/elf-service/src/knowledge/tests_memory.rs b/packages/elf-service/src/knowledge/tests_memory.rs index 82d3fea1..0cc4388d 100644 --- a/packages/elf-service/src/knowledge/tests_memory.rs +++ b/packages/elf-service/src/knowledge/tests_memory.rs @@ -25,7 +25,20 @@ fn memory_candidate_uses_reviewable_consolidation_proposal_contract() { assert_eq!(proposal.apply_intent, ConsolidationApplyIntent::CreateDerivedNote); assert_eq!(proposal.source_refs.len(), 1); + assert_eq!(proposal.diff.after["authority_boundary"]["authority"], "derived_non_authoritative"); + assert_eq!( + proposal.diff.after["authority_boundary"]["direct_memory_ledger_mutation_allowed"], + false + ); assert_eq!(proposal.proposed_payload["source_ref"]["source_mutation_allowed"], false); + assert_eq!( + proposal.proposed_payload["authority_boundary"]["review_surface"], + "consolidation_proposals" + ); + assert_eq!( + proposal.proposed_payload["authority_boundary"]["promotion_required_for_memory_authority"], + true + ); assert_eq!(proposal.proposed_payload["source_ref"]["reason"], "changed_claim"); assert!(!proposal.markers.staleness.is_empty()); } diff --git a/packages/elf-service/src/knowledge/watch/candidates/proposal.rs b/packages/elf-service/src/knowledge/watch/candidates/proposal.rs index b0db3907..b13ccf2a 100644 --- a/packages/elf-service/src/knowledge/watch/candidates/proposal.rs +++ b/packages/elf-service/src/knowledge/watch/candidates/proposal.rs @@ -48,6 +48,7 @@ pub(in crate::knowledge::watch::candidates) fn candidate_diff( "page_id": page.page.page_id, "section_id": section.section_id, "section_key": section.section_key, + "authority_boundary": knowledge_delta_authority_boundary(), }), } } @@ -77,6 +78,7 @@ pub(in crate::knowledge::watch::candidates) fn candidate_proposed_payload( "scope": "project_shared", "importance": 0.65, "confidence": 0.72, + "authority_boundary": knowledge_delta_authority_boundary(), "source_ref": { "schema": "elf.knowledge_delta/v1", "reason": reason, @@ -89,6 +91,18 @@ pub(in crate::knowledge::watch::candidates) fn candidate_proposed_payload( }) } +fn knowledge_delta_authority_boundary() -> Value { + serde_json::json!({ + "schema": "elf.knowledge_delta.authority_boundary/v1", + "origin_layer": "knowledge_workspace", + "authority": "derived_non_authoritative", + "review_surface": "consolidation_proposals", + "promotion_required_for_memory_authority": true, + "direct_memory_ledger_mutation_allowed": false, + "source_mutation_allowed": false, + }) +} + fn candidate_markers(candidate: &KnowledgeDeltaMemoryCandidate) -> ConsolidationMarkers { let marker = ConsolidationMarker { severity: ConsolidationMarkerSeverity::Medium,