Skip to content

feat(index): harden project search provenance and FTS repair - #113

Closed
kunkunGames wants to merge 3 commits into
tumourlove:masterfrom
kunkunGames:agent/index-structured-content-fts
Closed

feat(index): harden project search provenance and FTS repair#113
kunkunGames wants to merge 3 commits into
tumourlove:masterfrom
kunkunGames:agent/index-structured-content-fts

Conversation

@kunkunGames

@kunkunGames kunkunGames commented Jul 25, 2026

Copy link
Copy Markdown

Goal

Harden project-search match provenance, text boundaries, FTS5 query projection, and error classification while preserving the existing fts_assets and fts_nodes tables, and add a dry-run-first repair path limited to the affected FTS scope.

Plain-language explanation

Search results now state exactly where and why they matched, and each query condition is projected only to the asset or node table where it can apply. Invalid search syntax is reported as caller error -32602, while real database or schema failures are reported as internal error -32603. Live C++, the native offline executable, and the Python offline path follow the same syntax and failure contract.

Improvements

  • Results include match source, table, field, object path, value, length, and truncation metadata.
  • Bounded context is built only from the field reported as the match source.
  • Phrases, prefixes, anchors, NEAR(...), column filters, grouping, and AND/OR/NOT are projected without changing precedence.
  • NEAR phrases, * prefixes, + concatenation, and optional unsigned-decimal distances are fully validated before projection.
  • Conditions that can apply to both asset and node tables keep only compatible branches, while invalid syntax inside a branch for another table is still rejected rather than hidden.
  • project.repair_fts supports preview-first repair for assets, nodes, or all.
  • Python offline queries return structured internal errors for SQLite corruption and schema failures instead of leaking tracebacks.
  • The native offline build checks every translation unit and each link, staging, and copy step fail closed, preventing an old executable from being mistaken for a successful new build.

Before → After

Before After
It could be unclear which field produced a result Exact provenance is returned, with context derived from that field
Invalid FTS syntax and database failures could appear as the same internal error Caller syntax is -32602; storage and schema failures are -32603
Python offline could pass some NEAR contents through as opaque text C++ and Python prevalidate the same phrase, prefix, concatenation, and distance grammar
A malformed NEAR inside a branch for another table could disappear during projection The full query is validated before table-specific projection
SQLite corruption raised outside OperationalError could leak a traceback Every SQLite database failure returns a structured success:false envelope
A later successful compile could overwrite %ERRORLEVEL% after the first C++ translation unit failed Compile, link, staging, and copy steps are checked immediately, and the final executable is replaced only after success
FTS recovery depended on a broad reindex Only the selected existing tables are repaired, with dry-run as the default

Side-effect analysis

  • There is no schema migration, new FTS table, or search-default change.
  • A syntactically valid cross-table conjunction that no single table can satisfy returns no results rather than an error.
  • Unknown qualifiers and malformed NEAR expressions are rejected even inside an inapplicable branch.
  • Context and value fields are capped at 240 Unicode code points.
  • Numeric NEAR(..., N) and the existing distance-free NEAR form remain supported; invalid phrases, operators, anchors, commas, and distances are rejected explicitly.
  • A native build failure preserves the previously deployed executable and is never reported as success.

Resolved review feedback

The latest commit, 7f4252d9ad6ea9122433658c18f166749580745e, addresses all remaining review scope in code:

  • Added NEAR grammar validation to the Python offline path with behavior equivalent to the shared C++ implementation.
  • Validated the complete query before mutually exclusive field branches are removed, closing the path where malformed NEAR syntax could be hidden.
  • Updated Python SQLite execution to handle all sqlite3.DatabaseError failures as structured internal errors and added a real corrupted-FTS5 fixture.
  • Fixed the root cause of multi-source %ERRORLEVEL% masking in Tools/MonolithQuery/build.bat, discovered during verification, and proved it with compiler-failure injection.

Review threads were intentionally left for the reviewer to verify against the new diff rather than being resolved or replied to unilaterally.

