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
42 changes: 42 additions & 0 deletions crates/corecrux-memory/src/fact_privacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,26 @@ pub const DEFAULT_PRIVATE_PREFIXES: &[&str] = &[
"github::",
];

/// The marker that makes an entity a daemon-internal namespace: a leading
/// `__`, as in `__bootstrap__::`, `__coord__::`, `__work__::`.
///
/// 37 of the 39 [`DEFAULT_PRIVATE_PREFIXES`] use it; the two that do not
/// (`decisions::`, `github::`) are private *user* content rather than daemon
/// bookkeeping, which is exactly the distinction this predicate draws.
pub const INTERNAL_NAMESPACE_MARKER: &str = "__";

/// Whether an entity lives in a daemon-internal namespace.
///
/// Callers that assemble memory *for a human or a model* — recall surfaces,
/// injection bundles — should exclude these unless the caller named the
/// entity explicitly. They are the daemon's own bookkeeping and
/// documentation: real records, but not the user's memory, and on a fresh
/// node they outnumber it. Storage, export and audit paths must NOT use this;
/// they need the complete set.
pub fn is_internal_namespace(entity: &str) -> bool {
entity.starts_with(INTERNAL_NAMESPACE_MARKER)
}

/// Reserved entity namespaces owned exclusively by daemon governance flows.
///
/// Client-facing fact-write handlers must reject these prefixes before they
Expand Down Expand Up @@ -260,6 +280,28 @@ mod tests {
}
}

#[test]
fn internal_namespace_matches_the_reserved_prefixes() {
// Every `__`-prefixed default must be recognised, so a recall surface
// filtering on this predicate cannot miss one.
for prefix in DEFAULT_PRIVATE_PREFIXES.iter().filter(|p| p.starts_with("__")) {
assert!(is_internal_namespace(prefix), "{prefix} not detected as internal");
}
assert!(is_internal_namespace("__bootstrap__::doc:api-append"));
assert!(is_internal_namespace("__coord__::session"));
}

#[test]
fn internal_namespace_excludes_user_content() {
// These are private-by-default but they are the USER's records, not
// daemon bookkeeping — a recall surface must still return them.
assert!(!is_internal_namespace("decisions::pick-postgres"));
assert!(!is_internal_namespace("github::CueCrux/Crux"));
assert!(!is_internal_namespace("project:atlas"));
assert!(!is_internal_namespace("execplan:demo"));
assert!(!is_internal_namespace("_single_underscore::x"));
}

#[test]
fn defaults_cover_known_internal_prefixes() {
let p = PrivacyPolicy::from_prefixes(
Expand Down
55 changes: 55 additions & 0 deletions crates/corecruxd/src/http/context_surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ async fn gather_facts(

// 2. Keyword recall, effective-confidence ranked (spec §4 rule 2) —
// or the zero-hint default bundle (top facts overall).
//
// UNDIRECTED recall excludes daemon-internal namespaces (`__*::`).
// They are real records and they stay reachable — pass 1 above still
// resolves them when the caller names one — but they are the daemon's
// own bookkeeping and seeded documentation, not the user's memory, and
// on a fresh node they swamp it: measured 2026-08-07 against a node
// holding exactly one user fact, an authenticated `query=` returned 50
// facts of which 49 were `__bootstrap__::` docs. A user asking "what
// database do we use" got the daemon's own manual. `get_bootstrap` is
// the intended door to that content.
let keyword = req.query.as_deref().map(str::trim).filter(|q| !q.is_empty());
if keyword.is_some() || req.entity.is_none() {
let q = corecrux_memory::fact_store::FactQuery {
Expand All @@ -156,6 +166,9 @@ async fn gather_facts(
if fact.superseded_by.is_some() || !seen.insert(fact.fact_id.clone()) {
continue;
}
if corecrux_memory::fact_privacy::is_internal_namespace(&fact.entity) {
continue;
}
out.push(fact_input(fact, false));
}
}
Expand Down Expand Up @@ -650,6 +663,48 @@ mod tests {
);
}

#[tokio::test]
async fn undirected_recall_excludes_daemon_internal_namespaces() {
// The regression this guards: on a fresh node the seeded
// `__bootstrap__::` docs outnumber the user's memory, so an
// undirected query answered with the daemon's own manual.
let state = enabled_state();
store_fact(&state, "project:atlas", "database", "Postgres 16").await;
store_fact(&state, "__bootstrap__::doc:api-append", "content", "how to append").await;
store_fact(&state, "__coord__::session", "state", "internal bookkeeping").await;

for request in [req(None, None, Some(4000)), req(None, Some("database"), Some(4000))] {
let bundle = get_bundle(&state, request).await;
let entities: Vec<String> = facts_items(&bundle)
.iter()
.map(|f| f["entity"].as_str().unwrap_or_default().to_string())
.collect();
assert!(
entities.iter().any(|e| e == "project:atlas"),
"user fact missing from undirected recall: {entities:?}"
);
assert!(
!entities.iter().any(|e| e.starts_with("__")),
"internal namespace leaked into undirected recall: {entities:?}"
);
}
}

#[tokio::test]
async fn addressing_an_internal_entity_still_returns_it() {
// Excluded from undirected recall, NOT hidden. A caller that names the
// entity still gets it, so nothing becomes unreachable.
let state = enabled_state();
store_fact(&state, "__bootstrap__::doc:api-append", "content", "how to append").await;

let bundle = get_bundle(&state, req(Some("__bootstrap__::doc:api-append"), None, Some(4000))).await;
let entities: Vec<String> = facts_items(&bundle)
.iter()
.map(|f| f["entity"].as_str().unwrap_or_default().to_string())
.collect();
assert_eq!(entities, vec!["__bootstrap__::doc:api-append".to_string()]);
}

#[tokio::test]
async fn stable_region_is_byte_stable_across_calls() {
let state = enabled_state();
Expand Down
Loading