Studio: remove dead RAG code (AST-confirmed)
Remove code with no live references, each confirmed dead via AST reference analysis (no production callers and no importers), not just text search: - chunk_belongs_to_document plus its dedicated tests and the now-orphaned _insert_chunk test helper. The preview-target route already does a single-query membership check and deliberately never called this helper. - ingestion-progress.tsx and use-ingestion-events.ts (its only importer). Superseded by the aggregate ingestion toast stack; zero importers. - Unreferenced tests/fixtures/rag-preview sample files and their generator. No behavior change. The only non-deletion edits reword two comments that referenced the removed helper.
This commit is contained in:
parent
d1348cac3f
commit
f3bbd53afa
8 changed files with 6 additions and 284 deletions
|
|
@ -95,24 +95,3 @@ def document_for_subject_or_404(
|
|||
# Docs must belong to a KB or a thread (DB CHECK enforces XOR on insert);
|
||||
# a row satisfying neither is corrupt — treat as 404.
|
||||
raise HTTPException(status_code = 404, detail = _NOT_FOUND_DETAIL)
|
||||
|
||||
|
||||
def chunk_belongs_to_document(chunk_id: str, document_id: str) -> bool:
|
||||
"""True iff `chunk_id` exists in `rag_chunks` for `document_id`.
|
||||
|
||||
Used by `/preview-target?chunk_id=...` after the caller has
|
||||
already established subject authorization for `document_id`. Does
|
||||
NOT perform authorization itself: callers MUST call
|
||||
`document_for_subject_or_404(document_id, ...)` first, otherwise a
|
||||
valid `chunk_id` from another subject's document would leak via a
|
||||
`True` return.
|
||||
"""
|
||||
if not chunk_id or not document_id:
|
||||
return False
|
||||
|
||||
with get_connection() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT 1 FROM rag_chunks WHERE id = ? AND document_id = ?",
|
||||
(chunk_id, document_id),
|
||||
).fetchone()
|
||||
return row is not None
|
||||
|
|
|
|||
|
|
@ -1458,10 +1458,10 @@ def get_document_preview_target(
|
|||
)
|
||||
|
||||
# One connection enforces membership AND fetches the row in a single query.
|
||||
# A separate `chunk_belongs_to_document` call would open a second SQLite
|
||||
# connection and open a TOCTOU window — if the chunk is deleted between the
|
||||
# two calls, the fetch returns None and the route 500s (D1.1). Cross-document
|
||||
# collapses to the same 404 — never 400 (would leak doc existence).
|
||||
# A separate membership check would open a second SQLite connection and a
|
||||
# TOCTOU window — if the chunk is deleted between the two calls, the fetch
|
||||
# returns None and the route 500s (D1.1). Cross-document collapses to the
|
||||
# same 404 — never 400 (would leak doc existence).
|
||||
with get_connection() as conn:
|
||||
chunk_row = conn.execute(
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||
|
||||
"""Tests for document_for_subject_or_404 and chunk_belongs_to_document.
|
||||
"""Tests for document_for_subject_or_404.
|
||||
|
||||
Authorization rules under test (contracts.md §1 / §2, Risk #1):
|
||||
|
||||
|
|
@ -13,7 +13,6 @@ Authorization rules under test (contracts.md §1 / §2, Risk #1):
|
|||
- KB with NULL owner_user_id is NOT accessible (legacy row guard).
|
||||
- Both not-found and not-authorized return HTTP 404 with identical detail to
|
||||
prevent document-existence leaking.
|
||||
- chunk_belongs_to_document only returns True when chunk.document_id matches.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -24,10 +23,7 @@ import pytest
|
|||
from fastapi import HTTPException
|
||||
|
||||
import storage.studio_db as studio_db
|
||||
from core.rag.authorization import (
|
||||
chunk_belongs_to_document,
|
||||
document_for_subject_or_404,
|
||||
)
|
||||
from core.rag.authorization import document_for_subject_or_404
|
||||
|
||||
|
||||
# ── Fixtures ──────────────────────────────────────────────────────────
|
||||
|
|
@ -88,16 +84,6 @@ def _insert_thread_doc(
|
|||
)
|
||||
|
||||
|
||||
def _insert_chunk(conn, chunk_id: str, doc_id: str, chunk_index: int = 0) -> None:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO rag_chunks (id, document_id, chunk_index, text, token_count)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(chunk_id, doc_id, chunk_index, "some chunk text", 20),
|
||||
)
|
||||
|
||||
|
||||
# ── KB-document authorization ─────────────────────────────────────────
|
||||
|
||||
|
||||
|
|
@ -224,49 +210,3 @@ def test_empty_subject_raises_404(tmp_path, monkeypatch):
|
|||
with pytest.raises(HTTPException) as exc_info:
|
||||
document_for_subject_or_404(doc_id, "")
|
||||
assert exc_info.value.status_code == 404
|
||||
|
||||
|
||||
# ── chunk_belongs_to_document ─────────────────────────────────────────
|
||||
|
||||
|
||||
def test_chunk_belongs_returns_true_for_matching_doc(tmp_path, monkeypatch):
|
||||
"""chunk_belongs_to_document returns True when chunk.document_id matches."""
|
||||
_reset_db(tmp_path, monkeypatch)
|
||||
doc_id, kb_id, chunk_id = _uid(), _uid(), _uid()
|
||||
with studio_db.get_connection() as conn:
|
||||
_insert_kb(conn, kb_id, owner = "alice")
|
||||
_insert_kb_doc(conn, doc_id, kb_id)
|
||||
_insert_chunk(conn, chunk_id, doc_id)
|
||||
assert chunk_belongs_to_document(chunk_id, doc_id) is True
|
||||
|
||||
|
||||
def test_chunk_belongs_returns_false_for_wrong_doc(tmp_path, monkeypatch):
|
||||
"""chunk_belongs_to_document returns False when chunk belongs to a different document."""
|
||||
_reset_db(tmp_path, monkeypatch)
|
||||
kb_id = _uid()
|
||||
doc_a, doc_b, chunk_id = _uid(), _uid(), _uid()
|
||||
with studio_db.get_connection() as conn:
|
||||
_insert_kb(conn, kb_id, owner = "alice")
|
||||
_insert_kb_doc(conn, doc_a, kb_id, "a.pdf")
|
||||
_insert_kb_doc(conn, doc_b, kb_id, "b.pdf")
|
||||
_insert_chunk(conn, chunk_id, doc_a)
|
||||
# chunk is doc_a's; probing doc_b → False.
|
||||
assert chunk_belongs_to_document(chunk_id, doc_b) is False
|
||||
|
||||
|
||||
def test_chunk_belongs_returns_false_for_missing_chunk(tmp_path, monkeypatch):
|
||||
"""chunk_belongs_to_document returns False for a nonexistent chunk_id."""
|
||||
_reset_db(tmp_path, monkeypatch)
|
||||
doc_id, kb_id = _uid(), _uid()
|
||||
with studio_db.get_connection() as conn:
|
||||
_insert_kb(conn, kb_id, owner = "alice")
|
||||
_insert_kb_doc(conn, doc_id, kb_id)
|
||||
assert chunk_belongs_to_document("ghost-chunk-id", doc_id) is False
|
||||
|
||||
|
||||
def test_chunk_belongs_returns_false_for_empty_inputs(tmp_path, monkeypatch):
|
||||
"""chunk_belongs_to_document returns False for empty inputs without DB access."""
|
||||
_reset_db(tmp_path, monkeypatch)
|
||||
assert chunk_belongs_to_document("", "some-doc") is False
|
||||
assert chunk_belongs_to_document("some-chunk", "") is False
|
||||
assert chunk_belongs_to_document("", "") is False
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useIngestionEvents } from "../hooks/use-ingestion-events";
|
||||
|
||||
const STAGE_LABELS: Record<string, string> = {
|
||||
queued: "Queued",
|
||||
parse: "Parsing document",
|
||||
caption_images: "Captioning images",
|
||||
extract_images: "Extracting images",
|
||||
load_model: "Loading embedder",
|
||||
chunk: "Chunking text",
|
||||
embed: "Embedding chunks",
|
||||
done: "Indexing complete",
|
||||
};
|
||||
|
||||
export function IngestionProgress({
|
||||
jobId,
|
||||
className,
|
||||
}: {
|
||||
jobId: string;
|
||||
className?: string;
|
||||
}) {
|
||||
const event = useIngestionEvents(jobId);
|
||||
if (!event) {
|
||||
return (
|
||||
<div className={cn("text-xs text-muted-foreground", className)}>
|
||||
Starting…
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (event.type === "error") {
|
||||
return (
|
||||
<div className={cn("text-xs text-destructive", className)}>
|
||||
{event.error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (event.type === "cancelled") {
|
||||
return (
|
||||
<div className={cn("text-xs text-muted-foreground", className)}>
|
||||
Cancelled
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (event.type === "complete") {
|
||||
const chunks = event.num_chunks;
|
||||
return (
|
||||
<div className={cn("text-xs text-muted-foreground", className)}>
|
||||
1 document and {chunks} chunk{chunks === 1 ? "" : "s"} indexed
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const stage =
|
||||
"stage" in event && event.stage ? (event.stage as string) : "queued";
|
||||
const progress =
|
||||
"progress" in event && typeof event.progress === "number"
|
||||
? event.progress
|
||||
: 0;
|
||||
const label = STAGE_LABELS[stage] ?? stage;
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-1.5", className)}>
|
||||
<div className="flex items-center justify-between gap-3 text-xs text-muted-foreground">
|
||||
<span className="truncate">{label}</span>
|
||||
<span className="shrink-0 tabular-nums">{Math.round(progress * 100)}%</span>
|
||||
</div>
|
||||
<Progress value={Math.round(progress * 100)} className="h-1" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRagStore } from "../stores/rag-store";
|
||||
|
||||
/** Subscribe to a job's SSE; returns latest event, null skips. */
|
||||
export function useIngestionEvents(jobId: string | null) {
|
||||
const event = useRagStore((s) =>
|
||||
jobId ? (s.jobs[jobId] ?? null) : null,
|
||||
);
|
||||
const subscribeJob = useRagStore((s) => s.subscribeJob);
|
||||
|
||||
useEffect(() => {
|
||||
if (jobId) subscribeJob(jobId);
|
||||
}, [jobId, subscribeJob]);
|
||||
|
||||
return event;
|
||||
}
|
||||
93
tests/fixtures/rag-preview/make_fixture_pdf.py
vendored
93
tests/fixtures/rag-preview/make_fixture_pdf.py
vendored
|
|
@ -1,93 +0,0 @@
|
|||
"""Generate tests/fixtures/rag-preview/sample.pdf deterministically.
|
||||
|
||||
Run once: python tests/fixtures/rag-preview/make_fixture_pdf.py
|
||||
Requires no third-party deps — builds a minimal valid single-page PDF
|
||||
using only stdlib so the fixture can be regenerated in any environment.
|
||||
The output is committed alongside this script so tests load it directly.
|
||||
"""
|
||||
|
||||
import os
|
||||
import struct
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
OUTPUT = Path(__file__).parent / "sample.pdf"
|
||||
|
||||
|
||||
def _compress(data: bytes) -> bytes:
|
||||
return zlib.compress(data, level = 9)
|
||||
|
||||
|
||||
def _pdf() -> bytes:
|
||||
# Minimal one-page PDF 1.4: header, catalog, pages, page, content
|
||||
# stream, xref, trailer.
|
||||
page_text = b"BT /F1 12 Tf 72 720 Td (RAG preview fixture - page 1) Tj ET"
|
||||
compressed = _compress(page_text)
|
||||
stream_len = len(compressed)
|
||||
|
||||
objects: list[bytes] = []
|
||||
|
||||
def obj(n: int, body: bytes) -> bytes:
|
||||
return f"{n} 0 obj\n".encode() + body + b"\nendobj\n"
|
||||
|
||||
# 1: Catalog
|
||||
objects.append(obj(1, b"<< /Type /Catalog /Pages 2 0 R >>"))
|
||||
# 2: Pages
|
||||
objects.append(obj(2, b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>"))
|
||||
# 3: Page
|
||||
objects.append(
|
||||
obj(
|
||||
3,
|
||||
(
|
||||
b"<< /Type /Page /Parent 2 0 R "
|
||||
b"/MediaBox [0 0 612 792] "
|
||||
b"/Contents 4 0 R "
|
||||
b"/Resources << /Font << /F1 5 0 R >> >> >>"
|
||||
),
|
||||
)
|
||||
)
|
||||
# 4: Content stream
|
||||
objects.append(
|
||||
obj(
|
||||
4,
|
||||
(
|
||||
f"<< /Length {stream_len} /Filter /FlateDecode >>".encode()
|
||||
+ b"\nstream\n"
|
||||
+ compressed
|
||||
+ b"\nendstream"
|
||||
),
|
||||
)
|
||||
)
|
||||
# 5: Font
|
||||
objects.append(
|
||||
obj(
|
||||
5,
|
||||
b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
|
||||
)
|
||||
)
|
||||
|
||||
header = b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n"
|
||||
body = b""
|
||||
offsets: list[int] = []
|
||||
for o in objects:
|
||||
offsets.append(len(header) + len(body))
|
||||
body += o
|
||||
|
||||
xref_offset = len(header) + len(body)
|
||||
n = len(objects)
|
||||
xref = f"xref\n0 {n + 1}\n".encode()
|
||||
xref += b"0000000000 65535 f \n"
|
||||
for off in offsets:
|
||||
xref += f"{off:010d} 00000 n \n".encode()
|
||||
trailer = (
|
||||
f"trailer\n<< /Size {n + 1} /Root 1 0 R >>\n"
|
||||
f"startxref\n{xref_offset}\n%%EOF\n"
|
||||
).encode()
|
||||
|
||||
return header + body + xref + trailer
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pdf_bytes = _pdf()
|
||||
OUTPUT.write_bytes(pdf_bytes)
|
||||
print(f"Written {len(pdf_bytes)} bytes to {OUTPUT}")
|
||||
BIN
tests/fixtures/rag-preview/sample.pdf
vendored
BIN
tests/fixtures/rag-preview/sample.pdf
vendored
Binary file not shown.
8
tests/fixtures/rag-preview/sample.txt
vendored
8
tests/fixtures/rag-preview/sample.txt
vendored
|
|
@ -1,8 +0,0 @@
|
|||
This is a test document for RAG preview fixtures.
|
||||
|
||||
Section 1: Introduction
|
||||
The operating margin rose to 18.2% in Q3, driven by improved efficiency.
|
||||
|
||||
Section 2: Details
|
||||
Additional supporting evidence and analysis is contained here.
|
||||
Page 1 of 1.
|
||||
Loading…
Add table
Add a link
Reference in a new issue