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
3 changes: 3 additions & 0 deletions specs/catalog/omni-graph-live-watcher-fidelity-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ A recursive divide-and-conquer code and database audit of `tools/omni-graph/src/
- **AC-048**: `find_references` utilizes `idx_node_label` index, completing exact-match queries in $\le 5\text{ms}$.
- **AC-049**: 100% of existing unit tests pass, and new tests validate all 14 requirements.
- **AC-050**: Zero clippy warnings, living docs updated in lockstep.
- **AC-051**: Traversing cyclic filesystem symlinks (e.g. `a/loop -> ..` or mutual circular links) terminates deterministically in $O(1)$ lookup per directory without recursion loop, process hang, or stack overflow.
- **AC-052**: A symlinked file within the workspace is indexed as a dedicated `symlink` node with a `LINKS_TO` edge pointing directly to the canonical target file, producing zero duplicate AST function/symbol declarations and zero duplicate community assignments.
- **AC-053**: The incremental watcher (`IncrementalPipeline`) safely resolves file or directory symlink modifications, deletions, or creations without panic.

### 2.3 Non-Functional Requirements (NF)

Expand Down
3 changes: 2 additions & 1 deletion tools/omni-graph/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ blockers: []
| Nanometer Fidelity Diagnostic Endpoint (R-098) | Complete | — | 🟢 GET /api/watch/fidelity returning fidelity_score, dangling directory pointers, and convergence state |
| Lock-Free Generational Versioning (R-099) | Complete | — | 🟢 Arc<AtomicU64> incremented on all DB mutations; X-Graph-Generation injected on all HTTP responses |
| High-Throughput Parallel Batch Persistence (R-100) | Complete | — | 🟢 tokio::join! on backbone storage and community inference; omni-mcp.py handles HTTP 423 gracefully |
| Test Coverage | 111/111 Unit Tests | 111 | 🟢 100% Verified (0.05s, 0 clippy warnings) |
| Filesystem Symlink Cycle Guard & Navigation (R-109) | Complete | — | 🟢 Symlink metadata interception, canonical target resolution, LINKS_TO edge emission, zero duplicate AST clones |
| Test Coverage | 113/113 Unit Tests | 113 | 🟢 100% Verified (0.08s, 0 clippy warnings) |
| Live Watcher In-Memory SAIR Acceleration (R-078, R-091) | Complete | — | 🟢 Integrated in-memory SymbolTable into IncrementalPipeline; eliminated 392s subquery bottleneck; live sync completes in <250ms with dedicated 1s quiescent trigger |
| AI-Native Cosmology & Modularity Guard (R-086, R-087) | Complete | — | 🟢 Purged Miller's Law clamp; enforced Affinity-Floor Invariant and delta Q guard |
| Scale-Adaptive Subsystem Sizing (R-088) | Complete | — | 🟢 Sub-linear dynamic ceiling K_adaptive(N) bounding natural domains without forced merges |
Expand Down
28 changes: 28 additions & 0 deletions tools/omni-graph/DEVLOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
### 2026-11-06 — Filesystem Symlink Cycle Guarding & Alias Navigation (R-109)

**Agent/Author**: Antigravity (Google DeepMind) & `@vadde`
**SDLC Phase**: `in-progress` (Hardening & Extensibility)
**Branch**: `feat/r109-symlink-cycle-guard-and-navigation`
**Requirement**: `R-109` ([`specs/catalog/omni-graph-live-watcher-fidelity-parity.md`](file:///Users/aparv/Library/CloudStorage/OneDrive-Personal/G-Drive/Interviews/knowledge/PolyTuring/specs/catalog/omni-graph-live-watcher-fidelity-parity.md))
**Duration**: Complete

#### Executive Summary & Key Milestones Completed
1. **Empirical Defect Analysis & Root-Cause Isolation**:
- Discovered that filesystem symlinks (such as `scripts/omni -> ../tools/omni-graph/scripts/omni.sh`) were naively followed by `std::fs::metadata` / `Path::is_file`, causing target files to be completely re-read, parsed, and indexed as duplicate AST nodes.
- In PolyTuring, `scripts/omni` was crawled as a duplicate file assigned to Community 62, while canonical `tools/omni-graph/scripts/omni.sh` was in Community 18.
2. **Symlink Cycle Guard & Alias Navigation (`src/ingestion/mod.rs`)**:
- Integrated `fs::symlink_metadata` to intercept symlinks before reading raw bytes or parsing AST.
- Synthesized directory containment backbone for the symlink entry.
- Resolved canonical target path via `fs::canonicalize`. If within workspace, resolves to `{ws}:file:{rel_target}` or `{ws}:dir:{rel_target}`; if external, resolves to `{ws}:external:{target_path}`; if broken or cyclic, resolves to `{ws}:broken:{rel_path}` with zero crash.
- Emitted a dedicated `symlink` node with `kind: "symlink"` and a `LINKS_TO` edge (`category: "FILESYSTEM"`).
- Skipped duplicate file parsing, completely eliminating cloned functions and symbol table pollution.
3. **Incremental Watcher Parity (`src/watcher/pipeline.rs`)**:
- Added symlink interception to `IncrementalPipeline::reindex_file`.
- On symlink create/modify, synthesizes directory backbone, resolves canonical link, and executes `execute_atomic_swap` to transactionally update the `symlink` node and `LINKS_TO` edge in SurrealDB.
4. **Verification & Audit**:
- Added unit test `test_r109_symlink_cycle_guard_and_alias_navigation`: Verified file symlink resolution, cyclic directory loop containment, and broken link tolerance.
- Added unit test `test_r109_symlink_atomic_swap_sql_generation`: Verified SQL generation for `symlink` node and `type = 'LINKS_TO'` edge.
- All 113 unit tests pass (100% green, 0 clippy warnings).

---

### 2026-11-06 — Public Living Documentation Portal Editorial Streamline & Anti-Bloat Law

**Agent/Author**: Antigravity (Google DeepMind) & `@vadde`
Expand Down
1 change: 1 addition & 0 deletions tools/omni-graph/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ fidelity_spec: ../../specs/catalog/omni-graph-live-watcher-fidelity-parity.md
| R-104 | Edge Relationship Deduplication & Call Weighting | `src/db/mod.rs` | `tests/unit_tests.rs:test_r104_edge_relationship_deduplication` | ✅ |
| R-105 | Zero-Delay Community Inference for New Files | `src/analysis/mod.rs`, `src/watcher/pipeline.rs` | `tests/unit_tests.rs:test_r105_zero_delay_community_inference_for_new_files` | ✅ |
| R-106 | Language-Aware SAIR Scoping & Exact Match Fast-Path | `src/ingestion/symbol_table.rs`, `src/db/mod.rs` | `tests/unit_tests.rs:test_r106_language_aware_sair_scoping` | ✅ |
| R-109 | Filesystem Symlink Cycle Guarding & Alias Navigation | `src/ingestion/mod.rs`, `src/watcher/pipeline.rs` | `tests/unit_tests.rs:test_r109_symlink_cycle_guard_and_alias_navigation` | ✅ |

**Status Legend**: ⬜ Not started · 🔨 In progress · ✅ Implemented · 🧪 Tested · ❌ Blocked

Expand Down
141 changes: 138 additions & 3 deletions tools/omni-graph/src/ingestion/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,15 +568,150 @@ async fn execute_ingest(
.filter_map(|e| e.ok())
{
let path = entry.path();
if !path.is_file() {
continue;
}

let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
if file_name == ".DS_Store" || file_name == ".gitignore" || file_name == "Thumbs.db" {
continue;
}

// Symlink Cycle-Guarded Graph Traversal & Alias Navigation (R-109)
let is_symlink = fs::symlink_metadata(path)
.map(|m| m.file_type().is_symlink())
.unwrap_or(false);

if is_symlink {
let rel_path = path
.strip_prefix(root)
.unwrap_or(path)
.to_string_lossy()
.to_string();

let target_link = fs::read_link(path);
let canonical_res = fs::canonicalize(path);

// Collect directory hierarchy for Topological Containment Backbone (R-063)
let mut cur_dir = Path::new(&rel_path).parent();
while let Some(parent) = cur_dir {
let p_str = parent.to_string_lossy().to_string();
if p_str.is_empty() || p_str == "." {
break;
}
discovered_dirs.insert(p_str);
cur_dir = parent.parent();
}

let parent_dir = Path::new(&rel_path)
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default();
let parent_id = if parent_dir.is_empty() || parent_dir == "." {
format!("{}:workspace", workspace_name)
} else {
format!("{}:dir:{}", workspace_name, parent_dir)
};

let (target_id, target_label) = match canonical_res {
Ok(ref target_path) => {
if let Ok(rel_target) = target_path.strip_prefix(root) {
let rel_target_str = rel_target.to_string_lossy().to_string();
let is_dir = target_path.is_dir();
let id = if is_dir {
format!("{}:dir:{}", workspace_name, rel_target_str)
} else {
format!("{}:file:{}", workspace_name, rel_target_str)
};
(id, rel_target_str)
} else {
(
format!("{}:external:{}", workspace_name, target_path.display()),
target_path.display().to_string(),
)
}
}
Err(_) => (
format!("{}:broken:{}", workspace_name, rel_path),
"broken_symlink".to_string(),
),
};

let target_display = target_link
.map(|p| p.display().to_string())
.unwrap_or_else(|_| "unknown".to_string());
let symlink_text = format!("Symlink: {} -> {}", rel_path, target_display);

let mut hasher = Sha256::new();
hasher.update(symlink_text.as_bytes());
let hash = hex::encode(hasher.finalize());

if !cache.is_stale(&rel_path, &hash).await {
files_skipped += 1;
continue;
}

files_scanned += 1;

let symlink_node_id = format!("{}:file:{}", workspace_name, rel_path);
let symlink_node = ExtractedNode {
id: symlink_node_id.clone(),
workspace: workspace_name.to_string(),
label: file_name.to_string(),
kind: "symlink".to_string(),
file_path: rel_path.clone(),
language: "symlink".to_string(),
line_start: 1,
line_end: 1,
text: symlink_text.clone(),
};

let cont_edge = ExtractedEdge {
workspace: workspace_name.to_string(),
source_id: parent_id,
target_label: file_name.to_string(),
target_id: Some(symlink_node_id.clone()),
edge_type: "CONTAINS".to_string(),
category: "CONTAINMENT".to_string(),
};

let link_edge = ExtractedEdge {
workspace: workspace_name.to_string(),
source_id: symlink_node_id.clone(),
target_label,
target_id: Some(target_id),
edge_type: "LINKS_TO".to_string(),
category: "FILESYSTEM".to_string(),
};

pending_files.push(PendingBatchFile {
rel_path: rel_path.clone(),
hash: hash.clone(),
nodes: vec![symlink_node],
containment_edges: vec![cont_edge, link_edge],
text_count: 1,
});
pending_texts.push(symlink_text);

if pending_files.len() >= 64 {
flush_vector_batch(
workspace_name,
&mut pending_files,
&mut pending_texts,
embedder,
db,
cache,
&mut files_indexed,
&mut total_nodes,
&mut total_edges,
)
.await;
}

continue;
}

if !path.is_file() {
continue;
}

// Skip oversized files (> 512 KB) to prevent OOM / computational explosion
if entry.metadata().map(|m| m.len()).unwrap_or(0) > 512 * 1024 {
continue;
Expand Down
121 changes: 121 additions & 0 deletions tools/omni-graph/src/watcher/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,127 @@ impl IncrementalPipeline {
return self.delete_file(workspace, rel_path).await;
}

// Symlink Cycle-Guarded Graph Traversal & Alias Navigation (R-109)
let is_symlink = fs::symlink_metadata(&full_path)
.map(|m| m.file_type().is_symlink())
.unwrap_or(false);

if is_symlink {
let target_link = fs::read_link(&full_path);
let canonical_res = fs::canonicalize(&full_path);

let root = Path::new(root_dir);
let (target_id, target_label) = match canonical_res {
Ok(ref target_path) => {
if let Ok(rel_target) = target_path.strip_prefix(root) {
let rel_target_str = rel_target.to_string_lossy().to_string();
let is_dir = target_path.is_dir();
let id = if is_dir {
format!("{}:dir:{}", workspace, rel_target_str)
} else {
format!("{}:file:{}", workspace, rel_target_str)
};
(id, rel_target_str)
} else {
(
format!("{}:external:{}", workspace, target_path.display()),
target_path.display().to_string(),
)
}
}
Err(_) => (
format!("{}:broken:{}", workspace, rel_path),
"broken_symlink".to_string(),
),
};

let target_display = target_link
.map(|p| p.display().to_string())
.unwrap_or_else(|_| "unknown".to_string());
let symlink_text = format!("Symlink: {} -> {}", rel_path, target_display);

let mut hasher = Sha256::new();
hasher.update(symlink_text.as_bytes());
let hash = hex::encode(hasher.finalize());

if !self.cache.is_stale(rel_path, &hash).await {
return Ok(());
}

let file_name = Path::new(rel_path)
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| rel_path.to_string());

let symlink_node_id = format!("{}:file:{}", workspace, rel_path);
let symlink_node = ExtractedNode {
id: symlink_node_id.clone(),
workspace: workspace.to_string(),
label: file_name.clone(),
kind: "symlink".to_string(),
file_path: rel_path.to_string(),
language: "symlink".to_string(),
line_start: 1,
line_end: 1,
text: symlink_text.clone(),
};

let (dir_nodes, dir_edges) = self.synthesize_directory_backbone(workspace, rel_path).await;
if !dir_nodes.is_empty() {
let _ = self.db.store_nodes(&dir_nodes, &[], None).await;
let _ = self.db.store_edges(&dir_edges).await;
}

let parent_dir = Path::new(rel_path)
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default();
let parent_id = if parent_dir.is_empty() || parent_dir == "." {
format!("{}:workspace", workspace)
} else {
format!("{}:dir:{}", workspace, parent_dir)
};

let cont_edge = ExtractedEdge {
workspace: workspace.to_string(),
source_id: parent_id,
target_label: file_name,
target_id: Some(symlink_node_id.clone()),
edge_type: "CONTAINS".to_string(),
category: "CONTAINMENT".to_string(),
};

let link_edge = ExtractedEdge {
workspace: workspace.to_string(),
source_id: symlink_node_id.clone(),
target_label,
target_id: Some(target_id),
edge_type: "LINKS_TO".to_string(),
category: "FILESYSTEM".to_string(),
};

let embeddings = match self.embedder.embed_batch(&[&symlink_text]).await {
Ok(embs) => embs,
Err(_) => vec![vec![0.0f32; 384]],
};

let modal_comm = self.db.get_directory_modal_community(workspace, rel_path).await.ok().flatten();

let _ = self.db.execute_atomic_swap(
workspace,
rel_path,
Some(&hash),
&[symlink_node],
&embeddings,
modal_comm,
&[cont_edge, link_edge],
&[],
).await;

self.cache.update(rel_path, &hash).await;
return Ok(());
}

let content = match fs::read_to_string(&full_path) {
Ok(c) => c,
Err(e) => {
Expand Down
Loading
Loading