Verification

  • python -m py_compile: PASS.
  • Python offline unit tests: 4/4 PASS, covering valid NEAR, a hidden malformed branch exposed before projection, phrase and distance errors, and a real FTS5 corruption fixture with no traceback.
  • Native offline query build: PASS; freshness source hash 2cc5d9c574f46623, with matching executable and manifest.
  • Compiler-failure injection: the first translation unit exited 1, link and copy did not run, and the existing Binaries\monolith_query.exe SHA remained unchanged.
  • Native executable / Python CLI / SQLite NEAR syntax, classification, and JSON differential: 45/45 PASS.
  • Existing native/Python SQLite fixture deep equality: 25/25 PASS.
  • Existing generated-result and ranking semantic differential: 250/250 PASS.
  • Protected UE 5.8 Editor build: affected ProjectSearchAction.cpp, the hardening test, and database source were recompiled, and UnrealEditor-MonolithIndex.dll was relinked successfully. Monolith.Index.ProjectSearch.HardeningAndRepair: 1/1 PASS, with 0 test warnings, 0 errors, and process exit 0. Report: D:\P4\MonolithPR113ReviewUE58Host\Saved\Automation\PR113NearGrammarFinalUE58\index.json.
  • Protected UE 5.7 Editor build: the same authoritative source blobs were recompiled and relinked successfully. Focused automation: 1/1 PASS, with 0 test warnings, 0 errors, and process exit 0. Report: D:\P4\MonolithPR113FinalUE57Host\Saved\Automation\PR113NearGrammarFinalUE57\index.json.
  • Differential run with the same current static-check configuration: base and branch each reported 36 blockers and 802 advisories; 0 findings were introduced or resolved by this PR. The target base does not contain the latest hosted checker and configuration, so the same checker and configuration were applied to both sides, with only offline_exe_freshness disabled because binary identities differ on the base.
  • The release-wide RI/source parity runner could not execute 27 out-of-scope source/RI actions because this isolated PR checkout has no Saved\EngineSource.db. parity_spec_rev equality was confirmed; this limitation is separate from the 45/45, 25/25, and 250/250 project-search verification above.
  • git diff --check: PASS. The branch was 0 commits behind and 3 commits ahead of the latest tumourlove/master.
  • Verification record: Docs/testing/2026-07-26-project-search-hardening.md.

WorkFingerprint

  • agent: Codex
  • category: correctness / search / repair
  • module: MonolithIndex, MonolithQuery offline parity
  • component/action/helper: project.search, project.repair_fts, recursive FTS5 projector, project-search database, native offline build
  • intended files: ProjectSearchAction.{h,cpp}, ProjectRepairFtsAction.{h,cpp}, ProjectSearchQueryProjection*.h, MonolithIndexDatabase.{h,cpp}, MonolithProjectSearchHardeningTests.cpp, offline query/parity/build scripts, matching Index/API specs
  • risk type: query misprojection, wrong error classification, misleading provenance, over-broad repair, stale offline binary acceptance
  • public API impact: yes, richer result provenance and explicit invalid-query behavior
  • docs/spec impact: yes

Duplicate check

On 2026-07-28, open PRs #104, #112, #113, and #114, their related remote branches, and their actual changed files were checked again. #104 targets the updater, #112 targets discovery, and #114 targets activation and the index-writer lifecycle. #114 overlaps in the Index subsystem and some shared documentation, but #113 owns the read/search/FTS-repair contract while #114 owns writer activation and acceptance. No other open PR provides the same FTS projection, provenance, and repair implementation. Shared files may require a rebase after an earlier PR merges; that remains an explicit conflict risk rather than duplicate semantic ownership.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9029cf6b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
Comment thread Scripts/monolith_offline.py Outdated
Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
@kunkunGames kunkunGames changed the title feat: add structured-content FTS to project search feat(index): extend project.search FTS to variables, parameters, DataTable rows, and actors Jul 25, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 15a9369b9e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
@kunkunGames

Copy link
Copy Markdown
Author

Conflict/merge-order note for my six open PRs is in #104 (comment #104 (comment)).

