forked from VectifyAI/PageIndex
-
Notifications
You must be signed in to change notification settings - Fork 0
refactor(filesystem): simplify PIFS core surface #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
BukeLy
merged 14 commits into
feat/pageindex-filesystem
from
refactor/pifs-core-simplification
Jul 14, 2026
Merged
Changes from 9 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
8a9f590
refactor(filesystem): simplify PIFS core surface
BukeLy bd03e15
fix(filesystem): enforce exact PIFS schema validation
BukeLy 121bef9
fix(filesystem): close PIFS migration validation gaps
BukeLy 120b1ea
fix(filesystem): validate workspace consistency
BukeLy 30df722
fix(filesystem): reject broken projection symlinks
BukeLy 81ee9ba
fix(filesystem): preserve actionable file locators
BukeLy 0447889
fix(filesystem): complete projections for registrations
BukeLy eea969c
fix(filesystem): restore registration rollback baseline
BukeLy 867fd8f
fix(filesystem): restore PageIndex registration artifacts
BukeLy 89c6208
fix(filesystem): preserve registration rollback state
BukeLy 2b235bd
fix(filesystem): preflight registration mutations
BukeLy 4a784f7
fix(filesystem): require complete workspace projection
BukeLy 0559180
fix(filesystem): close PIFS consistency gaps
BukeLy 0b105ec
fix(filesystem): encode metadata dot segments
BukeLy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,51 +1,3 @@ | ||
| from importlib import import_module | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from .commands import PIFSCommandExecutor | ||
| from .core import PageIndexFileSystem | ||
| from .types import OpenResult, SearchResult | ||
|
|
||
| if TYPE_CHECKING: | ||
| from .semantic_projection import SemanticProjectionSearchBackend | ||
| from .semantic_projection import SummaryProjectionIndexer | ||
| from .semantic_index import ( | ||
| RebuildableSemanticIndex, | ||
| SemanticIndexRecord, | ||
| SemanticSearchResult, | ||
| SQLiteVecSemanticIndex, | ||
| ) | ||
|
|
||
| _LAZY_EXPORTS = { | ||
| "SemanticProjectionSearchBackend": (".semantic_projection", "SemanticProjectionSearchBackend"), | ||
| "RebuildableSemanticIndex": (".semantic_index", "RebuildableSemanticIndex"), | ||
| "SemanticIndexRecord": (".semantic_index", "SemanticIndexRecord"), | ||
| "SemanticSearchResult": (".semantic_index", "SemanticSearchResult"), | ||
| "SQLiteVecSemanticIndex": (".semantic_index", "SQLiteVecSemanticIndex"), | ||
| "SummaryProjectionIndexer": (".semantic_projection", "SummaryProjectionIndexer"), | ||
| } | ||
|
|
||
| __all__ = [ | ||
| "OpenResult", | ||
| "SemanticProjectionSearchBackend", | ||
| "PIFSCommandExecutor", | ||
| "PageIndexFileSystem", | ||
| "RebuildableSemanticIndex", | ||
| "SearchResult", | ||
| "SemanticIndexRecord", | ||
| "SemanticSearchResult", | ||
| "SummaryProjectionIndexer", | ||
| "SQLiteVecSemanticIndex", | ||
| ] | ||
|
|
||
|
|
||
| def __getattr__(name: str): | ||
| if name in _LAZY_EXPORTS: | ||
| module_name, attribute_name = _LAZY_EXPORTS[name] | ||
| value = getattr(import_module(module_name, __name__), attribute_name) | ||
| globals()[name] = value | ||
| return value | ||
| raise AttributeError(f"module {__name__!r} has no attribute {name!r}") | ||
|
|
||
|
|
||
| def __dir__() -> list[str]: | ||
| return sorted(set(globals()) | set(__all__) | set(_LAZY_EXPORTS)) | ||
| __all__ = ["PageIndexFileSystem"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from urllib.parse import urlsplit, urlunsplit | ||
|
|
||
|
|
||
| DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1" | ||
|
|
||
|
|
||
| def normalize_base_url(value: str | None) -> str: | ||
| raw = str(value or DEFAULT_OPENAI_BASE_URL).strip() | ||
| parsed = urlsplit(raw) | ||
| if parsed.scheme not in {"http", "https"} or not parsed.netloc: | ||
| raise ValueError("embedding base URL must be an absolute HTTP(S) URL") | ||
| if parsed.username or parsed.password or parsed.query or parsed.fragment: | ||
| raise ValueError( | ||
| "embedding base URL must not contain credentials, query parameters, or a fragment" | ||
| ) | ||
| return urlunsplit( | ||
| ( | ||
| parsed.scheme.lower(), | ||
| parsed.netloc.lower(), | ||
| parsed.path.rstrip("/"), | ||
| "", | ||
| "", | ||
| ) | ||
| ) | ||
|
|
||
|
|
||
| def normalize_model(value: str | None) -> str: | ||
| model = str(value or "").strip() | ||
| if not model: | ||
| raise ValueError("embedding model must not be empty") | ||
| return model |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from pathlib import Path | ||
|
|
||
|
|
||
| def projection_database_paths(index_dir: str | Path) -> tuple[Path, Path]: | ||
| index_dir = Path(index_dir).expanduser() | ||
| return index_dir / "summary.sqlite", index_dir / "embedding_cache.sqlite" | ||
|
|
||
|
|
||
| def projection_database_path_present(path: str | Path) -> bool: | ||
| path = Path(path) | ||
| return path.exists() or path.is_symlink() | ||
|
|
||
|
|
||
| def projection_database_pair( | ||
| index_dir: str | Path, | ||
| ) -> tuple[Path, Path] | None: | ||
| summary_path, cache_path = projection_database_paths(index_dir) | ||
| summary_exists = projection_database_path_present(summary_path) | ||
| cache_exists = projection_database_path_present(cache_path) | ||
| if not summary_exists and not cache_exists: | ||
| return None | ||
| if not summary_exists or not cache_exists: | ||
| raise RuntimeError( | ||
| "PIFS Summary Projection topology is incomplete; migrate this workspace with " | ||
| "pifs-data/scripts/migrate_pifs_workspace.py before opening it." | ||
| ) | ||
| return summary_path, cache_path |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import sqlite3 | ||
| from typing import Any | ||
|
|
||
|
|
||
| def regular_table_names(connection: sqlite3.Connection) -> set[str]: | ||
| return { | ||
| str(row[0]) | ||
| for row in connection.execute( | ||
| "SELECT name FROM sqlite_master " | ||
| "WHERE type = 'table' AND name NOT LIKE 'sqlite_%'" | ||
| ) | ||
| } | ||
|
|
||
|
|
||
| def sqlite_schema_signature( | ||
| connection: sqlite3.Connection, | ||
| tables: set[str], | ||
| ) -> dict[str, Any]: | ||
| columns = { | ||
| table: tuple( | ||
| tuple(row) | ||
| for row in connection.execute(f'PRAGMA table_xinfo("{table}")') | ||
| ) | ||
| for table in sorted(tables) | ||
| } | ||
| indexes: dict[str, tuple[tuple[Any, ...], ...]] = {} | ||
| foreign_keys: dict[str, tuple[tuple[Any, ...], ...]] = {} | ||
| for table in sorted(tables): | ||
| table_indexes = [] | ||
| for row in connection.execute(f'PRAGMA index_list("{table}")'): | ||
| name = str(row[1]) | ||
| origin = str(row[3]) | ||
| index_columns = tuple( | ||
| str(column[2]) | ||
| for column in connection.execute(f'PRAGMA index_info("{name}")') | ||
| ) | ||
| table_indexes.append( | ||
| ( | ||
| name if origin == "c" else None, | ||
| int(row[2]), | ||
| origin, | ||
| int(row[4]), | ||
| index_columns, | ||
| ) | ||
| ) | ||
| indexes[table] = tuple(sorted(table_indexes, key=repr)) | ||
| foreign_keys[table] = tuple( | ||
| sorted( | ||
| ( | ||
| str(row[2]), | ||
| str(row[3]), | ||
| str(row[4]), | ||
| str(row[5]), | ||
| str(row[6]), | ||
| str(row[7]), | ||
| ) | ||
| for row in connection.execute(f'PRAGMA foreign_key_list("{table}")') | ||
| ) | ||
| ) | ||
| return { | ||
| "tables": tables, | ||
| "columns": columns, | ||
| "indexes": indexes, | ||
| "foreign_keys": foreign_keys, | ||
| } | ||
|
|
||
|
|
||
| def normalized_table_sql(connection: sqlite3.Connection, table: str) -> str: | ||
| row = connection.execute( | ||
| "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?", | ||
| (table,), | ||
| ).fetchone() | ||
| return " ".join(str(row[0] if row else "").lower().split()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import sqlite3 | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| def _readonly_connection(path: Path) -> sqlite3.Connection: | ||
| connection = sqlite3.connect(f"{path.resolve().as_uri()}?mode=ro", uri=True) | ||
| connection.row_factory = sqlite3.Row | ||
| return connection | ||
|
|
||
|
|
||
| def _inconsistent_workspace_error(detail: str) -> RuntimeError: | ||
| return RuntimeError( | ||
| f"Inconsistent PIFS workspace: {detail}; migrate this workspace with " | ||
| "pifs-data/scripts/migrate_pifs_workspace.py before opening it." | ||
| ) | ||
|
|
||
|
|
||
| def validate_catalog_root(catalog_path: str | Path) -> None: | ||
| try: | ||
| with _readonly_connection(Path(catalog_path)) as catalog: | ||
| root = catalog.execute( | ||
| "SELECT folder_id, parent_id, name FROM folders WHERE path = '/'" | ||
| ).fetchone() | ||
| except sqlite3.Error as exc: | ||
| raise _inconsistent_workspace_error( | ||
| "the catalog canonical root folder could not be read" | ||
| ) from exc | ||
| if ( | ||
| root is None | ||
| or root["folder_id"] != "folder_root" | ||
| or root["parent_id"] is not None | ||
| or root["name"] != "/" | ||
| ): | ||
| raise _inconsistent_workspace_error( | ||
| "the catalog is missing its canonical root folder" | ||
| ) | ||
|
|
||
|
|
||
| def validate_workspace_consistency( | ||
| catalog_path: str | Path, | ||
| summary_path: str | Path, | ||
| ) -> None: | ||
| catalog_path = Path(catalog_path) | ||
| validate_catalog_root(catalog_path) | ||
| try: | ||
| with _readonly_connection(catalog_path) as catalog: | ||
| active_file_refs = { | ||
| str(row[0]) | ||
| for row in catalog.execute( | ||
| "SELECT file_ref FROM files WHERE deleted_at IS NULL" | ||
| ) | ||
| } | ||
| with _readonly_connection(Path(summary_path)) as summary: | ||
| projected_file_refs = { | ||
| str(row[0]) | ||
| for row in summary.execute( | ||
| "SELECT file_ref FROM semantic_index_docs" | ||
| ) | ||
| } | ||
| except sqlite3.Error as exc: | ||
| raise _inconsistent_workspace_error( | ||
| "catalog and Summary Projection references could not be read" | ||
| ) from exc | ||
| orphaned = sorted(projected_file_refs - active_file_refs) | ||
| if orphaned: | ||
|
BukeLy marked this conversation as resolved.
|
||
| shown = ", ".join(orphaned[:5]) | ||
| raise _inconsistent_workspace_error( | ||
| "Summary Projection file_ref values do not reference active catalog files: " | ||
| f"{shown}" | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.