RAGRAF — Code & Documentation Revision: Defect Register¶
Revision audited:
df7c1be(main, 2026-10-12) · Scope: 78 files —src/(≈18 500 LOC),tools/,scripts/,docs/,tests/,bench/,admin/, packaging files. Method: full read of README, all 3 PowerShell scripts, all 9 docs; automated scans (syntax, BOM/CRLF,exceptpatterns, dead-reference resolution, SQL/eval/shell greps); targeted deep read offacade.py,cli.py,mcp_server.py,admin_server.py,store.py,util.py,ensure.py,catalog.py,watch.py,telemetry.py,settings.py,cpg.py,embeddings.py,compute.py,tools/ragraf.py,admin/static/app.js, tests. Verification environment: Linux sandbox, Python 3.11 (syntax-level checks only; runtime checks require Python 3.13 + Windows and were performed as static analysis withfile:lineevidence).
Severity legend¶
| Sev | Meaning |
|---|---|
| 🔴 CRIT | Blocks public release or creates a real security/correctness risk |
| 🟠 HIGH | Should be fixed before/at release; user-visible breakage or doc-vs-code contradiction |
| 🟡 MED | Fix soon; robustness, portability, maintainability |
| 🔵 LOW | Polish, cosmetic, nice-to-have |
Totals: 4 CRIT · 11 HIGH · 19 MED · 9 LOW = 43 findings.
Register updated 2026-10-16: the file has grown to 61 findings in groups A–I — counted the same way the register is read, by its ### X-n sections. The severity totals above describe the revision audited on 2026-10-12; later sections were appended, not back-filled, because the ledger is append-only by design.
A. Release-blocking: dead references (files promised but not shipped)¶
A-01 🔴 CRIT — README documents AGENTS.md, which does not exist in the repository¶
- Where:
README.md→ «Документация» table, last row (AGENTS.md | правила разработки самого проекта; базовый чекер конвенций). - Evidence:
AGENTS.mdis absent from the git tree (git ls-files— not present). A reader clicking the implied reference finds nothing; the convention checkertools/Invoke-ProjectDoctrine.ps1that README also references reads its baseline fromAGENTS.md§10 (2 errors / 0 warnings), so the checker's documented contract is unverifiable. - Fix: either publish a sanitized
AGENTS.md, or remove the row and describe conventions inline (aCONTRIBUTING.mdis the natural home).
A-02 🔴 CRIT — 24 dead path references across published docs¶
- Where (all verified missing):
docs/skills/ragraf-core/SKILL.md→AGENTS.md,docs/AGENTS-context.md,tools/measure_safe_excludes.py,tools/check_ragraf_config.py,tools/check_service_excludes.py,tools/measure_dedupe_rules.py,tools/find_doc_dupes.py,tests/test_dedupe.py,tests/check_admin_agents.py,tests/check_admin_dedupe.pydocs/skills/ragraf-core/reference/repair.md→tools/measure_safe_excludes.py,tools/check_ragraf_config.py,tools/check_service_excludes.py,tools/measure_dedupe_rules.py,tests/test_dedupe.py,tests/check_admin_agents.py,tests/check_admin_dedupe.py,AGENTS.md§10docs/03_implementation/INTEGRATION.md→tests/test_mcp.py(«✅ все проверки пройдены» — cannot be reproduced),config/myproject.yamldocs/05_article/RAGRAF_ARTICLE.md→docs/03_implementation/RAGRAF_EFFECT_REPORT.md,docs/03_implementation/POLYGON_REPORT.md,docs/04_research/GAPS_FOR_LLM.md,docs/04_research/WORKFLOW_ANALOGS.md,docs/04_research/SHADCN_LOGIN_REFERENCE.md,docs/01_research/01_ARTICLE_COVERAGE.md,tools/bench_corpus.py,tests/test_watch_live.py,bench/baseline/,bench/tasks.yamldocs/02_architecture/ARCHITECTURE.md,TZ.md→docs/00_plan/PLAN.md,docs/01_research/SPEC_INPUT.md,docs/03_implementation/ACCEPTANCE_REPORT.md- Why it matters: the project's core value proposition is verifiable facts. A documentation set with 24 unverifiable references undermines exactly that promise; GitHub renders them as broken links.
- Fix: publish the missing artifacts where licensing/anonymization allows; otherwise rewrite references to «not shipped in the public tree» with rationale, or delete them. The article's «Приложение: артефакты» table needs a shipped / not shipped column.
A-03 🔴 CRIT — 26 dead references inside source-code comments¶
- Where (sample, full list verified by automated scan of
src/,tools/,scripts/):tests/test_auto_thresholds.py(catalog.py),tests/test_cpg_carry.py(catalog.py),tools/check_admin.py(admin_server.py),tools/check_admin_project_settings.py(settings.py),tools/check_case_sensitivity.py,tools/probe_vec_metric.py,docs/80-roadmap.md,docs/Stats.md, … — all cited as evidence («дефект найден проверкой X») in docstrings of shipped modules. - Why it matters: comments cite non-public verification tools as proof; a contributor cannot re-run them. Same credibility issue as A-02, but inside the code itself.
- Fix: ship the referenced checkers (most are small synthetic-fixture scripts), or reword comments to describe the observation without pointing at an unavailable file.
A-04 🔴 CRIT — shipped tool tools/audit_defects.py is dead on arrival¶
- Where:
tools/audit_defects.py:24—cfg = load_config(REPO / "config" / "z-batteryservice.yaml"). - Evidence:
config/is git-ignored (.gitignore§3/config/); the file references the author's private corpus. Any user runningpython tools/audit_defects.pygetsConfigError: конфигурация не найдена. The module also references the non-shippedGAPS_FOR_LLM.md. - Fix: accept
--project <cfg>argument with a clear usage message, or exclude the tool from the public tree until generalized. (Checktools/bench_index_modes.py,tools/measure_index_times.pyfor the same pattern.)
B. Documentation ↔ code divergence¶
B-01 🟠 HIGH — operation/tool counts contradict across documents and code¶
- Facts:
src/ragraf/facade.pydefines 22op_*methods;src/ragraf/mcp_server.pyexposes 23ragraf_*tools (addsensure, omitscalibrate);src/ragraf/cli.pyOPSlists 23 names (addsusage, which has noop_usage— served via telemetry). - Contradictions:
ARCHITECTURE.md§7 — «17 операций» (table lists 17);TZ.mdФТ-14 — «17 операций»;RAGRAF_ARTICLE.md§5 — «21 фиксированная операция», §8.3 — «17 → 21»;facade.py:1docstring — «Фасад: 17 операций»; README — «22 операции» (correct) and «23 инструмента MCP» (correct). - Note:
SKILL.md§4.2 knows about this («код объявляет 23 инструмента и 22 операции фасада, а документы называют 18, 16 и 21») — and the docs were still shipped unfixed. - Fix: single source of truth — generate the operation table from code (or at least sync ARCHITECTURE/TZ/article/facade docstring to 22/23). For EN production docs: 22 facade operations, 23 MCP tools, 23 CLI query ops.
B-02 🟠 HIGH — repair.md recipe §2 describes an already-fixed defect as open¶
- Where:
docs/skills/ragraf-core/reference/repair.md§2 «Реестр показывает пустую ревизию и нули» — prescribes «Правильное исправление: добавить запись результата вensure…». - Evidence:
src/ragraf/ensure.py:338-348already callsrecord_index_run(cfg, report, trigger="ensure:index")with an explicit comment that this was the measured defect and it is fixed. - Fix: move the recipe to past tense («historical defect, fixed on 2026-10-12; if you see zeros again — …») or delete.
B-03 🟠 HIGH — ARCHITECTURE promises parallel AST parsing; indexer is single-threaded¶
- Where:
ARCHITECTURE.md§9.1 «Разбор AST (tree-sitter, параллельно)», §13 «Параллельный разбор (8 ядер/16 потоков)»;RAGRAF_ARTICLE.md§4 implies the same. - Evidence:
src/ragraf/indexer.py,pipeline.py— zero occurrences ofthreading,concurrent.futures,multiprocessing; file loopfor cf in scan.files:(indexer.py:378) is sequential. - Impact: NFR-2 (≤15 min full index) may still hold on the pilot, but the documented mechanism doesn't exist; large corpora lose the promised speedup.
- Fix: either implement parallel parse (tree-sitter parsing is CPU-bound and trivially parallelizable per file) or correct the docs.
B-04 🟡 MED — JDK version divergence: docs say JDK 19, reality is JDK 21¶
- Where:
ARCHITECTURE.mdheader («JDK 19 + Joern»), §3, §6.1 vsCPG_INTEGRATION.md§4.3/§7 (Temurin 21.0.12.1, «JDK 21 вне протестированного диапазона»),install-joern.ps1(«JDK 21 в .tools\jdk»), README («нужны JDK 21 и Joern»). - Fix: unify on «JDK 21 (19+ may work, untested)».
B-05 🟡 MED — vec_chunk DDL differs between doc and code¶
- Where:
ARCHITECTURE.md§4.2:CREATE VIRTUAL TABLE vec_chunk USING vec0(rev_id TEXT, chunk_id TEXT, embedding FLOAT[384]);src/ragraf/store.py:443-446:chunk_id TEXT PRIMARY KEY, rev_id TEXT, embedding FLOAT[384](different column order + PK). - Fix: sync doc to code (code is authoritative).
B-06 🟡 MED — CPG_INTEGRATION.md leaks machine-local state into public docs¶
- Where: «На этой машине: PHP 8.5 найден (
C:\tools\php85\php.exe)», §7 status table («Joern 4.0.99 установлен… 1633 МБ»), «у пилота composer.json нет». - Why: a snapshot of the author's machine shipped as reference documentation; it will rot immediately and reads as noise to newcomers. The valuable content (§8 «что выяснилось только запуском» — 12 undocumented Joern behaviors) should stay.
- Fix: convert §1/§7 to «verified configuration (example)» framing; keep §8 as-is (it is excellent).
B-07 🟡 MED — INTEGRATION.md §5 status table references private state¶
- «✅
config/myproject.yaml», «✅ ревизияrev-0012, 392 файла», «✅tests/test_mcp.py— все проверки пройдены» — none of these exist in the public tree; the table presents the author's environment as the product's. - Fix: retitle as «integration status on the pilot machine (historical)» or remove.
B-08 🟡 MED — docs numbering has holes (00_plan, 01_research, 04_research missing)¶
- Published:
02_architecture,03_implementation,05_article,skills. Missing numbers are referenced (see A-02) and confuse navigation. - Fix: renumber for the EN release (
docs/architecture/,docs/implementation/,docs/article/,docs/skills/) or add adocs/README.mdindex explaining the layout.
B-09 🔵 LOW — requirements.txt header promises «команда проверки версий» that doesn't exist¶
operations.md§5 itself admits: «Нет штатной команды проверки версий зависимостей, хотя requirements.txt её обещает».- Fix: document
pip freeze/ addragraf selftestcross-reference, or drop the promise.
B-10 🔵 LOW — pyproject.toml description is Russian while keywords/classifiers are English¶
- Mixed-language packaging metadata; PyPI page would render a RU one-liner for an EN release.
- Fix: EN description for the translated release (done in the refactored packaging notes).
B-11 🔵 LOW — README test table vs shipped tests¶
- README lists 6 passing portable tests (56+31+25+23+17+12 checks) and explains 3 that need extras — accurate. But the article (§8.4) advertises «429 проверок, 0 провалов» across 11 suites, 8 of which are not shipped. A reader will look for them.
- Fix: add «on the author's corpora; not shipped» marker to article §8.4.
C. Security¶
C-01 🟠 HIGH — Admin panel token endpoint defeats its own threat model (DNS-rebinding + local malware)¶
- Where:
src/ragraf/admin_server.py:314-317(GET /api/tokenreturnsACCESS_TOKENunauthenticated),admin/static/app.js:64-66(page auto-fetches the token and stores it inlocalStorage). - Claimed model (module docstring): «случайная вкладка браузера не может изменить настройки» — false as implemented:
- No
Host/Originheader validation. A malicious web page can DNS-rebindevil.example→127.0.0.1, then same-originfetch('/api/token')reads the token and issues authenticated POSTs (/api/settings/global,/api/settings/project,/api/cache/clear). - Any local process (including malware running as the user) gets the token with one GET.
- Mitigating factors: loopback-only bind, POST-only mutations,
secrets.compare_digest, 1 MB body cap, settings writes validated (settings.py:validate) and backed up (write_project_settingscreates.bak.<ts>— settings.py:465). - Fix (production):
- Validate
Host∈ {127.0.0.1:port,localhost:port} and reject cross-originOriginon every request (kills DNS rebinding). - Remove
/api/token; require the operator to paste the console-printed token into the UI once (store insessionStorage, notlocalStorage), or issue a per-session cookie after a one-time pairing code. - Optional:
--token-file/ envRAGRAF_ADMIN_TOKENfor scripted use.
C-02 🟡 MED — MCP «graph query language» filter is substring-based: false positives, trivial bypass¶
- Where:
src/ragraf/mcp_server.py_call_tool— rejects any call whose stringified arguments containmatch (,return,cpgql,gremlin,create (,merge (. - Problems:
- False positives on legitimate work:
ragraf_search "when does the function return false", symbolmerge (, docs about Cypher — all rejected with-32602. For a code intelligence product, blocking the word «return » in queries is user-hostile. - Trivially bypassed (
m atch (? no — butMATCH(without space, or nested dict/list values are not scanned at all: only top-levelstr/int/floatvalues are joined). - FT-15 is already structurally guaranteed: there is no query-execution endpoint. The filter adds harm without adding security.
- Fix: delete the filter (architecture already enforces FT-15), or restrict it to a hypothetical free-form
queryargument that doesn't exist. Document the decision.
C-03 🟡 MED — Non-atomic JSON writes for catalog/settings (corruption window)¶
- Where:
src/ragraf/util.py:write_json— directp.write_text(...); used bycatalog.Catalog.save()andsettings.SettingsStore.save(). - Risk: crash/disk-full mid-write corrupts
catalog.json(machine registry of all corpora) orsettings.json..gitignorelistscatalog/catalog.json.*— backups anticipated, but the write itself is not atomic. - Fix: write to
path.with_suffix('.tmp')+os.replace()(atomic on Windows and POSIX); keep the timestamped backup for settings as today.
C-04 🟡 MED — shell=True with hand-rolled quoting for Joern .bat launchers¶
- Where:
src/ragraf/cpg.py:_shell_cmd—" ".join(f'"{part}"' if " " in part else part for part in cmd). - Risk: cmd.exe metacharacters (
&,|,^,%, embedded") in any path (e.g., Windows usernameA&B, directory100%_done) break the command or inject arguments. Documented as «проверено прогоном» — on the author's paths only. - Fix: avoid
shell=True: invokecmd.exe /c <bat> args…withsubprocesslist form, or useshutil.which+ direct.exeentry points where Joern provides them; at minimum escape cmd metacharacters.
C-05 🔵 LOW — Telemetry arg_digest stores first 60 chars of arguments in plaintext¶
- Where:
src/ragraf/telemetry.py:normalize_arg— docstring claims «открытый текст аргумента целиком не хранится», butdigest = text[:60]is stored verbatim inusage_call.arg_digest. - Risk: low (local SQLite, agent queries), but search queries may contain identifiers from secret-adjacent code; the claim overstates protection.
- Fix: reword the docstring, or hash-only mode behind a config flag for sensitive environments.
C-06 🔵 LOW — f-string SQL identifiers¶
- Where:
admin_server.py:149(SELECT COUNT(*) FROM "{name}"— name from SQLite catalog of tables),lexvec.py:678,revisions.py:200,220(DELETE FROM {table}— internal constant lists),store.py:399-405(PRAGMA/ALTER with internal names). - Risk: low (identifiers are internal constants, not user input), but the pattern invites future injection if a name ever becomes user-controlled.
- Fix: central allow-list +
"doubling helper (name.replace('"','""')), or a tinyquote_ident()instore.py.
D. Installer (scripts/*.ps1)¶
D-01 🟠 HIGH — Invoke-Checked deadlocks when child stderr exceeds the pipe buffer¶
- Where:
scripts/install.ps1Invoke-Checked:$out = $proc.StandardOutput.ReadToEnd()then$err = $proc.StandardError.ReadToEnd(). - Mechanics: classic .NET pipe deadlock — while the parent blocks reading stdout to EOF, the child can fill the 4 KB stderr pipe and block writing; neither side progresses.
pip installwrites progress/warnings to both streams; long installs on slow networks make this reachable, and the installer then hangs with no diagnostic. - Fix: read both streams concurrently (
BeginOutputReadLine/BeginErrorReadLinewithStringBuilder, or twoReadToEndAsynctasks awaited together). Implemented in the refactored installer.
D-02 🟠 HIGH — No timeouts on external processes¶
- Where: every
Invoke-Checkedcall —$proc.WaitForExit()unbounded (pip, python, ragraf ensure/index/selftest). - Risk: a hung pip (proxy blackhole) or a stuck first-time model download freezes the installer forever; user kills it, venv left half-built.
- Fix:
WaitForExit($timeoutMs)per step (pip: 30 min default w/-Timeoutoverride; index: configurable), kill + FAIL result on expiry. Implemented in the refactor.
D-03 🟡 MED — Selftest parsed by regex over Russian text although selftest --json exists¶
- Where:
install.ps1step 6 — splitsStdOutlines, matches component names and Russian failure markers (отсутствует|не загружен|недоступ). - Evidence:
cli.py:cmd_selftestsupports--json(args.json→ full checks array withokbooleans). - Risk: any wording change, EN translation, or extra line breaks the parser silently (WARN, install «succeeds»).
- Fix: call
selftest --json, parsechecks[].ok. Implemented in the refactor. (Same applies toensure --json— already used — and to thecatalogregex fallbackконфиг:\s*(.+\.yaml), D-04.)
D-04 🟡 MED — catalog output parsed with a fragile regex fallback¶
- Where:
install.ps1step 5 —if ($line -match 'конфиг:\s*(.+\.yaml)\s*$')to recover the config path whenensure --jsondidn't yield one. - Risk: breaks on EN localization, paths containing
.yamlearlier, CRLF nuances. - Fix:
catalog --json(verify CLI supports it; if not — add) and parse JSON only; keep text fallback as last resort with a WARN that the contract changed.
D-05 🟡 MED — No free-disk-space check¶
- Where:
install.ps1— before creating venv +pip install(ONNX Runtime, tree-sitter wheels: ≈1.5–2.5 GB), and before-Index(model ≈240 MB + index growth). - Evidence: README documents sizes; installer never checks
PSDrive.Free. - Fix: require ≥3 GB free on the venv target drive (warn-only threshold 5 GB with
-Index); FAIL with the computed requirement otherwise. Implemented.
D-06 🟡 MED — Microsoft Store Python stub produces a confusing failure¶
- Where:
install.ps1step 1 —Get-Command pythonmay resolve to…\WindowsApps\python.exe(App Execution Alias), which prints a Store banner and exits 9009. - Current behavior:
Invoke-Checkedcatches the non-zero exit and FAILs with «не запускается» — technically correct, diagnostically poor; alsoragraf.ps1would happily invoke the stub. - Fix: detect
WindowsAppsin the resolved path and print the exact remedy (disable App Execution Aliases or install python.org build). Implemented.
D-07 🟡 MED — -Force venv removal can throw mid-install (locked/read-only files)¶
- Where:
install.ps1—Remove-Item -LiteralPath $Venv -Recurse -Forceunder$ErrorActionPreference='Stop'with no try/catch; a python.exe still running from that venv (or AV lock) aborts the whole script with a raw Win32 error. - Fix: wrap in try/catch, emit a FAIL result row with actionable advice (close shells using the venv, retry), continue to summary. Implemented.
D-08 🟡 MED — «venv inside repository» warning only fires for pre-existing venvs¶
- Where:
install.ps1step 1 —$insideSourcecomputed only insideif (Test-Path $Venv); when the script creates a venv inside$Source, no warning is issued, and the venv becomes part of the indexed corpus. - Fix: compute
$insideSourceunconditionally and warn in both branches. Implemented.
D-09 🟡 MED — No installation transcript/log file¶
- Where: all three scripts — output only via
Write-Host; when a user reports a failure there is nothing to attach to an issue. - Fix:
Start-Transcriptto~/.ragraf/logs/install-<ts>.log(opt-out-NoTranscript), print the path in the summary and on failure. Implemented.
D-10 🔵 LOW — Dead code: $dyVersion computed and never used¶
- Where:
install.ps1step 1 —[version]::TryParse(...)into$dyVersion, then version compared via a second parse ($parts). - Fix: single parse. Done in refactor.
D-11 🔵 LOW — «What's next» prints Windows activation path on all platforms¶
- Where:
install.ps1final section —Join-Path $Venv 'Scripts/Activate.ps1'even when$IsWindows -eq $false(pwsh runs on Linux/macOS; the script is not Windows-gated). - Fix: branch on
$IsWindows(bin/activateotherwise). Implemented.
D-12 🔵 LOW — Exit-code semantics: missing pyproject.toml reported as environment error (3), not usage (2)¶
- Where:
install.ps1step 1 — «укажите каталог с исходниками: -Source <путь>» is a bad-argument condition; documented codes say 2 = invalid arguments, 3 = unsuitable environment. - Fix: exit 2 for that case. Implemented.
D-13 🟡 MED — install-joern.ps1 cannot actually install Joern (no download, no checksum)¶
- Where: script requires a manually placed
.tools/downloads/joern-cli.zipand a manually installed.tools/jdk; throws otherwise. README presents it as «Установка Joern». - Gaps: no
-Downloadfrom the official GitHub release, no SHA-256 verification, no JDK provisioning (e.g., Adoptium API), no-JoernVersionpin. - Fix: add opt-in download with pinned version + published SHA-256,
try/finallyrestore of$env:JAVA_HOME/$env:PATH(currently mutated for the session and never restored — D-14), and idempotent re-run that verifies the unpacked tree instead of only its existence. Implemented in the refactor.
D-14 🟡 MED — install-joern.ps1 leaks $env:JAVA_HOME / $env:PATH mutations¶
- Where:
install-joern.ps1«Проверка запуска Joern» — sets both for the session without save/restore. - Fix: save/restore in
finally(session-scoped changes should not outlive the verification step). Implemented.
D-15 🔵 LOW — install-joern.ps1 executable search includes .sh on Windows and trusts the first recursive hit¶
- Where: fallback
Get-ChildItem -Recurse -Filter 'joern*'accepts.sh; on Windows a.shmatch is unusable, and «first hit» may pick the wrong artifact (e.g.,joern-cli.bathelper). - Fix: platform-aware candidate order; verify the chosen executable responds to
--help. Implemented.
D-16 🔵 LOW — ragraf.ps1 checks Python presence but not version¶
- Where:
scripts/ragraf.ps1— error text says «Требуется Python 3.13», but onlyGet-Command pythonis checked; a 3.9 interpreter yields an import-time traceback instead of a clear message. - Fix: same version probe as
install.ps1(≥3.13). Implemented.
D-17 🟡 MED — Installers have no automated tests¶
- Where: no Pester tests for any
*.ps1; doctrine checkerInvoke-ProjectDoctrine.ps1inspects sources textually, not behavior. - Fix: Pester suite with mocked
Invoke-Checked(argument construction, exit-code mapping, summary table), plus a smoke test in CI onwindows-latest. (See roadmap R-CI.)
E. Core logic¶
E-01 🟠 HIGH — Glob matching is case-sensitive on Windows (product's primary platform)¶
- Where:
src/ragraf/util.py:compile_glob/glob_match—re.compile(...)withoutre.IGNORECASE; documented inSKILL.md§5.2 andrepair.md§4 as a known trap («**/cache/**не ловитsrc/Cache/»). - Impact: on a case-insensitive filesystem,
exclude/secrets_excludepatterns silently miss directories whose real case differs → vendor code bloats the corpus, and in the worst case a secrets pattern misses (**/*.PEMvs*.pem) — NFR-3 relevant. - Fix: on Windows (
sys.platform == 'win32'oros.name == 'nt') compile case-insensitively, or casefold both pattern and path before matching; keep case-sensitive behavior on POSIX. Add tests for both.
E-02 🟠 HIGH — Hardcoded machine-specific PHP discovery paths¶
- Where:
src/ragraf/cpg.py:119—for candidate in (Path("C:/tools/php85/php.exe"), Path("C:/tools/php/php.exe")). - Impact: on any other machine these never match (harmless) but they encode the author's layout in public code; the real discovery (PATH/env/config) is buried under it. If a user does have
C:\tools\php, an unexpected interpreter may be picked. - Fix: order:
RAGRAF_PHP_BINenv → corpus configcpg.php_bin→shutil.which("php")→ (optionally) common install dirs without version-pinned author paths.
E-03 🟡 MED — store._migrate_graph writes schema_version even when ALTER failed¶
- Where:
src/ragraf/store.py:399-415—except sqlite3.Error: passonALTER TABLE, then unconditionalINSERT OR REPLACE meta(schema_version). - Impact: DB claims to be at the new schema version while a column (
edge.props) is missing; later queries fail with «no such column» far from the root cause. - Fix: track migration success; only bump
schema_versionif all migrations applied (or store per-migration markers).
E-04 🟡 MED — MCP JSON-RPC: replies to notifications; no protocol-version negotiation¶
- Where:
src/ragraf/mcp_server.py:handle— unknown method →_error(req_id, -32601, …)even whenidis absent (notification);initializeignores client'sprotocolVersionand always answers2024-11-05. - Spec: JSON-RPC 2.0 — «The Server MUST NOT reply to a Notification»; MCP — server should echo a supported version and clients from 2025 may disconnect on unexpected old versions (most tolerate it, but
2025-06-18is current). - Fix:
if req_id is None: return Nonefor errors; implement minimal negotiation (accept known versions list, answer with the highest supported ≤ requested, else own latest); bump to a current spec version after client-compatibility check.
E-05 🟡 MED — GPU stack (torch, sentence-transformers) is used but not declared¶
- Where:
src/ragraf/compute.py:141-146(import + error message «pip install sentence-transformers»),graph_algo.py(torch.sparse backend); neither is inpyproject.tomldependencies or extras; README explains the absence,SKILL.md§5.5 too. - Impact: honest degradation works, but there is no supported install path for the optional accelerator;
tests/test_graph_algo.pyfails out of the box. - Fix: add
[project.optional-dependencies] gpu = ["torch>=2.x", "sentence-transformers>=3.x"](documented as ~2.5 GB), sopip install ragraf[gpu]becomes the sanctioned path; keep runtime optional-import guards.
E-06 🟡 MED — SettingsStore.save (global settings) has no backup, unlike project settings¶
- Where:
src/ragraf/settings.py:320-332(validate →util.write_json) vswrite_project_settings(backup atsettings.py:465). - Fix: same timestamped backup + atomic replace (see C-03).
E-07 🔵 LOW — EXIT_NOT_FOUND = EXIT_UNAVAILABLE alias kept «for external callers»¶
- Where:
src/ragraf/cli.py:30-33— public constant preserved with changed meaning (was «not found», now «layer refused»). External scripts checking== 3for «empty result» now misread semantics. - Fix: fine as compatibility, but document in the EN CLI reference: 0 = ok (incl. empty result with
found=false), 1 = error, 2 = usage, 3 = capability unavailable.
E-08 🔵 LOW — admin_server.py embedded page lang="ru" and RU strings throughout UI¶
- For the EN production release: i18n pass over
PAGE_HTML, API messages, andadmin/static/app.jslabels (all RU today). Not a bug — a release task.
E-09 🔵 LOW — repo_map budget counts JSON characters, not tokens¶
- Where:
facade.py:_payload_weight— «символов» (honest, documented); TZ ФТ-20 demandsbudget_tokensvia tiktoken.chars_usedfield name is honest; docs elsewhere say «в заданном бюджете символов» (README) vs «budget_tokens = 2000» (ARCHITECTURE/TZ). - Fix: align naming: either count tokens with tiktoken (dependency already present) or rename the contract field to
budget_charseverywhere.
E-10 🟡 MED — registry and revision report different symbol/edge totals for the same revision¶
- Where (hypothesis, not yet traced): the counters written by the indexing run (
catalog.py,record_run,src/ragraf/catalog.py:235) versus the counters stored with the revision and surfaced byindex_status(revisions.py/ therevrow). - Evidence (measured 2026-10-16;
status --jsoncompared with the registry entry for the samerev_id): - corpus A,
rev-0004: registrynode_count8 721 /edge_count41 201 against the revision's 4 498 / 56 106; - corpus B,
rev-0156: registry 13 800 / 45 361 against the revision's 3 620 / 46 296; file_countagrees in both cases (297 and 648), so the divergence is in the graph counters, not in the file scan.- Why it matters: the registry is what
catalog, the corpus list and any «by the numbers» table reads; the answer envelope reads the revision. Two totals for one revision means a number taken from the registry cannot be traced to the graph it describes — the project's own rule («a number must match its source») breaks before the number reaches a document. - Fix: one aggregation path — record the revision's own counters in
record_run, or state each counter's definition in the registry entry and incatalog's output. Until then, corpus-level symbol and edge counts read from the registry are provisional; the polygon numbers published inREADME.md,README.ru.mdand the wiki are taken from the revision record for this reason.
F. Robustness / portability¶
F-01 🟡 MED — except Exception: pass in ~10 places without logging¶
- Where:
cpg.py:806,ensure.py:330,watch.py:180,394,drift.py:450,476,catalog.py:217,340,facade.py:521,init_corpus.py:220(OSError). - Context: each is deliberate («учёт не должен отменять индексацию») and most append a note to the outcome — good practice — but several swallow silently (e.g.,
watch.indexed_hashesgraph read → falls back to catalog silently). - Fix: route through a tiny
util.swallow(exc, context)that appends to an in-memory diagnostic ring buffer exposed viaindex_status/selftest --json(«last swallowed errors»). Keeps behavior, removes blindness.
F-02 🟡 MED — Linux/macOS path is declared but unverified (README says so honestly)¶
- Where: README «Общий путь через pip на Linux и macOS не проверялся».
- Gaps found statically:
install.ps1activation hint (D-11),ragraf.ps1PATH separator;(Windows-only join —"$srcPath;$previousPath"breaks POSIX:), WMI-based test cleanup (win32-guarded — OK),joern.bat/java.execandidates incpg.py(Windows-only suffixes). - Fix (roadmap): CI matrix (windows/ubuntu/macos), POSIX-aware PATH joins,
joern(no ext) candidates. Until then keep the honest disclaimer.
F-03 🔵 LOW — ragraf.ps1 uses ; as PYTHONPATH separator¶
- Where:
scripts/ragraf.ps1—$env:PYTHONPATH = "$srcPath;$previousPath"; on Linux/macOS Python expects:. - Fix:
[IO.Path]::PathSeparator. (Windows-first product; low priority.)
F-04 🔵 LOW — tests/ has no unified runner¶
- Each test file is a standalone script with its own
check()harness (documented in README). Nopytestconfig, nopython -m tests, no aggregate exit code. - Fix (roadmap): thin
tools/run_tests.pythat discoverstests/test_*.py, runs each as a subprocess, aggregates PASS/FAIL counts and returns non-zero on any failure — without forcing a pytest migration.
G. Packaging / release engineering¶
G-01 🟠 HIGH — No CI: zero GitHub Actions workflows¶
- Where:
.github/absent. - Impact: claims like «49 файлов синтаксически чисты, BOM/CRLF отсутствуют, 6 тестов проходят» are manual; nothing prevents regression on the next commit. For a product whose pitch is verifiability, this is the most ironic gap.
- Fix: minimal workflow: syntax+BOM/CRLF lint, doctrine checker, portable tests (6 suites),
pip install -e .smoke,ragraf --version, onwindows-latest+ubuntu-latest(py 3.13/3.14). See refactoredci.ymlin deliverables.
G-02 🟠 HIGH — No release infrastructure: no tag, no CHANGELOG, no version single-source¶
- Evidence: repo has 0 tags;
version = "0.1.0"inpyproject.tomland__version__insrc/ragraf/__init__.py(verify sync — two places); noCHANGELOG.md; no GitHub Release notes. - Fix: tag
v0.1.0; addCHANGELOG.md(Keep-a-Changelog format); make__init__.pyread version from package metadata (importlib.metadata.version("ragraf")with fallback) or keep both synced via a check in CI.
G-03 🟡 MED — Dependency list duplicated (pyproject.toml + requirements.txt) without a lock¶
- Both files carry the same 7 runtime deps with identical bounds — maintained by hand; drift is a matter of time. НФТ-7 demands fixed versions; neither file is a lockfile.
- Fix: keep
pyproject.tomlas the source; generaterequirements.txt(pip-compile) or reduce it to-e .+ comments; optionally shiprequirements.lockwith hashes for offline/corporate installs.
G-04 🟡 MED — No CONTRIBUTING.md, SECURITY.md, issue/PR templates¶
- README «Участие» is three sentences. For a public MIT project expecting «воспроизводимый дефект с командой и выводом», an issue template enforcing that format is cheap and high-value.
- Fix: add all four (provided in deliverables).
G-05 🔵 LOW — .gitignore excludes /config/ while docs tell users to create configs there¶
- Intentional (machine-local), but the EN docs must state the workflow explicitly:
ragraf init <project>createsconfig/<name>.yaml, andconfig/never gets committed.
G-06 🔵 LOW — bench/results/*.json contain commit SHAs and file paths of the private pilot corpus¶
- e.g.
M2_rev-0012.json:"commit": "7748931a37","primary_file": "src/Skills/SkillSelector.php". No secrets; README declares artifacts are shipped deliberately. Acceptable — but the EN release should say once, explicitly, what these identifiers refer to («an unpublished PHP corpus of the author; SHAs are not resolvable publicly»).
G-07 🔵 LOW — No py.typed, no type-check config¶
- Code uses full type annotations (
from __future__ import annotations, typed dataclasses) but ships nopy.typedmarker and no mypy/pyright config. - Fix: add
src/ragraf/py.typed+ minimal mypy config; CI job optional.
H. Tests¶
H-01 🟡 MED — Test suite coverage vs claims¶
- Shipped: 10 files; 6 portable suites (164 checks total per README: 56+31+25+23+17+12); 3 need extras (Joern layer, torch, private config) — honestly documented.
- Gaps: no tests for
mcp_serverprotocol handling (the referencedtests/test_mcp.pyis not shipped), no tests foradmin_serverendpoints, no tests forinstall.ps1logic, no negative tests for the C-02 filter, no cross-platform glob-case tests (E-01). - Fix: add the four suites listed (roadmap R-T1..R-T4).
H-02 🔵 LOW — tests/test_ensure.py cleanup uses WMI (win32-only), silently skipped elsewhere¶
- Guarded by
sys.platform != "win32"— acceptable, but on Linux a stray Joern server would survive the test. Document or usepsutil-free/procscan on POSIX.
I. Cosmetic / editorial (mostly for the EN production pass)¶
| ID | Sev | Finding |
|---|---|---|
| I-01 | 🔵 | facade.py:1 docstring «17 операций» (see B-01) |
| I-02 | 🔵 | Mixed RU/EN identifiers in user-facing JSON ("не найдено", "конфиг не найден") — the EN release needs a message catalog or EN strings with RU kept in docs/ru/ |
| I-03 | 🔵 | README.md table «22 операции» lists usage (not a facade op) — footnote it |
| I-04 | 🔵 | ARCHITECTURE.md §12 item 3 contains a typo: «grубое изучение» |
| I-05 | 🔵 | TZ.md references «ФТ-10/ФТ-13» style IDs; EN translation should keep IDs stable (FT-xx) and map them in a table |
| I-06 | 🔵 | Article §11 diagram uses style D6/D7 nodes not defined in the flowchart (rendering quirk) |
| I-07 | 🔵 | examples/ragraf.example.yaml — comments RU; translate (done in deliverables) |
| I-08 | 🔵 | admin/static/app.js — RU labels; translate for EN release (E-08) |
| I-09 | 🔵 | LICENSE © 2026 — fine; ensure pyproject license-files picks it up in sdist (verify build) |
Summary table (top actions before public release)¶
| # | Action | Defects closed |
|---|---|---|
| 1 | Publish or excise all dead references; fix README doc table | A-01…A-04, B-02, B-07 |
| 2 | Harden admin server (Host/Origin check, remove/lock down /api/token) |
C-01 |
| 3 | Refactored installer (async pipes, timeouts, disk check, JSON selftest, transcript) | D-01…D-16 |
| 4 | Sync docs to code (22/23 counts, parallel-parse claim, JDK 21, vec0 DDL) | B-01, B-03, B-04, B-05 |
| 5 | Case-insensitive globs on Windows + tests | E-01 |
| 6 | CI workflow + tag v0.1.0 + CHANGELOG | G-01, G-02 |
| 7 | Remove MCP substring filter (or justify) | C-02 |
| 8 | Atomic JSON writes + settings backup | C-03, E-06 |
| 9 | Declare GPU extras | E-05 |
| 10 | EN translation pass of README/docs/UI strings | I-02, I-07, I-08, E-08 |
Prepared as part of the production-readiness revision, 2026-10-12. Every finding above carries file:line evidence and was verified against revision df7c1be.