Short version: a pairwise trial merge shows these PRs conflict only in CHANGELOG.md (plus Docs/specs/SPEC_MonolithCore.md for the #104 × #112 pair) — no source conflicts. Merge in any order that suits your batch; I will rebase the remainder and re-verify with a build plus automation tests.

@kunkunGames

Copy link
Copy Markdown
Author

@codex review

Both open comments are addressed in 70b856ac, which landed after your last pass.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70b856ac4a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
Comment thread Tools/MonolithQuery/monolith_query.cpp Outdated

kunkunGames commented Jul 26, 2026

Copy link
Copy Markdown
Author

@codex review

Please re-review the latest head, fc533c76. All existing and newly added inline findings have been addressed with replies and verification, and all threads are resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc533c7615

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: fc533c7615

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

kunkunGames commented Jul 26, 2026

Copy link
Copy Markdown
Author

@codex review

Please re-review the latest head, 31bf33d2. The two new P1 findings were addressed together with root-cause fixes in 1a051375; after an exact-head UE 5.8 fresh link and a 10/10 StructuredFTS+IncrementalSentinels verification run, I replied to and resolved both threads.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31bf33d210

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Source/MonolithIndex/Private/Indexers/DataTableIndexer.cpp Outdated
Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated

Copy link
Copy Markdown
Author

@codex review

Latest validated head: 8efbbe40. The two new P2 findings are addressed with class-change sentinel cleanup and FTS5-capability-gated degraded startup. Exact-head UE 5.8 build succeeded with a fresh UnrealEditor-MonolithIndex.dll relink, and the focused StructuredFTS + IncrementalSentinels suite passed 11/11.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8efbbe4068

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
@kunkunGames

Copy link
Copy Markdown
Author

@codex review

Please re-review the latest head, c1806197. The four new P2 findings are fixed at their roots: degraded mode now disables all FTS triggers and requires a six-target recovery rebuild, match_context is always bounded across live/native/Python paths, and full indexing aborts cleanly when reset initialization fails. After an exact-head UE 5.8 fresh link, 12/12 focused automation, live long-context proof, and native/Python deep-equal parity, I replied to and resolved all four threads.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: c1806197b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@tumourlove

Copy link
Copy Markdown
Owner

Staging this rather than taking it whole. The idea is sound and the incremental-sentinel work in particular is careful, so I want to be straight about what is happening to it.

Taking, in an upcoming release: search hardening (param validation, limit clamp, code-point-safe preview with truncation metadata, real error propagation), match provenance on the existing fts_assets/fts_nodes, and project.repair_fts with its dry-run default. Those need no schema change and repair_fts is the recovery tool everything later depends on, so it goes first.

Not scheduling slices 4 through 8 — the schema v3 migration and the four structured FTS tables. That is honest accounting rather than a soft no: costed properly it is eight to ten days once the redesign below is included, and there is no demonstrated demand for it (no requests on the Ideas board, this PR is the only ask). I would rather spend that week on the updater path and the deprecation sweep. If demand shows up on the board I will revisit.

The specific blockers, in case you want to take any of this further:

It does not build on UE 5.7. Stmt.Step(&ErrorCode) and FSQLiteDatabase::GetErrorForCode are 5.8-only and there are no ENGINE_MINOR_VERSION gates in the diff — authored against a 5.8-only host project. Step() plus GetLastError() works on both, at the cost of the numeric code. CONTRIBUTING.md now states 5.7 as the compile floor and there is a PR template checkbox for it.

The migration itself is fine — transactional, rolls back cleanly, cannot corrupt a database. The problem is the failure policy: on failure Open() does Close(); return false, which takes the entire project index offline including the asset and node search that worked fine on v2. It needs to degrade to v2 and stay open, and that redesign is the main cost driver.

Related: one unloadable level or one DataTable with a null row struct fails the sentinel without advancing hashes, so the same failing delta retries on every editor start, forever. Broken assets are normal in real projects.

include_content defaults true, which silently enlarges every existing caller's response, and the 240-code-point bound is only enforced when detail=false.

Leaving open while the first three slices land. You will be credited.

@kunkunGames
kunkunGames force-pushed the agent/index-structured-content-fts branch from c180619 to 01ffbdd Compare July 26, 2026 14:44
@kunkunGames kunkunGames changed the title feat(index): extend project.search FTS to variables, parameters, DataTable rows, and actors fix(index): harden project search provenance and FTS repair Jul 26, 2026
@kunkunGames
kunkunGames force-pushed the agent/index-structured-content-fts branch from 01ffbdd to b880852 Compare July 26, 2026 16:20

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b880852fce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp Outdated
@kunkunGames
kunkunGames force-pushed the agent/index-structured-content-fts branch from b880852 to c11f8d8 Compare July 26, 2026 17:33

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c11f8d8a9f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Source/MonolithIndex/Private/ProjectSearchQueryProjection.h Outdated
Comment thread Source/MonolithIndex/Private/ProjectSearchQueryProjection.h Outdated
Comment thread Source/MonolithIndex/Private/Actions/ProjectSearchAction.cpp Outdated
Comment thread Tools/MonolithQuery/monolith_query.cpp Outdated
@kunkunGames
kunkunGames force-pushed the agent/index-structured-content-fts branch from c11f8d8 to 6008779 Compare July 26, 2026 18:47

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60087799ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Source/MonolithIndex/Private/MonolithIndexDatabase.cpp
@kunkunGames
kunkunGames force-pushed the agent/index-structured-content-fts branch from 6008779 to e52c730 Compare July 26, 2026 19:27
@kunkunGames

Copy link
Copy Markdown
Author

The staging call is fair and the branch is now exactly the three slices you said you'd take. Slices 4–8 are deleted, not flagged off. Current head e52c730.

That removal resolves three of your four blockers by deletion. The fourth I re-verified rather than asserting, because it's the one that would send this back on sight.

"It does not build on UE 5.7." It does now. Stmt.Step(&ErrorCode) and FSQLiteDatabase::GetErrorForCode only ever existed in the migration path, and there are zero occurrences of either in the tree at this head. I deleted the MonolithIndex intermediates first so nothing could be skipped as up-to-date:

Host: D:\P4\MonolithPR113FinalUE57Host  (EngineAssociation 5.7, D:\Engine\UE_5.7)

[34/124] Compile [x64] MonolithIndexDatabase.cpp
[35/124] Compile [x64] ProjectRepairFtsAction.cpp
[41/124] Compile [x64] ProjectSearchAction.cpp
[47/124] Link    [x64] UnrealEditor-MonolithIndex.lib
[49/124] Link    [x64] UnrealEditor-MonolithIndex.dll
Result: Succeeded

Every file this PR touches in MonolithIndex compiled from scratch on 5.7. Monolith.Index.ProjectSearch.HardeningAndRepair then passed 1/1 on 5.7 under -RenderOffscreen, exit 0.

"The failure policy takes the entire project index offline." Gone with the migration — there is no Open() failure path added by this branch, because there is no schema change to fail. You were right that fixing it properly meant a degrade-to-v2 redesign; that cost is now out of this PR rather than paid badly.

"One unloadable level or one DataTable with a null row struct fails the sentinel without advancing hashes." Also gone — no sentinel writer, no incremental ownership change here. Still worth keeping on your list for whatever ships the structured tables later; broken assets are normal and that retry loop is real.

"include_content defaults true, and the 240-code-point bound is only enforced when detail=false." Both were properties of the dropped slices. There is no include_content parameter anywhere in Source/MonolithIndex at this head, and there is no detail gate — ProjectSearchTextProjection.h applies the bound unconditionally, on one path, to both fields:

constexpr int32 PreviewCodePoints = 240;

inline FString ProjectPreview(const FString& Value, int32& OutLength, bool& bOutTruncated)
{
	OutLength = CountUnicodeCodePoints(Value);
	bOutTruncated = OutLength > PreviewCodePoints;
	return bOutTruncated ? LeftUnicodeCodePoints(Value, PreviewCodePoints) : Value;
}

Surrogate pairs count as one code point, so the bound can't split an astral character. match_value_length / match_context_length report the pre-truncation size, and the response echoes the preview limit so clients never hardcode 240.

Two things you didn't comment on, so they aren't a surprise:

match_context now comes from the field that actually matched. The query selects a snippet and per-field highlights, and context falls back to the matched field's own highlighted value when the matched field isn't the snippet column — previously an asset_name match returned unrelated description text.

Error codes are split: -32602 for FTS5 query syntax, -32603 for database, prepare, step, and storage failure. That was the case where clients kept rewriting a valid query against a broken index.

Happy to rebase onto whatever you merge first — the only conflict against my other open PRs is the CHANGELOG.md [Unreleased] anchor.

@kunkunGames

Copy link
Copy Markdown
Author

@codex review

Latest head e52c7305. Since your last pass on 60087799 the schema v3 migration and the four structured FTS tables were dropped entirely; the branch is now search hardening, match provenance on the existing fts_assets/fts_nodes, and project.repair_fts. Rebuilt from clean intermediates on UE 5.7 with Monolith.Index.ProjectSearch.HardeningAndRepair 1/1.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e52c730513

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1035 to +1038
return Error.Contains(TEXT("fts5: syntax error"), ESearchCase::IgnoreCase)
|| Error.Contains(TEXT("unterminated string"), ESearchCase::IgnoreCase)
|| Error.Contains(TEXT("malformed MATCH"), ESearchCase::IgnoreCase)
|| Error.Contains(TEXT("unknown special query"), ESearchCase::IgnoreCase);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Classify invalid NEAR distances as query errors

When a caller submits a malformed query such as NEAR(foo, abc), the new projector accepts the whole NEAR group and SQLite reports expected integer, got "abc". Because that diagnostic matches none of these checks, RunSearch returns InternalError and the action emits -32603 instead of the documented invalid-parameter -32602. Validate the NEAR distance in the projector or recognize this MATCH-parser diagnostic as a syntax error.

Useful? React with 👍 / 👎.

@kunkunGames kunkunGames changed the title fix(index): harden project search provenance and FTS repair 🔎 Codex: harden project search provenance and FTS repair Jul 27, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3a9beb5a0c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Scripts/monolith_offline.py Outdated
Comment on lines +541 to +545
if depth == 0:
self.position += 1
return _trim_fts_space(
self.query[start:self.position]
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate NEAR distances in the Python projector

When the Python fallback receives NEAR(foo, abc) or NEAR(foo, 1, 2), this scanner returns the whole group as applicable without validating the optional distance, so the query reaches SQLite and is reported as the generic “Invalid or failed FTS5 query” error. The shared live/native projector now rejects these forms before execution with an explicit caller-syntax error, breaking the documented parity and preventing Python clients from distinguishing invalid input from index failure; mirror the distance/comma validation here.

Useful? React with 👍 / 👎.

Comment on lines +647 to +659
std::string near_text;
const near_parse_result near_result =
try_parse_near_group(near_text);
if (near_result == near_parse_result::parsed)
{
if (has_initial_anchor)
{
fail_at(
"FTS5 initial-token anchor is not valid on a NEAR group",
expression_start);
return nullptr;
}
result = node::make_leaf(std::move(near_text));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate NEAR contents before dropping incompatible branches

Fresh evidence beyond the distance case is that malformed NEAR contents remain opaque here: for example, asset_name:foo AND node_name:NEAR(foo OR bar) is rejected by SQLite because OR is not valid inside a NEAR phrase list, but both table projections become inapplicable before either executes the NEAR expression, so search incorrectly succeeds with zero results. Parse and validate the NEAR phrase list before projection so malformed syntax cannot be hidden inside a cross-table conjunction.

Useful? React with 👍 / 👎.

Comment thread Scripts/monolith_offline.py Outdated
Comment on lines +2231 to +2236
try:
rows = self.db.execute(
sql,
(search_query, limit),
).fetchall()
except sqlite3.OperationalError as error:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Catch SQLite corruption errors in the Python search path

When an FTS shadow table is corrupt, SQLite reports SQLITE_CORRUPT and Python raises sqlite3.DatabaseError rather than sqlite3.OperationalError from this fetchall(). The exception therefore escapes and the fallback exits with a traceback instead of the new fail-closed success: false JSON response, unlike the live and native implementations; catch the broader SQLite database error class while preserving the same failure envelope.

Useful? React with 👍 / 👎.

Validate complete FTS5 NEAR phrase grammar before table projection in native and Python paths. Preserve structured Python failures for SQLite corruption, make the native query build fail closed per translation unit, and add focused regression coverage plus synchronized docs.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@kunkunGames kunkunGames changed the title 🔎 Codex: harden project search provenance and FTS repair feat(index): harden project search provenance and FTS repair Jul 27, 2026
tumourlove added a commit that referenced this pull request Aug 1, 2026
…ading as zero results

Three changes to discovery and project search, plus their offline mirrors.

CROSS-NAMESPACE DISCOVER (#112)

discover's filter predicate lived inside the namespace branch, and the
no-namespace branch never parsed filter, offset, limit or detail at all. So
discover(filter="set_property") with no namespace was SILENTLY IGNORED -- the
caller got the full namespace inventory back and no error, and finding a
capability by partial name meant iterating every namespace by hand.

A non-empty filter with no namespace now searches the whole registry in
registry order, tagging each row with its owning namespace and reporting
matched_namespaces pre-pagination, so "which namespace owns this" stays
answerable even when rows are capped. Unlike the per-namespace path this one
is not naturally bounded -- ~150 actions worst case there versus ~1,400+ here
-- so an absent limit caps at 50 while an explicit limit=0 still means ALL,
matching the existing contract. Uncapped it would emit a row per match across
the entire registry, from the tool whose terse redesign existed to cut payload.

PAGINATION OVERFLOW (#112, independently shippable)

SliceEnd = FMath::Clamp(SliceStart + Limit, ...) formed SliceStart + Limit as
int32 before clamping. offset=1 with limit=2147483647 signed-overflows -- and
that call is well-formed, because TryGetNumberField range-checks against
TNumericLimits<int32> and so accepts INT32_MAX verbatim while rejecting
anything larger. On MSVC it wrapped negative, Clamp collapsed the slice, and
the caller got an EMPTY actions array plus a NEGATIVE next_offset. Min() is
now taken first so every intermediate stays in range, and next_offset emits
SliceEnd, which is value-identical and structurally overflow-free.

SEARCH ERROR CLASSIFICATION AND VALIDATION (#113 slices)

while (Step() == Row) treats a statement error as end-of-results, and
Stmt.Create / SetBindingValueByIndex returns were unchecked, so a genuine
FTS5 syntax error or a prepare failure surfaced as "no matches" -- the caller
could not tell a bad query from an empty index. Caller errors now return
-32602 and storage or schema failures -32603, with LIMIT bound as a parameter
rather than formatted into the SQL. Adds query validation, a limit clamp and
a 4096-character query cap.

DELIBERATE DEVIATION FROM THE PLAN, on review of live SQLite behaviour: the
plan's literal classification would have regressed column-qualified search.
search runs two independent statements, and a query like node_name:Branch
errors on the asset table before returning real hits from the node table --
so it works today only because the error is swallowed as end-of-results.
Treating any per-table error as fatal would turn a working feature into a
hard error. A table reporting "no such column" is therefore treated as not
applicable and skipped; the query is only rejected when BOTH tables reject it,
or when the error matches the table-independent syntax patterns. That
behaviour is now documented rather than accidental, and has a regression test.

PR #113's 1449-line FTS5 grammar parser is NOT taken. It introduced an
unguarded recursive AST teardown with no query-length cap, which kills the
editor process at roughly 32KB of chained terms -- reachable over MCP, on the
game thread, with no recovery -- and a 15,935-query differential against real
SQLite showed the safety property it claimed was actually delivered by an
eight-line error classifier. Queries continue to bind straight into MATCH ?,
where SQLite's own bounded grammar handles them.

Reported and prototyped by @kunkunGames (#112, #113).
tumourlove added a commit that referenced this pull request Aug 1, 2026
Folds the per-phase entries into the shared files in one pass. They were
written as fragments during execution because five agents worked the tree
concurrently and a shared file has no conflict marker -- a concurrent edit
just loses the earlier entry silently.

Also records the follow-ups the reviews surfaced but the fixes deliberately
did not absorb, so they survive as tracked gaps rather than as review
comments nobody reads again:

- Pin-type strictness is still asymmetric: three variable actions keep the
  best-effort parser, so an unresolvable enum: token there still yields a
  silent plain byte. The reported #115 symptom is fixed; that failure mode
  is not fully closed.
- Offline project.* parity is UNGATED. verify_offline_parity.py covers
  cppreflect, network, decision, risk and source, and has no project cases --
  confirmed in its own case list. So the three project search implementations
  are kept in step by hand and make_release.ps1's parity gate cannot catch a
  drift. Until that is closed, an edit to one is an edit to all three.
- A query naming columns from both FTS tables is refused rather than served,
  because per-table projection was the one thing PR #113's rejected parser
  provided.
- PR #102 part 2 is deferred pending redesign, not rejected: the need is
  real and ReconstructNode() after a node-property write is the genuinely
  new insight in that submission.
@tumourlove

Copy link
Copy Markdown
Owner

Partly shipped in v0.22.0. I land contributor fixes as my own commits rather than merging the branch — I keep the shipped history single-author for release integrity, and credit you in the release notes instead. No reflection on the patch.

Taken: the error classification (-32602 for caller syntax, -32603 for storage and schema failures), input validation, the limit clamp, a query-length cap, and the documentation corrections — including NEAR/3, which is documented but always errors; the valid form is NEAR(a b, 3).

The classifier is the load-bearing piece and it went in verbatim. Its five Contains patterns are what actually converts an FTS5 syntax error into a caller error, and previously while (Step() == Row) treated a statement error as end-of-results, so a bad query and an empty index were indistinguishable.

I have not taken the FTS5 projection parser. I had it extracted, compiled standalone and fuzzed before deciding, and the reason is concrete: its AST destructor recurses without a depth guard and nothing caps query length, so a query of roughly 12,000–16,000 chained terms — about 32 KB, trivially reachable over MCP — kills the editor process. It dies inside project(), tearing down the AST built for a query it had already correctly refused. FullTextSearch runs on the game thread, so that is editor death with no recovery, and today's shipped code has no such exposure because the query binds straight into MATCH ? where SQLite's own bounded grammar handles it.

A 15,935-query differential against real SQLite also showed 62 false accepts and 315 false rejects — so the parser does not eliminate FTS5 syntax errors either; your ~8-line classifier does. That is 1449 lines of permanent maintenance surface for a property already delivered in eight.

The query-length cap went in anyway. It is free defence in depth and it is the mitigation if any AST-building layer ever lands.

One thing you found that I want to credit separately, because it is a genuine release-integrity hole and it is now fixed: Tools/MonolithQuery/build.bat reports success on a failed compile. Your diagnosis of the mechanism was not quite right — cl with two sources is a single invocation returning a single code — but the bug is real. cmd.exe expands %VAR% for a whole parenthesised block when the block is parsed, so the inner %ERRORLEVEL% carried the preceding where cl's exit code. The test was literally if 0 equ 0 and the failure branch was unreachable, so a failed compile shipped a stale monolith_query.exe with exit 0.

I fixed it with conditional execution rather than the 63-line restructure, because if not errorlevel 1 is a signed >= test and would still let a compiler hard fault (-1073741819) through. I also wired up check_offline_exe_fresh.py, which was documented in two specs but invoked by no script — so that whole class was ungated. Both ran for the first time in this release.

Deferred: per-table query projection (the one thing the parser genuinely provided — a query naming columns from both FTS tables is still refused) and closing the project.* offline-parity gap, which your PR correctly implies exists. Both are logged.

@tumourlove tumourlove closed this Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants