Skip to content

fix(assistant): integrity, grounding & guard fixes from the PR #79 review - #223

Open
nedda76 wants to merge 17 commits into
midt-bg:mainfrom
nedda76:fix/assistant-integrity-grounding
Open

fix(assistant): integrity, grounding & guard fixes from the PR #79 review#223
nedda76 wants to merge 17 commits into
midt-bg:mainfrom
nedda76:fix/assistant-integrity-grounding

Conversation

@nedda76

@nedda76 nedda76 commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Отстранява набор от конкретни дефекти в асистента, открити при ревю на PR #79 (насочено към петте области, които екипът поиска: интегритет на стойностите, SQL guard-а, RAG/grounding-а, hardening-а и provisioning-а). Всяка находка е проверена срещу кода/миграцията и покрита с тест.

Какво влиза (по комит)

1. Речник на данните ↔ миграция (describe-schema.ts) — dictionary-ът, който моделът третира като твърд факт, беше се разминал с packages/db/migrations/0000_init.sql:

  • amendments няма contract_id — връзката е през unp/contract_number;
  • parties няма role — реалните колони са party_key, eik, ocid, party_id, name…;
  • value_flag enum-ът беше без value_low;
  • amount_eur IS NULL беше описано като „= value_suspect“, а има няколко причини (чужда валута без курс / value_suspect без оценка / липсва подписана+текуща); броят „непотвърдени“ е value_flag='value_suspect' (home_totals.suspect), не редовете с NULL;
  • data_freshness е таблица, не view.

2. RAG grounding (rag.ts, system-prompt.ts) — затваря случая, в който RAG ход остава по-слабо ограничен от no-RAG fallback-а:

  • твърдите DATA_TRAPS вече се инжектират безусловно (RAG само добавя релевантните таблици/примерни заявки), така че пропуск в retrieval-а не може да изхвърли правилото SUM(amount_eur);
  • добавен е праг на релевантност (MIN_SCHEMA_SCORE) — под него връщаме по-малко/нула чънкове, а нула връща пълния речник (безопасният изход).

3. SQL scalar guard (sql-guard.ts) — блокира group_concat / json_group_array / json_group_object: колабират цял table scan в една огромна клетка, която се материализира преди capRows — същият клас memory-amplification, който вече е блокиран за printf/randomblob, едно ниво по-нагоре.

4. Интегритет на справката (report-schema.ts, emit-report-schema.ts):

  • prose gate-ът пропускаше трилион/билион/квадрилион — „3 трилиона лева“ минаваше целия gate (цифрата не стига до „лева“ през кирилската дума), необвързана стойност с порядък по-висока от „12 млрд.“; добавени към шаблона на изписаните величини;
  • align на колона вече се валидира срещу left|right, а resolved колоните се строят изрично (без spread), за да не стигне неизвестно свойство до renderer-а;
  • добавени тавани на дължините на масивите (blocks/items/columns) във validateEmitShape.

Съзнателно ИЗВЪН обхвата (за отделно обсъждане/PR)

Това са архитектурни решения, не еднолинейни поправки — държа ги настрана, за да остане PR-ът ревюируем, и ги описвам в коментара към PR #79:

  • read-only D1 път / реален opcode(EXPLAIN) слой за run_sql — днешните два слоя стоят пред read-write binding;
  • account-wide circuit-breaker за платените BgGPT ходове (декларираният DO още не е закачен);
  • first-query cost bound (DoW: първата заявка на хода тече без таван);
  • cross-check на link.kind спрямо префикса на id-то и съгласуваност format↔стойност.

Проверка

  • pnpm test (apps/web): 342 passed
  • pnpm typecheck: чисто
  • prettier --check: чисто

Базирано на main, защото кодът на асистента вече е там; ако предпочитате да влезе през feat/ai-assistant, лесно пренасочвам.

Свързани issue-та

@ydimitrof ydimitrof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Преглед на PR: fix(assistant): integrity, grounding & guard fixes

Обобщение

PR-ът е фокусиран follow-up от прегледа на #79 и затяга няколко защитни слоя в асистента без разширяване на обхвата. Промените са атомарни, добре мотивирани в коментарите и всяка е придружена от смислени тестове (не тривиални „cheater" тестове).

Фаза 0 — Сигурност (ЗАДЪЛЖИТЕЛНА проверка): ✅ ЧИСТО

  • Няма хардкоднати тайни, ключове, пароли или токени.
  • Няма нови/променени URL адреси, нито промени в зависимостите.
  • Няма злонамерени шаблони (backdoor, code injection, обфускация).
  • Всички промени всъщност засилват защитата: разширен guard за SQL агрегати, capping на масивите от модела, whitelist за align, floor за релевантност при RAG.

По измерения

Сигурност (agent-level): 1.0/1.0

  • sql-guard.ts: добавени group_concat/json_group_array/json_group_object към блокираните функции — коректно затваря клас memory-amplification (една клетка от пълно сканиране преди capRows). Регексът изисква \s*\(, така че колона с такова име не се блокира по грешка.
  • report-schema.ts: явното изграждане на колоните вместо { ...c } премахва passthrough на непознати свойства към рендера — добра defensive промяна.
  • emit-report-schema.ts: whitelist isAlign (само left|right|undefined) предотвратява попадане на out-of-enum стойност в атрибут/style.

Тестове: 3.0/3.0

  • Всяка нова пътека има целеви тест: capping на масиви, align whitelist, floor при RAG (над/под прага и празен резултат), spelled трилион/билион, string-building агрегати, безусловни hard-traps под RAG. Тестовете са проектирани да разкриват дефекти, не да минават формално.

Качество на кода: 2.0/2.0

  • Извеждането на renderTraps() премахва дублиране между describeSchema и system-prompt (single source of truth) — точно според „NO CODE DUPLICATION".
  • Стилът е консистентен с останалата част на модула.

Производителност: 2.0/2.0

  • Няма регресии; caps-овете (MAX_BLOCKS/ITEMS/COLUMNS) са безопасни горни граници далеч над реален отчет.

Документация: 2.0/2.0

  • Коментарите обясняват „защо" на всяка граница; актуализирани доки за таблиците amendments/parties и коригиран grain на data_freshness (view→таблица).

Съответствие с CLAUDE.md

Без частични имплементации, без TODO/dead code, без смесени концерни, без ресурсни течове. Промените са атомарни и в рамките на обхвата.

Заключение

Висококачествен, добре тестван защитен PR. Нямам блокиращи забележки. Оставям два незадължителни инлайн коментара за проверка (grounding към home_totals.suspect и робъстност при липсваща score), които не блокират сливането.

Композитна оценка: 9.5/10 — препоръка: одобрение с малки уточнения.

Comment thread apps/web/app/lib/assistant/describe-schema.ts Outdated
Comment thread apps/web/app/lib/assistant/rag.ts Outdated

@ydimitrof ydimitrof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Преглед на PR: fix(assistant): integrity, grounding & guard fixes (follow-up на PR #79)

ВЕРДИКТ: COMMENT — одобрявам по същество; няма блокиращи проблеми. Не публикувам автоматично, изчаквам вашата проверка преди изпращане.

Фаза 0 — Скенер за сигурност (задължителен, изпълнен пръв)

  • Твърдо кодирани тайни: няма (0 API ключа/пароли/токени). ✅
  • Промени по URL адреси: няма нови/променени URL адреси. ✅
  • Зловредни модели (backdoor / инжекция на код / обфускация): няма. ✅
  • Промени по зависимости: няма нови пакети. ✅
  • Заключение на Фаза 0: ЧИСТО → продължавам към същинския преглед.

Важно: този PR е серия от защитни (hardening) поправки — той затяга SQL guard-а, ограничава размерите на масивите от модела, въвежда праг за релевантност при RAG и разширява детектора на изписани числа. Т.е. промените намаляват риска, а не го увеличават.

Анализ по файлове

sql-guard.ts — Добавени са group_concat / json_group_array / json_group_object към черния списък. Правилно: това са агрегати, които колабират цял table scan в една огромна клетка, преди capRows да я измери (memory amplification, същият клас като printf). Регулярният израз е коректен (\b…\s*\(); коментарите се премахват преди проверката, така че group_concat/*x*/( няма да заобиколи гарда. Fail-closed — правилен избор. (Виж инлайн бележка за остатъчен вектор с рекурсивни CTE.)

emit-report-schema.ts — Въведени горни граници MAX_BLOCKS=100, MAX_ITEMS=50, MAX_COLUMNS=50 и whitelist валидатор isAlign (само undefined|left|right). Затваря DoS през неограничена дължина на масив и предотвратява out-of-enum стойност за align да достигне рендерер, който я интерполира в атрибут/стил (защита срещу attribute/HTML injection — OWASP A03). Тестовете покриват center, "><b> и препълнените масиви. ✅

report-schema.ts — Добавени трилион|билион|квадрилион към стема за изписани величини. Коректно затваря пропуска „3 трилиона лева“ (дефамационен вектор един порядък над „12 млрд.“). Преминаването към явно построяване на колоните (без spread { ...c }) е важно подобрение: спира пренасянето на непознати, подадени от модела свойства към рендерера, тъй като validateEmitShape не отхвърля непознати ключове. Много добра защита в дълбочина. ✅

rag.ts — Въведен праг за релевантност MIN_SCHEMA_SCORE=0.35 с (m.score ?? 0). Логиката е правилна: под прага се връщат по-малко или нула чънкове, а нула кара buildSystemPrompt да падне обратно към пълния статичен речник — безопасният изход. ?? 0 защитава срещу backend, който пропуска score (не се инжектира като „контекст“ без ранг). ✅

system-prompt.ts / describe-schema.ts — Твърдите MUST/NEVER капани (SUM само amount_eur, ocid ≠ УНП, …) вече се инжектират безусловно чрез споделения renderTraps(), дори при RAG. Това коригира реалния дефект, при който извличането е заменяло капаните и е оставяло RAG-хода с по-малко ограничения от no-RAG отстъплението (пропускът, довел до SUM(amount)). Споделеният рендерер премахва риска от дрейф между двата пътя. Актуализирани са и enum-ите (value_flag + value_low) и документацията на таблиците amendments/parties. ✅

OWASP съответствие

  • A03 (Injection): SQL guard е fail-closed и SELECT-only; изписаните числа и подаваните от модела полета се санитизират/валидират. Няма конкатенация на непроверен вход в SQL в този diff.
  • A04 (Insecure Design) / DoS: новите горни граници и блокирането на string-агрегатите адресират точно ресурсното изчерпване.
  • Няма нови вектори за A01/A02/A05/A08.

Тестове и качество

Всяка промяна е придружена от смислен, целенасочен тест (align whitelist, лимити на масивите, RAG праг, трилион/билион, SQL агрегати, безусловни капани). Тестовете са проектирани да разкриват дефекти, не да минават тривиално. Именуването и стилът са консистентни с останалата кодова база. Няма частична имплементация, TODO-та, дублиран или мъртъв код.

Незадължителни препоръки (не блокират сливането)

  1. sql-guard.ts — Остатъчен вектор: рекурсивен CTE, който строи низ чрез || (напр. WITH RECURSIVE r(s) AS (SELECT 'x' UNION ALL SELECT s||'x' FROM r LIMIT 1e6) SELECT s), може да материализира огромен низ, без нито една от блокираните функции. Извън обхвата на този PR (съществуващ пропуск), но си струва отделен follow-up.
  2. report-schema.ts — Стемата покрива до квадрилион, но не и квинтилион/секстилион. Отворен край с ниска вероятност; евентуално по-общ шаблон в бъдеще.
  3. describe-schema.ts — Grain на data_freshness е сменен от „view“ на „таблица“. Моля потвърдете, че обектът реално е таблица, а не изглед — това е документация, която моделът чете при писане на SQL.

Обща оценка: висококачествен, добре тестван защитен PR. Препоръчвам одобрение след вашата ръчна проверка.

Comment thread apps/web/app/lib/assistant/sql-guard.ts Outdated
Comment thread apps/web/app/lib/assistant/report-schema.ts Outdated
Comment thread apps/web/app/lib/assistant/report-schema.ts
Comment thread apps/web/app/lib/assistant/emit-report-schema.ts
Comment thread apps/web/app/lib/assistant/rag.ts

@ydimitrof ydimitrof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ревю на PR: fix(assistant): integrity, grounding & guard fixes from the PR #79 review

Здравей, Явор! Прегледах промените изключително подробно, с фокус върху сигурност и цялост на данните (SQL инжекции, обход на guard-а, XSS през рендер, изтичане на ресурси). Ето обобщението.

ВЕРДИКТ: COMMENT — не блокиращо. Одобрявам след потвърждение по единствената бележка по-долу (string_agg). Не съм публикувал коментари — това е чернова за твоя преглед.

Фаза 0 — Сканиране за критична сигурност (по дифа)

  • Твърдо кодирани тайни (API ключове/пароли/токени): няма.
  • Промени по URL/крайни точки: няма.
  • Нови/променени зависимости: няма.
  • Зловредни шаблони (бекдор, инжекция на код, обфускация): няма. Всяка промяна затяга съществуваща защита, а не я отслабва.
  • Резултат: CLEAN — продължавам към прегледа по същество.

Оценка по файлове

  • sql-guard.ts — Добавени group_concat / json_group_array / json_group_object към забранените функции. Правилно — това е същият клас усилване на паметта като printf/format (цял скан се събира в една клетка преди capRows). Виж единствената бележка за string_agg.
  • report-schema.ts — (1) Регексът за изписани величини е сменен от точен списък (милиард|милион|хиляд) на суфиксите (илион|илиард|хиляд), което затваря дупката „3 квинтилиона лева" нагоре без регресия — тестовете го потвърждават, старите величини минават през суфиксите. (2) Колоните вече се строят с явни полета вместо spread — това спира пренасянето на непознати, подадени от модела свойства към рендера (validateEmitShape не отхвърля непознати ключове). Много добра защита.
  • emit-report-schema.ts — Тавани MAX_BLOCKS/ITEMS/COLUMNS и whitelist за align (left|right). align валидирането спира атрибут/style инжекция при рендер (тестът с '"><b>' го покрива). Капацитетите провалят валидацията, така че скъпият път в bindReport не се изпълнява — коректно.
  • rag.ts — Праг за релевантност MIN_SCHEMA_SCORE = 0.35 с ?? 0 защита за липсващ score. Правилна посока: под прага → падане към пълния речник (по-силна заземеност от частичен RAG). Тестовете покриват и трите случая.
  • system-prompt.ts / describe-schema.ts — Твърдите MUST/NEVER капани се инжектират безусловно и под RAG чрез споделен renderTraps(), така че двата пътя не могат да се разминат. Затваря първопричината (RAG turn с по-малко ограничения от no-RAG, който пусна SUM(amount)). Обновените описания на amendments/parties/data_freshness и разграничението amount_eur IS NULLvalue_suspect са коректни пояснения (само документация в промпта).

OWASP / CLAUDE.md

  • Няма инжекция (SQL guard е allowlist за SELECT + денилист за опасни функции; рендерът получава само whitelisted align). Валидацията на вход е засилена. Няма изтичане на ресурси. Няма мъртъв код, дублиране или частична имплементация. Наименованията са консистентни. Тестовете са смислени (проверяват отхвърляне, не тривиално минаване).

Единствена бележка (не блокираща)

Виж inline коментара в sql-guard.ts за string_agg. Понеже подходът е денилист, той е по природа непълен — заслужава да се обмисли allowlist на агрегатните функции в бъдеще.

Много добра, дисциплинирана работа по затягане на цялостта и заземеността. 👍

Comment thread apps/web/app/lib/assistant/sql-guard.ts Outdated

@ydimitrof ydimitrof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ревю на PR: fix(assistant): integrity, grounding & guard fixes from the PR #79 review

ВЕРДИКТ: COMMENT — одобрим по същество, без установени блокери; финално одобрение след зелена CI (тестове + покритие). Не съм публикувал inline коментари по твое изрично искане.


Обобщение

PR-ът е серия защитни (security-hardening) корекции по AI асистента като follow-up на ревюто на PR #79. Промените са атомарни, добре мотивирани в коментарите и придружени с целеви тестове. Не открих зловреден код, задни вратички (backdoors), обфускация, инжекции или изтичане на тайни. Промяната стеснява повърхността на атака — не я разширява.

Фаза 0 — Сканиране за критична сигурност (ЧИСТО)

  • Твърдо кодирани тайни (secrets/API ключове/пароли): няма.
  • Промени в URL/домейни: няма.
  • Зловредни шаблони (backdoor/eval/обфускация/code injection): няма.
  • Нови зависимости: няма промени в package.json/lockfile — нулев риск от dependency-supply-chain.
  • SQL експлойти: промяната всъщност засилва SQL пазача (виж по-долу).

Преглед по файлове

sql-guard.ts — Добавени в denylist-а group_concat, string_agg (SQLite ≥3.44 синоним, релевантен за D1), json_group_array, json_group_object. Това затваря клас „memory-amplification" — string-building агрегати, които сгъват цял table scan в една огромна клетка, материализирана преди capRows да я измери (потенциален OOM на изолата). Проверих локално: пазачът блокира изброените функции и коректно пропуска легитимни sum(amount_eur) / concat(...). Забележка: това остава denylist (игра на догонване); коментарът честно признава, че устойчивото решение е позитивен allowlist — приемам го като tracked-separately.

report-schema.ts — (1) Регексът за „изписани с думи" величини мина от изричен списък (милиард|милион|хиляд) към суфиксите илион|илиард|хиляд, което затваря скалата нагоре (трилион/квадрилион/квинтилион/секстилион…). Потвърдих локално, че „3 трилиона лева", „три квинтилиона" и старите случаи се флагват; „Илион" (Троя) се over-флагва — приемливо, тъй като гейтът трябва да fail-toward-flagging. (2) Изричното изграждане на колоните (без { ...c } spread) е правилно — пречи неизвестни, подадени от модела полета да достигнат до renderer-а. Добра защита в дълбочина.

emit-report-schema.ts — Горни граници MAX_BLOCKS=100 / MAX_ITEMS=50 / MAX_COLUMNS=50 върху дължините на масивите (преди беше ограничен само размерът в байтове на редовете, не броят елементи) + isAlign whitelist (undefined|left|right). Проверих renderer-а (ReportBlockRenderer.tsx:180): align се сравнява с 'right' и се мапва към контролиран клас 'num', т.е. не се интерполира сурово в атрибут/стил — whitelist-ът е коректна защита в дълбочина. Границите са доста над реален отчет — без функционален риск.

rag.ts — Въведен праг MIN_SCHEMA_SCORE=0.35 за релевантност. Логиката е правилна: под прага се връщат по-малко/нула чънкове, а нула кара buildSystemPrompt да падне обратно към пълния статичен речник — т.е. по-безопасният изход. (m.score ?? 0) е коректна защита срещу backend без score (чете се като „под прага", отпада). Покрито с тестове (включително случая без score).

system-prompt.ts / describe-schema.ts — Твърдите MUST/NEVER капани (SUM само amount_eur, ocid≠УНП, …) вече се инжектират безусловно, дори при RAG. Това коригира реален integrity-дефект: RAG turn можеше да остане с по-малко ограничения от no-RAG fallback-а (пътят, който пропускаше SUM(amount)). renderTraps() е споделен между двата пътя — премахва дрифт. Актуализациите на речника (value_low в enum-а, колони на amendments/parties, data_freshness като таблица) са документационни и консистентни.

Тестове

Всеки промяна носи целеви тестове (align whitelist, cap-ове на масиви, relevance floor вкл. missing-score, суфиксите на величините, string-building агрегати, безусловни hard-traps). Тестовете изглеждат смислени (не тривиални). Не мога да потвърдя точен процент покритие/100% pass от diff-а — това остава за CI.

OWASP

  • A03 Injection — SQL пазачът е усилен; prose-gate-ът и sanitizeProse ограничават материални числа в публичния отчет. ОК.
  • A04 Insecure Design / DoS — cap-овете на масивите и блокирането на amplification-агрегати адресират ресурсно изчерпване. ОК.
  • A05 Misconfiguration — без промени в конфигурация/тайни. ОК.
  • Няма разширяване на повърхността на атака.

Незадължителни препоръки (не блокират)

  1. sql-guard: приоритизирайте позитивния function-allowlist (вече споменат в коментара) — denylist-ите ще изостават от нови алиаси.
  2. prose-regex: over-flag на „Илион"/„милиони" контексти може да увеличи retry-та на модела; приемливо, но си струва да се следи като UX сигнал.
  3. Уверете се, че validateEmitShape винаги предхожда bindReport в реалния път (иначе align разчита само на renderer-а — който все пак е безопасен).

Заключение: качествен, добре тестван защитен PR. Препоръчвам сливане след зелена CI.

@nedda76

nedda76 commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator Author

Благодаря за подробното ревю! Кратко по трите незадължителни препоръки:

  1. Позитивен function-allowlist за SQL пазача — съгласна, денилистът е игра на догонване. Проследено е в run_sql: durable memory-amplification защита (allowlist на функции + cell-size cap преди capRows) #227 (позитивен allowlist + таван на размера на клетката преди capRows). L2 на feat(ai-assistant): conversational analytic layer (BgGPT) #79 вече има такъв allowlist, така че при мърджа на feat(ai-assistant): conversational analytic layer (BgGPT) #79 в main тази посока се затваря по-устойчиво и L1 остава cheap backstop.

  2. Over-flag на „Илион"/„милиони" контексти — приемам го като съзнателен fail-toward-flagging компромис: по-добре един retry, отколкото необвързано число на публичен отчет. Ще го следя като UX сигнал; ако retry-тата пораснат, това е още един довод за по-структурирания подход от run_sql: durable memory-amplification защита (allowlist на функции + cell-size cap преди capRows) #227.

  3. validateEmitShape преди bindReport — потвърждавам за реалния път: finalizeReport (tools.ts:222) валидира shape-а, връща рано при грешка (:224) и достига bindReport само при валиден вход (:227). Освен това bindReport е независимо defensive (изричното изграждане на колоните + коментарът на report-schema.ts:380 „Don't rely on validateEmitShape running first"), така че align не разчита единствено на renderer-а.

Локално тестовете (app/lib/assistant), typecheck и prettier минават; чакам зелена CI за финалното одобрение.

@lyubomir-bozhinov lyubomir-bozhinov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Прегледах стриктно на връх 038d4e3 — силна работа, guard-овете издържаха на доста red-team. Проверих (и всички отхвърлени): stacked ; вътре в '…', CTE кръстен като blocked table, recursive CTE без RECURSIVE, pragma_table_info(...) TVF, json_each/generate_series, tautological JOIN ON 1=1, LIMIT offset,count / отрицателен / 1e9, load_extension/group_concat/string_agg, numeric-entity double-encode, markdown-split число, homoglyph цифри, prompt injection през RAG контекста. Двуслойният guard (L1 текст + L2 AST) е издържан; bindReport explicit-field reconstruction и безусловният hardTraps() (fix за grounding-регресията) са точно правилните решения.

Едно дребно (ново в PR-а, hardening):

  • apps/web/app/lib/assistant/emit-report-schema.ts (~ред 68) — validateEmitShape не прави early-return след MAX_BLOCKS проверката, тъй че обхожда целия (model-emitted) масив. finalizeReport gate-ва bindReport зад shape.ok, тъй че няма downstream щета — но добавете return { ok:false, errors } веднага след push-а (както за !Array.isArray по-горе), също и за MAX_ITEMS.

Прерогатив за прод (не са scope на този PR, вече tracked — само маркирам): || string-concat memory-amplification (#227) и cost-ът на първата run_sql заявка (D1 таксува scanned rows, #122). Струва си да са hard-prerequisite за provision, не просто roadmap.

Одобрявам.

nedda76 added a commit to nedda76/sigma that referenced this pull request Jul 22, 2026
An over-cap blocks/items/columns array is exactly the unbounded structure
the ceilings guard against, yet validateEmitShape recorded the length error
and then walked the whole array anyway — doing the very scan the cap exists
to refuse. Return before the per-block scan on oversized blocks, and skip the
per-element scan on oversized items/columns. Behaviour is unchanged for valid
reports (ok:false either way); this only stops the wasted walk. Test asserts a
single cap error with no per-element errors, proving the array is not scanned.

Addresses lyubomir-bozhinov's review note on PR midt-bg#223.
@nedda76

nedda76 commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

Благодаря за red-team прегледа! По бележката за validateEmitShape: адресирана е в ab2e247 — ранен return { ok:false, errors } веднага след MAX_BLOCKS проверката, плюс пропускане на per-element скана и при over-cap items/columns (същият принцип, приложен и на трите тавана). Тестът твърди точно един cap error без per-element грешки — доказва, че масивът не се обхожда.

Клонът е обновен и с актуалния main (merge, без пренаписване на историята) — конфликтите в describe-schema.ts (речникът се разви на main; запазих уникалния trap „непотвърдени → home_totals.suspect") и osv-scanner.toml (запазени са и двете изключения) са разрешени. Локално: 491/491 web теста, typecheck 7/7, prettier чист. PR-ът вече е MERGEABLE.

@nedda76

nedda76 commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

@ydimitrof @lyubomir-bozhinov @todorkolev един бърз преглед моля.

nedda76 added a commit to nedda76/sigma that referenced this pull request Aug 13, 2026
An over-cap blocks/items/columns array is exactly the unbounded structure
the ceilings guard against, yet validateEmitShape recorded the length error
and then walked the whole array anyway — doing the very scan the cap exists
to refuse. Return before the per-block scan on oversized blocks, and skip the
per-element scan on oversized items/columns. Behaviour is unchanged for valid
reports (ok:false either way); this only stops the wasted walk. Test asserts a
single cap error with no per-element errors, proving the array is not scanned.

Addresses lyubomir-bozhinov's review note on PR midt-bg#223.
@nedda76
nedda76 force-pushed the fix/assistant-integrity-grounding branch from f016dcd to dcb7f60 Compare August 13, 2026 12:40
nedda76 added a commit to nedda76/sigma that referenced this pull request Aug 16, 2026
An over-cap blocks/items/columns array is exactly the unbounded structure
the ceilings guard against, yet validateEmitShape recorded the length error
and then walked the whole array anyway — doing the very scan the cap exists
to refuse. Return before the per-block scan on oversized blocks, and skip the
per-element scan on oversized items/columns. Behaviour is unchanged for valid
reports (ok:false either way); this only stops the wasted walk. Test asserts a
single cap error with no per-element errors, proving the array is not scanned.

Addresses lyubomir-bozhinov's review note on PR midt-bg#223.
@nedda76
nedda76 force-pushed the fix/assistant-integrity-grounding branch from dcb7f60 to 7c31fa9 Compare August 16, 2026 09:14

@ydimitrof ydimitrof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Преглед на PR: „fix(assistant): integrity, grounding & guard fixes from the PR #79 review“

Фаза 0 — Скенер за критична сигурност: ЧИСТО ✅

  • Твърдо кодирани тайни: няма (нито ключове, пароли или токени).
  • Промени по URL адреси: няма нови/променени URL.
  • Злонамерени модели (backdoor, инжекция на код, обфускация): няма. Обратно — промените в sql-guard.ts затягат защитата, като добавят group_concat/string_agg/json_group_array/json_group_object към denylist-а срещу амплификация на паметта.
  • Зависимости: единствената промяна е нов IgnoredVulns запис в osv-scanner.toml за sharp (транзитивна, само за dev, през miniflare) — добре обоснован, с дата за повторно преразглеждане. Не влиза в деплойнатия Worker.

Резултат: няма блокиране, продължавам към преглед по измерения.

Обобщение по измерения

Сигурност (код-ниво) — отлично. Разширяването на denylist-а е коректно; регулярният израз с \b…\s*\( не дава очевидни фалшиви положителни (напр. my_group_concat( не се хваща заради _ като част от думата). Явното изграждане на колоните в bindReport (вместо { ...c }) е правилна защита срещу пренасяне на непознати, подадени от модела свойства към рендерера, тъй като validateEmitShape не отхвърля непознати ключове. Whitelist-ът isAlign (left|right) затваря дупка, при която стойност извън enum-а би достигнала атрибут/стил.

Тестове — 3/3. Всяка промяна е придружена от смислени тестове, които разкриват дефекти, а не минават тривиално: капове на масивите (къс път при over-cap), floor на релевантността в RAG (вкл. защитния случай на липсващ score), суфиксите -илион/-илиард при всички магнитуди, новите SQL агрегати. Тестовете за short-circuit проверяват точното поведение (само cap грешка, без обхождане).

Качество на кода — отлично. renderTraps() премахва риска от дрейф между describeSchema и RAG hard-traps блока — единствен източник на истина. Няма дублиране, мъртъв код или смесени отговорности.

Производителност — подобрение. Капове на масивите (MAX_BLOCKS/ITEMS/COLUMNS) и SQL агрегатните забрани реално намаляват риска от OOM в изолата. Няма регресии.

Документация — пълна. Коментарите обясняват „защо“, а не „какво“; актуализирани са речниковите описания (amendments, parties, data_freshness като таблица), и е добавено важното пояснение, че amount_eur IS NULL НЕ Е синоним на value_suspect.

Архитектура / grounding — стабилно. Безусловното инжектиране на hard-traps (hardTraps()) в RAG пътя коректно гарантира, че извличането никога не оставя хода с по-малко ограничения от no-RAG fallback-а — това е ключова поправка за интегритета. Заглавието # в RAG пътя е контекстуално правилно (там няма обгръщащ # Речник на данните).

Забележки (незадължителни)

  • Дребна консистентност: новият ignoreUntil = 2026-10-22 използва формат само-дата, докато съществуващият запис по-горе е пълен RFC3339 (2026-10-01T00:00:00Z). Виж inline коментар.
  • Възможна лека редундантност: ако trap-текст попадне и в извлечените RAG чънкове, той може да се появи два пъти (веднъж през hardTraps()). Безвредно, но си струва да се провери дали корпусът индексира trap-редовете.

Композитна оценка: 9.6/10

Няма открити дефекти. Промените са атомарни, добре тествани и подобряват сигурността и grounding-а. Препоръка: APPROVE.

Comment thread osv-scanner.toml Outdated

@ydimitrof ydimitrof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ревю на PR: fix(assistant): integrity, grounding & guard fixes

Фаза 0 — Сигурност (задължителен сканиращ гейт): ЧИСТО ✅

  • Твърдо кодирани тайни: няма. BGGPT_API_KEY се задава през wrangler secret put (интерактивно, некомитнато) — правилен подход.
  • URL промени: няма нови външни URL-и; само wrangler/Vectorize/R2 ресурсни имена.
  • Злонамерени шаблони: няма backdoor/eval/обфускация. Всъщност PR-ът затяга атакувателната повърхност (sql-guard denylist, cap-ове на масиви, floor за релевантност).
  • Зависимости: няма нови пакети. osv-scanner.toml добавя два обосновани ignore-а (sharp transitive dev-only през miniflare, react-router RSC unreachable) с ignoreUntil дати и ясни „remove when" условия. Приемливо.

Резултат: PR не се блокира от Фаза 0.

По измерения

Сигурност (agent-level): силно. sql-guard.ts разширява denylist-а с group_concat/string_agg/json_group_array/json_group_object — реален клас за memory-amplification (агрегиране на цял scan в една клетка преди capRows). string_agg като SQLite ≥3.44 синоним на D1 е коректно уловен. Коментарът честно отбелязва, че denylist е „catch-up" игра и че positive allowlist е трайното решение — добра прозрачност. isAlign whitelist в emit-report-schema.ts затваря injection през атрибут/стил дори когато типът твърди, че стойността е невъзможна — правилен defense-in-depth.

Коректност/архитектура: солидно. Изнасянето на trap-овете от RAG корпуса и безусловното им инжектиране през hardTraps() е правилната поправка на реалния дефект (RAG turn с по-малко ограничения от no-RAG fallback-а). renderTraps() премахва дублирането между describeSchema() и hard-traps блока — няма drift. Версионираният native namespace (SCHEMA_NS='schema-v2') с версия и в id-тата коректно поддържа rollback на Worker-а срещу стар кохорт. MIN_SCHEMA_SCORE floor-ът с ?? 0 защита срещу липсващ score е разумен.

report-schema.ts: експлицитното изграждане на колоните (вместо { ...c }) е важна поправка — spread би пренесъл непроверени model-supplied ключове към рендера. Типизирано като EmitTableColumn[], така че tsc гарантира пълнота — без регресия на познати полета.

Тестове: отлични. Всяка промяна има прицелен тест: cap short-circuit (проверява, че over-cap масив НЕ се обхожда), score-floor fallback вкл. scoreless match, композиционен тест през реалния write→read seam (indexSchemaCorpusretrieveSchemaContextbuildSystemPrompt) с exactly-once проверка на всеки trap, суфикс-магнитуди на всяка скала, и string-агрегатите в sql-guard. Тестовете са смислени, не тривиални.

Документация: пълна. README покрива re-indexing семантиката, „WHEN TO BUMP" правилото и rollback прозореца. Коментарите в кода са изчерпателни.

Забележки (ниска важност)

  1. report-schema.ts regex /илион|илиард|хиляд/ — over-flagging е умишлено и безопасно, но виж inline коментара за един реален false-positive извън „Илион".
  2. rag.ts: структурните типове са умишлено стеснена гледка към реалните bindings и не се проверяват срещу тях (route-ът кастира с as unknown as) — вече документирано, но drift срещу worker-configuration.d.ts няма да бъде уловен от tsc. Дръжте под око при промяна на Vectorize API.

Заключение

PR-ът е фокусиран, атомарен (integrity/grounding/guard follow-up), без scope creep, с висококачествени тестове и без открити уязвимости. Не мога да изпълня pnpm --filter web test/coverage в тази среда, затова оставям вердикт COMMENT вместо автоматично APPROVE — при зелен CI (typecheck 0, целият пакет минава, pnpm audit чист, както твърди README) PR-ът покрива критериите за одобрение.

Comment thread apps/web/app/lib/assistant/report-schema.ts Outdated
Comment thread apps/web/app/lib/assistant/sql-guard.ts
@nedda76
nedda76 force-pushed the fix/assistant-integrity-grounding branch from fd3138f to c67a011 Compare August 19, 2026 17:01

@ydimitrof ydimitrof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Преглед на PR: fix(assistant): integrity, grounding & guard fixes

Фаза 0 — Скан за критична сигурност: ЧИСТ ✅

  • Твърдо кодирани тайни: няма. BGGPT_API_KEY се задава през wrangler secret put и изрично се коментира „никога не се комитва".
  • URL промени: няма нови/променени външни URL-и. Промените в osv-scanner.toml са само игнориране на CVE с обосновка и ignoreUntil дата.
  • Злонамерени шаблони: няма backdoor/обфускация/инжекция. Промените в sql-guard.ts всъщност разширяват защитата (блокират string-building агрегати, които могат да OOM-нат isolate-а).
  • Зависимости: няма нови пакети; само документиран, dev-only, transitive sharp CVE ignore.

Обобщение по измерения

  • Сигурност (код): Силна. sql-guard.ts добавя group_concat/string_agg/json_group_array/json_group_object към denylist-а със стабилна обосновка (памет-амплификация преди capRows). emit-report-schema.ts добавя горни граници на масивите (MAX_BLOCKS/ITEMS/COLUMNS) и whitelist за align — коректна защита срещу неограничени структури и срещу out-of-enum стойност, стигаща до renderer, който я интерполира в атрибут/стил. report-schema.ts строи колоните ЯВНО вместо със spread — правилно затваря пропускането на непознати model-подадени полета.
  • Grounding/RAG: Добра инженерна работа. Trap-правилата вече влизат безусловно през hardTraps(), а RAG корпусът съдържа само таблици/примерни заявки — премахва дублирането и гарантира, че RAG turn никога не е с по-малко ограничения от no-RAG fallback-а. Версионираният native namespace (schema-v2) + релевантен праг (MIN_SCHEMA_SCORE) са коректно обосновани, вкл. rollback семантиката.
  • Тестове: Много добро покритие. Новите тестове са смислени (не тривиални): проверяват capping short-circuit, floor-drop, scoreless-match защита, exactly-once рендиране на trap-овете през реалния write→read seam, и разширените prose-magnitude/агрегат случаи.
  • Документация: README и inline коментарите са изчерпателни, вкл. „WHEN TO BUMP" правило и ре-индексиране/rollback процедура.
  • Производителност: Няма регресии; промените намаляват risk-а от OOM и не добавят горещи пътища.

Забележки (незадължителни, defense-in-depth)

Една минорна бележка по sql-guard.ts denylist-а (виж inline коментара) — самите автори вече отбелязват, че denylist е „catch-up game" и че positive allowlist е трайното решение (проследено отделно). Приемам това като известно ограничение, не като блокер.

Заключение

Промените са атомарни, добре тествани и подобряват integrity/сигурността. Няма блокиращи проблеми. Оставям COMMENT с една незадължителна бележка; препоръчвам мърдж след потвърждение, че пълният тестов пакет на apps/web минава (не мога да го изпълня в тази среда).

Comment thread apps/web/app/lib/assistant/sql-guard.ts Outdated

@ydimitrof ydimitrof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ревю на PR: fix(assistant): integrity, grounding & guard fixes

Обща оценка: ~9.2/10 — COMMENT (мержваем след потвърждаване на deploy-предпоставката).

Phase 0 — Сигурност (критичен скан): ЧИСТО ✅

  • Няма hardcoded secretsBGGPT_API_KEY минава само през wrangler secret put, изрично документирано „никога не се комитва".
  • Няма нови/съмнителни URL-и и без обфускация/backdoor патерни.
  • Зависимости: промяната в osv-scanner.toml (sharp <0.35.0 libvips CVE) е ignore, а не нов пакет — обосновката е коректна (транзитивна dev-only зависимост на miniflare, не влиза в деплойнатия Worker, има ignoreUntil дата за ре-евалуация).
  • Промените в sql-guard.ts подобряват позата по сигурност (blokира string-building агрегати и quoted-identifier bypass).

Силни страни

  • Отлично тестово покритие — всяка поправка идва със смислен, целеви тест (не тривиален): capове на масиви, align whitelist, quoted-identifier bypass, string_agg синоним, релевантен floor + fallback, exactly-once рендер на trap-овете през реалния write→read seam.
  • emit-report-schema.ts: caповете (MAX_BLOCKS/ITEMS/COLUMNS) с ранен изход преди сканиране са коректни; short-circuit семантиката е покрита от тест.
  • report-schema.ts: явното построяване на колоните (вместо { ...c }) затваря реален път за пропускане на непроверени model-полета към рендера — добра корекция.
  • rag.ts: разделянето на trap-ове (безусловно през hardTraps()) от RAG корпуса маха дублирането и гарантира, че RAG turn никога не е по-слабо ограничен от no-RAG fallback-а. Версионираният native namespace + MIN_SCHEMA_SCORE floor + score ?? 0 защитата са добре обосновани.
  • Консистентно именуване, споделеният renderTraps() премахва дрейфа между двата пътя.

CLAUDE.md съответствие

Без частични имплементации, без TODO-та, без дублиран код (renderTraps е извлечен точно за да няма дублиране), без dead code. Разделението на отговорности е спазено.

Наблюдения (незадължителни, не блокират)

  1. Deploy-предпоставка (главен операционен риск): след този деплой заявките ползват native namespace schema-v2, който е ПРАЗЕН докато indexSchemaCorpus не се пусне отново. Поведението е безопасно (fallback към пълния речник), но означава асистент без RAG grounding, докато ре-индексът не мине. Уверете се, че CD/runbook-ът гарантира ре-индекса — иначе тихо RAG-off. Документирано в README, но заслужава явна стъпка в деплой процедурата.
  2. sql-guard.ts denylist остава по същество „догонваща игра" срещу нови алиаси — самите автори го признават и трекват positive allowlist отделно. Одобрявам посоката; препоръчвам да се приоритизира allowlist-ът като трайно решение.

Няма намерени блокиращи дефекти. Препоръка: APPROVE след потвърждение, че деплой процедурата включва ре-индекса.

Comment thread apps/web/app/lib/assistant/rag.ts
Comment thread apps/web/app/lib/assistant/sql-guard.ts
nedda76 added a commit to nedda76/sigma that referenced this pull request Aug 21, 2026
An over-cap blocks/items/columns array is exactly the unbounded structure
the ceilings guard against, yet validateEmitShape recorded the length error
and then walked the whole array anyway — doing the very scan the cap exists
to refuse. Return before the per-block scan on oversized blocks, and skip the
per-element scan on oversized items/columns. Behaviour is unchanged for valid
reports (ok:false either way); this only stops the wasted walk. Test asserts a
single cap error with no per-element errors, proving the array is not scanned.

Addresses lyubomir-bozhinov's review note on PR midt-bg#223.
@nedda76
nedda76 force-pushed the fix/assistant-integrity-grounding branch from 1dacbdc to 40eaeea Compare August 21, 2026 10:51
nedda76 added a commit to nedda76/sigma that referenced this pull request Aug 24, 2026
An over-cap blocks/items/columns array is exactly the unbounded structure
the ceilings guard against, yet validateEmitShape recorded the length error
and then walked the whole array anyway — doing the very scan the cap exists
to refuse. Return before the per-block scan on oversized blocks, and skip the
per-element scan on oversized items/columns. Behaviour is unchanged for valid
reports (ok:false either way); this only stops the wasted walk. Test asserts a
single cap error with no per-element errors, proving the array is not scanned.

Addresses lyubomir-bozhinov's review note on PR midt-bg#223.
@nedda76
nedda76 force-pushed the fix/assistant-integrity-grounding branch from 40eaeea to 7f3376d Compare August 24, 2026 12:48
nedda76 added a commit to nedda76/sigma that referenced this pull request Aug 25, 2026
An over-cap blocks/items/columns array is exactly the unbounded structure
the ceilings guard against, yet validateEmitShape recorded the length error
and then walked the whole array anyway — doing the very scan the cap exists
to refuse. Return before the per-block scan on oversized blocks, and skip the
per-element scan on oversized items/columns. Behaviour is unchanged for valid
reports (ok:false either way); this only stops the wasted walk. Test asserts a
single cap error with no per-element errors, proving the array is not scanned.

Addresses lyubomir-bozhinov's review note on PR midt-bg#223.
@nedda76
nedda76 force-pushed the fix/assistant-integrity-grounding branch from 7f3376d to 1252f16 Compare August 25, 2026 18:29
nedda76 added 17 commits August 26, 2026 20:57
The curated dictionary the model treats as hard fact had drifted from
packages/db/migrations/0000_init.sql:
- amendments: no contract_id column — it links via unp/contract_number
- parties: no role column — real cols are party_key, eik, ocid, party_id, name…
- value_flag enum was missing value_low
- amount_eur IS NULL was described as meaning value_suspect; it actually has
  several causes (FX-rateless foreign / value_suspect w/o estimate / no
  signing+current), and the unconfirmed count is value_flag='value_suspect'
  (home_totals.suspect), not NULL-amount rows
- data_freshness is a table, not a view

Drift here misleads a weak model into wrong joins or a wrong integrity KPI.
…e retrieval

Two grounding gaps that could leave a RAG turn LESS constrained than the
no-RAG fallback:
- buildSystemPrompt used the retrieved chunks INSTEAD of the dictionary, so a
  retrieval that missed the money-sum trap dropped the SUM(amount_eur) rule
  entirely. Inject the short imperative DATA_TRAPS unconditionally; RAG now only
  selects the extra tables/example-queries for the question.
- retrieveSchemaContext had no relevance floor — top-K returned its K
  least-distant chunks even when all were off-topic. Add MIN_SCHEMA_SCORE; below
  it we return fewer/zero chunks, and zero falls back to the full dictionary
  (the safe outcome).
group_concat / json_group_array / json_group_object collapse an entire
full-table scan into one huge cell that materialises in Worker memory before
capRows can measure it (and capRows keeps the first row whole) — the same
memory-amplification class already blocked for printf/format/randomblob, one
level up. Add them to the scalar blocklist.
- Prose number-gate missed трилион/билион/квадрилион: '3 трилиона лева' slipped
  the whole gate (the digit can't reach 'лева' across the Cyrillic word), an
  unbound order-up figure on a public report — the '12 млрд.' vector one
  magnitude higher. Add them to the spelled-magnitude stem.
- Validate the optional column align against a left|right whitelist, and build
  resolved table columns explicitly instead of spreading the model object, so no
  unknown/unvalidated property reaches the renderer.
- Cap model-emitted array lengths (blocks, items, columns) in validateEmitShape.
…h prompt paths

renderTraps() now owns the numbered-list rendering that describeSchema (full
dictionary) and the RAG hard-traps block duplicated, so the two paths cannot
drift, and the full-dictionary heading is harmonised to match the RAG block
("Задължителни правила за данните"). No behaviour change — string assembly only.
retrieveSchemaContext relied on `m.score` always being numeric. If an index
backend ever returns a match without a `score`, the comparison was falsy and the
chunk was dropped — the correct, safe outcome, but only incidentally. Make it
explicit with `(m.score ?? 0) >= minScore` and a comment so a future refactor
can't strip the guard, and cover it with a test. Addresses the review note on
rag.ts robustness (ydimitrof).
…vers квинтилион+)

The prose-number gate listed magnitudes explicitly and stopped at квадрилион, so
"3 квинтилиона лева" slipped. Match the shared suffixes instead — милион⊃"илион",
милиард⊃"илиард" — which covers the whole family (милион…секстилион…, милиард…)
and closes the row upward for good rather than chasing an endless list. Addresses
the review note on report-schema.ts (ydimitrof).
… the SQL guard

string_agg(X, sep) is the official SQLite 3.44 synonym of group_concat and reaches
the same code path on D1's modern SQLite, so it bypassed the scalar/aggregate
denylist and achieved the same memory amplification (whole scan into one cell
before capRows) the guard just closed for group_concat. Add it to the regex and
the adversarial test. Addresses the review note on sql-guard.ts (ydimitrof).
An over-cap blocks/items/columns array is exactly the unbounded structure
the ceilings guard against, yet validateEmitShape recorded the length error
and then walked the whole array anyway — doing the very scan the cap exists
to refuse. Return before the per-block scan on oversized blocks, and skip the
per-element scan on oversized items/columns. Behaviour is unchanged for valid
reports (ok:false either way); this only stops the wasted walk. Test asserts a
single cap error with no per-element errors, proving the array is not scanned.

Addresses lyubomir-bozhinov's review note on PR midt-bg#223.
The Dependency-audit step (osv-scanner) fails on ANY known vuln and, per its
own comment, expects intentional exceptions in osv-scanner.toml — which did not
exist yet. sharp@0.34.5 (High, GHSA-f88m-g3jw-g9cj: inherited libvips decoder
CVEs) has no in-range upstream fix: miniflare pins sharp ^0.34.5 and its latest
release still ships 0.34.5, so 0.35.0 is unreachable without a forced override.
sharp is a transitive dev-only dep (miniflare dev server / test runtime), absent
from the deployed Worker, and the vuln needs decoding an untrusted image the
toolchain never handles. Record a dated (ignoreUntil 2026-10-22) exception so
the audit gate goes green and the entry auto-resurfaces for revisit.

Verified locally with osv-scanner 2.4.0: fails without the config, passes with.
…d RAG retrieval

DATA_TRAPS are injected into the system prompt unconditionally (hardTraps),
so indexing them in the schema corpus let retrieval hand the same rule back
as "context" and render it twice. Traps are no longer indexed, and
retrieveSchemaContext drops kind:'trap' matches a previously deployed index
may still hold. Retrieval's job stays selecting relevant tables/queries.
(review note, ydimitrof)
The sharp entry used a TOML local-date (2026-10-22) while the react-router
entry above uses full RFC3339; align on the latter so parser versions cannot
read the file inconsistently. (review note, ydimitrof)
…ace instead of a runtime trap filter

Self-review of the previous commit found the client-side kind:'trap' filter
ran AFTER Vectorize's server-side topK cut, so legacy trap vectors (12 of ~37
in a pre-change index, and the most money-question-similar text in the corpus)
could eat up to all six retrieval slots — leaving the turn with fewer
tables/queries than the no-RAG fallback, silently and permanently, since
upsert never deletes the stale ids.

Replaced with a versioned NATIVE namespace (SCHEMA_NS = 'schema-v2') on both
the upserted vectors and the query: native namespaces need no metadata index
and exclude every stale cohort at the source, so no topK slot is ever spent on
a discarded match and the filter is gone. The version is in the vector ids too,
so re-indexing writes a new cohort and a Worker rollback keeps working against
the old one. An un-reindexed environment gets zero matches → the documented
full-dictionary fallback.

Also from the self-review: the stale module header still said trap-rules are
embedded; system-prompt tests fed trap strings retrieval can no longer produce;
and no test entered through the composed seam — added a retrieveSchemaContext →
buildSystemPrompt test seeded with the real corpus asserting every DATA_TRAP
renders exactly once (negative-controlled: re-adding traps under a disguised
id/kind fails it and the new corpus-length assertion). README provisioning now
documents the re-index-on-bump requirement.
Gap-sweep on the namespace fix found the composed exactly-once test was
weaker than advertised: it hand-mirrored the write mapping instead of running
indexSchemaCorpus, sliced only the first topK chunks (so a trap appended at
the corpus tail escaped it), and hard-coded a 0.9 score silently coupled to
MIN_SCHEMA_SCORE. It now routes through the real write path into a recording
fake, retrieves the WHOLE corpus, and derives its score from the floor — so
the write→read metadata contract (text key, ids, namespace) is under test and
a trap re-added at any position under any id/kind fails it
(negative-controlled again with a tail-appended, table-kind trap).

Also: remaining fixtures moved off pre-v2 unversioned ids; the semanticSearch
test title no longer claims a namespace it does not use (it pins the entity
METADATA filter); the module header no longer claims the bindings satisfy the
structural types (the route casts — drift is not tsc-checked); the new-cohort
rollback guarantee is now correctly stated as bump-only, with an explicit
WHEN TO BUMP rule (in-place upserts, positional query ids, orphan risk); the
README no longer suggests purging a cohort inside its rollback window and
notes delete-vectors needs an explicit id list; dropped the stale '150 теста'
verification claim.
… величините

Единствените near-collisions на суфиксния шаблон са думи на -лион
(напр. „Илион") — приети съзнателно: gate-ът нарочно флагва в повече,
а в регистъра на поръчките такива думи почти не се срещат. Записан е
изходът при евентуални фалшиви отхвърляния: \p{L} lookaround граница
(JS \b е ASCII-only), а не списък с изключения. Изброяването на
-илиард величините е сведено до реалните форми на „милиард"
(бележка от ревюто).
… gate-а

„Дванадесет млрд. лева" нямаше нито цифра (за \d…млрд шаблона), нито
пълнословен суфикс — изписано числително + абревиатура се промъкваше
покрай целия gate. млрд/млн влизат в стем шаблона (флагват и без цифра;
негативен контрол: тестът пада без промяната). Остатъкът „хил." без
цифра остава приет — хилядите не са defamation-мащабният вектор
(бележка от ревюто на midt-bg#320).
…enylist

SQLite (D1) резолва "group_concat"(x), [group_concat](x) и
`group_concat`(x) до същия built-in, а регексът изискваше голо име
непосредствено пред скобата — цитиран идентификатор минаваше L1.
Опционален quote клас след името затваря и трите форми (adversarial
тестове; негативен контрол: падат без промяната). Идентификатор с
padding в кавичките е РАЗЛИЧЕН за SQLite и не резолва built-in — не
изисква обработка (бележка от ревюто).
@nedda76
nedda76 force-pushed the fix/assistant-integrity-grounding branch from 1252f16 to d344a8a Compare August 26, 2026 18:03
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.

3 participants